From 8dafcda902ffc9a314c15e4b6c8281a6b3bb2a41 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Sat, 3 Aug 2024 12:08:00 -0400 Subject: [PATCH 01/89] KDL3: fix invalid inno_setup components and deathlink messages (#2922) * remove component checking * fix missing deathlink messages * move reads under deathlink check Core: Fix OptionList and OptionSet to allow Iterable of Iterable (#2911) * fix, maybe * typegard for iterable of any * wow I'm so tired I just changed the method name without changing what it actually does... * also exclude bytes in is_iterable_but_str * apply pr comments * Update Utils.py Co-authored-by: Doug Hoskisson * Revert "also exclude bytes in is_iterable_but_str" This reverts commit cf087d2ee20727dbbe561c8c0f90aa85ef0a5d4b. --------- Co-authored-by: Doug Hoskisson Docs: Added snes9x-nwa as recommended emulator to the setup guides for SNES games (#1778) * Added snes9x-nwa as recommended emulator to the setup guides * Removed snes9x-nwa from the setup guides of DKC3 and SMW * Update worlds/alttp/docs/multiworld_en.md Co-authored-by: Aaron Wagener * Removed duplicate text Minor grammar and spelling fixes * Unified required software for SM, SMZ3 and SoE with ALTTP * Added instructions for usage of BSNES-Plus for ALTTP, SM and SMZ3 --------- Co-authored-by: Aaron Wagener KH2: Update all instances of multiworld.option_name to option.option_name (#2634) * update the multiworld to options * Update worlds/kh2/Rules.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * does this work * namine sketches * wrong branch :) --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> The Messenger: fix items accessibility reachability bug due to new rules (#2937) CI: Don't auto-remove content based labels (#2941) Docs: improve AutoWorld method docstrings (#2509) * clarify some autoworld docstrings * revert accidental change The Witness: Don't unnecessarily break people's 0.4.4 yamls (#2940) kvui: allow sorting hints in the hint tab (#2684) MultiServer: send new read_hints datastore values on change (#2558) The Witness: Obelisk Keys (#2805) Core: String comparison with FreeText class (#2942) CommonClient: use rich text for /received (#2715) CommonClient: Fix item link group name when member slot name contains brackets (#2794) SMW: v2.0 Content Update (#2762) Changelog: Features: - New optional Location Checks - 3-Up Moons - Hidden 1-Ups - Bonus Blocks - Blocksanity - All blocks that contain coins or items are included, with the exception of: - Blocks in Top Secret Area & Front Door/Bowser Castle - Blocks that are unreachable without glitches/unreasonable movement - New Items - Special Zone Clear - New Filler Items - 1 Coin - 5 Coins - 10 Coins - 50 Coins - New Trap Items - Reverse Trap - Thwimp Trap - SFX Shuffle - Palette Shuffle Overhaul - New Curated Palette can now be used for the Overworld and Level Palette Shuffle options - Foreground and Background Shuffle options have been merged into a single setting - Max possible Yoshi Egg value is 255 - UI in-game is updated to handle 3-digits - New `Display Received Item Popups` option: `progression_minus_yoshi_eggs` Quality of Life: - In-Game Indicators are now displayed on the map screen for location checks and received items - In-level sprites are displayed upon receiving certain items - The Camera Scroll unlocking is now only enabled on levels where it needs to be - SMW can now handle receiving more than 255 items - Significant World Code cleanup - New Options API - Removal of `world: MultiWorld` across the world - The PopTracker pack now has tabs for every level/sublevel, and can automatically swap tabs while playing if connected to the server Bug Fixes: - Several logic tweaks/fixes "Major credit to @TheLX5 for being the driving force for almost all of this update. We've been collaborating on design and polish of the features for the last few months, but all of the heavy lifting was all @TheLX5." Core: typing for `Option.default` and a few other ClassVars (#2899) * Core: typing for `Option.default` and a few other `Option` class variables This is a replacement for https://github.com/ArchipelagoMW/Archipelago/pull/2173 You can read discussion there for issues we found for why we can't have more specific typing on `default` instead of setting a default in `Option` (where we don't know the type), we check in the metaclass to make sure they have a default. * NumericOption doesn't need the type annotation that brings out the mypy bug * SoE default ClassVar Core: add list/dict merging feature to triggers (#2793) * proof of concept * add dict support, block top/game level merge * prevent key error when option being merged is new * update triggers guide * Add documentation about add/remove/replace * move to trailing name instead of proper tag * update docs * confirm types * Update Utils.py * Update Generate.py * pep8 * move to + syntax * forgot to support sets * specify received type of type error * Update Generate.py Co-authored-by: Fabian Dill * Apply suggestion from review * add test for update weights * move test to new test case * Apply suggestions from code review Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: Fabian Dill Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> CI: build: create setup (#2936) * CI: build: create setup also add /DNO_SIGNTOOL to inno_setup.iss * CI: build: trigger when changing setup-related files Shivers: Renaming for clarity and consistency (#2869) * Moves plaque location to front for better tracker referencing. * Tiki should be Shaman. * Hanging should be Gallows. * Merrick spelling. * Clarity change. FFMQ: Update Map Shuffle Seed description (#2658) * Update Map Shuffle Seed description * Update worlds/ffmq/Options.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Core: fix incorrect ordering on the always_allow static method (#2938) CI: update actions (#2943) HK: Removes Vanilla Items from ItemPool and Uses Grimmchild1 when relevant (#2898) KDL3: Ensure all abilities accessible on non-minimal (#2929) Pokemon Emerald: v2 Update (#2918) Core: add layer for patches that don't use `Patch.py` (#2889) * Core: add layer for patches that don't use `Patch.py` * bump container version * APAutoPatchInterface name * mystic quest change * OoT and Adventure changes * missed name in docstring * container version compatibility Lingo: Pre-compile datafile to improve loading time (#2829) CommonClient: Don't retry connection when connection details are invalid (#2831) Stardew Valley: 5.x.x - The Allsanity Update (#2764) Major Content update for Stardew Valley, including the following features - Major performance improvements all across the Stardew Valley apworld, including a significant reduction in the test time - Randomized Farm Type - Bundles rework (Remixed Bundles and Missing Bundle!) - New Settings: * Shipsanity - Shipping individual items * Monstersanity - Slaying monsters * Cooksanity - Cooking individual recipes * Chefsanity - Learning individual recipes * Craftsanity - Crafting individual items - New Goals: * Protector of the Valley - Complete every monster slayer goal * Full Shipment - Ship every item * Craftmaster - Craft every item * Gourmet Chef - Cook every recipe * Legend - Earn 10 000 000g * Mystery of the Stardrops - Find every stardrop (Maguffin Hunt) * Allsanity - Complete every check in your slot - Building Shuffle: Cheaper options - Tool Shuffle: Cheaper options - Money rework - New traps - New isolated checks and items, including the farm cave, the movie theater, etc - Mod Support: SVE [Albrekka] - Mod Support: Distant Lands [Albrekka] - Mod Support: Hat Mouse Lacey [Albrekka] - Mod Support: Boarding House [Albrekka] Co-authored-by: Witchybun Co-authored-by: Witchybun <96719127+Witchybun@users.noreply.github.com> Co-authored-by: Jouramie Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com> Launcher: make scrollbar more prominent (#2955) TUNIC: Updated display name for a few options (#2953) SMW: Add CHANGELOG.md (#2947) Celeste 64: Add CHANGELOG.md (#2948) DKC3: Add CHANGELOG.md (#2946) Core: increment version (#2958) SA2B: Add CHANGELOG.md (#2945) The Witness: Add newly submitted junk hints (#2949) OoT: Entrance Spoiler Fixes (#2500) Stardew Valley: Added a Great Combat requirement to an entrance that could block its own key (#2959) SC2: Multi-campaign (#2954) Adds HotS, LotV and NCO campaigns to SC2 game. The world's name has changed to reflect that (it's not only Wings of Liberty now) The client was patched in a way that can still join to games generated prior this change --------- Co-authored-by: Magnemania Co-authored-by: EnvyDragon <138727357+EnvyDragon@users.noreply.github.com> Co-authored-by: Matthew Co-authored-by: hopop201 Co-authored-by: Salzkorn Co-authored-by: genderdruid Co-authored-by: MadiMadsen <137329235+MadiMadsen@users.noreply.github.com> Co-authored-by: neocerber Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Fabian Dill Zork Grand Inquisitor: Implement New Game (#2539) Adds Archipelago support for Zork Grand Inquisitor, the 1997 point-and-click PC adventure game. The client (based on `CommonClient`), on top of its regular Archipelago duties, fully handles the randomization of the game and the monitoring / modification of the game state. No game modding needed at all; the player is ready to play an Archipelago seed if they can play the vanilla game through ScummVM. The "reverse engineering" (there's likely a better term for this...) of the game is my own original work and I included an MIT license at the root of my world directory. A PopTracker pack was also created to help people learn the game: https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker TUNIC: Implement support for connection plando (#2864) The Witness: Add junk hint for Zork: Grand Inquisitor (#2961) SMW: Increment Required Client Version (#2962) Core: implement APProcedurePatch and APTokenMixin (#2536) * initial work on procedure patch * more flexibility load default procedure for version 5 patches add args for procedure add default extension for tokens and bsdiff allow specifying additional required extensions for generation * pushing current changes to go fix tloz bug * move tokens into a separate inheritable class * forgot the commit to remove token from ProcedurePatch * further cleaning from bad commit * start on docstrings * further work on docstrings and typing * improve docstrings * fix incorrect docstring * cleanup * clean defaults and docstring * define interface that has only the bare minimum required for `Patch.create_rom_file` * change to dictionary.get * remove unnecessary if statement * update to explicitly check for procedure, restore compatible version and manual override * Update Files.py * remove struct uses * ensure returning bytes, add token type checking * Apply suggestions from code review Co-authored-by: Doug Hoskisson * pep8 --------- Co-authored-by: beauxq Co-authored-by: Doug Hoskisson Pokemon Emerald: Bump required client version (#2963) --- .github/pyright-config.json | 27 + .github/type_check.py | 15 + .github/workflows/analyze-modified-files.yml | 4 +- .github/workflows/build.yml | 82 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/ctest.yml | 54 + .github/workflows/label-pull-requests.yml | 2 +- .github/workflows/release.yml | 10 +- .github/workflows/strict-type-check.yml | 33 + .github/workflows/unittests.yml | 35 +- .gitignore | 4 +- AHITClient.py | 8 + AdventureClient.py | 10 +- BaseClasses.py | 295 +- CommonClient.py | 129 +- Fill.py | 115 +- Generate.py | 175 +- Launcher.py | 88 +- Main.py | 133 +- ModuleUpdate.py | 2 +- MultiServer.py | 346 +- NetUtils.py | 13 +- Options.py | 513 +- Patch.py | 4 +- README.md | 56 +- SNIClient.py | 21 +- Starcraft2Client.py | 2 +- UndertaleClient.py | 18 +- Utils.py | 121 +- Wargroove2Client.py | 11 + WargrooveClient.py | 6 +- WebHost.py | 18 +- WebHostLib/__init__.py | 4 +- WebHostLib/api/__init__.py | 52 +- WebHostLib/api/datapackage.py | 32 + WebHostLib/autolauncher.py | 120 +- WebHostLib/check.py | 7 +- WebHostLib/customserver.py | 275 +- WebHostLib/generate.py | 84 +- WebHostLib/misc.py | 79 +- WebHostLib/options.py | 431 +- WebHostLib/requirements.txt | 15 +- WebHostLib/robots.py | 15 + WebHostLib/static/assets/lttp-tracker.js | 20 - WebHostLib/static/assets/player-options.js | 523 -- WebHostLib/static/assets/playerOptions.js | 335 + .../{sc2wolTracker.js => sc2Tracker.js} | 10 +- WebHostLib/static/assets/supportedGames.js | 38 +- WebHostLib/static/assets/trackerCommon.js | 85 +- WebHostLib/static/assets/weighted-options.js | 1190 ---- WebHostLib/static/assets/weightedOptions.js | 223 + WebHostLib/static/robots_file.txt | 20 + .../static/icons/sc2/SC2_Lab_BioSteel_L1.png | Bin 5945 -> 0 bytes .../static/icons/sc2/SC2_Lab_BioSteel_L2.png | Bin 6699 -> 0 bytes .../static/icons/sc2/advanceballistics.png | Bin 11624 -> 0 bytes .../static/icons/sc2/autoturretblackops.png | Bin 8834 -> 0 bytes .../static/icons/sc2/biomechanicaldrone.png | Bin 6999 -> 0 bytes .../static/icons/sc2/burstcapacitors.png | Bin 2579 -> 0 bytes .../icons/sc2/crossspectrumdampeners.png | Bin 5344 -> 0 bytes .../static/static/icons/sc2/cyclone.png | Bin 8524 -> 0 bytes .../static/icons/sc2/cyclonerangeupgrade.png | Bin 12682 -> 0 bytes .../static/static/icons/sc2/drillingclaws.png | Bin 8451 -> 0 bytes .../static/icons/sc2/emergencythrusters.png | Bin 6796 -> 0 bytes .../static/icons/sc2/hellionbattlemode.png | Bin 8210 -> 0 bytes .../icons/sc2/high-explosive-spidermine.png | Bin 14334 -> 0 bytes .../static/icons/sc2/hyperflightrotors.png | Bin 14285 -> 0 bytes .../static/static/icons/sc2/hyperfluxor.png | Bin 9261 -> 0 bytes .../static/static/icons/sc2/impalerrounds.png | Bin 4347 -> 0 bytes .../static/icons/sc2/improvedburstlaser.png | Bin 11115 -> 0 bytes .../static/icons/sc2/improvedsiegemode.png | Bin 14293 -> 0 bytes .../static/icons/sc2/interferencematrix.png | Bin 10537 -> 0 bytes .../icons/sc2/internalizedtechmodule.png | Bin 16425 -> 0 bytes .../static/static/icons/sc2/jotunboosters.png | Bin 5740 -> 0 bytes .../static/static/icons/sc2/jumpjets.png | Bin 17881 -> 0 bytes .../static/icons/sc2/lasertargetingsystem.png | Bin 14802 -> 0 bytes .../static/static/icons/sc2/liberator.png | Bin 9012 -> 0 bytes .../static/static/icons/sc2/lockdown.png | Bin 8289 -> 0 bytes .../static/icons/sc2/magfieldaccelerator.png | Bin 11459 -> 0 bytes .../static/icons/sc2/magrailmunitions.png | Bin 19193 -> 0 bytes .../icons/sc2/medivacemergencythrusters.png | Bin 8954 -> 0 bytes .../icons/sc2/neosteelfortifiedarmor.png | Bin 13032 -> 0 bytes .../static/static/icons/sc2/opticalflare.png | Bin 11440 -> 0 bytes .../static/icons/sc2/optimizedlogistics.png | Bin 13116 -> 0 bytes .../static/icons/sc2/reapercombatdrugs.png | Bin 7102 -> 0 bytes .../static/static/icons/sc2/restoration.png | Bin 7754 -> 0 bytes .../static/icons/sc2/ripwavemissiles.png | Bin 13628 -> 0 bytes .../static/icons/sc2/shreddermissile.png | Bin 9827 -> 0 bytes .../icons/sc2/siegetank-spidermines.png | Bin 12764 -> 0 bytes .../static/icons/sc2/siegetankrange.png | Bin 11833 -> 0 bytes .../static/icons/sc2/specialordance.png | Bin 12992 -> 0 bytes .../static/static/icons/sc2/spidermine.png | Bin 3872 -> 0 bytes .../static/icons/sc2/staticempblast.png | Bin 12094 -> 0 bytes .../static/static/icons/sc2/superstimpack.png | Bin 14901 -> 0 bytes .../static/icons/sc2/targetingoptics.png | Bin 8431 -> 0 bytes .../static/icons/sc2/terran-cloak-color.png | Bin 8134 -> 0 bytes .../static/icons/sc2/terran-emp-color.png | Bin 7693 -> 0 bytes .../sc2/terrandefendermodestructureattack.png | Bin 14017 -> 0 bytes .../static/static/icons/sc2/thorsiegemode.png | Bin 11345 -> 0 bytes .../static/icons/sc2/transformationservos.png | Bin 9215 -> 0 bytes .../static/static/icons/sc2/valkyrie.png | Bin 7490 -> 0 bytes .../static/static/icons/sc2/warpjump.png | Bin 8665 -> 0 bytes .../icons/sc2/widowmine-attackrange.png | Bin 13367 -> 0 bytes .../icons/sc2/widowmine-deathblossom.png | Bin 12946 -> 0 bytes .../static/static/icons/sc2/widowmine.png | Bin 5671 -> 0 bytes .../static/icons/sc2/widowminehidden.png | Bin 12777 -> 0 bytes WebHostLib/static/styles/globalStyles.css | 10 +- WebHostLib/static/styles/lttp-tracker.css | 75 - WebHostLib/static/styles/markdown.css | 14 +- WebHostLib/static/styles/player-options.css | 244 - .../styles/playerOptions/playerOptions.css | 310 + .../playerOptions/playerOptions.css.map | 1 + .../styles/playerOptions/playerOptions.scss | 364 ++ WebHostLib/static/styles/sc2Tracker.css | 160 + WebHostLib/static/styles/sc2wolTracker.css | 112 - WebHostLib/static/styles/supportedGames.css | 21 +- WebHostLib/static/styles/tooltip.css | 55 +- .../static/styles/tracker__ALinkToThePast.css | 142 + WebHostLib/static/styles/weighted-options.css | 315 - .../weightedOptions/weightedOptions.css | 232 + .../weightedOptions/weightedOptions.css.map | 1 + .../weightedOptions/weightedOptions.scss | 274 + WebHostLib/templates/hostRoom.html | 96 +- WebHostLib/templates/lttpTracker.html | 86 - WebHostLib/templates/macros.html | 3 - WebHostLib/templates/multispheretracker.html | 72 + WebHostLib/templates/multitracker.html | 2 +- .../multitracker__ALinkToThePast.html | 355 +- WebHostLib/templates/ootTracker.html | 180 - WebHostLib/templates/player-options.html | 62 - .../templates/playerOptions/macros.html | 221 + .../playerOptions/playerOptions.html | 166 + WebHostLib/templates/siteMap.html | 9 +- WebHostLib/templates/startPlaying.html | 2 +- WebHostLib/templates/supportedGames.html | 14 +- .../templates/tracker__ALinkToThePast.html | 335 +- WebHostLib/templates/tracker__Starcraft2.html | 1092 ++++ .../tracker__Starcraft2WingsOfLiberty.html | 366 -- WebHostLib/templates/userContent.html | 4 + WebHostLib/templates/weighted-options.html | 48 - .../templates/weightedOptions/macros.html | 264 + .../weightedOptions/weightedOptions.html | 119 + WebHostLib/tracker.py | 1903 ++++-- WebHostLib/upload.py | 35 +- Zelda1Client.py | 4 +- _speedups.pyx | 48 +- _speedups.pyxbld | 8 +- data/basepatch.bsdiff4 | Bin 114116 -> 114119 bytes data/client.kv | 7 + data/lua/connector_mmbn3.lua | 274 +- data/options.yaml | 14 +- data/yatta.ico | Bin 0 -> 152484 bytes data/yatta.png | Bin 0 -> 34873 bytes docs/CODEOWNERS | 90 +- docs/adding games.md | 347 +- docs/apworld_dev_faq.md | 45 + docs/contributing.md | 56 +- docs/img/creepy-castle-directory.png | Bin 35874 -> 0 bytes docs/img/gato-roboto-directory.png | Bin 81020 -> 0 bytes docs/img/heavy-bullets-data-directory.png | Bin 56842 -> 0 bytes docs/img/heavy-bullets-directory.png | Bin 38535 -> 0 bytes docs/img/stardew-valley-directory.png | Bin 66397 -> 0 bytes docs/network protocol.md | 61 +- docs/options api.md | 111 +- docs/running from source.md | 3 +- docs/settings api.md | 2 +- docs/style.md | 9 + docs/webhost configuration sample.yaml | 13 +- docs/world api.md | 125 +- inno_setup.iss | 49 +- intset.h | 135 + kvui.py | 188 +- playerSettings.yaml | 591 -- requirements.txt | 14 +- settings.py | 43 +- setup.py | 9 +- test/bases.py | 10 +- test/cpp/CMakeLists.txt | 49 + test/cpp/README.md | 32 + test/cpp/intset/CMakeLists.txt | 4 + test/cpp/intset/test_intset.cpp | 105 + test/general/__init__.py | 71 +- .../general/test_client_server_interaction.py | 23 + test/general/test_fill.py | 154 +- test/general/test_host_yaml.py | 81 +- test/general/test_ids.py | 18 +- test/general/test_implemented.py | 5 + test/general/test_items.py | 15 +- test/general/test_locations.py | 9 - test/general/test_options.py | 42 + test/general/test_player_options.py | 39 + test/general/test_reachability.py | 6 +- .../test/checks => test/hosting}/__init__.py | 0 test/hosting/__main__.py | 191 + test/hosting/client.py | 110 + test/hosting/generate.py | 76 + test/hosting/serve.py | 115 + test/hosting/webhost.py | 208 + test/hosting/world.py | 42 + test/multiworld/test_multiworlds.py | 2 +- test/netutils/test_location_store.py | 33 +- test/options/__init__.py | 0 test/options/test_option_classes.py | 67 + test/programs/test_common_client.py | 106 + test/programs/test_generate.py | 7 +- test/webhost/__init__.py | 36 + test/webhost/test_api_generate.py | 25 +- test/webhost/test_descriptions.py | 23 + test/webhost/test_host_room.py | 192 + test/webhost/test_option_presets.py | 4 +- typings/kivy/core/window.pyi | 15 + typings/kivy/event.pyi | 2 + typings/kivy/uix/boxlayout.pyi | 6 + typings/kivy/uix/layout.pyi | 8 +- typings/kivy/uix/widget.pyi | 2 +- typings/schema/__init__.pyi | 17 + worlds/AutoWorld.py | 201 +- worlds/Files.py | 319 +- worlds/LauncherComponents.py | 87 +- worlds/__init__.py | 27 +- worlds/_bizhawk/__init__.py | 2 +- worlds/_bizhawk/client.py | 14 +- worlds/_bizhawk/context.py | 19 +- worlds/_sc2common/bot/bot_ai.py | 3 + worlds/_sc2common/bot/sc2process.py | 7 +- worlds/adventure/Locations.py | 28 +- worlds/adventure/Options.py | 39 +- worlds/adventure/Regions.py | 7 +- worlds/adventure/Rom.py | 4 +- worlds/adventure/Rules.py | 8 +- worlds/adventure/__init__.py | 59 +- worlds/adventure/docs/en_Adventure.md | 10 +- worlds/adventure/docs/setup_en.md | 4 +- worlds/adventure/docs/setup_fr.md | 4 +- worlds/ahit/Client.py | 232 + worlds/ahit/DeathWishLocations.py | 243 + worlds/ahit/DeathWishRules.py | 462 ++ worlds/ahit/Items.py | 302 + worlds/ahit/Locations.py | 1057 +++ worlds/ahit/Options.py | 770 +++ worlds/ahit/Regions.py | 1036 +++ worlds/ahit/Rules.py | 958 +++ worlds/ahit/Types.py | 86 + worlds/ahit/__init__.py | 386 ++ worlds/ahit/docs/en_A Hat in Time.md | 53 + worlds/ahit/docs/setup_en.md | 65 + worlds/ahit/test/__init__.py | 5 + worlds/ahit/test/test_acts.py | 31 + worlds/alttp/Client.py | 8 +- worlds/alttp/Dungeons.py | 2 +- worlds/alttp/EntranceRandomizer.py | 224 +- worlds/alttp/EntranceShuffle.py | 33 +- worlds/alttp/InvertedRegions.py | 12 +- worlds/alttp/ItemPool.py | 115 +- worlds/alttp/Options.py | 40 +- worlds/alttp/OverworldGlitchRules.py | 19 - worlds/alttp/Regions.py | 10 +- worlds/alttp/Rom.py | 109 +- worlds/alttp/Rules.py | 584 +- worlds/alttp/Shops.py | 10 +- worlds/alttp/StateHelpers.py | 19 +- worlds/alttp/SubClasses.py | 4 - worlds/alttp/Text.py | 409 ++ worlds/alttp/UnderworldGlitchRules.py | 13 +- worlds/alttp/__init__.py | 120 +- worlds/alttp/docs/en_A Link to the Past.md | 4 +- worlds/alttp/docs/multiworld_de.md | 4 +- worlds/alttp/docs/multiworld_en.md | 19 +- worlds/alttp/docs/multiworld_es.md | 7 +- worlds/alttp/docs/multiworld_fr.md | 13 +- worlds/alttp/test/__init__.py | 5 + worlds/alttp/test/dungeons/TestDungeon.py | 6 +- worlds/alttp/test/dungeons/TestGanonsTower.py | 12 +- worlds/alttp/test/dungeons/TestThievesTown.py | 3 +- worlds/alttp/test/dungeons/TestTowerOfHera.py | 10 +- worlds/alttp/test/inverted/TestInverted.py | 9 +- .../test/inverted/TestInvertedBombRules.py | 2 +- .../TestInvertedMinor.py | 7 +- .../test/inverted_owg/TestDeathMountain.py | 8 +- .../alttp/test/inverted_owg/TestDungeons.py | 3 +- .../test/inverted_owg/TestInvertedOWG.py | 7 +- worlds/alttp/test/minor_glitches/TestMinor.py | 5 +- worlds/alttp/test/owg/TestDeathMountain.py | 8 +- worlds/alttp/test/owg/TestDungeons.py | 8 +- worlds/alttp/test/owg/TestVanillaOWG.py | 5 +- worlds/alttp/test/vanilla/TestVanilla.py | 5 +- worlds/apsudoku/__init__.py | 34 + worlds/apsudoku/docs/en_Sudoku.md | 13 + worlds/apsudoku/docs/setup_en.md | 37 + worlds/aquaria/Items.py | 214 + worlds/aquaria/Locations.py | 575 ++ worlds/aquaria/Options.py | 153 + worlds/aquaria/Regions.py | 1391 ++++ worlds/aquaria/__init__.py | 215 + worlds/aquaria/docs/en_Aquaria.md | 64 + worlds/aquaria/docs/fr_Aquaria.md | 65 + worlds/aquaria/docs/setup_en.md | 115 + worlds/aquaria/docs/setup_fr.md | 118 + worlds/aquaria/test/__init__.py | 218 + worlds/aquaria/test/test_beast_form_access.py | 48 + worlds/aquaria/test/test_bind_song_access.py | 36 + .../test/test_bind_song_option_access.py | 42 + .../aquaria/test/test_confined_home_water.py | 20 + worlds/aquaria/test/test_dual_song_access.py | 26 + .../aquaria/test/test_energy_form_access.py | 72 + worlds/aquaria/test/test_fish_form_access.py | 37 + worlds/aquaria/test/test_li_song_access.py | 45 + worlds/aquaria/test/test_light_access.py | 71 + .../aquaria/test/test_nature_form_access.py | 57 + ...st_no_progression_hard_hidden_locations.py | 60 + .../test_progression_hard_hidden_locations.py | 52 + .../aquaria/test/test_spirit_form_access.py | 36 + worlds/aquaria/test/test_sun_form_access.py | 28 + .../test_unconfine_home_water_via_both.py | 21 + ...st_unconfine_home_water_via_energy_door.py | 20 + ...st_unconfine_home_water_via_transturtle.py | 20 + worlds/archipidle/Items.py | 7 +- worlds/archipidle/Rules.py | 30 +- worlds/archipidle/__init__.py | 45 +- worlds/archipidle/docs/en_ArchipIDLE.md | 4 +- worlds/archipidle/docs/guide_en.md | 4 +- worlds/archipidle/docs/guide_fr.md | 7 +- worlds/bk_sudoku/__init__.py | 44 - worlds/bk_sudoku/docs/de_Sudoku.md | 21 - worlds/bk_sudoku/docs/en_Sudoku.md | 13 - worlds/bk_sudoku/docs/setup_de.md | 27 - worlds/bk_sudoku/docs/setup_en.md | 24 - worlds/blasphemous/__init__.py | 1 - worlds/blasphemous/docs/en_Blasphemous.md | 4 +- worlds/bomb_rush_cyberfunk/Items.py | 553 ++ worlds/bomb_rush_cyberfunk/Locations.py | 785 +++ worlds/bomb_rush_cyberfunk/Options.py | 200 + worlds/bomb_rush_cyberfunk/Regions.py | 103 + worlds/bomb_rush_cyberfunk/Rules.py | 1041 +++ worlds/bomb_rush_cyberfunk/__init__.py | 203 + .../docs/en_Bomb Rush Cyberfunk.md | 29 + worlds/bomb_rush_cyberfunk/docs/setup_en.md | 41 + worlds/bomb_rush_cyberfunk/test/__init__.py | 5 + .../test/test_graffiti_spots.py | 284 + .../bomb_rush_cyberfunk/test/test_options.py | 29 + .../test/test_rep_items.py | 45 + worlds/bumpstik/Options.py | 22 +- worlds/bumpstik/Regions.py | 10 +- worlds/bumpstik/__init__.py | 23 +- worlds/bumpstik/docs/en_Bumper Stickers.md | 4 +- worlds/celeste64/CHANGELOG.md | 53 + worlds/celeste64/Items.py | 60 +- worlds/celeste64/Locations.py | 182 +- worlds/celeste64/Names/ItemName.py | 6 + worlds/celeste64/Names/LocationName.py | 22 + worlds/celeste64/Options.py | 143 +- worlds/celeste64/Rules.py | 429 +- worlds/celeste64/__init__.py | 101 +- worlds/celeste64/docs/guide_en.md | 5 + worlds/checksfinder/Locations.py | 4 - worlds/checksfinder/__init__.py | 2 - worlds/checksfinder/docs/en_ChecksFinder.md | 4 +- worlds/checksfinder/docs/setup_en.md | 2 +- worlds/clique/__init__.py | 1 - worlds/clique/docs/en_Clique.md | 4 +- worlds/cv64/__init__.py | 317 + worlds/cv64/aesthetics.py | 653 ++ worlds/cv64/client.py | 207 + worlds/cv64/data/APLogo-LICENSE.txt | 3 + worlds/cv64/data/ap_icons.bin | Bin 0 -> 4666 bytes worlds/cv64/data/ename.py | 71 + worlds/cv64/data/iname.py | 49 + worlds/cv64/data/lname.py | 479 ++ worlds/cv64/data/patches.py | 2878 +++++++++ worlds/cv64/data/rname.py | 63 + worlds/cv64/docs/en_Castlevania 64.md | 148 + worlds/cv64/docs/obscure_checks.md | 429 ++ worlds/cv64/docs/setup_en.md | 63 + worlds/cv64/entrances.py | 149 + worlds/cv64/items.py | 214 + worlds/cv64/locations.py | 699 ++ worlds/cv64/lzkn64.py | 266 + worlds/cv64/options.py | 591 ++ worlds/cv64/regions.py | 517 ++ worlds/cv64/rom.py | 1026 +++ worlds/cv64/rules.py | 103 + worlds/cv64/src/drop_sub_weapon.c | 69 + worlds/cv64/src/print.c | 116 + worlds/cv64/src/print_text_ovl.c | 26 + worlds/cv64/stages.py | 490 ++ worlds/cv64/test/__init__.py | 6 + worlds/cv64/test/test_access.py | 250 + worlds/cv64/text.py | 98 + worlds/dark_souls_3/Items.py | 6 +- worlds/dark_souls_3/__init__.py | 5 +- worlds/dark_souls_3/docs/en_Dark Souls III.md | 6 +- worlds/dark_souls_3/docs/setup_en.md | 35 +- worlds/dark_souls_3/docs/setup_fr.md | 2 +- worlds/dkc3/CHANGELOG.md | 47 + worlds/dkc3/Client.py | 8 +- worlds/dkc3/Names/LocationName.py | 2 +- worlds/dkc3/Options.py | 31 +- worlds/dkc3/Rom.py | 4 +- worlds/dkc3/__init__.py | 29 +- worlds/dkc3/docs/en_Donkey Kong Country 3.md | 4 +- worlds/dkc3/docs/setup_en.md | 8 +- worlds/dlcquest/__init__.py | 8 +- worlds/dlcquest/docs/en_DLCQuest.md | 4 +- worlds/dlcquest/docs/fr_DLCQuest.md | 4 +- worlds/dlcquest/docs/setup_en.md | 2 +- worlds/dlcquest/docs/setup_fr.md | 4 +- worlds/dlcquest/option_groups.py | 27 + worlds/dlcquest/presets.py | 68 + worlds/dlcquest/test/checks/world_checks.py | 4 +- worlds/doom_1993/Options.py | 42 +- worlds/doom_1993/Rules.py | 323 +- worlds/doom_1993/__init__.py | 35 +- worlds/doom_1993/docs/en_DOOM 1993.md | 4 +- worlds/doom_1993/docs/setup_en.md | 2 +- worlds/doom_ii/Rules.py | 248 +- worlds/doom_ii/__init__.py | 19 +- worlds/doom_ii/docs/en_DOOM II.md | 4 +- worlds/doom_ii/docs/setup_en.md | 2 +- worlds/factorio/Client.py | 10 +- worlds/factorio/__init__.py | 1 - worlds/factorio/data/mod_template/control.lua | 9 +- worlds/factorio/docs/en_Factorio.md | 6 +- worlds/factorio/docs/setup_en.md | 14 +- worlds/factorio/requirements.txt | 2 +- worlds/ff1/Options.py | 14 +- worlds/ff1/__init__.py | 24 +- worlds/ff1/docs/en_Final Fantasy.md | 6 +- worlds/ffmq/Client.py | 2 +- worlds/ffmq/Items.py | 20 +- worlds/ffmq/Options.py | 73 +- worlds/ffmq/Output.py | 80 +- worlds/ffmq/Regions.py | 47 +- worlds/ffmq/__init__.py | 80 +- worlds/ffmq/data/entrances.yaml | 2450 ------- worlds/ffmq/data/rooms.py | 2 + worlds/ffmq/data/rooms.yaml | 4026 ------------ .../docs/en_Final Fantasy Mystic Quest.md | 7 +- .../docs/fr_Final Fantasy Mystic Quest.md | 36 + worlds/ffmq/docs/setup_en.md | 31 +- worlds/ffmq/docs/setup_fr.md | 178 + worlds/generic/Rules.py | 36 +- worlds/generic/__init__.py | 7 - worlds/generic/docs/advanced_settings_en.md | 53 +- worlds/generic/docs/commands_en.md | 2 + worlds/generic/docs/plando_en.md | 2 +- worlds/generic/docs/setup_en.md | 22 +- worlds/generic/docs/triggers_en.md | 42 +- worlds/heretic/Locations.py | 22 +- worlds/heretic/Options.py | 43 +- worlds/heretic/Regions.py | 19 +- worlds/heretic/Rules.py | 440 +- worlds/heretic/__init__.py | 44 +- worlds/heretic/docs/en_Heretic.md | 4 +- worlds/heretic/docs/setup_en.md | 2 +- worlds/hk/GodhomeData.py | 55 + worlds/hk/Items.py | 6 + worlds/hk/Options.py | 74 +- worlds/hk/Rules.py | 41 + worlds/hk/__init__.py | 205 +- worlds/hk/docs/en_Hollow Knight.md | 10 +- worlds/hk/docs/setup_en.md | 46 +- worlds/hylics2/Options.py | 80 +- worlds/hylics2/Rules.py | 33 +- worlds/hylics2/__init__.py | 135 +- worlds/hylics2/docs/en_Hylics 2.md | 4 +- worlds/kdl3/Client.py | 28 +- worlds/kdl3/Options.py | 7 +- worlds/kdl3/Regions.py | 109 +- worlds/kdl3/Rules.py | 2 +- worlds/kdl3/__init__.py | 16 + worlds/kdl3/docs/en_Kirby's Dream Land 3.md | 5 +- worlds/kdl3/docs/setup_en.md | 6 +- worlds/kdl3/test/__init__.py | 3 +- worlds/kdl3/test/test_locations.py | 9 +- worlds/kh2/Client.py | 67 +- worlds/kh2/OpenKH.py | 53 +- worlds/kh2/Options.py | 6 +- worlds/kh2/Regions.py | 10 +- worlds/kh2/Rules.py | 41 +- worlds/kh2/__init__.py | 34 +- worlds/kh2/docs/en_Kingdom Hearts 2.md | 4 +- worlds/kh2/docs/setup_en.md | 18 +- worlds/ladx/LADXR/assembler.py | 2 +- worlds/ladx/LADXR/generator.py | 152 +- worlds/ladx/LADXR/locations/droppedKey.py | 2 +- worlds/ladx/LADXR/locations/startItem.py | 1 - .../ladx/LADXR/patches/bank3e.asm/chest.asm | 9 +- worlds/ladx/LADXR/patches/core.py | 102 +- worlds/ladx/Locations.py | 8 +- worlds/ladx/Options.py | 194 +- worlds/ladx/Rom.py | 4 +- worlds/ladx/Tracker.py | 2 +- worlds/ladx/__init__.py | 108 +- worlds/ladx/docs/en_Links Awakening DX.md | 6 +- worlds/ladx/docs/setup_en.md | 6 +- worlds/landstalker/Hints.py | 5 +- worlds/landstalker/Locations.py | 15 +- worlds/landstalker/Regions.py | 2 +- worlds/landstalker/Rules.py | 2 +- worlds/landstalker/__init__.py | 3 + ...andstalker - The Treasures of King Nole.md | 8 +- .../landstalker/docs/landstalker_setup_en.md | 6 +- worlds/lingo/__init__.py | 95 +- worlds/lingo/data/LL1.yaml | 1529 ++++- worlds/lingo/data/README.md | 5 + worlds/lingo/data/generated.dat | Bin 0 -> 149055 bytes worlds/lingo/data/ids.yaml | 187 +- worlds/lingo/datatypes.py | 95 + worlds/lingo/docs/en_Lingo.md | 4 +- worlds/lingo/items.py | 94 +- worlds/lingo/locations.py | 25 +- worlds/lingo/options.py | 204 +- worlds/lingo/player_logic.py | 280 +- worlds/lingo/regions.py | 145 +- worlds/lingo/rules.py | 77 +- worlds/lingo/static_logic.py | 561 +- worlds/lingo/test/TestDatafile.py | 16 + worlds/lingo/test/TestDoors.py | 56 +- worlds/lingo/test/TestMastery.py | 23 +- worlds/lingo/test/TestOptions.py | 37 +- worlds/lingo/test/TestOrangeTower.py | 2 +- worlds/lingo/test/TestPanelsanity.py | 2 +- worlds/lingo/test/TestPilgrimage.py | 138 + worlds/lingo/test/TestPostgame.py | 62 + worlds/lingo/test/TestProgressive.py | 7 +- worlds/lingo/test/TestSunwarps.py | 220 + worlds/lingo/utils/__init__.py | 0 worlds/lingo/utils/assign_ids.rb | 40 + worlds/lingo/utils/pickle_static_data.py | 609 ++ worlds/lingo/utils/validate_config.rb | 141 +- worlds/lufia2ac/Client.py | 13 +- worlds/lufia2ac/Options.py | 17 +- worlds/lufia2ac/__init__.py | 2 +- worlds/lufia2ac/basepatch/basepatch.asm | 91 +- worlds/lufia2ac/basepatch/basepatch.bsdiff4 | Bin 8652 -> 8865 bytes .../lufia2ac/docs/en_Lufia II Ancient Cave.md | 9 +- worlds/lufia2ac/docs/setup_en.md | 6 +- worlds/meritous/Locations.py | 5 - worlds/meritous/Options.py | 18 +- worlds/meritous/Regions.py | 22 +- worlds/meritous/__init__.py | 21 +- worlds/meritous/docs/en_Meritous.md | 4 +- worlds/meritous/docs/setup_en.md | 4 +- worlds/messenger/__init__.py | 50 +- worlds/messenger/connections.py | 18 +- worlds/messenger/constants.py | 7 + worlds/messenger/docs/en_The Messenger.md | 29 +- worlds/messenger/docs/setup_en.md | 14 +- worlds/messenger/options.py | 51 +- worlds/messenger/portals.py | 84 +- worlds/messenger/rules.py | 8 +- worlds/messenger/test/test_access.py | 9 +- worlds/messenger/test/test_portals.py | 4 + worlds/minecraft/Options.py | 15 +- worlds/minecraft/__init__.py | 2 - worlds/minecraft/docs/en_Minecraft.md | 4 +- worlds/minecraft/docs/minecraft_en.md | 2 +- worlds/minecraft/docs/minecraft_es.md | 2 +- worlds/minecraft/docs/minecraft_fr.md | 4 +- worlds/minecraft/docs/minecraft_sv.md | 4 +- worlds/mlss/Client.py | 284 + worlds/mlss/Data.py | 5705 +++++++++++++++++ worlds/mlss/Items.py | 190 + worlds/mlss/Locations.py | 1185 ++++ worlds/mlss/Names/LocationName.py | 559 ++ worlds/mlss/Options.py | 299 + worlds/mlss/Regions.py | 320 + worlds/mlss/Rom.py | 433 ++ worlds/mlss/Rules.py | 571 ++ worlds/mlss/StateLogic.py | 155 + worlds/mlss/__init__.py | 183 + worlds/mlss/data/basepatch.bsdiff | Bin 0 -> 17596 bytes .../docs/en_Mario & Luigi Superstar Saga.md | 66 + worlds/mlss/docs/setup_en.md | 53 + worlds/mmbn3/Options.py | 14 +- worlds/mmbn3/__init__.py | 20 +- .../mmbn3/docs/en_MegaMan Battle Network 3.md | 4 +- worlds/mmbn3/docs/setup_en.md | 21 +- worlds/musedash/MuseDashCollection.py | 46 +- worlds/musedash/MuseDashData.txt | 35 +- worlds/musedash/Options.py | 124 +- worlds/musedash/Presets.py | 9 +- worlds/musedash/__init__.py | 139 +- worlds/musedash/docs/en_Muse Dash.md | 4 +- worlds/musedash/docs/setup_en.md | 11 +- worlds/musedash/docs/setup_es.md | 11 +- worlds/musedash/test/TestCollection.py | 17 +- worlds/musedash/test/TestDifficultyRanges.py | 36 +- worlds/musedash/test/TestPlandoSettings.py | 39 +- worlds/musedash/test/TestTrapOption.py | 33 + worlds/musedash/test/TestWorstCaseSettings.py | 9 +- worlds/musedash/test/__init__.py | 9 +- worlds/noita/__init__.py | 5 +- worlds/noita/docs/en_Noita.md | 12 +- worlds/noita/docs/setup_en.md | 2 +- worlds/noita/locations.py | 138 +- worlds/noita/options.py | 60 +- worlds/noita/regions.py | 18 +- worlds/noita/rules.py | 4 +- worlds/oot/Location.py | 5 +- worlds/oot/Options.py | 9 +- worlds/oot/Patches.py | 4 +- worlds/oot/__init__.py | 98 +- worlds/oot/docs/en_Ocarina of Time.md | 6 +- worlds/oot/docs/setup_en.md | 4 +- worlds/oot/docs/setup_fr.md | 4 +- worlds/overcooked2/__init__.py | 5 - worlds/overcooked2/docs/en_Overcooked! 2.md | 4 +- worlds/overcooked2/docs/setup_en.md | 10 +- worlds/overcooked2/test/TestOvercooked2.py | 9 +- worlds/pokemon_emerald/CHANGELOG.md | 223 + worlds/pokemon_emerald/__init__.py | 844 +-- worlds/pokemon_emerald/client.py | 557 +- worlds/pokemon_emerald/data.py | 1470 +++-- .../pokemon_emerald/data/base_patch.bsdiff4 | Bin 209743 -> 243175 bytes .../pokemon_emerald/data/extracted_data.json | 2 +- worlds/pokemon_emerald/data/items.json | 1016 +-- worlds/pokemon_emerald/data/locations.json | 4057 +++++++++++- .../data/regions/battle_frontier.json | 458 ++ .../pokemon_emerald/data/regions/cities.json | 1419 +++- .../data/regions/dungeons.json | 1040 ++- .../data/regions/{unused => }/islands.json | 120 +- .../pokemon_emerald/data/regions/routes.json | 1699 ++++- .../data/regions/unused/battle_frontier.json | 368 +- .../data/regions/unused/dungeons.json | 50 +- .../data/regions/unused/routes.json | 82 - .../data/trade_pokemon_schema.json | 162 + .../docs/en_Pokemon Emerald.md | 26 +- .../{data/README.md => docs/region data.md} | 38 +- worlds/pokemon_emerald/docs/rom changes.md | 36 +- worlds/pokemon_emerald/docs/setup_en.md | 4 +- worlds/pokemon_emerald/docs/setup_es.md | 38 +- worlds/pokemon_emerald/docs/setup_sv.md | 78 + .../{README.md => docs/warps.md} | 10 +- worlds/pokemon_emerald/docs/wonder trades.md | 103 + worlds/pokemon_emerald/items.py | 6 +- worlds/pokemon_emerald/locations.py | 188 +- worlds/pokemon_emerald/opponents.py | 116 + worlds/pokemon_emerald/options.py | 595 +- worlds/pokemon_emerald/pokemon.py | 662 +- worlds/pokemon_emerald/regions.py | 80 +- worlds/pokemon_emerald/rom.py | 867 ++- worlds/pokemon_emerald/rules.py | 1030 ++- worlds/pokemon_emerald/sanity_check.py | 145 +- .../test/test_accessibility.py | 63 +- worlds/pokemon_emerald/util.py | 340 +- worlds/pokemon_rb/__init__.py | 109 +- worlds/pokemon_rb/client.py | 4 +- .../docs/en_Pokemon Red and Blue.md | 10 +- worlds/pokemon_rb/docs/setup_en.md | 10 +- worlds/pokemon_rb/docs/setup_es.md | 6 +- worlds/pokemon_rb/encounters.py | 2 - worlds/pokemon_rb/items.py | 10 +- worlds/pokemon_rb/locations.py | 6 +- worlds/pokemon_rb/options.py | 5 +- worlds/pokemon_rb/pokemon.py | 101 +- worlds/pokemon_rb/regions.py | 5 +- worlds/pokemon_rb/rules.py | 2 +- worlds/raft/Options.py | 45 +- worlds/raft/Rules.py | 4 +- worlds/raft/__init__.py | 49 +- worlds/raft/docs/en_Raft.md | 4 +- worlds/raft/docs/setup_en.md | 2 +- worlds/rogue_legacy/__init__.py | 1 - worlds/rogue_legacy/docs/en_Rogue Legacy.md | 6 +- worlds/rogue_legacy/docs/rogue-legacy_en.md | 2 +- worlds/rogue_legacy/test/TestUnique.py | 4 +- worlds/ror2/__init__.py | 30 +- worlds/ror2/docs/en_Risk of Rain 2.md | 17 +- worlds/ror2/docs/setup_en.md | 2 +- worlds/ror2/items.py | 2 +- worlds/ror2/options.py | 59 +- worlds/ror2/regions.py | 4 +- worlds/ror2/ror2environments.py | 1 + worlds/ror2/rules.py | 23 +- worlds/ror2/test/test_mithrix_goal.py | 4 +- worlds/sa2b/CHANGELOG.md | 247 + worlds/sa2b/Options.py | 354 +- worlds/sa2b/__init__.py | 23 +- .../sa2b/docs/en_Sonic Adventure 2 Battle.md | 4 +- worlds/sa2b/docs/setup_en.md | 48 +- worlds/sc2/Client.py | 1630 +++++ worlds/sc2/ClientGui.py | 304 + worlds/sc2/ItemGroups.py | 100 + worlds/sc2/ItemNames.py | 661 ++ worlds/sc2/Items.py | 2553 ++++++++ worlds/sc2/Locations.py | 1638 +++++ worlds/sc2/MissionTables.py | 736 +++ worlds/sc2/Options.py | 908 +++ worlds/sc2/PoolFilter.py | 661 ++ worlds/sc2/Regions.py | 691 ++ worlds/sc2/Rules.py | 952 +++ worlds/sc2/Starcraft2.kv | 28 + worlds/sc2/__init__.py | 490 ++ worlds/sc2/docs/contributors.md | 42 + worlds/sc2/docs/en_Starcraft 2.md | 67 + worlds/sc2/docs/fr_Starcraft 2.md | 95 + worlds/sc2/docs/setup_en.md | 143 + worlds/sc2/docs/setup_fr.md | 214 + worlds/{sc2wol => sc2}/requirements.txt | 1 - worlds/sc2/test/__init__.py | 0 worlds/sc2/test/test_Regions.py | 41 + worlds/sc2/test/test_base.py | 11 + worlds/sc2/test/test_options.py | 7 + worlds/sc2wol/Client.py | 1222 ---- worlds/sc2wol/Items.py | 421 -- worlds/sc2wol/Locations.py | 516 -- worlds/sc2wol/LogicMixin.py | 148 - worlds/sc2wol/MissionTables.py | 230 - worlds/sc2wol/Options.py | 362 -- worlds/sc2wol/PoolFilter.py | 367 -- worlds/sc2wol/Regions.py | 313 - worlds/sc2wol/Starcraft2.kv | 16 - worlds/sc2wol/__init__.py | 324 - .../docs/en_Starcraft 2 Wings of Liberty.md | 54 - worlds/sc2wol/docs/setup_en.md | 98 - worlds/shivers/Items.py | 62 +- worlds/shivers/Options.py | 78 +- worlds/shivers/Rules.py | 92 +- worlds/shivers/__init__.py | 73 +- worlds/shivers/data/excluded_locations.json | 80 +- worlds/shivers/data/locations.json | 224 +- worlds/shivers/data/regions.json | 78 +- worlds/shivers/docs/en_Shivers.md | 12 +- worlds/shivers/docs/setup_en.md | 6 +- worlds/shorthike/Items.py | 62 + worlds/shorthike/Locations.py | 709 ++ worlds/shorthike/Options.py | 170 + worlds/shorthike/Rules.py | 106 + worlds/shorthike/__init__.py | 153 + worlds/shorthike/docs/en_A Short Hike.md | 29 + worlds/shorthike/docs/setup_en.md | 27 + worlds/sm/Client.py | 9 +- worlds/sm/__init__.py | 31 +- worlds/sm/docs/en_Super Metroid.md | 4 +- worlds/sm/docs/multiworld_en.md | 41 +- worlds/sm64ex/Options.py | 69 +- worlds/sm64ex/Regions.py | 98 +- worlds/sm64ex/Rules.py | 65 +- worlds/sm64ex/__init__.py | 75 +- worlds/sm64ex/docs/en_Super Mario 64.md | 20 +- worlds/sm64ex/docs/setup_en.md | 4 +- worlds/smw/Aesthetics.py | 907 ++- worlds/smw/CHANGELOG.md | 118 + worlds/smw/Client.py | 298 +- worlds/smw/Items.py | 15 +- worlds/smw/Levels.py | 722 ++- worlds/smw/Locations.py | 767 ++- worlds/smw/Names/ItemName.py | 17 +- worlds/smw/Names/LocationName.py | 608 ++ worlds/smw/Names/TextBox.py | 18 +- worlds/smw/Options.py | 279 +- worlds/smw/Presets.py | 57 + worlds/smw/Regions.py | 1953 ++++-- worlds/smw/Rom.py | 2446 ++++++- worlds/smw/Rules.py | 17 +- worlds/smw/__init__.py | 157 +- worlds/smw/data/blocksanity.json | 747 +++ worlds/smw/data/graphics/indicators.bin | Bin 0 -> 384 bytes .../level/castle_pillars/agnus_castle.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_pillars/cheese.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/chocolate_blue.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/dark_aqua_marine.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/dollhouse.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/gold_caslte.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/keves_castle.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_green.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_mustard.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_white.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/pink_purple.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/purple_pink.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/sand_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/sand_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_pillars/shenhe.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/whatsapp.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/dark_lava.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/dark_purple.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/dollhouse.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/forgotten_temple.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/original_gray.mw3 | Bin 0 -> 514 bytes .../original_volcanic.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/original_water.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/sand_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/sand_green.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/shenhe.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/water.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/whatsapp.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/cheese.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/dollhouse.mw3 | Bin 0 -> 514 bytes .../level/castle_wall/grand_marshall.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/hot_wall.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/sand_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/shenhe.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/castle_wall/water.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/brawler_pink.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_windows/cheese.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/dark_aqua_marine.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/dollhouse.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/original_brown.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/original_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/original_water.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/red_castle.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_windows/shenhe.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/underwater.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_windows/water.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/whatsapp.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/brawler_dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/brawler_purple.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/brawler_red.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/brawler_teal.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/bright_magma.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/cave/dark_red.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/glowing_mushroom.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/green_depths.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/cave/ice.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/magma_cave.mw3 | Bin 0 -> 514 bytes .../level/cave/original_chocolate.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/original_gray.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/original_ice.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/original_mustard.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/original_volcanic.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/cave/snow.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/cave/toxic.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/toxic_moss.mw3 | Bin 0 -> 514 bytes .../bocchi_rock_hair_cube_things.mw3 | Bin 0 -> 514 bytes .../level/cave_rocks/brawler_volcanic.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave_rocks/ice.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave_rocks/layer_2.mw3 | Bin 0 -> 514 bytes .../level/cave_rocks/original_gray.mw3 | Bin 0 -> 514 bytes .../level/cave_rocks/original_mustard.mw3 | Bin 0 -> 514 bytes .../cave_rocks/pyra_mythra_ft_pneuma.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave_rocks/snow.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave_rocks/toxic.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/clouds/atardecer.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/clouds/charcoal.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/clouds/cloudy.mw3 | Bin 0 -> 514 bytes .../palettes/level/clouds/cotton_candy.mw3 | Bin 0 -> 514 bytes .../palettes/level/clouds/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/clouds/original_orange.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/agnian_queen.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/forest/atardecer.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/forest/frozen.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/forest/halloween.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/kevesi_queen.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/original_dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/original_fall.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/original_green.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/forest/sakura.mw3 | Bin 0 -> 514 bytes .../level/forest/snow_dark_leaves.mw3 | Bin 0 -> 514 bytes .../level/forest/snow_green_leaves.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/brawler_cyan.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/brawler_orange.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/brawler_purple.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/creepypasta.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/crimson_house.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/golden_house.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/halloween_pallet.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/orange_lights.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_blue.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_dark.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_white.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/evening_exit.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/golden_house.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/original.mw3 | Bin 0 -> 514 bytes .../ghost_house_exit/original_blue_door.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/underwater.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/crimson.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_clouds/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_clouds/miku.mw3 | Bin 0 -> 514 bytes .../level/grass_clouds/original_blue.mw3 | Bin 0 -> 514 bytes .../level/grass_clouds/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/pizza.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/sakura.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/shukfr.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/snow_day.mw3 | Bin 0 -> 514 bytes .../level/grass_clouds/volcanic_rock.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/autumn.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/brawler.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/brawler_atardecer.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/brawler_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/crimson.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/deep_forest.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_forest/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_forest/miku.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_forest/myon.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/pizza.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/sakura.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/snow_dark_leaves.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/snow_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/winter.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/atardecer.mw3 | Bin 0 -> 514 bytes .../level/grass_hills/brawler_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/crimson.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_hills/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_hills/miku.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/mogumogu.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/nocturno.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/sakura.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_hills/snow.mw3 | Bin 0 -> 514 bytes .../grass_hills/sunsetish_grass_hills.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/toothpaste.mw3 | Bin 0 -> 514 bytes .../grass_mountains/brawler_lifeless.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/classic_sm.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/crimson.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/dry_hills.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/electro.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_mountains/geo.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/late_sandish.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_mountains/miku.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_blue.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_green.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_white.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/recksfr.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/sakura_hills.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/snow_day.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/crimson.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/ice.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/miku.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/napolitano.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_aqua.mw3 | Bin 0 -> 514 bytes .../grass_rocks/original_choco_volcanic.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_ice.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_volcanic.mw3 | Bin 0 -> 514 bytes .../grass_rocks/original_volcanic_green.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_white.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_white_2.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/recks.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/sakura.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/thanks_doc.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/brawler.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/evening.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/mahogany.mw3 | Bin 0 -> 514 bytes .../level/logs/not_quite_dawnbreak.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/original.mw3 | Bin 0 -> 514 bytes .../level/logs/riesgo_de_chubascos.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/argent_cave.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/glowing_mushroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/green_aqua.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/mushroom_cave/ice.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_cave/original.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/really_dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_cave/toxic.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/atardecer.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/greenshroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/oilshroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/original_blue.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/original_yellow.mw3 | Bin 0 -> 514 bytes .../mushroom_clouds/riesgo_de_chubascos.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_forest/autumn.mw3 | Bin 0 -> 514 bytes .../mushroom_forest/count_shroomcula.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/cursed_gold.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/dark_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/lifeless_gray.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/original.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/snow_dark.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/snow_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/atardecer.mw3 | Bin 0 -> 514 bytes .../mushroom_hills/atardecer_naranjo.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/atardecer_verde.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_hills/future.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/original.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_azul.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_cafe.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_negro.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/watermelon_skies.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/atardecer.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/brightshroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/original_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/original_ice.mw3 | Bin 0 -> 514 bytes .../mushroom_rocks/original_volcanic.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/original_white.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_cafe.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_negro.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/shuk_ft_reyn.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_stars/cool.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/dark_night.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/halloween.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/light_pollution.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_stars/midas.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/original_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/original_night.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/purpleish_night.mw3 | Bin 0 -> 514 bytes .../mushroom_stars/riesgo_de_chubascos.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/palettes.json | 358 ++ .../level/ship_exterior/blue_purple.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_exterior/doc_ship.mw3 | Bin 0 -> 514 bytes .../level/ship_exterior/grey_ship.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_exterior/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_exterior/reddish.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/blue_purple.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/bocchi_hitori.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/bocchi_rock.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_interior/brawler.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/grey_ship.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_interior/original.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/blue_grid.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/brawler_brown.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/cafe_claro.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/color_de_gato.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/color_del_gato_2.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/green_grid.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/gris.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/mario_pants.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/monado.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/morado.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/negro.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/onigiria.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/original.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/original_bonus.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/pink.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/red_grid.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/verde.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/verde_agua.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/yellow_grid.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/youbonus.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/water/dark_water.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/water/deep_aqua.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/deep_chocolate.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/harmless_magma.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/water/murky.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/water/oil_spill.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_brown.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_gray.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_mustard.mw3 | Bin 0 -> 514 bytes .../level/water/original_volcanic.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/pickle_juice.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/atardecer.mw3 | Bin 0 -> 514 bytes .../level/yoshi_house/brawler_green.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/choco.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/crimson.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/miku.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/mogumogu.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/monocromo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/neon.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/nieve.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/night.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/nocturno.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/sakura.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/snow.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/strong_sun.mw3 | Bin 0 -> 514 bytes .../yoshi_house/sunsetish_grass_hills.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/atardecer.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/burnt_forest.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/dark_forest.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/halloween.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/ice_forest.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/lost_woods.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/forest/mono.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/forest/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/forest/original_special.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/forest/sepia.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/forest/snow_day.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/atardecer.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/brawler.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/main/cake_frosting.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/invertido.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/mono.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/morning.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/night.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/night_time.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/main/original_special.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/sepia.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/snow_day.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/palettes.json | 92 + .../data/palettes/map/special/black_out.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/special/blood_star.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/special/brawler.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/special/green.mw3 | Bin 0 -> 514 bytes .../map/special/light_pollution_map.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/special/original.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/special/purple.mw3 | Bin 0 -> 514 bytes .../palettes/map/special/white_special.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/star/blood_moon.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/star/mono.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/star/mountain_top.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/star/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/star/original_special.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/star/pink_star.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/star/sepia.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/star/yellow_star.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/Tamaulipas.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/valley/bowser.mw3 | Bin 0 -> 514 bytes .../palettes/map/valley/castle_colors.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/dark cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/dream_world.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/fire cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/invertido.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/valley/mono.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/valley/orange.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/valley/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/valley/original_special.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/purple_blue.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/valley/sepia.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/valley/snow.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/vanilla/DOMO.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/aqua_marine.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/dark cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/fire cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/gold_mine.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/invertido.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/vanilla/mono.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/vanilla/original_special.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/vanilla/purple.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/vanilla/sepia.mw3 | Bin 0 -> 514 bytes .../palettes/map/vanilla/witches_cauldron.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/yoshi/atardecer.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/gum.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/yoshi/lava_island.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/mono.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/yoshi/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/yoshi/original_special.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/sepia.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/yoshi/snow_day.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/sunset.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/yoshi/tritanopia.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/yoshi/yochis_ailand.mw3 | Bin 0 -> 514 bytes worlds/smw/docs/en_Super Mario World.md | 14 +- worlds/smw/docs/setup_en.md | 4 +- worlds/smz3/Client.py | 7 +- worlds/smz3/Options.py | 7 +- worlds/smz3/__init__.py | 137 +- worlds/smz3/docs/en_SMZ3.md | 4 +- worlds/smz3/docs/multiworld_en.md | 40 +- worlds/soe/__init__.py | 52 +- worlds/soe/docs/en_Secret of Evermore.md | 4 +- worlds/soe/docs/multiworld_en.md | 40 +- worlds/soe/logic.py | 7 +- worlds/soe/options.py | 31 +- worlds/soe/requirements.txt | 72 +- worlds/soe/test/__init__.py | 2 + worlds/soe/test/test_sniffamizer.py | 130 + worlds/spire/Options.py | 25 +- worlds/spire/__init__.py | 20 +- worlds/spire/docs/en_Slay the Spire.md | 4 +- worlds/spire/docs/slay-the-spire_en.md | 4 +- worlds/stardew_valley/__init__.py | 382 +- worlds/stardew_valley/bundles.py | 254 - worlds/stardew_valley/bundles/__init__.py | 0 worlds/stardew_valley/bundles/bundle.py | 175 + worlds/stardew_valley/bundles/bundle_item.py | 101 + worlds/stardew_valley/bundles/bundle_room.py | 43 + worlds/stardew_valley/bundles/bundles.py | 124 + worlds/stardew_valley/content/__init__.py | 107 + .../stardew_valley/content/content_packs.py | 31 + .../content/feature/__init__.py | 4 + .../content/feature/booksanity.py | 72 + .../content/feature/cropsanity.py | 42 + .../content/feature/fishsanity.py | 101 + .../content/feature/friendsanity.py | 139 + worlds/stardew_valley/content/game_content.py | 117 + worlds/stardew_valley/content/mod_registry.py | 7 + .../stardew_valley/content/mods/__init__.py | 0 worlds/stardew_valley/content/mods/alecto.py | 33 + .../stardew_valley/content/mods/archeology.py | 34 + .../content/mods/big_backpack.py | 7 + .../content/mods/boarding_house.py | 13 + .../stardew_valley/content/mods/deepwoods.py | 28 + .../content/mods/distant_lands.py | 42 + worlds/stardew_valley/content/mods/jasper.py | 14 + worlds/stardew_valley/content/mods/magic.py | 10 + .../stardew_valley/content/mods/npc_mods.py | 81 + .../stardew_valley/content/mods/skill_mods.py | 25 + .../content/mods/skull_cavern_elevator.py | 7 + worlds/stardew_valley/content/mods/sve.py | 210 + worlds/stardew_valley/content/mods/tractor.py | 7 + worlds/stardew_valley/content/override.py | 7 + worlds/stardew_valley/content/unpacking.py | 97 + .../content/vanilla/__init__.py | 0 worlds/stardew_valley/content/vanilla/base.py | 172 + .../content/vanilla/ginger_island.py | 85 + .../content/vanilla/pelican_town.py | 390 ++ .../content/vanilla/qi_board.py | 36 + .../content/vanilla/the_desert.py | 46 + .../content/vanilla/the_farm.py | 43 + .../content/vanilla/the_mines.py | 35 + worlds/stardew_valley/data/__init__.py | 2 - worlds/stardew_valley/data/artisan.py | 10 + worlds/stardew_valley/data/bundle_data.py | 1323 ++-- worlds/stardew_valley/data/common_data.py | 9 - worlds/stardew_valley/data/craftable_data.py | 374 ++ worlds/stardew_valley/data/crops.csv | 40 - worlds/stardew_valley/data/crops_data.py | 48 - worlds/stardew_valley/data/fish_data.py | 215 +- worlds/stardew_valley/data/game_item.py | 85 +- worlds/stardew_valley/data/harvest.py | 66 + worlds/stardew_valley/data/items.csv | 622 +- worlds/stardew_valley/data/locations.csv | 4579 +++++++++---- worlds/stardew_valley/data/monster_data.py | 177 +- worlds/stardew_valley/data/museum_data.py | 291 +- worlds/stardew_valley/data/recipe_data.py | 189 +- worlds/stardew_valley/data/recipe_source.py | 159 + worlds/stardew_valley/data/requirement.py | 57 + .../data/shipsanity_unimplemented_items.csv | 0 worlds/stardew_valley/data/shop.py | 40 + worlds/stardew_valley/data/skill.py | 9 + worlds/stardew_valley/data/villagers_data.py | 152 +- .../stardew_valley/docs/en_Stardew Valley.md | 174 +- worlds/stardew_valley/docs/setup_en.md | 49 +- worlds/stardew_valley/early_items.py | 83 + worlds/stardew_valley/items.py | 676 +- worlds/stardew_valley/locations.py | 401 +- worlds/stardew_valley/logic.py | 1626 ----- worlds/stardew_valley/logic/__init__.py | 0 worlds/stardew_valley/logic/ability_logic.py | 47 + worlds/stardew_valley/logic/action_logic.py | 40 + worlds/stardew_valley/logic/animal_logic.py | 59 + worlds/stardew_valley/logic/arcade_logic.py | 34 + worlds/stardew_valley/logic/artisan_logic.py | 93 + worlds/stardew_valley/logic/base_logic.py | 52 + worlds/stardew_valley/logic/book_logic.py | 24 + worlds/stardew_valley/logic/building_logic.py | 97 + worlds/stardew_valley/logic/bundle_logic.py | 84 + worlds/stardew_valley/logic/combat_logic.py | 62 + worlds/stardew_valley/logic/cooking_logic.py | 108 + worlds/stardew_valley/logic/crafting_logic.py | 115 + worlds/stardew_valley/logic/farming_logic.py | 62 + worlds/stardew_valley/logic/fishing_logic.py | 112 + worlds/stardew_valley/logic/gift_logic.py | 20 + worlds/stardew_valley/logic/grind_logic.py | 74 + .../stardew_valley/logic/harvesting_logic.py | 56 + worlds/stardew_valley/logic/has_logic.py | 65 + worlds/stardew_valley/logic/logic.py | 590 ++ .../logic/logic_and_mods_design.md | 75 + worlds/stardew_valley/logic/logic_event.py | 33 + worlds/stardew_valley/logic/mine_logic.py | 91 + worlds/stardew_valley/logic/money_logic.py | 116 + worlds/stardew_valley/logic/monster_logic.py | 74 + worlds/stardew_valley/logic/museum_logic.py | 85 + worlds/stardew_valley/logic/pet_logic.py | 47 + worlds/stardew_valley/logic/quality_logic.py | 33 + worlds/stardew_valley/logic/quest_logic.py | 142 + worlds/stardew_valley/logic/received_logic.py | 44 + worlds/stardew_valley/logic/region_logic.py | 69 + .../logic/relationship_logic.py | 206 + .../stardew_valley/logic/requirement_logic.py | 80 + worlds/stardew_valley/logic/season_logic.py | 65 + worlds/stardew_valley/logic/shipping_logic.py | 60 + worlds/stardew_valley/logic/skill_logic.py | 209 + worlds/stardew_valley/logic/source_logic.py | 106 + .../logic/special_order_logic.py | 125 + worlds/stardew_valley/logic/time_logic.py | 54 + worlds/stardew_valley/logic/tool_logic.py | 104 + .../logic/traveling_merchant_logic.py | 26 + worlds/stardew_valley/logic/wallet_logic.py | 19 + worlds/stardew_valley/logic/walnut_logic.py | 135 + worlds/stardew_valley/mods/logic/buildings.py | 16 - .../mods/logic/buildings_logic.py | 28 + worlds/stardew_valley/mods/logic/deepwoods.py | 35 - .../mods/logic/deepwoods_logic.py | 73 + .../mods/logic/elevator_logic.py | 18 + .../stardew_valley/mods/logic/item_logic.py | 153 + worlds/stardew_valley/mods/logic/magic.py | 80 - .../stardew_valley/mods/logic/magic_logic.py | 83 + worlds/stardew_valley/mods/logic/mod_logic.py | 21 + .../mods/logic/mod_skills_levels.py | 22 + worlds/stardew_valley/mods/logic/quests.py | 31 - .../stardew_valley/mods/logic/quests_logic.py | 128 + worlds/stardew_valley/mods/logic/skills.py | 94 - .../stardew_valley/mods/logic/skills_logic.py | 116 + .../mods/logic/skullcavernelevator.py | 10 - .../mods/logic/special_orders.py | 24 - .../mods/logic/special_orders_logic.py | 75 + worlds/stardew_valley/mods/logic/sve_logic.py | 68 + worlds/stardew_valley/mods/mod_data.py | 13 +- .../mods/mod_monster_locations.py | 40 + worlds/stardew_valley/mods/mod_regions.py | 246 +- worlds/stardew_valley/option_groups.py | 76 + worlds/stardew_valley/options.py | 424 +- worlds/stardew_valley/presets.py | 169 +- worlds/stardew_valley/region_classes.py | 37 +- worlds/stardew_valley/regions.py | 588 +- worlds/stardew_valley/requirements.txt | 3 +- worlds/stardew_valley/rules.py | 1179 ++-- .../scripts/export_locations.py | 8 +- worlds/stardew_valley/scripts/update_data.py | 10 +- worlds/stardew_valley/stardew_rule.py | 384 -- .../stardew_valley/stardew_rule/__init__.py | 4 + worlds/stardew_valley/stardew_rule/base.py | 479 ++ .../stardew_rule/indirect_connection.py | 43 + worlds/stardew_valley/stardew_rule/literal.py | 56 + .../stardew_valley/stardew_rule/protocol.py | 26 + .../stardew_rule/rule_explain.py | 191 + worlds/stardew_valley/stardew_rule/state.py | 125 + .../strings/animal_product_names.py | 34 +- .../strings/ap_names/ap_option_names.py | 16 + .../strings/ap_names/ap_weapon_names.py | 7 + .../strings/ap_names/buff_names.py | 12 +- .../ap_names/community_upgrade_names.py | 6 + .../strings/ap_names/event_names.py | 20 + .../strings/ap_names/mods/mod_items.py | 56 + .../strings/artisan_good_names.py | 43 + worlds/stardew_valley/strings/book_names.py | 61 + worlds/stardew_valley/strings/bundle_names.py | 108 + .../stardew_valley/strings/craftable_names.py | 214 +- worlds/stardew_valley/strings/crop_names.py | 110 +- .../stardew_valley/strings/currency_names.py | 16 + .../strings/decoration_names.py | 2 + .../stardew_valley/strings/entrance_names.py | 178 +- .../strings/festival_check_names.py | 48 + worlds/stardew_valley/strings/fish_names.py | 173 +- worlds/stardew_valley/strings/flower_names.py | 6 +- worlds/stardew_valley/strings/food_names.py | 104 +- .../strings/forageable_names.py | 48 +- worlds/stardew_valley/strings/gift_names.py | 12 +- worlds/stardew_valley/strings/goal_names.py | 7 + .../strings/ingredient_names.py | 1 + .../stardew_valley/strings/machine_names.py | 21 +- .../stardew_valley/strings/material_names.py | 1 + worlds/stardew_valley/strings/metal_names.py | 113 +- .../strings/monster_drop_names.py | 15 + .../stardew_valley/strings/monster_names.py | 67 + .../stardew_valley/strings/quality_names.py | 63 + worlds/stardew_valley/strings/quest_names.py | 21 +- worlds/stardew_valley/strings/region_names.py | 152 +- worlds/stardew_valley/strings/season_names.py | 5 +- worlds/stardew_valley/strings/seed_names.py | 75 +- worlds/stardew_valley/strings/skill_names.py | 5 + .../strings/special_order_names.py | 9 +- worlds/stardew_valley/strings/spells.py | 50 +- worlds/stardew_valley/strings/tool_names.py | 1 + .../stardew_valley/strings/villager_names.py | 22 +- .../strings/wallet_item_names.py | 6 + worlds/stardew_valley/strings/weapon_names.py | 1 - worlds/stardew_valley/test/TestBooksanity.py | 207 + worlds/stardew_valley/test/TestBundles.py | 90 +- worlds/stardew_valley/test/TestCrops.py | 20 + worlds/stardew_valley/test/TestData.py | 45 +- .../stardew_valley/test/TestDynamicGoals.py | 108 + worlds/stardew_valley/test/TestFarmType.py | 31 + worlds/stardew_valley/test/TestFill.py | 30 + worlds/stardew_valley/test/TestFishsanity.py | 405 ++ .../stardew_valley/test/TestFriendsanity.py | 159 + worlds/stardew_valley/test/TestGeneration.py | 993 +-- worlds/stardew_valley/test/TestItemLink.py | 4 +- worlds/stardew_valley/test/TestItems.py | 105 +- worlds/stardew_valley/test/TestLogic.py | 125 +- .../test/TestLogicSimplification.py | 57 - .../test/TestMultiplePlayers.py | 92 + .../test/TestNumberLocations.py | 98 + worlds/stardew_valley/test/TestOptionFlags.py | 105 + worlds/stardew_valley/test/TestOptions.py | 320 +- .../stardew_valley/test/TestOptionsPairs.py | 56 + worlds/stardew_valley/test/TestPresets.py | 21 + worlds/stardew_valley/test/TestRegions.py | 158 +- worlds/stardew_valley/test/TestRules.py | 506 -- worlds/stardew_valley/test/TestStardewRule.py | 305 + .../stardew_valley/test/TestStartInventory.py | 41 + .../stardew_valley/test/TestWalnutsanity.py | 209 + worlds/stardew_valley/test/__init__.py | 511 +- .../stardew_valley/test/assertion/__init__.py | 5 + .../test/assertion/goal_assert.py | 55 + .../test/assertion/mod_assert.py | 28 + .../test/assertion/option_assert.py | 100 + .../test/assertion/rule_assert.py | 49 + .../test/assertion/world_assert.py | 83 + .../stardew_valley/test/checks/goal_checks.py | 55 - .../test/checks/option_checks.py | 72 - .../test/checks/world_checks.py | 33 - .../test/content/TestArtisanEquipment.py | 54 + .../test/content/TestGingerIsland.py | 55 + .../test/content/TestPelicanTown.py | 112 + .../test/content/TestQiBoard.py | 27 + .../stardew_valley/test/content/__init__.py | 23 + .../test/content/feature/TestFriendsanity.py | 33 + .../test/content/feature/__init__.py | 0 .../test/content/mods/TestDeepwoods.py | 14 + .../test/content/mods/TestJasper.py | 27 + .../test/content/mods/TestSVE.py | 143 + .../test/content/mods/__init__.py | 0 .../stardew_valley/test/long/TestModsLong.py | 104 +- .../test/long/TestOptionsLong.py | 72 +- .../test/long/TestPreRolledRandomness.py | 28 + .../test/long/TestRandomWorlds.py | 84 +- .../stardew_valley/test/long/option_names.py | 28 +- .../test/mods/TestBiggerBackpack.py | 20 +- worlds/stardew_valley/test/mods/TestMods.py | 180 +- .../test/performance/TestPerformance.py | 232 + .../test/performance/__init__.py | 0 .../stardew_valley/test/rules/TestArcades.py | 97 + .../test/rules/TestBuildings.py | 62 + .../stardew_valley/test/rules/TestBundles.py | 66 + .../test/rules/TestCookingRecipes.py | 83 + .../test/rules/TestCraftingRecipes.py | 123 + .../test/rules/TestDonations.py | 73 + .../test/rules/TestFriendship.py | 58 + .../stardew_valley/test/rules/TestMuseum.py | 16 + .../stardew_valley/test/rules/TestShipping.py | 82 + .../stardew_valley/test/rules/TestSkills.py | 40 + .../test/rules/TestStateRules.py | 12 + worlds/stardew_valley/test/rules/TestTools.py | 141 + .../stardew_valley/test/rules/TestWeapons.py | 75 + worlds/stardew_valley/test/rules/__init__.py | 0 worlds/stardew_valley/test/script/__init__.py | 0 .../test/script/benchmark_locations.py | 140 + .../test/stability/StabilityOutputScript.py | 35 + .../test/stability/TestStability.py | 58 + .../test/stability/TestUniversalTracker.py | 52 + .../stardew_valley/test/stability/__init__.py | 0 worlds/subnautica/__init__.py | 22 +- worlds/subnautica/docs/en_Subnautica.md | 4 +- worlds/subnautica/docs/setup_en.md | 2 +- worlds/subnautica/items.py | 3 + worlds/subnautica/options.py | 50 +- worlds/subnautica/rules.py | 4 +- worlds/subnautica/test/__init__.py | 2 +- worlds/terraria/Checks.py | 111 +- worlds/terraria/Options.py | 16 +- worlds/terraria/Rules.dsv | 14 +- worlds/terraria/__init__.py | 24 +- worlds/terraria/docs/en_Terraria.md | 6 +- worlds/terraria/docs/setup_en.md | 2 +- worlds/timespinner/Locations.py | 21 +- worlds/timespinner/LogicExtensions.py | 23 +- worlds/timespinner/Options.py | 343 +- worlds/timespinner/PreCalculatedWeights.py | 68 +- worlds/timespinner/Regions.py | 43 +- worlds/timespinner/__init__.py | 188 +- worlds/timespinner/docs/en_Timespinner.md | 4 +- worlds/timespinner/docs/setup_de.md | 2 +- worlds/timespinner/docs/setup_en.md | 4 +- worlds/tloz/ItemPool.py | 10 +- worlds/tloz/Items.py | 4 +- worlds/tloz/Locations.py | 8 + worlds/tloz/Rules.py | 8 +- worlds/tloz/__init__.py | 26 +- worlds/tloz/docs/en_The Legend of Zelda.md | 5 +- worlds/tloz/docs/multiworld_en.md | 8 +- worlds/tunic/__init__.py | 253 +- worlds/tunic/docs/en_TUNIC.md | 38 +- worlds/tunic/docs/setup_en.md | 13 +- worlds/tunic/er_data.py | 1867 ++++-- worlds/tunic/er_rules.py | 1235 +++- worlds/tunic/er_scripts.py | 695 +- worlds/tunic/items.py | 292 +- worlds/tunic/locations.py | 235 +- worlds/tunic/options.py | 162 +- worlds/tunic/regions.py | 6 +- worlds/tunic/rules.py | 218 +- worlds/tunic/test/__init__.py | 2 +- worlds/tunic/test/test_access.py | 16 +- worlds/undertale/Items.py | 2 +- worlds/undertale/Locations.py | 4 - worlds/undertale/Options.py | 32 +- worlds/undertale/Rules.py | 67 +- worlds/undertale/__init__.py | 155 +- worlds/undertale/docs/en_Undertale.md | 4 +- worlds/undertale/docs/setup_en.md | 2 +- worlds/v6/__init__.py | 2 - worlds/v6/docs/en_VVVVVV.md | 6 +- worlds/v6/docs/setup_en.md | 2 +- worlds/wargroove/Options.py | 14 +- worlds/wargroove/__init__.py | 30 +- worlds/wargroove/docs/en_Wargroove.md | 4 +- worlds/wargroove/docs/wargroove_en.md | 2 +- worlds/wargroove2/Items.py | 140 + worlds/wargroove2/Levels.py | 416 ++ worlds/wargroove2/Locations.py | 120 + worlds/wargroove2/Options.py | 65 + worlds/wargroove2/Presets.py | 23 + worlds/wargroove2/RegionFilter.py | 23 + worlds/wargroove2/Regions.py | 86 + worlds/wargroove2/Rules.py | 28 + worlds/wargroove2/Wargroove2.kv | 35 + worlds/wargroove2/__init__.py | 191 + worlds/wargroove2/client.py | 739 +++ .../data/mods/ArchipelagoMod/maps.dat | Bin 0 -> 350032 bytes .../data/mods/ArchipelagoMod/mod.dat | Bin 0 -> 692 bytes .../data/mods/ArchipelagoMod/modAssets.dat | Bin 0 -> 305148 bytes ...paign-45747c660b6a2f09601327a18d662a7d.cmp | Bin 0 -> 220976 bytes ...n-45747c660b6a2f09601327a18d662a7d.cmp.bak | Bin 0 -> 221024 bytes worlds/wargroove2/docs/en_Wargroove 2.md | 59 + worlds/wargroove2/docs/wargroove2_en.md | 92 + .../wargroove2/levels/A_Ribbitting_Time.json | 1 + worlds/wargroove2/levels/Air_Support.json | 1 + worlds/wargroove2/levels/Beached.json | 1 + worlds/wargroove2/levels/Bridge_Brigade.json | 1 + .../levels/Cherrystone_Landing.json | 1 + worlds/wargroove2/levels/Dark_Mirror.json | 1 + worlds/wargroove2/levels/Dementia_Castle.json | 1 + worlds/wargroove2/levels/Den-Two-Away.json | 1 + .../levels/Disastrous_Crossing.json | 1 + .../wargroove2/levels/Doomed_Metropolis.json | 1 + worlds/wargroove2/levels/Enmity_Cliffs.json | 1 + worlds/wargroove2/levels/Finishing_Blow.json | 1 + worlds/wargroove2/levels/Fortification.json | 1 + worlds/wargroove2/levels/Frantic_Inlet.json | 1 + .../levels/Gnarled_Mountaintop.json | 1 + worlds/wargroove2/levels/Gold_Rush.json | 1 + .../levels/Grand_Theft_Village.json | 1 + worlds/wargroove2/levels/Kraken_Strait.json | 1 + worlds/wargroove2/levels/Nuru_Vengeance.json | 1 + .../wargroove2/levels/Operation_Seagull.json | 1 + worlds/wargroove2/levels/Portal_Peril.json | 1 + .../wargroove2/levels/Precarious_Cliffs.json | 1 + .../wargroove2/levels/Riflemen_Blockade.json | 1 + worlds/wargroove2/levels/Skydiving.json | 1 + worlds/wargroove2/levels/Slippery_Bridge.json | 1 + worlds/wargroove2/levels/Spire_Fire.json | 1 + worlds/wargroove2/levels/Split_Valley.json | 1 + worlds/wargroove2/levels/Sunken_Forest.json | 1 + worlds/wargroove2/levels/Tenris_Mistake.json | 1 + .../levels/Terrible_Tributaries.json | 1 + .../levels/Towers_of_the_Abyss.json | 1 + worlds/wargroove2/levels/Wagon_Freeway.json | 1 + worlds/witness/__init__.py | 281 +- worlds/witness/{ => data}/WitnessItems.txt | 15 +- worlds/witness/{ => data}/WitnessLogic.txt | 14 +- .../witness/{ => data}/WitnessLogicExpert.txt | 12 +- .../{ => data}/WitnessLogicVanilla.txt | 12 +- worlds/witness/data/__init__.py | 0 .../witness/data/item_definition_classes.py | 59 + .../{ => data}/settings/Audio_Logs.txt | 0 .../{ => data}/settings/Door_Shuffle/Boat.txt | 0 .../Complex_Additional_Panels.txt | 0 .../Door_Shuffle/Complex_Door_Panels.txt | 2 +- .../settings/Door_Shuffle/Complex_Doors.txt | 6 +- .../Door_Shuffle/Elevators_Come_To_You.txt | 0 .../settings/Door_Shuffle/Obelisk_Keys.txt | 7 + .../Door_Shuffle/Simple_Additional_Panels.txt | 0 .../settings/Door_Shuffle/Simple_Doors.txt | 0 .../settings/Door_Shuffle/Simple_Panels.txt | 0 .../{ => data}/settings/EP_Shuffle/EP_All.txt | 0 .../settings/EP_Shuffle/EP_Easy.txt | 1 + .../settings/EP_Shuffle/EP_NoEclipse.txt | 0 .../settings/EP_Shuffle/EP_Sides.txt | 0 .../{ => data}/settings/Early_Caves.txt | 0 .../{ => data}/settings/Early_Caves_Start.txt | 0 .../Caves_Except_Path_To_Challenge.txt} | 0 .../Exclusions/Disable_Unrandomized.txt | 14 - .../settings/Exclusions/Discards.txt | 0 .../data/settings/Exclusions/Vaults.txt | 8 + .../{ => data}/settings/Laser_Shuffle.txt | 0 .../{ => data}/settings/Symbol_Shuffle.txt | 0 worlds/witness/data/static_items.py | 56 + worlds/witness/data/static_locations.py | 484 ++ worlds/witness/data/static_logic.py | 303 + worlds/witness/{ => data}/utils.py | 110 +- worlds/witness/docs/en_The Witness.md | 8 +- worlds/witness/docs/setup_en.md | 2 +- worlds/witness/hints.py | 287 +- worlds/witness/locations.py | 534 +- worlds/witness/options.py | 262 +- worlds/witness/{items.py => player_items.py} | 142 +- worlds/witness/player_logic.py | 804 ++- worlds/witness/presets.py | 6 + worlds/witness/regions.py | 115 +- worlds/witness/ruff.toml | 11 + worlds/witness/rules.py | 234 +- worlds/witness/settings/Exclusions/Vaults.txt | 31 - .../settings/Postgame/Beyond_Challenge.txt | 4 - .../Postgame/Bottom_Floor_Discard.txt | 2 - .../Bottom_Floor_Discard_NonDoors.txt | 6 - .../settings/Postgame/Challenge_Vault_Box.txt | 22 - .../settings/Postgame/Mountain_Lower.txt | 27 - .../settings/Postgame/Mountain_Upper.txt | 41 - .../settings/Postgame/Path_To_Challenge.txt | 30 - worlds/witness/static_logic.py | 300 - worlds/witness/test/__init__.py | 161 + worlds/witness/test/test_auto_elevators.py | 66 + .../test/test_disable_non_randomized.py | 37 + worlds/witness/test/test_door_shuffle.py | 24 + worlds/witness/test/test_ep_shuffle.py | 54 + worlds/witness/test/test_lasers.py | 185 + .../witness/test/test_roll_other_options.py | 58 + worlds/witness/test/test_symbol_shuffle.py | 74 + worlds/yoshisisland/Client.py | 147 + worlds/yoshisisland/Items.py | 122 + worlds/yoshisisland/Locations.py | 355 + worlds/yoshisisland/Options.py | 296 + worlds/yoshisisland/Regions.py | 248 + worlds/yoshisisland/Rom.py | 1230 ++++ worlds/yoshisisland/Rules.py | 612 ++ worlds/yoshisisland/__init__.py | 387 ++ worlds/yoshisisland/docs/en_Yoshi's Island.md | 71 + worlds/yoshisisland/docs/setup_en.md | 122 + worlds/yoshisisland/level_logic.py | 482 ++ worlds/yoshisisland/setup_bosses.py | 19 + worlds/yoshisisland/setup_game.py | 460 ++ worlds/yugioh06/__init__.py | 456 ++ worlds/yugioh06/boosterpacks.py | 923 +++ worlds/yugioh06/client_bh.py | 139 + worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md | 53 + worlds/yugioh06/docs/setup_en.md | 72 + worlds/yugioh06/fusions.py | 72 + worlds/yugioh06/items.py | 369 ++ worlds/yugioh06/locations.py | 213 + worlds/yugioh06/logic.py | 28 + worlds/yugioh06/opponents.py | 264 + worlds/yugioh06/options.py | 195 + worlds/yugioh06/patch.bsdiff4 | Bin 0 -> 2959 bytes worlds/yugioh06/patches/draft.bsdiff4 | Bin 0 -> 306 bytes worlds/yugioh06/patches/ocg.bsdiff4 | Bin 0 -> 396 bytes worlds/yugioh06/rom.py | 161 + worlds/yugioh06/rom_values.py | 38 + worlds/yugioh06/ruff.toml | 12 + worlds/yugioh06/rules.py | 868 +++ worlds/yugioh06/structure_deck.py | 87 + worlds/zillion/__init__.py | 17 +- worlds/zillion/client.py | 19 +- worlds/zillion/docs/en_Zillion.md | 4 +- worlds/zillion/docs/setup_en.md | 4 +- worlds/zillion/gen_data.py | 7 + worlds/zillion/options.py | 51 +- worlds/zillion/patch.py | 4 +- worlds/zillion/requirements.txt | 2 +- worlds/zillion/test/TestOptions.py | 2 +- worlds/zillion/test/TestReproducibleRandom.py | 2 +- worlds/zillion/test/__init__.py | 2 +- worlds/zork_grand_inquisitor/LICENSE | 21 + worlds/zork_grand_inquisitor/__init__.py | 17 + worlds/zork_grand_inquisitor/client.py | 188 + worlds/zork_grand_inquisitor/data/__init__.py | 0 .../data/entrance_rule_data.py | 419 ++ .../zork_grand_inquisitor/data/item_data.py | 792 +++ .../data/location_data.py | 1535 +++++ ...missable_location_grant_conditions_data.py | 200 + .../zork_grand_inquisitor/data/region_data.py | 183 + worlds/zork_grand_inquisitor/data_funcs.py | 247 + .../docs/en_Zork Grand Inquisitor.md | 102 + worlds/zork_grand_inquisitor/docs/setup_en.md | 42 + worlds/zork_grand_inquisitor/enums.py | 350 + .../zork_grand_inquisitor/game_controller.py | 1388 ++++ .../game_state_manager.py | 370 ++ worlds/zork_grand_inquisitor/options.py | 61 + worlds/zork_grand_inquisitor/requirements.txt | 1 + worlds/zork_grand_inquisitor/test/__init__.py | 5 + .../zork_grand_inquisitor/test/test_access.py | 2927 +++++++++ .../test/test_data_funcs.py | 132 + .../test/test_locations.py | 49 + worlds/zork_grand_inquisitor/world.py | 205 + 1652 files changed, 143730 insertions(+), 39973 deletions(-) create mode 100644 .github/pyright-config.json create mode 100644 .github/type_check.py create mode 100644 .github/workflows/ctest.yml create mode 100644 .github/workflows/strict-type-check.yml create mode 100644 AHITClient.py create mode 100644 Wargroove2Client.py create mode 100644 WebHostLib/api/datapackage.py create mode 100644 WebHostLib/robots.py delete mode 100644 WebHostLib/static/assets/lttp-tracker.js delete mode 100644 WebHostLib/static/assets/player-options.js create mode 100644 WebHostLib/static/assets/playerOptions.js rename WebHostLib/static/assets/{sc2wolTracker.js => sc2Tracker.js} (85%) delete mode 100644 WebHostLib/static/assets/weighted-options.js create mode 100644 WebHostLib/static/assets/weightedOptions.js create mode 100644 WebHostLib/static/robots_file.txt delete mode 100644 WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png delete mode 100644 WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png delete mode 100644 WebHostLib/static/static/icons/sc2/advanceballistics.png delete mode 100644 WebHostLib/static/static/icons/sc2/autoturretblackops.png delete mode 100644 WebHostLib/static/static/icons/sc2/biomechanicaldrone.png delete mode 100644 WebHostLib/static/static/icons/sc2/burstcapacitors.png delete mode 100644 WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png delete mode 100644 WebHostLib/static/static/icons/sc2/cyclone.png delete mode 100644 WebHostLib/static/static/icons/sc2/cyclonerangeupgrade.png delete mode 100644 WebHostLib/static/static/icons/sc2/drillingclaws.png delete mode 100644 WebHostLib/static/static/icons/sc2/emergencythrusters.png delete mode 100644 WebHostLib/static/static/icons/sc2/hellionbattlemode.png delete mode 100644 WebHostLib/static/static/icons/sc2/high-explosive-spidermine.png delete mode 100644 WebHostLib/static/static/icons/sc2/hyperflightrotors.png delete mode 100644 WebHostLib/static/static/icons/sc2/hyperfluxor.png delete mode 100644 WebHostLib/static/static/icons/sc2/impalerrounds.png delete mode 100644 WebHostLib/static/static/icons/sc2/improvedburstlaser.png delete mode 100644 WebHostLib/static/static/icons/sc2/improvedsiegemode.png delete mode 100644 WebHostLib/static/static/icons/sc2/interferencematrix.png delete mode 100644 WebHostLib/static/static/icons/sc2/internalizedtechmodule.png delete mode 100644 WebHostLib/static/static/icons/sc2/jotunboosters.png delete mode 100644 WebHostLib/static/static/icons/sc2/jumpjets.png delete mode 100644 WebHostLib/static/static/icons/sc2/lasertargetingsystem.png delete mode 100644 WebHostLib/static/static/icons/sc2/liberator.png delete mode 100644 WebHostLib/static/static/icons/sc2/lockdown.png delete mode 100644 WebHostLib/static/static/icons/sc2/magfieldaccelerator.png delete mode 100644 WebHostLib/static/static/icons/sc2/magrailmunitions.png delete mode 100644 WebHostLib/static/static/icons/sc2/medivacemergencythrusters.png delete mode 100644 WebHostLib/static/static/icons/sc2/neosteelfortifiedarmor.png delete mode 100644 WebHostLib/static/static/icons/sc2/opticalflare.png delete mode 100644 WebHostLib/static/static/icons/sc2/optimizedlogistics.png delete mode 100644 WebHostLib/static/static/icons/sc2/reapercombatdrugs.png delete mode 100644 WebHostLib/static/static/icons/sc2/restoration.png delete mode 100644 WebHostLib/static/static/icons/sc2/ripwavemissiles.png delete mode 100644 WebHostLib/static/static/icons/sc2/shreddermissile.png delete mode 100644 WebHostLib/static/static/icons/sc2/siegetank-spidermines.png delete mode 100644 WebHostLib/static/static/icons/sc2/siegetankrange.png delete mode 100644 WebHostLib/static/static/icons/sc2/specialordance.png delete mode 100644 WebHostLib/static/static/icons/sc2/spidermine.png delete mode 100644 WebHostLib/static/static/icons/sc2/staticempblast.png delete mode 100644 WebHostLib/static/static/icons/sc2/superstimpack.png delete mode 100644 WebHostLib/static/static/icons/sc2/targetingoptics.png delete mode 100644 WebHostLib/static/static/icons/sc2/terran-cloak-color.png delete mode 100644 WebHostLib/static/static/icons/sc2/terran-emp-color.png delete mode 100644 WebHostLib/static/static/icons/sc2/terrandefendermodestructureattack.png delete mode 100644 WebHostLib/static/static/icons/sc2/thorsiegemode.png delete mode 100644 WebHostLib/static/static/icons/sc2/transformationservos.png delete mode 100644 WebHostLib/static/static/icons/sc2/valkyrie.png delete mode 100644 WebHostLib/static/static/icons/sc2/warpjump.png delete mode 100644 WebHostLib/static/static/icons/sc2/widowmine-attackrange.png delete mode 100644 WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png delete mode 100644 WebHostLib/static/static/icons/sc2/widowmine.png delete mode 100644 WebHostLib/static/static/icons/sc2/widowminehidden.png delete mode 100644 WebHostLib/static/styles/lttp-tracker.css delete mode 100644 WebHostLib/static/styles/player-options.css create mode 100644 WebHostLib/static/styles/playerOptions/playerOptions.css create mode 100644 WebHostLib/static/styles/playerOptions/playerOptions.css.map create mode 100644 WebHostLib/static/styles/playerOptions/playerOptions.scss create mode 100644 WebHostLib/static/styles/sc2Tracker.css delete mode 100644 WebHostLib/static/styles/sc2wolTracker.css create mode 100644 WebHostLib/static/styles/tracker__ALinkToThePast.css delete mode 100644 WebHostLib/static/styles/weighted-options.css create mode 100644 WebHostLib/static/styles/weightedOptions/weightedOptions.css create mode 100644 WebHostLib/static/styles/weightedOptions/weightedOptions.css.map create mode 100644 WebHostLib/static/styles/weightedOptions/weightedOptions.scss delete mode 100644 WebHostLib/templates/lttpTracker.html create mode 100644 WebHostLib/templates/multispheretracker.html delete mode 100644 WebHostLib/templates/ootTracker.html delete mode 100644 WebHostLib/templates/player-options.html create mode 100644 WebHostLib/templates/playerOptions/macros.html create mode 100644 WebHostLib/templates/playerOptions/playerOptions.html create mode 100644 WebHostLib/templates/tracker__Starcraft2.html delete mode 100644 WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html delete mode 100644 WebHostLib/templates/weighted-options.html create mode 100644 WebHostLib/templates/weightedOptions/macros.html create mode 100644 WebHostLib/templates/weightedOptions/weightedOptions.html create mode 100644 data/yatta.ico create mode 100644 data/yatta.png create mode 100644 docs/apworld_dev_faq.md delete mode 100644 docs/img/creepy-castle-directory.png delete mode 100644 docs/img/gato-roboto-directory.png delete mode 100644 docs/img/heavy-bullets-data-directory.png delete mode 100644 docs/img/heavy-bullets-directory.png delete mode 100644 docs/img/stardew-valley-directory.png create mode 100644 intset.h delete mode 100644 playerSettings.yaml create mode 100644 test/cpp/CMakeLists.txt create mode 100644 test/cpp/README.md create mode 100644 test/cpp/intset/CMakeLists.txt create mode 100644 test/cpp/intset/test_intset.cpp create mode 100644 test/general/test_client_server_interaction.py create mode 100644 test/general/test_player_options.py rename {worlds/stardew_valley/test/checks => test/hosting}/__init__.py (100%) create mode 100644 test/hosting/__main__.py create mode 100644 test/hosting/client.py create mode 100644 test/hosting/generate.py create mode 100644 test/hosting/serve.py create mode 100644 test/hosting/webhost.py create mode 100644 test/hosting/world.py create mode 100644 test/options/__init__.py create mode 100644 test/options/test_option_classes.py create mode 100644 test/programs/test_common_client.py create mode 100644 test/webhost/test_descriptions.py create mode 100644 test/webhost/test_host_room.py create mode 100644 typings/kivy/core/window.pyi create mode 100644 typings/kivy/event.pyi create mode 100644 typings/kivy/uix/boxlayout.pyi create mode 100644 typings/schema/__init__.pyi create mode 100644 worlds/ahit/Client.py create mode 100644 worlds/ahit/DeathWishLocations.py create mode 100644 worlds/ahit/DeathWishRules.py create mode 100644 worlds/ahit/Items.py create mode 100644 worlds/ahit/Locations.py create mode 100644 worlds/ahit/Options.py create mode 100644 worlds/ahit/Regions.py create mode 100644 worlds/ahit/Rules.py create mode 100644 worlds/ahit/Types.py create mode 100644 worlds/ahit/__init__.py create mode 100644 worlds/ahit/docs/en_A Hat in Time.md create mode 100644 worlds/ahit/docs/setup_en.md create mode 100644 worlds/ahit/test/__init__.py create mode 100644 worlds/ahit/test/test_acts.py create mode 100644 worlds/apsudoku/__init__.py create mode 100644 worlds/apsudoku/docs/en_Sudoku.md create mode 100644 worlds/apsudoku/docs/setup_en.md create mode 100644 worlds/aquaria/Items.py create mode 100644 worlds/aquaria/Locations.py create mode 100644 worlds/aquaria/Options.py create mode 100755 worlds/aquaria/Regions.py create mode 100644 worlds/aquaria/__init__.py create mode 100644 worlds/aquaria/docs/en_Aquaria.md create mode 100644 worlds/aquaria/docs/fr_Aquaria.md create mode 100644 worlds/aquaria/docs/setup_en.md create mode 100644 worlds/aquaria/docs/setup_fr.md create mode 100644 worlds/aquaria/test/__init__.py create mode 100644 worlds/aquaria/test/test_beast_form_access.py create mode 100644 worlds/aquaria/test/test_bind_song_access.py create mode 100644 worlds/aquaria/test/test_bind_song_option_access.py create mode 100644 worlds/aquaria/test/test_confined_home_water.py create mode 100644 worlds/aquaria/test/test_dual_song_access.py create mode 100644 worlds/aquaria/test/test_energy_form_access.py create mode 100644 worlds/aquaria/test/test_fish_form_access.py create mode 100644 worlds/aquaria/test/test_li_song_access.py create mode 100644 worlds/aquaria/test/test_light_access.py create mode 100644 worlds/aquaria/test/test_nature_form_access.py create mode 100644 worlds/aquaria/test/test_no_progression_hard_hidden_locations.py create mode 100644 worlds/aquaria/test/test_progression_hard_hidden_locations.py create mode 100644 worlds/aquaria/test/test_spirit_form_access.py create mode 100644 worlds/aquaria/test/test_sun_form_access.py create mode 100644 worlds/aquaria/test/test_unconfine_home_water_via_both.py create mode 100644 worlds/aquaria/test/test_unconfine_home_water_via_energy_door.py create mode 100644 worlds/aquaria/test/test_unconfine_home_water_via_transturtle.py delete mode 100644 worlds/bk_sudoku/__init__.py delete mode 100644 worlds/bk_sudoku/docs/de_Sudoku.md delete mode 100644 worlds/bk_sudoku/docs/en_Sudoku.md delete mode 100644 worlds/bk_sudoku/docs/setup_de.md delete mode 100644 worlds/bk_sudoku/docs/setup_en.md create mode 100644 worlds/bomb_rush_cyberfunk/Items.py create mode 100644 worlds/bomb_rush_cyberfunk/Locations.py create mode 100644 worlds/bomb_rush_cyberfunk/Options.py create mode 100644 worlds/bomb_rush_cyberfunk/Regions.py create mode 100644 worlds/bomb_rush_cyberfunk/Rules.py create mode 100644 worlds/bomb_rush_cyberfunk/__init__.py create mode 100644 worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md create mode 100644 worlds/bomb_rush_cyberfunk/docs/setup_en.md create mode 100644 worlds/bomb_rush_cyberfunk/test/__init__.py create mode 100644 worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py create mode 100644 worlds/bomb_rush_cyberfunk/test/test_options.py create mode 100644 worlds/bomb_rush_cyberfunk/test/test_rep_items.py create mode 100644 worlds/celeste64/CHANGELOG.md create mode 100644 worlds/cv64/__init__.py create mode 100644 worlds/cv64/aesthetics.py create mode 100644 worlds/cv64/client.py create mode 100644 worlds/cv64/data/APLogo-LICENSE.txt create mode 100644 worlds/cv64/data/ap_icons.bin create mode 100644 worlds/cv64/data/ename.py create mode 100644 worlds/cv64/data/iname.py create mode 100644 worlds/cv64/data/lname.py create mode 100644 worlds/cv64/data/patches.py create mode 100644 worlds/cv64/data/rname.py create mode 100644 worlds/cv64/docs/en_Castlevania 64.md create mode 100644 worlds/cv64/docs/obscure_checks.md create mode 100644 worlds/cv64/docs/setup_en.md create mode 100644 worlds/cv64/entrances.py create mode 100644 worlds/cv64/items.py create mode 100644 worlds/cv64/locations.py create mode 100644 worlds/cv64/lzkn64.py create mode 100644 worlds/cv64/options.py create mode 100644 worlds/cv64/regions.py create mode 100644 worlds/cv64/rom.py create mode 100644 worlds/cv64/rules.py create mode 100644 worlds/cv64/src/drop_sub_weapon.c create mode 100644 worlds/cv64/src/print.c create mode 100644 worlds/cv64/src/print_text_ovl.c create mode 100644 worlds/cv64/stages.py create mode 100644 worlds/cv64/test/__init__.py create mode 100644 worlds/cv64/test/test_access.py create mode 100644 worlds/cv64/text.py create mode 100644 worlds/dkc3/CHANGELOG.md create mode 100644 worlds/dlcquest/option_groups.py create mode 100644 worlds/dlcquest/presets.py delete mode 100644 worlds/ffmq/data/entrances.yaml create mode 100644 worlds/ffmq/data/rooms.py delete mode 100644 worlds/ffmq/data/rooms.yaml create mode 100644 worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md create mode 100644 worlds/ffmq/docs/setup_fr.md create mode 100644 worlds/hk/GodhomeData.py create mode 100644 worlds/lingo/data/README.md create mode 100644 worlds/lingo/data/generated.dat create mode 100644 worlds/lingo/datatypes.py create mode 100644 worlds/lingo/test/TestDatafile.py create mode 100644 worlds/lingo/test/TestPilgrimage.py create mode 100644 worlds/lingo/test/TestPostgame.py create mode 100644 worlds/lingo/test/TestSunwarps.py create mode 100644 worlds/lingo/utils/__init__.py create mode 100644 worlds/lingo/utils/pickle_static_data.py create mode 100644 worlds/mlss/Client.py create mode 100644 worlds/mlss/Data.py create mode 100644 worlds/mlss/Items.py create mode 100644 worlds/mlss/Locations.py create mode 100644 worlds/mlss/Names/LocationName.py create mode 100644 worlds/mlss/Options.py create mode 100644 worlds/mlss/Regions.py create mode 100644 worlds/mlss/Rom.py create mode 100644 worlds/mlss/Rules.py create mode 100644 worlds/mlss/StateLogic.py create mode 100644 worlds/mlss/__init__.py create mode 100644 worlds/mlss/data/basepatch.bsdiff create mode 100644 worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md create mode 100644 worlds/mlss/docs/setup_en.md create mode 100644 worlds/musedash/test/TestTrapOption.py create mode 100644 worlds/pokemon_emerald/CHANGELOG.md create mode 100644 worlds/pokemon_emerald/data/regions/battle_frontier.json rename worlds/pokemon_emerald/data/regions/{unused => }/islands.json (73%) delete mode 100644 worlds/pokemon_emerald/data/regions/unused/routes.json create mode 100644 worlds/pokemon_emerald/data/trade_pokemon_schema.json rename worlds/pokemon_emerald/{data/README.md => docs/region data.md} (74%) create mode 100644 worlds/pokemon_emerald/docs/setup_sv.md rename worlds/pokemon_emerald/{README.md => docs/warps.md} (86%) create mode 100644 worlds/pokemon_emerald/docs/wonder trades.md create mode 100644 worlds/pokemon_emerald/opponents.py create mode 100644 worlds/sa2b/CHANGELOG.md create mode 100644 worlds/sc2/Client.py create mode 100644 worlds/sc2/ClientGui.py create mode 100644 worlds/sc2/ItemGroups.py create mode 100644 worlds/sc2/ItemNames.py create mode 100644 worlds/sc2/Items.py create mode 100644 worlds/sc2/Locations.py create mode 100644 worlds/sc2/MissionTables.py create mode 100644 worlds/sc2/Options.py create mode 100644 worlds/sc2/PoolFilter.py create mode 100644 worlds/sc2/Regions.py create mode 100644 worlds/sc2/Rules.py create mode 100644 worlds/sc2/Starcraft2.kv create mode 100644 worlds/sc2/__init__.py create mode 100644 worlds/sc2/docs/contributors.md create mode 100644 worlds/sc2/docs/en_Starcraft 2.md create mode 100644 worlds/sc2/docs/fr_Starcraft 2.md create mode 100644 worlds/sc2/docs/setup_en.md create mode 100644 worlds/sc2/docs/setup_fr.md rename worlds/{sc2wol => sc2}/requirements.txt (62%) create mode 100644 worlds/sc2/test/__init__.py create mode 100644 worlds/sc2/test/test_Regions.py create mode 100644 worlds/sc2/test/test_base.py create mode 100644 worlds/sc2/test/test_options.py delete mode 100644 worlds/sc2wol/Client.py delete mode 100644 worlds/sc2wol/Items.py delete mode 100644 worlds/sc2wol/Locations.py delete mode 100644 worlds/sc2wol/LogicMixin.py delete mode 100644 worlds/sc2wol/MissionTables.py delete mode 100644 worlds/sc2wol/Options.py delete mode 100644 worlds/sc2wol/PoolFilter.py delete mode 100644 worlds/sc2wol/Starcraft2.kv delete mode 100644 worlds/sc2wol/__init__.py delete mode 100644 worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md delete mode 100644 worlds/sc2wol/docs/setup_en.md create mode 100644 worlds/shorthike/Items.py create mode 100644 worlds/shorthike/Locations.py create mode 100644 worlds/shorthike/Options.py create mode 100644 worlds/shorthike/Rules.py create mode 100644 worlds/shorthike/__init__.py create mode 100644 worlds/shorthike/docs/en_A Short Hike.md create mode 100644 worlds/shorthike/docs/setup_en.md create mode 100644 worlds/smw/CHANGELOG.md create mode 100644 worlds/smw/Presets.py create mode 100644 worlds/smw/data/blocksanity.json create mode 100644 worlds/smw/data/graphics/indicators.bin create mode 100644 worlds/smw/data/palettes/level/castle_pillars/agnus_castle.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/cheese.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/chocolate_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/dark_aqua_marine.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/gold_caslte.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/keves_castle.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/pink_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/purple_pink.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/sand_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/sand_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/whatsapp.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/dark_lava.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/dark_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/forgotten_temple.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/original_water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/sand_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/sand_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/whatsapp.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/cheese.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/grand_marshall.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/hot_wall.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/original.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/sand_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/brawler_pink.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/cheese.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/dark_aqua_marine.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/original_brown.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/original_water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/red_castle.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/underwater.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/whatsapp.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_red.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_teal.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/bright_magma.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/dark_red.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/glowing_mushroom.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/green_depths.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/magma_cave.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_chocolate.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_ice.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/toxic.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/toxic_moss.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/bocchi_rock_hair_cube_things.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/brawler_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/layer_2.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/pyra_mythra_ft_pneuma.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/toxic.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/charcoal.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/cloudy.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/cotton_candy.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/original_orange.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/agnian_queen.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/frozen.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/halloween.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/kevesi_queen.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/original_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/original_fall.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/snow_dark_leaves.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/snow_green_leaves.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/brawler_cyan.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/brawler_orange.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/brawler_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/creepypasta.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/crimson_house.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/golden_house.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/halloween_pallet.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/orange_lights.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/evening_exit.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/golden_house.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/original.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/original_blue_door.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/underwater.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/pizza.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/shukfr.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/volcanic_rock.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/autumn.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/brawler.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/brawler_atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/brawler_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/deep_forest.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/myon.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/pizza.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/snow_dark_leaves.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/snow_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/winter.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/brawler_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/mogumogu.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/nocturno.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/original.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/sunsetish_grass_hills.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/toothpaste.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/brawler_lifeless.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/classic_sm.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/dry_hills.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/late_sandish.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/recksfr.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/sakura_hills.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/dark.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/napolitano.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_choco_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_ice.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_volcanic_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_white_2.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/recks.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/thanks_doc.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/brawler.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/evening.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/mahogany.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/not_quite_dawnbreak.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/original.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/riesgo_de_chubascos.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/argent_cave.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/glowing_mushroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/green_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/original.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/really_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/toxic.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/greenshroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/oilshroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/original_yellow.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/riesgo_de_chubascos.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/autumn.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/count_shroomcula.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/cursed_gold.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/dark_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/lifeless_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/original.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/snow_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/snow_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/atardecer_naranjo.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/atardecer_verde.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/future.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/original.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/riesgo_de_chubascos_azul.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/riesgo_de_chubascos_cafe.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/riesgo_de_chubascos_negro.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/watermelon_skies.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/brightshroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_ice.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_cafe.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_negro.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/shuk_ft_reyn.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/cool.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/dark_night.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/halloween.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/light_pollution.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/midas.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/original_night.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/purpleish_night.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/riesgo_de_chubascos.mw3 create mode 100644 worlds/smw/data/palettes/level/palettes.json create mode 100644 worlds/smw/data/palettes/level/ship_exterior/blue_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/doc_ship.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/grey_ship.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/original.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/reddish.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/blue_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/bocchi_hitori.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/bocchi_rock.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/brawler.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/grey_ship.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/original.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/blue_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/brawler_brown.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/cafe_claro.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/color_de_gato.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/color_del_gato_2.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/green_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/gris.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/mario_pants.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/monado.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/morado.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/negro.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/onigiria.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/original.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/original_bonus.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/pink.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/red_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/verde.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/verde_agua.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/yellow_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/youbonus.mw3 create mode 100644 worlds/smw/data/palettes/level/water/dark_water.mw3 create mode 100644 worlds/smw/data/palettes/level/water/deep_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/water/deep_chocolate.mw3 create mode 100644 worlds/smw/data/palettes/level/water/harmless_magma.mw3 create mode 100644 worlds/smw/data/palettes/level/water/murky.mw3 create mode 100644 worlds/smw/data/palettes/level/water/oil_spill.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_brown.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/water/pickle_juice.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/brawler_green.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/choco.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/mogumogu.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/monocromo.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/neon.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/nieve.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/night.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/nocturno.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/original.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/strong_sun.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/sunsetish_grass_hills.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/burnt_forest.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/dark_forest.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/halloween.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/ice_forest.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/lost_woods.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/original.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/map/main/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/map/main/brawler.mw3 create mode 100644 worlds/smw/data/palettes/map/main/cake_frosting.mw3 create mode 100644 worlds/smw/data/palettes/map/main/invertido.mw3 create mode 100644 worlds/smw/data/palettes/map/main/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/main/morning.mw3 create mode 100644 worlds/smw/data/palettes/map/main/night.mw3 create mode 100644 worlds/smw/data/palettes/map/main/night_time.mw3 create mode 100644 worlds/smw/data/palettes/map/main/original.mw3 create mode 100644 worlds/smw/data/palettes/map/main/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/main/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/main/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/map/palettes.json create mode 100644 worlds/smw/data/palettes/map/special/black_out.mw3 create mode 100644 worlds/smw/data/palettes/map/special/blood_star.mw3 create mode 100644 worlds/smw/data/palettes/map/special/brawler.mw3 create mode 100644 worlds/smw/data/palettes/map/special/green.mw3 create mode 100644 worlds/smw/data/palettes/map/special/light_pollution_map.mw3 create mode 100644 worlds/smw/data/palettes/map/special/original.mw3 create mode 100644 worlds/smw/data/palettes/map/special/purple.mw3 create mode 100644 worlds/smw/data/palettes/map/special/white_special.mw3 create mode 100644 worlds/smw/data/palettes/map/star/blood_moon.mw3 create mode 100644 worlds/smw/data/palettes/map/star/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/star/mountain_top.mw3 create mode 100644 worlds/smw/data/palettes/map/star/original.mw3 create mode 100644 worlds/smw/data/palettes/map/star/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/star/pink_star.mw3 create mode 100644 worlds/smw/data/palettes/map/star/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/star/yellow_star.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/Tamaulipas.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/bowser.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/castle_colors.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/dark cave.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/dream_world.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/fire cave.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/invertido.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/orange.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/original.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/purple_blue.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/snow.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/DOMO.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/aqua_marine.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/dark cave.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/fire cave.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/gold_mine.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/invertido.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/original.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/purple.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/witches_cauldron.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/gum.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/lava_island.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/original.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/sunset.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/tritanopia.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/yochis_ailand.mw3 create mode 100644 worlds/soe/test/test_sniffamizer.py delete mode 100644 worlds/stardew_valley/bundles.py create mode 100644 worlds/stardew_valley/bundles/__init__.py create mode 100644 worlds/stardew_valley/bundles/bundle.py create mode 100644 worlds/stardew_valley/bundles/bundle_item.py create mode 100644 worlds/stardew_valley/bundles/bundle_room.py create mode 100644 worlds/stardew_valley/bundles/bundles.py create mode 100644 worlds/stardew_valley/content/__init__.py create mode 100644 worlds/stardew_valley/content/content_packs.py create mode 100644 worlds/stardew_valley/content/feature/__init__.py create mode 100644 worlds/stardew_valley/content/feature/booksanity.py create mode 100644 worlds/stardew_valley/content/feature/cropsanity.py create mode 100644 worlds/stardew_valley/content/feature/fishsanity.py create mode 100644 worlds/stardew_valley/content/feature/friendsanity.py create mode 100644 worlds/stardew_valley/content/game_content.py create mode 100644 worlds/stardew_valley/content/mod_registry.py create mode 100644 worlds/stardew_valley/content/mods/__init__.py create mode 100644 worlds/stardew_valley/content/mods/alecto.py create mode 100644 worlds/stardew_valley/content/mods/archeology.py create mode 100644 worlds/stardew_valley/content/mods/big_backpack.py create mode 100644 worlds/stardew_valley/content/mods/boarding_house.py create mode 100644 worlds/stardew_valley/content/mods/deepwoods.py create mode 100644 worlds/stardew_valley/content/mods/distant_lands.py create mode 100644 worlds/stardew_valley/content/mods/jasper.py create mode 100644 worlds/stardew_valley/content/mods/magic.py create mode 100644 worlds/stardew_valley/content/mods/npc_mods.py create mode 100644 worlds/stardew_valley/content/mods/skill_mods.py create mode 100644 worlds/stardew_valley/content/mods/skull_cavern_elevator.py create mode 100644 worlds/stardew_valley/content/mods/sve.py create mode 100644 worlds/stardew_valley/content/mods/tractor.py create mode 100644 worlds/stardew_valley/content/override.py create mode 100644 worlds/stardew_valley/content/unpacking.py create mode 100644 worlds/stardew_valley/content/vanilla/__init__.py create mode 100644 worlds/stardew_valley/content/vanilla/base.py create mode 100644 worlds/stardew_valley/content/vanilla/ginger_island.py create mode 100644 worlds/stardew_valley/content/vanilla/pelican_town.py create mode 100644 worlds/stardew_valley/content/vanilla/qi_board.py create mode 100644 worlds/stardew_valley/content/vanilla/the_desert.py create mode 100644 worlds/stardew_valley/content/vanilla/the_farm.py create mode 100644 worlds/stardew_valley/content/vanilla/the_mines.py create mode 100644 worlds/stardew_valley/data/artisan.py delete mode 100644 worlds/stardew_valley/data/common_data.py create mode 100644 worlds/stardew_valley/data/craftable_data.py delete mode 100644 worlds/stardew_valley/data/crops.csv delete mode 100644 worlds/stardew_valley/data/crops_data.py create mode 100644 worlds/stardew_valley/data/harvest.py create mode 100644 worlds/stardew_valley/data/recipe_source.py create mode 100644 worlds/stardew_valley/data/requirement.py create mode 100644 worlds/stardew_valley/data/shipsanity_unimplemented_items.csv create mode 100644 worlds/stardew_valley/data/shop.py create mode 100644 worlds/stardew_valley/data/skill.py create mode 100644 worlds/stardew_valley/early_items.py delete mode 100644 worlds/stardew_valley/logic.py create mode 100644 worlds/stardew_valley/logic/__init__.py create mode 100644 worlds/stardew_valley/logic/ability_logic.py create mode 100644 worlds/stardew_valley/logic/action_logic.py create mode 100644 worlds/stardew_valley/logic/animal_logic.py create mode 100644 worlds/stardew_valley/logic/arcade_logic.py create mode 100644 worlds/stardew_valley/logic/artisan_logic.py create mode 100644 worlds/stardew_valley/logic/base_logic.py create mode 100644 worlds/stardew_valley/logic/book_logic.py create mode 100644 worlds/stardew_valley/logic/building_logic.py create mode 100644 worlds/stardew_valley/logic/bundle_logic.py create mode 100644 worlds/stardew_valley/logic/combat_logic.py create mode 100644 worlds/stardew_valley/logic/cooking_logic.py create mode 100644 worlds/stardew_valley/logic/crafting_logic.py create mode 100644 worlds/stardew_valley/logic/farming_logic.py create mode 100644 worlds/stardew_valley/logic/fishing_logic.py create mode 100644 worlds/stardew_valley/logic/gift_logic.py create mode 100644 worlds/stardew_valley/logic/grind_logic.py create mode 100644 worlds/stardew_valley/logic/harvesting_logic.py create mode 100644 worlds/stardew_valley/logic/has_logic.py create mode 100644 worlds/stardew_valley/logic/logic.py create mode 100644 worlds/stardew_valley/logic/logic_and_mods_design.md create mode 100644 worlds/stardew_valley/logic/logic_event.py create mode 100644 worlds/stardew_valley/logic/mine_logic.py create mode 100644 worlds/stardew_valley/logic/money_logic.py create mode 100644 worlds/stardew_valley/logic/monster_logic.py create mode 100644 worlds/stardew_valley/logic/museum_logic.py create mode 100644 worlds/stardew_valley/logic/pet_logic.py create mode 100644 worlds/stardew_valley/logic/quality_logic.py create mode 100644 worlds/stardew_valley/logic/quest_logic.py create mode 100644 worlds/stardew_valley/logic/received_logic.py create mode 100644 worlds/stardew_valley/logic/region_logic.py create mode 100644 worlds/stardew_valley/logic/relationship_logic.py create mode 100644 worlds/stardew_valley/logic/requirement_logic.py create mode 100644 worlds/stardew_valley/logic/season_logic.py create mode 100644 worlds/stardew_valley/logic/shipping_logic.py create mode 100644 worlds/stardew_valley/logic/skill_logic.py create mode 100644 worlds/stardew_valley/logic/source_logic.py create mode 100644 worlds/stardew_valley/logic/special_order_logic.py create mode 100644 worlds/stardew_valley/logic/time_logic.py create mode 100644 worlds/stardew_valley/logic/tool_logic.py create mode 100644 worlds/stardew_valley/logic/traveling_merchant_logic.py create mode 100644 worlds/stardew_valley/logic/wallet_logic.py create mode 100644 worlds/stardew_valley/logic/walnut_logic.py delete mode 100644 worlds/stardew_valley/mods/logic/buildings.py create mode 100644 worlds/stardew_valley/mods/logic/buildings_logic.py delete mode 100644 worlds/stardew_valley/mods/logic/deepwoods.py create mode 100644 worlds/stardew_valley/mods/logic/deepwoods_logic.py create mode 100644 worlds/stardew_valley/mods/logic/elevator_logic.py create mode 100644 worlds/stardew_valley/mods/logic/item_logic.py delete mode 100644 worlds/stardew_valley/mods/logic/magic.py create mode 100644 worlds/stardew_valley/mods/logic/magic_logic.py create mode 100644 worlds/stardew_valley/mods/logic/mod_logic.py create mode 100644 worlds/stardew_valley/mods/logic/mod_skills_levels.py delete mode 100644 worlds/stardew_valley/mods/logic/quests.py create mode 100644 worlds/stardew_valley/mods/logic/quests_logic.py delete mode 100644 worlds/stardew_valley/mods/logic/skills.py create mode 100644 worlds/stardew_valley/mods/logic/skills_logic.py delete mode 100644 worlds/stardew_valley/mods/logic/skullcavernelevator.py delete mode 100644 worlds/stardew_valley/mods/logic/special_orders.py create mode 100644 worlds/stardew_valley/mods/logic/special_orders_logic.py create mode 100644 worlds/stardew_valley/mods/logic/sve_logic.py create mode 100644 worlds/stardew_valley/mods/mod_monster_locations.py create mode 100644 worlds/stardew_valley/option_groups.py delete mode 100644 worlds/stardew_valley/stardew_rule.py create mode 100644 worlds/stardew_valley/stardew_rule/__init__.py create mode 100644 worlds/stardew_valley/stardew_rule/base.py create mode 100644 worlds/stardew_valley/stardew_rule/indirect_connection.py create mode 100644 worlds/stardew_valley/stardew_rule/literal.py create mode 100644 worlds/stardew_valley/stardew_rule/protocol.py create mode 100644 worlds/stardew_valley/stardew_rule/rule_explain.py create mode 100644 worlds/stardew_valley/stardew_rule/state.py create mode 100644 worlds/stardew_valley/strings/ap_names/ap_option_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/ap_weapon_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/community_upgrade_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/event_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/mods/mod_items.py create mode 100644 worlds/stardew_valley/strings/book_names.py create mode 100644 worlds/stardew_valley/strings/bundle_names.py create mode 100644 worlds/stardew_valley/strings/currency_names.py create mode 100644 worlds/stardew_valley/strings/decoration_names.py create mode 100644 worlds/stardew_valley/strings/monster_names.py create mode 100644 worlds/stardew_valley/strings/quality_names.py create mode 100644 worlds/stardew_valley/test/TestBooksanity.py create mode 100644 worlds/stardew_valley/test/TestCrops.py create mode 100644 worlds/stardew_valley/test/TestDynamicGoals.py create mode 100644 worlds/stardew_valley/test/TestFarmType.py create mode 100644 worlds/stardew_valley/test/TestFill.py create mode 100644 worlds/stardew_valley/test/TestFishsanity.py create mode 100644 worlds/stardew_valley/test/TestFriendsanity.py delete mode 100644 worlds/stardew_valley/test/TestLogicSimplification.py create mode 100644 worlds/stardew_valley/test/TestMultiplePlayers.py create mode 100644 worlds/stardew_valley/test/TestNumberLocations.py create mode 100644 worlds/stardew_valley/test/TestOptionFlags.py create mode 100644 worlds/stardew_valley/test/TestOptionsPairs.py create mode 100644 worlds/stardew_valley/test/TestPresets.py delete mode 100644 worlds/stardew_valley/test/TestRules.py create mode 100644 worlds/stardew_valley/test/TestStardewRule.py create mode 100644 worlds/stardew_valley/test/TestStartInventory.py create mode 100644 worlds/stardew_valley/test/TestWalnutsanity.py create mode 100644 worlds/stardew_valley/test/assertion/__init__.py create mode 100644 worlds/stardew_valley/test/assertion/goal_assert.py create mode 100644 worlds/stardew_valley/test/assertion/mod_assert.py create mode 100644 worlds/stardew_valley/test/assertion/option_assert.py create mode 100644 worlds/stardew_valley/test/assertion/rule_assert.py create mode 100644 worlds/stardew_valley/test/assertion/world_assert.py delete mode 100644 worlds/stardew_valley/test/checks/goal_checks.py delete mode 100644 worlds/stardew_valley/test/checks/option_checks.py delete mode 100644 worlds/stardew_valley/test/checks/world_checks.py create mode 100644 worlds/stardew_valley/test/content/TestArtisanEquipment.py create mode 100644 worlds/stardew_valley/test/content/TestGingerIsland.py create mode 100644 worlds/stardew_valley/test/content/TestPelicanTown.py create mode 100644 worlds/stardew_valley/test/content/TestQiBoard.py create mode 100644 worlds/stardew_valley/test/content/__init__.py create mode 100644 worlds/stardew_valley/test/content/feature/TestFriendsanity.py create mode 100644 worlds/stardew_valley/test/content/feature/__init__.py create mode 100644 worlds/stardew_valley/test/content/mods/TestDeepwoods.py create mode 100644 worlds/stardew_valley/test/content/mods/TestJasper.py create mode 100644 worlds/stardew_valley/test/content/mods/TestSVE.py create mode 100644 worlds/stardew_valley/test/content/mods/__init__.py create mode 100644 worlds/stardew_valley/test/long/TestPreRolledRandomness.py create mode 100644 worlds/stardew_valley/test/performance/TestPerformance.py create mode 100644 worlds/stardew_valley/test/performance/__init__.py create mode 100644 worlds/stardew_valley/test/rules/TestArcades.py create mode 100644 worlds/stardew_valley/test/rules/TestBuildings.py create mode 100644 worlds/stardew_valley/test/rules/TestBundles.py create mode 100644 worlds/stardew_valley/test/rules/TestCookingRecipes.py create mode 100644 worlds/stardew_valley/test/rules/TestCraftingRecipes.py create mode 100644 worlds/stardew_valley/test/rules/TestDonations.py create mode 100644 worlds/stardew_valley/test/rules/TestFriendship.py create mode 100644 worlds/stardew_valley/test/rules/TestMuseum.py create mode 100644 worlds/stardew_valley/test/rules/TestShipping.py create mode 100644 worlds/stardew_valley/test/rules/TestSkills.py create mode 100644 worlds/stardew_valley/test/rules/TestStateRules.py create mode 100644 worlds/stardew_valley/test/rules/TestTools.py create mode 100644 worlds/stardew_valley/test/rules/TestWeapons.py create mode 100644 worlds/stardew_valley/test/rules/__init__.py create mode 100644 worlds/stardew_valley/test/script/__init__.py create mode 100644 worlds/stardew_valley/test/script/benchmark_locations.py create mode 100644 worlds/stardew_valley/test/stability/StabilityOutputScript.py create mode 100644 worlds/stardew_valley/test/stability/TestStability.py create mode 100644 worlds/stardew_valley/test/stability/TestUniversalTracker.py create mode 100644 worlds/stardew_valley/test/stability/__init__.py create mode 100644 worlds/wargroove2/Items.py create mode 100644 worlds/wargroove2/Levels.py create mode 100644 worlds/wargroove2/Locations.py create mode 100644 worlds/wargroove2/Options.py create mode 100644 worlds/wargroove2/Presets.py create mode 100644 worlds/wargroove2/RegionFilter.py create mode 100644 worlds/wargroove2/Regions.py create mode 100644 worlds/wargroove2/Rules.py create mode 100644 worlds/wargroove2/Wargroove2.kv create mode 100644 worlds/wargroove2/__init__.py create mode 100644 worlds/wargroove2/client.py create mode 100644 worlds/wargroove2/data/mods/ArchipelagoMod/maps.dat create mode 100644 worlds/wargroove2/data/mods/ArchipelagoMod/mod.dat create mode 100644 worlds/wargroove2/data/mods/ArchipelagoMod/modAssets.dat create mode 100644 worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp create mode 100644 worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak create mode 100644 worlds/wargroove2/docs/en_Wargroove 2.md create mode 100644 worlds/wargroove2/docs/wargroove2_en.md create mode 100644 worlds/wargroove2/levels/A_Ribbitting_Time.json create mode 100644 worlds/wargroove2/levels/Air_Support.json create mode 100644 worlds/wargroove2/levels/Beached.json create mode 100644 worlds/wargroove2/levels/Bridge_Brigade.json create mode 100644 worlds/wargroove2/levels/Cherrystone_Landing.json create mode 100644 worlds/wargroove2/levels/Dark_Mirror.json create mode 100644 worlds/wargroove2/levels/Dementia_Castle.json create mode 100644 worlds/wargroove2/levels/Den-Two-Away.json create mode 100644 worlds/wargroove2/levels/Disastrous_Crossing.json create mode 100644 worlds/wargroove2/levels/Doomed_Metropolis.json create mode 100644 worlds/wargroove2/levels/Enmity_Cliffs.json create mode 100644 worlds/wargroove2/levels/Finishing_Blow.json create mode 100644 worlds/wargroove2/levels/Fortification.json create mode 100644 worlds/wargroove2/levels/Frantic_Inlet.json create mode 100644 worlds/wargroove2/levels/Gnarled_Mountaintop.json create mode 100644 worlds/wargroove2/levels/Gold_Rush.json create mode 100644 worlds/wargroove2/levels/Grand_Theft_Village.json create mode 100644 worlds/wargroove2/levels/Kraken_Strait.json create mode 100644 worlds/wargroove2/levels/Nuru_Vengeance.json create mode 100644 worlds/wargroove2/levels/Operation_Seagull.json create mode 100644 worlds/wargroove2/levels/Portal_Peril.json create mode 100644 worlds/wargroove2/levels/Precarious_Cliffs.json create mode 100644 worlds/wargroove2/levels/Riflemen_Blockade.json create mode 100644 worlds/wargroove2/levels/Skydiving.json create mode 100644 worlds/wargroove2/levels/Slippery_Bridge.json create mode 100644 worlds/wargroove2/levels/Spire_Fire.json create mode 100644 worlds/wargroove2/levels/Split_Valley.json create mode 100644 worlds/wargroove2/levels/Sunken_Forest.json create mode 100644 worlds/wargroove2/levels/Tenris_Mistake.json create mode 100644 worlds/wargroove2/levels/Terrible_Tributaries.json create mode 100644 worlds/wargroove2/levels/Towers_of_the_Abyss.json create mode 100644 worlds/wargroove2/levels/Wagon_Freeway.json rename worlds/witness/{ => data}/WitnessItems.txt (89%) rename worlds/witness/{ => data}/WitnessLogic.txt (99%) rename worlds/witness/{ => data}/WitnessLogicExpert.txt (99%) rename worlds/witness/{ => data}/WitnessLogicVanilla.txt (99%) create mode 100644 worlds/witness/data/__init__.py create mode 100644 worlds/witness/data/item_definition_classes.py rename worlds/witness/{ => data}/settings/Audio_Logs.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Boat.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Complex_Additional_Panels.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Complex_Door_Panels.txt (97%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Complex_Doors.txt (98%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Elevators_Come_To_You.txt (100%) create mode 100644 worlds/witness/data/settings/Door_Shuffle/Obelisk_Keys.txt rename worlds/witness/{ => data}/settings/Door_Shuffle/Simple_Additional_Panels.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Simple_Doors.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Simple_Panels.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_All.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_Easy.txt (93%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_NoEclipse.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_Sides.txt (100%) rename worlds/witness/{ => data}/settings/Early_Caves.txt (100%) rename worlds/witness/{ => data}/settings/Early_Caves_Start.txt (100%) rename worlds/witness/{settings/Postgame/Caves.txt => data/settings/Exclusions/Caves_Except_Path_To_Challenge.txt} (100%) rename worlds/witness/{ => data}/settings/Exclusions/Disable_Unrandomized.txt (90%) rename worlds/witness/{ => data}/settings/Exclusions/Discards.txt (100%) create mode 100644 worlds/witness/data/settings/Exclusions/Vaults.txt rename worlds/witness/{ => data}/settings/Laser_Shuffle.txt (100%) rename worlds/witness/{ => data}/settings/Symbol_Shuffle.txt (100%) create mode 100644 worlds/witness/data/static_items.py create mode 100644 worlds/witness/data/static_locations.py create mode 100644 worlds/witness/data/static_logic.py rename worlds/witness/{ => data}/utils.py (70%) rename worlds/witness/{items.py => player_items.py} (59%) create mode 100644 worlds/witness/ruff.toml delete mode 100644 worlds/witness/settings/Exclusions/Vaults.txt delete mode 100644 worlds/witness/settings/Postgame/Beyond_Challenge.txt delete mode 100644 worlds/witness/settings/Postgame/Bottom_Floor_Discard.txt delete mode 100644 worlds/witness/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt delete mode 100644 worlds/witness/settings/Postgame/Challenge_Vault_Box.txt delete mode 100644 worlds/witness/settings/Postgame/Mountain_Lower.txt delete mode 100644 worlds/witness/settings/Postgame/Mountain_Upper.txt delete mode 100644 worlds/witness/settings/Postgame/Path_To_Challenge.txt delete mode 100644 worlds/witness/static_logic.py create mode 100644 worlds/witness/test/__init__.py create mode 100644 worlds/witness/test/test_auto_elevators.py create mode 100644 worlds/witness/test/test_disable_non_randomized.py create mode 100644 worlds/witness/test/test_door_shuffle.py create mode 100644 worlds/witness/test/test_ep_shuffle.py create mode 100644 worlds/witness/test/test_lasers.py create mode 100644 worlds/witness/test/test_roll_other_options.py create mode 100644 worlds/witness/test/test_symbol_shuffle.py create mode 100644 worlds/yoshisisland/Client.py create mode 100644 worlds/yoshisisland/Items.py create mode 100644 worlds/yoshisisland/Locations.py create mode 100644 worlds/yoshisisland/Options.py create mode 100644 worlds/yoshisisland/Regions.py create mode 100644 worlds/yoshisisland/Rom.py create mode 100644 worlds/yoshisisland/Rules.py create mode 100644 worlds/yoshisisland/__init__.py create mode 100644 worlds/yoshisisland/docs/en_Yoshi's Island.md create mode 100644 worlds/yoshisisland/docs/setup_en.md create mode 100644 worlds/yoshisisland/level_logic.py create mode 100644 worlds/yoshisisland/setup_bosses.py create mode 100644 worlds/yoshisisland/setup_game.py create mode 100644 worlds/yugioh06/__init__.py create mode 100644 worlds/yugioh06/boosterpacks.py create mode 100644 worlds/yugioh06/client_bh.py create mode 100644 worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md create mode 100644 worlds/yugioh06/docs/setup_en.md create mode 100644 worlds/yugioh06/fusions.py create mode 100644 worlds/yugioh06/items.py create mode 100644 worlds/yugioh06/locations.py create mode 100644 worlds/yugioh06/logic.py create mode 100644 worlds/yugioh06/opponents.py create mode 100644 worlds/yugioh06/options.py create mode 100644 worlds/yugioh06/patch.bsdiff4 create mode 100644 worlds/yugioh06/patches/draft.bsdiff4 create mode 100644 worlds/yugioh06/patches/ocg.bsdiff4 create mode 100644 worlds/yugioh06/rom.py create mode 100644 worlds/yugioh06/rom_values.py create mode 100644 worlds/yugioh06/ruff.toml create mode 100644 worlds/yugioh06/rules.py create mode 100644 worlds/yugioh06/structure_deck.py create mode 100644 worlds/zork_grand_inquisitor/LICENSE create mode 100644 worlds/zork_grand_inquisitor/__init__.py create mode 100644 worlds/zork_grand_inquisitor/client.py create mode 100644 worlds/zork_grand_inquisitor/data/__init__.py create mode 100644 worlds/zork_grand_inquisitor/data/entrance_rule_data.py create mode 100644 worlds/zork_grand_inquisitor/data/item_data.py create mode 100644 worlds/zork_grand_inquisitor/data/location_data.py create mode 100644 worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py create mode 100644 worlds/zork_grand_inquisitor/data/region_data.py create mode 100644 worlds/zork_grand_inquisitor/data_funcs.py create mode 100644 worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md create mode 100644 worlds/zork_grand_inquisitor/docs/setup_en.md create mode 100644 worlds/zork_grand_inquisitor/enums.py create mode 100644 worlds/zork_grand_inquisitor/game_controller.py create mode 100644 worlds/zork_grand_inquisitor/game_state_manager.py create mode 100644 worlds/zork_grand_inquisitor/options.py create mode 100644 worlds/zork_grand_inquisitor/requirements.txt create mode 100644 worlds/zork_grand_inquisitor/test/__init__.py create mode 100644 worlds/zork_grand_inquisitor/test/test_access.py create mode 100644 worlds/zork_grand_inquisitor/test/test_data_funcs.py create mode 100644 worlds/zork_grand_inquisitor/test/test_locations.py create mode 100644 worlds/zork_grand_inquisitor/world.py diff --git a/.github/pyright-config.json b/.github/pyright-config.json new file mode 100644 index 000000000000..6ad7fa5f19b5 --- /dev/null +++ b/.github/pyright-config.json @@ -0,0 +1,27 @@ +{ + "include": [ + "type_check.py", + "../worlds/AutoSNIClient.py", + "../Patch.py" + ], + + "exclude": [ + "**/__pycache__" + ], + + "stubPath": "../typings", + + "typeCheckingMode": "strict", + "reportImplicitOverride": "error", + "reportMissingImports": true, + "reportMissingTypeStubs": true, + + "pythonVersion": "3.8", + "pythonPlatform": "Windows", + + "executionEnvironments": [ + { + "root": ".." + } + ] +} diff --git a/.github/type_check.py b/.github/type_check.py new file mode 100644 index 000000000000..90d41722c9a5 --- /dev/null +++ b/.github/type_check.py @@ -0,0 +1,15 @@ +from pathlib import Path +import subprocess + +config = Path(__file__).parent / "pyright-config.json" + +command = ("pyright", "-p", str(config)) +print(" ".join(command)) + +try: + result = subprocess.run(command) +except FileNotFoundError as e: + print(f"{e} - Is pyright installed?") + exit(1) + +exit(result.returncode) diff --git a/.github/workflows/analyze-modified-files.yml b/.github/workflows/analyze-modified-files.yml index d01365745c96..c9995fa2d043 100644 --- a/.github/workflows/analyze-modified-files.yml +++ b/.github/workflows/analyze-modified-files.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Determine modified files (pull_request)" if: github.event_name == 'pull_request' @@ -50,7 +50,7 @@ jobs: run: | echo "diff=." >> $GITHUB_ENV - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: env.diff != '' with: python-version: 3.8 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a40084b9ab72..23c463fb947a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,13 @@ on: - '.github/workflows/build.yml' - 'setup.py' - 'requirements.txt' + - '*.iss' pull_request: paths: - '.github/workflows/build.yml' - 'setup.py' - 'requirements.txt' + - '*.iss' workflow_dispatch: env: @@ -25,19 +27,24 @@ jobs: build-win-py38: # RCs will still be built and signed by hand runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' - name: Download run-time dependencies run: | Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip Expand-Archive -Path enemizer.zip -DestinationPath EnemizerCLI -Force + choco install innosetup --version=6.2.2 --allow-downgrade - name: Build run: | python -m pip install --upgrade pip python setup.py build_exe --yes + if ( $? -eq $false ) { + Write-Error "setup.py failed!" + exit 1 + } $NAME="$(ls build | Select-String -Pattern 'exe')".Split('.',2)[1] $ZIP_NAME="Archipelago_$NAME.7z" echo "$NAME -> $ZIP_NAME" @@ -46,25 +53,63 @@ jobs: cd build Rename-Item "exe.$NAME" Archipelago 7z a -mx=9 -mhe=on -ms "../dist/$ZIP_NAME" Archipelago + Rename-Item Archipelago "exe.$NAME" # inno_setup.iss expects the original name + - name: Build Setup + run: | + & "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe" inno_setup.iss /DNO_SIGNTOOL + if ( $? -eq $false ) { + Write-Error "Building setup failed!" + exit 1 + } + $contents = Get-ChildItem -Path setups/*.exe -Force -Recurse + $SETUP_NAME=$contents[0].Name + echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV + - name: Check build loads expected worlds + shell: bash + run: | + cd build/exe* + mv Players/Templates/meta.yaml . + ls -1 Players/Templates | sort > setup-player-templates.txt + rm -R Players/Templates + timeout 30 ./ArchipelagoLauncher "Generate Template Options" || true + ls -1 Players/Templates | sort > generated-player-templates.txt + cmp setup-player-templates.txt generated-player-templates.txt \ + || diff setup-player-templates.txt generated-player-templates.txt + mv meta.yaml Players/Templates/ + - name: Test Generate + shell: bash + run: | + cd build/exe* + cp Players/Templates/Clique.yaml Players/ + timeout 30 ./ArchipelagoGenerate - name: Store 7z - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.ZIP_NAME }} path: dist/${{ env.ZIP_NAME }} + compression-level: 0 # .7z is incompressible by zip + if-no-files-found: error + retention-days: 7 # keep for 7 days, should be enough + - name: Store Setup + uses: actions/upload-artifact@v4 + with: + name: ${{ env.SETUP_NAME }} + path: setups/${{ env.SETUP_NAME }} + if-no-files-found: error retention-days: 7 # keep for 7 days, should be enough build-ubuntu2004: runs-on: ubuntu-20.04 steps: # - copy code below to release.yml - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install base dependencies run: | sudo apt update sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0 sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below - name: Get a recent python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install build-time dependencies @@ -91,7 +136,7 @@ jobs: echo -e "setup.py dist output:\n `ls dist`" cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd .. export TAR_NAME="${APPIMAGE_NAME%.AppImage}.tar.gz" - (cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -czvf ../dist/$TAR_NAME Archipelago && mv Archipelago "$DIR_NAME") + (cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -cv Archipelago | gzip -8 > ../dist/$TAR_NAME && mv Archipelago "$DIR_NAME") echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV # - copy code above to release.yml - @@ -99,15 +144,36 @@ jobs: run: | source venv/bin/activate python setup.py build_exe --yes + - name: Check build loads expected worlds + shell: bash + run: | + cd build/exe* + mv Players/Templates/meta.yaml . + ls -1 Players/Templates | sort > setup-player-templates.txt + rm -R Players/Templates + timeout 30 ./ArchipelagoLauncher "Generate Template Options" || true + ls -1 Players/Templates | sort > generated-player-templates.txt + cmp setup-player-templates.txt generated-player-templates.txt \ + || diff setup-player-templates.txt generated-player-templates.txt + mv meta.yaml Players/Templates/ + - name: Test Generate + shell: bash + run: | + cd build/exe* + cp Players/Templates/Clique.yaml Players/ + timeout 30 ./ArchipelagoGenerate - name: Store AppImage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.APPIMAGE_NAME }} path: dist/${{ env.APPIMAGE_NAME }} + if-no-files-found: error retention-days: 7 - name: Store .tar.gz - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.TAR_NAME }} path: dist/${{ env.TAR_NAME }} + compression-level: 0 # .gz is incompressible by zip + if-no-files-found: error retention-days: 7 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6aeb477a22d7..b0cfe35d2bc5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml new file mode 100644 index 000000000000..9492c83c9e53 --- /dev/null +++ b/.github/workflows/ctest.yml @@ -0,0 +1,54 @@ +# Run CMake / CTest C++ unit tests + +name: ctest + +on: + push: + paths: + - '**.cc?' + - '**.cpp' + - '**.cxx' + - '**.hh?' + - '**.hpp' + - '**.hxx' + - '**.CMakeLists' + - '.github/workflows/ctest.yml' + pull_request: + paths: + - '**.cc?' + - '**.cpp' + - '**.cxx' + - '**.hh?' + - '**.hpp' + - '**.hxx' + - '**.CMakeLists' + - '.github/workflows/ctest.yml' + +jobs: + ctest: + runs-on: ${{ matrix.os }} + name: Test C++ ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + - uses: ilammy/msvc-dev-cmd@v1 + if: startsWith(matrix.os,'windows') + - uses: Bacondish2023/setup-googletest@v1 + with: + build-type: 'Release' + - name: Build tests + run: | + cd test/cpp + mkdir build + cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Release + cmake --build build/ --config Release + ls + - name: Run tests + run: | + cd test/cpp + ctest --test-dir build/ -C Release --output-on-failure diff --git a/.github/workflows/label-pull-requests.yml b/.github/workflows/label-pull-requests.yml index e26f6f34a4d2..bc0f6999b6a8 100644 --- a/.github/workflows/label-pull-requests.yml +++ b/.github/workflows/label-pull-requests.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/labeler@v5 with: - sync-labels: true + sync-labels: false peer_review: name: 'Apply peer review label' needs: labeler diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc68a88b7651..3f8651d408e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # tag x.y.z will become "Archipelago x.y.z" - name: Create Release - uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf + uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a with: draft: true # don't publish right away, especially since windows build is added by hand prerelease: false @@ -35,14 +35,14 @@ jobs: - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # - code below copied from build.yml - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install base dependencies run: | sudo apt update sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0 sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below - name: Get a recent python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install build-time dependencies @@ -69,12 +69,12 @@ jobs: echo -e "setup.py dist output:\n `ls dist`" cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd .. export TAR_NAME="${APPIMAGE_NAME%.AppImage}.tar.gz" - (cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -czvf ../dist/$TAR_NAME Archipelago && mv Archipelago "$DIR_NAME") + (cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -cv Archipelago | gzip -8 > ../dist/$TAR_NAME && mv Archipelago "$DIR_NAME") echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV # - code above copied from build.yml - - name: Add to Release - uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf + uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a with: draft: true # see above prerelease: false diff --git a/.github/workflows/strict-type-check.yml b/.github/workflows/strict-type-check.yml new file mode 100644 index 000000000000..bafd572a26ae --- /dev/null +++ b/.github/workflows/strict-type-check.yml @@ -0,0 +1,33 @@ +name: type check + +on: + pull_request: + paths: + - "**.py" + - ".github/pyright-config.json" + - ".github/workflows/strict-type-check.yml" + - "**.pyi" + push: + paths: + - "**.py" + - ".github/pyright-config.json" + - ".github/workflows/strict-type-check.yml" + - "**.pyi" + +jobs: + pyright: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: "Install dependencies" + run: | + python -m pip install --upgrade pip pyright==1.1.358 + python ModuleUpdate.py --append "WebHostLib/requirements.txt" --force --yes + + - name: "pyright: strict check on specific files" + run: python .github/type_check.py diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 1a76a7f47160..3ad29b007772 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -24,7 +24,7 @@ on: - '.github/workflows/unittests.yml' jobs: - build: + unit: runs-on: ${{ matrix.os }} name: Test Python ${{ matrix.python.version }} ${{ matrix.os }} @@ -46,9 +46,9 @@ jobs: os: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python.version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python.version }} - name: Install dependencies @@ -60,3 +60,32 @@ jobs: - name: Unittests run: | pytest -n auto + + hosting: + runs-on: ${{ matrix.os }} + name: Test hosting with ${{ matrix.python.version }} on ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + python: + - {version: '3.11'} # current + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python.version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python.version }} + - name: Install dependencies + run: | + python -m venv venv + source venv/bin/activate + python -m pip install --upgrade pip + python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt" + - name: Test hosting + run: | + source venv/bin/activate + export PYTHONPATH=$(pwd) + python test/hosting/__main__.py diff --git a/.gitignore b/.gitignore index 022abe38fe40..791f7b1bb7fe 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ Output Logs/ /installdelete.iss /data/user.kv /datapackage +/custom_worlds # Byte-compiled / optimized / DLL files __pycache__/ @@ -149,7 +150,7 @@ venv/ ENV/ env.bak/ venv.bak/ -.code-workspace +*.code-workspace shell.nix # Spyder project settings @@ -177,6 +178,7 @@ dmypy.json cython_debug/ # Cython intermediates +_speedups.c _speedups.cpp _speedups.html diff --git a/AHITClient.py b/AHITClient.py new file mode 100644 index 000000000000..6ed7d7b49d48 --- /dev/null +++ b/AHITClient.py @@ -0,0 +1,8 @@ +from worlds.ahit.Client import launch +import Utils +import ModuleUpdate +ModuleUpdate.update() + +if __name__ == "__main__": + Utils.init_logging("AHITClient", exception_logger="Client") + launch() diff --git a/AdventureClient.py b/AdventureClient.py index 06e4d60dad43..24c6a4c4fc58 100644 --- a/AdventureClient.py +++ b/AdventureClient.py @@ -80,7 +80,7 @@ def __init__(self, server_address, password): self.local_item_locations = {} self.dragon_speed_info = {} - options = Utils.get_options() + options = Utils.get_settings() self.display_msgs = options["adventure_options"]["display_msgs"] async def server_auth(self, password_requested: bool = False): @@ -102,7 +102,7 @@ def _set_message(self, msg: str, msg_id: int): def on_package(self, cmd: str, args: dict): if cmd == 'Connected': self.locations_array = None - if Utils.get_options()["adventure_options"].get("death_link", False): + if Utils.get_settings()["adventure_options"].get("death_link", False): self.set_deathlink = True async_start(self.get_freeincarnates_used()) elif cmd == "RoomInfo": @@ -112,7 +112,7 @@ def on_package(self, cmd: str, args: dict): if ': !' not in msg: self._set_message(msg, SYSTEM_MESSAGE_ID) elif cmd == "ReceivedItems": - msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}" + msg = f"Received {', '.join([self.item_names.lookup_in_game(item.item) for item in args['items']])}" self._set_message(msg, SYSTEM_MESSAGE_ID) elif cmd == "Retrieved": if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]: @@ -415,8 +415,8 @@ async def atari_sync_task(ctx: AdventureContext): async def run_game(romfile): - auto_start = Utils.get_options()["adventure_options"].get("rom_start", True) - rom_args = Utils.get_options()["adventure_options"].get("rom_args") + auto_start = Utils.get_settings()["adventure_options"].get("rom_start", True) + rom_args = Utils.get_settings()["adventure_options"].get("rom_args") if auto_start is True: import webbrowser webbrowser.open(romfile) diff --git a/BaseClasses.py b/BaseClasses.py index 446eea5b481f..8aac9384da68 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,5 +1,6 @@ from __future__ import annotations +import collections import copy import itertools import functools @@ -11,8 +12,8 @@ from collections import Counter, deque from collections.abc import Collection, MutableSequence from enum import IntEnum, IntFlag -from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union, \ - Type, ClassVar +from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Set, Tuple, \ + TypedDict, Union, Type, ClassVar import NetUtils import Options @@ -36,6 +37,7 @@ class Group(TypedDict, total=False): class ThreadBarrierProxy: """Passes through getattr while passthrough is True""" + def __init__(self, obj: object) -> None: self.passthrough = True self.obj = obj @@ -51,10 +53,6 @@ def __getattr__(self, name: str) -> Any: class MultiWorld(): debug_types = False player_name: Dict[int, str] - difficulty_requirements: dict - required_medallions: dict - dark_room_logic: Dict[int, str] - restrict_dungeon_item_on_boss: Dict[int, bool] plando_texts: List[Dict[str, str]] plando_items: List[List[Dict[str, Any]]] plando_connections: List @@ -67,7 +65,6 @@ class MultiWorld(): state: CollectionState plando_options: PlandoOptions - accessibility: Dict[int, Options.Accessibility] early_items: Dict[int, Dict[str, int]] local_early_items: Dict[int, Dict[str, int]] local_items: Dict[int, Options.LocalItems] @@ -101,9 +98,9 @@ class RegionManager: location_cache: Dict[int, Dict[str, Location]] def __init__(self, players: int): - self.region_cache = {player: {} for player in range(1, players+1)} - self.entrance_cache = {player: {} for player in range(1, players+1)} - self.location_cache = {player: {} for player in range(1, players+1)} + self.region_cache = {player: {} for player in range(1, players + 1)} + self.entrance_cache = {player: {} for player in range(1, players + 1)} + self.location_cache = {player: {} for player in range(1, players + 1)} def __iadd__(self, other: Iterable[Region]): self.extend(other) @@ -137,7 +134,6 @@ def __init__(self, players: int): self.random = ThreadBarrierProxy(random.Random()) self.players = players self.player_types = {player: NetUtils.SlotType.player for player in self.player_ids} - self.glitch_triforce = False self.algorithm = 'balanced' self.groups = {} self.regions = self.RegionManager(players) @@ -160,65 +156,19 @@ def __init__(self, players: int): self.local_early_items = {player: {} for player in self.player_ids} self.indirect_connections = {} self.start_inventory_from_pool: Dict[int, Options.StartInventoryPool] = {} - self.fix_trock_doors = self.AttributeProxy( - lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted') - self.fix_skullwoods_exit = self.AttributeProxy( - lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']) - self.fix_palaceofdarkness_exit = self.AttributeProxy( - lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']) - self.fix_trock_exit = self.AttributeProxy( - lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']) for player in range(1, players + 1): def set_player_attr(attr, val): self.__dict__.setdefault(attr, {})[player] = val - set_player_attr('shuffle', "vanilla") - set_player_attr('logic', "noglitches") - set_player_attr('mode', 'open') - set_player_attr('difficulty', 'normal') - set_player_attr('item_functionality', 'normal') - set_player_attr('timer', False) - set_player_attr('goal', 'ganon') - set_player_attr('required_medallions', ['Ether', 'Quake']) - set_player_attr('swamp_patch_required', False) - set_player_attr('powder_patch_required', False) - set_player_attr('ganon_at_pyramid', True) - set_player_attr('ganonstower_vanilla', True) - set_player_attr('can_access_trock_eyebridge', None) - set_player_attr('can_access_trock_front', None) - set_player_attr('can_access_trock_big_chest', None) - set_player_attr('can_access_trock_middle', None) - set_player_attr('fix_fake_world', True) - set_player_attr('difficulty_requirements', None) - set_player_attr('boss_shuffle', 'none') - set_player_attr('enemy_health', 'default') - set_player_attr('enemy_damage', 'default') - set_player_attr('beemizer_total_chance', 0) - set_player_attr('beemizer_trap_chance', 0) - set_player_attr('escape_assist', []) - set_player_attr('treasure_hunt_icon', 'Triforce Piece') - set_player_attr('treasure_hunt_count', 0) - set_player_attr('clock_mode', False) - set_player_attr('countdown_start_time', 10) - set_player_attr('red_clock_time', -2) - set_player_attr('blue_clock_time', 2) - set_player_attr('green_clock_time', 4) - set_player_attr('can_take_damage', True) - set_player_attr('triforce_pieces_available', 30) - set_player_attr('triforce_pieces_required', 20) - set_player_attr('shop_shuffle', 'off') - set_player_attr('shuffle_prizes', "g") - set_player_attr('sprite_pool', []) - set_player_attr('dark_room_logic', "lamp") set_player_attr('plando_items', []) set_player_attr('plando_texts', {}) set_player_attr('plando_connections', []) - set_player_attr('game', "A Link to the Past") + set_player_attr('game', "Archipelago") set_player_attr('completion_condition', lambda state: True) self.worlds = {} self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the " - "world's random object instead (usually self.random)") + "world's random object instead (usually self.random)") self.plando_options = PlandoOptions.none def get_all_ids(self) -> Tuple[int, ...]: @@ -340,6 +290,84 @@ def set_item_links(self): group["non_local_items"] = item_link["non_local_items"] group["link_replacement"] = replacement_prio[item_link["link_replacement"]] + def link_items(self) -> None: + """Called to link together items in the itempool related to the registered item link groups.""" + for group_id, group in self.groups.items(): + def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ + Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] + ]: + classifications: Dict[str, int] = collections.defaultdict(int) + counters = {player: {name: 0 for name in shared_pool} for player in players} + for item in self.itempool: + if item.player in counters and item.name in shared_pool: + counters[item.player][item.name] += 1 + classifications[item.name] |= item.classification + + for player in players.copy(): + if all([counters[player][item] == 0 for item in shared_pool]): + players.remove(player) + del (counters[player]) + + if not players: + return None, None + + for item in shared_pool: + count = min(counters[player][item] for player in players) + if count: + for player in players: + counters[player][item] = count + else: + for player in players: + del (counters[player][item]) + return counters, classifications + + common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) + if not common_item_count: + continue + + new_itempool: List[Item] = [] + for item_name, item_count in next(iter(common_item_count.values())).items(): + for _ in range(item_count): + new_item = group["world"].create_item(item_name) + # mangle together all original classification bits + new_item.classification |= classifications[item_name] + new_itempool.append(new_item) + + region = Region("Menu", group_id, self, "ItemLink") + self.regions.append(region) + locations = region.locations + for item in self.itempool: + count = common_item_count.get(item.player, {}).get(item.name, 0) + if count: + loc = Location(group_id, f"Item Link: {item.name} -> {self.player_name[item.player]} {count}", + None, region) + loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ + state.has(item_name, group_id_, count_) + + locations.append(loc) + loc.place_locked_item(item) + common_item_count[item.player][item.name] -= 1 + else: + new_itempool.append(item) + + itemcount = len(self.itempool) + self.itempool = new_itempool + + while itemcount > len(self.itempool): + items_to_add = [] + for player in group["players"]: + if group["link_replacement"]: + item_player = group_id + else: + item_player = player + if group["replacement_items"][player]: + items_to_add.append(AutoWorld.call_single(self, "create_item", item_player, + group["replacement_items"][player])) + else: + items_to_add.append(AutoWorld.call_single(self, "create_filler", item_player)) + self.random.shuffle(items_to_add) + self.itempool.extend(items_to_add[:itemcount - len(self.itempool)]) + def secure(self): self.random = ThreadBarrierProxy(secrets.SystemRandom()) self.is_race = True @@ -425,7 +453,8 @@ def find_item(self, item, player: int) -> Location: return next(location for location in self.get_locations() if location.item and location.item.name == item and location.item.player == player) - def find_items_in_locations(self, items: Set[str], player: int, resolve_group_locations: bool = False) -> List[Location]: + def find_items_in_locations(self, items: Set[str], player: int, resolve_group_locations: bool = False) -> List[ + Location]: if resolve_group_locations: player_groups = self.get_player_groups(player) return [location for location in self.get_locations() if @@ -445,7 +474,7 @@ def push_item(self, location: Location, item: Item, collect: bool = True): location.item = item item.location = location if collect: - self.state.collect(item, location.event, location) + self.state.collect(item, location.advancement, location) logging.debug('Placed %s at %s', item, location) @@ -472,13 +501,15 @@ def get_unfilled_locations(self, player: Optional[int] = None) -> List[Location] def get_filled_locations(self, player: Optional[int] = None) -> List[Location]: return [location for location in self.get_locations(player) if location.item is not None] - def get_reachable_locations(self, state: Optional[CollectionState] = None, player: Optional[int] = None) -> List[Location]: + def get_reachable_locations(self, state: Optional[CollectionState] = None, player: Optional[int] = None) -> List[ + Location]: state: CollectionState = state if state else self.state return [location for location in self.get_locations(player) if location.can_reach(state)] def get_placeable_locations(self, state=None, player=None) -> List[Location]: state: CollectionState = state if state else self.state - return [location for location in self.get_locations(player) if location.item is None and location.can_reach(state)] + return [location for location in self.get_locations(player) if + location.item is None and location.can_reach(state)] def get_unfilled_locations_for_players(self, location_names: List[str], players: Iterable[int]): for player in players: @@ -575,27 +606,22 @@ def fulfills_accessibility(self, state: Optional[CollectionState] = None): players: Dict[str, Set[int]] = { "minimal": set(), "items": set(), - "locations": set() + "full": set() } - for player, access in self.accessibility.items(): - players[access.current_key].add(player) + for player, world in self.worlds.items(): + players[world.options.accessibility.current_key].add(player) beatable_fulfilled = False - def location_condition(location: Location): + def location_condition(location: Location) -> bool: """Determine if this location has to be accessible, location is already filtered by location_relevant""" - if location.player in players["locations"] or (location.item and location.item.player not in - players["minimal"]): - return True - return False + return location.player in players["full"] or \ + (location.item and location.item.player not in players["minimal"]) - def location_relevant(location: Location): + def location_relevant(location: Location) -> bool: """Determine if this location is relevant to sweep.""" - if location.progress_type != LocationProgressType.EXCLUDED \ - and (location.player in players["locations"] or location.event - or (location.item and location.item.advancement)): - return True - return False + return location.progress_type != LocationProgressType.EXCLUDED \ + and (location.player in players["full"] or location.advancement) def all_done() -> bool: """Check if all access rules are fulfilled""" @@ -733,13 +759,13 @@ def can_reach_entrance(self, spot: str, player: int) -> bool: def can_reach_region(self, spot: str, player: int) -> bool: return self.multiworld.get_region(spot, player).can_reach(self) - def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None: + def sweep_for_events(self, locations: Optional[Iterable[Location]] = None) -> None: if locations is None: locations = self.multiworld.get_filled_locations() reachable_events = True # since the loop has a good chance to run more than once, only filter the events once - locations = {location for location in locations if location.event and location not in self.events and - not key_only or getattr(location.item, "locked_dungeon_item", False)} + locations = {location for location in locations if location.advancement and location not in self.events} + while reachable_events: reachable_events = {location for location in locations if location.can_reach(self)} locations -= reachable_events @@ -760,15 +786,49 @@ def has_any(self, items: Iterable[str], player: int) -> bool: """Returns True if at least one item name of items is in state at least once.""" return any(self.prog_items[player][item] for item in items) + def has_all_counts(self, item_counts: Mapping[str, int], player: int) -> bool: + """Returns True if each item name is in the state at least as many times as specified.""" + return all(self.prog_items[player][item] >= count for item, count in item_counts.items()) + + def has_any_count(self, item_counts: Mapping[str, int], player: int) -> bool: + """Returns True if at least one item name is in the state at least as many times as specified.""" + return any(self.prog_items[player][item] >= count for item, count in item_counts.items()) + def count(self, item: str, player: int) -> int: return self.prog_items[player][item] - def item_count(self, item: str, player: int) -> int: - Utils.deprecate("Use count instead.") - return self.count(item, player) + def has_from_list(self, items: Iterable[str], player: int, count: int) -> bool: + """Returns True if the state contains at least `count` items matching any of the item names from a list.""" + found: int = 0 + player_prog_items = self.prog_items[player] + for item_name in items: + found += player_prog_items[item_name] + if found >= count: + return True + return False + + def has_from_list_unique(self, items: Iterable[str], player: int, count: int) -> bool: + """Returns True if the state contains at least `count` items matching any of the item names from a list. + Ignores duplicates of the same item.""" + found: int = 0 + player_prog_items = self.prog_items[player] + for item_name in items: + found += player_prog_items[item_name] > 0 + if found >= count: + return True + return False + + def count_from_list(self, items: Iterable[str], player: int) -> int: + """Returns the cumulative count of items from a list present in state.""" + return sum(self.prog_items[player][item_name] for item_name in items) + + def count_from_list_unique(self, items: Iterable[str], player: int) -> int: + """Returns the cumulative count of items from a list present in state. Ignores duplicates of the same item.""" + return sum(self.prog_items[player][item_name] > 0 for item_name in items) # item name group related def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool: + """Returns True if the state contains at least `count` items present in a specified item group.""" found: int = 0 player_prog_items = self.prog_items[player] for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]: @@ -777,12 +837,34 @@ def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool: return True return False - def count_group(self, item_name_group: str, player: int) -> int: + def has_group_unique(self, item_name_group: str, player: int, count: int = 1) -> bool: + """Returns True if the state contains at least `count` items present in a specified item group. + Ignores duplicates of the same item. + """ found: int = 0 player_prog_items = self.prog_items[player] for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]: - found += player_prog_items[item_name] - return found + found += player_prog_items[item_name] > 0 + if found >= count: + return True + return False + + def count_group(self, item_name_group: str, player: int) -> int: + """Returns the cumulative count of items from an item group present in state.""" + player_prog_items = self.prog_items[player] + return sum( + player_prog_items[item_name] + for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group] + ) + + def count_group_unique(self, item_name_group: str, player: int) -> int: + """Returns the cumulative count of items from an item group present in state. + Ignores duplicates of the same item.""" + player_prog_items = self.prog_items[player] + return sum( + player_prog_items[item_name] > 0 + for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group] + ) # Item related def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool: @@ -886,7 +968,7 @@ class LocationRegister(Register): def __delitem__(self, index: int) -> None: location: Location = self._list.__getitem__(index) self._list.__delitem__(index) - del(self.region_manager.location_cache[location.player][location.name]) + del (self.region_manager.location_cache[location.player][location.name]) def insert(self, index: int, value: Location) -> None: assert value.name not in self.region_manager.location_cache[value.player], \ @@ -898,7 +980,7 @@ class EntranceRegister(Register): def __delitem__(self, index: int) -> None: entrance: Entrance = self._list.__getitem__(index) self._list.__delitem__(index) - del(self.region_manager.entrance_cache[entrance.player][entrance.name]) + del (self.region_manager.entrance_cache[entrance.player][entrance.name]) def insert(self, index: int, value: Entrance) -> None: assert value.name not in self.region_manager.entrance_cache[value.player], \ @@ -1013,7 +1095,8 @@ def __repr__(self): return self.__str__() def __str__(self): - return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})' + return self.multiworld.get_name_string_for_object( + self) if self.multiworld else f'{self.name} (Player {self.player})' class LocationProgressType(IntEnum): @@ -1028,11 +1111,10 @@ class Location: name: str address: Optional[int] parent_region: Optional[Region] - event: bool = False locked: bool = False show_in_spoiler: bool = True progress_type: LocationProgressType = LocationProgressType.DEFAULT - always_allow = staticmethod(lambda item, state: False) + always_allow = staticmethod(lambda state, item: False) access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True) item_rule = staticmethod(lambda item: True) item: Optional[Item] = None @@ -1044,7 +1126,7 @@ def __init__(self, player: int, name: str = '', address: Optional[int] = None, p self.parent_region = parent def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool: - return ((self.always_allow(state, item) and item.name not in state.multiworld.non_local_items[item.player]) + return ((self.always_allow(state, item) and item.name not in state.multiworld.worlds[item.player].options.non_local_items) or ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful)) and self.item_rule(item) and (not check_access or self.can_reach(state)))) @@ -1059,7 +1141,6 @@ def place_locked_item(self, item: Item): raise Exception(f"Location {self} already filled.") self.item = item item.location = self - self.event = item.advancement self.locked = True def __repr__(self): @@ -1075,6 +1156,15 @@ def __hash__(self): def __lt__(self, other: Location): return (self.player, self.name) < (other.player, other.name) + @property + def advancement(self) -> bool: + return self.item is not None and self.item.advancement + + @property + def is_event(self) -> bool: + """Returns True if the address of this location is None, denoting it is an Event Location.""" + return self.address is None + @property def native_item(self) -> bool: """Returns True if the item in this location matches game.""" @@ -1232,7 +1322,7 @@ def create_playthrough(self, create_paths: bool = True) -> None: logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % ( location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) - if any([multiworld.accessibility[location.item.player] != 'minimal' for location in sphere_candidates]): + if any([multiworld.worlds[location.item.player].options.accessibility != 'minimal' for location in sphere_candidates]): raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). ' f'Something went terribly wrong here.') else: @@ -1281,8 +1371,6 @@ def create_playthrough(self, create_paths: bool = True) -> None: state = CollectionState(multiworld) collection_spheres = [] while required_locations: - state.sweep_for_events(key_only=True) - sphere = set(filter(state.can_reach, required_locations)) for location in sphere: @@ -1352,12 +1440,15 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st get_path(state, multiworld.get_region('Inverted Big Bomb Shop', player)) def to_file(self, filename: str) -> None: + from itertools import chain from worlds import AutoWorld + from Options import Visibility def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None: res = getattr(self.multiworld.worlds[player].options, option_key) - display_name = getattr(option_obj, "display_name", option_key) - outfile.write(f"{display_name + ':':33}{res.current_option_name}\n") + if res.visibility & Visibility.spoiler: + display_name = getattr(option_obj, "display_name", option_key) + outfile.write(f"{display_name + ':':33}{res.current_option_name}\n") with open(filename, 'w', encoding="utf-8-sig") as outfile: outfile.write( @@ -1388,6 +1479,14 @@ def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None: AutoWorld.call_all(self.multiworld, "write_spoiler", outfile) + precollected_items = [f"{item.name} ({self.multiworld.get_player_name(item.player)})" + if self.multiworld.players > 1 + else item.name + for item in chain.from_iterable(self.multiworld.precollected_items.values())] + if precollected_items: + outfile.write("\n\nStarting Items:\n\n") + outfile.write("\n".join([item for item in precollected_items])) + locations = [(str(location), str(location.item) if location.item is not None else "Nothing") for location in self.multiworld.get_locations() if location.show_in_spoiler] outfile.write('\n\nLocations:\n\n') diff --git a/CommonClient.py b/CommonClient.py index 3665b9f177c6..09937e4b9ab8 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -1,5 +1,6 @@ from __future__ import annotations +import collections import copy import logging import asyncio @@ -8,6 +9,7 @@ import typing import time import functools +import warnings import ModuleUpdate ModuleUpdate.update() @@ -20,8 +22,8 @@ Utils.init_logging("TextClient", exception_logger="Client") from MultiServer import CommandProcessor -from NetUtils import Endpoint, decode, NetworkItem, encode, JSONtoTextParser, \ - ClientStatus, Permission, NetworkSlot, RawJSONtoTextParser +from NetUtils import (Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot, + RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes, SlotType) from Utils import Version, stream_input, async_start from worlds import network_data_package, AutoWorldRegister import os @@ -59,6 +61,7 @@ def _cmd_connect(self, address: str = "") -> bool: if address: self.ctx.server_address = None self.ctx.username = None + self.ctx.password = None elif not self.ctx.server_address: self.output("Please specify an address.") return False @@ -72,9 +75,16 @@ def _cmd_disconnect(self) -> bool: def _cmd_received(self) -> bool: """List all received items""" - self.output(f'{len(self.ctx.items_received)} received items:') + item: NetworkItem + self.output(f'{len(self.ctx.items_received)} received items, sorted by time:') for index, item in enumerate(self.ctx.items_received, 1): - self.output(f"{self.ctx.item_names[item.item]} from {self.ctx.player_names[item.player]}") + parts = [] + add_json_item(parts, item.item, self.ctx.slot, item.flags) + add_json_text(parts, " from ") + add_json_location(parts, item.location, item.player) + add_json_text(parts, " by ") + add_json_text(parts, item.player, type=JSONTypes.player_id) + self.ctx.on_print_json({"data": parts, "cmd": "PrintJSON"}) return True def _cmd_missing(self, filter_text = "") -> bool: @@ -166,10 +176,77 @@ class CommonContext: items_handling: typing.Optional[int] = None want_slot_data: bool = True # should slot_data be retrieved via Connect - # data package - # Contents in flux until connection to server is made, to download correct data for this multiworld. - item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})') - location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})') + class NameLookupDict: + """A specialized dict, with helper methods, for id -> name item/location data package lookups by game.""" + def __init__(self, ctx: CommonContext, lookup_type: typing.Literal["item", "location"]): + self.ctx: CommonContext = ctx + self.lookup_type: typing.Literal["item", "location"] = lookup_type + self._unknown_item: typing.Callable[[int], str] = lambda key: f"Unknown {lookup_type} (ID: {key})" + self._archipelago_lookup: typing.Dict[int, str] = {} + self._flat_store: typing.Dict[int, str] = Utils.KeyedDefaultDict(self._unknown_item) + self._game_store: typing.Dict[str, typing.ChainMap[int, str]] = collections.defaultdict( + lambda: collections.ChainMap(self._archipelago_lookup, Utils.KeyedDefaultDict(self._unknown_item))) + self.warned: bool = False + + # noinspection PyTypeChecker + def __getitem__(self, key: str) -> typing.Mapping[int, str]: + # TODO: In a future version (0.6.0?) this should be simplified by removing implicit id lookups support. + if isinstance(key, int): + if not self.warned: + # Use warnings instead of logger to avoid deprecation message from appearing on user side. + self.warned = True + warnings.warn(f"Implicit name lookup by id only is deprecated and only supported to maintain " + f"backwards compatibility for now. If multiple games share the same id for a " + f"{self.lookup_type}, name could be incorrect. Please use " + f"`{self.lookup_type}_names.lookup_in_game()` or " + f"`{self.lookup_type}_names.lookup_in_slot()` instead.") + return self._flat_store[key] # type: ignore + + return self._game_store[key] + + def __len__(self) -> int: + return len(self._game_store) + + def __iter__(self) -> typing.Iterator[str]: + return iter(self._game_store) + + def __repr__(self) -> str: + return self._game_store.__repr__() + + def lookup_in_game(self, code: int, game_name: typing.Optional[str] = None) -> str: + """Returns the name for an item/location id in the context of a specific game or own game if `game` is + omitted. + """ + if game_name is None: + game_name = self.ctx.game + assert game_name is not None, f"Attempted to lookup {self.lookup_type} with no game name available." + + return self._game_store[game_name][code] + + def lookup_in_slot(self, code: int, slot: typing.Optional[int] = None) -> str: + """Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is + omitted. + + Use of `lookup_in_slot` should not be used when not connected to a server. If looking in own game, set + `ctx.game` and use `lookup_in_game` method instead. + """ + if slot is None: + slot = self.ctx.slot + assert slot is not None, f"Attempted to lookup {self.lookup_type} with no slot info available." + + return self.lookup_in_game(code, self.ctx.slot_info[slot].game) + + def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int]) -> None: + """Overrides existing lookup tables for a particular game.""" + id_to_name_lookup_table = Utils.KeyedDefaultDict(self._unknown_item) + id_to_name_lookup_table.update({code: name for name, code in name_to_id_lookup_table.items()}) + self._game_store[game] = collections.ChainMap(self._archipelago_lookup, id_to_name_lookup_table) + self._flat_store.update(id_to_name_lookup_table) # Only needed for legacy lookup method. + if game == "Archipelago": + # Keep track of the Archipelago data package separately so if it gets updated in a custom datapackage, + # it updates in all chain maps automatically. + self._archipelago_lookup.clear() + self._archipelago_lookup.update(id_to_name_lookup_table) # defaults starting_reconnect_delay: int = 5 @@ -186,6 +263,7 @@ class CommonContext: server_version: Version = Version(0, 0, 0) generator_version: Version = Version(0, 0, 0) current_energy_link_value: typing.Optional[int] = None # to display in UI, gets set by server + max_size: int = 16*1024*1024 # 16 MB of max incoming packet size last_death_link: float = time.time() # last send/received death link on AP layer @@ -199,6 +277,8 @@ class CommonContext: finished_game: bool ready: bool + team: typing.Optional[int] + slot: typing.Optional[int] auth: typing.Optional[str] seed_name: typing.Optional[str] @@ -221,7 +301,7 @@ class CommonContext: # message box reporting a loss of connection _messagebox_connection_loss: typing.Optional["kvui.MessageBox"] = None - def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None: + def __init__(self, server_address: typing.Optional[str] = None, password: typing.Optional[str] = None) -> None: # server state self.server_address = server_address self.username = None @@ -261,6 +341,9 @@ def __init__(self, server_address: typing.Optional[str], password: typing.Option self.exit_event = asyncio.Event() self.watcher_event = asyncio.Event() + self.item_names = self.NameLookupDict(self, "item") + self.location_names = self.NameLookupDict(self, "location") + self.jsontotextparser = JSONtoTextParser(self) self.rawjsontotextparser = RawJSONtoTextParser(self) self.update_data_package(network_data_package) @@ -414,6 +497,11 @@ def on_user_say(self, text: str) -> typing.Optional[str]: """Gets called before sending a Say to the server from the user. Returned text is sent, or sending is aborted if None is returned.""" return text + + def on_ui_command(self, text: str) -> None: + """Gets called by kivy when the user executes a command starting with `/` or `!`. + The command processor is still called; this is just intended for command echoing.""" + self.ui.print_json([{"text": text, "type": "color", "color": "orange"}]) def update_permissions(self, permissions: typing.Dict[str, int]): for permission_name, permission_flag in permissions.items(): @@ -427,6 +515,7 @@ def update_permissions(self, permissions: typing.Dict[str, int]): async def shutdown(self): self.server_address = "" self.username = None + self.password = None self.cancel_autoreconnect() if self.server and not self.server.socket.closed: await self.server.socket.close() @@ -476,19 +565,17 @@ async def prepare_data_package(self, relevant_games: typing.Set[str], or remote_checksum != cache_checksum: needed_updates.add(game) else: - self.update_game(cached_game) + self.update_game(cached_game, game) if needed_updates: await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates]) - def update_game(self, game_package: dict): - for item_name, item_id in game_package["item_name_to_id"].items(): - self.item_names[item_id] = item_name - for location_name, location_id in game_package["location_name_to_id"].items(): - self.location_names[location_id] = location_name + def update_game(self, game_package: dict, game: str): + self.item_names.update_game(game, game_package["item_name_to_id"]) + self.location_names.update_game(game, game_package["location_name_to_id"]) def update_data_package(self, data_package: dict): for game, game_data in data_package["games"].items(): - self.update_game(game_data) + self.update_game(game_data, game) def consume_network_data_package(self, data_package: dict): self.update_data_package(data_package) @@ -636,15 +723,16 @@ async def server_loop(ctx: CommonContext, address: typing.Optional[str] = None) ctx.username = server_url.username if server_url.password: ctx.password = server_url.password - port = server_url.port or 38281 def reconnect_hint() -> str: return ", type /connect to reconnect" if ctx.server_address else "" logger.info(f'Connecting to Archipelago server at {address}') try: + port = server_url.port or 38281 # raises ValueError if invalid socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None, - ssl=get_ssl_context() if address.startswith("wss://") else None) + ssl=get_ssl_context() if address.startswith("wss://") else None, + max_size=ctx.max_size) if ctx.ui is not None: ctx.ui.update_address_bar(server_url.netloc) ctx.server = Endpoint(socket) @@ -751,8 +839,10 @@ async def process_server_cmd(ctx: CommonContext, args: dict): elif cmd == 'ConnectionRefused': errors = args["errors"] if 'InvalidSlot' in errors: + ctx.disconnected_intentionally = True ctx.event_invalid_slot() elif 'InvalidGame' in errors: + ctx.disconnected_intentionally = True ctx.event_invalid_game() elif 'IncompatibleVersion' in errors: raise Exception('Server reported your client version as incompatible. ' @@ -774,7 +864,8 @@ async def process_server_cmd(ctx: CommonContext, args: dict): ctx.team = args["team"] ctx.slot = args["slot"] # int keys get lost in JSON transfer - ctx.slot_info = {int(pid): data for pid, data in args["slot_info"].items()} + ctx.slot_info = {0: NetworkSlot("Archipelago", "Archipelago", SlotType.player)} + ctx.slot_info.update({int(pid): data for pid, data in args["slot_info"].items()}) ctx.hint_points = args.get("hint_points", 0) ctx.consume_players_package(args["players"]) ctx.stored_data_notification_keys.add(f"_read_hints_{ctx.team}_{ctx.slot}") diff --git a/Fill.py b/Fill.py index 2d6257eae30a..5185bbb60ee4 100644 --- a/Fill.py +++ b/Fill.py @@ -19,11 +19,12 @@ def _log_fill_progress(name: str, placed: int, total_items: int) -> None: logging.info(f"Current fill step ({name}) at {placed}/{total_items} items placed.") -def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple()) -> CollectionState: +def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple(), + locations: typing.Optional[typing.List[Location]] = None) -> CollectionState: new_state = base_state.copy() for item in itempool: new_state.collect(item, True) - new_state.sweep_for_events() + new_state.sweep_for_events(locations=locations) return new_state @@ -34,8 +35,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati """ :param multiworld: Multiworld to be filled. :param base_state: State assumed before fill. - :param locations: Locations to be filled with item_pool - :param item_pool: Items to fill into the locations + :param locations: Locations to be filled with item_pool, gets mutated by removing locations that get filled. + :param item_pool: Items to fill into the locations, gets mutated by removing items that get placed. :param single_player_placement: if true, can speed up placement if everything belongs to a single player :param lock: locations are set to locked as they are filled :param swap: if true, swaps of already place items are done in the event of a dead end @@ -66,7 +67,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati item_pool.pop(p) break maximum_exploration_state = sweep_from_pool( - base_state, item_pool + unplaced_items) + base_state, item_pool + unplaced_items, multiworld.get_filled_locations(item.player) + if single_player_placement else None) has_beaten_game = multiworld.has_beaten_game(maximum_exploration_state) @@ -112,7 +114,9 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati location.item = None placed_item.location = None - swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool) + swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool, + multiworld.get_filled_locations(item.player) + if single_player_placement else None) # unsafe means swap_state assumes we can somehow collect placed_item before item_to_place # by continuing to swap, which is not guaranteed. This is unsafe because there is no mechanic # to clean that up later, so there is a chance generation fails. @@ -159,7 +163,6 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati multiworld.push_item(spot_to_fill, item_to_place, False) spot_to_fill.locked = lock placements.append(spot_to_fill) - spot_to_fill.event = item_to_place.advancement placed += 1 if not placed % 1000: _log_fill_progress(name, placed, total) @@ -171,7 +174,9 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati if cleanup_required: # validate all placements and remove invalid ones - state = sweep_from_pool(base_state, []) + state = sweep_from_pool( + base_state, [], multiworld.get_filled_locations(item.player) + if single_player_placement else None) for placement in placements: if multiworld.worlds[placement.item.player].options.accessibility != "minimal" and not placement.can_reach(state): placement.item.location = None @@ -198,10 +203,16 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati # There are leftover unplaceable items and locations that won't accept them if multiworld.can_beat_game(): logging.warning( - f'Not all items placed. Game beatable anyway. (Could not place {unplaced_items})') + f"Not all items placed. Game beatable anyway.\nCould not place:\n" + f"{', '.join(str(item) for item in unplaced_items)}") else: - raise FillError(f'No more spots to place {unplaced_items}, locations {locations} are invalid. ' - f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}') + raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n" + f"Unplaced items:\n" + f"{', '.join(str(item) for item in unplaced_items)}\n" + f"Unfilled locations:\n" + f"{', '.join(str(location) for location in locations)}\n" + f"Already placed {len(placements)}:\n" + f"{', '.join(str(place) for place in placements)}") item_pool.extend(unplaced_items) @@ -209,7 +220,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati def remaining_fill(multiworld: MultiWorld, locations: typing.List[Location], itempool: typing.List[Item], - name: str = "Remaining") -> None: + name: str = "Remaining", + move_unplaceable_to_start_inventory: bool = False) -> None: unplaced_items: typing.List[Item] = [] placements: typing.List[Location] = [] swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter() @@ -273,8 +285,21 @@ def remaining_fill(multiworld: MultiWorld, if unplaced_items and locations: # There are leftover unplaceable items and locations that won't accept them - raise FillError(f'No more spots to place {unplaced_items}, locations {locations} are invalid. ' - f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}') + if move_unplaceable_to_start_inventory: + last_batch = [] + for item in unplaced_items: + logging.debug(f"Moved {item} to start_inventory to prevent fill failure.") + multiworld.push_precollected(item) + last_batch.append(multiworld.worlds[item.player].create_filler()) + remaining_fill(multiworld, locations, unplaced_items, name + " Start Inventory Retry") + else: + raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n" + f"Unplaced items:\n" + f"{', '.join(str(item) for item in unplaced_items)}\n" + f"Unfilled locations:\n" + f"{', '.join(str(location) for location in locations)}\n" + f"Already placed {len(placements)}:\n" + f"{', '.join(str(place) for place in placements)}") itempool.extend(unplaced_items) @@ -299,7 +324,6 @@ def accessibility_corrections(multiworld: MultiWorld, state: CollectionState, lo pool.append(location.item) state.remove(location.item) location.item = None - location.event = False if location in state.events: state.events.remove(location) locations.append(location) @@ -405,7 +429,8 @@ def distribute_early_items(multiworld: MultiWorld, return fill_locations, itempool -def distribute_items_restrictive(multiworld: MultiWorld) -> None: +def distribute_items_restrictive(multiworld: MultiWorld, + panic_method: typing.Literal["swap", "raise", "start_inventory"] = "swap") -> None: fill_locations = sorted(multiworld.get_unfilled_locations()) multiworld.random.shuffle(fill_locations) # get items to distribute @@ -447,17 +472,42 @@ def mark_for_locking(location: Location): if prioritylocations: # "priority fill" - fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking, + fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool, + single_player_placement=multiworld.players == 1, swap=False, on_place=mark_for_locking, name="Priority") accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool) defaultlocations = prioritylocations + defaultlocations if progitempool: # "advancement/progression fill" - fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, name="Progression") + if panic_method == "swap": + fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, + swap=True, + name="Progression", single_player_placement=multiworld.players == 1) + elif panic_method == "raise": + fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, + swap=False, + name="Progression", single_player_placement=multiworld.players == 1) + elif panic_method == "start_inventory": + fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, + swap=False, allow_partial=True, + name="Progression", single_player_placement=multiworld.players == 1) + if progitempool: + for item in progitempool: + logging.debug(f"Moved {item} to start_inventory to prevent fill failure.") + multiworld.push_precollected(item) + filleritempool.append(multiworld.worlds[item.player].create_filler()) + logging.warning(f"{len(progitempool)} items moved to start inventory," + f" due to failure in Progression fill step.") + progitempool[:] = [] + + else: + raise ValueError(f"Generator Panic Method {panic_method} not recognized.") if progitempool: raise FillError( - f'Not enough locations for progress items. There are {len(progitempool)} more items than locations') + f"Not enough locations for progression items. " + f"There are {len(progitempool)} more progression items than there are available locations." + ) accessibility_corrections(multiworld, multiworld.state, defaultlocations) for location in lock_later: @@ -467,27 +517,31 @@ def mark_for_locking(location: Location): inaccessible_location_rules(multiworld, multiworld.state, defaultlocations) - remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded") + remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded", + move_unplaceable_to_start_inventory=panic_method=="start_inventory") + if excludedlocations: raise FillError( - f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items") + f"Not enough filler items for excluded locations. " + f"There are {len(excludedlocations)} more excluded locations than filler or trap items." + ) restitempool = filleritempool + usefulitempool - remaining_fill(multiworld, defaultlocations, restitempool) + remaining_fill(multiworld, defaultlocations, restitempool, + move_unplaceable_to_start_inventory=panic_method=="start_inventory") unplaced = restitempool unfilled = defaultlocations if unplaced or unfilled: logging.warning( - f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}') - items_counter = Counter(location.item.player for location in multiworld.get_locations() if location.item) + f"Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}") + items_counter = Counter(location.item.player for location in multiworld.get_filled_locations()) locations_counter = Counter(location.player for location in multiworld.get_locations()) items_counter.update(item.player for item in unplaced) - locations_counter.update(location.player for location in unfilled) print_data = {"items": items_counter, "locations": locations_counter} - logging.info(f'Per-Player counts: {print_data})') + logging.info(f"Per-Player counts: {print_data})") def flood_items(multiworld: MultiWorld) -> None: @@ -592,7 +646,6 @@ def balance_multiworld_progression(multiworld: MultiWorld) -> None: def get_sphere_locations(sphere_state: CollectionState, locations: typing.Set[Location]) -> typing.Set[Location]: - sphere_state.sweep_for_events(key_only=True, locations=locations) return {loc for loc in locations if sphere_state.can_reach(loc)} def item_percentage(player: int, num: int) -> float: @@ -644,7 +697,7 @@ def item_percentage(player: int, num: int) -> float: while True: # Check locations in the current sphere and gather progression items to swap earlier for location in balancing_sphere: - if location.event: + if location.advancement: balancing_state.collect(location.item, True, location) player = location.item.player # only replace items that end up in another player's world @@ -701,7 +754,7 @@ def item_percentage(player: int, num: int) -> float: # sort then shuffle to maintain deterministic behaviour, # while allowing use of set for better algorithm growth behaviour elsewhere - replacement_locations = sorted(l for l in checked_locations if not l.event and not l.locked) + replacement_locations = sorted(l for l in checked_locations if not l.advancement and not l.locked) multiworld.random.shuffle(replacement_locations) items_to_replace.sort() multiworld.random.shuffle(items_to_replace) @@ -732,7 +785,7 @@ def item_percentage(player: int, num: int) -> float: sphere_locations.add(location) for location in sphere_locations: - if location.event: + if location.advancement: state.collect(location.item, True, location) checked_locations |= sphere_locations @@ -753,7 +806,6 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked: location_2.item, location_1.item = location_1.item, location_2.item location_1.item.location = location_1 location_2.item.location = location_2 - location_1.event, location_2.event = location_2.event, location_1.event def distribute_planned(multiworld: MultiWorld) -> None: @@ -950,7 +1002,6 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: placement['force']) for (item, location) in successful_pairs: multiworld.push_item(location, item, collect=False) - location.event = True # flag location to be checked during fill location.locked = True logging.debug(f"Plando placed {item} at {location}") if from_pool: diff --git a/Generate.py b/Generate.py index ecdc81833a15..d7dd6523e7f1 100644 --- a/Generate.py +++ b/Generate.py @@ -1,48 +1,44 @@ from __future__ import annotations import argparse +import copy import logging import os import random import string +import sys import urllib.parse import urllib.request from collections import Counter from typing import Any, Dict, Tuple, Union +from itertools import chain import ModuleUpdate ModuleUpdate.update() -import copy import Utils import Options from BaseClasses import seeddigits, get_seed, PlandoOptions -from Main import main as ERmain -from settings import get_settings from Utils import parse_yamls, version_tuple, __version__, tuplize_version -from worlds.alttp import Options as LttPOptions -from worlds.alttp.EntranceRandomizer import parse_arguments -from worlds.alttp.Text import TextTable -from worlds.AutoWorld import AutoWorldRegister -from worlds.generic import PlandoConnection def mystery_argparse(): - options = get_settings() - defaults = options.generator + from settings import get_settings + settings = get_settings() + defaults = settings.generator parser = argparse.ArgumentParser(description="CMD Generation Interface, defaults come from host.yaml.") parser.add_argument('--weights_file_path', default=defaults.weights_file_path, - help='Path to the weights file to use for rolling game settings, urls are also valid') - parser.add_argument('--samesettings', help='Rolls settings per weights file rather than per player', + help='Path to the weights file to use for rolling game options, urls are also valid') + parser.add_argument('--sameoptions', help='Rolls options per weights file rather than per player', action='store_true') parser.add_argument('--player_files_path', default=defaults.player_files_path, help="Input directory for player files.") parser.add_argument('--seed', help='Define seed number to generate.', type=int) parser.add_argument('--multi', default=defaults.players, type=lambda value: max(int(value), 1)) parser.add_argument('--spoiler', type=int, default=defaults.spoiler) - parser.add_argument('--outputpath', default=options.general_options.output_path, + parser.add_argument('--outputpath', default=settings.general_options.output_path, help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd parser.add_argument('--race', action='store_true', default=defaults.race) parser.add_argument('--meta_file_path', default=defaults.meta_file_path) @@ -62,20 +58,23 @@ def mystery_argparse(): if not os.path.isabs(args.meta_file_path): args.meta_file_path = os.path.join(args.player_files_path, args.meta_file_path) args.plando: PlandoOptions = PlandoOptions.from_option_string(args.plando) - return args, options + return args def get_seed_name(random_source) -> str: return f"{random_source.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits) -def main(args=None, callback=ERmain): +def main(args=None) -> Tuple[argparse.Namespace, int]: + # __name__ == "__main__" check so unittests that already imported worlds don't trip this. + if __name__ == "__main__" and "worlds" in sys.modules: + raise Exception("Worlds system should not be loaded before logging init.") + if not args: - args, options = mystery_argparse() - else: - options = get_settings() + args = mystery_argparse() seed = get_seed(args.seed) + Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level) random.seed(seed) seed_name = get_seed_name(random) @@ -103,8 +102,8 @@ def main(args=None, callback=ERmain): del(meta_weights["meta_description"]) except Exception as e: raise ValueError("No meta description found for meta.yaml. Unable to verify.") from e - if args.samesettings: - raise Exception("Cannot mix --samesettings with --meta") + if args.sameoptions: + raise Exception("Cannot mix --sameoptions with --meta") else: meta_weights = None player_id = 1 @@ -120,7 +119,7 @@ def main(args=None, callback=ERmain): raise ValueError(f"File {fname} is invalid. Please fix your yaml.") from e # sort dict for consistent results across platforms: - weights_cache = {key: value for key, value in sorted(weights_cache.items())} + weights_cache = {key: value for key, value in sorted(weights_cache.items(), key=lambda k: k[0].casefold())} for filename, yaml_data in weights_cache.items(): if filename not in {args.meta_file_path, args.weights_file_path}: for yaml in yaml_data: @@ -144,10 +143,12 @@ def main(args=None, callback=ERmain): raise Exception(f"No weights found. " f"Provide a general weights file ({args.weights_file_path}) or individual player files. " f"A mix is also permitted.") + + from worlds.AutoWorld import AutoWorldRegister + from worlds.alttp.EntranceRandomizer import parse_arguments erargs = parse_arguments(['--multi', str(args.multi)]) erargs.seed = seed erargs.plando_options = args.plando - erargs.glitch_triforce = options.generator.glitch_triforce_room erargs.spoiler = args.spoiler erargs.race = args.race erargs.outputname = seed_name @@ -156,7 +157,7 @@ def main(args=None, callback=ERmain): erargs.skip_output = args.skip_output settings_cache: Dict[str, Tuple[argparse.Namespace, ...]] = \ - {fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.samesettings else None) + {fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None) for fname, yamls in weights_cache.items()} if meta_weights: @@ -236,7 +237,7 @@ def main(args=None, callback=ERmain): with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f: yaml.dump(important, f) - return callback(erargs, seed) + return erargs, seed def read_weights_yamls(path) -> Tuple[Any, ...]: @@ -310,32 +311,59 @@ def handle_name(name: str, player: int, name_counter: Counter): return new_name -def prefer_int(input_data: str) -> Union[str, int]: - try: - return int(input_data) - except: - return input_data - - def roll_percentage(percentage: Union[int, float]) -> bool: """Roll a percentage chance. percentage is expected to be in range [0, 100]""" return random.random() < (float(percentage) / 100) -def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> dict: +def update_weights(weights: dict, new_weights: dict, update_type: str, name: str) -> dict: logging.debug(f'Applying {new_weights}') - new_options = set(new_weights) - set(weights) - weights.update(new_weights) + cleaned_weights = {} + for option in new_weights: + option_name = option.lstrip("+-") + if option.startswith("+") and option_name in weights: + cleaned_value = weights[option_name] + new_value = new_weights[option] + if isinstance(new_value, set): + cleaned_value.update(new_value) + elif isinstance(new_value, list): + cleaned_value.extend(new_value) + elif isinstance(new_value, dict): + cleaned_value = dict(Counter(cleaned_value) + Counter(new_value)) + else: + raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name}," + f" received {type(new_value).__name__}.") + cleaned_weights[option_name] = cleaned_value + elif option.startswith("-") and option_name in weights: + cleaned_value = weights[option_name] + new_value = new_weights[option] + if isinstance(new_value, set): + cleaned_value.difference_update(new_value) + elif isinstance(new_value, list): + for element in new_value: + cleaned_value.remove(element) + elif isinstance(new_value, dict): + cleaned_value = dict(Counter(cleaned_value) - Counter(new_value)) + else: + raise Exception(f"Cannot apply remove to non-dict, set, or list type {option_name}," + f" received {type(new_value).__name__}.") + cleaned_weights[option_name] = cleaned_value + else: + cleaned_weights[option_name] = new_weights[option] + new_options = set(cleaned_weights) - set(weights) + weights.update(cleaned_weights) if new_options: for new_option in new_options: - logging.warning(f'{type} Suboption "{new_option}" of "{name}" did not ' + logging.warning(f'{update_type} Suboption "{new_option}" of "{name}" did not ' f'overwrite a root option. ' f'This is probably in error.') return weights def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any: + from worlds import AutoWorldRegister + if not game: return get_choice(option_key, category_dict) if game in AutoWorldRegister.world_types: @@ -345,7 +373,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any: if options[option_key].supports_weighting: return get_choice(option_key, category_dict) return category_dict[option_key] - raise Exception(f"Error generating meta option {option_key} for {game}.") + raise Options.OptionError(f"Error generating meta option {option_key} for {game}.") def roll_linked_options(weights: dict) -> dict: @@ -370,7 +398,7 @@ def roll_linked_options(weights: dict) -> dict: return weights -def roll_triggers(weights: dict, triggers: list) -> dict: +def roll_triggers(weights: dict, triggers: list, valid_keys: set) -> dict: weights = copy.deepcopy(weights) # make sure we don't write back to other weights sets in same_settings weights["_Generator_Version"] = Utils.__version__ for i, option_set in enumerate(triggers): @@ -393,7 +421,7 @@ def roll_triggers(weights: dict, triggers: list) -> dict: if category_name: currently_targeted_weights = currently_targeted_weights[category_name] update_weights(currently_targeted_weights, category_options, "Triggered", option_set["option_name"]) - + valid_keys.add(key) except Exception as e: raise ValueError(f"Your trigger number {i + 1} is invalid. " f"Please fix your triggers.") from e @@ -401,27 +429,31 @@ def roll_triggers(weights: dict, triggers: list) -> dict: def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, option: type(Options.Option), plando_options: PlandoOptions): - if option_key in game_weights: - try: + try: + if option_key in game_weights: if not option.supports_weighting: player_option = option.from_any(game_weights[option_key]) else: player_option = option.from_any(get_choice(option_key, game_weights)) - setattr(ret, option_key, player_option) - except Exception as e: - raise Exception(f"Error generating option {option_key} in {ret.game}") from e else: - player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options) + player_option = option.from_any(option.default) # call the from_any here to support default "random" + setattr(ret, option_key, player_option) + except Exception as e: + raise Options.OptionError(f"Error generating option {option_key} in {ret.game}") from e else: - setattr(ret, option_key, option.from_any(option.default)) # call the from_any here to support default "random" + from worlds import AutoWorldRegister + player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options) def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses): + from worlds import AutoWorldRegister + if "linked_options" in weights: weights = roll_linked_options(weights) + valid_keys = set() if "triggers" in weights: - weights = roll_triggers(weights, weights["triggers"]) + weights = roll_triggers(weights, weights["triggers"], valid_keys) requirements = weights.get("requires", {}) if requirements: @@ -442,7 +474,12 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b ret.game = get_choice("game", weights) if ret.game not in AutoWorldRegister.world_types: - picks = Utils.get_fuzzy_results(ret.game, AutoWorldRegister.world_types, limit=1)[0] + from worlds import failed_world_loads + picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + failed_world_loads, limit=1)[0] + if picks[0] in failed_world_loads: + raise Exception(f"No functional world found to handle game {ret.game}. " + f"Did you mean '{picks[0]}' ({picks[1]}% sure)? " + f"If so, it appears the world failed to initialize correctly.") raise Exception(f"No world found to handle game {ret.game}. Did you mean '{picks[0]}' ({picks[1]}% sure)? " f"Check your spelling or installation of that world.") @@ -452,8 +489,14 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b world_type = AutoWorldRegister.world_types[ret.game] game_weights = weights[ret.game] + for weight in chain(game_weights, weights): + if weight.startswith("+"): + raise Exception(f"Merge tag cannot be used outside of trigger contexts. Found {weight}") + if weight.startswith("-"): + raise Exception(f"Remove tag cannot be used outside of trigger contexts. Found {weight}") + if "triggers" in game_weights: - weights = roll_triggers(weights, game_weights["triggers"]) + weights = roll_triggers(weights, game_weights["triggers"], valid_keys) game_weights = weights[ret.game] ret.name = get_choice('name', weights) @@ -462,38 +505,20 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b for option_key, option in world_type.options_dataclass.type_hints.items(): handle_option(ret, game_weights, option_key, option, plando_options) + valid_keys.add(option_key) + for option_key in game_weights: + if option_key in {"triggers", *valid_keys}: + continue + logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.") if PlandoOptions.items in plando_options: ret.plando_items = game_weights.get("plando_items", []) if ret.game == "A Link to the Past": - roll_alttp_settings(ret, game_weights, plando_options) - if PlandoOptions.connections in plando_options: - ret.plando_connections = [] - options = game_weights.get("plando_connections", []) - for placement in options: - if roll_percentage(get_choice("percentage", placement, 100)): - ret.plando_connections.append(PlandoConnection( - get_choice("entrance", placement), - get_choice("exit", placement), - get_choice("direction", placement, "both") - )) + roll_alttp_settings(ret, game_weights) return ret -def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): - - ret.plando_texts = {} - if PlandoOptions.texts in plando_options: - tt = TextTable() - tt.removeUnwantedText() - options = weights.get("plando_texts", []) - for placement in options: - if roll_percentage(get_choice_legacy("percentage", placement, 100)): - at = str(get_choice_legacy("at", placement)) - if at not in tt: - raise Exception(f"No text target \"{at}\" found.") - ret.plando_texts[at] = str(get_choice_legacy("text", placement)) - +def roll_alttp_settings(ret: argparse.Namespace, weights): ret.sprite_pool = weights.get('sprite_pool', []) ret.sprite = get_choice_legacy('sprite', weights, "Link") if 'random_sprite_on_event' in weights: @@ -521,7 +546,9 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): if __name__ == '__main__': import atexit confirmation = atexit.register(input, "Press enter to close.") - multiworld = main() + erargs, seed = main() + from Main import main as ERmain + multiworld = ERmain(erargs, seed) if __debug__: import gc import sys diff --git a/Launcher.py b/Launcher.py index 890957958391..e4b65be93a68 100644 --- a/Launcher.py +++ b/Launcher.py @@ -19,7 +19,7 @@ import webbrowser from os.path import isfile from shutil import which -from typing import Sequence, Union, Optional +from typing import Callable, Sequence, Union, Optional import Utils import settings @@ -100,9 +100,9 @@ def update_settings(): # Functions Component("Open host.yaml", func=open_host_yaml), Component("Open Patch", func=open_patch), - Component("Generate Template Settings", func=generate_yamls), + Component("Generate Template Options", func=generate_yamls), Component("Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/8Z65BR2")), - Component("18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4")), + Component("Unrated/18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4")), Component("Browse Files", func=browse_files), ]) @@ -160,8 +160,12 @@ def launch(exe, in_terminal=False): subprocess.Popen(exe) +refresh_components: Optional[Callable[[], None]] = None + + def run_gui(): from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget + from kivy.core.window import Window from kivy.uix.image import AsyncImage from kivy.uix.relativelayout import RelativeLayout @@ -169,11 +173,8 @@ class Launcher(App): base_title: str = "Archipelago Launcher" container: ContainerLayout grid: GridLayout - - _tools = {c.display_name: c for c in components if c.type == Type.TOOL} - _clients = {c.display_name: c for c in components if c.type == Type.CLIENT} - _adjusters = {c.display_name: c for c in components if c.type == Type.ADJUSTER} - _miscs = {c.display_name: c for c in components if c.type == Type.MISC} + _tool_layout: Optional[ScrollBox] = None + _client_layout: Optional[ScrollBox] = None def __init__(self, ctx=None): self.title = self.base_title @@ -181,18 +182,7 @@ def __init__(self, ctx=None): self.icon = r"data/icon.png" super().__init__() - def build(self): - self.container = ContainerLayout() - self.grid = GridLayout(cols=2) - self.container.add_widget(self.grid) - self.grid.add_widget(Label(text="General", size_hint_y=None, height=40)) - self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40)) - tool_layout = ScrollBox() - tool_layout.layout.orientation = "vertical" - self.grid.add_widget(tool_layout) - client_layout = ScrollBox() - client_layout.layout.orientation = "vertical" - self.grid.add_widget(client_layout) + def _refresh_components(self) -> None: def build_button(component: Component) -> Widget: """ @@ -217,14 +207,49 @@ def build_button(component: Component) -> Widget: return box_layout return button + # clear before repopulating + assert self._tool_layout and self._client_layout, "must call `build` first" + tool_children = reversed(self._tool_layout.layout.children) + for child in tool_children: + self._tool_layout.layout.remove_widget(child) + client_children = reversed(self._client_layout.layout.children) + for child in client_children: + self._client_layout.layout.remove_widget(child) + + _tools = {c.display_name: c for c in components if c.type == Type.TOOL} + _clients = {c.display_name: c for c in components if c.type == Type.CLIENT} + _adjusters = {c.display_name: c for c in components if c.type == Type.ADJUSTER} + _miscs = {c.display_name: c for c in components if c.type == Type.MISC} + for (tool, client) in itertools.zip_longest(itertools.chain( - self._tools.items(), self._miscs.items(), self._adjusters.items()), self._clients.items()): + _tools.items(), _miscs.items(), _adjusters.items() + ), _clients.items()): # column 1 if tool: - tool_layout.layout.add_widget(build_button(tool[1])) + self._tool_layout.layout.add_widget(build_button(tool[1])) # column 2 if client: - client_layout.layout.add_widget(build_button(client[1])) + self._client_layout.layout.add_widget(build_button(client[1])) + + def build(self): + self.container = ContainerLayout() + self.grid = GridLayout(cols=2) + self.container.add_widget(self.grid) + self.grid.add_widget(Label(text="General", size_hint_y=None, height=40)) + self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40)) + self._tool_layout = ScrollBox() + self._tool_layout.layout.orientation = "vertical" + self.grid.add_widget(self._tool_layout) + self._client_layout = ScrollBox() + self._client_layout.layout.orientation = "vertical" + self.grid.add_widget(self._client_layout) + + self._refresh_components() + + global refresh_components + refresh_components = self._refresh_components + + Window.bind(on_drop_file=self._on_drop_file) return self.container @@ -235,6 +260,14 @@ def component_action(button): else: launch(get_exe(button.component), button.component.cli) + def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None: + """ When a patch file is dropped into the window, run the associated component. """ + file, component = identify(filename.decode()) + if file and component: + run_component(component, file) + else: + logging.warning(f"unable to identify component for {filename}") + def _stop(self, *largs): # ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm. # Closing the window explicitly cleans it up. @@ -243,10 +276,17 @@ def _stop(self, *largs): Launcher().run() + # avoiding Launcher reference leak + # and don't try to do something with widgets after window closed + global refresh_components + refresh_components = None + def run_component(component: Component, *args): if component.func: component.func(*args) + if refresh_components: + refresh_components() elif component.script_name: subprocess.run([*get_exe(component.script_name), *args]) else: @@ -259,7 +299,7 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None): elif not args: args = {} - if "Patch|Game|Component" in args: + if args.get("Patch|Game|Component", None) is not None: file, component = identify(args["Patch|Game|Component"]) if file: args['file'] = file diff --git a/Main.py b/Main.py index f1d2f63692d6..ce054dcd393f 100644 --- a/Main.py +++ b/Main.py @@ -13,7 +13,7 @@ from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items from Options import StartInventoryPool -from Utils import __version__, output_path, version_tuple +from Utils import __version__, output_path, version_tuple, get_settings from settings import get_settings from worlds import AutoWorld from worlds.generic.Rules import exclusion_rules, locality_rules @@ -36,38 +36,13 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No logger = logging.getLogger() multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None) multiworld.plando_options = args.plando_options - - multiworld.shuffle = args.shuffle.copy() - multiworld.logic = args.logic.copy() - multiworld.mode = args.mode.copy() - multiworld.difficulty = args.difficulty.copy() - multiworld.item_functionality = args.item_functionality.copy() - multiworld.timer = args.timer.copy() - multiworld.goal = args.goal.copy() - multiworld.boss_shuffle = args.shufflebosses.copy() - multiworld.enemy_health = args.enemy_health.copy() - multiworld.enemy_damage = args.enemy_damage.copy() - multiworld.beemizer_total_chance = args.beemizer_total_chance.copy() - multiworld.beemizer_trap_chance = args.beemizer_trap_chance.copy() - multiworld.countdown_start_time = args.countdown_start_time.copy() - multiworld.red_clock_time = args.red_clock_time.copy() - multiworld.blue_clock_time = args.blue_clock_time.copy() - multiworld.green_clock_time = args.green_clock_time.copy() - multiworld.dungeon_counters = args.dungeon_counters.copy() - multiworld.triforce_pieces_available = args.triforce_pieces_available.copy() - multiworld.triforce_pieces_required = args.triforce_pieces_required.copy() - multiworld.shop_shuffle = args.shop_shuffle.copy() - multiworld.shuffle_prizes = args.shuffle_prizes.copy() - multiworld.sprite_pool = args.sprite_pool.copy() - multiworld.dark_room_logic = args.dark_room_logic.copy() multiworld.plando_items = args.plando_items.copy() multiworld.plando_texts = args.plando_texts.copy() multiworld.plando_connections = args.plando_connections.copy() - multiworld.required_medallions = args.required_medallions.copy() multiworld.game = args.game.copy() multiworld.player_name = args.name.copy() multiworld.sprite = args.sprite.copy() - multiworld.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. + multiworld.sprite_pool = args.sprite_pool.copy() multiworld.set_options(args) multiworld.set_item_links() @@ -149,14 +124,19 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for player in multiworld.player_ids: exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value) multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value + world_excluded_locations = set() for location_name in multiworld.worlds[player].options.priority_locations.value: try: location = multiworld.get_location(location_name, player) - except KeyError as e: # failed to find the given location. Check if it's a legitimate location - if location_name not in multiworld.worlds[player].location_name_to_id: - raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e - else: + except KeyError: + continue + + if location.progress_type != LocationProgressType.EXCLUDED: location.progress_type = LocationProgressType.PRIORITY + else: + logger.warning(f"Unable to prioritize location \"{location_name}\" in player {player}'s world because the world excluded it.") + world_excluded_locations.add(location_name) + multiworld.worlds[player].options.priority_locations.value -= world_excluded_locations # Set local and non-local item rules. if multiworld.players > 1: @@ -204,82 +184,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change." multiworld.itempool[:] = new_items - # temporary home for item links, should be moved out of Main - for group_id, group in multiworld.groups.items(): - def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ - Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] - ]: - classifications: Dict[str, int] = collections.defaultdict(int) - counters = {player: {name: 0 for name in shared_pool} for player in players} - for item in multiworld.itempool: - if item.player in counters and item.name in shared_pool: - counters[item.player][item.name] += 1 - classifications[item.name] |= item.classification - - for player in players.copy(): - if all([counters[player][item] == 0 for item in shared_pool]): - players.remove(player) - del (counters[player]) - - if not players: - return None, None - - for item in shared_pool: - count = min(counters[player][item] for player in players) - if count: - for player in players: - counters[player][item] = count - else: - for player in players: - del (counters[player][item]) - return counters, classifications - - common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) - if not common_item_count: - continue - - new_itempool: List[Item] = [] - for item_name, item_count in next(iter(common_item_count.values())).items(): - for _ in range(item_count): - new_item = group["world"].create_item(item_name) - # mangle together all original classification bits - new_item.classification |= classifications[item_name] - new_itempool.append(new_item) - - region = Region("Menu", group_id, multiworld, "ItemLink") - multiworld.regions.append(region) - locations = region.locations - for item in multiworld.itempool: - count = common_item_count.get(item.player, {}).get(item.name, 0) - if count: - loc = Location(group_id, f"Item Link: {item.name} -> {multiworld.player_name[item.player]} {count}", - None, region) - loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ - state.has(item_name, group_id_, count_) - - locations.append(loc) - loc.place_locked_item(item) - common_item_count[item.player][item.name] -= 1 - else: - new_itempool.append(item) - - itemcount = len(multiworld.itempool) - multiworld.itempool = new_itempool - - while itemcount > len(multiworld.itempool): - items_to_add = [] - for player in group["players"]: - if group["link_replacement"]: - item_player = group_id - else: - item_player = player - if group["replacement_items"][player]: - items_to_add.append(AutoWorld.call_single(multiworld, "create_item", item_player, - group["replacement_items"][player])) - else: - items_to_add.append(AutoWorld.call_single(multiworld, "create_filler", item_player)) - multiworld.random.shuffle(items_to_add) - multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)]) + multiworld.link_items() if any(multiworld.item_links.values()): multiworld._all_state = None @@ -297,7 +202,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ if multiworld.algorithm == 'flood': flood_items(multiworld) # different algo, biased towards early game progress items elif multiworld.algorithm == 'balanced': - distribute_items_restrictive(multiworld) + distribute_items_restrictive(multiworld, get_settings().generator.panic_method) AutoWorld.call_all(multiworld, 'post_fill') @@ -397,6 +302,17 @@ def precollect_hint(location): checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {} + # get spheres -> filter address==None -> skip empty + spheres: List[Dict[int, Set[int]]] = [] + for sphere in multiworld.get_spheres(): + current_sphere: Dict[int, Set[int]] = collections.defaultdict(set) + for sphere_location in sphere: + if type(sphere_location.address) is int: + current_sphere[sphere_location.player].add(sphere_location.address) + + if current_sphere: + spheres.append(dict(current_sphere)) + multidata = { "slot_data": slot_data, "slot_info": slot_info, @@ -411,6 +327,7 @@ def precollect_hint(location): "tags": ["AP"], "minimum_versions": minimum_versions, "seed_name": multiworld.seed_name, + "spheres": spheres, "datapackage": data_package, } AutoWorld.call_all(multiworld, "modify_multidata", multidata) diff --git a/ModuleUpdate.py b/ModuleUpdate.py index c3dc8c8a87b2..ed041bef4604 100644 --- a/ModuleUpdate.py +++ b/ModuleUpdate.py @@ -70,7 +70,7 @@ def install_pkg_resources(yes=False): subprocess.call([sys.executable, "-m", "pip", "install", "--upgrade", "setuptools"]) -def update(yes=False, force=False): +def update(yes: bool = False, force: bool = False) -> None: global update_ran if not update_ran: update_ran = True diff --git a/MultiServer.py b/MultiServer.py index c374d6d70481..f59855fca6a4 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -3,6 +3,7 @@ import argparse import asyncio import collections +import contextlib import copy import datetime import functools @@ -37,7 +38,7 @@ import NetUtils import Utils -from Utils import version_tuple, restricted_loads, Version, async_start +from Utils import version_tuple, restricted_loads, Version, async_start, get_intended_text from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission, NetworkSlot, \ SlotType, LocationStore @@ -168,18 +169,25 @@ class Context: slot_info: typing.Dict[int, NetworkSlot] generator_version = Version(0, 0, 0) checksums: typing.Dict[str, str] - item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})') + item_names: typing.Dict[str, typing.Dict[int, str]] = ( + collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})'))) item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]] - location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})') + location_names: typing.Dict[str, typing.Dict[int, str]] = ( + collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})'))) location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]] all_item_and_group_names: typing.Dict[str, typing.Set[str]] all_location_and_group_names: typing.Dict[str, typing.Set[str]] - non_hintable_names: typing.Dict[str, typing.Set[str]] + non_hintable_names: typing.Dict[str, typing.AbstractSet[str]] + spheres: typing.List[typing.Dict[int, typing.Set[int]]] + """ each sphere is { player: { location_id, ... } } """ + logger: logging.Logger + def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int, hint_cost: int, item_cheat: bool, release_mode: str = "disabled", collect_mode="disabled", remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2, - log_network: bool = False): + log_network: bool = False, logger: logging.Logger = logging.getLogger()): + self.logger = logger super(Context, self).__init__() self.slot_info = {} self.log_network = log_network @@ -224,7 +232,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo self.embedded_blacklist = {"host", "port"} self.client_ids: typing.Dict[typing.Tuple[int, int], datetime.datetime] = {} self.auto_save_interval = 60 # in seconds - self.auto_saver_thread = None + self.auto_saver_thread: typing.Optional[threading.Thread] = None self.save_dirty = False self.tags = ['AP'] self.games: typing.Dict[int, str] = {} @@ -236,6 +244,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo self.stored_data = {} self.stored_data_notification_clients = collections.defaultdict(weakref.WeakSet) self.read_data = {} + self.spheres = [] # init empty to satisfy linter, I suppose self.gamespackage = {} @@ -260,19 +269,31 @@ def _load_game_data(self): for world_name, world in worlds.AutoWorldRegister.world_types.items(): self.non_hintable_names[world_name] = world.hint_blacklist + for game_package in self.gamespackage.values(): + # remove groups from data sent to clients + del game_package["item_name_groups"] + del game_package["location_name_groups"] + def _init_game_data(self): for game_name, game_package in self.gamespackage.items(): if "checksum" in game_package: self.checksums[game_name] = game_package["checksum"] for item_name, item_id in game_package["item_name_to_id"].items(): - self.item_names[item_id] = item_name + self.item_names[game_name][item_id] = item_name for location_name, location_id in game_package["location_name_to_id"].items(): - self.location_names[location_id] = location_name + self.location_names[game_name][location_id] = location_name self.all_item_and_group_names[game_name] = \ set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name]) self.all_location_and_group_names[game_name] = \ set(game_package["location_name_to_id"]) | set(self.location_name_groups.get(game_name, [])) + archipelago_item_names = self.item_names["Archipelago"] + archipelago_location_names = self.location_names["Archipelago"] + for game in [game_name for game_name in self.gamespackage if game_name != "Archipelago"]: + # Add Archipelago items and locations to each data package. + self.item_names[game].update(archipelago_item_names) + self.location_names[game].update(archipelago_location_names) + def item_names_for_game(self, game: str) -> typing.Optional[typing.Dict[str, int]]: return self.gamespackage[game]["item_name_to_id"] if game in self.gamespackage else None @@ -287,12 +308,12 @@ async def send_msgs(self, endpoint: Endpoint, msgs: typing.Iterable[dict]) -> bo try: await endpoint.socket.send(msg) except websockets.ConnectionClosed: - logging.exception(f"Exception during send_msgs, could not send {msg}") + self.logger.exception(f"Exception during send_msgs, could not send {msg}") await self.disconnect(endpoint) return False else: if self.log_network: - logging.info(f"Outgoing message: {msg}") + self.logger.info(f"Outgoing message: {msg}") return True async def send_encoded_msgs(self, endpoint: Endpoint, msg: str) -> bool: @@ -301,12 +322,12 @@ async def send_encoded_msgs(self, endpoint: Endpoint, msg: str) -> bool: try: await endpoint.socket.send(msg) except websockets.ConnectionClosed: - logging.exception("Exception during send_encoded_msgs") + self.logger.exception("Exception during send_encoded_msgs") await self.disconnect(endpoint) return False else: if self.log_network: - logging.info(f"Outgoing message: {msg}") + self.logger.info(f"Outgoing message: {msg}") return True async def broadcast_send_encoded_msgs(self, endpoints: typing.Iterable[Endpoint], msg: str) -> bool: @@ -317,11 +338,11 @@ async def broadcast_send_encoded_msgs(self, endpoints: typing.Iterable[Endpoint] try: websockets.broadcast(sockets, msg) except RuntimeError: - logging.exception("Exception during broadcast_send_encoded_msgs") + self.logger.exception("Exception during broadcast_send_encoded_msgs") return False else: if self.log_network: - logging.info(f"Outgoing broadcast: {msg}") + self.logger.info(f"Outgoing broadcast: {msg}") return True def broadcast_all(self, msgs: typing.List[dict]): @@ -330,7 +351,7 @@ def broadcast_all(self, msgs: typing.List[dict]): async_start(self.broadcast_send_encoded_msgs(endpoints, msgs)) def broadcast_text_all(self, text: str, additional_arguments: dict = {}): - logging.info("Notice (all): %s" % text) + self.logger.info("Notice (all): %s" % text) self.broadcast_all([{**{"cmd": "PrintJSON", "data": [{ "text": text }]}, **additional_arguments}]) def broadcast_team(self, team: int, msgs: typing.List[dict]): @@ -352,7 +373,7 @@ async def disconnect(self, endpoint: Client): def notify_client(self, client: Client, text: str, additional_arguments: dict = {}): if not client.auth: return - logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team + 1, text)) + self.logger.info("Notice (Player %s in team %d): %s" % (client.name, client.team + 1, text)) async_start(self.send_msgs(client, [{"cmd": "PrintJSON", "data": [{ "text": text }], **additional_arguments}])) def notify_client_multiple(self, client: Client, texts: typing.List[str], additional_arguments: dict = {}): @@ -451,7 +472,7 @@ def _load(self, decoded_obj: dict, game_data_packages: typing.Dict[str, typing.A for game_name, data in decoded_obj.get("datapackage", {}).items(): if game_name in game_data_packages: data = game_data_packages[game_name] - logging.info(f"Loading embedded data package for game {game_name}") + self.logger.info(f"Loading embedded data package for game {game_name}") self.gamespackage[game_name] = data self.item_name_groups[game_name] = data["item_name_groups"] if "location_name_groups" in data: @@ -464,6 +485,9 @@ def _load(self, decoded_obj: dict, game_data_packages: typing.Dict[str, typing.A for game_name, data in self.location_name_groups.items(): self.read_data[f"location_name_groups_{game_name}"] = lambda lgame=game_name: self.location_name_groups[lgame] + # sorted access spheres + self.spheres = decoded_obj.get("spheres", []) + # saving def save(self, now=False) -> bool: @@ -483,7 +507,7 @@ def _save(self, exit_save: bool = False) -> bool: with open(self.save_filename, "wb") as f: f.write(zlib.compress(encoded_save)) except Exception as e: - logging.exception(e) + self.logger.exception(e) return False else: return True @@ -501,12 +525,12 @@ def init_save(self, enabled: bool = True): save_data = restricted_loads(zlib.decompress(f.read())) self.set_save(save_data) except FileNotFoundError: - logging.error('No save data found, starting a new game') + self.logger.error('No save data found, starting a new game') except Exception as e: - logging.exception(e) + self.logger.exception(e) self._start_async_saving() - def _start_async_saving(self): + def _start_async_saving(self, atexit_save: bool = True): if not self.auto_saver_thread: def save_regularly(): # time.time() is platform dependent, so using the expensive datetime method instead @@ -520,18 +544,19 @@ def get_datetime_second(): next_wakeup = (second - get_datetime_second()) % self.auto_save_interval time.sleep(max(1.0, next_wakeup)) if self.save_dirty: - logging.debug("Saving via thread.") + self.logger.debug("Saving via thread.") self._save() except OperationalError as e: - logging.exception(e) - logging.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.") + self.logger.exception(e) + self.logger.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.") else: self.save_dirty = False self.auto_saver_thread = threading.Thread(target=save_regularly, daemon=True) self.auto_saver_thread.start() - import atexit - atexit.register(self._save, True) # make sure we save on exit too + if atexit_save: + import atexit + atexit.register(self._save, True) # make sure we save on exit too def get_save(self) -> dict: self.recheck_hints() @@ -586,7 +611,7 @@ def set_save(self, savedata: dict): self.location_check_points = savedata["game_options"]["location_check_points"] self.server_password = savedata["game_options"]["server_password"] self.password = savedata["game_options"]["password"] - self.release_mode = savedata["game_options"].get("release_mode", savedata["game_options"].get("forfeit_mode", "goal")) + self.release_mode = savedata["game_options"]["release_mode"] self.remaining_mode = savedata["game_options"]["remaining_mode"] self.collect_mode = savedata["game_options"]["collect_mode"] self.item_cheat = savedata["game_options"]["item_cheat"] @@ -598,7 +623,7 @@ def set_save(self, savedata: dict): if "stored_data" in savedata: self.stored_data = savedata["stored_data"] # count items and slots from lists for items_handling = remote - logging.info( + self.logger.info( f'Loaded save file with {sum([len(v) for k, v in self.received_items.items() if k[2]])} received items ' f'for {sum(k[2] for k in self.received_items)} players') @@ -621,6 +646,16 @@ def get_rechecked_hints(self, team: int, slot: int): self.recheck_hints(team, slot) return self.hints[team, slot] + def get_sphere(self, player: int, location_id: int) -> int: + """Get sphere of a location, -1 if spheres are not available.""" + if self.spheres: + for i, sphere in enumerate(self.spheres): + if location_id in sphere.get(player, set()): + return i + raise KeyError(f"No Sphere found for location ID {location_id} belonging to player {player}. " + f"Location or player may not exist.") + return -1 + def get_players_package(self): return [NetworkPlayer(t, p, self.get_aliased_name(t, p), n) for (t, p), n in self.player_names.items()] @@ -631,8 +666,6 @@ def slot_set(self, slot) -> typing.Set[int]: def _set_options(self, server_options: dict): for key, value in server_options.items(): - if key == "forfeit_mode": - key = "release_mode" data_type = self.simple_options.get(key, None) if data_type is not None: if value not in {False, True, None}: # some can be boolean OR text, such as password @@ -642,13 +675,13 @@ def _set_options(self, server_options: dict): try: raise Exception(f"Could not set server option {key}, skipping.") from e except Exception as e: - logging.exception(e) - logging.debug(f"Setting server option {key} to {value} from supplied multidata") + self.logger.exception(e) + self.logger.debug(f"Setting server option {key} to {value} from supplied multidata") setattr(self, key, value) elif key == "disable_item_cheat": self.item_cheat = not bool(value) else: - logging.debug(f"Unrecognized server option {key}") + self.logger.debug(f"Unrecognized server option {key}") def get_aliased_name(self, team: int, slot: int): if (team, slot) in self.name_aliases: @@ -682,7 +715,7 @@ def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: b self.hints[team, player].add(hint) new_hint_events.add(player) - logging.info("Notice (Team #%d): %s" % (team + 1, format_hint(self, team, hint))) + self.logger.info("Notice (Team #%d): %s" % (team + 1, format_hint(self, team, hint))) for slot in new_hint_events: self.on_new_hint(team, slot) for slot, hint_data in concerns.items(): @@ -690,7 +723,7 @@ def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: b clients = self.clients[team].get(slot) if not clients: continue - client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)] + client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player != slot)] for client in clients: async_start(self.send_msgs(client, client_hints)) @@ -707,15 +740,18 @@ def on_goal_achieved(self, client: Client): self.save() # save goal completion flag def on_new_hint(self, team: int, slot: int): - key: str = f"_read_hints_{team}_{slot}" - targets: typing.Set[Client] = set(self.stored_data_notification_clients[key]) - if targets: - self.broadcast(targets, [{"cmd": "SetReply", "key": key, "value": self.hints[team, slot]}]) + self.on_changed_hints(team, slot) self.broadcast(self.clients[team][slot], [{ "cmd": "RoomUpdate", "hint_points": get_slot_points(self, team, slot) }]) + def on_changed_hints(self, team: int, slot: int): + key: str = f"_read_hints_{team}_{slot}" + targets: typing.Set[Client] = set(self.stored_data_notification_clients[key]) + if targets: + self.broadcast(targets, [{"cmd": "SetReply", "key": key, "value": self.hints[team, slot]}]) + def on_client_status_change(self, team: int, slot: int): key: str = f"_read_client_status_{team}_{slot}" targets: typing.Set[Client] = set(self.stored_data_notification_clients[key]) @@ -738,21 +774,21 @@ async def server(websocket, path: str = "/", ctx: Context = None): try: if ctx.log_network: - logging.info("Incoming connection") + ctx.logger.info("Incoming connection") await on_client_connected(ctx, client) if ctx.log_network: - logging.info("Sent Room Info") + ctx.logger.info("Sent Room Info") async for data in websocket: if ctx.log_network: - logging.info(f"Incoming message: {data}") + ctx.logger.info(f"Incoming message: {data}") for msg in decode(data): await process_client_cmd(ctx, client, msg) except Exception as e: if not isinstance(e, websockets.WebSocketException): - logging.exception(e) + ctx.logger.exception(e) finally: if ctx.log_network: - logging.info("Disconnected") + ctx.logger.info("Disconnected") await ctx.disconnect(client) @@ -762,10 +798,7 @@ async def on_client_connected(ctx: Context, client: Client): for slot, connected_clients in clients.items(): if connected_clients: name = ctx.player_names[team, slot] - players.append( - NetworkPlayer(team, slot, - ctx.name_aliases.get((team, slot), name), name) - ) + players.append(NetworkPlayer(team, slot, ctx.name_aliases.get((team, slot), name), name)) games = {ctx.games[x] for x in range(1, len(ctx.games) + 1)} games.add("Archipelago") await ctx.send_msgs(client, [{ @@ -780,8 +813,6 @@ async def on_client_connected(ctx: Context, client: Client): 'permissions': get_permissions(ctx), 'hint_cost': ctx.hint_cost, 'location_check_points': ctx.location_check_points, - 'datapackage_versions': {game: game_data["version"] for game, game_data - in ctx.gamespackage.items() if game in games}, 'datapackage_checksums': {game: game_data["checksum"] for game, game_data in ctx.gamespackage.items() if game in games and "checksum" in game_data}, 'seed_name': ctx.seed_name, @@ -802,14 +833,25 @@ async def on_client_disconnected(ctx: Context, client: Client): await on_client_left(ctx, client) +_non_game_messages = {"HintGame": "hinting", "Tracker": "tracking", "TextOnly": "viewing"} +""" { tag: ui_message } """ + + async def on_client_joined(ctx: Context, client: Client): if ctx.client_game_state[client.team, client.slot] == ClientStatus.CLIENT_UNKNOWN: update_client_status(ctx, client, ClientStatus.CLIENT_CONNECTED) version_str = '.'.join(str(x) for x in client.version) - verb = "tracking" if "Tracker" in client.tags else "playing" + + for tag, verb in _non_game_messages.items(): + if tag in client.tags: + final_verb = verb + break + else: + final_verb = "playing" + ctx.broadcast_text_all( f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) " - f"{verb} {ctx.games[client.slot]} has joined. " + f"{final_verb} {ctx.games[client.slot]} has joined. " f"Client({version_str}), {client.tags}.", {"type": "Join", "team": client.team, "slot": client.slot, "tags": client.tags}) ctx.notify_client(client, "Now that you are connected, " @@ -824,8 +866,19 @@ async def on_client_left(ctx: Context, client: Client): if len(ctx.clients[client.team][client.slot]) < 1: update_client_status(ctx, client, ClientStatus.CLIENT_UNKNOWN) ctx.client_connection_timers[client.team, client.slot] = datetime.datetime.now(datetime.timezone.utc) + + version_str = '.'.join(str(x) for x in client.version) + + for tag, verb in _non_game_messages.items(): + if tag in client.tags: + final_verb = f"stopped {verb}" + break + else: + final_verb = "left" + ctx.broadcast_text_all( - "%s (Team #%d) has left the game" % (ctx.get_aliased_name(client.team, client.slot), client.team + 1), + f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has {final_verb} the game. " + f"Client({version_str}), {client.tags}.", {"type": "Part", "team": client.team, "slot": client.slot}) @@ -962,9 +1015,9 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi new_item = NetworkItem(item_id, location, slot, flags) send_items_to(ctx, team, target_player, new_item) - logging.info('(Team #%d) %s sent %s to %s (%s)' % ( - team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id], - ctx.player_names[(team, target_player)], ctx.location_names[location])) + ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % ( + team + 1, ctx.player_names[(team, slot)], ctx.item_names[ctx.slot_info[target_player].game][item_id], + ctx.player_names[(team, target_player)], ctx.location_names[ctx.slot_info[slot].game][location])) info_text = json_format_send_event(new_item, target_player) ctx.broadcast_team(team, [info_text]) @@ -975,7 +1028,10 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi "hint_points": get_slot_points(ctx, team, slot), "checked_locations": new_locations, # send back new checks only }]) - + old_hints = ctx.hints[team, slot].copy() + ctx.recheck_hints(team, slot) + if old_hints != ctx.hints[team, slot]: + ctx.on_changed_hints(team, slot) ctx.save() @@ -1015,8 +1071,8 @@ def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str: text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \ - f"{ctx.item_names[hint.item]} is " \ - f"at {ctx.location_names[hint.location]} " \ + f"{ctx.item_names[ctx.slot_info[hint.receiving_player].game][hint.item]} is " \ + f"at {ctx.location_names[ctx.slot_info[hint.finding_player].game][hint.location]} " \ f"in {ctx.player_names[team, hint.finding_player]}'s World" if hint.entrance: @@ -1045,28 +1101,6 @@ def json_format_send_event(net_item: NetworkItem, receiving_player: int): "item": net_item} -def get_intended_text(input_text: str, possible_answers) -> typing.Tuple[str, bool, str]: - picks = Utils.get_fuzzy_results(input_text, possible_answers, limit=2) - if len(picks) > 1: - dif = picks[0][1] - picks[1][1] - if picks[0][1] == 100: - return picks[0][0], True, "Perfect Match" - elif picks[0][1] < 75: - return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \ - f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" - elif dif > 5: - return picks[0][0], True, "Close Match" - else: - return picks[0][0], False, f"Too many close matches for '{input_text}', " \ - f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" - else: - if picks[0][1] > 90: - return picks[0][0], True, "Only Option Match" - else: - return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \ - f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" - - class CommandMeta(type): def __new__(cls, name, bases, attrs): commands = attrs["commands"] = {} @@ -1318,7 +1352,7 @@ def _cmd_remaining(self) -> bool: if self.ctx.remaining_mode == "enabled": remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) if remaining_item_ids: - self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id] + self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id] for item_id in remaining_item_ids)) else: self.output("No remaining items found.") @@ -1331,7 +1365,7 @@ def _cmd_remaining(self) -> bool: if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL: remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) if remaining_item_ids: - self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id] + self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id] for item_id in remaining_item_ids)) else: self.output("No remaining items found.") @@ -1341,6 +1375,7 @@ def _cmd_remaining(self) -> bool: "Sorry, !remaining requires you to have beaten the game on this server") return False + @mark_raw def _cmd_missing(self, filter_text="") -> bool: """List all missing location checks from the server's perspective. Can be given text, which will be used as filter.""" @@ -1348,9 +1383,14 @@ def _cmd_missing(self, filter_text="") -> bool: locations = get_missing_checks(self.ctx, self.client.team, self.client.slot) if locations: - names = [self.ctx.location_names[location] for location in locations] + game = self.ctx.slot_info[self.client.slot].game + names = [self.ctx.location_names[game][location] for location in locations] if filter_text: - names = [name for name in names if filter_text in name] + location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]] + if filter_text in location_groups: # location group name + names = [name for name in names if name in location_groups[filter_text]] + else: + names = [name for name in names if filter_text in name] texts = [f'Missing: {name}' for name in names] if filter_text: texts.append(f"Found {len(locations)} missing location checks, displaying {len(names)} of them.") @@ -1361,6 +1401,7 @@ def _cmd_missing(self, filter_text="") -> bool: self.output("No missing location checks found.") return True + @mark_raw def _cmd_checked(self, filter_text="") -> bool: """List all done location checks from the server's perspective. Can be given text, which will be used as filter.""" @@ -1368,9 +1409,14 @@ def _cmd_checked(self, filter_text="") -> bool: locations = get_checked_checks(self.ctx, self.client.team, self.client.slot) if locations: - names = [self.ctx.location_names[location] for location in locations] + game = self.ctx.slot_info[self.client.slot].game + names = [self.ctx.location_names[game][location] for location in locations] if filter_text: - names = [name for name in names if filter_text in name] + location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]] + if filter_text in location_groups: # location group name + names = [name for name in names if name in location_groups[filter_text]] + else: + names = [name for name in names if filter_text in name] texts = [f'Checked: {name}' for name in names] if filter_text: texts.append(f"Found {len(locations)} done location checks, displaying {len(names)} of them.") @@ -1445,10 +1491,10 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool: elif input_text.isnumeric(): game = self.ctx.games[self.client.slot] hint_id = int(input_text) - hint_name = self.ctx.item_names[hint_id] \ - if not for_location and hint_id in self.ctx.item_names \ - else self.ctx.location_names[hint_id] \ - if for_location and hint_id in self.ctx.location_names \ + hint_name = self.ctx.item_names[game][hint_id] \ + if not for_location and hint_id in self.ctx.item_names[game] \ + else self.ctx.location_names[game][hint_id] \ + if for_location and hint_id in self.ctx.location_names[game] \ else None if hint_name in self.ctx.non_hintable_names[game]: self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.") @@ -1493,15 +1539,13 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool: if hints: new_hints = set(hints) - self.ctx.hints[self.client.team, self.client.slot] - old_hints = set(hints) - new_hints - if old_hints: - self.ctx.notify_hints(self.client.team, list(old_hints)) - if not new_hints: - self.output("Hint was previously used, no points deducted.") + old_hints = list(set(hints) - new_hints) + if old_hints and not new_hints: + self.ctx.notify_hints(self.client.team, old_hints) + self.output("Hint was previously used, no points deducted.") if new_hints: found_hints = [hint for hint in new_hints if hint.found] not_found_hints = [hint for hint in new_hints if not hint.found] - if not not_found_hints: # everything's been found, no need to pay can_pay = 1000 elif cost: @@ -1512,8 +1556,11 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool: self.ctx.random.shuffle(not_found_hints) # By popular vote, make hints prefer non-local placements not_found_hints.sort(key=lambda hint: int(hint.receiving_player != hint.finding_player)) + # By another popular vote, prefer early sphere + not_found_hints.sort(key=lambda hint: self.ctx.get_sphere(hint.finding_player, hint.location), + reverse=True) - hints = found_hints + hints = found_hints + old_hints while can_pay > 0: if not not_found_hints: break @@ -1521,9 +1568,10 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool: hints.append(hint) can_pay -= 1 self.ctx.hints_used[self.client.team, self.client.slot] += 1 - points_available = get_client_points(self.ctx, self.client) + self.ctx.notify_hints(self.client.team, hints) if not_found_hints: + points_available = get_client_points(self.ctx, self.client) if hints and cost and int((points_available // cost) == 0): self.output( f"There may be more hintables, however, you cannot afford to pay for any more. " @@ -1536,7 +1584,6 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool: self.output(f"You can't afford the hint. " f"You have {points_available} points and need at least " f"{self.ctx.get_hint_cost(self.client.slot)}.") - self.ctx.notify_hints(self.client.team, hints) self.ctx.save() return True @@ -1591,7 +1638,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict): try: cmd: str = args["cmd"] except: - logging.exception(f"Could not get command from {args}") + ctx.logger.exception(f"Could not get command from {args}") await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "cmd", "original_cmd": None, "text": f"Could not get command from {args} at `cmd`"}]) raise @@ -1617,7 +1664,9 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict): else: team, slot = ctx.connect_names[args['name']] game = ctx.games[slot] - ignore_game = ("TextOnly" in args["tags"] or "Tracker" in args["tags"]) and not args.get("game") + + ignore_game = not args.get("game") and any(tag in _non_game_messages for tag in args["tags"]) + if not ignore_game and args['game'] != game: errors.add('InvalidGame') minver = min_client_version if ignore_game else ctx.minimum_client_versions[slot] @@ -1632,7 +1681,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict): if ctx.compatibility == 0 and args['version'] != version_tuple: errors.add('IncompatibleVersion') if errors: - logging.info(f"A client connection was refused due to: {errors}, the sent connect information was {args}.") + ctx.logger.info(f"A client connection was refused due to: {errors}, the sent connect information was {args}.") await ctx.send_msgs(client, [{"cmd": "ConnectionRefused", "errors": list(errors)}]) else: team, slot = ctx.connect_names[args['name']] @@ -1833,6 +1882,11 @@ def update_client_status(ctx: Context, client: Client, new_status: ClientStatus) if current != ClientStatus.CLIENT_GOAL: # can't undo goal completion if new_status == ClientStatus.CLIENT_GOAL: ctx.on_goal_achieved(client) + # if player has yet to ever connect to the server, they will not be in client_game_state + if all(player in ctx.client_game_state and ctx.client_game_state[player] == ClientStatus.CLIENT_GOAL + for player in ctx.player_names + if player[0] == client.team and player[1] != client.slot): + ctx.broadcast_text_all(f"Team #{client.team + 1} has completed all of their games! Congratulations!") ctx.client_game_state[client.team, client.slot] = new_status ctx.on_client_status_change(client.team, client.slot) @@ -1878,15 +1932,13 @@ def _cmd_status(self, tag: str = "") -> bool: def _cmd_exit(self) -> bool: """Shutdown the server""" self.ctx.server.ws_server.close() - if self.ctx.shutdown_task: - self.ctx.shutdown_task.cancel() self.ctx.exit_event.set() return True @mark_raw def _cmd_alias(self, player_name_then_alias_name): """Set a player's alias, by listing their base name and then their intended alias.""" - player_name, alias_name = player_name_then_alias_name.split(" ", 1) + player_name, _, alias_name = player_name_then_alias_name.partition(" ") player_name, usable, response = get_intended_text(player_name, self.ctx.player_names.values()) if usable: for (team, slot), name in self.ctx.player_names.items(): @@ -2086,8 +2138,8 @@ def _cmd_hint_location(self, player_name: str, *location_name: str) -> bool: if full_name.isnumeric(): location, usable, response = int(full_name), True, None - elif self.ctx.location_names_for_game(game) is not None: - location, usable, response = get_intended_text(full_name, self.ctx.location_names_for_game(game)) + elif game in self.ctx.all_location_and_group_names: + location, usable, response = get_intended_text(full_name, self.ctx.all_location_and_group_names[game]) else: self.output("Can't look up location for unknown game. Hint for ID instead.") return False @@ -2095,6 +2147,11 @@ def _cmd_hint_location(self, player_name: str, *location_name: str) -> bool: if usable: if isinstance(location, int): hints = collect_hint_location_id(self.ctx, team, slot, location) + elif game in self.ctx.location_name_groups and location in self.ctx.location_name_groups[game]: + hints = [] + for loc_name_from_group in self.ctx.location_name_groups[game][location]: + if loc_name_from_group in self.ctx.location_names_for_game(game): + hints.extend(collect_hint_location_name(self.ctx, team, slot, loc_name_from_group)) else: hints = collect_hint_location_name(self.ctx, team, slot, location) if hints: @@ -2110,32 +2167,47 @@ def _cmd_hint_location(self, player_name: str, *location_name: str) -> bool: self.output(response) return False - def _cmd_option(self, option_name: str, option: str): - """Set options for the server.""" - - attrtype = self.ctx.simple_options.get(option_name, None) - if attrtype: - if attrtype == bool: - def attrtype(input_text: str): - return input_text.lower() not in {"off", "0", "false", "none", "null", "no"} - elif attrtype == str and option_name.endswith("password"): - def attrtype(input_text: str): - if input_text.lower() in {"null", "none", '""', "''"}: - return None - return input_text - setattr(self.ctx, option_name, attrtype(option)) - self.output(f"Set option {option_name} to {getattr(self.ctx, option_name)}") - if option_name in {"release_mode", "remaining_mode", "collect_mode"}: - self.ctx.broadcast_all([{"cmd": "RoomUpdate", 'permissions': get_permissions(self.ctx)}]) - elif option_name in {"hint_cost", "location_check_points"}: - self.ctx.broadcast_all([{"cmd": "RoomUpdate", option_name: getattr(self.ctx, option_name)}]) - return True - else: - known = (f"{option}:{otype}" for option, otype in self.ctx.simple_options.items()) - self.output(f"Unrecognized Option {option_name}, known: " - f"{', '.join(known)}") + def _cmd_option(self, option_name: str, option_value: str): + """Set an option for the server.""" + value_type = self.ctx.simple_options.get(option_name, None) + if not value_type: + known_options = (f"{option}: {option_type}" for option, option_type in self.ctx.simple_options.items()) + self.output(f"Unrecognized option '{option_name}', known: {', '.join(known_options)}") return False + if value_type == bool: + def value_type(input_text: str): + return input_text.lower() not in {"off", "0", "false", "none", "null", "no"} + elif value_type == str and option_name.endswith("password"): + def value_type(input_text: str): + return None if input_text.lower() in {"null", "none", '""', "''"} else input_text + elif value_type == str and option_name.endswith("mode"): + valid_values = {"goal", "enabled", "disabled"} + valid_values.update(("auto", "auto_enabled") if option_name != "remaining_mode" else []) + if option_value.lower() not in valid_values: + self.output(f"Unrecognized {option_name} value '{option_value}', known: {', '.join(valid_values)}") + return False + + setattr(self.ctx, option_name, value_type(option_value)) + self.output(f"Set option {option_name} to {getattr(self.ctx, option_name)}") + if option_name in {"release_mode", "remaining_mode", "collect_mode"}: + self.ctx.broadcast_all([{"cmd": "RoomUpdate", 'permissions': get_permissions(self.ctx)}]) + elif option_name in {"hint_cost", "location_check_points"}: + self.ctx.broadcast_all([{"cmd": "RoomUpdate", option_name: getattr(self.ctx, option_name)}]) + return True + + def _cmd_datastore(self): + """Debug Tool: list writable datastorage keys and approximate the size of their values with pickle.""" + total: int = 0 + texts = [] + for key, value in self.ctx.stored_data.items(): + size = len(pickle.dumps(value)) + total += size + texts.append(f"Key: {key} | Size: {size}B") + texts.insert(0, f"Found {len(self.ctx.stored_data)} keys, " + f"approximately totaling {Utils.format_SI_prefix(total, power=1024)}B") + self.output("\n".join(texts)) + async def console(ctx: Context): import sys @@ -2159,7 +2231,7 @@ async def console(ctx: Context): def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() - defaults = Utils.get_options()["server_options"].as_dict() + defaults = Utils.get_settings()["server_options"].as_dict() parser.add_argument('multidata', nargs="?", default=defaults["multidata"]) parser.add_argument('--host', default=defaults["host"]) parser.add_argument('--port', default=defaults["port"], type=int) @@ -2217,7 +2289,8 @@ def parse_args() -> argparse.Namespace: async def auto_shutdown(ctx, to_cancel=None): - await asyncio.sleep(ctx.auto_shutdown) + with contextlib.suppress(asyncio.TimeoutError): + await asyncio.wait_for(ctx.exit_event.wait(), ctx.auto_shutdown) def inactivity_shutdown(): ctx.server.ws_server.close() @@ -2225,7 +2298,7 @@ def inactivity_shutdown(): if to_cancel: for task in to_cancel: task.cancel() - logging.info("Shutting down due to inactivity.") + ctx.logger.info("Shutting down due to inactivity.") while not ctx.exit_event.is_set(): if not ctx.client_activity_timers.values(): @@ -2237,7 +2310,8 @@ def inactivity_shutdown(): if seconds < 0: inactivity_shutdown() else: - await asyncio.sleep(seconds) + with contextlib.suppress(asyncio.TimeoutError): + await asyncio.wait_for(ctx.exit_event.wait(), seconds) def load_server_cert(path: str, cert_key: typing.Optional[str]) -> "ssl.SSLContext": diff --git a/NetUtils.py b/NetUtils.py index a2db6a2ac5c4..f8d698c74fcc 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -198,7 +198,8 @@ class JSONtoTextParser(metaclass=HandlerMeta): "slateblue": "6D8BE8", "plum": "AF99EF", "salmon": "FA8072", - "white": "FFFFFF" + "white": "FFFFFF", + "orange": "FF7700", } def __init__(self, ctx): @@ -247,7 +248,7 @@ def _handle_item_name(self, node: JSONMessagePart): def _handle_item_id(self, node: JSONMessagePart): item_id = int(node["text"]) - node["text"] = self.ctx.item_names[item_id] + node["text"] = self.ctx.item_names.lookup_in_slot(item_id, node["player"]) return self._handle_item_name(node) def _handle_location_name(self, node: JSONMessagePart): @@ -255,8 +256,8 @@ def _handle_location_name(self, node: JSONMessagePart): return self._handle_color(node) def _handle_location_id(self, node: JSONMessagePart): - item_id = int(node["text"]) - node["text"] = self.ctx.location_names[item_id] + location_id = int(node["text"]) + node["text"] = self.ctx.location_names.lookup_in_slot(location_id, node["player"]) return self._handle_location_name(node) def _handle_entrance_name(self, node: JSONMessagePart): @@ -290,8 +291,8 @@ def add_json_item(parts: list, item_id: int, player: int = 0, item_flags: int = parts.append({"text": str(item_id), "player": player, "flags": item_flags, "type": JSONTypes.item_id, **kwargs}) -def add_json_location(parts: list, item_id: int, player: int = 0, **kwargs) -> None: - parts.append({"text": str(item_id), "player": player, "type": JSONTypes.location_id, **kwargs}) +def add_json_location(parts: list, location_id: int, player: int = 0, **kwargs) -> None: + parts.append({"text": str(location_id), "player": player, "type": JSONTypes.location_id, **kwargs}) class Hint(typing.NamedTuple): diff --git a/Options.py b/Options.py index ff8ad11c5a5a..d040828509d1 100644 --- a/Options.py +++ b/Options.py @@ -7,12 +7,14 @@ import numbers import random import typing +import enum from copy import deepcopy from dataclasses import dataclass from schema import And, Optional, Or, Schema +from typing_extensions import Self -from Utils import get_fuzzy_results, is_iterable_of_str +from Utils import get_fuzzy_results, is_iterable_except_str if typing.TYPE_CHECKING: from BaseClasses import PlandoOptions @@ -20,6 +22,19 @@ import pathlib +class OptionError(ValueError): + pass + + +class Visibility(enum.IntFlag): + none = 0b0000 + template = 0b0001 + simple_ui = 0b0010 # show option in simple menus, such as player-options + complex_ui = 0b0100 # show option in complex menus, such as weighted-options + spoiler = 0b1000 + all = 0b1111 + + class AssembleOptions(abc.ABCMeta): def __new__(mcs, name, bases, attrs): options = attrs["options"] = {} @@ -38,9 +53,14 @@ def __new__(mcs, name, bases, attrs): attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()}) options.update(new_options) # apply aliases, without name_lookup - aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if - name.startswith("alias_")} - + aliases = attrs["aliases"] = {name[6:].lower(): option_id for name, option_id in attrs.items() if + name.startswith("alias_")} + + assert ( + name in {"Option", "VerifyKeys"} or # base abstract classes don't need default + "default" in attrs or + any(hasattr(base, "default") for base in bases) + ), f"Option class {name} needs default value" assert "random" not in aliases, "Choice option 'random' cannot be manually assigned." # auto-alias Off and On being parsed as True and False @@ -96,7 +116,8 @@ def meta__init__(self, *args, **kwargs): class Option(typing.Generic[T], metaclass=AssembleOptions): value: T - default = 0 + default: typing.ClassVar[typing.Any] # something that __init__ will be able to convert to the correct type + visibility = Visibility.all # convert option_name_long into Name Long as display_name, otherwise name_long is the result. # Handled in get_option_name() @@ -105,9 +126,28 @@ class Option(typing.Generic[T], metaclass=AssembleOptions): # can be weighted between selections supports_weighting = True + rich_text_doc: typing.Optional[bool] = None + """Whether the WebHost should render the Option's docstring as rich text. + + If this is True, the Option's docstring is interpreted as reStructuredText_, + the standard Python markup format. In the WebHost, it's rendered to HTML so + that lists, emphasis, and other rich text features are displayed properly. + + If this is False, the docstring is instead interpreted as plain text, and + displayed as-is on the WebHost with whitespace preserved. + + If this is None, it inherits the value of `World.rich_text_options_doc`. For + backwards compatibility, this defaults to False, but worlds are encouraged to + set it to True and use reStructuredText for their Option documentation. + + .. _reStructuredText: https://docutils.sourceforge.io/rst.html + """ + # filled by AssembleOptions: - name_lookup: typing.Dict[T, str] - options: typing.Dict[str, int] + name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore + # https://github.com/python/typing/discussions/1460 the reason for this type: ignore + options: typing.ClassVar[typing.Dict[str, int]] + aliases: typing.ClassVar[typing.Dict[str, int]] def __repr__(self) -> str: return f"{self.__class__.__name__}({self.current_option_name})" @@ -119,12 +159,6 @@ def __hash__(self) -> int: def current_key(self) -> str: return self.name_lookup[self.value] - def get_current_option_name(self) -> str: - """Deprecated. use current_option_name instead. TODO remove around 0.4""" - logging.warning(DeprecationWarning(f"get_current_option_name for {self.__class__.__name__} is deprecated." - f" use current_option_name instead. Worlds should use {self}.current_key")) - return self.current_option_name - @property def current_option_name(self) -> str: """For display purposes. Worlds should be using current_key.""" @@ -160,6 +194,8 @@ class FreeText(Option[str]): """Text option that allows users to enter strings. Needs to be validated by the world or option definition.""" + default = "" + def __init__(self, value: str): assert isinstance(value, str), "value of FreeText must be a string" self.value = value @@ -180,6 +216,14 @@ def from_any(cls, data: typing.Any) -> FreeText: def get_option_name(cls, value: str) -> str: return value + def __eq__(self, other): + if isinstance(other, self.__class__): + return other.value == self.value + elif isinstance(other, str): + return other == self.value + else: + raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") + class NumericOption(Option[int], numbers.Integral, abc.ABC): default = 0 @@ -357,7 +401,8 @@ class Toggle(NumericOption): default = 0 def __init__(self, value: int): - assert value == 0 or value == 1, "value of Toggle can only be 0 or 1" + # if user puts in an invalid value, make it valid + value = int(bool(value)) self.value = value @classmethod @@ -708,6 +753,12 @@ def __init__(self, value: int) -> None: elif value > self.range_end and value not in self.special_range_names.values(): raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__} " + f"and is also not one of the supported named special values: {self.special_range_names}") + + # See docstring + for key in self.special_range_names: + if key != key.lower(): + raise Exception(f"{self.__class__.__name__} has an invalid special_range_names key: {key}. " + f"NamedRange keys must use only lowercase letters, and ideally should be snake_case.") self.value = value @classmethod @@ -718,39 +769,9 @@ def from_text(cls, text: str) -> Range: return super().from_text(text) -class SpecialRange(NamedRange): - special_range_cutoff = 0 - - # TODO: remove class SpecialRange, earliest 3 releases after 0.4.3 - def __new__(cls, value: int) -> SpecialRange: - from Utils import deprecate - deprecate(f"Option type {cls.__name__} is a subclass of SpecialRange, which is deprecated and pending removal. " - "Consider switching to NamedRange, which supports all use-cases of SpecialRange, and more. In " - "NamedRange, range_start specifies the lower end of the regular range, while special values can be " - "placed anywhere (below, inside, or above the regular range).") - return super().__new__(cls) - - @classmethod - def weighted_range(cls, text) -> Range: - if text == "random-low": - return cls(cls.triangular(cls.special_range_cutoff, cls.range_end, cls.special_range_cutoff)) - elif text == "random-high": - return cls(cls.triangular(cls.special_range_cutoff, cls.range_end, cls.range_end)) - elif text == "random-middle": - return cls(cls.triangular(cls.special_range_cutoff, cls.range_end)) - elif text.startswith("random-range-"): - return cls.custom_range(text) - elif text == "random": - return cls(random.randint(cls.special_range_cutoff, cls.range_end)) - else: - raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. " - f"Acceptable values are: random, random-high, random-middle, random-low, " - f"random-range-low--, random-range-middle--, " - f"random-range-high--, or random-range--.") - - class FreezeValidKeys(AssembleOptions): def __new__(mcs, name, bases, attrs): + assert not "_valid_keys" in attrs, "'_valid_keys' gets set by FreezeValidKeys, define 'valid_keys' instead." if "valid_keys" in attrs: attrs["_valid_keys"] = frozenset(attrs["valid_keys"]) return super(FreezeValidKeys, mcs).__new__(mcs, name, bases, attrs) @@ -765,17 +786,22 @@ class VerifyKeys(metaclass=FreezeValidKeys): verify_location_name: bool = False value: typing.Any - @classmethod - def verify_keys(cls, data: typing.Iterable[str]) -> None: - if cls.valid_keys: - data = set(data) - dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data) - extra = dataset - cls._valid_keys + def verify_keys(self) -> None: + if self.valid_keys: + data = set(self.value) + dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data) + extra = dataset - self._valid_keys if extra: - raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. " - f"Allowed keys: {cls._valid_keys}.") + raise OptionError( + f"Found unexpected key {', '.join(extra)} in {getattr(self, 'display_name', self)}. " + f"Allowed keys: {self._valid_keys}." + ) def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None: + try: + self.verify_keys() + except OptionError as validation_error: + raise OptionError(f"Player {player_name} has invalid option keys:\n{validation_error}") if self.convert_name_groups and self.verify_item_name: new_value = type(self.value)() # empty container of whatever value is for item_name in self.value: @@ -803,7 +829,7 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mapping[str, typing.Any]): - default: typing.Dict[str, typing.Any] = {} + default = {} supports_weighting = False def __init__(self, value: typing.Dict[str, typing.Any]): @@ -812,7 +838,6 @@ def __init__(self, value: typing.Dict[str, typing.Any]): @classmethod def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict: if type(data) == dict: - cls.verify_keys(data) return cls(data) else: raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") @@ -844,10 +869,10 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead. # Not a docstring so it doesn't get grabbed by the options system. - default: typing.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = () + default = () supports_weighting = False - def __init__(self, value: typing.Iterable[str]): + def __init__(self, value: typing.Iterable[typing.Any]): self.value = list(deepcopy(value)) super(OptionList, self).__init__() @@ -857,8 +882,7 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): - if is_iterable_of_str(data): - cls.verify_keys(data) + if is_iterable_except_str(data): return cls(data) return cls.from_text(str(data)) @@ -870,7 +894,7 @@ def __contains__(self, item): class OptionSet(Option[typing.Set[str]], VerifyKeys): - default: typing.Union[typing.Set[str], typing.FrozenSet[str]] = frozenset() + default = frozenset() supports_weighting = False def __init__(self, value: typing.Iterable[str]): @@ -883,8 +907,7 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): - if is_iterable_of_str(data): - cls.verify_keys(data) + if is_iterable_except_str(data): return cls(data) return cls.from_text(str(data)) @@ -900,26 +923,283 @@ class ItemSet(OptionSet): convert_name_groups = True +class PlandoText(typing.NamedTuple): + at: str + text: typing.List[str] + percentage: int = 100 + + +PlandoTextsFromAnyType = typing.Union[ + typing.Iterable[typing.Union[typing.Mapping[str, typing.Any], PlandoText, typing.Any]], typing.Any +] + + +class PlandoTexts(Option[typing.List[PlandoText]], VerifyKeys): + default = () + supports_weighting = False + display_name = "Plando Texts" + + def __init__(self, value: typing.Iterable[PlandoText]) -> None: + self.value = list(deepcopy(value)) + super().__init__() + + def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None: + from BaseClasses import PlandoOptions + if self.value and not (PlandoOptions.texts & plando_options): + # plando is disabled but plando options were given so overwrite the options + self.value = [] + logging.warning(f"The plando texts module is turned off, " + f"so text for {player_name} will be ignored.") + else: + super().verify(world, player_name, plando_options) + + def verify_keys(self) -> None: + if self.valid_keys: + data = set(text.at for text in self) + dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data) + extra = dataset - self._valid_keys + if extra: + raise OptionError( + f"Invalid \"at\" placement {', '.join(extra)} in {getattr(self, 'display_name', self)}. " + f"Allowed placements: {self._valid_keys}." + ) + + @classmethod + def from_any(cls, data: PlandoTextsFromAnyType) -> Self: + texts: typing.List[PlandoText] = [] + if isinstance(data, typing.Iterable): + for text in data: + if isinstance(text, typing.Mapping): + if random.random() < float(text.get("percentage", 100)/100): + at = text.get("at", None) + if at is not None: + given_text = text.get("text", []) + if isinstance(given_text, str): + given_text = [given_text] + texts.append(PlandoText( + at, + given_text, + text.get("percentage", 100) + )) + elif isinstance(text, PlandoText): + if random.random() < float(text.percentage/100): + texts.append(text) + else: + raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}") + return cls(texts) + else: + raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}") + + @classmethod + def get_option_name(cls, value: typing.List[PlandoText]) -> str: + return str({text.at: " ".join(text.text) for text in value}) + + def __iter__(self) -> typing.Iterator[PlandoText]: + yield from self.value + + def __getitem__(self, index: typing.SupportsIndex) -> PlandoText: + return self.value.__getitem__(index) + + def __len__(self) -> int: + return self.value.__len__() + + +class ConnectionsMeta(AssembleOptions): + def __new__(mcs, name: str, bases: tuple[type, ...], attrs: dict[str, typing.Any]): + if name != "PlandoConnections": + assert "entrances" in attrs, f"Please define valid entrances for {name}" + attrs["entrances"] = frozenset((connection.lower() for connection in attrs["entrances"])) + assert "exits" in attrs, f"Please define valid exits for {name}" + attrs["exits"] = frozenset((connection.lower() for connection in attrs["exits"])) + if "__doc__" not in attrs: + attrs["__doc__"] = PlandoConnections.__doc__ + cls = super().__new__(mcs, name, bases, attrs) + return cls + + +class PlandoConnection(typing.NamedTuple): + class Direction: + entrance = "entrance" + exit = "exit" + both = "both" + + entrance: str + exit: str + direction: typing.Literal["entrance", "exit", "both"] # TODO: convert Direction to StrEnum once 3.8 is dropped + percentage: int = 100 + + +PlandoConFromAnyType = typing.Union[ + typing.Iterable[typing.Union[typing.Mapping[str, typing.Any], PlandoConnection, typing.Any]], typing.Any +] + + +class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=ConnectionsMeta): + """Generic connections plando. Format is: + - entrance: "Entrance Name" + exit: "Exit Name" + direction: "Direction" + percentage: 100 + Direction must be one of 'entrance', 'exit', or 'both', and defaults to 'both' if omitted. + Percentage is an integer from 1 to 100, and defaults to 100 when omitted.""" + + display_name = "Plando Connections" + + default = () + supports_weighting = False + + entrances: typing.ClassVar[typing.AbstractSet[str]] + exits: typing.ClassVar[typing.AbstractSet[str]] + + duplicate_exits: bool = False + """Whether or not exits should be allowed to be duplicate.""" + + def __init__(self, value: typing.Iterable[PlandoConnection]): + self.value = list(deepcopy(value)) + super(PlandoConnections, self).__init__() + + @classmethod + def validate_entrance_name(cls, entrance: str) -> bool: + return entrance.lower() in cls.entrances + + @classmethod + def validate_exit_name(cls, exit: str) -> bool: + return exit.lower() in cls.exits + + @classmethod + def can_connect(cls, entrance: str, exit: str) -> bool: + """Checks that a given entrance can connect to a given exit. + By default, this will always return true unless overridden.""" + return True + + @classmethod + def validate_plando_connections(cls, connections: typing.Iterable[PlandoConnection]) -> None: + used_entrances: typing.List[str] = [] + used_exits: typing.List[str] = [] + for connection in connections: + entrance = connection.entrance + exit = connection.exit + direction = connection.direction + if direction not in (PlandoConnection.Direction.entrance, + PlandoConnection.Direction.exit, + PlandoConnection.Direction.both): + raise ValueError(f"Unknown direction: {direction}") + if entrance in used_entrances: + raise ValueError(f"Duplicate Entrance {entrance} not allowed.") + if not cls.duplicate_exits and exit in used_exits: + raise ValueError(f"Duplicate Exit {exit} not allowed.") + used_entrances.append(entrance) + used_exits.append(exit) + if not cls.validate_entrance_name(entrance): + raise ValueError(f"{entrance.title()} is not a valid entrance.") + if not cls.validate_exit_name(exit): + raise ValueError(f"{exit.title()} is not a valid exit.") + if not cls.can_connect(entrance, exit): + raise ValueError(f"Connection between {entrance.title()} and {exit.title()} is invalid.") + + @classmethod + def from_any(cls, data: PlandoConFromAnyType) -> Self: + if not isinstance(data, typing.Iterable): + raise Exception(f"Cannot create plando connections from non-List value, got {type(data)}.") + + value: typing.List[PlandoConnection] = [] + for connection in data: + if isinstance(connection, typing.Mapping): + percentage = connection.get("percentage", 100) + if random.random() < float(percentage / 100): + entrance = connection.get("entrance", None) + if is_iterable_except_str(entrance): + entrance = random.choice(sorted(entrance)) + exit = connection.get("exit", None) + if is_iterable_except_str(exit): + exit = random.choice(sorted(exit)) + direction = connection.get("direction", "both") + + if not entrance or not exit: + raise Exception("Plando connection must have an entrance and an exit.") + value.append(PlandoConnection( + entrance, + exit, + direction, + percentage + )) + elif isinstance(connection, PlandoConnection): + if random.random() < float(connection.percentage / 100): + value.append(connection) + else: + raise Exception(f"Cannot create connection from non-Dict type, got {type(connection)}.") + cls.validate_plando_connections(value) + return cls(value) + + def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None: + from BaseClasses import PlandoOptions + if self.value and not (PlandoOptions.connections & plando_options): + # plando is disabled but plando options were given so overwrite the options + self.value = [] + logging.warning(f"The plando connections module is turned off, " + f"so connections for {player_name} will be ignored.") + + @classmethod + def get_option_name(cls, value: typing.List[PlandoConnection]) -> str: + return ", ".join(["%s %s %s" % (connection.entrance, + "<=>" if connection.direction == PlandoConnection.Direction.both else + "<=" if connection.direction == PlandoConnection.Direction.exit else + "=>", + connection.exit) for connection in value]) + + def __getitem__(self, index: typing.SupportsIndex) -> PlandoConnection: + return self.value.__getitem__(index) + + def __iter__(self) -> typing.Iterator[PlandoConnection]: + yield from self.value + + def __len__(self) -> int: + return len(self.value) + + class Accessibility(Choice): - """Set rules for reachability of your items/locations. - Locations: ensure everything can be reached and acquired. - Items: ensure all logically relevant items can be acquired. - Minimal: ensure what is needed to reach your goal can be acquired.""" + """ + Set rules for reachability of your items/locations. + + **Full:** ensure everything can be reached and acquired. + + **Minimal:** ensure what is needed to reach your goal can be acquired. + """ display_name = "Accessibility" - option_locations = 0 - option_items = 1 + rich_text_doc = True + option_full = 0 option_minimal = 2 alias_none = 2 + alias_locations = 0 + alias_items = 0 + default = 0 + + +class ItemsAccessibility(Accessibility): + """ + Set rules for reachability of your items/locations. + + **Full:** ensure everything can be reached and acquired. + + **Minimal:** ensure what is needed to reach your goal can be acquired. + + **Items:** ensure all logically relevant items can be acquired. Some items, such as keys, may be self-locking, and + some locations may be inaccessible. + """ + option_items = 1 default = 1 class ProgressionBalancing(NamedRange): """A system that can move progression earlier, to try and prevent the player from getting stuck and bored early. - A lower setting means more getting stuck. A higher setting means less getting stuck.""" + + A lower setting means more getting stuck. A higher setting means less getting stuck. + """ default = 50 range_start = 0 range_end = 99 display_name = "Progression Balancing" + rich_text_doc = True special_range_names = { "disabled": 0, "normal": 50, @@ -952,7 +1232,7 @@ class CommonOptions(metaclass=OptionsMetaProperty): def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, typing.Any]: """ Returns a dictionary of [str, Option.value] - + :param option_names: names of the options to return :param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab` """ @@ -984,29 +1264,36 @@ def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, class LocalItems(ItemSet): """Forces these items to be in their native world.""" display_name = "Local Items" + rich_text_doc = True class NonLocalItems(ItemSet): """Forces these items to be outside their native world.""" - display_name = "Not Local Items" + display_name = "Non-local Items" + rich_text_doc = True class StartInventory(ItemDict): """Start with these items.""" verify_item_name = True display_name = "Start Inventory" + rich_text_doc = True class StartInventoryPool(StartInventory): """Start with these items and don't place them in the world. - The game decides what the replacement items will be.""" + + The game decides what the replacement items will be. + """ verify_item_name = True display_name = "Start Inventory from Pool" + rich_text_doc = True class StartHints(ItemSet): - """Start with these item's locations prefilled into the !hint command.""" + """Start with these item's locations prefilled into the ``!hint`` command.""" display_name = "Start Hints" + rich_text_doc = True class LocationSet(OptionSet): @@ -1015,28 +1302,33 @@ class LocationSet(OptionSet): class StartLocationHints(LocationSet): - """Start with these locations and their item prefilled into the !hint command""" + """Start with these locations and their item prefilled into the ``!hint`` command.""" display_name = "Start Location Hints" + rich_text_doc = True class ExcludeLocations(LocationSet): - """Prevent these locations from having an important item""" + """Prevent these locations from having an important item.""" display_name = "Excluded Locations" + rich_text_doc = True class PriorityLocations(LocationSet): - """Prevent these locations from having an unimportant item""" + """Prevent these locations from having an unimportant item.""" display_name = "Priority Locations" + rich_text_doc = True class DeathLink(Toggle): """When you die, everyone dies. Of course the reverse is true too.""" display_name = "Death Link" + rich_text_doc = True class ItemLinks(OptionList): """Share part of your item pool with other players.""" display_name = "Item Links" + rich_text_doc = True default = [] schema = Schema([ { @@ -1051,7 +1343,8 @@ class ItemLinks(OptionList): ]) @staticmethod - def verify_items(items: typing.List[str], item_link: str, pool_name: str, world, allow_item_groups: bool = True) -> typing.Set: + def verify_items(items: typing.List[str], item_link: str, pool_name: str, world, + allow_item_groups: bool = True) -> typing.Set: pool = set() for item_name in items: if item_name not in world.item_names and (not allow_item_groups or item_name not in world.item_name_groups): @@ -1097,6 +1390,19 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P raise Exception(f"item_link {link['name']} has {intersection} " f"items in both its local_items and non_local_items pool.") link.setdefault("link_replacement", None) + link["item_pool"] = list(pool) + + +class Removed(FreeText): + """This Option has been Removed.""" + rich_text_doc = True + default = "" + visibility = Visibility.none + + def __init__(self, value: str): + if value: + raise Exception("Option removed, please update your options file.") + super().__init__(value) @dataclass @@ -1116,7 +1422,47 @@ class DeathLinkMixin: death_link: DeathLink -def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True): +class OptionGroup(typing.NamedTuple): + """Define a grouping of options.""" + name: str + """Name of the group to categorize these options in for display on the WebHost and in generated YAMLS.""" + options: typing.List[typing.Type[Option[typing.Any]]] + """Options to be in the defined group.""" + start_collapsed: bool = False + """Whether the group will start collapsed on the WebHost options pages.""" + + +item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints, + StartLocationHints, ExcludeLocations, PriorityLocations, ItemLinks] +""" +Options that are always populated in "Item & Location Options" Option Group. Cannot be moved to another group. +If desired, a custom "Item & Location Options" Option Group can be defined, but only for adding additional options to +it. +""" + + +def get_option_groups(world: typing.Type[World], visibility_level: Visibility = Visibility.template) -> typing.Dict[ + str, typing.Dict[str, typing.Type[Option[typing.Any]]]]: + """Generates and returns a dictionary for the option groups of a specified world.""" + option_groups = {option: option_group.name + for option_group in world.web.option_groups + for option in option_group.options} + # add a default option group for uncategorized options to get thrown into + ordered_groups = ["Game Options"] + [ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups] + grouped_options = {group: {} for group in ordered_groups} + for option_name, option in world.options_dataclass.type_hints.items(): + if visibility_level & option.visibility: + grouped_options[option_groups.get(option, "Game Options")][option_name] = option + + # if the world doesn't have any ungrouped options, this group will be empty so just remove it + if not grouped_options["Game Options"]: + del grouped_options["Game Options"] + + return grouped_options + + +def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True) -> None: import os import yaml @@ -1152,15 +1498,18 @@ def dictify_range(option: Range): return data, notes + def yaml_dump_scalar(scalar) -> str: + # yaml dump may add end of document marker and newlines. + return yaml.dump(scalar).replace("...\n", "").strip() + for game_name, world in AutoWorldRegister.world_types.items(): if not world.hidden or generate_hidden: - all_options: typing.Dict[str, AssembleOptions] = world.options_dataclass.type_hints - + option_groups = get_option_groups(world) with open(local_path("data", "options.yaml")) as f: file_data = f.read() res = Template(file_data).render( - options=all_options, - __version__=__version__, game=game_name, yaml_dump=yaml.dump, + option_groups=option_groups, + __version__=__version__, game=game_name, yaml_dump=yaml_dump_scalar, dictify_range=dictify_range, ) diff --git a/Patch.py b/Patch.py index 091545700059..9b49876bb72d 100644 --- a/Patch.py +++ b/Patch.py @@ -8,7 +8,7 @@ import ModuleUpdate ModuleUpdate.update() -from worlds.Files import AutoPatchRegister, APPatch +from worlds.Files import AutoPatchRegister, APAutoPatchInterface class RomMeta(TypedDict): @@ -20,7 +20,7 @@ class RomMeta(TypedDict): def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]: auto_handler = AutoPatchRegister.get_handler(patch_file) if auto_handler: - handler: APPatch = auto_handler(patch_file) + handler: APAutoPatchInterface = auto_handler(patch_file) target = os.path.splitext(patch_file)[0]+handler.result_file_ending handler.patch(target) return {"server": handler.server, diff --git a/README.md b/README.md index 3c3c41475bab..cebd4f7e7529 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # [Archipelago](https://archipelago.gg) ![Discord Shield](https://discordapp.com/api/guilds/731205301247803413/widget.png?style=shield) | [Install](https://github.com/ArchipelagoMW/Archipelago/releases) -Archipelago provides a generic framework for developing multiworld capability for game randomizers. In all cases, presently, Archipelago is also the randomizer itself. +Archipelago provides a generic framework for developing multiworld capability for game randomizers. In all cases, +presently, Archipelago is also the randomizer itself. Currently, the following games are supported: + * The Legend of Zelda: A Link to the Past * Factorio * Minecraft @@ -25,7 +27,7 @@ Currently, the following games are supported: * Hollow Knight * The Witness * Sonic Adventure 2: Battle -* Starcraft 2: Wings of Liberty +* Starcraft 2 * Donkey Kong Country 3 * Dark Souls 3 * Super Mario World @@ -61,6 +63,15 @@ Currently, the following games are supported: * TUNIC * Kirby's Dream Land 3 * Celeste 64 +* Zork Grand Inquisitor +* Castlevania 64 +* A Short Hike +* Yoshi's Island +* Mario & Luigi: Superstar Saga +* Bomb Rush Cyberfunk +* Aquaria +* Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 +* A Hat in Time 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 @@ -68,36 +79,57 @@ windows binaries. ## History -Archipelago is built upon a strong legacy of brilliant hobbyists. We want to honor that legacy by showing it here. The repositories which Archipelago is built upon, inspired by, or otherwise owes its gratitude to are: +Archipelago is built upon a strong legacy of brilliant hobbyists. We want to honor that legacy by showing it here. +The repositories which Archipelago is built upon, inspired by, or otherwise owes its gratitude to are: * [bonta0's MultiWorld](https://github.com/Bonta0/ALttPEntranceRandomizer/tree/multiworld_31) * [AmazingAmpharos' Entrance Randomizer](https://github.com/AmazingAmpharos/ALttPEntranceRandomizer) * [VT Web Randomizer](https://github.com/sporchia/alttp_vt_randomizer) * [Dessyreqt's alttprandomizer](https://github.com/Dessyreqt/alttprandomizer) -* [Zarby89's](https://github.com/Ijwu/Enemizer/commits?author=Zarby89) and [sosuke3's](https://github.com/Ijwu/Enemizer/commits?author=sosuke3) contributions to Enemizer, which make the vast majority of Enemizer contributions. +* [Zarby89's](https://github.com/Ijwu/Enemizer/commits?author=Zarby89) + and [sosuke3's](https://github.com/Ijwu/Enemizer/commits?author=sosuke3) contributions to Enemizer, which make up the + vast majority of Enemizer contributions. -We recognize that there is a strong community of incredibly smart people that have come before us and helped pave the path. Just because one person's name may be in a repository title does not mean that only one person made that project happen. We can't hope to perfectly cover every single contribution that lead up to Archipelago but we hope to honor them fairly. +We recognize that there is a strong community of incredibly smart people that have come before us and helped pave the +path. Just because one person's name may be in a repository title does not mean that only one person made that project +happen. We can't hope to perfectly cover every single contribution that lead up to Archipelago, but we hope to honor +them fairly. ### Path to the Archipelago -Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to _MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as "Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository (as opposed to a 'forked repo') and change the name (which came later) to better reflect our project. + +Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a +long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to +_MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as +"Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository +(as opposed to a 'forked repo') and change the name (which came later) to better reflect our project. ## Running Archipelago -For most people all you need to do is head over to the [releases](https://github.com/ArchipelagoMW/Archipelago/releases) page then download and run the appropriate installer. The installers function on Windows only. -If you are running Archipelago from a non-Windows system then the likely scenario is that you are comfortable running source code directly. Please see our doc on [running Archipelago from source](docs/running%20from%20source.md). +For most people, all you need to do is head over to +the [releases page](https://github.com/ArchipelagoMW/Archipelago/releases), then download and run the appropriate +installer, or AppImage for Linux-based systems. + +If you are a developer or are running on a platform with no compiled releases available, please see our doc on +[running Archipelago from source](docs/running%20from%20source.md). ## Related Repositories -This project makes use of multiple other projects. We wouldn't be here without these other repositories and the contributions of their developers, past and present. + +This project makes use of multiple other projects. We wouldn't be here without these other repositories and the +contributions of their developers, past and present. * [z3randomizer](https://github.com/ArchipelagoMW/z3randomizer) * [Enemizer](https://github.com/Ijwu/Enemizer) * [Ocarina of Time Randomizer](https://github.com/TestRunnerSRL/OoT-Randomizer) ## Contributing -For contribution guidelines, please see our [Contributing doc.](/docs/contributing.md) + +To contribute to Archipelago, including the WebHost, core program, or by adding a new game, see our +[Contributing guidelines](/docs/contributing.md). ## FAQ -For Frequently asked questions, please see the website's [FAQ Page.](https://archipelago.gg/faq/en/) + +For Frequently asked questions, please see the website's [FAQ Page](https://archipelago.gg/faq/en/). ## Code of Conduct -Please refer to our [code of conduct.](/docs/code_of_conduct.md) + +Please refer to our [code of conduct](/docs/code_of_conduct.md). diff --git a/SNIClient.py b/SNIClient.py index 062d7a7cbea1..222ed54f5cc5 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -85,6 +85,7 @@ def _cmd_snes_close(self) -> bool: """Close connection to a currently connected snes""" self.ctx.snes_reconnect_address = None self.ctx.cancel_snes_autoreconnect() + self.ctx.snes_state = SNESState.SNES_DISCONNECTED if self.ctx.snes_socket and not self.ctx.snes_socket.closed: async_start(self.ctx.snes_socket.close()) return True @@ -281,7 +282,7 @@ class SNESState(enum.IntEnum): def launch_sni() -> None: - sni_path = Utils.get_options()["sni_options"]["sni_path"] + sni_path = Utils.get_settings()["sni_options"]["sni_path"] if not os.path.isdir(sni_path): sni_path = Utils.local_path(sni_path) @@ -564,16 +565,12 @@ async def snes_write(ctx: SNIContext, write_list: typing.List[typing.Tuple[int, PutAddress_Request: SNESRequest = {"Opcode": "PutAddress", "Operands": [], 'Space': 'SNES'} try: for address, data in write_list: - while data: - # Divide the write into packets of 256 bytes. - PutAddress_Request['Operands'] = [hex(address)[2:], hex(min(len(data), 256))[2:]] - if ctx.snes_socket is not None: - await ctx.snes_socket.send(dumps(PutAddress_Request)) - await ctx.snes_socket.send(data[:256]) - address += 256 - data = data[256:] - else: - snes_logger.warning(f"Could not send data to SNES: {data}") + PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]] + if ctx.snes_socket is not None: + await ctx.snes_socket.send(dumps(PutAddress_Request)) + await ctx.snes_socket.send(data) + else: + snes_logger.warning(f"Could not send data to SNES: {data}") except ConnectionClosed: return False @@ -657,7 +654,7 @@ async def game_watcher(ctx: SNIContext) -> None: async def run_game(romfile: str) -> None: auto_start = typing.cast(typing.Union[bool, str], - Utils.get_options()["sni_options"].get("snes_rom_start", True)) + Utils.get_settings()["sni_options"].get("snes_rom_start", True)) if auto_start is True: import webbrowser webbrowser.open(romfile) diff --git a/Starcraft2Client.py b/Starcraft2Client.py index 87b50d35063e..fb219a690460 100644 --- a/Starcraft2Client.py +++ b/Starcraft2Client.py @@ -3,7 +3,7 @@ import ModuleUpdate ModuleUpdate.update() -from worlds.sc2wol.Client import launch +from worlds.sc2.Client import launch import Utils if __name__ == "__main__": diff --git a/UndertaleClient.py b/UndertaleClient.py index e1538ce81d2e..dfacee148abc 100644 --- a/UndertaleClient.py +++ b/UndertaleClient.py @@ -29,7 +29,7 @@ def _cmd_resync(self): def _cmd_patch(self): """Patch the game. Only use this command if /auto_patch fails.""" if isinstance(self.ctx, UndertaleContext): - os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True) + os.makedirs(name=Utils.user_path("Undertale"), exist_ok=True) self.ctx.patch_game() self.output("Patched.") @@ -43,7 +43,7 @@ def _cmd_savepath(self, directory: str): def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None): """Patch the game automatically.""" if isinstance(self.ctx, UndertaleContext): - os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True) + os.makedirs(name=Utils.user_path("Undertale"), exist_ok=True) tempInstall = steaminstall if not os.path.isfile(os.path.join(tempInstall, "data.win")): tempInstall = None @@ -62,7 +62,7 @@ def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None): for file_name in os.listdir(tempInstall): if file_name != "steam_api.dll": shutil.copy(os.path.join(tempInstall, file_name), - os.path.join(os.getcwd(), "Undertale", file_name)) + Utils.user_path("Undertale", file_name)) self.ctx.patch_game() self.output("Patching successful!") @@ -111,12 +111,12 @@ def __init__(self, server_address, password): self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE") def patch_game(self): - with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "rb") as f: + with open(Utils.user_path("Undertale", "data.win"), "rb") as f: patchedFile = bsdiff4.patch(f.read(), undertale.data_path("patch.bsdiff")) - with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "wb") as f: + with open(Utils.user_path("Undertale", "data.win"), "wb") as f: f.write(patchedFile) - os.makedirs(name=os.path.join(os.getcwd(), "Undertale", "Custom Sprites"), exist_ok=True) - with open(os.path.expandvars(os.path.join(os.getcwd(), "Undertale", "Custom Sprites", + os.makedirs(name=Utils.user_path("Undertale", "Custom Sprites"), exist_ok=True) + with open(os.path.expandvars(Utils.user_path("Undertale", "Custom Sprites", "Which Character.txt")), "w") as f: f.writelines(["// Put the folder name of the sprites you want to play as, make sure it is the only " "line other than this one.\n", "frisk"]) @@ -247,8 +247,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict): with open(os.path.join(ctx.save_game_folder, filename), "w") as f: toDraw = "" for i in range(20): - if i < len(str(ctx.item_names[l.item])): - toDraw += str(ctx.item_names[l.item])[i] + if i < len(str(ctx.item_names.lookup_in_game(l.item))): + toDraw += str(ctx.item_names.lookup_in_game(l.item))[i] else: break f.write(toDraw) diff --git a/Utils.py b/Utils.py index cea6405a38b4..f89330cf7c65 100644 --- a/Utils.py +++ b/Utils.py @@ -46,7 +46,7 @@ def as_simple_string(self) -> str: return ".".join(str(item) for item in self) -__version__ = "0.4.4" +__version__ = "0.5.0" version_tuple = tuplize_version(__version__) is_linux = sys.platform.startswith("linux") @@ -101,8 +101,7 @@ def cache_self1(function: typing.Callable[[S, T], RetType]) -> typing.Callable[[ @functools.wraps(function) def wrap(self: S, arg: T) -> RetType: - cache: Optional[Dict[T, RetType]] = typing.cast(Optional[Dict[T, RetType]], - getattr(self, cache_name, None)) + cache: Optional[Dict[T, RetType]] = getattr(self, cache_name, None) if cache is None: res = function(self, arg) setattr(self, cache_name, {arg: res}) @@ -201,7 +200,7 @@ def cache_path(*path: str) -> str: def output_path(*path: str) -> str: if hasattr(output_path, 'cached_path'): return os.path.join(output_path.cached_path, *path) - output_path.cached_path = user_path(get_options()["general_options"]["output_path"]) + output_path.cached_path = user_path(get_settings()["general_options"]["output_path"]) path = os.path.join(output_path.cached_path, *path) os.makedirs(os.path.dirname(path), exist_ok=True) return path @@ -209,10 +208,11 @@ def output_path(*path: str) -> str: def open_file(filename: typing.Union[str, "pathlib.Path"]) -> None: if is_windows: - os.startfile(filename) + os.startfile(filename) # type: ignore else: from shutil import which open_command = which("open") if is_macos else (which("xdg-open") or which("gnome-open") or which("kde-open")) + assert open_command, "Didn't find program for open_file! Please report this together with system details." subprocess.call([open_command, filename]) @@ -225,6 +225,9 @@ def construct_mapping(self, node, deep=False): if key in mapping: logging.error(f"YAML duplicates sanity check failed{key_node.start_mark}") raise KeyError(f"Duplicate key {key} found in YAML. Already found keys: {mapping}.") + if (str(key).startswith("+") and (str(key)[1:] in mapping)) or (f"+{key}" in mapping): + logging.error(f"YAML merge duplicates sanity check failed{key_node.start_mark}") + raise KeyError(f"Equivalent key {key} found in YAML. Already found keys: {mapping}.") mapping.add(key) return super().construct_mapping(node, deep) @@ -297,21 +300,21 @@ def get_options() -> Settings: return get_settings() -def persistent_store(category: str, key: typing.Any, value: typing.Any): +def persistent_store(category: str, key: str, value: typing.Any): path = user_path("_persistent_storage.yaml") - storage: dict = persistent_load() - category = storage.setdefault(category, {}) - category[key] = value + storage = persistent_load() + category_dict = storage.setdefault(category, {}) + category_dict[key] = value with open(path, "wt") as f: f.write(dump(storage, Dumper=Dumper)) -def persistent_load() -> typing.Dict[str, dict]: - storage = getattr(persistent_load, "storage", None) +def persistent_load() -> Dict[str, Dict[str, Any]]: + storage: Union[Dict[str, Dict[str, Any]], None] = getattr(persistent_load, "storage", None) if storage: return storage path = user_path("_persistent_storage.yaml") - storage: dict = {} + storage = {} if os.path.exists(path): try: with open(path, "r") as f: @@ -320,7 +323,7 @@ def persistent_load() -> typing.Dict[str, dict]: logging.debug(f"Could not read store: {e}") if storage is None: storage = {} - persistent_load.storage = storage + setattr(persistent_load, "storage", storage) return storage @@ -362,6 +365,7 @@ def store_data_package_for_checksum(game: str, data: typing.Dict[str, Any]) -> N except Exception as e: logging.debug(f"Could not store data package: {e}") + def get_default_adjuster_settings(game_name: str) -> Namespace: import LttPAdjuster adjuster_settings = Namespace() @@ -380,7 +384,9 @@ def get_adjuster_settings(game_name: str) -> Namespace: default_settings = get_default_adjuster_settings(game_name) # Fill in any arguments from the argparser that we haven't seen before - return Namespace(**vars(adjuster_settings), **{k:v for k,v in vars(default_settings).items() if k not in vars(adjuster_settings)}) + return Namespace(**vars(adjuster_settings), **{ + k: v for k, v in vars(default_settings).items() if k not in vars(adjuster_settings) + }) @cache_argsless @@ -404,13 +410,13 @@ def get_unique_identifier(): class RestrictedUnpickler(pickle.Unpickler): generic_properties_module: Optional[object] - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(RestrictedUnpickler, self).__init__(*args, **kwargs) self.options_module = importlib.import_module("Options") self.net_utils_module = importlib.import_module("NetUtils") self.generic_properties_module = None - def find_class(self, module, name): + def find_class(self, module: str, name: str) -> type: if module == "builtins" and name in safe_builtins: return getattr(builtins, name) # used by MultiServer -> savegame/multidata @@ -434,7 +440,7 @@ def find_class(self, module, name): raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden") -def restricted_loads(s): +def restricted_loads(s: bytes) -> Any: """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() @@ -452,6 +458,15 @@ class KeyedDefaultDict(collections.defaultdict): """defaultdict variant that uses the missing key as argument to default_factory""" default_factory: typing.Callable[[typing.Any], typing.Any] + def __init__(self, + default_factory: typing.Callable[[Any], Any] = None, + seq: typing.Union[typing.Mapping, typing.Iterable, None] = None, + **kwargs): + if seq is not None: + super().__init__(default_factory, seq, **kwargs) + else: + super().__init__(default_factory, **kwargs) + def __missing__(self, key): self[key] = value = self.default_factory(key) return value @@ -490,7 +505,7 @@ def init_logging(name: str, loglevel: typing.Union[str, int] = logging.INFO, wri file_handler.setFormatter(logging.Formatter(log_format)) class Filter(logging.Filter): - def __init__(self, filter_name, condition): + def __init__(self, filter_name: str, condition: typing.Callable[[logging.LogRecord], bool]) -> None: super().__init__(filter_name) self.condition = condition @@ -538,10 +553,11 @@ def _cleanup(): f"Archipelago ({__version__}) logging initialized" f" on {platform.platform()}" f" running Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + f"{' (frozen)' if is_frozen() else ''}" ) -def stream_input(stream, queue): +def stream_input(stream: typing.TextIO, queue: "asyncio.Queue[str]"): def queuer(): while 1: try: @@ -569,7 +585,7 @@ class VersionException(Exception): pass -def chaining_prefix(index: int, labels: typing.Tuple[str]) -> str: +def chaining_prefix(index: int, labels: typing.Sequence[str]) -> str: text = "" max_label = len(labels) - 1 while index > max_label: @@ -592,7 +608,7 @@ def format_SI_prefix(value, power=1000, power_labels=("", "k", "M", "G", "T", "P return f"{value.quantize(decimal.Decimal('1.00'))} {chaining_prefix(n, power_labels)}" -def get_fuzzy_results(input_word: str, wordlist: typing.Sequence[str], limit: typing.Optional[int] = None) \ +def get_fuzzy_results(input_word: str, word_list: typing.Collection[str], limit: typing.Optional[int] = None) \ -> typing.List[typing.Tuple[str, int]]: import jellyfish @@ -600,22 +616,58 @@ def get_fuzzy_ratio(word1: str, word2: str) -> float: return (1 - jellyfish.damerau_levenshtein_distance(word1.lower(), word2.lower()) / max(len(word1), len(word2))) - limit: int = limit if limit else len(wordlist) + limit = limit if limit else len(word_list) return list( map( lambda container: (container[0], int(container[1]*100)), # convert up to limit to int % sorted( - map(lambda candidate: - (candidate, get_fuzzy_ratio(input_word, candidate)), - wordlist), + map(lambda candidate: (candidate, get_fuzzy_ratio(input_word, candidate)), word_list), key=lambda element: element[1], - reverse=True)[0:limit] + reverse=True + )[0:limit] ) ) -def open_filename(title: str, filetypes: typing.Sequence[typing.Tuple[str, typing.Sequence[str]]], suggest: str = "") \ +def get_intended_text(input_text: str, possible_answers) -> typing.Tuple[str, bool, str]: + picks = get_fuzzy_results(input_text, possible_answers, limit=2) + if len(picks) > 1: + dif = picks[0][1] - picks[1][1] + if picks[0][1] == 100: + return picks[0][0], True, "Perfect Match" + elif picks[0][1] < 75: + return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \ + f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" + elif dif > 5: + return picks[0][0], True, "Close Match" + else: + return picks[0][0], False, f"Too many close matches for '{input_text}', " \ + f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" + else: + if picks[0][1] > 90: + return picks[0][0], True, "Only Option Match" + else: + return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \ + f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" + + +def get_input_text_from_response(text: str, command: str) -> typing.Optional[str]: + if "did you mean " in text: + for question in ("Didn't find something that closely matches", + "Too many close matches"): + if text.startswith(question): + name = get_text_between(text, "did you mean '", + "'? (") + return f"!{command} {name}" + elif text.startswith("Missing: "): + return text.replace("Missing: ", "!hint_location ") + return None + + +def open_filename(title: str, filetypes: typing.Iterable[typing.Tuple[str, typing.Iterable[str]]], suggest: str = "") \ -> typing.Optional[str]: + logging.info(f"Opening file input dialog for {title}.") + def run(*args: str): return subprocess.run(args, capture_output=True, text=True).stdout.split("\n", 1)[0] or None @@ -713,7 +765,7 @@ def is_kivy_running(): import ctypes style = 0x10 if error else 0x0 return ctypes.windll.user32.MessageBoxW(0, text, title, style) - + # fall back to tk try: import tkinter @@ -729,7 +781,7 @@ def is_kivy_running(): root.update() -def title_sorted(data: typing.Sequence, key=None, ignore: typing.Set = frozenset(("a", "the"))): +def title_sorted(data: typing.Iterable, key=None, ignore: typing.AbstractSet[str] = frozenset(("a", "the"))): """Sorts a sequence of text ignoring typical articles like "a" or "the" in the beginning.""" def sorter(element: Union[str, Dict[str, Any]]) -> str: if (not isinstance(element, str)): @@ -783,7 +835,7 @@ class DeprecateDict(dict): log_message: str should_error: bool - def __init__(self, message, error: bool = False) -> None: + def __init__(self, message: str, error: bool = False) -> None: self.log_message = message self.should_error = error super().__init__() @@ -969,11 +1021,8 @@ def __len__(self): return sum(len(iterable) for iterable in self.iterable) -def is_iterable_of_str(obj: object) -> TypeGuard[typing.Iterable[str]]: - """ but not a `str` (because technically, `str` is `Iterable[str]`) """ +def is_iterable_except_str(obj: object) -> TypeGuard[typing.Iterable[typing.Any]]: + """ `str` is `Iterable`, but that's not what we want """ if isinstance(obj, str): return False - if not isinstance(obj, typing.Iterable): - return False - obj_it: typing.Iterable[object] = obj - return all(isinstance(v, str) for v in obj_it) + return isinstance(obj, typing.Iterable) diff --git a/Wargroove2Client.py b/Wargroove2Client.py new file mode 100644 index 000000000000..6f827df15b0f --- /dev/null +++ b/Wargroove2Client.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import ModuleUpdate +ModuleUpdate.update() + +from worlds.wargroove2.client import launch +import Utils + +if __name__ == "__main__": + Utils.init_logging("Wargroove2Client", exception_logger="Client") + launch() diff --git a/WargrooveClient.py b/WargrooveClient.py index 77180502cefc..39da044d659c 100644 --- a/WargrooveClient.py +++ b/WargrooveClient.py @@ -176,7 +176,7 @@ def on_package(self, cmd: str, args: dict): if not os.path.isfile(path): open(path, 'w').close() # Announcing commander unlocks - item_name = self.item_names[network_item.item] + item_name = self.item_names.lookup_in_game(network_item.item) if item_name in faction_table.keys(): for commander in faction_table[item_name]: logger.info(f"{commander.name} has been unlocked!") @@ -197,7 +197,7 @@ def on_package(self, cmd: str, args: dict): open(print_path, 'w').close() with open(print_path, 'w') as f: f.write("Received " + - self.item_names[network_item.item] + + self.item_names.lookup_in_game(network_item.item) + " from " + self.player_names[network_item.player]) f.close() @@ -342,7 +342,7 @@ def update_commander_data(self): faction_items = 0 faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()] for network_item in self.items_received: - if self.item_names[network_item.item] in faction_item_names: + if self.item_names.lookup_in_game(network_item.item) in faction_item_names: faction_items += 1 starting_groove = (faction_items - 1) * self.starting_groove_multiplier # Must be an integer larger than 0 diff --git a/WebHost.py b/WebHost.py index 8595fa7a27a4..08ef3c430795 100644 --- a/WebHost.py +++ b/WebHost.py @@ -12,6 +12,9 @@ import Utils import settings +if typing.TYPE_CHECKING: + from flask import Flask + Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8 settings.no_gui = True configpath = os.path.abspath("config.yaml") @@ -19,11 +22,10 @@ configpath = os.path.abspath(Utils.user_path('config.yaml')) -def get_app(): +def get_app() -> "Flask": from WebHostLib import register, cache, app as raw_app from WebHostLib.models import db - register() app = raw_app if os.path.exists(configpath) and not app.config["TESTING"]: import yaml @@ -34,6 +36,7 @@ def get_app(): app.config["HOST_ADDRESS"] = Utils.get_public_ipv4() logging.info(f"HOST_ADDRESS was set to {app.config['HOST_ADDRESS']}") + register() cache.init_app(app) db.bind(**app.config["PONY"]) db.generate_mapping(create_tables=True) @@ -55,6 +58,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]] worlds[game] = world base_target_path = Utils.local_path("WebHostLib", "static", "generated", "docs") + shutil.rmtree(base_target_path, ignore_errors=True) for game, world in worlds.items(): # copy files from world's docs folder to the generated folder target_path = os.path.join(base_target_path, game) @@ -117,7 +121,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]] logging.basicConfig(format='[%(asctime)s] %(message)s', level=logging.INFO) from WebHostLib.lttpsprites import update_sprites_lttp - from WebHostLib.autolauncher import autohost, autogen + from WebHostLib.autolauncher import autohost, autogen, stop from WebHostLib.options import create as create_options_files try: @@ -138,3 +142,11 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]] else: from waitress import serve serve(app, port=app.config["PORT"], threads=app.config["WAITRESS_THREADS"]) + else: + from time import sleep + try: + while True: + sleep(1) # wait for process to be killed + except (SystemExit, KeyboardInterrupt): + pass + stop() # stop worker threads diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index 43ca89f0b3f3..fdf3037fe015 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -23,6 +23,7 @@ app.config["SELFHOST"] = True # application process is in charge of running the websites app.config["GENERATORS"] = 8 # maximum concurrent world gens +app.config["HOSTERS"] = 8 # maximum concurrent room hosters app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms. app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections @@ -51,6 +52,7 @@ app.config["MAX_ROLL"] = 20 app.config["CACHE_TYPE"] = "SimpleCache" app.config["HOST_ADDRESS"] = "" +app.config["ASSET_RIGHTS"] = False cache = Cache() Compress(app) @@ -82,6 +84,6 @@ def register(): from WebHostLib.customserver import run_server_process # to trigger app routing picking up on it - from . import tracker, upload, landing, check, generate, downloads, api, stats, misc + from . import tracker, upload, landing, check, generate, downloads, api, stats, misc, robots, options app.register_blueprint(api.api_endpoints) diff --git a/WebHostLib/api/__init__.py b/WebHostLib/api/__init__.py index 102c3a49f6aa..4003243a281d 100644 --- a/WebHostLib/api/__init__.py +++ b/WebHostLib/api/__init__.py @@ -2,9 +2,9 @@ from typing import List, Tuple from uuid import UUID -from flask import Blueprint, abort +from flask import Blueprint, abort, url_for -from .. import cache +import worlds.Files from ..models import Room, Seed api_endpoints = Blueprint('api', __name__, url_prefix="/api") @@ -21,39 +21,31 @@ def room_info(room: UUID): room = Room.get(id=room) if room is None: return abort(404) + + def supports_apdeltapatch(game: str): + return game in worlds.Files.AutoPatchRegister.patch_types + downloads = [] + for slot in sorted(room.seed.slots): + if slot.data and not supports_apdeltapatch(slot.game): + slot_download = { + "slot": slot.player_id, + "download": url_for("download_slot_file", room_id=room.id, player_id=slot.player_id) + } + downloads.append(slot_download) + elif slot.data: + slot_download = { + "slot": slot.player_id, + "download": url_for("download_patch", patch_id=slot.id, room_id=room.id) + } + downloads.append(slot_download) return { "tracker": room.tracker, "players": get_players(room.seed), "last_port": room.last_port, "last_activity": room.last_activity, - "timeout": room.timeout + "timeout": room.timeout, + "downloads": downloads, } -@api_endpoints.route('/datapackage') -@cache.cached() -def get_datapackage(): - from worlds import network_data_package - return network_data_package - - -@api_endpoints.route('/datapackage_version') -@cache.cached() -def get_datapackage_versions(): - from worlds import AutoWorldRegister - - version_package = {game: world.data_version for game, world in AutoWorldRegister.world_types.items()} - return version_package - - -@api_endpoints.route('/datapackage_checksum') -@cache.cached() -def get_datapackage_checksums(): - from worlds import network_data_package - version_package = { - game: game_data["checksum"] for game, game_data in network_data_package["games"].items() - } - return version_package - - -from . import generate, user # trigger registration +from . import generate, user, datapackage # trigger registration diff --git a/WebHostLib/api/datapackage.py b/WebHostLib/api/datapackage.py new file mode 100644 index 000000000000..3fb472d95dfd --- /dev/null +++ b/WebHostLib/api/datapackage.py @@ -0,0 +1,32 @@ +from flask import abort + +from Utils import restricted_loads +from WebHostLib import cache +from WebHostLib.models import GameDataPackage +from . import api_endpoints + + +@api_endpoints.route('/datapackage') +@cache.cached() +def get_datapackage(): + from worlds import network_data_package + return network_data_package + + +@api_endpoints.route('/datapackage/') +@cache.memoize(timeout=3600) +def get_datapackage_by_checksum(checksum: str): + package = GameDataPackage.get(checksum=checksum) + if package: + return restricted_loads(package.data) + return abort(404) + + +@api_endpoints.route('/datapackage_checksum') +@cache.cached() +def get_datapackage_checksums(): + from worlds import network_data_package + version_package = { + game: game_data["checksum"] for game, game_data in network_data_package["games"].items() + } + return version_package diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index 90838671200c..08a1309ebc73 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -3,25 +3,25 @@ import json import logging import multiprocessing -import threading -import time import typing from datetime import timedelta, datetime +from threading import Event, Thread +from uuid import UUID from pony.orm import db_session, select, commit from Utils import restricted_loads from .locker import Locker, AlreadyRunningException +_stop_event = Event() -def launch_room(room: Room, config: dict): - # requires db_session! - if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout): - multiworld = multiworlds.get(room.id, None) - if not multiworld: - multiworld = MultiworldInstance(room, config) - multiworld.start() +def stop(): + """Stops previously launched threads""" + global _stop_event + stop_event = _stop_event + _stop_event = Event() # new event for new threads + stop_event.set() def handle_generation_success(seed_id): @@ -58,29 +58,50 @@ def init_db(pony_config: dict): db.generate_mapping() +def cleanup(): + """delete unowned user-content""" + with db_session: + # >>> bool(uuid.UUID(int=0)) + # True + rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True) + seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True) + slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True) + # Command gets deleted by ponyorm Cascade Delete, as Room is Required + if rooms or seeds or slots: + logging.info(f"{rooms} Rooms, {seeds} Seeds and {slots} Slots have been deleted.") + + def autohost(config: dict): def keep_running(): + stop_event = _stop_event try: with Locker("autohost"): - run_guardian() - while 1: - time.sleep(0.1) + cleanup() + hosters = [] + for x in range(config["HOSTERS"]): + hoster = MultiworldInstance(config, x) + hosters.append(hoster) + hoster.start() + + while not stop_event.wait(0.1): with db_session: rooms = select( room for room in Room if room.last_activity >= datetime.utcnow() - timedelta(days=3)) for room in rooms: - launch_room(room, config) + # we have to filter twice, as the per-room timeout can't currently be PonyORM transpiled. + if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout + 5): + hosters[room.id.int % len(hosters)].start_room(room.id) except AlreadyRunningException: logging.info("Autohost reports as already running, not starting another.") - import threading - threading.Thread(target=keep_running, name="AP_Autohost").start() + Thread(target=keep_running, name="AP_Autohost").start() def autogen(config: dict): def keep_running(): + stop_event = _stop_event try: with Locker("autogen"): @@ -101,8 +122,7 @@ def keep_running(): commit() select(generation for generation in Generation if generation.state == STATE_ERROR).delete() - while 1: - time.sleep(0.1) + while not stop_event.wait(0.1): with db_session: # for update locks the database row(s) during transaction, preventing writes from elsewhere to_start = select( @@ -113,37 +133,45 @@ def keep_running(): except AlreadyRunningException: logging.info("Autogen reports as already running, not starting another.") - import threading - threading.Thread(target=keep_running, name="AP_Autogen").start() + Thread(target=keep_running, name="AP_Autogen").start() multiworlds: typing.Dict[type(Room.id), MultiworldInstance] = {} class MultiworldInstance(): - def __init__(self, room: Room, config: dict): - self.room_id = room.id + def __init__(self, config: dict, id: int): + self.room_ids = set() self.process: typing.Optional[multiprocessing.Process] = None - with guardian_lock: - multiworlds[self.room_id] = self self.ponyconfig = config["PONY"] self.cert = config["SELFLAUNCHCERT"] self.key = config["SELFLAUNCHKEY"] self.host = config["HOST_ADDRESS"] + self.rooms_to_start = multiprocessing.Queue() + self.rooms_shutting_down = multiprocessing.Queue() + self.name = f"MultiHoster{id}" def start(self): if self.process and self.process.is_alive(): return False - logging.info(f"Spinning up {self.room_id}") process = multiprocessing.Process(group=None, target=run_server_process, - args=(self.room_id, self.ponyconfig, get_static_server_data(), - self.cert, self.key, self.host), - name="MultiHost") + args=(self.name, self.ponyconfig, get_static_server_data(), + self.cert, self.key, self.host, + self.rooms_to_start, self.rooms_shutting_down), + name=self.name) process.start() - # bind after start to prevent thread sync issues with guardian. self.process = process + def start_room(self, room_id): + while not self.rooms_shutting_down.empty(): + self.room_ids.remove(self.rooms_shutting_down.get(block=True, timeout=None)) + if room_id in self.room_ids: + pass # should already be hosted currently. + else: + self.room_ids.add(room_id) + self.rooms_to_start.put(room_id) + def stop(self): if self.process: self.process.terminate() @@ -157,40 +185,6 @@ def collect(self): self.process = None -guardian = None -guardian_lock = threading.Lock() - - -def run_guardian(): - global guardian - global multiworlds - with guardian_lock: - if not guardian: - try: - import resource - except ModuleNotFoundError: - pass # unix only module - else: - # Each Server is another file handle, so request as many as we can from the system - file_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1] - # set soft limit to hard limit - resource.setrlimit(resource.RLIMIT_NOFILE, (file_limit, file_limit)) - - def guard(): - while 1: - time.sleep(1) - done = [] - with guardian_lock: - for key, instance in multiworlds.items(): - if instance.done(): - instance.collect() - done.append(key) - for key in done: - del (multiworlds[key]) - - guardian = threading.Thread(name="Guardian", target=guard) - - -from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed +from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed, Slot from .customserver import run_server_process, get_static_server_data from .generate import gen_game diff --git a/WebHostLib/check.py b/WebHostLib/check.py index e739dda02d79..97cb797f7a56 100644 --- a/WebHostLib/check.py +++ b/WebHostLib/check.py @@ -28,7 +28,7 @@ def check(): results, _ = roll_options(options) if len(options) > 1: # offer combined file back - combined_yaml = "---\n".join(f"# original filename: {file_name}\n{file_content.decode('utf-8-sig')}" + combined_yaml = "\n---\n".join(f"# original filename: {file_name}\n{file_content.decode('utf-8-sig')}" for file_name, file_content in options.items()) combined_yaml = base64.b64encode(combined_yaml.encode("utf-8-sig")).decode() else: @@ -108,7 +108,10 @@ def roll_options(options: Dict[str, Union[dict, str]], rolled_results[f"{filename}/{i + 1}"] = roll_settings(yaml_data, plando_options=plando_options) except Exception as e: - results[filename] = f"Failed to generate options in {filename}: {e}" + if e.__cause__: + results[filename] = f"Failed to generate options in {filename}: {e} - {e.__cause__}" + else: + results[filename] = f"Failed to generate options in {filename}: {e}" else: results[filename] = True return results, rolled_results diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index fb3b314753cf..ccffc40b384d 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -5,6 +5,7 @@ import datetime import functools import logging +import multiprocessing import pickle import random import socket @@ -53,17 +54,19 @@ def _cmd_video(self, platform: str, user: str): class DBCommandProcessor(ServerCommandProcessor): def output(self, text: str): - logging.info(text) + self.ctx.logger.info(text) class WebHostContext(Context): room_id: int - def __init__(self, static_server_data: dict): + def __init__(self, static_server_data: dict, logger: logging.Logger): # static server data is used during _load_game_data to load required data, # without needing to import worlds system, which takes quite a bit of memory self.static_server_data = static_server_data - super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", "enabled", 0, 2) + super(WebHostContext, self).__init__("", 0, "", "", 1, + 40, True, "enabled", "enabled", + "enabled", 0, 2, logger=logger) del self.static_server_data self.main_loop = asyncio.get_running_loop() self.video = {} @@ -71,6 +74,7 @@ def __init__(self, static_server_data: dict): def _load_game_data(self): for key, value in self.static_server_data.items(): + # NOTE: attributes are mutable and shared, so they will have to be copied before being modified setattr(self, key, value) self.non_hintable_names = collections.defaultdict(frozenset, self.non_hintable_names) @@ -98,18 +102,37 @@ def load(self, room_id: int): multidata = self.decompress(room.seed.multidata) game_data_packages = {} + + static_gamespackage = self.gamespackage # this is shared across all rooms + static_item_name_groups = self.item_name_groups + static_location_name_groups = self.location_name_groups + self.gamespackage = {"Archipelago": static_gamespackage.get("Archipelago", {})} # this may be modified by _load + self.item_name_groups = {"Archipelago": static_item_name_groups.get("Archipelago", {})} + self.location_name_groups = {"Archipelago": static_location_name_groups.get("Archipelago", {})} + for game in list(multidata.get("datapackage", {})): game_data = multidata["datapackage"][game] if "checksum" in game_data: - if self.gamespackage.get(game, {}).get("checksum") == game_data["checksum"]: - # non-custom. remove from multidata + if static_gamespackage.get(game, {}).get("checksum") == game_data["checksum"]: + # non-custom. remove from multidata and use static data # games package could be dropped from static data once all rooms embed data package del multidata["datapackage"][game] else: row = GameDataPackage.get(checksum=game_data["checksum"]) if row: # None if rolled on >= 0.3.9 but uploaded to <= 0.3.8. multidata should be complete game_data_packages[game] = Utils.restricted_loads(row.data) - + continue + else: + self.logger.warning(f"Did not find game_data_package for {game}: {game_data['checksum']}") + self.gamespackage[game] = static_gamespackage.get(game, {}) + self.item_name_groups[game] = static_item_name_groups.get(game, {}) + self.location_name_groups[game] = static_location_name_groups.get(game, {}) + + if not game_data_packages: + # all static -> use the static dicts directly + self.gamespackage = static_gamespackage + self.item_name_groups = static_item_name_groups + self.location_name_groups = static_location_name_groups return self._load(multidata, game_data_packages, True) @db_session @@ -119,7 +142,7 @@ def init_save(self, enabled: bool = True): savegame_data = Room.get(id=self.room_id).multisave if savegame_data: self.set_save(restricted_loads(Room.get(id=self.room_id).multisave)) - self._start_async_saving() + self._start_async_saving(atexit_save=False) threading.Thread(target=self.listen_to_db_commands, daemon=True).start() @db_session @@ -145,86 +168,178 @@ def get_random_port(): def get_static_server_data() -> dict: import worlds data = { - "non_hintable_names": {}, - "gamespackage": worlds.network_data_package["games"], - "item_name_groups": {world_name: world.item_name_groups for world_name, world in - worlds.AutoWorldRegister.world_types.items()}, - "location_name_groups": {world_name: world.location_name_groups for world_name, world in - worlds.AutoWorldRegister.world_types.items()}, + "non_hintable_names": { + world_name: world.hint_blacklist + for world_name, world in worlds.AutoWorldRegister.world_types.items() + }, + "gamespackage": { + world_name: { + key: value + for key, value in game_package.items() + if key not in ("item_name_groups", "location_name_groups") + } + for world_name, game_package in worlds.network_data_package["games"].items() + }, + "item_name_groups": { + world_name: world.item_name_groups + for world_name, world in worlds.AutoWorldRegister.world_types.items() + }, + "location_name_groups": { + world_name: world.location_name_groups + for world_name, world in worlds.AutoWorldRegister.world_types.items() + }, } - for world_name, world in worlds.AutoWorldRegister.world_types.items(): - data["non_hintable_names"][world_name] = world.hint_blacklist - return data -def run_server_process(room_id, ponyconfig: dict, static_server_data: dict, +def set_up_logging(room_id) -> logging.Logger: + import os + # logger setup + logger = logging.getLogger(f"RoomLogger {room_id}") + + # this *should* be empty, but just in case. + for handler in logger.handlers[:]: + logger.removeHandler(handler) + handler.close() + + file_handler = logging.FileHandler( + os.path.join(Utils.user_path("logs"), f"{room_id}.txt"), + "a", + encoding="utf-8-sig") + file_handler.setFormatter(logging.Formatter("[%(asctime)s]: %(message)s")) + logger.setLevel(logging.INFO) + logger.addHandler(file_handler) + return logger + + +def run_server_process(name: str, ponyconfig: dict, static_server_data: dict, cert_file: typing.Optional[str], cert_key_file: typing.Optional[str], - host: str): + host: str, rooms_to_run: multiprocessing.Queue, rooms_shutting_down: multiprocessing.Queue): + Utils.init_logging(name) + try: + import resource + except ModuleNotFoundError: + pass # unix only module + else: + # Each Server is another file handle, so request as many as we can from the system + file_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + # set soft limit to hard limit + resource.setrlimit(resource.RLIMIT_NOFILE, (file_limit, file_limit)) + del resource, file_limit + # establish DB connection for multidata and multisave db.bind(**ponyconfig) db.generate_mapping(check_tables=False) - async def main(): - if "worlds" in sys.modules: - raise Exception("Worlds system should not be loaded in the custom server.") - - import gc - Utils.init_logging(str(room_id), write_mode="a") - ctx = WebHostContext(static_server_data) - ctx.load(room_id) - ctx.init_save() - ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None - gc.collect() # free intermediate objects used during setup - try: - ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context) - - await ctx.server - except OSError: # likely port in use - ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context) - - await ctx.server - port = 0 - for wssocket in ctx.server.ws_server.sockets: - socketname = wssocket.getsockname() - if wssocket.family == socket.AF_INET6: - # Prefer IPv4, as most users seem to not have working ipv6 support - if not port: - port = socketname[1] - elif wssocket.family == socket.AF_INET: - port = socketname[1] - if port: - logging.info(f'Hosting game at {host}:{port}') - with db_session: - room = Room.get(id=ctx.room_id) - room.last_port = port - else: - logging.exception("Could not determine port. Likely hosting failure.") - with db_session: - ctx.auto_shutdown = Room.get(id=room_id).timeout - ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [])) - await ctx.shutdown_task - - # ensure auto launch is on the same page in regard to room activity. - with db_session: - room: Room = Room.get(id=ctx.room_id) - room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(seconds=room.timeout + 60) - - logging.info("Shutting down") - - with Locker(room_id): - try: - asyncio.run(main()) - except (KeyboardInterrupt, SystemExit): - with db_session: - room = Room.get(id=room_id) - # ensure the Room does not spin up again on its own, minute of safety buffer - room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(minutes=1, seconds=room.timeout) - except Exception: - with db_session: - room = Room.get(id=room_id) - room.last_port = -1 - # ensure the Room does not spin up again on its own, minute of safety buffer - room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(minutes=1, seconds=room.timeout) - raise + if "worlds" in sys.modules: + raise Exception("Worlds system should not be loaded in the custom server.") + + import gc + ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None + del cert_file, cert_key_file, ponyconfig + gc.collect() # free intermediate objects used during setup + + loop = asyncio.get_event_loop() + + async def start_room(room_id): + with Locker(f"RoomLocker {room_id}"): + try: + logger = set_up_logging(room_id) + ctx = WebHostContext(static_server_data, logger) + ctx.load(room_id) + ctx.init_save() + try: + ctx.server = websockets.serve( + functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context) + + await ctx.server + except OSError: # likely port in use + ctx.server = websockets.serve( + functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context) + + await ctx.server + port = 0 + for wssocket in ctx.server.ws_server.sockets: + socketname = wssocket.getsockname() + if wssocket.family == socket.AF_INET6: + # Prefer IPv4, as most users seem to not have working ipv6 support + if not port: + port = socketname[1] + elif wssocket.family == socket.AF_INET: + port = socketname[1] + if port: + ctx.logger.info(f'Hosting game at {host}:{port}') + with db_session: + room = Room.get(id=ctx.room_id) + room.last_port = port + else: + ctx.logger.exception("Could not determine port. Likely hosting failure.") + with db_session: + ctx.auto_shutdown = Room.get(id=room_id).timeout + if ctx.saving: + setattr(asyncio.current_task(), "save", lambda: ctx._save(True)) + ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [])) + await ctx.shutdown_task + + except (KeyboardInterrupt, SystemExit): + if ctx.saving: + ctx._save() + setattr(asyncio.current_task(), "save", None) + except Exception as e: + with db_session: + room = Room.get(id=room_id) + room.last_port = -1 + logger.exception(e) + raise + else: + if ctx.saving: + ctx._save() + setattr(asyncio.current_task(), "save", None) + finally: + try: + ctx.save_dirty = False # make sure the saving thread does not write to DB after final wakeup + ctx.exit_event.set() # make sure the saving thread stops at some point + # NOTE: async saving should probably be an async task and could be merged with shutdown_task + with (db_session): + # ensure the Room does not spin up again on its own, minute of safety buffer + room = Room.get(id=room_id) + room.last_activity = datetime.datetime.utcnow() - \ + datetime.timedelta(minutes=1, seconds=room.timeout) + logging.info(f"Shutting down room {room_id} on {name}.") + finally: + await asyncio.sleep(5) + rooms_shutting_down.put(room_id) + + class Starter(threading.Thread): + _tasks: typing.List[asyncio.Future] + + def __init__(self): + super().__init__() + self._tasks = [] + + def _done(self, task: asyncio.Future): + self._tasks.remove(task) + task.result() + + def run(self): + while 1: + next_room = rooms_to_run.get(block=True, timeout=None) + gc.collect(0) + task = asyncio.run_coroutine_threadsafe(start_room(next_room), loop) + self._tasks.append(task) + task.add_done_callback(self._done) + logging.info(f"Starting room {next_room} on {name}.") + del task # delete reference to task object + + starter = Starter() + starter.daemon = True + starter.start() + try: + loop.run_forever() + finally: + # save all tasks that want to be saved during shutdown + for task in asyncio.all_tasks(loop): + save: typing.Optional[typing.Callable[[], typing.Any]] = getattr(task, "save", None) + if save: + save() diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index ee1ce591ee84..a12dc0f4ae14 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -6,7 +6,7 @@ import tempfile import zipfile from collections import Counter -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, Set from flask import flash, redirect, render_template, request, session, url_for from pony.orm import commit, db_session @@ -16,6 +16,7 @@ from Main import main as ERmain from Utils import __version__ from WebHostLib import app +from settings import ServerOptions, GeneratorOptions from worlds.alttp.EntranceRandomizer import parse_arguments from .check import get_yaml_data, roll_options from .models import Generation, STATE_ERROR, STATE_QUEUED, Seed, UUID @@ -23,25 +24,22 @@ def get_meta(options_source: dict, race: bool = False) -> Dict[str, Union[List[str], Dict[str, Any]]]: - plando_options = { - options_source.get("plando_bosses", ""), - options_source.get("plando_items", ""), - options_source.get("plando_connections", ""), - options_source.get("plando_texts", "") - } - plando_options -= {""} + plando_options: Set[str] = set() + for substr in ("bosses", "items", "connections", "texts"): + if options_source.get(f"plando_{substr}", substr in GeneratorOptions.plando_options): + plando_options.add(substr) server_options = { - "hint_cost": int(options_source.get("hint_cost", 10)), - "release_mode": options_source.get("release_mode", "goal"), - "remaining_mode": options_source.get("remaining_mode", "disabled"), - "collect_mode": options_source.get("collect_mode", "disabled"), - "item_cheat": bool(int(options_source.get("item_cheat", 1))), + "hint_cost": int(options_source.get("hint_cost", ServerOptions.hint_cost)), + "release_mode": options_source.get("release_mode", ServerOptions.release_mode), + "remaining_mode": options_source.get("remaining_mode", ServerOptions.remaining_mode), + "collect_mode": options_source.get("collect_mode", ServerOptions.collect_mode), + "item_cheat": bool(int(options_source.get("item_cheat", not ServerOptions.disable_item_cheat))), "server_password": options_source.get("server_password", None), } generator_options = { - "spoiler": int(options_source.get("spoiler", 0)), - "race": race + "spoiler": int(options_source.get("spoiler", GeneratorOptions.spoiler)), + "race": race, } if race: @@ -70,35 +68,39 @@ def generate(race=False): flash(options) else: meta = get_meta(request.form, race) - results, gen_options = roll_options(options, set(meta["plando_options"])) - - if any(type(result) == str for result in results.values()): - return render_template("checkResult.html", results=results) - elif len(gen_options) > app.config["MAX_ROLL"]: - flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. " - f"If you have a larger group, please generate it yourself and upload it.") - elif len(gen_options) >= app.config["JOB_THRESHOLD"]: - gen = Generation( - options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}), - # convert to json compatible - meta=json.dumps(meta), - state=STATE_QUEUED, - owner=session["_id"]) - commit() + return start_generation(options, meta) + + return render_template("generate.html", race=race, version=__version__) - return redirect(url_for("wait_seed", seed=gen.id)) - else: - try: - seed_id = gen_game({name: vars(options) for name, options in gen_options.items()}, - meta=meta, owner=session["_id"].int) - except BaseException as e: - from .autolauncher import handle_generation_failure - handle_generation_failure(e) - return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": " + str(e))) - return redirect(url_for("view_seed", seed=seed_id)) +def start_generation(options: Dict[str, Union[dict, str]], meta: Dict[str, Any]): + results, gen_options = roll_options(options, set(meta["plando_options"])) + + if any(type(result) == str for result in results.values()): + return render_template("checkResult.html", results=results) + elif len(gen_options) > app.config["MAX_ROLL"]: + flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. " + f"If you have a larger group, please generate it yourself and upload it.") + elif len(gen_options) >= app.config["JOB_THRESHOLD"]: + gen = Generation( + options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}), + # convert to json compatible + meta=json.dumps(meta), + state=STATE_QUEUED, + owner=session["_id"]) + commit() + + return redirect(url_for("wait_seed", seed=gen.id)) + else: + try: + seed_id = gen_game({name: vars(options) for name, options in gen_options.items()}, + meta=meta, owner=session["_id"].int) + except BaseException as e: + from .autolauncher import handle_generation_failure + handle_generation_failure(e) + return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": " + str(e))) - return render_template("generate.html", race=race, version=__version__) + return redirect(url_for("view_seed", seed=seed_id)) def gen_game(gen_options: dict, meta: Optional[Dict[str, Any]] = None, owner=None, sid=None): diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index ee04e56fd768..01c1ad84a707 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -1,6 +1,6 @@ import datetime import os -from typing import List, Dict, Union +from typing import Any, IO, Dict, Iterator, List, Tuple, Union import jinja2.exceptions from flask import request, redirect, url_for, render_template, Response, session, abort, send_from_directory @@ -37,31 +37,6 @@ def start_playing(): return render_template(f"startPlaying.html") -# TODO for back compat. remove around 0.4.5 -@app.route("/weighted-settings") -def weighted_settings(): - return redirect("weighted-options", 301) - - -@app.route("/weighted-options") -@cache.cached() -def weighted_options(): - return render_template("weighted-options.html") - - -# TODO for back compat. remove around 0.4.5 -@app.route("/games//player-settings") -def player_settings(game: str): - return redirect(url_for("player_options", game=game), 301) - - -# Player options pages -@app.route("/games//player-options") -@cache.cached() -def player_options(game: str): - return render_template("player-options.html", game=game, theme=get_world_theme(game)) - - # Game Info Pages @app.route('/games//info/') @cache.cached() @@ -122,25 +97,37 @@ def new_room(seed: UUID): return redirect(url_for("host_room", room=room.id)) -def _read_log(path: str): - if os.path.exists(path): - with open(path, encoding="utf-8-sig") as log: - yield from log - else: - yield f"Logfile {path} does not exist. " \ - f"Likely a crash during spinup of multiworld instance or it is still spinning up." +def _read_log(log: IO[Any], offset: int = 0) -> Iterator[bytes]: + marker = log.read(3) # skip optional BOM + if marker != b'\xEF\xBB\xBF': + log.seek(0, os.SEEK_SET) + log.seek(offset, os.SEEK_CUR) + yield from log + log.close() # free file handle as soon as possible @app.route('/log/') -def display_log(room: UUID): +def display_log(room: UUID) -> Union[str, Response, Tuple[str, int]]: room = Room.get(id=room) if room is None: return abort(404) if room.owner == session["_id"]: file_path = os.path.join("logs", str(room.id) + ".txt") - if os.path.exists(file_path): - return Response(_read_log(file_path), mimetype="text/plain;charset=UTF-8") - return "Log File does not exist." + try: + log = open(file_path, "rb") + range_header = request.headers.get("Range") + if range_header: + range_type, range_values = range_header.split('=') + start, end = map(str.strip, range_values.split('-', 1)) + if range_type != "bytes" or end != "": + return "Unsupported range", 500 + # NOTE: we skip Content-Range in the response here, which isn't great but works for our JS + return Response(_read_log(log, int(start)), mimetype="text/plain", status=206) + return Response(_read_log(log), mimetype="text/plain") + except FileNotFoundError: + return Response(f"Logfile {file_path} does not exist. " + f"Likely a crash during spinup of multiworld instance or it is still spinning up.", + mimetype="text/plain") return "Access Denied", 403 @@ -156,6 +143,7 @@ def host_room(room: UUID): if cmd: Command(room=room, commandtext=cmd) commit() + return redirect(url_for("host_room", room=room.id)) now = datetime.datetime.utcnow() # indicate that the page should reload to get the assigned port @@ -163,7 +151,22 @@ def host_room(room: UUID): with db_session: room.last_activity = now # will trigger a spinup, if it's not already running - return render_template("hostRoom.html", room=room, should_refresh=should_refresh) + def get_log(max_size: int = 1024000) -> str: + try: + with open(os.path.join("logs", str(room.id) + ".txt"), "rb") as log: + raw_size = 0 + fragments: List[str] = [] + for block in _read_log(log): + if raw_size + len(block) > max_size: + fragments.append("…") + break + raw_size += len(block) + fragments.append(block.decode("utf-8")) + return "".join(fragments) + except FileNotFoundError: + return "" + + return render_template("hostRoom.html", room=room, should_refresh=should_refresh, get_log=get_log) @app.route('/favicon.ico') diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 0158de7e241f..15b7bd61ceee 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -1,188 +1,281 @@ +import collections.abc import json -import logging import os -import typing +from textwrap import dedent +from typing import Dict, Union +from docutils.core import publish_parts + +import yaml +from flask import redirect, render_template, request, Response import Options from Utils import local_path from worlds.AutoWorld import AutoWorldRegister - -handled_in_js = {"start_inventory", "local_items", "non_local_items", "start_hints", "start_location_hints", - "exclude_locations", "priority_locations"} +from . import app, cache +from .generate import get_meta -def create(): +def create() -> None: target_folder = local_path("WebHostLib", "static", "generated") yaml_folder = os.path.join(target_folder, "configs") Options.generate_yaml_templates(yaml_folder) - def get_html_doc(option_type: type(Options.Option)) -> str: - if not option_type.__doc__: - return "Please document me!" - return "\n".join(line.strip() for line in option_type.__doc__.split("\n")).strip() - - weighted_options = { - "baseOptions": { - "description": "Generated by https://archipelago.gg/", - "name": "", - "game": {}, - }, - "games": {}, - } - - for game_name, world in AutoWorldRegister.world_types.items(): - - all_options: typing.Dict[str, Options.AssembleOptions] = world.options_dataclass.type_hints - - # Generate JSON files for player-options pages - player_options = { - "baseOptions": { - "description": f"Generated by https://archipelago.gg/ for {game_name}", - "game": game_name, - "name": "", - }, - } - game_options = {} - for option_name, option in all_options.items(): - if option_name in handled_in_js: - pass - - elif issubclass(option, Options.Choice) or issubclass(option, Options.Toggle): - game_options[option_name] = this_option = { - "type": "select", - "displayName": option.display_name if hasattr(option, "display_name") else option_name, - "description": get_html_doc(option), - "defaultValue": None, - "options": [] - } - - for sub_option_id, sub_option_name in option.name_lookup.items(): - if sub_option_name != "random": - this_option["options"].append({ - "name": option.get_option_name(sub_option_id), - "value": sub_option_name, - }) - if sub_option_id == option.default: - this_option["defaultValue"] = sub_option_name - - if not this_option["defaultValue"]: - this_option["defaultValue"] = "random" - - elif issubclass(option, Options.Range): - game_options[option_name] = { - "type": "range", - "displayName": option.display_name if hasattr(option, "display_name") else option_name, - "description": get_html_doc(option), - "defaultValue": option.default if hasattr( - option, "default") and option.default != "random" else option.range_start, - "min": option.range_start, - "max": option.range_end, - } - - if issubclass(option, Options.NamedRange): - game_options[option_name]["type"] = 'named_range' - game_options[option_name]["value_names"] = {} - for key, val in option.special_range_names.items(): - game_options[option_name]["value_names"][key] = val - - elif issubclass(option, Options.ItemSet): - game_options[option_name] = { - "type": "items-list", - "displayName": option.display_name if hasattr(option, "display_name") else option_name, - "description": get_html_doc(option), - "defaultValue": list(option.default) - } - - elif issubclass(option, Options.LocationSet): - game_options[option_name] = { - "type": "locations-list", - "displayName": option.display_name if hasattr(option, "display_name") else option_name, - "description": get_html_doc(option), - "defaultValue": list(option.default) - } - - elif issubclass(option, Options.VerifyKeys) and not issubclass(option, Options.OptionDict): - if option.valid_keys: - game_options[option_name] = { - "type": "custom-list", - "displayName": option.display_name if hasattr(option, "display_name") else option_name, - "description": get_html_doc(option), - "options": list(option.valid_keys), - "defaultValue": list(option.default) if hasattr(option, "default") else [] - } +def get_world_theme(game_name: str) -> str: + if game_name in AutoWorldRegister.world_types: + return AutoWorldRegister.world_types[game_name].web.theme + return 'grass' + + +def render_options_page(template: str, world_name: str, is_complex: bool = False) -> Union[Response, str]: + world = AutoWorldRegister.world_types[world_name] + if world.hidden or world.web.options_page is False: + return redirect("games") + visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui + + start_collapsed = {"Game Options": False} + for group in world.web.option_groups: + start_collapsed[group.name] = group.start_collapsed + + return render_template( + template, + world_name=world_name, + world=world, + option_groups=Options.get_option_groups(world, visibility_level=visibility_flag), + start_collapsed=start_collapsed, + issubclass=issubclass, + Options=Options, + theme=get_world_theme(world_name), + ) + + +def generate_game(options: Dict[str, Union[dict, str]]) -> Union[Response, str]: + from .generate import start_generation + return start_generation(options, get_meta({})) + + +def send_yaml(player_name: str, formatted_options: dict) -> Response: + response = Response(yaml.dump(formatted_options, sort_keys=False)) + response.headers["Content-Type"] = "text/yaml" + response.headers["Content-Disposition"] = f"attachment; filename={player_name}.yaml" + return response + + +@app.template_filter("dedent") +def filter_dedent(text: str) -> str: + return dedent(text).strip("\n ") + + +@app.template_filter("rst_to_html") +def filter_rst_to_html(text: str) -> str: + """Converts reStructuredText (such as a Python docstring) to HTML.""" + if text.startswith(" ") or text.startswith("\t"): + text = dedent(text) + elif "\n" in text: + lines = text.splitlines() + text = lines[0] + "\n" + dedent("\n".join(lines[1:])) + + return publish_parts(text, writer_name='html', settings=None, settings_overrides={ + 'raw_enable': False, + 'file_insertion_enabled': False, + 'output_encoding': 'unicode' + })['body'] + + +@app.template_test("ordered") +def test_ordered(obj): + return isinstance(obj, collections.abc.Sequence) + + +@app.route("/games//option-presets", methods=["GET"]) +@cache.cached() +def option_presets(game: str) -> Response: + world = AutoWorldRegister.world_types[game] + + presets = {} + for preset_name, preset in world.web.options_presets.items(): + presets[preset_name] = {} + for preset_option_name, preset_option in preset.items(): + if preset_option == "random": + presets[preset_name][preset_option_name] = preset_option + continue + + option = world.options_dataclass.type_hints[preset_option_name].from_any(preset_option) + if isinstance(option, Options.NamedRange) and isinstance(preset_option, str): + assert preset_option in option.special_range_names, \ + f"Invalid preset value '{preset_option}' for '{preset_option_name}' in '{preset_name}'. " \ + f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}." + + presets[preset_name][preset_option_name] = option.value + elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.ItemDict)): + presets[preset_name][preset_option_name] = option.value + elif isinstance(preset_option, str): + # Ensure the option value is valid for Choice and Toggle options + assert option.name_lookup[option.value] == preset_option, \ + f"Invalid option value '{preset_option}' for '{preset_option_name}' in preset '{preset_name}'. " \ + f"Values must not be resolved to a different option via option.from_text (or an alias)." + # Use the name of the option + presets[preset_name][preset_option_name] = option.current_key + else: + # Use the name of the option + presets[preset_name][preset_option_name] = option.current_key + + class SetEncoder(json.JSONEncoder): + def default(self, obj): + from collections.abc import Set + if isinstance(obj, Set): + return list(obj) + return json.JSONEncoder.default(self, obj) + + json_data = json.dumps(presets, cls=SetEncoder) + response = Response(json_data) + response.headers["Content-Type"] = "application/json" + return response + + +@app.route("/weighted-options") +def weighted_options_old(): + return redirect("games", 301) + + +@app.route("/games//weighted-options") +@cache.cached() +def weighted_options(game: str): + return render_options_page("weightedOptions/weightedOptions.html", game, is_complex=True) + + +@app.route("/games//generate-weighted-yaml", methods=["POST"]) +def generate_weighted_yaml(game: str): + if request.method == "POST": + intent_generate = False + options = {} + for key, val in request.form.items(): + if "||" not in key: + if len(str(val)) == 0: + continue + + options[key] = val else: - logging.debug(f"{option} not exported to Web Options.") - - player_options["gameOptions"] = game_options - - player_options["presetOptions"] = {} - for preset_name, preset in world.web.options_presets.items(): - player_options["presetOptions"][preset_name] = {} - for option_name, option_value in preset.items(): - # Random range type settings are not valid. - assert (not str(option_value).startswith("random-")), \ - f"Invalid preset value '{option_value}' for '{option_name}' in '{preset_name}'. Special random " \ - f"values are not supported for presets." - - # Normal random is supported, but needs to be handled explicitly. - if option_value == "random": - player_options["presetOptions"][preset_name][option_name] = option_value + if int(val) == 0: continue - option = world.options_dataclass.type_hints[option_name].from_any(option_value) - if isinstance(option, Options.NamedRange) and isinstance(option_value, str): - assert option_value in option.special_range_names, \ - f"Invalid preset value '{option_value}' for '{option_name}' in '{preset_name}'. " \ - f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}." - - # Still use the true value for the option, not the name. - player_options["presetOptions"][preset_name][option_name] = option.value - elif isinstance(option, Options.Range): - player_options["presetOptions"][preset_name][option_name] = option.value - elif isinstance(option_value, str): - # For Choice and Toggle options, the value should be the name of the option. This is to prevent - # setting a preset for an option with an overridden from_text method that would normally be okay, - # but would not be okay for the webhost's current implementation of player options UI. - assert option.name_lookup[option.value] == option_value, \ - f"Invalid option value '{option_value}' for '{option_name}' in preset '{preset_name}'. " \ - f"Values must not be resolved to a different option via option.from_text (or an alias)." - player_options["presetOptions"][preset_name][option_name] = option.current_key - else: - # int and bool values are fine, just resolve them to the current key for webhost. - player_options["presetOptions"][preset_name][option_name] = option.current_key - - os.makedirs(os.path.join(target_folder, 'player-options'), exist_ok=True) - - with open(os.path.join(target_folder, 'player-options', game_name + ".json"), "w") as f: - json.dump(player_options, f, indent=2, separators=(',', ': ')) - - if not world.hidden and world.web.options_page is True: - # Add the random option to Choice, TextChoice, and Toggle options - for option in game_options.values(): - if option["type"] == "select": - option["options"].append({"name": "Random", "value": "random"}) - - if not option["defaultValue"]: - option["defaultValue"] = "random" - - weighted_options["baseOptions"]["game"][game_name] = 0 - weighted_options["games"][game_name] = { - "gameSettings": game_options, - "gameItems": tuple(world.item_names), - "gameItemGroups": [ - group for group in world.item_name_groups.keys() if group != "Everything" - ], - "gameItemDescriptions": world.item_descriptions, - "gameLocations": tuple(world.location_names), - "gameLocationGroups": [ - group for group in world.location_name_groups.keys() if group != "Everywhere" - ], - "gameLocationDescriptions": world.location_descriptions, - } - - with open(os.path.join(target_folder, 'weighted-options.json'), "w") as f: - json.dump(weighted_options, f, indent=2, separators=(',', ': ')) + [option, setting] = key.split("||") + options.setdefault(option, {})[setting] = int(val) + + # Error checking + if "name" not in options: + return "Player name is required." + + # Remove POST data irrelevant to YAML + if "intent-generate" in options: + intent_generate = True + del options["intent-generate"] + if "intent-export" in options: + del options["intent-export"] + + # Properly format YAML output + player_name = options["name"] + del options["name"] + + formatted_options = { + "name": player_name, + "game": game, + "description": f"Generated by https://archipelago.gg/ for {game}", + game: options, + } + + if intent_generate: + return generate_game({player_name: formatted_options}) + + else: + return send_yaml(player_name, formatted_options) + + +# Player options pages +@app.route("/games//player-options") +@cache.cached() +def player_options(game: str): + return render_options_page("playerOptions/playerOptions.html", game, is_complex=False) + + +# YAML generator for player-options +@app.route("/games//generate-yaml", methods=["POST"]) +def generate_yaml(game: str): + if request.method == "POST": + options = {} + intent_generate = False + for key, val in request.form.items(multi=True): + if key in options: + if not isinstance(options[key], list): + options[key] = [options[key]] + options[key].append(val) + else: + options[key] = val + + for key, val in options.copy().items(): + key_parts = key.rsplit("||", 2) + # Detect and build ItemDict options from their name pattern + if key_parts[-1] == "qty": + if key_parts[0] not in options: + options[key_parts[0]] = {} + if val != "0": + options[key_parts[0]][key_parts[1]] = int(val) + del options[key] + + # Detect keys which end with -custom, indicating a TextChoice with a possible custom value + elif key_parts[-1].endswith("-custom"): + if val: + options[key_parts[-1][:-7]] = val + + del options[key] + + # Detect keys which end with -range, indicating a NamedRange with a possible custom value + elif key_parts[-1].endswith("-range"): + if options[key_parts[-1][:-6]] == "custom": + options[key_parts[-1][:-6]] = val + + del options[key] + + # Detect random-* keys and set their options accordingly + for key, val in options.copy().items(): + if key.startswith("random-"): + options[key.removeprefix("random-")] = "random" + del options[key] + + # Error checking + if not options["name"]: + return "Player name is required." + + # Remove POST data irrelevant to YAML + preset_name = 'default' + if "intent-generate" in options: + intent_generate = True + del options["intent-generate"] + if "intent-export" in options: + del options["intent-export"] + if "game-options-preset" in options: + preset_name = options["game-options-preset"] + del options["game-options-preset"] + + # Properly format YAML output + player_name = options["name"] + del options["name"] + + description = f"Generated by https://archipelago.gg/ for {game}" + if preset_name != 'default' and preset_name != 'custom': + description += f" using {preset_name} preset" + + formatted_options = { + "name": player_name, + "game": game, + "description": description, + game: options, + } + + if intent_generate: + return generate_game({player_name: formatted_options}) + else: + return send_yaml(player_name, formatted_options) diff --git a/WebHostLib/requirements.txt b/WebHostLib/requirements.txt index 62707d78cf1f..3452c9d416db 100644 --- a/WebHostLib/requirements.txt +++ b/WebHostLib/requirements.txt @@ -1,9 +1,10 @@ -flask>=3.0.0 +flask>=3.0.3 +werkzeug>=3.0.3 pony>=0.7.17 -waitress>=2.1.2 -Flask-Caching>=2.1.0 -Flask-Compress>=1.14 -Flask-Limiter>=3.5.0 +waitress>=3.0.0 +Flask-Caching>=2.3.0 +Flask-Compress>=1.15 +Flask-Limiter>=3.7.0 bokeh>=3.1.1; python_version <= '3.8' -bokeh>=3.3.2; python_version >= '3.9' -markupsafe>=2.1.3 +bokeh>=3.4.1; python_version >= '3.9' +markupsafe>=2.1.5 diff --git a/WebHostLib/robots.py b/WebHostLib/robots.py new file mode 100644 index 000000000000..93c735c71015 --- /dev/null +++ b/WebHostLib/robots.py @@ -0,0 +1,15 @@ +from WebHostLib import app +from flask import abort +from . import cache + + +@cache.cached() +@app.route('/robots.txt') +def robots(): + # If this host is not official, do not allow search engine crawling + if not app.config["ASSET_RIGHTS"]: + # filename changed in case the path is intercepted and served by an outside service + return app.send_static_file('robots_file.txt') + + # Send 404 if the host has affirmed this to be the official WebHost + abort(404) diff --git a/WebHostLib/static/assets/lttp-tracker.js b/WebHostLib/static/assets/lttp-tracker.js deleted file mode 100644 index 3f01f93cd38c..000000000000 --- a/WebHostLib/static/assets/lttp-tracker.js +++ /dev/null @@ -1,20 +0,0 @@ -window.addEventListener('load', () => { - const url = window.location; - setInterval(() => { - const ajax = new XMLHttpRequest(); - ajax.onreadystatechange = () => { - if (ajax.readyState !== 4) { return; } - - // Create a fake DOM using the returned HTML - const domParser = new DOMParser(); - const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html'); - - // Update item and location trackers - document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML; - document.getElementById('location-table').innerHTML = fakeDOM.getElementById('location-table').innerHTML; - - }; - ajax.open('GET', url); - ajax.send(); - }, 15000) -}); diff --git a/WebHostLib/static/assets/player-options.js b/WebHostLib/static/assets/player-options.js deleted file mode 100644 index 92cd6c43f3cc..000000000000 --- a/WebHostLib/static/assets/player-options.js +++ /dev/null @@ -1,523 +0,0 @@ -let gameName = null; - -window.addEventListener('load', () => { - gameName = document.getElementById('player-options').getAttribute('data-game'); - - // Update game name on page - document.getElementById('game-name').innerText = gameName; - - fetchOptionData().then((results) => { - let optionHash = localStorage.getItem(`${gameName}-hash`); - if (!optionHash) { - // If no hash data has been set before, set it now - optionHash = md5(JSON.stringify(results)); - localStorage.setItem(`${gameName}-hash`, optionHash); - localStorage.removeItem(gameName); - } - - if (optionHash !== md5(JSON.stringify(results))) { - showUserMessage( - 'Your options are out of date! Click here to update them! Be aware this will reset them all to default.' - ); - document.getElementById('user-message').addEventListener('click', resetOptions); - } - - // Page setup - createDefaultOptions(results); - buildUI(results); - adjustHeaderWidth(); - - // Event listeners - document.getElementById('export-options').addEventListener('click', () => exportOptions()); - document.getElementById('generate-race').addEventListener('click', () => generateGame(true)); - document.getElementById('generate-game').addEventListener('click', () => generateGame()); - - // Name input field - const playerOptions = JSON.parse(localStorage.getItem(gameName)); - const nameInput = document.getElementById('player-name'); - nameInput.addEventListener('keyup', (event) => updateBaseOption(event)); - nameInput.value = playerOptions.name; - - // Presets - const presetSelect = document.getElementById('game-options-preset'); - presetSelect.addEventListener('change', (event) => setPresets(results, event.target.value)); - for (const preset in results['presetOptions']) { - const presetOption = document.createElement('option'); - presetOption.innerText = preset; - presetSelect.appendChild(presetOption); - } - presetSelect.value = localStorage.getItem(`${gameName}-preset`); - results['presetOptions']['__default'] = {}; - }).catch((e) => { - console.error(e); - const url = new URL(window.location.href); - window.location.replace(`${url.protocol}//${url.hostname}/page-not-found`); - }) -}); - -const resetOptions = () => { - localStorage.removeItem(gameName); - localStorage.removeItem(`${gameName}-hash`); - localStorage.removeItem(`${gameName}-preset`); - window.location.reload(); -}; - -const fetchOptionData = () => new Promise((resolve, reject) => { - const ajax = new XMLHttpRequest(); - ajax.onreadystatechange = () => { - if (ajax.readyState !== 4) { return; } - if (ajax.status !== 200) { - reject(ajax.responseText); - return; - } - try{ resolve(JSON.parse(ajax.responseText)); } - catch(error){ reject(error); } - }; - ajax.open('GET', `${window.location.origin}/static/generated/player-options/${gameName}.json`, true); - ajax.send(); -}); - -const createDefaultOptions = (optionData) => { - if (!localStorage.getItem(gameName)) { - const newOptions = { - [gameName]: {}, - }; - for (let baseOption of Object.keys(optionData.baseOptions)){ - newOptions[baseOption] = optionData.baseOptions[baseOption]; - } - for (let gameOption of Object.keys(optionData.gameOptions)){ - newOptions[gameName][gameOption] = optionData.gameOptions[gameOption].defaultValue; - } - localStorage.setItem(gameName, JSON.stringify(newOptions)); - } - - if (!localStorage.getItem(`${gameName}-preset`)) { - localStorage.setItem(`${gameName}-preset`, '__default'); - } -}; - -const buildUI = (optionData) => { - // Game Options - const leftGameOpts = {}; - const rightGameOpts = {}; - Object.keys(optionData.gameOptions).forEach((key, index) => { - if (index < Object.keys(optionData.gameOptions).length / 2) { - leftGameOpts[key] = optionData.gameOptions[key]; - } else { - rightGameOpts[key] = optionData.gameOptions[key]; - } - }); - document.getElementById('game-options-left').appendChild(buildOptionsTable(leftGameOpts)); - document.getElementById('game-options-right').appendChild(buildOptionsTable(rightGameOpts)); -}; - -const buildOptionsTable = (options, romOpts = false) => { - const currentOptions = JSON.parse(localStorage.getItem(gameName)); - const table = document.createElement('table'); - const tbody = document.createElement('tbody'); - - Object.keys(options).forEach((option) => { - const tr = document.createElement('tr'); - - // td Left - const tdl = document.createElement('td'); - const label = document.createElement('label'); - label.textContent = `${options[option].displayName}: `; - label.setAttribute('for', option); - - const questionSpan = document.createElement('span'); - questionSpan.classList.add('interactive'); - questionSpan.setAttribute('data-tooltip', options[option].description); - questionSpan.innerText = '(?)'; - - label.appendChild(questionSpan); - tdl.appendChild(label); - tr.appendChild(tdl); - - // td Right - const tdr = document.createElement('td'); - let element = null; - - const randomButton = document.createElement('button'); - - switch(options[option].type) { - case 'select': - element = document.createElement('div'); - element.classList.add('select-container'); - let select = document.createElement('select'); - select.setAttribute('id', option); - select.setAttribute('data-key', option); - if (romOpts) { select.setAttribute('data-romOpt', '1'); } - options[option].options.forEach((opt) => { - const optionElement = document.createElement('option'); - optionElement.setAttribute('value', opt.value); - optionElement.innerText = opt.name; - - if ((isNaN(currentOptions[gameName][option]) && - (parseInt(opt.value, 10) === parseInt(currentOptions[gameName][option]))) || - (opt.value === currentOptions[gameName][option])) - { - optionElement.selected = true; - } - select.appendChild(optionElement); - }); - select.addEventListener('change', (event) => updateGameOption(event.target)); - element.appendChild(select); - - // Randomize button - randomButton.innerText = '🎲'; - randomButton.classList.add('randomize-button'); - randomButton.setAttribute('data-key', option); - randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!'); - randomButton.addEventListener('click', (event) => toggleRandomize(event, select)); - if (currentOptions[gameName][option] === 'random') { - randomButton.classList.add('active'); - select.disabled = true; - } - - element.appendChild(randomButton); - break; - - case 'range': - element = document.createElement('div'); - element.classList.add('range-container'); - - let range = document.createElement('input'); - range.setAttribute('id', option); - range.setAttribute('type', 'range'); - range.setAttribute('data-key', option); - range.setAttribute('min', options[option].min); - range.setAttribute('max', options[option].max); - range.value = currentOptions[gameName][option]; - range.addEventListener('change', (event) => { - document.getElementById(`${option}-value`).innerText = event.target.value; - updateGameOption(event.target); - }); - element.appendChild(range); - - let rangeVal = document.createElement('span'); - rangeVal.classList.add('range-value'); - rangeVal.setAttribute('id', `${option}-value`); - rangeVal.innerText = currentOptions[gameName][option] !== 'random' ? - currentOptions[gameName][option] : options[option].defaultValue; - element.appendChild(rangeVal); - - // Randomize button - randomButton.innerText = '🎲'; - randomButton.classList.add('randomize-button'); - randomButton.setAttribute('data-key', option); - randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!'); - randomButton.addEventListener('click', (event) => toggleRandomize(event, range)); - if (currentOptions[gameName][option] === 'random') { - randomButton.classList.add('active'); - range.disabled = true; - } - - element.appendChild(randomButton); - break; - - case 'named_range': - element = document.createElement('div'); - element.classList.add('named-range-container'); - - // Build the select element - let namedRangeSelect = document.createElement('select'); - namedRangeSelect.setAttribute('data-key', option); - Object.keys(options[option].value_names).forEach((presetName) => { - let presetOption = document.createElement('option'); - presetOption.innerText = presetName; - presetOption.value = options[option].value_names[presetName]; - const words = presetOption.innerText.split('_'); - for (let i = 0; i < words.length; i++) { - words[i] = words[i][0].toUpperCase() + words[i].substring(1); - } - presetOption.innerText = words.join(' '); - namedRangeSelect.appendChild(presetOption); - }); - let customOption = document.createElement('option'); - customOption.innerText = 'Custom'; - customOption.value = 'custom'; - customOption.selected = true; - namedRangeSelect.appendChild(customOption); - if (Object.values(options[option].value_names).includes(Number(currentOptions[gameName][option]))) { - namedRangeSelect.value = Number(currentOptions[gameName][option]); - } - - // Build range element - let namedRangeWrapper = document.createElement('div'); - namedRangeWrapper.classList.add('named-range-wrapper'); - let namedRange = document.createElement('input'); - namedRange.setAttribute('type', 'range'); - namedRange.setAttribute('data-key', option); - namedRange.setAttribute('min', options[option].min); - namedRange.setAttribute('max', options[option].max); - namedRange.value = currentOptions[gameName][option]; - - // Build rage value element - let namedRangeVal = document.createElement('span'); - namedRangeVal.classList.add('range-value'); - namedRangeVal.setAttribute('id', `${option}-value`); - namedRangeVal.innerText = currentOptions[gameName][option] !== 'random' ? - currentOptions[gameName][option] : options[option].defaultValue; - - // Configure select event listener - namedRangeSelect.addEventListener('change', (event) => { - if (event.target.value === 'custom') { return; } - - // Update range slider - namedRange.value = event.target.value; - document.getElementById(`${option}-value`).innerText = event.target.value; - updateGameOption(event.target); - }); - - // Configure range event handler - namedRange.addEventListener('change', (event) => { - // Update select element - namedRangeSelect.value = - (Object.values(options[option].value_names).includes(parseInt(event.target.value))) ? - parseInt(event.target.value) : 'custom'; - document.getElementById(`${option}-value`).innerText = event.target.value; - updateGameOption(event.target); - }); - - element.appendChild(namedRangeSelect); - namedRangeWrapper.appendChild(namedRange); - namedRangeWrapper.appendChild(namedRangeVal); - element.appendChild(namedRangeWrapper); - - // Randomize button - randomButton.innerText = '🎲'; - randomButton.classList.add('randomize-button'); - randomButton.setAttribute('data-key', option); - randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!'); - randomButton.addEventListener('click', (event) => toggleRandomize( - event, namedRange, namedRangeSelect) - ); - if (currentOptions[gameName][option] === 'random') { - randomButton.classList.add('active'); - namedRange.disabled = true; - namedRangeSelect.disabled = true; - } - - namedRangeWrapper.appendChild(randomButton); - break; - - default: - console.error(`Ignoring unknown option type: ${options[option].type} with name ${option}`); - return; - } - - tdr.appendChild(element); - tr.appendChild(tdr); - tbody.appendChild(tr); - }); - - table.appendChild(tbody); - return table; -}; - -const setPresets = (optionsData, presetName) => { - const defaults = optionsData['gameOptions']; - const preset = optionsData['presetOptions'][presetName]; - - localStorage.setItem(`${gameName}-preset`, presetName); - - if (!preset) { - console.error(`No presets defined for preset name: '${presetName}'`); - return; - } - - const updateOptionElement = (option, presetValue) => { - const optionElement = document.querySelector(`#${option}[data-key='${option}']`); - const randomElement = document.querySelector(`.randomize-button[data-key='${option}']`); - - if (presetValue === 'random') { - randomElement.classList.add('active'); - optionElement.disabled = true; - updateGameOption(randomElement, false); - } else { - optionElement.value = presetValue; - randomElement.classList.remove('active'); - optionElement.disabled = undefined; - updateGameOption(optionElement, false); - } - }; - - for (const option in defaults) { - let presetValue = preset[option]; - if (presetValue === undefined) { - // Using the default value if not set in presets. - presetValue = defaults[option]['defaultValue']; - } - - switch (defaults[option].type) { - case 'range': - const numberElement = document.querySelector(`#${option}-value`); - if (presetValue === 'random') { - numberElement.innerText = defaults[option]['defaultValue'] === 'random' - ? defaults[option]['min'] // A fallback so we don't print 'random' in the UI. - : defaults[option]['defaultValue']; - } else { - numberElement.innerText = presetValue; - } - - updateOptionElement(option, presetValue); - break; - - case 'select': { - updateOptionElement(option, presetValue); - break; - } - - case 'named_range': { - const selectElement = document.querySelector(`select[data-key='${option}']`); - const rangeElement = document.querySelector(`input[data-key='${option}']`); - const randomElement = document.querySelector(`.randomize-button[data-key='${option}']`); - - if (presetValue === 'random') { - randomElement.classList.add('active'); - selectElement.disabled = true; - rangeElement.disabled = true; - updateGameOption(randomElement, false); - } else { - rangeElement.value = presetValue; - selectElement.value = Object.values(defaults[option]['value_names']).includes(parseInt(presetValue)) ? - parseInt(presetValue) : 'custom'; - document.getElementById(`${option}-value`).innerText = presetValue; - - randomElement.classList.remove('active'); - selectElement.disabled = undefined; - rangeElement.disabled = undefined; - updateGameOption(rangeElement, false); - } - break; - } - - default: - console.warn(`Ignoring preset value for unknown option type: ${defaults[option].type} with name ${option}`); - break; - } - } -}; - -const toggleRandomize = (event, inputElement, optionalSelectElement = null) => { - const active = event.target.classList.contains('active'); - const randomButton = event.target; - - if (active) { - randomButton.classList.remove('active'); - inputElement.disabled = undefined; - if (optionalSelectElement) { - optionalSelectElement.disabled = undefined; - } - } else { - randomButton.classList.add('active'); - inputElement.disabled = true; - if (optionalSelectElement) { - optionalSelectElement.disabled = true; - } - } - updateGameOption(active ? inputElement : randomButton); -}; - -const updateBaseOption = (event) => { - const options = JSON.parse(localStorage.getItem(gameName)); - options[event.target.getAttribute('data-key')] = isNaN(event.target.value) ? - event.target.value : parseInt(event.target.value); - localStorage.setItem(gameName, JSON.stringify(options)); -}; - -const updateGameOption = (optionElement, toggleCustomPreset = true) => { - const options = JSON.parse(localStorage.getItem(gameName)); - - if (toggleCustomPreset) { - localStorage.setItem(`${gameName}-preset`, '__custom'); - const presetElement = document.getElementById('game-options-preset'); - presetElement.value = '__custom'; - } - - if (optionElement.classList.contains('randomize-button')) { - // If the event passed in is the randomize button, then we know what we must do. - options[gameName][optionElement.getAttribute('data-key')] = 'random'; - } else { - options[gameName][optionElement.getAttribute('data-key')] = isNaN(optionElement.value) ? - optionElement.value : parseInt(optionElement.value, 10); - } - - localStorage.setItem(gameName, JSON.stringify(options)); -}; - -const exportOptions = () => { - const options = JSON.parse(localStorage.getItem(gameName)); - const preset = localStorage.getItem(`${gameName}-preset`); - switch (preset) { - case '__default': - options['description'] = `Generated by https://archipelago.gg with the default preset.`; - break; - - case '__custom': - options['description'] = `Generated by https://archipelago.gg.`; - break; - - default: - options['description'] = `Generated by https://archipelago.gg with the ${preset} preset.`; - } - - if (!options.name || options.name.toString().trim().length === 0) { - return showUserMessage('You must enter a player name!'); - } - const yamlText = jsyaml.safeDump(options, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); - download(`${document.getElementById('player-name').value}.yaml`, yamlText); -}; - -/** Create an anchor and trigger a download of a text file. */ -const download = (filename, text) => { - const downloadLink = document.createElement('a'); - downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text)) - downloadLink.setAttribute('download', filename); - downloadLink.style.display = 'none'; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); -}; - -const generateGame = (raceMode = false) => { - const options = JSON.parse(localStorage.getItem(gameName)); - if (!options.name || options.name.toLowerCase() === 'player' || options.name.trim().length === 0) { - return showUserMessage('You must enter a player name!'); - } - - axios.post('/api/generate', { - weights: { player: options }, - presetData: { player: options }, - playerCount: 1, - spoiler: 3, - race: raceMode ? '1' : '0', - }).then((response) => { - window.location.href = response.data.url; - }).catch((error) => { - let userMessage = 'Something went wrong and your game could not be generated.'; - if (error.response.data.text) { - userMessage += ' ' + error.response.data.text; - } - showUserMessage(userMessage); - console.error(error); - }); -}; - -const showUserMessage = (message) => { - const userMessage = document.getElementById('user-message'); - userMessage.innerText = message; - userMessage.classList.add('visible'); - window.scrollTo(0, 0); - userMessage.addEventListener('click', () => { - userMessage.classList.remove('visible'); - userMessage.addEventListener('click', hideUserMessage); - }); -}; - -const hideUserMessage = () => { - const userMessage = document.getElementById('user-message'); - userMessage.classList.remove('visible'); - userMessage.removeEventListener('click', hideUserMessage); -}; diff --git a/WebHostLib/static/assets/playerOptions.js b/WebHostLib/static/assets/playerOptions.js new file mode 100644 index 000000000000..d0f2e388c2a6 --- /dev/null +++ b/WebHostLib/static/assets/playerOptions.js @@ -0,0 +1,335 @@ +let presets = {}; + +window.addEventListener('load', async () => { + // Load settings from localStorage, if available + loadSettings(); + + // Fetch presets if available + await fetchPresets(); + + // Handle changes to range inputs + document.querySelectorAll('input[type=range]').forEach((range) => { + const optionName = range.getAttribute('id'); + range.addEventListener('change', () => { + document.getElementById(`${optionName}-value`).innerText = range.value; + + // Handle updating named range selects to "custom" if appropriate + const select = document.querySelector(`select[data-option-name=${optionName}]`); + if (select) { + let updated = false; + select?.childNodes.forEach((option) => { + if (option.value === range.value) { + select.value = range.value; + updated = true; + } + }); + if (!updated) { + select.value = 'custom'; + } + } + }); + }); + + // Handle changes to named range selects + document.querySelectorAll('.named-range-container select').forEach((select) => { + const optionName = select.getAttribute('data-option-name'); + select.addEventListener('change', (evt) => { + document.getElementById(optionName).value = evt.target.value; + document.getElementById(`${optionName}-value`).innerText = evt.target.value; + }); + }); + + // Handle changes to randomize checkboxes + document.querySelectorAll('.randomize-checkbox').forEach((checkbox) => { + const optionName = checkbox.getAttribute('data-option-name'); + checkbox.addEventListener('change', () => { + const optionInput = document.getElementById(optionName); + const namedRangeSelect = document.querySelector(`select[data-option-name=${optionName}]`); + const customInput = document.getElementById(`${optionName}-custom`); + if (checkbox.checked) { + optionInput.setAttribute('disabled', '1'); + namedRangeSelect?.setAttribute('disabled', '1'); + if (customInput) { + customInput.setAttribute('disabled', '1'); + } + } else { + optionInput.removeAttribute('disabled'); + namedRangeSelect?.removeAttribute('disabled'); + if (customInput) { + customInput.removeAttribute('disabled'); + } + } + }); + }); + + // Handle changes to TextChoice input[type=text] + document.querySelectorAll('.text-choice-container input[type=text]').forEach((input) => { + const optionName = input.getAttribute('data-option-name'); + input.addEventListener('input', () => { + const select = document.getElementById(optionName); + const optionValues = []; + select.childNodes.forEach((option) => optionValues.push(option.value)); + select.value = (optionValues.includes(input.value)) ? input.value : 'custom'; + }); + }); + + // Handle changes to TextChoice select + document.querySelectorAll('.text-choice-container select').forEach((select) => { + const optionName = select.getAttribute('id'); + select.addEventListener('change', () => { + document.getElementById(`${optionName}-custom`).value = ''; + }); + }); + + // Update the "Option Preset" select to read "custom" when changes are made to relevant inputs + const presetSelect = document.getElementById('game-options-preset'); + document.querySelectorAll('input, select').forEach((input) => { + if ( // Ignore inputs which have no effect on yaml generation + (input.id === 'player-name') || + (input.id === 'game-options-preset') || + (input.classList.contains('group-toggle')) || + (input.type === 'submit') + ) { + return; + } + input.addEventListener('change', () => { + presetSelect.value = 'custom'; + }); + }); + + // Handle changes to presets select + document.getElementById('game-options-preset').addEventListener('change', choosePreset); + + // Save settings to localStorage when form is submitted + document.getElementById('options-form').addEventListener('submit', (evt) => { + const playerName = document.getElementById('player-name'); + if (!playerName.value.trim()) { + evt.preventDefault(); + window.scrollTo(0, 0); + showUserMessage('You must enter a player name!'); + } + + saveSettings(); + }); +}); + +// Save all settings to localStorage +const saveSettings = () => { + const options = { + inputs: {}, + checkboxes: {}, + }; + document.querySelectorAll('input, select').forEach((input) => { + if (input.type === 'submit') { + // Ignore submit inputs + } + else if (input.type === 'checkbox') { + options.checkboxes[input.id] = input.checked; + } + else { + options.inputs[input.id] = input.value + } + }); + const game = document.getElementById('player-options').getAttribute('data-game'); + localStorage.setItem(game, JSON.stringify(options)); +}; + +// Load all options from localStorage +const loadSettings = () => { + const game = document.getElementById('player-options').getAttribute('data-game'); + + const options = JSON.parse(localStorage.getItem(game)); + if (options) { + if (!options.inputs || !options.checkboxes) { + localStorage.removeItem(game); + return; + } + + // Restore value-based inputs and selects + Object.keys(options.inputs).forEach((key) => { + try{ + document.getElementById(key).value = options.inputs[key]; + const rangeValue = document.getElementById(`${key}-value`); + if (rangeValue) { + rangeValue.innerText = options.inputs[key]; + } + } catch (err) { + console.error(`Unable to restore value to input with id ${key}`); + } + }); + + // Restore checkboxes + Object.keys(options.checkboxes).forEach((key) => { + try{ + if (options.checkboxes[key]) { + document.getElementById(key).setAttribute('checked', '1'); + } + } catch (err) { + console.error(`Unable to restore value to input with id ${key}`); + } + }); + } + + // Ensure any input for which the randomize checkbox is checked by default, the relevant inputs are disabled + document.querySelectorAll('.randomize-checkbox').forEach((checkbox) => { + const optionName = checkbox.getAttribute('data-option-name'); + if (checkbox.checked) { + const input = document.getElementById(optionName); + if (input) { + input.setAttribute('disabled', '1'); + } + const customInput = document.getElementById(`${optionName}-custom`); + if (customInput) { + customInput.setAttribute('disabled', '1'); + } + } + }); +}; + +/** + * Fetch the preset data for this game and apply the presets if localStorage indicates one was previously chosen + * @returns {Promise} + */ +const fetchPresets = async () => { + const response = await fetch('option-presets'); + presets = await response.json(); + const presetSelect = document.getElementById('game-options-preset'); + presetSelect.removeAttribute('disabled'); + + const game = document.getElementById('player-options').getAttribute('data-game'); + const presetToApply = localStorage.getItem(`${game}-preset`); + const playerName = localStorage.getItem(`${game}-player`); + if (presetToApply) { + localStorage.removeItem(`${game}-preset`); + presetSelect.value = presetToApply; + applyPresets(presetToApply); + } + + if (playerName) { + document.getElementById('player-name').value = playerName; + localStorage.removeItem(`${game}-player`); + } +}; + +/** + * Clear the localStorage for this game and set a preset to be loaded upon page reload + * @param evt + */ +const choosePreset = (evt) => { + if (evt.target.value === 'custom') { return; } + + const game = document.getElementById('player-options').getAttribute('data-game'); + localStorage.removeItem(game); + + localStorage.setItem(`${game}-player`, document.getElementById('player-name').value); + if (evt.target.value !== 'default') { + localStorage.setItem(`${game}-preset`, evt.target.value); + } + + document.querySelectorAll('#options-form input, #options-form select').forEach((input) => { + if (input.id === 'player-name') { return; } + input.removeAttribute('value'); + }); + + window.location.replace(window.location.href); +}; + +const applyPresets = (presetName) => { + // Ignore the "default" preset, because it gets set automatically by Jinja + if (presetName === 'default') { + saveSettings(); + return; + } + + if (!presets[presetName]) { + console.error(`Unknown preset ${presetName} chosen`); + return; + } + + const preset = presets[presetName]; + Object.keys(preset).forEach((optionName) => { + const optionValue = preset[optionName]; + + // Handle List and Set options + if (Array.isArray(optionValue)) { + document.querySelectorAll(`input[type=checkbox][name=${optionName}]`).forEach((checkbox) => { + if (optionValue.includes(checkbox.value)) { + checkbox.setAttribute('checked', '1'); + } else { + checkbox.removeAttribute('checked'); + } + }); + return; + } + + // Handle Dict options + if (typeof(optionValue) === 'object' && optionValue !== null) { + const itemNames = Object.keys(optionValue); + document.querySelectorAll(`input[type=number][data-option-name=${optionName}]`).forEach((input) => { + const itemName = input.getAttribute('data-item-name'); + input.value = (itemNames.includes(itemName)) ? optionValue[itemName] : 0 + }); + return; + } + + // Identify all possible elements + const normalInput = document.getElementById(optionName); + const customInput = document.getElementById(`${optionName}-custom`); + const rangeValue = document.getElementById(`${optionName}-value`); + const randomizeInput = document.getElementById(`random-${optionName}`); + const namedRangeSelect = document.getElementById(`${optionName}-select`); + + // It is possible for named ranges to use name of a value rather than the value itself. This is accounted for here + let trueValue = optionValue; + if (namedRangeSelect) { + namedRangeSelect.querySelectorAll('option').forEach((opt) => { + if (opt.innerText.startsWith(optionValue)) { + trueValue = opt.value; + } + }); + namedRangeSelect.value = trueValue; + } + + // Handle options whose presets are "random" + if (optionValue === 'random') { + normalInput.setAttribute('disabled', '1'); + randomizeInput.setAttribute('checked', '1'); + if (customInput) { + customInput.setAttribute('disabled', '1'); + } + if (rangeValue) { + rangeValue.innerText = normalInput.value; + } + if (namedRangeSelect) { + namedRangeSelect.setAttribute('disabled', '1'); + } + return; + } + + // Handle normal (text, number, select, etc.) and custom inputs (custom inputs exist with TextChoice only) + normalInput.value = trueValue; + normalInput.removeAttribute('disabled'); + randomizeInput.removeAttribute('checked'); + if (customInput) { + document.getElementById(`${optionName}-custom`).removeAttribute('disabled'); + } + if (rangeValue) { + rangeValue.innerText = trueValue; + } + }); + + saveSettings(); +}; + +const showUserMessage = (text) => { + const userMessage = document.getElementById('user-message'); + userMessage.innerText = text; + userMessage.addEventListener('click', hideUserMessage); + userMessage.style.display = 'block'; +}; + +const hideUserMessage = () => { + const userMessage = document.getElementById('user-message'); + userMessage.removeEventListener('click', hideUserMessage); + userMessage.style.display = 'none'; +}; diff --git a/WebHostLib/static/assets/sc2wolTracker.js b/WebHostLib/static/assets/sc2Tracker.js similarity index 85% rename from WebHostLib/static/assets/sc2wolTracker.js rename to WebHostLib/static/assets/sc2Tracker.js index a698214b8dd6..30d4acd60b7e 100644 --- a/WebHostLib/static/assets/sc2wolTracker.js +++ b/WebHostLib/static/assets/sc2Tracker.js @@ -25,16 +25,16 @@ window.addEventListener('load', () => { // Collapsible advancement sections const categories = document.getElementsByClassName("location-category"); - for (let i = 0; i < categories.length; i++) { - let hide_id = categories[i].id.split('-')[0]; - if (hide_id == 'Total') { + for (let category of categories) { + let hide_id = category.id.split('_')[0]; + if (hide_id === 'Total') { continue; } - categories[i].addEventListener('click', function() { + category.addEventListener('click', function() { // Toggle the advancement list document.getElementById(hide_id).classList.toggle("hide"); // Change text of the header - const tab_header = document.getElementById(hide_id+'-header').children[0]; + const tab_header = document.getElementById(hide_id+'_header').children[0]; const orig_text = tab_header.innerHTML; let new_text; if (orig_text.includes("▼")) { diff --git a/WebHostLib/static/assets/supportedGames.js b/WebHostLib/static/assets/supportedGames.js index 56eb15b5e580..b692db9283d2 100644 --- a/WebHostLib/static/assets/supportedGames.js +++ b/WebHostLib/static/assets/supportedGames.js @@ -1,18 +1,16 @@ window.addEventListener('load', () => { // Add toggle listener to all elements with .collapse-toggle - const toggleButtons = document.querySelectorAll('.collapse-toggle'); - toggleButtons.forEach((e) => e.addEventListener('click', toggleCollapse)); + const toggleButtons = document.querySelectorAll('details'); // Handle game filter input const gameSearch = document.getElementById('game-search'); gameSearch.value = ''; gameSearch.addEventListener('input', (evt) => { if (!evt.target.value.trim()) { - // If input is empty, display all collapsed games + // If input is empty, display all games as collapsed return toggleButtons.forEach((header) => { header.style.display = null; - header.firstElementChild.innerText = '▶'; - header.nextElementSibling.classList.add('collapsed'); + header.removeAttribute('open'); }); } @@ -21,12 +19,10 @@ window.addEventListener('load', () => { // If the game name includes the search string, display the game. If not, hide it if (header.getAttribute('data-game').toLowerCase().includes(evt.target.value.toLowerCase())) { header.style.display = null; - header.firstElementChild.innerText = '▼'; - header.nextElementSibling.classList.remove('collapsed'); + header.setAttribute('open', '1'); } else { header.style.display = 'none'; - header.firstElementChild.innerText = '▶'; - header.nextElementSibling.classList.add('collapsed'); + header.removeAttribute('open'); } }); }); @@ -35,30 +31,14 @@ window.addEventListener('load', () => { document.getElementById('collapse-all').addEventListener('click', collapseAll); }); -const toggleCollapse = (evt) => { - const gameArrow = evt.target.firstElementChild; - const gameInfo = evt.target.nextElementSibling; - if (gameInfo.classList.contains('collapsed')) { - gameArrow.innerText = '▼'; - gameInfo.classList.remove('collapsed'); - } else { - gameArrow.innerText = '▶'; - gameInfo.classList.add('collapsed'); - } -}; - const expandAll = () => { - document.querySelectorAll('.collapse-toggle').forEach((header) => { - if (header.style.display === 'none') { return; } - header.firstElementChild.innerText = '▼'; - header.nextElementSibling.classList.remove('collapsed'); + document.querySelectorAll('details').forEach((detail) => { + detail.setAttribute('open', '1'); }); }; const collapseAll = () => { - document.querySelectorAll('.collapse-toggle').forEach((header) => { - if (header.style.display === 'none') { return; } - header.firstElementChild.innerText = '▶'; - header.nextElementSibling.classList.add('collapsed'); + document.querySelectorAll('details').forEach((detail) => { + detail.removeAttribute('open'); }); }; diff --git a/WebHostLib/static/assets/trackerCommon.js b/WebHostLib/static/assets/trackerCommon.js index b8e089ece5d3..6324837b2816 100644 --- a/WebHostLib/static/assets/trackerCommon.js +++ b/WebHostLib/static/assets/trackerCommon.js @@ -27,7 +27,7 @@ const adjustTableHeight = () => { * @returns {string} */ const secondsToHours = (seconds) => { - let hours = Math.floor(seconds / 3600); + let hours = Math.floor(seconds / 3600); let minutes = Math.floor((seconds - (hours * 3600)) / 60).toString().padStart(2, '0'); return `${hours}:${minutes}`; }; @@ -38,18 +38,18 @@ window.addEventListener('load', () => { info: false, dom: "t", stateSave: true, - stateSaveCallback: function(settings, data) { + stateSaveCallback: function (settings, data) { delete data.search; localStorage.setItem(`DataTables_${settings.sInstance}_/tracker`, JSON.stringify(data)); }, - stateLoadCallback: function(settings) { + stateLoadCallback: function (settings) { return JSON.parse(localStorage.getItem(`DataTables_${settings.sInstance}_/tracker`)); }, - footerCallback: function(tfoot, data, start, end, display) { + footerCallback: function (tfoot, data, start, end, display) { if (tfoot) { const activityData = this.api().column('lastActivity:name').data().toArray().filter(x => !isNaN(x)); Array.from(tfoot?.children).find(td => td.classList.contains('last-activity')).innerText = - (activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None'; + (activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None'; } }, columnDefs: [ @@ -123,49 +123,64 @@ window.addEventListener('load', () => { event.preventDefault(); } }); - const tracker = document.getElementById('tracker-wrapper').getAttribute('data-tracker'); - const target_second = document.getElementById('tracker-wrapper').getAttribute('data-second') + 3; + const target_second = parseInt(document.getElementById('tracker-wrapper').getAttribute('data-second')) + 3; + console.log("Target second of refresh: " + target_second); - function getSleepTimeSeconds(){ + function getSleepTimeSeconds() { // -40 % 60 is -40, which is absolutely wrong and should burn var sleepSeconds = (((target_second - new Date().getSeconds()) % 60) + 60) % 60; return sleepSeconds || 60; } + let update_on_view = false; const update = () => { - const target = $("
"); - console.log("Updating Tracker..."); - target.load(location.href, function (response, status) { - if (status === "success") { - target.find(".table").each(function (i, new_table) { - const new_trs = $(new_table).find("tbody>tr"); - const footer_tr = $(new_table).find("tfoot>tr"); - const old_table = tables.eq(i); - const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop(); - const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft(); - old_table.clear(); - if (footer_tr.length) { - $(old_table.table).find("tfoot").html(footer_tr); - } - old_table.rows.add(new_trs); - old_table.draw(); - $(old_table.settings()[0].nScrollBody).scrollTop(topscroll); - $(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll); - }); - $("#multi-stream-link").replaceWith(target.find("#multi-stream-link")); - } else { - console.log("Failed to connect to Server, in order to update Table Data."); - console.log(response); - } - }) - setTimeout(update, getSleepTimeSeconds()*1000); + if (document.hidden) { + console.log("Document reporting as not visible, not updating Tracker..."); + update_on_view = true; + } else { + update_on_view = false; + const target = $("
"); + console.log("Updating Tracker..."); + target.load(location.href, function (response, status) { + if (status === "success") { + target.find(".table").each(function (i, new_table) { + const new_trs = $(new_table).find("tbody>tr"); + const footer_tr = $(new_table).find("tfoot>tr"); + const old_table = tables.eq(i); + const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop(); + const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft(); + old_table.clear(); + if (footer_tr.length) { + $(old_table.table).find("tfoot").html(footer_tr); + } + old_table.rows.add(new_trs); + old_table.draw(); + $(old_table.settings()[0].nScrollBody).scrollTop(topscroll); + $(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll); + }); + $("#multi-stream-link").replaceWith(target.find("#multi-stream-link")); + } else { + console.log("Failed to connect to Server, in order to update Table Data."); + console.log(response); + } + }) + } + updater = setTimeout(update, getSleepTimeSeconds() * 1000); } - setTimeout(update, getSleepTimeSeconds()*1000); + let updater = setTimeout(update, getSleepTimeSeconds() * 1000); window.addEventListener('resize', () => { adjustTableHeight(); tables.draw(); }); + window.addEventListener('visibilitychange', () => { + if (!document.hidden && update_on_view) { + console.log("Page became visible, tracker should be refreshed."); + clearTimeout(updater); + update(); + } + }); + adjustTableHeight(); }); diff --git a/WebHostLib/static/assets/weighted-options.js b/WebHostLib/static/assets/weighted-options.js deleted file mode 100644 index 80f8efd1d7de..000000000000 --- a/WebHostLib/static/assets/weighted-options.js +++ /dev/null @@ -1,1190 +0,0 @@ -window.addEventListener('load', () => { - fetchSettingData().then((data) => { - let settingHash = localStorage.getItem('weighted-settings-hash'); - if (!settingHash) { - // If no hash data has been set before, set it now - settingHash = md5(JSON.stringify(data)); - localStorage.setItem('weighted-settings-hash', settingHash); - localStorage.removeItem('weighted-settings'); - } - - if (settingHash !== md5(JSON.stringify(data))) { - const userMessage = document.getElementById('user-message'); - userMessage.innerText = "Your settings are out of date! Click here to update them! Be aware this will reset " + - "them all to default."; - userMessage.classList.add('visible'); - userMessage.addEventListener('click', resetSettings); - } - - // Page setup - const settings = new WeightedSettings(data); - settings.buildUI(); - settings.updateVisibleGames(); - adjustHeaderWidth(); - - // Event listeners - document.getElementById('export-options').addEventListener('click', () => settings.export()); - document.getElementById('generate-race').addEventListener('click', () => settings.generateGame(true)); - document.getElementById('generate-game').addEventListener('click', () => settings.generateGame()); - - // Name input field - const nameInput = document.getElementById('player-name'); - nameInput.setAttribute('data-type', 'data'); - nameInput.setAttribute('data-setting', 'name'); - nameInput.addEventListener('keyup', (evt) => settings.updateBaseSetting(evt)); - nameInput.value = settings.current.name; - }); -}); - -const resetSettings = () => { - localStorage.removeItem('weighted-settings'); - localStorage.removeItem('weighted-settings-hash') - window.location.reload(); -}; - -const fetchSettingData = () => new Promise((resolve, reject) => { - fetch(new Request(`${window.location.origin}/static/generated/weighted-options.json`)).then((response) => { - try{ response.json().then((jsonObj) => resolve(jsonObj)); } - catch(error){ reject(error); } - }); -}); - -/// The weighted settings across all games. -class WeightedSettings { - // The data from the server describing the types of settings available for - // each game, as a JSON-safe blob. - data; - - // The settings chosen by the user as they'd appear in the YAML file, stored - // to and retrieved from local storage. - current; - - // A record mapping game names to the associated GameSettings. - games; - - constructor(data) { - this.data = data; - this.current = JSON.parse(localStorage.getItem('weighted-settings')); - this.games = Object.keys(this.data.games).map((game) => new GameSettings(this, game)); - if (this.current) { return; } - - this.current = {}; - - // Transfer base options directly - for (let baseOption of Object.keys(this.data.baseOptions)){ - this.current[baseOption] = this.data.baseOptions[baseOption]; - } - - // Set options per game - for (let game of Object.keys(this.data.games)) { - // Initialize game object - this.current[game] = {}; - - // Transfer game settings - for (let gameSetting of Object.keys(this.data.games[game].gameSettings)){ - this.current[game][gameSetting] = {}; - - const setting = this.data.games[game].gameSettings[gameSetting]; - switch(setting.type){ - case 'select': - setting.options.forEach((option) => { - this.current[game][gameSetting][option.value] = - (setting.hasOwnProperty('defaultValue') && setting.defaultValue === option.value) ? 25 : 0; - }); - break; - case 'range': - case 'named_range': - this.current[game][gameSetting]['random'] = 0; - this.current[game][gameSetting]['random-low'] = 0; - this.current[game][gameSetting]['random-middle'] = 0; - this.current[game][gameSetting]['random-high'] = 0; - if (setting.hasOwnProperty('defaultValue')) { - this.current[game][gameSetting][setting.defaultValue] = 25; - } else { - this.current[game][gameSetting][setting.min] = 25; - } - break; - - case 'items-list': - case 'locations-list': - case 'custom-list': - this.current[game][gameSetting] = setting.defaultValue; - break; - - default: - console.error(`Unknown setting type for ${game} setting ${gameSetting}: ${setting.type}`); - } - } - - this.current[game].start_inventory = {}; - this.current[game].exclude_locations = []; - this.current[game].priority_locations = []; - this.current[game].local_items = []; - this.current[game].non_local_items = []; - this.current[game].start_hints = []; - this.current[game].start_location_hints = []; - } - - this.save(); - } - - // Saves the current settings to local storage. - save() { - localStorage.setItem('weighted-settings', JSON.stringify(this.current)); - } - - buildUI() { - // Build the game-choice div - this.#buildGameChoice(); - - const gamesWrapper = document.getElementById('games-wrapper'); - this.games.forEach((game) => { - gamesWrapper.appendChild(game.buildUI()); - }); - } - - #buildGameChoice() { - const gameChoiceDiv = document.getElementById('game-choice'); - const h2 = document.createElement('h2'); - h2.innerText = 'Game Select'; - gameChoiceDiv.appendChild(h2); - - const gameSelectDescription = document.createElement('p'); - gameSelectDescription.classList.add('setting-description'); - gameSelectDescription.innerText = 'Choose which games you might be required to play.'; - gameChoiceDiv.appendChild(gameSelectDescription); - - const hintText = document.createElement('p'); - hintText.classList.add('hint-text'); - hintText.innerText = 'If a game\'s value is greater than zero, you can click it\'s name to jump ' + - 'to that section.' - gameChoiceDiv.appendChild(hintText); - - // Build the game choice table - const table = document.createElement('table'); - const tbody = document.createElement('tbody'); - - Object.keys(this.data.games).forEach((game) => { - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - const span = document.createElement('span'); - span.innerText = game; - span.setAttribute('id', `${game}-game-option`) - tdLeft.appendChild(span); - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.setAttribute('data-type', 'weight'); - range.setAttribute('data-setting', 'game'); - range.setAttribute('data-option', game); - range.value = this.current.game[game]; - range.addEventListener('change', (evt) => { - this.updateBaseSetting(evt); - this.updateVisibleGames(); // Show or hide games based on the new settings - }); - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `game-${game}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - tbody.appendChild(tr); - }); - - table.appendChild(tbody); - gameChoiceDiv.appendChild(table); - } - - // Verifies that `this.settings` meets all the requirements for world - // generation, normalizes it for serialization, and returns the result. - #validateSettings() { - const settings = structuredClone(this.current); - const userMessage = document.getElementById('user-message'); - let errorMessage = null; - - // User must choose a name for their file - if ( - !settings.name || - settings.name.toString().trim().length === 0 || - settings.name.toString().toLowerCase().trim() === 'player' - ) { - userMessage.innerText = 'You forgot to set your player name at the top of the page!'; - userMessage.classList.add('visible'); - userMessage.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - return; - } - - // Clean up the settings output - Object.keys(settings.game).forEach((game) => { - // Remove any disabled games - if (settings.game[game] === 0) { - delete settings.game[game]; - delete settings[game]; - return; - } - - Object.keys(settings[game]).forEach((setting) => { - // Remove any disabled options - Object.keys(settings[game][setting]).forEach((option) => { - if (settings[game][setting][option] === 0) { - delete settings[game][setting][option]; - } - }); - - if ( - Object.keys(settings[game][setting]).length === 0 && - !Array.isArray(settings[game][setting]) && - setting !== 'start_inventory' - ) { - errorMessage = `${game} // ${setting} has no values above zero!`; - } - - // Remove weights from options with only one possibility - if ( - Object.keys(settings[game][setting]).length === 1 && - !Array.isArray(settings[game][setting]) && - setting !== 'start_inventory' - ) { - settings[game][setting] = Object.keys(settings[game][setting])[0]; - } - - // Remove empty arrays - else if ( - ['exclude_locations', 'priority_locations', 'local_items', - 'non_local_items', 'start_hints', 'start_location_hints'].includes(setting) && - settings[game][setting].length === 0 - ) { - delete settings[game][setting]; - } - - // Remove empty start inventory - else if ( - setting === 'start_inventory' && - Object.keys(settings[game]['start_inventory']).length === 0 - ) { - delete settings[game]['start_inventory']; - } - }); - }); - - if (Object.keys(settings.game).length === 0) { - errorMessage = 'You have not chosen a game to play!'; - } - - // Remove weights if there is only one game - else if (Object.keys(settings.game).length === 1) { - settings.game = Object.keys(settings.game)[0]; - } - - // If an error occurred, alert the user and do not export the file - if (errorMessage) { - userMessage.innerText = errorMessage; - userMessage.classList.add('visible'); - userMessage.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - return; - } - - // If no error occurred, hide the user message if it is visible - userMessage.classList.remove('visible'); - return settings; - } - - updateVisibleGames() { - Object.entries(this.current.game).forEach(([game, weight]) => { - const gameDiv = document.getElementById(`${game}-div`); - const gameOption = document.getElementById(`${game}-game-option`); - if (parseInt(weight, 10) > 0) { - gameDiv.classList.remove('invisible'); - gameOption.classList.add('jump-link'); - gameOption.addEventListener('click', () => { - const gameDiv = document.getElementById(`${game}-div`); - if (gameDiv.classList.contains('invisible')) { return; } - gameDiv.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - }); - } else { - gameDiv.classList.add('invisible'); - gameOption.classList.remove('jump-link'); - } - }); - } - - updateBaseSetting(event) { - const setting = event.target.getAttribute('data-setting'); - const option = event.target.getAttribute('data-option'); - const type = event.target.getAttribute('data-type'); - - switch(type){ - case 'weight': - this.current[setting][option] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10); - document.getElementById(`${setting}-${option}`).innerText = event.target.value; - break; - case 'data': - this.current[setting] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10); - break; - } - - this.save(); - } - - export() { - const settings = this.#validateSettings(); - if (!settings) { return; } - - const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); - download(`${document.getElementById('player-name').value}.yaml`, yamlText); - } - - generateGame(raceMode = false) { - const settings = this.#validateSettings(); - if (!settings) { return; } - - axios.post('/api/generate', { - weights: { player: JSON.stringify(settings) }, - presetData: { player: JSON.stringify(settings) }, - playerCount: 1, - spoiler: 3, - race: raceMode ? '1' : '0', - }).then((response) => { - window.location.href = response.data.url; - }).catch((error) => { - const userMessage = document.getElementById('user-message'); - userMessage.innerText = 'Something went wrong and your game could not be generated.'; - if (error.response.data.text) { - userMessage.innerText += ' ' + error.response.data.text; - } - userMessage.classList.add('visible'); - userMessage.scrollIntoView({ - behavior: 'smooth', - block: 'start', - }); - console.error(error); - }); - } -} - -// Settings for an individual game. -class GameSettings { - // The WeightedSettings that contains this game's settings. Used to save - // settings after editing. - #allSettings; - - // The name of this game. - name; - - // The data from the server describing the types of settings available for - // this game, as a JSON-safe blob. - get data() { - return this.#allSettings.data.games[this.name]; - } - - // The settings chosen by the user as they'd appear in the YAML file, stored - // to and retrieved from local storage. - get current() { - return this.#allSettings.current[this.name]; - } - - constructor(allSettings, name) { - this.#allSettings = allSettings; - this.name = name; - } - - // Builds and returns the settings UI for this game. - buildUI() { - // Create game div, invisible by default - const gameDiv = document.createElement('div'); - gameDiv.setAttribute('id', `${this.name}-div`); - gameDiv.classList.add('game-div'); - gameDiv.classList.add('invisible'); - - const gameHeader = document.createElement('h2'); - gameHeader.innerText = this.name; - gameDiv.appendChild(gameHeader); - - const collapseButton = document.createElement('a'); - collapseButton.innerText = '(Collapse)'; - gameDiv.appendChild(collapseButton); - - const expandButton = document.createElement('a'); - expandButton.innerText = '(Expand)'; - expandButton.classList.add('invisible'); - gameDiv.appendChild(expandButton); - - // Sort items and locations alphabetically. - this.data.gameItems.sort(); - this.data.gameLocations.sort(); - - const weightedSettingsDiv = this.#buildWeightedSettingsDiv(); - gameDiv.appendChild(weightedSettingsDiv); - - const itemPoolDiv = this.#buildItemPoolDiv(); - gameDiv.appendChild(itemPoolDiv); - - const hintsDiv = this.#buildHintsDiv(); - gameDiv.appendChild(hintsDiv); - - const locationsDiv = this.#buildPriorityExclusionDiv(); - gameDiv.appendChild(locationsDiv); - - collapseButton.addEventListener('click', () => { - collapseButton.classList.add('invisible'); - weightedSettingsDiv.classList.add('invisible'); - itemPoolDiv.classList.add('invisible'); - hintsDiv.classList.add('invisible'); - locationsDiv.classList.add('invisible'); - expandButton.classList.remove('invisible'); - }); - - expandButton.addEventListener('click', () => { - collapseButton.classList.remove('invisible'); - weightedSettingsDiv.classList.remove('invisible'); - itemPoolDiv.classList.remove('invisible'); - hintsDiv.classList.remove('invisible'); - locationsDiv.classList.remove('invisible'); - expandButton.classList.add('invisible'); - }); - - return gameDiv; - } - - #buildWeightedSettingsDiv() { - const settingsWrapper = document.createElement('div'); - settingsWrapper.classList.add('settings-wrapper'); - - Object.keys(this.data.gameSettings).forEach((settingName) => { - const setting = this.data.gameSettings[settingName]; - const settingWrapper = document.createElement('div'); - settingWrapper.classList.add('setting-wrapper'); - - const settingNameHeader = document.createElement('h4'); - settingNameHeader.innerText = setting.displayName; - settingWrapper.appendChild(settingNameHeader); - - const settingDescription = document.createElement('p'); - settingDescription.classList.add('setting-description'); - settingDescription.innerText = setting.description.replace(/(\n)/g, ' '); - settingWrapper.appendChild(settingDescription); - - switch(setting.type){ - case 'select': - const optionTable = document.createElement('table'); - const tbody = document.createElement('tbody'); - - // Add a weight range for each option - setting.options.forEach((option) => { - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - tdLeft.innerText = option.name; - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option.value); - range.setAttribute('data-type', setting.type); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[settingName][option.value]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option.value}`); - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - - tbody.appendChild(tr); - }); - - optionTable.appendChild(tbody); - settingWrapper.appendChild(optionTable); - break; - - case 'range': - case 'named_range': - const rangeTable = document.createElement('table'); - const rangeTbody = document.createElement('tbody'); - - const hintText = document.createElement('p'); - hintText.classList.add('hint-text'); - hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' + - `below, then press the "Add" button to add a weight for it.

Accepted values:
` + - `Normal range: ${setting.min} - ${setting.max}`; - - const acceptedValuesOutsideRange = []; - if (setting.hasOwnProperty('value_names')) { - Object.keys(setting.value_names).forEach((specialName) => { - if ( - (setting.value_names[specialName] < setting.min) || - (setting.value_names[specialName] > setting.max) - ) { - hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`; - acceptedValuesOutsideRange.push(setting.value_names[specialName]); - } - }); - - hintText.innerHTML += '

Certain values have special meaning:'; - Object.keys(setting.value_names).forEach((specialName) => { - hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`; - }); - } - - settingWrapper.appendChild(hintText); - - const addOptionDiv = document.createElement('div'); - addOptionDiv.classList.add('add-option-div'); - const optionInput = document.createElement('input'); - optionInput.setAttribute('id', `${this.name}-${settingName}-option`); - let placeholderText = `${setting.min} - ${setting.max}`; - acceptedValuesOutsideRange.forEach((aVal) => placeholderText += `, ${aVal}`); - optionInput.setAttribute('placeholder', placeholderText); - addOptionDiv.appendChild(optionInput); - const addOptionButton = document.createElement('button'); - addOptionButton.innerText = 'Add'; - addOptionDiv.appendChild(addOptionButton); - settingWrapper.appendChild(addOptionDiv); - optionInput.addEventListener('keydown', (evt) => { - if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); } - }); - - addOptionButton.addEventListener('click', () => { - const optionInput = document.getElementById(`${this.name}-${settingName}-option`); - let option = optionInput.value; - if (!option || !option.trim()) { return; } - option = parseInt(option, 10); - - let optionAcceptable = false; - if ((option >= setting.min) && (option <= setting.max)) { - optionAcceptable = true; - } - if (setting.hasOwnProperty('value_names') && Object.values(setting.value_names).includes(option)){ - optionAcceptable = true; - } - if (!optionAcceptable) { return; } - - optionInput.value = ''; - if (document.getElementById(`${this.name}-${settingName}-${option}-range`)) { return; } - - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - tdLeft.innerText = option; - if ( - setting.hasOwnProperty('value_names') && - Object.values(setting.value_names).includes(parseInt(option, 10)) - ) { - const optionName = Object.keys(setting.value_names).find( - (key) => setting.value_names[key] === parseInt(option, 10) - ); - tdLeft.innerText += ` [${optionName}]`; - } - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('id', `${this.name}-${settingName}-${option}-range`); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[settingName][parseInt(option, 10)]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - - const tdDelete = document.createElement('td'); - tdDelete.classList.add('td-delete'); - const deleteButton = document.createElement('span'); - deleteButton.classList.add('range-option-delete'); - deleteButton.innerText = 'âŒ'; - deleteButton.addEventListener('click', () => { - range.value = 0; - range.dispatchEvent(new Event('change')); - rangeTbody.removeChild(tr); - }); - tdDelete.appendChild(deleteButton); - tr.appendChild(tdDelete); - - rangeTbody.appendChild(tr); - - // Save new option to settings - range.dispatchEvent(new Event('change')); - }); - - Object.keys(this.current[settingName]).forEach((option) => { - // These options are statically generated below, and should always appear even if they are deleted - // from localStorage - if (['random', 'random-low', 'random-middle', 'random-high'].includes(option)) { return; } - - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - tdLeft.innerText = option; - if ( - setting.hasOwnProperty('value_names') && - Object.values(setting.value_names).includes(parseInt(option, 10)) - ) { - const optionName = Object.keys(setting.value_names).find( - (key) => setting.value_names[key] === parseInt(option, 10) - ); - tdLeft.innerText += ` [${optionName}]`; - } - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('id', `${this.name}-${settingName}-${option}-range`); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[settingName][parseInt(option, 10)]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - - const tdDelete = document.createElement('td'); - tdDelete.classList.add('td-delete'); - const deleteButton = document.createElement('span'); - deleteButton.classList.add('range-option-delete'); - deleteButton.innerText = 'âŒ'; - deleteButton.addEventListener('click', () => { - range.value = 0; - const changeEvent = new Event('change'); - changeEvent.action = 'rangeDelete'; - range.dispatchEvent(changeEvent); - rangeTbody.removeChild(tr); - }); - tdDelete.appendChild(deleteButton); - tr.appendChild(tdDelete); - - rangeTbody.appendChild(tr); - }); - - ['random', 'random-low', 'random-middle', 'random-high'].forEach((option) => { - const tr = document.createElement('tr'); - const tdLeft = document.createElement('td'); - tdLeft.classList.add('td-left'); - switch(option){ - case 'random': - tdLeft.innerText = 'Random'; - break; - case 'random-low': - tdLeft.innerText = "Random (Low)"; - break; - case 'random-middle': - tdLeft.innerText = 'Random (Middle)'; - break; - case 'random-high': - tdLeft.innerText = "Random (High)"; - break; - } - tr.appendChild(tdLeft); - - const tdMiddle = document.createElement('td'); - tdMiddle.classList.add('td-middle'); - const range = document.createElement('input'); - range.setAttribute('type', 'range'); - range.setAttribute('id', `${this.name}-${settingName}-${option}-range`); - range.setAttribute('data-game', this.name); - range.setAttribute('data-setting', settingName); - range.setAttribute('data-option', option); - range.setAttribute('min', 0); - range.setAttribute('max', 50); - range.addEventListener('change', (evt) => this.#updateRangeSetting(evt)); - range.value = this.current[settingName][option]; - tdMiddle.appendChild(range); - tr.appendChild(tdMiddle); - - const tdRight = document.createElement('td'); - tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`) - tdRight.classList.add('td-right'); - tdRight.innerText = range.value; - tr.appendChild(tdRight); - rangeTbody.appendChild(tr); - }); - - rangeTable.appendChild(rangeTbody); - settingWrapper.appendChild(rangeTable); - break; - - case 'items-list': - const itemsList = this.#buildItemsDiv(settingName); - settingWrapper.appendChild(itemsList); - break; - - case 'locations-list': - const locationsList = this.#buildLocationsDiv(settingName); - settingWrapper.appendChild(locationsList); - break; - - case 'custom-list': - const customList = this.#buildListDiv(settingName, this.data.gameSettings[settingName].options); - settingWrapper.appendChild(customList); - break; - - default: - console.error(`Unknown setting type for ${this.name} setting ${settingName}: ${setting.type}`); - return; - } - - settingsWrapper.appendChild(settingWrapper); - }); - - return settingsWrapper; - } - - #buildItemPoolDiv() { - const itemsDiv = document.createElement('div'); - itemsDiv.classList.add('items-div'); - - const itemsDivHeader = document.createElement('h3'); - itemsDivHeader.innerText = 'Item Pool'; - itemsDiv.appendChild(itemsDivHeader); - - const itemsDescription = document.createElement('p'); - itemsDescription.classList.add('setting-description'); - itemsDescription.innerText = 'Choose if you would like to start with items, or control if they are placed in ' + - 'your seed or someone else\'s.'; - itemsDiv.appendChild(itemsDescription); - - const itemsHint = document.createElement('p'); - itemsHint.classList.add('hint-text'); - itemsHint.innerText = 'Drag and drop items from one box to another.'; - itemsDiv.appendChild(itemsHint); - - const itemsWrapper = document.createElement('div'); - itemsWrapper.classList.add('items-wrapper'); - - const itemDragoverHandler = (evt) => evt.preventDefault(); - const itemDropHandler = (evt) => this.#itemDropHandler(evt); - - // Create container divs for each category - const availableItemsWrapper = document.createElement('div'); - availableItemsWrapper.classList.add('item-set-wrapper'); - availableItemsWrapper.innerText = 'Available Items'; - const availableItems = document.createElement('div'); - availableItems.classList.add('item-container'); - availableItems.setAttribute('id', `${this.name}-available_items`); - availableItems.addEventListener('dragover', itemDragoverHandler); - availableItems.addEventListener('drop', itemDropHandler); - - const startInventoryWrapper = document.createElement('div'); - startInventoryWrapper.classList.add('item-set-wrapper'); - startInventoryWrapper.innerText = 'Start Inventory'; - const startInventory = document.createElement('div'); - startInventory.classList.add('item-container'); - startInventory.setAttribute('id', `${this.name}-start_inventory`); - startInventory.setAttribute('data-setting', 'start_inventory'); - startInventory.addEventListener('dragover', itemDragoverHandler); - startInventory.addEventListener('drop', itemDropHandler); - - const localItemsWrapper = document.createElement('div'); - localItemsWrapper.classList.add('item-set-wrapper'); - localItemsWrapper.innerText = 'Local Items'; - const localItems = document.createElement('div'); - localItems.classList.add('item-container'); - localItems.setAttribute('id', `${this.name}-local_items`); - localItems.setAttribute('data-setting', 'local_items') - localItems.addEventListener('dragover', itemDragoverHandler); - localItems.addEventListener('drop', itemDropHandler); - - const nonLocalItemsWrapper = document.createElement('div'); - nonLocalItemsWrapper.classList.add('item-set-wrapper'); - nonLocalItemsWrapper.innerText = 'Non-Local Items'; - const nonLocalItems = document.createElement('div'); - nonLocalItems.classList.add('item-container'); - nonLocalItems.setAttribute('id', `${this.name}-non_local_items`); - nonLocalItems.setAttribute('data-setting', 'non_local_items'); - nonLocalItems.addEventListener('dragover', itemDragoverHandler); - nonLocalItems.addEventListener('drop', itemDropHandler); - - // Populate the divs - this.data.gameItems.forEach((item) => { - if (Object.keys(this.current.start_inventory).includes(item)){ - const itemDiv = this.#buildItemQtyDiv(item); - itemDiv.setAttribute('data-setting', 'start_inventory'); - startInventory.appendChild(itemDiv); - } else if (this.current.local_items.includes(item)) { - const itemDiv = this.#buildItemDiv(item); - itemDiv.setAttribute('data-setting', 'local_items'); - localItems.appendChild(itemDiv); - } else if (this.current.non_local_items.includes(item)) { - const itemDiv = this.#buildItemDiv(item); - itemDiv.setAttribute('data-setting', 'non_local_items'); - nonLocalItems.appendChild(itemDiv); - } else { - const itemDiv = this.#buildItemDiv(item); - availableItems.appendChild(itemDiv); - } - }); - - availableItemsWrapper.appendChild(availableItems); - startInventoryWrapper.appendChild(startInventory); - localItemsWrapper.appendChild(localItems); - nonLocalItemsWrapper.appendChild(nonLocalItems); - itemsWrapper.appendChild(availableItemsWrapper); - itemsWrapper.appendChild(startInventoryWrapper); - itemsWrapper.appendChild(localItemsWrapper); - itemsWrapper.appendChild(nonLocalItemsWrapper); - itemsDiv.appendChild(itemsWrapper); - return itemsDiv; - } - - #buildItemDiv(item) { - const itemDiv = document.createElement('div'); - itemDiv.classList.add('item-div'); - itemDiv.setAttribute('id', `${this.name}-${item}`); - itemDiv.setAttribute('data-game', this.name); - itemDiv.setAttribute('data-item', item); - itemDiv.setAttribute('draggable', 'true'); - itemDiv.innerText = item; - itemDiv.addEventListener('dragstart', (evt) => { - evt.dataTransfer.setData('text/plain', itemDiv.getAttribute('id')); - }); - return itemDiv; - } - - #buildItemQtyDiv(item) { - const itemQtyDiv = document.createElement('div'); - itemQtyDiv.classList.add('item-qty-div'); - itemQtyDiv.setAttribute('id', `${this.name}-${item}`); - itemQtyDiv.setAttribute('data-game', this.name); - itemQtyDiv.setAttribute('data-item', item); - itemQtyDiv.setAttribute('draggable', 'true'); - itemQtyDiv.innerText = item; - - const inputWrapper = document.createElement('div'); - inputWrapper.classList.add('item-qty-input-wrapper') - - const itemQty = document.createElement('input'); - itemQty.setAttribute('value', this.current.start_inventory.hasOwnProperty(item) ? - this.current.start_inventory[item] : '1'); - itemQty.setAttribute('data-game', this.name); - itemQty.setAttribute('data-setting', 'start_inventory'); - itemQty.setAttribute('data-option', item); - itemQty.setAttribute('maxlength', '3'); - itemQty.addEventListener('keyup', (evt) => { - evt.target.value = isNaN(parseInt(evt.target.value)) ? 0 : parseInt(evt.target.value); - this.#updateItemSetting(evt); - }); - inputWrapper.appendChild(itemQty); - itemQtyDiv.appendChild(inputWrapper); - - itemQtyDiv.addEventListener('dragstart', (evt) => { - evt.dataTransfer.setData('text/plain', itemQtyDiv.getAttribute('id')); - }); - return itemQtyDiv; - } - - #itemDropHandler(evt) { - evt.preventDefault(); - const sourceId = evt.dataTransfer.getData('text/plain'); - const sourceDiv = document.getElementById(sourceId); - - const item = sourceDiv.getAttribute('data-item'); - - const oldSetting = sourceDiv.hasAttribute('data-setting') ? sourceDiv.getAttribute('data-setting') : null; - const newSetting = evt.target.hasAttribute('data-setting') ? evt.target.getAttribute('data-setting') : null; - - const itemDiv = newSetting === 'start_inventory' ? this.#buildItemQtyDiv(item) : this.#buildItemDiv(item); - - if (oldSetting) { - if (oldSetting === 'start_inventory') { - if (this.current[oldSetting].hasOwnProperty(item)) { - delete this.current[oldSetting][item]; - } - } else { - if (this.current[oldSetting].includes(item)) { - this.current[oldSetting].splice(this.current[oldSetting].indexOf(item), 1); - } - } - } - - if (newSetting) { - itemDiv.setAttribute('data-setting', newSetting); - document.getElementById(`${this.name}-${newSetting}`).appendChild(itemDiv); - if (newSetting === 'start_inventory') { - this.current[newSetting][item] = 1; - } else { - if (!this.current[newSetting].includes(item)){ - this.current[newSetting].push(item); - } - } - } else { - // No setting was assigned, this item has been removed from the settings - document.getElementById(`${this.name}-available_items`).appendChild(itemDiv); - } - - // Remove the source drag object - sourceDiv.parentElement.removeChild(sourceDiv); - - // Save the updated settings - this.save(); - } - - #buildHintsDiv() { - const hintsDiv = document.createElement('div'); - hintsDiv.classList.add('hints-div'); - const hintsHeader = document.createElement('h3'); - hintsHeader.innerText = 'Item & Location Hints'; - hintsDiv.appendChild(hintsHeader); - const hintsDescription = document.createElement('p'); - hintsDescription.classList.add('setting-description'); - hintsDescription.innerText = 'Choose any items or locations to begin the game with the knowledge of where those ' + - ' items are, or what those locations contain.'; - hintsDiv.appendChild(hintsDescription); - - const itemHintsContainer = document.createElement('div'); - itemHintsContainer.classList.add('hints-container'); - - // Item Hints - const itemHintsWrapper = document.createElement('div'); - itemHintsWrapper.classList.add('hints-wrapper'); - itemHintsWrapper.innerText = 'Starting Item Hints'; - - const itemHintsDiv = this.#buildItemsDiv('start_hints'); - itemHintsWrapper.appendChild(itemHintsDiv); - itemHintsContainer.appendChild(itemHintsWrapper); - - // Starting Location Hints - const locationHintsWrapper = document.createElement('div'); - locationHintsWrapper.classList.add('hints-wrapper'); - locationHintsWrapper.innerText = 'Starting Location Hints'; - - const locationHintsDiv = this.#buildLocationsDiv('start_location_hints'); - locationHintsWrapper.appendChild(locationHintsDiv); - itemHintsContainer.appendChild(locationHintsWrapper); - - hintsDiv.appendChild(itemHintsContainer); - return hintsDiv; - } - - #buildPriorityExclusionDiv() { - const locationsDiv = document.createElement('div'); - locationsDiv.classList.add('locations-div'); - const locationsHeader = document.createElement('h3'); - locationsHeader.innerText = 'Priority & Exclusion Locations'; - locationsDiv.appendChild(locationsHeader); - const locationsDescription = document.createElement('p'); - locationsDescription.classList.add('setting-description'); - locationsDescription.innerText = 'Priority locations guarantee a progression item will be placed there while ' + - 'excluded locations will not contain progression or useful items.'; - locationsDiv.appendChild(locationsDescription); - - const locationsContainer = document.createElement('div'); - locationsContainer.classList.add('locations-container'); - - // Priority Locations - const priorityLocationsWrapper = document.createElement('div'); - priorityLocationsWrapper.classList.add('locations-wrapper'); - priorityLocationsWrapper.innerText = 'Priority Locations'; - - const priorityLocationsDiv = this.#buildLocationsDiv('priority_locations'); - priorityLocationsWrapper.appendChild(priorityLocationsDiv); - locationsContainer.appendChild(priorityLocationsWrapper); - - // Exclude Locations - const excludeLocationsWrapper = document.createElement('div'); - excludeLocationsWrapper.classList.add('locations-wrapper'); - excludeLocationsWrapper.innerText = 'Exclude Locations'; - - const excludeLocationsDiv = this.#buildLocationsDiv('exclude_locations'); - excludeLocationsWrapper.appendChild(excludeLocationsDiv); - locationsContainer.appendChild(excludeLocationsWrapper); - - locationsDiv.appendChild(locationsContainer); - return locationsDiv; - } - - // Builds a div for a setting whose value is a list of locations. - #buildLocationsDiv(setting) { - return this.#buildListDiv(setting, this.data.gameLocations, { - groups: this.data.gameLocationGroups, - descriptions: this.data.gameLocationDescriptions, - }); - } - - // Builds a div for a setting whose value is a list of items. - #buildItemsDiv(setting) { - return this.#buildListDiv(setting, this.data.gameItems, { - groups: this.data.gameItemGroups, - descriptions: this.data.gameItemDescriptions - }); - } - - // Builds a div for a setting named `setting` with a list value that can - // contain `items`. - // - // The `groups` option can be a list of additional options for this list - // (usually `item_name_groups` or `location_name_groups`) that are displayed - // in a special section at the top of the list. - // - // The `descriptions` option can be a map from item names or group names to - // descriptions for the user's benefit. - #buildListDiv(setting, items, {groups = [], descriptions = {}} = {}) { - const div = document.createElement('div'); - div.classList.add('simple-list'); - - groups.forEach((group) => { - const row = this.#addListRow(setting, group, descriptions[group]); - div.appendChild(row); - }); - - if (groups.length > 0) { - div.appendChild(document.createElement('hr')); - } - - items.forEach((item) => { - const row = this.#addListRow(setting, item, descriptions[item]); - div.appendChild(row); - }); - - return div; - } - - // Builds and returns a row for a list of checkboxes. - // - // If `help` is passed, it's displayed as a help tooltip for this list item. - #addListRow(setting, item, help = undefined) { - const row = document.createElement('div'); - row.classList.add('list-row'); - - const label = document.createElement('label'); - label.setAttribute('for', `${this.name}-${setting}-${item}`); - - const checkbox = document.createElement('input'); - checkbox.setAttribute('type', 'checkbox'); - checkbox.setAttribute('id', `${this.name}-${setting}-${item}`); - checkbox.setAttribute('data-game', this.name); - checkbox.setAttribute('data-setting', setting); - checkbox.setAttribute('data-option', item); - if (this.current[setting].includes(item)) { - checkbox.setAttribute('checked', '1'); - } - checkbox.addEventListener('change', (evt) => this.#updateListSetting(evt)); - label.appendChild(checkbox); - - const name = document.createElement('span'); - name.innerText = item; - - if (help) { - const helpSpan = document.createElement('span'); - helpSpan.classList.add('interactive'); - helpSpan.setAttribute('data-tooltip', help); - helpSpan.innerText = '(?)'; - name.innerText += ' '; - name.appendChild(helpSpan); - - // Put the first 7 tooltips below their rows. CSS tooltips in scrolling - // containers can't be visible outside those containers, so this helps - // ensure they won't be pushed out the top. - if (helpSpan.parentNode.childNodes.length < 7) { - helpSpan.classList.add('tooltip-bottom'); - } - } - - label.appendChild(name); - - row.appendChild(label); - return row; - } - - #updateRangeSetting(evt) { - const setting = evt.target.getAttribute('data-setting'); - const option = evt.target.getAttribute('data-option'); - document.getElementById(`${this.name}-${setting}-${option}`).innerText = evt.target.value; - if (evt.action && evt.action === 'rangeDelete') { - delete this.current[setting][option]; - } else { - this.current[setting][option] = parseInt(evt.target.value, 10); - } - this.save(); - } - - #updateListSetting(evt) { - const setting = evt.target.getAttribute('data-setting'); - const option = evt.target.getAttribute('data-option'); - - if (evt.target.checked) { - // If the option is to be enabled and it is already enabled, do nothing - if (this.current[setting].includes(option)) { return; } - - this.current[setting].push(option); - } else { - // If the option is to be disabled and it is already disabled, do nothing - if (!this.current[setting].includes(option)) { return; } - - this.current[setting].splice(this.current[setting].indexOf(option), 1); - } - this.save(); - } - - #updateItemSetting(evt) { - const setting = evt.target.getAttribute('data-setting'); - const option = evt.target.getAttribute('data-option'); - if (setting === 'start_inventory') { - this.current[setting][option] = evt.target.value.trim() ? parseInt(evt.target.value) : 0; - } else { - this.current[setting][option] = isNaN(evt.target.value) ? - evt.target.value : parseInt(evt.target.value, 10); - } - this.save(); - } - - // Saves the current settings to local storage. - save() { - this.#allSettings.save(); - } -} - -/** Create an anchor and trigger a download of a text file. */ -const download = (filename, text) => { - const downloadLink = document.createElement('a'); - downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text)) - downloadLink.setAttribute('download', filename); - downloadLink.style.display = 'none'; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); -}; diff --git a/WebHostLib/static/assets/weightedOptions.js b/WebHostLib/static/assets/weightedOptions.js new file mode 100644 index 000000000000..0417ab174b0e --- /dev/null +++ b/WebHostLib/static/assets/weightedOptions.js @@ -0,0 +1,223 @@ +let deletedOptions = {}; + +window.addEventListener('load', () => { + const worldName = document.querySelector('#weighted-options').getAttribute('data-game'); + + // Generic change listener. Detecting unique qualities and acting on them here reduces initial JS initialisation time + // and handles dynamically created elements + document.addEventListener('change', (evt) => { + // Handle updates to range inputs + if (evt.target.type === 'range') { + // Update span containing range value. All ranges have a corresponding `{rangeId}-value` span + document.getElementById(`${evt.target.id}-value`).innerText = evt.target.value; + + // If the changed option was the name of a game, determine whether to show or hide that game's div + if (evt.target.id.startsWith('game||')) { + const gameName = evt.target.id.split('||')[1]; + const gameDiv = document.getElementById(`${gameName}-container`); + if (evt.target.value > 0) { + gameDiv.classList.remove('hidden'); + } else { + gameDiv.classList.add('hidden'); + } + } + } + }); + + // Generic click listener + document.addEventListener('click', (evt) => { + // Handle creating new rows for Range options + if (evt.target.classList.contains('add-range-option-button')) { + const optionName = evt.target.getAttribute('data-option'); + addRangeRow(optionName); + } + + // Handle deleting range rows + if (evt.target.classList.contains('range-option-delete')) { + const targetRow = document.querySelector(`tr[data-row="${evt.target.getAttribute('data-target')}"]`); + setDeletedOption( + targetRow.getAttribute('data-option-name'), + targetRow.getAttribute('data-value'), + ); + targetRow.parentElement.removeChild(targetRow); + } + }); + + // Listen for enter presses on inputs intended to add range rows + document.addEventListener('keydown', (evt) => { + if (evt.key === 'Enter') { + evt.preventDefault(); + } + + if (evt.key === 'Enter' && evt.target.classList.contains('range-option-value')) { + const optionName = evt.target.getAttribute('data-option'); + addRangeRow(optionName); + } + }); + + // Detect form submission + document.getElementById('weighted-options-form').addEventListener('submit', (evt) => { + // Save data to localStorage + const weightedOptions = {}; + document.querySelectorAll('input[name]').forEach((input) => { + const keys = input.getAttribute('name').split('||'); + + // Determine keys + const optionName = keys[0] ?? null; + const subOption = keys[1] ?? null; + + // Ensure keys exist + if (!weightedOptions[optionName]) { weightedOptions[optionName] = {}; } + if (subOption && !weightedOptions[optionName][subOption]) { + weightedOptions[optionName][subOption] = null; + } + + if (subOption) { return weightedOptions[optionName][subOption] = determineValue(input); } + if (optionName) { return weightedOptions[optionName] = determineValue(input); } + }); + + localStorage.setItem(`${worldName}-weights`, JSON.stringify(weightedOptions)); + localStorage.setItem(`${worldName}-deletedOptions`, JSON.stringify(deletedOptions)); + }); + + // Remove all deleted values as specified by localStorage + deletedOptions = JSON.parse(localStorage.getItem(`${worldName}-deletedOptions`) || '{}'); + Object.keys(deletedOptions).forEach((optionName) => { + deletedOptions[optionName].forEach((value) => { + const targetRow = document.querySelector(`tr[data-row="${value}-row"]`); + targetRow.parentElement.removeChild(targetRow); + }); + }); + + // Populate all settings from localStorage on page initialisation + const previousSettingsJson = localStorage.getItem(`${worldName}-weights`); + if (previousSettingsJson) { + const previousSettings = JSON.parse(previousSettingsJson); + Object.keys(previousSettings).forEach((option) => { + if (typeof previousSettings[option] === 'string') { + return document.querySelector(`input[name="${option}"]`).value = previousSettings[option]; + } + + Object.keys(previousSettings[option]).forEach((value) => { + const input = document.querySelector(`input[name="${option}||${value}"]`); + if (!input?.type) { + return console.error(`Unable to populate option with name ${option}||${value}.`); + } + + switch (input.type) { + case 'checkbox': + input.checked = (parseInt(previousSettings[option][value], 10) === 1); + break; + case 'range': + input.value = parseInt(previousSettings[option][value], 10); + break; + case 'number': + input.value = previousSettings[option][value].toString(); + break; + default: + console.error(`Found unsupported input type: ${input.type}`); + } + }); + }); + } +}); + +const addRangeRow = (optionName) => { + const inputQuery = `input[type=number][data-option="${optionName}"].range-option-value`; + const inputTarget = document.querySelector(inputQuery); + const newValue = inputTarget.value; + if (!/^-?\d+$/.test(newValue)) { + alert('Range values must be a positive or negative integer!'); + return; + } + inputTarget.value = ''; + const tBody = document.querySelector(`table[data-option="${optionName}"].range-rows tbody`); + const tr = document.createElement('tr'); + tr.setAttribute('data-row', `${optionName}-${newValue}-row`); + tr.setAttribute('data-option-name', optionName); + tr.setAttribute('data-value', newValue); + const tdLeft = document.createElement('td'); + tdLeft.classList.add('td-left'); + const label = document.createElement('label'); + label.setAttribute('for', `${optionName}||${newValue}`); + label.innerText = newValue.toString(); + tdLeft.appendChild(label); + tr.appendChild(tdLeft); + const tdMiddle = document.createElement('td'); + tdMiddle.classList.add('td-middle'); + const range = document.createElement('input'); + range.setAttribute('type', 'range'); + range.setAttribute('min', '0'); + range.setAttribute('max', '50'); + range.setAttribute('value', '0'); + range.setAttribute('id', `${optionName}||${newValue}`); + range.setAttribute('name', `${optionName}||${newValue}`); + tdMiddle.appendChild(range); + tr.appendChild(tdMiddle); + const tdRight = document.createElement('td'); + tdRight.classList.add('td-right'); + const valueSpan = document.createElement('span'); + valueSpan.setAttribute('id', `${optionName}||${newValue}-value`); + valueSpan.innerText = '0'; + tdRight.appendChild(valueSpan); + tr.appendChild(tdRight); + const tdDelete = document.createElement('td'); + const deleteSpan = document.createElement('span'); + deleteSpan.classList.add('range-option-delete'); + deleteSpan.classList.add('js-required'); + deleteSpan.setAttribute('data-target', `${optionName}-${newValue}-row`); + deleteSpan.innerText = 'âŒ'; + tdDelete.appendChild(deleteSpan); + tr.appendChild(tdDelete); + tBody.appendChild(tr); + + // Remove this option from the set of deleted options if it exists + unsetDeletedOption(optionName, newValue); +}; + +/** + * Determines the value of an input element, or returns a 1 or 0 if the element is a checkbox + * + * @param {object} input - The input element. + * @returns {number} The value of the input element. + */ +const determineValue = (input) => { + switch (input.type) { + case 'checkbox': + return (input.checked ? 1 : 0); + case 'range': + return parseInt(input.value, 10); + default: + return input.value; + } +}; + +/** + * Sets the deleted option value for a given world and option name. + * If the world or option does not exist, it creates the necessary entries. + * + * @param {string} optionName - The name of the option. + * @param {*} value - The value to be set for the deleted option. + * @returns {void} + */ +const setDeletedOption = (optionName, value) => { + deletedOptions[optionName] = deletedOptions[optionName] || []; + deletedOptions[optionName].push(`${optionName}-${value}`); +}; + +/** + * Removes a specific value from the deletedOptions object. + * + * @param {string} optionName - The name of the option. + * @param {*} value - The value to be removed + * @returns {void} + */ +const unsetDeletedOption = (optionName, value) => { + if (!deletedOptions.hasOwnProperty(optionName)) { return; } + if (deletedOptions[optionName].includes(`${optionName}-${value}`)) { + deletedOptions[optionName].splice(deletedOptions[optionName].indexOf(`${optionName}-${value}`), 1); + } + if (deletedOptions[optionName].length === 0) { + delete deletedOptions[optionName]; + } +}; diff --git a/WebHostLib/static/robots_file.txt b/WebHostLib/static/robots_file.txt new file mode 100644 index 000000000000..770ae26c1985 --- /dev/null +++ b/WebHostLib/static/robots_file.txt @@ -0,0 +1,20 @@ +User-agent: Googlebot +Disallow: / + +User-agent: APIs-Google +Disallow: / + +User-agent: AdsBot-Google-Mobile +Disallow: / + +User-agent: AdsBot-Google-Mobile +Disallow: / + +User-agent: Mediapartners-Google +Disallow: / + +User-agent: Google-Safety +Disallow: / + +User-agent: * +Disallow: / diff --git a/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png b/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png deleted file mode 100644 index 8fb366b93ff0debd825faf213132da839c04a89b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5945 zcmV-97slv`P)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X z 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 Eg6#q-ApigX diff --git a/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png b/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png deleted file mode 100644 index e7ebf4031619f66461de686511e9212989c34ff6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6999 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 diff --git a/WebHostLib/static/static/icons/sc2/burstcapacitors.png b/WebHostLib/static/static/icons/sc2/burstcapacitors.png deleted file mode 100644 index 3af9b20a1698065fa07a7729b74cfed01b094a27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2579 zcmV+u3hecXP)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X zbWe2dEI& zG`&F}uE$B-+{bb3u|2bSm~YD~TYKzrZO`~}mbA6c*=L{2{?1x!e{1axx#W^dF1h5A zOD?(Ol1nbzMlzQ^ow>Asqn}&Y!I-_V84oS)V8EUO<};TjwsAbExXh)c@uaelYuNpA z5;y@Iid=gtb7^VUYgp#eBf!^zUmDO(0>20W1Ezt>uF$Z|rAITD?vGp=1imfJ@_;=C zybx&GLp$E-XD;1mPQM47i*$PkI3Q#$_+Fs!oUXUUgghV@yM9^fO+-pQ~S?>)ADbOCzVe$_O0mjM}X6c0bds&i76#uQD7X` z7;NzXU6zI|s+?I5o!IKLv)-_F*2dQQ*tLwIyjz%%t81 z27r%&Swlf;kx5`j0X8Z}KI&;eU==;)`>kVt5BOBjzosDjh``+5CYiZ(Aaki5$ivbe zUmw$;YdT*5&P!Vs1V%|3l>k>%_Ud`RmF7)sgobSeA9ey5i?lz}qW>M>T^&yXe*^vs z{H{e`Uq0GNK5Q&=>8}J{=9N$NylT(*D?n8s8~|>q#N;pPhXA`nJfXO?H7s-KGnq@j z64A0T;D>5^Hn?qGniMAeZnt>NO%*HWx2uLdExz@ZzF`>nzQ8D&z}e4o7i4}_k=O#w zjOi_DUQeI|PJI*kvPI3VcLmz)b~MS6;Cx7>*1{K6WP+A!gkh!akF`8L0=PrmLQ6D!u* z_~yQD0`|=opnX?>oekRm0gRjTHw9M3IOSH~H6Kacc)8HRm(EFp_nJ7gtFhz@#P=$A z(>p64X7low1lsStSsR~KFu?T$K>ahA8SP5Zq>9y`KGBnZ>->hyw%Sd=9sxeDXFUr1 zKxT48VD^idUo<|XsL9|%<8x-rwW2^uG+R#V_6@|X14Y9L6PNy`l-bri0=wuXN=FXdmz00!DI+Z5$0WxX6=(^m8CuNG&hr6k_KIp zS?$+#uRXVnhRrFWuruPnR1QkRj&>8UUYZM}CCx--WgwP}U%4))Uyk_AX$@pjob8&l z>asNSrYOU0nVHk`JeT@deJpRgX(oq$`Nw`eqaK7>9S3Z$KJ1G@g0Njbae?M?zfU>W zj31gX8d=qQTqLti@~ZTZ;bzApfUFfDx6b?hnBVF&4;zi}tSdD4XYG-h`ghF8XMLb} zaisw#9RjSQ8s^pdyqI_@)4Qzf!Jq==drB_eFsyxkg+V55fRwD`y5A$)=rmyW1cG0@ zs0P>GH|Mv5_xU8@k{Lv@2I3h`RxScgvdYh1>@;A15?BwI*sW@4$2}96C&BytCXv`m zw%v!?qggPC08)tbFPQ6%fY#W)U2*z1B5hMf!`jh~!YUM}9vN)x{z4mc$@cYhB$r%r p$t9Oua>*r^Tyn`Jms~m`{|C`oK4n}?j*$QW002ovPDHLkV1isK*}?z- diff --git a/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png b/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png deleted file mode 100644 index d1c0c6c9a01050df51d171481302cdffc38326dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5344 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 Eg7O=+9{>OV diff --git a/WebHostLib/static/static/icons/sc2/drillingclaws.png b/WebHostLib/static/static/icons/sc2/drillingclaws.png deleted file mode 100644 index 2b067a6e44d4f787505814b9bfdbc54e006a6d2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8451 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 G{C@z-2s91= diff --git a/WebHostLib/static/static/icons/sc2/hyperflightrotors.png b/WebHostLib/static/static/icons/sc2/hyperflightrotors.png deleted file mode 100644 index 3753258458769357ecd299f44b44dc9a3be45e8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14285 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 diff --git a/WebHostLib/static/static/icons/sc2/hyperfluxor.png b/WebHostLib/static/static/icons/sc2/hyperfluxor.png deleted file mode 100644 index cdd95bb515bef7d712352f33e40f92aca5fbb871..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9261 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 P00000NkvXXu0mjfj`g)7 diff --git a/WebHostLib/static/static/icons/sc2/impalerrounds.png b/WebHostLib/static/static/icons/sc2/impalerrounds.png deleted file mode 100644 index b00e0c475827388aa30c6470055aceecb04b8e27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4347 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

hpilznk6nM#o3T-srRT^HN9S9KCrk|Id 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 diff --git a/WebHostLib/static/static/icons/sc2/improvedsiegemode.png b/WebHostLib/static/static/icons/sc2/improvedsiegemode.png deleted file mode 100644 index f19dad952bb571357e72fe2bca831e127f3396db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14293 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 D09+_4 diff --git a/WebHostLib/static/static/icons/sc2/jotunboosters.png b/WebHostLib/static/static/icons/sc2/jotunboosters.png deleted file mode 100644 index 25720306e5c2d580647e3abdcfb2bf86af30d0fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5740 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 IM6N<$g5

j{pDw diff --git a/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png b/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png deleted file mode 100644 index c57899b270ff580c51980956089b24e629c18128..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14802 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

UNj)5@82Mmt*U$~`bXx%B7W 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 diff --git a/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png b/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png deleted file mode 100644 index 0272b4b73892fa735ad850ba22ecaf0646b08a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11459 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 diff --git a/WebHostLib/static/static/icons/sc2/magrailmunitions.png b/WebHostLib/static/static/icons/sc2/magrailmunitions.png deleted file mode 100644 index ec303498ccdbffc072664429e7a943a128c84025..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19193 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 QF8}}l07*qoM6N<$f)DZ>AOHXW diff --git a/WebHostLib/static/static/icons/sc2/ripwavemissiles.png b/WebHostLib/static/static/icons/sc2/ripwavemissiles.png deleted file mode 100644 index f68e820397652a0e3177a9e03cda1954b1ab12a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13628 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 diff --git a/WebHostLib/static/static/icons/sc2/specialordance.png b/WebHostLib/static/static/icons/sc2/specialordance.png deleted file mode 100644 index 4f7410d7ca9e250b0305fae35b0ccee603b2e5ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12992 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 diff --git a/WebHostLib/static/static/icons/sc2/superstimpack.png b/WebHostLib/static/static/icons/sc2/superstimpack.png deleted file mode 100644 index 0fba8ce5749ad42fe9fa5c86246280aade0e1fa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14901 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 diff --git a/WebHostLib/static/static/icons/sc2/targetingoptics.png b/WebHostLib/static/static/icons/sc2/targetingoptics.png deleted file mode 100644 index 057a40f08e3097f095239cb437618258d58fcb59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8431 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 diff --git a/WebHostLib/static/static/icons/sc2/thorsiegemode.png b/WebHostLib/static/static/icons/sc2/thorsiegemode.png deleted file mode 100644 index a298fb57de5a10de67b9d1c88ad2576884df5967..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11345 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 M07*qoM6N<$f-r-FHvj+t diff --git a/WebHostLib/static/static/icons/sc2/warpjump.png b/WebHostLib/static/static/icons/sc2/warpjump.png deleted file mode 100644 index ff0a7b1af4aaeb9fa6cd112b093c3300d8b46041..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8665 zcmV;~Atv65P)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X z;WsDJA`W= zUT;};3vAiavTW&gleUB(rjU@3klG|DacrC@QKZOGq)5mfYtTr_{qc-uB+Cg&*!$yN z$Mbs4OR_YYdFDNz@AvsW-|sVq58H?B!}ekOf44Q9>I`K>v(MipE#dW#^;pJPEB(_ zYdDR%#09vV0v3Scf}()sQr&3b3`Smjr3O{hph`NOW;&X&fH~$%xPr~-Z)b4Km+&m} z;XYp>e^dj&Rg!AX9q0_8c6Ok4c91+6qitCip7sE_c!FF!!3Ar2t$mXdNfvbm@Tn@9 zWD=jMvb?#s=6JI1#_N#(&44w|wd0)1GCz~flWkbXb^5MdjT%%*Mw29?NlL{6#=APw zd>Pk#87-Qn5S>N}Dmdr6ges0|=oMfKz1FGvuDgUlM}XwP7|DY%)Xt6?Sf2I(0NM!+ z?SzKfr9v4C)|{r|kpJ)dFxe|x3Zu35gzXn~i`SkY0HZthV5AL{?j{Op1EzJHO->4_ z0_A)On%yYNytvMjDIC|)4rQ%lshw>YPMz*c`|$?_jLGmQ086gu2f%IU=;v0X`^!F^^5J5}qy=3+vF zeZ=-3CO#G=J{Co8bR+pCTz;XR!nBTlB#*=GL|){^-PZ=d^k^0%rGsq4Qab|#*9j9! zM-!x@2|Qjm-X-EU*+U6@D?`YBahciihy}{>QC*>$K57W%s;Jg`4+=4ocu??~AkVcm zo++sQYc2*LJiL#c|GKqypzBsBqzh={GcaAi;dbI|a@K;XYHDX2eIFaJKviD-!2s<8 zA+m?WXWk_NWWNtRok34$YhZc(etiDcQ}$UyLqS2pL7b$iK9v{+JUH>-tou!j!utX_ z?e`nLbQ3pyS)#0!YR=$q_tSH6 zH<2R|4v$XItSS_=0?|kb8#1uw{tANbQKujE&3n< zqg%p4Ni!J$?zrg=>wF&5Q|V;zHrASKk`yHqnu3Q()6=C$(~6&MY(F=J4gKZML_Ae8 zkh~HupNq_h?XVp_;f&k?nS4S&?Mb@V2nb_4#?cBIQj^4V(i|n{3E$V!>BZA&-{0X^ zw=i(|dba)Kd7gXdDMq*Lrgz;c0)FAtLtR|}40PFH#O~4Eytwrx0B*eI26~qE)b&NB z1C3nGXBAi}(&y&GBCm!|`y+=>#<@Voxxg*%WEC)3QP4HbdLO)~i*!6qIxadRpC@C? z7)VVLE=8(=<>~anF>#!M%hv<&+(S=^(RkY)@j7p>0Cr%4$bkcd2iLHwcNIVWh1f4} zo-pN&FKl8Xn~g$p5(z|YD{Y^b^K&Wc}9(M2FbJ{+K5AC*qMHoe)#Zp?7 z$uk0V`bfW3IVrUYN9k%+@Gdil_5E4UL0Tw)HLvvI4Y)~eO`=5wC`md=bE}W(R0boj zBm3NDz^mX=70Oz{K2Jbl)qof+!+XMPxOxNL3)>jq6{dT2ALF~iJo|h9qc#p)+e`b^ z!VKdtj-t$Ov5&XNgHLkX0eDcs+t9-L>#ns&{Ek|m7B44ieO$qIJNLExLBm-AbD7Y* z%e?3*9X+L6z+C1g8D)}N#jvde6dxwui#0z zNFGa~E>uw$s-z2IuDJHb4FLEWg^B(6Gv8&;FSo+9=;NC2-6VW$BNUDZpJW`#;g^~P ztVN>l{T~~&u50lHU8e<>bR6{LWIvRXvl+O8yeqj37kV*wG@EpI};3P8ejh6eT{?26;($Hf3AFL>BM))I^*4%Gvv@sW$M6}0 zn|t!zEK$}?U*lF_kq;6YdVC6sd3!|kxsm40Beyq)?2}Qtyg2<%a=UFOs(6Ku>AGSi z6EE!M&aeC@!pnyVZyz>&o=|K~6K1t^%?eVvjNKRAo;wbG{bv-8Yk0cs8KI+D#)xDw z4jsohbezD|t6_VN@RnTy_(DIupIB|}A03YZ@c7TfsCwl!R|3#8D12$8D9$(f%UyF1 zdZ(i)FY)4C(_a61(1UldqgG|+MO4EA=(=VV*FJm)o9}yyYahPjw0E!l+U}aa7hh<{ zQ9t_VH;KF)MjOwP**?kixb2*!ke8+_m(XM*l?sP}kc& zEV(7Zrtdv&{XMpI_}mmGFA*0nheRNmkLQHCzyhU5g(AGyb)!s(&TA7{YjW*b<)`C6 z_P}m<{>wiG;D;Z%kJ}!L2Ok-PZ`0!Ggq5FSU?>5ppneCIfD+$TFM0O+m zv;fn$el-vL=I?pk;bXA|V&-DJZ1Id4mg)j0LOGZhF#G+P?^;0ffIh z1m6X9FvN{tyW85gXK;XD|HHFqABU;8^qti93a8uB?I&=1FZshc^usxf;~Is$L9<^t z?UoyZB!6lLr=hQkJDq+t#`ps}YqK8zbQ9svzld>o%Dyp+74*I7>RcS^7sWA>kBlHA zBd<}B*QmJHsL0cxg;O@HccEjjhuGFJ;;)b6YxZ&Pi{G>Z=~Zf}ko1YVw_mXx-Zh4MMe$IP)FWPG{pp3l2kM~xgDBmRkwCxF!kBr#o-I+w+ znIvl_G?f*8vGuj0Hux=A03ZM6O>-(E6BpMcnV>$pS~&Sv$9I@1CzYYU8=^QVtfgZx zbap1G-r8qE%Hdw@K|h=`<15Kb6VD*IC6YfKC-fcRgS59(#Gee4el1=D%Dc9m?Dne9 zZnr1Q@(O=_n6frNXsrk*@~;V47QBNww7qGxy=j!aY1G@+S+LqK?WXJU<#WQ)r_7lM z^Q_O`+%-%xl|=1S3BMFR_pm~}wGS>h3$B7qxIb- zU;lS?*OJd^7Oc~?WW>HHr&8A`eOUXQeJ4HrD!Gv)+Ng#yV5=a#M_icy(l&a&K0szN zL*&oJ2$LO}v{T35=tUpRp^ph=%H3v$prKrQvh3_Yc-F*z+N?e`OSsw;JiT6&J~41a zzBM9j47lp4 zLfNsGBJ8~X41JiVUnTaqkf>9x&w*E9rM4!BKC!O`6o9-;1gmnd2l)a!m7fi-6`@N50F4h9(e>tWKbPojr2^(mhHQ%rtmRDg3-U`QhIhX_5YRwJA^qOj+SY8_(98SMs0*%;T|>lGVTlSBOb@ zIJ&DwVQy92y!a`g&1sS?7pit4S5_>ORR5@(QcP$3!Kx8#7O;xC?7uze<2ehMv{0^5 z82a4V+NKLtTmc1#nQwQs38m|A&jM6c2fp$C+X-IXV?onqvUDuy#pCf2iyx$;y^}V) zM3YgXNijvr=}AjnZq-ffrHBP9{_^OVrFWE7!IS1I$>Sxu2m8748=qz4cmAH}6VJ_( z6FB@5PQMGM-v!OJ3Mplr7>6{ZCE|jm`Svo4nb)&0uV~F8W&@NKw;;7REt4%I@;D7p zZVjP56T#S5wZ0cA{z6FDdAP1x1zL*HVDBa!DuYJgS4s#aHxv=IXwO7YZVh4V z&0-wRQW(u3E%RdR&(^36VC?7iM0(BlZ$Rl1gFy0yLv`vpljfZ3L;spZ-#-89E<&GL zK}?=vvYfzJ1e4_izG4HT|8pC~A{2|z=yiiES(~oP6g`a;J&p8Tbs6vKGdSkWqj6yi zjSE{S7IkQDw6;c96Z)|{#atPAp`WH655>bXl;15;%$C7bL|UStT;2vQ2Pc~y%#7t| zT;QbXvR3p=k$iNTLQ1FklS`1*dB~j}^npeaZ%vSRW*i(KwacUr9pw|tZ|2c2e1o1) zAJ<&@DdyemMbGHG{i8Q2N<|F4j8PJs9?kyc87XYwvD@q%(cg+?0sf1Ew5|@G`8tft z2Wu)n6_ofi~XvU2De9-1X9}#{i`-Ct>?~MVpT^Wp{IzQ&fFvu8^a_vidZb$s0R`Fbf7K{+w*^rG|M_2S z6+LnzcmKXvO0eqV>sj^j^-!!0E0ld68&*|Yb@@7WzqXC>oqH@;a=?Rr{Fs@KGXZWD z3X?iSSW@wY`u-{8Wi9CYvrK-+jtkrVw43}$3jM7#-aEU|V^d@|CM=azpjDLSxX8=o zSpb_eMV;QCAvfbdZ&jgDW^LmDH(v4y7QE$RGI|_(ecbeu2hsP#!;d_`!;d_`Q=9&Y z&Evaq7A5X&3~_H`h=xl(O1wcQ9yyG!QO4IO6OaSe)_low^gszcP@?wcLmnJHJ5nti(~(!qTJaT9nEbnOSmuS0g8oVZxf$e>^HBUU zZOy%m{ceI31^j<}G1;w2Asch^UC@ThCDCA%K6vGO(_Et}G6pglnleRSKKy z#%@`H9?qbLvv~i*66-rzwt)ri?5D};A^p23t*aM{5M+-Xqiy)Z8yIb|*QUC?a-+f|&ZQ&`^x^{Z%}-!)VRY}K0e{BYhwB!9WXg63$I>Y$Y~ z2Kufn@@lVTl5(Gq$#0E7Bgh}|)u^kwumjM2&!8CrY^k;8l|5|z(Posz{!>Dm6PW)z z59AF3>w2xy{Mgnv8Gn5jx!kORhaxd}!ynrnJ2t^cMmW)aUl8x29*$O-X;#*W>>nnp zXOMkfe5pJeTJ40L{zv!mBUypGsk4@0AH`W(?< z!c>&&nYN8tuCq**{D+mB8B?z}p(&gSM7S+sJKAF!lN*Etj_8 z?r*i?Fv|kg+Wx9zt++XF{P&?+OSrV3@arSzHcC!z8)e`=QB2tQ;NMuV#$Vrsb|h_W zO3+WBd)3?^eRd}Y%+lIOQK)mtvVj_4L#c@Mx_k2wdtMl-JKjwX-OWu8-CdvT91IYD zaO8~Os%wK5u=pFbCfheSU;!(f(9X_>^_#GUA9{}Lyt&tcDU19l!P){wVthCKzF-Zk zO8cxF;@;^ILd$AnvZ4L^EMNdFFKlP!H5;tIZ}~6R)$ISjGQR@gbH3}j>xr-P?31>S zoqBx|Wu@no>QdJ(KDD~epz3`}V4;-(cKmQ7lj8|m7S~pyD?UM8z64)ufUJ43se|Hm za(OrJ$$mnO3X?#^I}MjLe6knt@=Y^Iw2u<6EL+FEe;#FYRNURBkPx3{0z$X2Gz3Qq1|5<<|jQYHS6VR9=~ zwq0D{zO~=5ely;sk)E(4_1th8&p>_YZs5;^YGZ#pUR&^4F4oG%_f4F;!YZ)rbqoTU zlLYnLO7cJq-K>wwUN;oZGOVaqmwnOGktEZRB!z6PaynMlmgwKu^mAh4qoiXKq+@gY zhvy&vG5Mn@^rLC?gl#H?Oa>!cUp~t`6Jc|@ekoGlUx>(Ey~0*m^5qHCtGZCviO<3h ziBwYsmZipH8K#bjbG1}GS!ZOSlo$1?x+lW!w!KRMxa+cxg`N#sj|6&Ih^2hL8BWzCw}Cd z=c4pp0mTI7^vay{O1Rp<=+uc!4|8zi5an43M0Kr7<06ABqr~0_Grnbv{E;NG2};+@ zutL*7({PwQa!ERrbZcz|Kycj(a;`LshBCxH62|k9fHn66SP*rSG9Sf}y)sPcM8Xl0 zo5yVHnJM6Ca8NeNI3x#U^U-v}EEqSq#H+H*&&xbvO6N+6;9f-v<=Tvd#)IJBTQ^Zn9Mf2^Li`^E|-8+QP_JRd7$)OrSfj7^pYU0 zYZvqO_qQ<>PB0Zt5dSIAD$07d-+3EPJ@GSSz;l5ehb5n^&mNbJvi)jCH}XOc`Y~Zi z>LoPKFqqcp8iE*O*x#;jC{A35a59TG#g1*>|Fw={sKD?rC3eHN_vj<5wv zk0ooARe|Q1Cty{8S*fKjtv_`V^1?n=_4l#zLb0Z;;|4pOr0q`53Ckhbmo6`Cv7jl- z{3L%dj$VIHCKjf(Hw=8K_R8pJw<$4U?s!ADM_cUh_48cd`3j* z`mw6cEUe8G#5TP_K9NJ8%n4W(P}Q%E=6Q;RIeXN7-x^Y{$4GAz3B~fe8jkt)NPV66 z;@WB5VOjd0TP$@|!vbnyfep&) zfix#90QL7nq_@WFfR*3XaGEOnb#@28&hD_lc~%E3b=AUZ!QJk`J-_yvl3QbE!KV@bwg*ZD z0mNCUd^b6PG89W9uU@I{=mO}H&kc}2s-s2Hl(alXMz86yvy_7qISvnPp`*WxktJbb zBNO0*+=~;CFqaR=GTy-s>miKHq)y?OhH*?N$taoi$C(0T^_m=taW)Ebx@DYA=2|Sj zjLYvv8_!Ulv7Mn)k&y!}$miR>E3>^C7B#R+Mgb>I6V|L&tnH2O;sB8!2tQd_aZ@;v zr+gZl*zv?>+AkJUqZV@x=`5GgqdD9`5BX@W7S=HhnoO^BVy4!Q7d}8Cw*t#Z+yAJ; z(In$k%xAuj=8(_#kl7WR6IRKXWo|PiTV;AxFHC4lJ~u$*2g77{CSY1e^2tcE9-Eez z`f!^dJNm`c>*6~DS?aQ2RqvzBn(H0S zp(m!$v$d01W5#x(j_Q$ji5rrII+c0Xh}l7QXQC=|px2osfV;~_M}HTwkqM!$3zriA z!Pa{J74)D-bI7JyTCgfDZ+tKx=5zr?(ZQLhPD!u2uwny}SHU^2LF}UoaC$Ohs{|;S zrYlJ(%$|gsbIagG0Z8R>mZnI& zJVB^kCGpIN^?hl&V2*-5^kW)&N`rY$%E=t1s&%x^S*mh6oRo^CbI(?k^a3T_z$u?P zF4L14^rX1JQq{Ric^w*zs(T6_s09ess>fc&+3?_ z;ZjwUP9Mpa56vAA_xecw^Z5Ikh5w=e>E8*Ht-wkQO)xohz=Bm-+pouSWOv4__eNGf rBhxcEX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X z|cVw~8g%D(>CJHa56n0|r9~ro<@(0tvr>4FM7$0g^xlF<{&c#vS)2 zTh+39zq&=b{oH%%E^Dp#$2pQ^BVlK}H{KiLjqEelIQ#Cq_daWX_gr($IoEei_}}e+ zw28Oye*xG=6aOFd@o2w$q~rFm{))GFZw|ZWe+1a!7def_5pE>j;=}#1{r|~JI~p)Y zdvN}5044#|*pGnZFrhIV2Fe?MOf^~r#Q#4UP4*~)a&S|C+xRvOzz%{K1J*h0qJt+L zY~_P@k;qnCKi3Mdw(&L^-$H-@VH*3?k6ZEDpU9(0fiESAwHRxUCPktlF5-<3eeihW z^$XEBhCm81hh2B557r+1ecQN)1(1PAc`20|A4^8}Hv~x>2I8Tk*(N7`a+7t zYJ@-tD>MROG1g+R#vs9_lt-u6B93o8PWA)`(rYT~`gRZM8;6VIdyk;UUkBL11LhF) z$kSc?e|1E#LZae~a1(@-ku}mm;v%g;iV%pg2d}XbM@XcQsCpk8?^p_n#1XIcKw2w= zjI?EjGm8`boEeNOL^={iTPu+gA<g}rVXd){*sSvDN>8yfveRXAkg_9k+`7t$AlJstg070s}#0AtMw9fx#kBI1T-1F(eoS5{W_` z64r){9s0aBi3uzYlB(5gjz*c+)JhefNJ86jBm0niw-@uDQ z>liJK(cx#Ak?$ny$Sd2trr}Xr{8X4kwKpn4=SWIX;mmdLjuD7MS}H>0Sb--LHET&Q z$of#k0SvN%{K1cDXcXHR?-GfJBRmuio(OP-M_xG$#fDOnWpOZr{ZSD|3ADCobba(n zNmW@w37!!G_}HtbYG$&i3-0SS+zOQ{~?HR?hWLrBlq| zw!&{&weMA4>R-cXQ~;noljFSQC$MnNES`B{4X^a>VuA_*-s#P$OvgYH^&D+T(ol+x z9p!?U09r-kT#Kbff{P-+A;h3DI4JzaZ9) zec&>@NmF>~`B(YbkA5CLwBbc{q^Z_9ijkVUHlbLAt#?`jp*3WMLL-Q+MPiUx6yV_^ zg-;-|_#%UAJscdAaO$#H*Jwxq0*%FB&@?H@&S;2NqXACLOkp547$MNcVhm_3k+q0Q zmD7)ZKd1g_DEXc$#V_F6i-_5gID!A?>DzzGfnyD@AoO{}d z>>b$0tFP|Elv0eP$`mN^*bn}L3(h}{Yu^6}5`5ZHS?0{haQOu*m_KhZRqeBN_eO5~ z{!jSE_kYaI|MpMz+7Dc98-Q`NA^DoZsjFur4?oumj4&9hDPic69ZXVr66=r`IRacF z5+YlrO40zxHWa);2#XYT14#*q5~_iq1mThu4g*P8g(alI7^6&x zQ4&!^h$@;I2@wd5BLt36xI*Ddi6<4llz2*!k{-TLcv8~r_!M=>nra`@ynrUxqsjAW z_5<4e3{8$ts|avpjwxQf>&-2B=5}>4a$uOppIt+XlcBFN!i7s#@TH%8m+$`c4z~8~ zXX(<@c!zzC z!Ic76SR7&Tgus)6j8x3@GHk3A7|ly&2b~m?8p0}!f>@Q9?YAP-1lIKlq>D}x?)}}P z^xSd_zMCdUXYh4}f4<>50$(tHdKZ&}G!H%U1n>UIHKe9=QQN(V(Sb2Gzp{lhKX@%o z^XKuzw{PNsAAOtO-1>dyz4t?6@OQsCVu0XifZ4p;ibDo#CE6rtqYnXPL_$J>u^Me% zCd&-Ia4-l2ia;r>kd#eQcc26aDR6{CAbnCYAT2Wl!Y8d<(jr5~c9K@pI40Z9o^T_( zs#T89Eu)2Tgmo|~M(Son*Ic+b%kEcvtPL5jRH()YT6-ARvbS2{b2H~)Hf>~Z_H5=% z?ZB#pzJWeoS-F~vKXDSaqaB?j+;-DX&?rXs_Oa^0d-%flzs9`xeguGZk3Vw6=*W?x z6{OuJk@wn(;)rS-VhmUenix%j#UdJRZ)h|%p~*Ww6FMfc^;tBKkRUMDB_&d1M4F7u zkd^^~^zem8-gQXXE@n5+Wcd=8C6gQ`Duc{Ba2})m14J^7H*Yx@P=SOzOtgf!(;ncX z`Pn>UUnSNSAtYEqwW6tL!|bp~`^K&8Jo9XpE?deMzWzCOzr31zZ~PXke(@bzrq07r z22mK|v0wg_c}o}Z#RuVPg|oi9lIFjWS6i$|D+5 zBpT|^0Ao5N>$n8UQZW&Evb4%3CQFBLZ9ymk;o=F26c$8`<7kwg&nYcUTygClNO#k} zbqg0fvl4e`I`^OTD1#@p(iXfzDytYA7-DU8I{*6c4g7nh$mN;IG_i$|at%R5KI74z zX=S`n;=Z90r;QAe@9@biKY`0nJrAz4REGDm;ql+G?l%vTpE{ijue^a7%T6Z9G#vsV z+OnG8e)u|jx==j$77W)q2u53DG}c%WYG@H+Ee)W=VHebgXqANF#AT5;otg#DrpS`U zkzlAWVM1aO92_(XDJ71c!twK`am5cD%rp0M_6<+aTlJVb=@_p1<_G9s@-(y7Zz5IR z#Hy!ubJl&k`TV6PF(s|oyE@0RR+p4eWLi^PeeP*2JZ1q8Jhqb2YREH{8kLqw*mbM8 ze_uD}oqRgk$#Y4!&*s$keUek(_sQ3RFtK$dFFy1b8#eF2f8hl_^5g%K&+O@kk6!mj z0Xt-GXsvBRmB_{*ES`0VMRLf_5EiU3SS$_!K3Rm$Aa$Bf*X2Mm!59%a;gMDl3PVgn zBI3I8cLL_*=X3Q1`=I9lOaJi|!raGr;q6nH`nD2Wcp=<=EA;h{$QG`;Z!gQ6r}LFR z`#3lL=nhsM80TGcC(-UW)ciElJ7$q`n&}%H#<7|L3SXv}zjus1KY5UQr`}KN2}^0t z`W&-#1#OE@rf2<&^ln^B|HDsVCbcu3o6L((J;maA9c0frS4^5aDvo4_9nsPcO^A{b zPc#BK!eXqc&juDO2@XCfa`dNF)-!8{nApwn}C(ZrCF z%=ZHx?mNJ$SGRKMgJ z@zTp1`O25S%w_+5J3(s`y0@Rv$DUv)XyG^i@=wNHeDaZ^vqv712t#5mzL3a9c+eFN z+J;yZfyj{}hr&fS9BzXlG=`n=AjbrG%2rdgH3AWkl`c{!Vk2;cqCIG1YDW=TD*SLX z#icVB!SJss500Z^7qMV2V)0}KUf#@qjuc3XW}fNofuSmkCbjY7&3ho9X3FF|wcQ0K z#tKY~43JAnhRYQex3sdQTI9-}GRMkp{@Mxo_(vU*Pv&|32-0>P_u1`&#H#S^>n?;7A87IMyL8BvNFMHo#(ur~;

nxcfh3|``*e62#*z?i1)lOqNyT`Q5I7l>Tf}z{kZjz`aIKBc9J2_@>yS(55e*kf zjTE6==D${Mqnyg&Y3Lsv#UvGGEuW45z|)wFS$$N@m-ds$inLAqGGmDnV2Pca5 zsj-o_X1E%x@PtBJOK4&O;gI(8_)dmutwuSiVH!bd(*R6hafCtud&3f)ZiZGjO=RkU z#gue$97WFWq#$Cp?%T{c3%hVcAFu3ukl7!+5;xyMeD{;Md7ti0n|Wxwikr@ncU=nQ z8Y4aZbj(?V3Os)Ht0&005mQ?f&kqij)SQU48G|HrW1$qwk2mWNeMG zU|l4!H3WX1jF-h?aFxQ5f|wd{BPvG2WEG7tVa>$s3QKgT6uxwbjlmTjt*VJmCrjYA zaq}zPU|r74l=)?{oAtLo$I6YPDZ<$cl-C5|Y+*D@HjaI)X$a zECkX+HR7tW5tcq^mkA`EkQ|813?)@2c^NDcV->#i@J)$R@+;^O)4Ao>51`ACqM3jG z{bLO7-HB*&+4tmH?jJ0Y^&Dz(Lb*~Ui4(?h4opk&*nuL=PQV3ziq_T+uKC+*x$!UF z%l)U$h*`q(mlF`8py{eJS>ce1xI zK#z)O8LjZ3*T(qiT`Jid9&r&w9tlMOLSvC$nv_bR9jMX`imMww#Q+eA!FJErU5(F9e+fVcH=XS9C!IyBRwDYa+e4f>x|8o>D zCxwb}d#{@f^Eb)c2xO9%T+xEC` z8s0gCq{Wyfx7ivYD8&_g6p>D->Kcv!g}@g+dD(=A-x!Lj9vMU+sfVXS4O=S(rny;s zsi+x)Bbw+N+rdXVK0r(JWC~31($Fwfoe&Nj;QEJOIljY@|d*G>uP-BA>Hf*Es+;>6Zk^k|{JbwD|BteGV+^bHMh z!h-pH>gEbtU##(-s}g2^x|5gBTELyUjUP{%&t+FDj-MXUTXw}Cld#uqo(!z#xI$}9 zDG7;8f)7+}ga&-+A%Q@6UZWKiTC2kAIet2N4nGIN_dxN+r5mi+WFUTSOR)G2K| z9!n@yx$@LSWTjx;s^?(qZg%WGK>Mz3{IWGgnmdDBfR%wY17(J(-9fv z;-HAEB`pISQNOhQt*}U3T$FGyLNjD5Xc^K*o>84}QfelyN>S7?8I>c6#yPwBEZmkf zQ}eUA`Mp2l*E=6#-*`89FUw$J7>r}`=?uAaisD3xcb{=S>5eY$e|0O*loDR9X+Gge zW@d7Hzfj|q)oXZP&ZBMeRJLu~#mRY}Y56AZn@DitnD%^@Y&y;Ajju8(9bq%a@bf2X z%t&IcxU|f&6+VMonz-T4YWGBO`<=z8RBqrR8?=Gf0p=bA%$KQ72mfjeZQ5uOG$6@4 z0on?T6=ao<5E3CIj&Mk9-M~=z3{&9(e;T)@|AMV{gq=wdB~mO@c?z`<*Z1oNNi@nC z&8P96uB(_6%;38_Ze*Y|jH3d^q8MLEa=8r3aoJkf$5;RSlf3=JrTp~4`{}KQXsI|Z z2|1y)nVBaoCY|zW-M)oQXtM2-=1+^etnxnA=wS zOdM!oaxD4h+ecWtvv*(Mlxh7zMin}{270XnFpgrTU zVysAOsm8mfv{DMI+_P~TqiLVvYRI~wVHT%S>=yw8VNAwX7_Au?8$}5ZsY6sU$eNxi zzuT;6%I8>dOnbNEr|#O;*IjOabqFwdB*licScB0vIb)VaX}DCAbT6@OjsN{56Z;cOJQNg|VngRmUjdFwLLD>fsFp z<1ROy{v9F=V`d!JRm8eVH3|8^`#;R9TekA-hPBL`(t(u%fn?0U7%?xe*-YQ#t9WU& zNKazfzk4qwU!YraJUY-Z2R)CF600&PlDzoja4$kL~1)W2e&6o@eLo zy>?6A-Xpq3>J9q_u%1j!me#r2VA^G429%9Y#Tbwd%u~A>QF0!_8m$Dfvq~7<;sriPgIp&484Wl=8^CDJmvm+xQ;d(R3jT zr_JTT{wL@u4x**STEX_7eO!3vDV(-2yBz1<9#Vs1$+^HofTO<2^NVkk0H9fv_{ zDNa-gQa*lc*jE@uSr}-^bNZsiJh@>V+xmMsY4Jk5z-LM-&8%#WVdL=hbI_O$>x39`1jJQ#zJ#er`E#5443D{`2@B^3Ma` z=3Br11!kbkoEcr5eC+XT-MO3Y(J^-VmSfVER1#r~#YzYrh1Q0e(d2R|a!Hj4OYiV7 z?Q>>v`I%=DB{7bH9j|s%sKxw#bO(dwzN0cYoujbz8nh?DYup=&Po2HHXhiYj_p6t-qHw1KSCWWku^uW@H13 zfk!@FW1=(x(xoE6B=yJeEj0OpsreK;!-&zbadz+RrxwNZ4~$R@Ll6STT9izf)0$^Y zYs%weZwgq$pA@oAihF(ZObSVpRSfGYpV80a)gEU-YBnhpe(4vSl$pgz&O-W=kc@6& z2~#=txTVCs;JWbq%P%oX!Ymo^YI%S~=}tP+O$>!)Vx#dLm+5{JJ>f(>q~!Rx(!s@H z<=|RQeAx1Doqx}T*PPC*1t)OVn!EV%{r|;+Or8Z(Ph?82mFI@}_}mPw-&B-Y|N z3bbZCj_4_c#9A;oIzdKwxJr@7Rva9FM${=N z4SgpX&b7cINFi}Wo>5z7Q*|?QnaVhZ{;XIfL6V`_UQ(1j9atV(LpY>ItjG+CFIx2&Rs z$0QU|2wWjaONT9$5te4B(dp;d9~Veho0+w6F$+)2GqktJSa+3j-!7_^F-#y43S8+T zb3WVpH?iX3c5c7ncg+0wEY=O}!vQJ*IG)E~p~Be;Za8}uOS+nAn=uV%`ZToD#7H?} z;f*)*(;mylnq+Y%ARSfksvc`!SZj;ACfXrNo%{U$5luPJva>m<0urC|H{`Z-_by6Zm$MF*M$)d9`G zwqdX!jl@J68^u&6DmW?wl`+m`0+~c|OiZl8B%?~nhAm%SGU(CW6CRkuS zrnO4AaF!i4&2PHQ_L`3*rSLr;NkW@xV@Iu*6TkB|zPJ8gSiE2f!^6Y%S<`|@6ljCh z5lUO)T9sy}iFPMocz-|3FSwkR>C^E>SCezo)Dn#p0!Ijx04I^mN_V1F6Pu^s$HzOS zV_)fmOJ^XbxzIKXnolM;CL;Tb|6pC;?X39ZN*1iRg6N@c_Uzn*76Ktb`vRGFiMtcT z?pv99#scnoYCB)MHQ2El&5Ppv3T?sGjs`A`Wjrm{N z$yj;+TQ^)w;W$&XbCJ}**63m;qj7ebCo*Py~(mzU4$Q3uzNh z8Q8(n>BllP(~fW)QqmwL^t=E75-3STK~z%K5ozHtxi*WLQx|aQpPb7BLr=3Hzn*M& zK-31_ImctCtw6Lb2C0c(+)ewk_pp5MUVd=>5~hFbhrH{QGnkcKLf?i>7~#;Sz(4mm z1`j+*)^39-7qI!!2e>g4aO&U8hHE-uohA9|9>)Ih4)QnsHA5GjNZ|+f^8A55epz*y zk9Uk6s4qEt$NhSwmuWo5-YtyFAPxWz;CXFG5LlX`;9J09N@Myvn zSzM7OkQtOnF{l#eHcw?oZHV6D08=w-zil%Y{qAQ}#vb63D?UeNON2W< zPQJAryP|`n_)T(`okTn?IsMr^%vzX2|NHU8v(CjX7{>k8dVafZkj_>YV|`v+y_uOa zX7On62(lWoDG9sx#Ni#LKL0yM3h%sLhvC`&(jGfrERfGO;|D&0AJF9IXc8HG6h4Xo zmq57qvQE8BNryl>G)bSVaOkd$Gb7bZ2vn1ZjP%GTnAda`7ku_OrcL`VCC)aM{H@E&8S=87{6%GvYiuIWcOsw5RFPrfGdkFHhH}^Q&7tZID#U~H~#uyPL zkvK9d;PpdN*HhfQ$sVbz6{>x#HjKift*HYkB^HZP4pJ!W!MwJE%ebO$l-A9X`tTdq zF|BTjyyH{W5!1c-%sMGR_5B3vOhrv@q3h)1n1AsZBv+qJb2`JS@7~G%dwclwmp{pe zvKIfjXHf$atlKxp2lwx1aZ`qKasksPO=JD~H5BYPwOJCUdysT$CbGq2$wxoVe}DC> zoHJ(<&eD1O*`r%|c4NY?#wGrxCvd}u&*Q5%JxBMhKCb_huhKMqDZcM=`=fVKvq^U> zxE-jxSv<_Xwqu1t;o{LhzLzMe(bYN|&-JNPDkM^)9Ty!NbYdD&T7#AHQ0|C8kP?#J zwE`zNX%sM|%P=~INGzW35yc61sKBwOPKR6x4jka}vrb{)#Z_E?<-|h!GL`4{jdIe$DIC9WCePl#kyS6O;?L$!qGiQp6dxVq)cI%fw)4*5*sdj_UAD`A z{Mpyv+ALWgWY*R>6ABNXk@6tbu)_4#*)-*vh~tE6wT5vlMjLEmu}S@6Z8gdYtgMq+ zB(SqK#_{P6%FzLai^tNDnujWE##o2YbqID3;2iMLYc~_(@n@$kr||qMym$33E@{v6 zgRxP{S~J6OSy!%M4D6~VtdB$HPMyxolh0z&wl!?qbr)kdKF;V!jV(i(?TO^?zJ4pM zv)h>{SGo7L0z1b`2yJPO;neHCi5s@Cck7EZJ6$|-{Zo{B3gWxBeEUr~u803$)K?X0 z@Fo-iDdHqy=ioLvvYkxMPa)sbOcX`LVT5%7ZD~*~5T>38p)q*Ep=KiXMMdU2ma%b{ zf!sy3*B+-S+X<5cwsy?q(e45tzw{ksA9xIU`dQW#@WlfI+`q3#Mo6Z34&6pKvQ;gv zawuubo?672+h66K3zkrDCb99k4LtSC3v3UE>ItCpR?sB6Nab>LEe|-UwTr1inqxXz znb~<2<-UGWolWfBu!HCCdz!+;xY<6w>n*XU4~zX+OC!;*p37lygag)Lq%=gaT&#nZ zYbDhbpkqxO#h^i&dZwf@5@qWdAD)m5M`bcd=J)KPcgBhAsJ3w_1%}OJF1!9_hHGOy za@INgbW$gKHm>Bha*a;Up*Ioa94KmCAL@-X+n9vd#Eg}T4CxkraQ}VGSiFMixmI?D z!?ZQEa_7(P=d{JA;T{lmjRa7m?xg@jJA2u?XAgaSgA5D}f&`s$NQ&0HWy95A&A}nb zq4#MqHbDv(p8#tu`wP8{PK?o(Yoj^WOeUK}T7ix zFXX13Coq2ht-S5+d0M1LYs+k&_}%Zh?ubyI}X}F zq&32X>$+*OS?tYYjE{_BJrF4uTtm51Wu!31 zfx#gL3k4z(LrM{wgeVS)Z6f~%Jbhs`3E&_R4Vrm~7KSPkp)cInGMHzudok=YX?1`#~ZTq5>alOm1mqB1vFun9omjc=tbim}vd0T=4H}x#;ST z(A1G7{>K7IwTP4V2uT<(jWba$F*r2LXmPy0D&<3>4b>zhHVKJNh)nXvko_OwRtR4@ z_`)2@wKa!`bJm&$k*-dr69Qb}B7j0^jNXA>diM7)I6O$PSOfv7bU-GTC7sKV%jF32 z8SZ)hL1x-4KYDl#Hx)|!^*y)n>C;YSc~h33{QgNsl7zXbG#e`wzCAR`sX+z@fwj=& zXGr@QM#^O-;*ca3oH#X$*CE)vHl!E09HU)iL3tmPw5uBS$^ILj2$R2 z8;95lX!aDCgtAE}ONWbdt!yhzu&z4Jq#%z{KEA8Sq0CLwOH4MAB4E9z3GFj%YV(}SqfGaU(zba16ZtP{qI z6O=`n;>0)wKSR#Tka2wq!^0Hb^<6&jPuDWCW{gulcp___TEi7-pK)QSX+?)|=|$78 z6FR*VR7=D<#!-q|6frO{goEbkl%l8V(3Bl$Tp~T z27@`6TPX}yNQ4w9sX$61olprY=qRRY6|2X_*?a9jaNQ-J;j!;L&vEB>P#Ybivwbq7 zGDaK8WE_T#rpXKF^iqt(RjNs~9zM5%1(_71agm`Y=1;G@4vD1u?pLVz66;!Gn-E$} zXzRKuG%-~j5}Jrm$91rhdYVwAW1=J?G1?p{9(F_>R*Xh%IWAh)@$o$$$CY@VOUg@= zax!=#Kq4{L)Eh|yFx&7j>&JDYC03IdP0|265f&ps`3kQoMXkl-Cy)FG;nOky`VZe@ z7lznKJmq4gL|TEchO)KH3{phaqNGALQmONfB=6MUy(tyo092BMNGFZqmr&C&H62q; z4oXu@Er}aIMO5Otq{Sp?WgY2A@yDF+*DXP6o5ndE&CGb-O zZW>SdNTFUMo?CNhGO!qosZSXRb;?w18_gv3w{8*=3|c}t>+swy_j7@`g!i5>2VES* zYH+PZ8H-9ZyG!G=DVNE9K+Tv#tMO@BXAc3mg$bxw5)UF96X}?mi3xQ~m_&pPX*wuT zmAFPt*VFs01HzNYKw064BRbzl;kYWHaAY|tGEnYesCQYDLNvtnpg>5=~+!>c!IaT{nK>Fm{cQMPCEwQ zTB10nrV|#rX+mobNp)VOXi`1^eys{=18Inpgqp4oyHH0|laOiys8GkmXpFMp31py< zzCySH;V7eA^_DVnUq=;iaFIxgae)KHeGHZMQ;kYE$|dEe@jVaMbMSl@FYxev58rcf zTnC}RiiXu@4T;gjMw6I2P?3on`lQ}8hEia7;gOeVO<&IOD;6**E>hJHT1`b-a*o5E zT9uS?Xmc zDIeu2G=@T?tSxplGKvegQ2s!-6g2$Dz$2jQmshZ$q{%d0xyl{ zddNnqO}&`IK^n`!0yFi&elTu^5LhYD(xQb$Nhk*iYaV-r`B%N4ImiBjDFcGXlN_zC zv0(FRgFB<;j!Ll$kX326~rW#jq z%Vm5wAmyj=93R(l@q&6%R-IX02hX9-iFJg7QVvSGNZ}%tgCkuWB)j@IvTWLEsI#x2 zdgBi{rE?*#R4dHRzg^6*Bl@iscLS*8)=b|LqgzHOsqIe`r|A>nq7D9oTDG(>U zmf<1(fG;K*#bQ*W+?F<)Fbauk5m6MABpQ=wY{LxjTo>1=4_(jo@m#mjJbcf`cRhU9 zM<_{495C}1aL?cXgK@}YzlnT0M_3-=-e+E5W@{Il zzqgVomf#9PlTa*`7@HVpbgaO@;1I(@BNQeksD-t?^*SQfwSb=Sne3C*UkV0BVNgU}~ zM>y73byv!DTzuhJ2Z@YeddCzNpRnBKCN~o}N>GSRkPbvRGZugKwNF?|A+6bFJ5nyl zGfzFuUb*N4=F!_95fxcOxDHkcix5I%h>SLgO@y(=##T#XwbfP&ZM4j+QF)Mhl+w6Vk#c`HsZB%dscw4;pHg$OMxx;5l@dlgB93jy`|B)NZ#<}t^dquoKa`j>7@g~(W z-x&F|Z!mrSknbQwT{HY)Intw5_B`6L!z+vmpxWrWIK;v`x(>r0rMjj(?B?nXARGxW z4oeI;TB*>ZRlnr0&T`axtM>mfp*;A4%v&gf`oG&-tSS4y+y8X?zW^fZAAD+@m;L|% N002ovPDHLkV1mnDw5$LC diff --git a/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png b/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png deleted file mode 100644 index 7097db05e6c0da7760362b090ad385232f2220b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12946 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 IM6N<$g1}Pepa1{> diff --git a/WebHostLib/static/static/icons/sc2/widowmine.png b/WebHostLib/static/static/icons/sc2/widowmine.png deleted file mode 100644 index 802c49a83d882539becb20df50e6d8a360cc6001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5671 zcmV+?7TD>DP)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X z6 zg27-U0>K~zuqc95)PmF$s-m?{`!-$Mb=tLiY~9-VxawS2`}nc5?bfxkYwhanbavMH z)~cw*MtLbO%7|!e6av8zObCet4G=NH^T#b9)o$(cXvLQE`Q(%PKKFHB_my9M=UnGH z=enU^`lVm`rC)BSjEo;Q3y~zQFOJQpuMKGG{SW{5_=)5HIz&1ovHJAM4Sj8V-hGv2 zhDt+vUu7A6kY$}CY*9rLcGweUeg{HGj@~E`wt_u!C0|uwMFPQ$`@AUt@?2t#L10W7k+$#=L z+}9Td<#n?r(w6_j?FYWBkSh`Z{IZ`Y`f&4yYh8;MKj~V$_{raVhkTf52=IWuLW{k@yaw=m zU2;vFoVFUEIKPmHNCi%p9JwNa3(YnxCf(?be|cpAx-1ZF9S7^(+ zjwKT1jIVp&yeAhOSh8YS_jz%CAr_Oan`LrE0;#EE04Oaz{#|nV?rL$^5?U^X(Jofb z`A@ASA>#C%ySCMBes68y`d3ykEhB}dwkB5o<`)F}83^FwXF`we`4W}!h?tU3+yS(>u!@aSzuIQ{&5QP zkF)fDwvahF0RUsI(fpkWD@^*_V_gmni1{*5Vv)Ff@27jsyFS?w4sidhIW#r4u{lS> z;$<%akeZ%B+T_G*zynyb;t`svHeW0+`1R>EFVA`NyAX#RQ^aM_?lJ<5>Qa8D`E2Xn zPxqREq#5@L?yae@jStpu=En=>Ge28RX+a)utzHR0dPXA4e)Cv2%K#4M>$We3Bnx_G}?$4Gxwg8PpSsX+C)!xm?WQ5(9Tnh-T#< z_j}%5x9z4bqi)*L=*P=H+8iQRN{@bi6d>-Yd7~eBaM51&ef%bsRaLzA&RSwsDwaS0 zJO>XHk~uYvr=FS51CJ*oSIV%|+Ss>2!x5VcfVj9=;^JaCuy0@YGips%21y2!S7IQq z#6ZS`Xz%SG7hV90Z!42jjm^FXP@UFldg;q|&*Q!s*#O)(vnSeE^Tsl=rlpfLExlWp zJf(+e0K0bWX7=p+$hfP=k7eAI$jfi`j&l>nB>eui^I`Y>WYwR$z{Sv9eRoQC=#ZM8L1{rAr3HE2l-KObxu);jeZrq$tSjo)EqQhY`eVn5A3Nsirmous zuz^4(fb*0v>ye^Fog_w`BxcfLE0d;z`tB4~y!0-sUV6HlS@WmgLuo-CZFWl!(9^S7 z^75*y02BPx-aUf3=j`e6k?~21Z2#*gV$ zYXz2R^2*TUm7&f^VbYX^6z%&p0Gs!H20*C9AI-kKR{)!V)7eDszWw}a@$+3zVa7~# z&$4QG7&|s>La(hrf9yE&C<(xGz)b<{hK3bR>(Lxb!6(EIIKqizRk&MS1!L1=BcA%N zSzbn49Z@mQaXIY@pQOww8ny;D%@q)z7AaKvkZG- z)!OCh_hhsAy;WDM_he;djm^!?4Lq28us66>J7}uhjyhe5rohOg>Fb3H9{qLupE^b= zB+H7*m@#Kzw_Xt`n58T-qxXV-xO%1YtY%4{Wmusmr%IuWS+aUfPkihYcip*g#qaDKXWZ?Y@4oSx10m z1Obu}v^1aL;^iQL34Xx}mtFftUp7fsp(bbLx_8PWfaJH|TZk%Fj;XetnwlE2va*O8 z8O5RUi-@K1xX%epQculh^SZexBZFDBTuoA9966tsc6a*fLyGR>X=9``4N0c3sO%~e zaa|WMAjoskxzE$?tnbrHBX_Iylh_F9ko3Ezr@GIJhz%=Y^Oj;fu42;1oyXJK#4}Hf zq0ZWbxxvZiog2{~2W&Yv7{_ zU#2F=i5otYR&Vg`Y+g5FIAa|RmEHq7O!!fUA9XaGH3x&|pM8EUx#z&Pe*j{Dva)i1 z^uTa_wp5QIOiY8##V?kw!(1ZgXNbS#j8%NJAu}+FD&J#gH^= zLgwo92cH^HQ&!mQ>F`|mww6Y*^B$hEYu>|CI>lniPsC!$AFsq-J`_y=Txp)qm+yYj;x{M-i4y#^WMoK~;@BX8nGF{JadbcpD*C6hW z5kizagS$%=3W(zG7=tQK$;7lICZ;8!ic=znq?ymHd9$+Q;1j@B&C%D*Vv%^{O`jz< zytiiM?EA7_lt}dW2QLCx51@=zmKhDktBb4^LXeqD*7Ph2N}780Q&g&FVrqO(iYB-4 zs^bF6bgjMGrzn~zGX-~_AFdKW%7_;8+iVHi5%byp(MI$qkE1_%oXZ}eLg%nMYaMpy zVL%JSs3v3*J28vH2j2lOh(+R|Zjal3(@fGQF(a$We`vbD&mjKxmlNPU6ugJ>)Dx4_ zCZ~^A+wJEhc6-xNcLyCIL*ze@1j|G9Ul_UEUQC6~hM&lS&3>69+7^5UrxF}B7jOAI zJOl2>Q1KC3T?gjcQ+Paq$O7%Ov<*akM-i@;AWokwqv7HZ#34#rJ?Clj5%I(6$>e;| z$l48mBO)>kZMl{~frF^4HLbH+EO)lGy50oO#{ixdUK_4V@G9Q98Ik{B9PV)hgp6I- zY%6>9rkSKZzvz-aUs%IsWF=gENd`=hkBeRltOC%TsCZ6yLdYZ-YP~U>@xf5*&9sc3 zI5IpY6?2s^8%h+9;%>gS3ZyfsdNLD7glva8Wfba^QLI?EofYrxz-qAqkTfo->-e)? zsix7H52eq1#H(6w6r)^5Jp-O>1jwuUS!{N>~V>h)KXyr9%;Z8B?7%itx(-f0^^(oX3HH zo0bn-xpWJ8xw>wq#m7aX*Bj8EG60NC89Qbhzz3T)jZGMx*iwDQ2g4a3s;{tYLiS=N zWG|*9rzff5Zmz&-+IZdP=3jc$<9d8xa!N9mvlc98Edc6X6Ocsm zV5BM`Yx5f`1h8TR%P^UJP(*Z~2)~TE&iujn3FCi%!=1j^SC00ddMtGopcrY0ic9Vw z#DfIpNF_li!b1s^$>wg}`hI75xh_9BIiXQ~(H*dKrLgR&cO(%QkVA6%B+7Ga*s69z z#3QtyZ^PBz0{+opc@=r63`@OD2s=Up&>TNQiy{MMXcNAE3hb^HUf;Mw0BoQbe_!zG zaHCX&qP07F*cF>RZQ@WX6IVaO{ybre+41^INac}~X$^?QmpOT=kpPKu=nV%9z*1A* zeJl-8VQ)N5=pZoFSumL`)xb!A_ut<_a&p3CfULWd$vQ0DmvwhCKYjAw|C#bo85XMm zR&r7T#wsKAmB%qxRxp0%qiA#AC4F)_1^GgDwx-gE)8V8^>LgeyX}`L!#8mazE}*K< z2mybK%Sk|RG%Zd8009yuEl%U*8*Y-88f~~1yL#`5~+kJl@h>o<(9zLKP>tekkr-3qQ~YkGBOOA zavCj724u=<1W2L@kSK4C(@)M^FfciD0n!jv??rcs_OV`B2)lK+({A-jN=~4>yeBW8 zdxd2SmiHtOlF}xkKlBVivSfl}NnB{uVzt@`y#kiSVq^TwM+I}f!ZJmq0@E1*7tZw0(Ln@Wv9v}g++t2NGU(^0%xpsC!oN8B7qu^62PwTmF)*So;m8fSw$d0uy z6F)u!i!ry;k}o)8x4E7%2?{TOuy6^cS`(MNf~YPRGOgA+6SCpa1cy`uaG~WA31I@b zVPP@|lcD`=qgVY2ApsdO5Syk{J6d~8i)4uOHHlRESg>d(0YHER#Gy(qoWFprLDzA^ z0W;MYN)=Jj*V_f?yk2{o-SM%eu-FSg6&wF6;CH>Uu$!)=mwhgfvuV4)xXGzVgM5ip zMsmh@rpI4KB;csi;V|it1xKTZ8i}b!0PSZF2pQg^UkL46s~y;E4gintWaY5#L7sCw zu2iWWb-P-;#NwV>uWXnM-Ep1mhDZ7)V|Bra@}n!;THRX#b6cA`5D08(Y5iqqXJ_lb zyVv2ce?IZ9$uAWb72v2hW3NBMsMNcKfcWUA#3YU)CUF$5=JO2n^YM}dOK5gBbK=+u zh727}#Ar2b%{EkNk6=EP-woEhpDx0Bs)85a*-P0MCd$4rF;qU3fPjG*jpo0%w6^W) zHPibKjPezU5=A26XNyEW`1<^6b56bEobz{%bIu8lb52P|M@Qq= zyww1pKO*eR`J(hN+yAtZ^86Bjs`C62wE3kffPzB>m}&(-eJbsNSI2@bY?n}7cy))Og23yN5E#zKL%8nG>6&qe|b^&eP9*Q&{<8E=>zhlGt*xKro-foZ6;C8!pcTT+1EenZH{?JoFe`3jjtxj0;HHXs<(ByD1;2R?Ft!_KFTCEk@ z63sk|**FXMqt$GD-lnjw}j)+lpN@dcV+Z8O7u}P~{G0IL=jI#4;y|1sYj4ONDyuH1H ze0_bx{y)$06^kF1goZBj6^m83HCPLtUDY{rhPn%|KlR0Nif+Y2712p6frUW1j}KyF zwfsseh*fnjQ)X0F8K3QIfZdXJjS{9WNCt)jGu8Lb&Ah^rZ<1Zrp`0SKCZ`B>_9VKt z_$H1P_D)!APUJF4iM7%B^S%h!EqPy38FObGkZr80BWA>9tPQQ?YRp(0S_$%Rzot|!Wg&slvXH=$eHAdTZ~KAcXK#Ba zJbv!er8|ZEl|rtq)5YoAlcc{&dgN;AGN2gv_?D zTd1b{56ed4^9;d~0AJsLp(sYiVyZs`_T!T^>KGRrRkFmC%z+pnx)OSz}o|Y=wn{^BhmUgquT5oB0w|oBly^ZwU z&Z@G@m+Y;IaK9|Wu}_JKy%#Zd#MtrizDP~wVSN08@DUHjan6O^W|Fzxo|WJ0z_s$E z?lq}RW>wvhuu)$ygTRoqUe~oHg~%f!`{S^y#nEWn6d=#mwve+^1m){Gj_vVYM=lB N002ovPDHLkV1hnhB6a`( diff --git a/WebHostLib/static/static/icons/sc2/widowminehidden.png b/WebHostLib/static/static/icons/sc2/widowminehidden.png deleted file mode 100644 index e568742e8a50c5a1084e6d6de4a43309908f49de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12777 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 diff --git a/WebHostLib/static/styles/globalStyles.css b/WebHostLib/static/styles/globalStyles.css index a787b0c6570a..1a0144830e7c 100644 --- a/WebHostLib/static/styles/globalStyles.css +++ b/WebHostLib/static/styles/globalStyles.css @@ -44,7 +44,7 @@ a{ font-family: LexendDeca-Regular, sans-serif; } -button{ +button, input[type=submit]{ font-weight: 500; font-size: 0.9rem; padding: 10px 17px 11px 16px; /* top right bottom left */ @@ -57,7 +57,7 @@ button{ cursor: pointer; } -button:active{ +button:active, input[type=submit]:active{ border-right: 1px solid rgba(0, 0, 0, 0.5); border-bottom: 1px solid rgba(0, 0, 0, 0.5); padding-right: 16px; @@ -66,11 +66,11 @@ button:active{ margin-bottom: 2px; } -button.button-grass{ +button.button-grass, input[type=submit].button-grass{ border: 1px solid black; } -button.button-dirt{ +button.button-dirt, input[type=submit].button-dirt{ border: 1px solid black; } @@ -111,4 +111,4 @@ h5, h6{ .interactive{ color: #ffef00; -} \ No newline at end of file +} diff --git a/WebHostLib/static/styles/lttp-tracker.css b/WebHostLib/static/styles/lttp-tracker.css deleted file mode 100644 index 899a8f695925..000000000000 --- a/WebHostLib/static/styles/lttp-tracker.css +++ /dev/null @@ -1,75 +0,0 @@ -#player-tracker-wrapper{ - margin: 0; - font-family: LexendDeca-Light, sans-serif; - color: white; - font-size: 14px; -} - -#inventory-table{ - border-top: 2px solid #000000; - border-left: 2px solid #000000; - border-right: 2px solid #000000; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - padding: 3px 3px 10px; - width: 284px; - background-color: #42b149; -} - -#inventory-table td{ - width: 40px; - height: 40px; - text-align: center; - vertical-align: middle; -} - -#inventory-table img{ - height: 100%; - max-width: 40px; - max-height: 40px; - filter: grayscale(100%) contrast(75%) brightness(75%); -} - -#inventory-table img.acquired{ - filter: none; -} - -#inventory-table img.powder-fix{ - width: 35px; - height: 35px; -} - -#location-table{ - width: 284px; - border-left: 2px solid #000000; - border-right: 2px solid #000000; - border-bottom: 2px solid #000000; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - background-color: #42b149; - padding: 0 3px 3px; -} - -#location-table th{ - vertical-align: middle; - text-align: center; - padding-right: 10px; -} - -#location-table td{ - padding-top: 2px; - padding-bottom: 2px; - padding-right: 5px; - line-height: 20px; -} - -#location-table td.counter{ - padding-right: 8px; - text-align: right; -} - -#location-table img{ - height: 100%; - max-width: 30px; - max-height: 30px; -} diff --git a/WebHostLib/static/styles/markdown.css b/WebHostLib/static/styles/markdown.css index dce135588e5f..e0165b7489ef 100644 --- a/WebHostLib/static/styles/markdown.css +++ b/WebHostLib/static/styles/markdown.css @@ -23,7 +23,7 @@ .markdown a{} -.markdown h1{ +.markdown h1, .markdown details summary.h1{ font-size: 52px; font-weight: normal; font-family: LondrinaSolid-Regular, sans-serif; @@ -33,7 +33,7 @@ text-shadow: 1px 1px 4px #000000; } -.markdown h2{ +.markdown h2, .markdown details summary.h2{ font-size: 38px; font-weight: normal; font-family: LondrinaSolid-Light, sans-serif; @@ -45,7 +45,7 @@ text-shadow: 1px 1px 2px #000000; } -.markdown h3{ +.markdown h3, .markdown details summary.h3{ font-size: 26px; font-family: LexendDeca-Regular, sans-serif; text-transform: none; @@ -55,7 +55,7 @@ margin-bottom: 0.5rem; } -.markdown h4{ +.markdown h4, .markdown details summary.h4{ font-family: LexendDeca-Regular, sans-serif; text-transform: none; font-size: 24px; @@ -63,21 +63,21 @@ margin-bottom: 24px; } -.markdown h5{ +.markdown h5, .markdown details summary.h5{ font-family: LexendDeca-Regular, sans-serif; text-transform: none; font-size: 22px; cursor: pointer; } -.markdown h6{ +.markdown h6, .markdown details summary.h6{ font-family: LexendDeca-Regular, sans-serif; text-transform: none; font-size: 20px; cursor: pointer;; } -.markdown h4, .markdown h5,.markdown h6{ +.markdown h4, .markdown h5, .markdown h6{ margin-bottom: 0.5rem; } diff --git a/WebHostLib/static/styles/player-options.css b/WebHostLib/static/styles/player-options.css deleted file mode 100644 index cc2d5e2de5ce..000000000000 --- a/WebHostLib/static/styles/player-options.css +++ /dev/null @@ -1,244 +0,0 @@ -html{ - background-image: url('../static/backgrounds/grass.png'); - background-repeat: repeat; - background-size: 650px 650px; -} - -#player-options{ - box-sizing: border-box; - max-width: 1024px; - margin-left: auto; - margin-right: auto; - background-color: rgba(0, 0, 0, 0.15); - border-radius: 8px; - padding: 1rem; - color: #eeffeb; -} - -#player-options #player-options-button-row{ - display: flex; - flex-direction: row; - justify-content: space-between; - margin-top: 15px; -} - -#player-options code{ - background-color: #d9cd8e; - border-radius: 4px; - padding-left: 0.25rem; - padding-right: 0.25rem; - color: #000000; -} - -#player-options #user-message{ - display: none; - width: calc(100% - 8px); - background-color: #ffe86b; - border-radius: 4px; - color: #000000; - padding: 4px; - text-align: center; -} - -#player-options #user-message.visible{ - display: block; - cursor: pointer; -} - -#player-options h1{ - font-size: 2.5rem; - font-weight: normal; - width: 100%; - margin-bottom: 0.5rem; - text-shadow: 1px 1px 4px #000000; -} - -#player-options h2{ - font-size: 40px; - font-weight: normal; - width: 100%; - margin-bottom: 0.5rem; - text-transform: lowercase; - text-shadow: 1px 1px 2px #000000; -} - -#player-options h3, #player-options h4, #player-options h5, #player-options h6{ - text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); -} - -#player-options input:not([type]){ - border: 1px solid #000000; - padding: 3px; - border-radius: 3px; - min-width: 150px; -} - -#player-options input:not([type]):focus{ - border: 1px solid #ffffff; -} - -#player-options select{ - border: 1px solid #000000; - padding: 3px; - border-radius: 3px; - min-width: 150px; - background-color: #ffffff; -} - -#player-options #game-options, #player-options #rom-options{ - display: flex; - flex-direction: row; -} - -#player-options #meta-options { - display: flex; - justify-content: space-between; - gap: 20px; - padding: 3px; -} - -#player-options div { - display: flex; - flex-grow: 1; -} - -#player-options #meta-options label { - display: inline-block; - min-width: 180px; - flex-grow: 1; -} - -#player-options #meta-options input, -#player-options #meta-options select { - box-sizing: border-box; - min-width: 150px; - width: 50%; -} - -#player-options .left, #player-options .right{ - flex-grow: 1; -} - -#player-options .left{ - margin-right: 10px; -} - -#player-options .right{ - margin-left: 10px; -} - -#player-options table{ - margin-bottom: 30px; - width: 100%; -} - -#player-options table .select-container{ - display: flex; - flex-direction: row; -} - -#player-options table .select-container select{ - min-width: 200px; - flex-grow: 1; -} - -#player-options table select:disabled{ - background-color: lightgray; -} - -#player-options table .range-container{ - display: flex; - flex-direction: row; -} - -#player-options table .range-container input[type=range]{ - flex-grow: 1; -} - -#player-options table .range-value{ - min-width: 20px; - margin-left: 0.25rem; -} - -#player-options table .named-range-container{ - display: flex; - flex-direction: column; -} - -#player-options table .named-range-wrapper{ - display: flex; - flex-direction: row; - margin-top: 0.25rem; -} - -#player-options table .named-range-wrapper input[type=range]{ - flex-grow: 1; -} - -#player-options table .randomize-button { - max-height: 24px; - line-height: 16px; - padding: 2px 8px; - margin: 0 0 0 0.25rem; - font-size: 12px; - border: 1px solid black; - border-radius: 3px; -} - -#player-options table .randomize-button.active { - background-color: #ffef00; /* Same as .interactive in globalStyles.css */ -} - -#player-options table .randomize-button[data-tooltip]::after { - left: unset; - right: 0; -} - -#player-options table label{ - display: block; - min-width: 200px; - margin-right: 4px; - cursor: default; -} - -#player-options th, #player-options td{ - border: none; - padding: 3px; - font-size: 17px; - vertical-align: top; -} - -@media all and (max-width: 1024px) { - #player-options { - border-radius: 0; - } - - #player-options #meta-options { - flex-direction: column; - justify-content: flex-start; - gap: 6px; - } - - #player-options #game-options{ - justify-content: flex-start; - flex-wrap: wrap; - } - - #player-options .left, - #player-options .right { - margin: 0; - } - - #game-options table { - margin-bottom: 0; - } - - #game-options table label{ - display: block; - min-width: 200px; - } - - #game-options table tr td { - width: 50%; - } -} diff --git a/WebHostLib/static/styles/playerOptions/playerOptions.css b/WebHostLib/static/styles/playerOptions/playerOptions.css new file mode 100644 index 000000000000..56c9263d3330 --- /dev/null +++ b/WebHostLib/static/styles/playerOptions/playerOptions.css @@ -0,0 +1,310 @@ +@import "../markdown.css"; +html { + background-image: url("../../static/backgrounds/grass.png"); + background-repeat: repeat; + background-size: 650px 650px; + overflow-x: hidden; +} + +#player-options { + box-sizing: border-box; + max-width: 1024px; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; + word-break: break-word; +} +#player-options #player-options-header h1 { + margin-bottom: 0; + padding-bottom: 0; +} +#player-options #player-options-header h1:nth-child(2) { + font-size: 1.4rem; + margin-top: -8px; + margin-bottom: 0.5rem; +} +#player-options .js-warning-banner { + width: calc(100% - 1rem); + padding: 0.5rem; + border-radius: 4px; + background-color: #f3f309; + color: #000000; + margin-bottom: 0.5rem; + text-align: center; +} +#player-options .group-container { + padding: 0; + margin: 0; +} +#player-options .group-container h2 { + user-select: none; + cursor: unset; +} +#player-options .group-container h2 label { + cursor: pointer; +} +#player-options #player-options-button-row { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 15px; +} +#player-options #user-message { + display: none; + width: calc(100% - 8px); + background-color: #ffe86b; + border-radius: 4px; + color: #000000; + padding: 4px; + text-align: center; + cursor: pointer; +} +#player-options h1 { + font-size: 2.5rem; + font-weight: normal; + width: 100%; + margin-bottom: 0.5rem; + text-shadow: 1px 1px 4px #000000; +} +#player-options h2 { + font-size: 40px; + font-weight: normal; + width: 100%; + margin-bottom: 0.5rem; + text-transform: lowercase; + text-shadow: 1px 1px 2px #000000; +} +#player-options h3, #player-options h4, #player-options h5, #player-options h6 { + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); +} +#player-options input:not([type]) { + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; +} +#player-options input:not([type]):focus { + border: 1px solid #ffffff; +} +#player-options select { + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + background-color: #ffffff; + text-overflow: ellipsis; +} +#player-options .game-options { + display: flex; + flex-direction: row; +} +#player-options .game-options .left, #player-options .game-options .right { + display: grid; + grid-template-columns: 12rem auto; + grid-row-gap: 0.5rem; + grid-auto-rows: min-content; + align-items: start; + min-width: 480px; + width: 50%; +} +#player-options #meta-options { + display: flex; + justify-content: space-between; + gap: 20px; + padding: 3px; +} +#player-options #meta-options input, #player-options #meta-options select { + box-sizing: border-box; + width: 200px; +} +#player-options .left, #player-options .right { + flex-grow: 1; + margin-bottom: 0.5rem; +} +#player-options .left { + margin-right: 20px; +} +#player-options .select-container { + display: flex; + flex-direction: row; + max-width: 270px; +} +#player-options .select-container select { + min-width: 200px; + flex-grow: 1; +} +#player-options .select-container select:disabled { + background-color: lightgray; +} +#player-options .range-container { + display: flex; + flex-direction: row; + max-width: 270px; +} +#player-options .range-container input[type=range] { + flex-grow: 1; +} +#player-options .range-container .range-value { + min-width: 20px; + margin-left: 0.25rem; +} +#player-options .named-range-container { + display: flex; + flex-direction: column; + max-width: 270px; +} +#player-options .named-range-container .named-range-wrapper { + display: flex; + flex-direction: row; + margin-top: 0.25rem; +} +#player-options .named-range-container .named-range-wrapper input[type=range] { + flex-grow: 1; +} +#player-options .free-text-container { + display: flex; + flex-direction: column; + max-width: 270px; +} +#player-options .free-text-container input[type=text] { + flex-grow: 1; +} +#player-options .text-choice-container { + display: flex; + flex-direction: column; + max-width: 270px; +} +#player-options .text-choice-container .text-choice-wrapper { + display: flex; + flex-direction: row; + margin-bottom: 0.25rem; +} +#player-options .text-choice-container .text-choice-wrapper select { + flex-grow: 1; +} +#player-options .option-container { + display: flex; + flex-direction: column; + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(20, 20, 20, 0.25); + border-radius: 3px; + color: #ffffff; + max-height: 10rem; + min-width: 14.5rem; + overflow-y: auto; + padding-right: 0.25rem; + padding-left: 0.25rem; +} +#player-options .option-container .option-divider { + width: 100%; + height: 2px; + background-color: rgba(20, 20, 20, 0.25); + margin-top: 0.125rem; + margin-bottom: 0.125rem; +} +#player-options .option-container .option-entry { + display: flex; + flex-direction: row; + align-items: flex-start; + margin-bottom: 0.125rem; + margin-top: 0.125rem; + user-select: none; +} +#player-options .option-container .option-entry:hover { + background-color: rgba(20, 20, 20, 0.25); +} +#player-options .option-container .option-entry input[type=checkbox] { + margin-right: 0.25rem; +} +#player-options .option-container .option-entry input[type=number] { + max-width: 1.5rem; + max-height: 1rem; + margin-left: 0.125rem; + text-align: center; + /* Hide arrows on input[type=number] fields */ + -moz-appearance: textfield; +} +#player-options .option-container .option-entry input[type=number]::-webkit-outer-spin-button, #player-options .option-container .option-entry input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +#player-options .option-container .option-entry label { + flex-grow: 1; + margin-right: 0; + min-width: unset; + display: unset; +} +#player-options .randomize-button { + display: flex; + flex-direction: column; + justify-content: center; + height: 22px; + max-width: 30px; + margin: 0 0 0 0.25rem; + font-size: 14px; + border: 1px solid black; + border-radius: 3px; + background-color: #d3d3d3; + user-select: none; +} +#player-options .randomize-button:hover { + background-color: #c0c0c0; + cursor: pointer; +} +#player-options .randomize-button label { + line-height: 22px; + padding-left: 5px; + padding-right: 2px; + margin-right: 4px; + width: 100%; + height: 100%; + min-width: unset; +} +#player-options .randomize-button label:hover { + cursor: pointer; +} +#player-options .randomize-button input[type=checkbox] { + display: none; +} +#player-options .randomize-button:has(input[type=checkbox]:checked) { + background-color: #ffef00; /* Same as .interactive in globalStyles.css */ +} +#player-options .randomize-button:has(input[type=checkbox]:checked):hover { + background-color: #eedd27; +} +#player-options .randomize-button[data-tooltip]::after { + left: unset; + right: 0; +} +#player-options label { + display: block; + margin-right: 4px; + cursor: default; + word-break: break-word; +} +#player-options th, #player-options td { + border: none; + padding: 3px; + font-size: 17px; + vertical-align: top; +} + +@media all and (max-width: 1024px) { + #player-options { + border-radius: 0; + } + #player-options #meta-options { + flex-direction: column; + justify-content: flex-start; + gap: 6px; + } + #player-options .game-options { + justify-content: flex-start; + flex-wrap: wrap; + } +} + +/*# sourceMappingURL=playerOptions.css.map */ diff --git a/WebHostLib/static/styles/playerOptions/playerOptions.css.map b/WebHostLib/static/styles/playerOptions/playerOptions.css.map new file mode 100644 index 000000000000..6797b88c7bfe --- /dev/null +++ b/WebHostLib/static/styles/playerOptions/playerOptions.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["playerOptions.scss"],"names":[],"mappings":"AAAQ;AAER;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAEA;EACI;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;AAEA;EACA;;AACA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACI;;AAIR;EACI;;AAGJ;EACI;;AAEA;EACI;;AAIR;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;;AAIR;EACI;IACI;;EAEA;IACI;IACA;IACA;;EAGJ;IACI;IACA","file":"playerOptions.css"} \ No newline at end of file diff --git a/WebHostLib/static/styles/playerOptions/playerOptions.scss b/WebHostLib/static/styles/playerOptions/playerOptions.scss new file mode 100644 index 000000000000..06bde759d263 --- /dev/null +++ b/WebHostLib/static/styles/playerOptions/playerOptions.scss @@ -0,0 +1,364 @@ +@import "../markdown.css"; + +html{ + background-image: url('../../static/backgrounds/grass.png'); + background-repeat: repeat; + background-size: 650px 650px; + overflow-x: hidden; +} + +#player-options{ + box-sizing: border-box; + max-width: 1024px; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; + word-break: break-word; + + #player-options-header{ + h1{ + margin-bottom: 0; + padding-bottom: 0; + } + + h1:nth-child(2){ + font-size: 1.4rem; + margin-top: -8px; + margin-bottom: 0.5rem; + } + } + + .js-warning-banner{ + width: calc(100% - 1rem); + padding: 0.5rem; + border-radius: 4px; + background-color: #f3f309; + color: #000000; + margin-bottom: 0.5rem; + text-align: center; + } + + .group-container{ + padding: 0; + margin: 0; + + h2{ + user-select: none; + cursor: unset; + + label{ + cursor: pointer; + } + } + } + + #player-options-button-row{ + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 15px; + } + + #user-message{ + display: none; + width: calc(100% - 8px); + background-color: #ffe86b; + border-radius: 4px; + color: #000000; + padding: 4px; + text-align: center; + cursor: pointer; + } + + h1{ + font-size: 2.5rem; + font-weight: normal; + width: 100%; + margin-bottom: 0.5rem; + text-shadow: 1px 1px 4px #000000; + } + + h2{ + font-size: 40px; + font-weight: normal; + width: 100%; + margin-bottom: 0.5rem; + text-transform: lowercase; + text-shadow: 1px 1px 2px #000000; + } + + h3, h4, h5, h6{ + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); + } + + input:not([type]){ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + + &:focus{ + border: 1px solid #ffffff; + } + } + + select{ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + background-color: #ffffff; + text-overflow: ellipsis; + } + + .game-options{ + display: flex; + flex-direction: row; + + .left, .right{ + display: grid; + grid-template-columns: 12rem auto; + grid-row-gap: 0.5rem; + grid-auto-rows: min-content; + align-items: start; + min-width: 480px; + width: 50%; + } + } + + #meta-options{ + display: flex; + justify-content: space-between; + gap: 20px; + padding: 3px; + + input, select{ + box-sizing: border-box; + width: 200px; + } + } + + .left, .right{ + flex-grow: 1; + margin-bottom: 0.5rem; + } + + .left{ + margin-right: 20px; + } + + .select-container{ + display: flex; + flex-direction: row; + max-width: 270px; + + select{ + min-width: 200px; + flex-grow: 1; + + &:disabled{ + background-color: lightgray; + } + } + } + + .range-container{ + display: flex; + flex-direction: row; + max-width: 270px; + + input[type=range]{ + flex-grow: 1; + } + + .range-value{ + min-width: 20px; + margin-left: 0.25rem; + } + } + + .named-range-container{ + display: flex; + flex-direction: column; + max-width: 270px; + + .named-range-wrapper{ + display: flex; + flex-direction: row; + margin-top: 0.25rem; + + input[type=range]{ + flex-grow: 1; + } + } + } + + .free-text-container{ + display: flex; + flex-direction: column; + max-width: 270px; + + input[type=text]{ + flex-grow: 1; + } + } + + .text-choice-container{ + display: flex; + flex-direction: column; + max-width: 270px; + + .text-choice-wrapper{ + display: flex; + flex-direction: row; + margin-bottom: 0.25rem; + + select{ + flex-grow: 1; + } + } + } + + .option-container{ + display: flex; + flex-direction: column; + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(20, 20, 20, 0.25); + border-radius: 3px; + color: #ffffff; + max-height: 10rem; + min-width: 14.5rem; + overflow-y: auto; + padding-right: 0.25rem; + padding-left: 0.25rem; + + .option-divider{ + width: 100%; + height: 2px; + background-color: rgba(20, 20, 20, 0.25); + margin-top: 0.125rem; + margin-bottom: 0.125rem; + } + + .option-entry{ + display: flex; + flex-direction: row; + align-items: flex-start; + margin-bottom: 0.125rem; + margin-top: 0.125rem; + user-select: none; + + &:hover{ + background-color: rgba(20, 20, 20, 0.25); + } + + input[type=checkbox]{ + margin-right: 0.25rem; + } + + input[type=number]{ + max-width: 1.5rem; + max-height: 1rem; + margin-left: 0.125rem; + text-align: center; + + /* Hide arrows on input[type=number] fields */ + -moz-appearance: textfield; + &::-webkit-outer-spin-button, &::-webkit-inner-spin-button{ + -webkit-appearance: none; + margin: 0; + } + } + + label{ + flex-grow: 1; + margin-right: 0; + min-width: unset; + display: unset; + } + } + } + + .randomize-button{ + display: flex; + flex-direction: column; + justify-content: center; + height: 22px; + max-width: 30px; + margin: 0 0 0 0.25rem; + font-size: 14px; + border: 1px solid black; + border-radius: 3px; + background-color: #d3d3d3; + user-select: none; + + &:hover{ + background-color: #c0c0c0; + cursor: pointer; + } + + label{ + line-height: 22px; + padding-left: 5px; + padding-right: 2px; + margin-right: 4px; + width: 100%; + height: 100%; + min-width: unset; + &:hover{ + cursor: pointer; + } + } + + input[type=checkbox]{ + display: none; + } + + &:has(input[type=checkbox]:checked){ + background-color: #ffef00; /* Same as .interactive in globalStyles.css */ + + &:hover{ + background-color: #eedd27; + } + } + + &[data-tooltip]::after{ + left: unset; + right: 0; + } + } + + label{ + display: block; + margin-right: 4px; + cursor: default; + word-break: break-word; + } + + th, td{ + border: none; + padding: 3px; + font-size: 17px; + vertical-align: top; + } +} + +@media all and (max-width: 1024px) { + #player-options { + border-radius: 0; + + #meta-options { + flex-direction: column; + justify-content: flex-start; + gap: 6px; + } + + .game-options{ + justify-content: flex-start; + flex-wrap: wrap; + } + } +} diff --git a/WebHostLib/static/styles/sc2Tracker.css b/WebHostLib/static/styles/sc2Tracker.css new file mode 100644 index 000000000000..29a719a110c8 --- /dev/null +++ b/WebHostLib/static/styles/sc2Tracker.css @@ -0,0 +1,160 @@ +#player-tracker-wrapper{ + margin: 0; +} + +#tracker-table td { + vertical-align: top; +} + +.inventory-table-area{ + border: 2px solid #000000; + border-radius: 4px; + padding: 3px 10px 3px 10px; +} + +.inventory-table-area:has(.inventory-table-terran) { + width: 690px; + background-color: #525494; +} + +.inventory-table-area:has(.inventory-table-zerg) { + width: 360px; + background-color: #9d60d2; +} + +.inventory-table-area:has(.inventory-table-protoss) { + width: 400px; + background-color: #d2b260; +} + +#tracker-table .inventory-table td{ + width: 40px; + height: 40px; + text-align: center; + vertical-align: middle; +} + +.inventory-table td.title{ + padding-top: 10px; + height: 20px; + font-family: "JuraBook", monospace; + font-size: 16px; + font-weight: bold; +} + +.inventory-table img{ + height: 100%; + max-width: 40px; + max-height: 40px; + border: 1px solid #000000; + filter: grayscale(100%) contrast(75%) brightness(20%); + background-color: black; +} + +.inventory-table img.acquired{ + filter: none; + background-color: black; +} + +.inventory-table .tint-terran img.acquired { + filter: sepia(100%) saturate(300%) brightness(130%) hue-rotate(120deg) +} + +.inventory-table .tint-protoss img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(180deg) +} + +.inventory-table .tint-level-1 img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) +} + +.inventory-table .tint-level-2 img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(120deg) +} + +.inventory-table .tint-level-3 img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(240deg) +} + +.inventory-table div.counted-item { + position: relative; +} + +.inventory-table div.item-count { + width: 160px; + text-align: left; + color: black; + font-family: "JuraBook", monospace; + font-weight: bold; +} + +#location-table{ + border: 2px solid #000000; + border-radius: 4px; + background-color: #87b678; + padding: 10px 3px 3px; + font-family: "JuraBook", monospace; + font-size: 16px; + font-weight: bold; + cursor: default; +} + +#location-table table{ + width: 100%; +} + +#location-table th{ + vertical-align: middle; + text-align: left; + padding-right: 10px; +} + +#location-table td{ + padding-top: 2px; + padding-bottom: 2px; + line-height: 20px; +} + +#location-table td.counter { + text-align: right; + font-size: 14px; +} + +#location-table td.toggle-arrow { + text-align: right; +} + +#location-table tr#Total-header { + font-weight: bold; +} + +#location-table img{ + height: 100%; + max-width: 30px; + max-height: 30px; +} + +#location-table tbody.locations { + font-size: 16px; +} + +#location-table td.location-name { + padding-left: 16px; +} + +#location-table td:has(.location-column) { + vertical-align: top; +} + +#location-table .location-column { + width: 100%; + height: 100%; +} + +#location-table .location-column .spacer { + min-height: 24px; +} + +.hide { + display: none; +} diff --git a/WebHostLib/static/styles/sc2wolTracker.css b/WebHostLib/static/styles/sc2wolTracker.css deleted file mode 100644 index a7d8bd28c4f8..000000000000 --- a/WebHostLib/static/styles/sc2wolTracker.css +++ /dev/null @@ -1,112 +0,0 @@ -#player-tracker-wrapper{ - margin: 0; -} - -#inventory-table{ - border-top: 2px solid #000000; - border-left: 2px solid #000000; - border-right: 2px solid #000000; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - padding: 3px 3px 10px; - width: 710px; - background-color: #525494; -} - -#inventory-table td{ - width: 40px; - height: 40px; - text-align: center; - vertical-align: middle; -} - -#inventory-table td.title{ - padding-top: 10px; - height: 20px; - font-family: "JuraBook", monospace; - font-size: 16px; - font-weight: bold; -} - -#inventory-table img{ - height: 100%; - max-width: 40px; - max-height: 40px; - border: 1px solid #000000; - filter: grayscale(100%) contrast(75%) brightness(20%); - background-color: black; -} - -#inventory-table img.acquired{ - filter: none; - background-color: black; -} - -#inventory-table div.counted-item { - position: relative; -} - -#inventory-table div.item-count { - text-align: left; - color: black; - font-family: "JuraBook", monospace; - font-weight: bold; -} - -#location-table{ - width: 710px; - border-left: 2px solid #000000; - border-right: 2px solid #000000; - border-bottom: 2px solid #000000; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - background-color: #525494; - padding: 10px 3px 3px; - font-family: "JuraBook", monospace; - font-size: 16px; - font-weight: bold; - cursor: default; -} - -#location-table th{ - vertical-align: middle; - text-align: left; - padding-right: 10px; -} - -#location-table td{ - padding-top: 2px; - padding-bottom: 2px; - line-height: 20px; -} - -#location-table td.counter { - text-align: right; - font-size: 14px; -} - -#location-table td.toggle-arrow { - text-align: right; -} - -#location-table tr#Total-header { - font-weight: bold; -} - -#location-table img{ - height: 100%; - max-width: 30px; - max-height: 30px; -} - -#location-table tbody.locations { - font-size: 16px; -} - -#location-table td.location-name { - padding-left: 16px; -} - -.hide { - display: none; -} diff --git a/WebHostLib/static/styles/supportedGames.css b/WebHostLib/static/styles/supportedGames.css index 7396daa95404..ab12f320716b 100644 --- a/WebHostLib/static/styles/supportedGames.css +++ b/WebHostLib/static/styles/supportedGames.css @@ -8,30 +8,15 @@ cursor: unset; } -#games h1{ +#games h1, #games details summary.h1{ font-size: 60px; cursor: unset; } -#games h2{ +#games h2, #games details summary.h2{ color: #93dcff; margin-bottom: 2px; -} - -#games .collapse-toggle{ - cursor: pointer; -} - -#games h2 .collapse-arrow{ - font-size: 20px; - display: inline-block; /* make vertical-align work */ - padding-bottom: 9px; - vertical-align: middle; - padding-right: 8px; -} - -#games p.collapsed{ - display: none; + text-transform: none; } #games a{ diff --git a/WebHostLib/static/styles/tooltip.css b/WebHostLib/static/styles/tooltip.css index 7cd8463f64a4..dc9026ce6c3d 100644 --- a/WebHostLib/static/styles/tooltip.css +++ b/WebHostLib/static/styles/tooltip.css @@ -12,12 +12,12 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, */ /* Base styles for the element that has a tooltip */ -[data-tooltip], .tooltip { +[data-tooltip], .tooltip-container { position: relative; } /* Base styles for the entire tooltip */ -[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after { +[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip { position: absolute; visibility: hidden; opacity: 0; @@ -39,13 +39,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, pointer-events: none; } -[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after{ +[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before, +.tooltip-container:hover .tooltip { visibility: visible; opacity: 1; + word-break: break-word; } /** Directional arrow styles */ -.tooltip:before, [data-tooltip]:before { +[data-tooltip]:before, .tooltip-container:before { z-index: 10000; border: 6px solid transparent; background: transparent; @@ -53,7 +55,7 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, } /** Content styles */ -.tooltip:after, [data-tooltip]:after { +[data-tooltip]:after, .tooltip { width: 260px; z-index: 10000; padding: 8px; @@ -62,24 +64,26 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, background-color: hsla(0, 0%, 20%, 0.9); color: #fff; content: attr(data-tooltip); - white-space: pre-wrap; font-size: 14px; line-height: 1.2; } -[data-tooltip]:before, [data-tooltip]:after{ +[data-tooltip]:after { + white-space: pre-wrap; +} + +[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip { visibility: hidden; opacity: 0; pointer-events: none; } -[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after, -.tooltip-top:before, .tooltip-top:after { +[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip { bottom: 100%; left: 50%; } -[data-tooltip]:before, .tooltip:before, .tooltip-top:before { +[data-tooltip]:before, .tooltip-container:before { margin-left: -6px; margin-bottom: -12px; border-top-color: #000; @@ -87,19 +91,19 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, } /** Horizontally align tooltips on the top and bottom */ -[data-tooltip]:after, .tooltip:after, .tooltip-top:after { +[data-tooltip]:after, .tooltip { margin-left: -80px; } -[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after, -.tooltip-top:hover:before, .tooltip-top:hover:after { +[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before, +.tooltip-container:hover .tooltip { -webkit-transform: translateY(-12px); -moz-transform: translateY(-12px); transform: translateY(-12px); } /** Tooltips on the left */ -.tooltip-left:before, .tooltip-left:after { +.tooltip-left:before, [data-tooltip].tooltip-left:after, .tooltip-left .tooltip { right: 100%; bottom: 50%; left: auto; @@ -114,14 +118,14 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, border-left-color: hsla(0, 0%, 20%, 0.9); } -.tooltip-left:hover:before, .tooltip-left:hover:after { +.tooltip-left:hover:before, [data-tooltip].tooltip-left:hover:after, .tooltip-left:hover .tooltip { -webkit-transform: translateX(-12px); -moz-transform: translateX(-12px); transform: translateX(-12px); } /** Tooltips on the bottom */ -.tooltip-bottom:before, .tooltip-bottom:after { +.tooltip-bottom:before, [data-tooltip].tooltip-bottom:after, .tooltip-bottom .tooltip { top: 100%; bottom: auto; left: 50%; @@ -135,14 +139,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, border-bottom-color: hsla(0, 0%, 20%, 0.9); } -.tooltip-bottom:hover:before, .tooltip-bottom:hover:after { +.tooltip-bottom:hover:before, [data-tooltip].tooltip-bottom:hover:after, +.tooltip-bottom:hover .tooltip { -webkit-transform: translateY(12px); -moz-transform: translateY(12px); transform: translateY(12px); } /** Tooltips on the right */ -.tooltip-right:before, .tooltip-right:after { +.tooltip-right:before, [data-tooltip].tooltip-right:after, .tooltip-right .tooltip { bottom: 50%; left: 100%; } @@ -155,7 +160,8 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, border-right-color: hsla(0, 0%, 20%, 0.9); } -.tooltip-right:hover:before, .tooltip-right:hover:after { +.tooltip-right:hover:before, [data-tooltip].tooltip-right:hover:after, +.tooltip-right:hover .tooltip { -webkit-transform: translateX(12px); -moz-transform: translateX(12px); transform: translateX(12px); @@ -167,7 +173,16 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, } /** Center content vertically for tooltips ont he left and right */ -.tooltip-left:after, .tooltip-right:after { +[data-tooltip].tooltip-left:after, [data-tooltip].tooltip-right:after, +.tooltip-left .tooltip, .tooltip-right .tooltip { margin-left: 0; margin-bottom: -16px; } + +.tooltip ul, .tooltip ol { + padding-left: 1rem; +} + +.tooltip :last-child { + margin-bottom: 0; +} diff --git a/WebHostLib/static/styles/tracker__ALinkToThePast.css b/WebHostLib/static/styles/tracker__ALinkToThePast.css new file mode 100644 index 000000000000..db5dfcbdfed7 --- /dev/null +++ b/WebHostLib/static/styles/tracker__ALinkToThePast.css @@ -0,0 +1,142 @@ +@import url('https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@100..900&display=swap'); + +.tracker-container { + width: 440px; + box-sizing: border-box; + font-family: "Lexend Deca", Arial, Helvetica, sans-serif; + border: 2px solid black; + border-radius: 4px; + resize: both; + + background-color: #42b149; + color: white; +} + +.hidden { + visibility: hidden; +} + +/** Inventory Grid ****************************************************************************************************/ +.inventory-grid { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + padding: 1rem; + gap: 1rem; +} + +.inventory-grid .item { + position: relative; + display: flex; + justify-content: center; + height: 48px; +} + +.inventory-grid .dual-item { + display: flex; + justify-content: center; +} + +.inventory-grid .missing { + /* Missing items will be in full grayscale to signify "uncollected". */ + filter: grayscale(100%) contrast(75%) brightness(75%); +} + +.inventory-grid .item img, +.inventory-grid .dual-item img { + display: flex; + align-items: center; + text-align: center; + font-size: 0.8rem; + text-shadow: 0 1px 2px black; + font-weight: bold; + image-rendering: crisp-edges; + background-size: contain; + background-repeat: no-repeat; +} + +.inventory-grid .dual-item img { + height: 48px; + margin: 0 -4px; +} + +.inventory-grid .dual-item img:first-child { + align-self: flex-end; +} + +.inventory-grid .item .quantity { + position: absolute; + bottom: 0; + right: 0; + text-align: right; + font-weight: 600; + font-size: 1.75rem; + line-height: 1.75rem; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000; + user-select: none; +} + +/** Regions List ******************************************************************************************************/ +.regions-list { + padding: 1rem; +} + +.regions-list summary { + list-style: none; + display: flex; + gap: 0.5rem; + cursor: pointer; +} + +.regions-list summary::before { + content: "⯈"; + width: 1em; + flex-shrink: 0; +} + +.regions-list details { + font-weight: 300; +} + +.regions-list details[open] > summary::before { + content: "⯆"; +} + +.regions-list .region { + width: 100%; + display: grid; + grid-template-columns: 20fr 8fr 2fr 2fr; + align-items: center; + gap: 4px; + text-align: center; + font-weight: 300; + box-sizing: border-box; +} + +.regions-list .region :first-child { + text-align: left; + font-weight: 500; +} + +.regions-list .region.region-header { + margin-left: 24px; + width: calc(100% - 24px); + padding: 2px; +} + +.regions-list .location-rows { + border-top: 1px solid white; + display: grid; + grid-template-columns: auto 32px; + font-weight: 300; + padding: 2px 8px; + margin-top: 4px; + font-size: 0.8rem; +} + +.regions-list .location-rows :nth-child(even) { + text-align: right; +} diff --git a/WebHostLib/static/styles/weighted-options.css b/WebHostLib/static/styles/weighted-options.css deleted file mode 100644 index 8a66ca237015..000000000000 --- a/WebHostLib/static/styles/weighted-options.css +++ /dev/null @@ -1,315 +0,0 @@ -html{ - background-image: url('../static/backgrounds/grass.png'); - background-repeat: repeat; - background-size: 650px 650px; - scroll-padding-top: 90px; -} - -#weighted-settings{ - max-width: 1000px; - margin-left: auto; - margin-right: auto; - background-color: rgba(0, 0, 0, 0.15); - border-radius: 8px; - padding: 1rem; - color: #eeffeb; -} - -#weighted-settings #games-wrapper{ - width: 100%; -} - -#weighted-settings .setting-wrapper{ - width: 100%; - margin-bottom: 2rem; -} - -#weighted-settings .setting-wrapper .add-option-div{ - display: flex; - flex-direction: row; - justify-content: flex-start; - margin-bottom: 1rem; -} - -#weighted-settings .setting-wrapper .add-option-div button{ - width: auto; - height: auto; - margin: 0 0 0 0.15rem; - padding: 0 0.25rem; - border-radius: 4px; - cursor: default; -} - -#weighted-settings .setting-wrapper .add-option-div button:active{ - margin-bottom: 1px; -} - -#weighted-settings p.setting-description{ - margin: 0 0 1rem; -} - -#weighted-settings p.hint-text{ - margin: 0 0 1rem; - font-style: italic; -} - -#weighted-settings .jump-link{ - color: #ffef00; - cursor: pointer; - text-decoration: underline; -} - -#weighted-settings table{ - width: 100%; -} - -#weighted-settings table th, #weighted-settings table td{ - border: none; -} - -#weighted-settings table td{ - padding: 5px; -} - -#weighted-settings table .td-left{ - font-family: LexendDeca-Regular, sans-serif; - padding-right: 1rem; - width: 200px; -} - -#weighted-settings table .td-middle{ - display: flex; - flex-direction: column; - justify-content: space-evenly; - padding-right: 1rem; -} - -#weighted-settings table .td-right{ - width: 4rem; - text-align: right; -} - -#weighted-settings table .td-delete{ - width: 50px; - text-align: right; -} - -#weighted-settings table .range-option-delete{ - cursor: pointer; -} - -#weighted-settings .items-wrapper{ - display: flex; - flex-direction: row; - justify-content: space-between; -} - -#weighted-settings .items-div h3{ - margin-bottom: 0.5rem; -} - -#weighted-settings .items-wrapper .item-set-wrapper{ - width: 24%; - font-weight: bold; -} - -#weighted-settings .item-container{ - border: 1px solid #ffffff; - border-radius: 2px; - width: 100%; - height: 300px; - overflow-y: auto; - overflow-x: hidden; - margin-top: 0.125rem; - font-weight: normal; -} - -#weighted-settings .item-container .item-div{ - padding: 0.125rem 0.5rem; - cursor: pointer; -} - -#weighted-settings .item-container .item-div:hover{ - background-color: rgba(0, 0, 0, 0.1); -} - -#weighted-settings .item-container .item-qty-div{ - display: flex; - flex-direction: row; - justify-content: space-between; - padding: 0.125rem 0.5rem; - cursor: pointer; -} - -#weighted-settings .item-container .item-qty-div .item-qty-input-wrapper{ - display: flex; - flex-direction: column; - justify-content: space-around; -} - -#weighted-settings .item-container .item-qty-div input{ - min-width: unset; - width: 1.5rem; - text-align: center; -} - -#weighted-settings .item-container .item-qty-div:hover{ - background-color: rgba(0, 0, 0, 0.1); -} - -#weighted-settings .hints-div, #weighted-settings .locations-div{ - margin-top: 2rem; -} - -#weighted-settings .hints-div h3, #weighted-settings .locations-div h3{ - margin-bottom: 0.5rem; -} - -#weighted-settings .hints-container, #weighted-settings .locations-container{ - display: flex; - flex-direction: row; - justify-content: space-between; -} - -#weighted-settings .hints-wrapper, #weighted-settings .locations-wrapper{ - width: calc(50% - 0.5rem); - font-weight: bold; -} - -#weighted-settings .hints-wrapper .simple-list, #weighted-settings .locations-wrapper .simple-list{ - margin-top: 0.25rem; - height: 300px; - font-weight: normal; -} - -#weighted-settings #weighted-settings-button-row{ - display: flex; - flex-direction: row; - justify-content: space-between; - margin-top: 15px; -} - -#weighted-settings code{ - background-color: #d9cd8e; - border-radius: 4px; - padding-left: 0.25rem; - padding-right: 0.25rem; - color: #000000; -} - -#weighted-settings #user-message{ - display: none; - width: calc(100% - 8px); - background-color: #ffe86b; - border-radius: 4px; - color: #000000; - padding: 4px; - text-align: center; -} - -#weighted-settings #user-message.visible{ - display: block; - cursor: pointer; -} - -#weighted-settings h1{ - font-size: 2.5rem; - font-weight: normal; - border-bottom: 1px solid #ffffff; - width: 100%; - margin-bottom: 0.5rem; - color: #ffffff; - text-shadow: 1px 1px 4px #000000; -} - -#weighted-settings h2{ - font-size: 2rem; - font-weight: normal; - border-bottom: 1px solid #ffffff; - width: 100%; - margin-bottom: 0.5rem; - color: #ffe993; - text-transform: none; - text-shadow: 1px 1px 2px #000000; -} - -#weighted-settings h3, #weighted-settings h4, #weighted-settings h5, #weighted-settings h6{ - color: #ffffff; - text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); - text-transform: none; -} - -#weighted-settings a{ - color: #ffef00; - cursor: pointer; -} - -#weighted-settings input:not([type]){ - border: 1px solid #000000; - padding: 3px; - border-radius: 3px; - min-width: 150px; -} - -#weighted-settings input:not([type]):focus{ - border: 1px solid #ffffff; -} - -#weighted-settings select{ - border: 1px solid #000000; - padding: 3px; - border-radius: 3px; - min-width: 150px; - background-color: #ffffff; -} - -#weighted-settings .game-options, #weighted-settings .rom-options{ - display: flex; - flex-direction: column; -} - -#weighted-settings .simple-list{ - display: flex; - flex-direction: column; - - max-height: 300px; - overflow-y: auto; - border: 1px solid #ffffff; - border-radius: 4px; -} - -#weighted-settings .simple-list .list-row label{ - display: block; - width: calc(100% - 0.5rem); - padding: 0.0625rem 0.25rem; -} - -#weighted-settings .simple-list .list-row label:hover{ - background-color: rgba(0, 0, 0, 0.1); -} - -#weighted-settings .simple-list .list-row label input[type=checkbox]{ - margin-right: 0.5rem; -} - -#weighted-settings .simple-list hr{ - width: calc(100% - 2px); - margin: 2px auto; - border-bottom: 1px solid rgb(255 255 255 / 0.6); -} - -#weighted-settings .invisible{ - display: none; -} - -@media all and (max-width: 1000px), all and (orientation: portrait){ - #weighted-settings .game-options{ - justify-content: flex-start; - flex-wrap: wrap; - } - - #game-options table label{ - display: block; - min-width: 200px; - } -} diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.css b/WebHostLib/static/styles/weightedOptions/weightedOptions.css new file mode 100644 index 000000000000..3cfc6d24992d --- /dev/null +++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.css @@ -0,0 +1,232 @@ +html { + background-image: url("../../static/backgrounds/grass.png"); + background-repeat: repeat; + background-size: 650px 650px; + scroll-padding-top: 90px; +} + +#weighted-options { + max-width: 1000px; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; +} +#weighted-options #weighted-options-header h1 { + margin-bottom: 0; + padding-bottom: 0; +} +#weighted-options #weighted-options-header h1:nth-child(2) { + font-size: 1.4rem; + margin-top: -8px; + margin-bottom: 0.5rem; +} +#weighted-options .js-warning-banner { + width: calc(100% - 1rem); + padding: 0.5rem; + border-radius: 4px; + background-color: #f3f309; + color: #000000; + margin-bottom: 0.5rem; + text-align: center; +} +#weighted-options .option-wrapper { + width: 100%; + margin-bottom: 2rem; +} +#weighted-options .option-wrapper .add-option-div { + display: flex; + flex-direction: row; + justify-content: flex-start; + margin-bottom: 1rem; +} +#weighted-options .option-wrapper .add-option-div button { + width: auto; + height: auto; + margin: 0 0 0 0.15rem; + padding: 0 0.25rem; + border-radius: 4px; + cursor: default; +} +#weighted-options .option-wrapper .add-option-div button:active { + margin-bottom: 1px; +} +#weighted-options p.option-description { + margin: 0 0 1rem; +} +#weighted-options p.hint-text { + margin: 0 0 1rem; + font-style: italic; +} +#weighted-options table { + width: 100%; + margin-top: 0.5rem; + margin-bottom: 1.5rem; +} +#weighted-options table th, #weighted-options table td { + border: none; +} +#weighted-options table td { + padding: 5px; +} +#weighted-options table .td-left { + font-family: LexendDeca-Regular, sans-serif; + padding-right: 1rem; + width: 200px; +} +#weighted-options table .td-middle { + display: flex; + flex-direction: column; + justify-content: space-evenly; + padding-right: 1rem; +} +#weighted-options table .td-right { + width: 4rem; + text-align: right; +} +#weighted-options table .td-delete { + width: 50px; + text-align: right; +} +#weighted-options table .range-option-delete { + cursor: pointer; +} +#weighted-options #weighted-options-button-row { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 15px; +} +#weighted-options #user-message { + display: none; + width: calc(100% - 8px); + background-color: #ffe86b; + border-radius: 4px; + color: #000000; + padding: 4px; + text-align: center; +} +#weighted-options #user-message.visible { + display: block; + cursor: pointer; +} +#weighted-options h1 { + font-size: 2.5rem; + font-weight: normal; + width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; +} +#weighted-options h2, #weighted-options details summary.h2 { + font-size: 2rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: none; + text-shadow: 1px 1px 2px #000000; +} +#weighted-options h3, #weighted-options h4, #weighted-options h5, #weighted-options h6 { + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); + text-transform: none; + cursor: unset; +} +#weighted-options h3.option-group-header { + margin-top: 0.75rem; + font-weight: bold; +} +#weighted-options a { + color: #ffef00; + cursor: pointer; +} +#weighted-options input:not([type]) { + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; +} +#weighted-options input:not([type]):focus { + border: 1px solid #ffffff; +} +#weighted-options .invisible { + display: none; +} +#weighted-options .unsupported-option { + margin-top: 0.5rem; +} +#weighted-options .set-container, #weighted-options .dict-container, #weighted-options .list-container { + display: flex; + flex-direction: column; + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(20, 20, 20, 0.25); + border-radius: 3px; + color: #ffffff; + max-height: 15rem; + min-width: 14.5rem; + overflow-y: auto; + padding-right: 0.25rem; + padding-left: 0.25rem; + margin-top: 0.5rem; +} +#weighted-options .set-container .divider, #weighted-options .dict-container .divider, #weighted-options .list-container .divider { + width: 100%; + height: 2px; + background-color: rgba(20, 20, 20, 0.25); + margin-top: 0.125rem; + margin-bottom: 0.125rem; +} +#weighted-options .set-container .set-entry, #weighted-options .set-container .dict-entry, #weighted-options .set-container .list-entry, #weighted-options .dict-container .set-entry, #weighted-options .dict-container .dict-entry, #weighted-options .dict-container .list-entry, #weighted-options .list-container .set-entry, #weighted-options .list-container .dict-entry, #weighted-options .list-container .list-entry { + display: flex; + flex-direction: row; + align-items: flex-start; + padding-bottom: 0.25rem; + padding-top: 0.25rem; + user-select: none; + line-height: 1rem; +} +#weighted-options .set-container .set-entry:hover, #weighted-options .set-container .dict-entry:hover, #weighted-options .set-container .list-entry:hover, #weighted-options .dict-container .set-entry:hover, #weighted-options .dict-container .dict-entry:hover, #weighted-options .dict-container .list-entry:hover, #weighted-options .list-container .set-entry:hover, #weighted-options .list-container .dict-entry:hover, #weighted-options .list-container .list-entry:hover { + background-color: rgba(20, 20, 20, 0.25); +} +#weighted-options .set-container .set-entry input[type=checkbox], #weighted-options .set-container .dict-entry input[type=checkbox], #weighted-options .set-container .list-entry input[type=checkbox], #weighted-options .dict-container .set-entry input[type=checkbox], #weighted-options .dict-container .dict-entry input[type=checkbox], #weighted-options .dict-container .list-entry input[type=checkbox], #weighted-options .list-container .set-entry input[type=checkbox], #weighted-options .list-container .dict-entry input[type=checkbox], #weighted-options .list-container .list-entry input[type=checkbox] { + margin-right: 0.25rem; +} +#weighted-options .set-container .set-entry input[type=number], #weighted-options .set-container .dict-entry input[type=number], #weighted-options .set-container .list-entry input[type=number], #weighted-options .dict-container .set-entry input[type=number], #weighted-options .dict-container .dict-entry input[type=number], #weighted-options .dict-container .list-entry input[type=number], #weighted-options .list-container .set-entry input[type=number], #weighted-options .list-container .dict-entry input[type=number], #weighted-options .list-container .list-entry input[type=number] { + max-width: 1.5rem; + max-height: 1rem; + margin-left: 0.125rem; + text-align: center; + /* Hide arrows on input[type=number] fields */ + -moz-appearance: textfield; +} +#weighted-options .set-container .set-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .set-container .set-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .set-container .dict-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .set-container .dict-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .set-container .list-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .set-container .list-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .dict-container .set-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .dict-container .set-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .dict-container .dict-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .dict-container .dict-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .dict-container .list-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .dict-container .list-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .list-container .set-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .list-container .set-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .list-container .dict-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .list-container .dict-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .list-container .list-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .list-container .list-entry input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +#weighted-options .set-container .set-entry label, #weighted-options .set-container .dict-entry label, #weighted-options .set-container .list-entry label, #weighted-options .dict-container .set-entry label, #weighted-options .dict-container .dict-entry label, #weighted-options .dict-container .list-entry label, #weighted-options .list-container .set-entry label, #weighted-options .list-container .dict-entry label, #weighted-options .list-container .list-entry label { + flex-grow: 1; + margin-right: 0; + min-width: unset; + display: unset; +} + +.hidden { + display: none; +} + +@media all and (max-width: 1000px), all and (orientation: portrait) { + #weighted-options .game-options { + justify-content: flex-start; + flex-wrap: wrap; + } + #game-options table label { + display: block; + min-width: 200px; + } +} + +/*# sourceMappingURL=weightedOptions.css.map */ diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map b/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map new file mode 100644 index 000000000000..7c57cde01506 --- /dev/null +++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["weightedOptions.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAOZ;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAIR;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIA;EACI;EACA;;AAIR;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;AAEA;EACA;;AACA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;;AAMhB;EACI;;;AAGJ;EACI;IACI;IACA;;EAGJ;IACI;IACA","file":"weightedOptions.css"} \ No newline at end of file diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.scss b/WebHostLib/static/styles/weightedOptions/weightedOptions.scss new file mode 100644 index 000000000000..7ff3a2c3722e --- /dev/null +++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.scss @@ -0,0 +1,274 @@ +html{ + background-image: url('../../static/backgrounds/grass.png'); + background-repeat: repeat; + background-size: 650px 650px; + scroll-padding-top: 90px; +} + +#weighted-options{ + max-width: 1000px; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; + + #weighted-options-header{ + h1{ + margin-bottom: 0; + padding-bottom: 0; + } + + h1:nth-child(2){ + font-size: 1.4rem; + margin-top: -8px; + margin-bottom: 0.5rem; + } + } + + .js-warning-banner{ + width: calc(100% - 1rem); + padding: 0.5rem; + border-radius: 4px; + background-color: #f3f309; + color: #000000; + margin-bottom: 0.5rem; + text-align: center; + } + + .option-wrapper{ + width: 100%; + margin-bottom: 2rem; + + .add-option-div{ + display: flex; + flex-direction: row; + justify-content: flex-start; + margin-bottom: 1rem; + + button{ + width: auto; + height: auto; + margin: 0 0 0 0.15rem; + padding: 0 0.25rem; + border-radius: 4px; + cursor: default; + + &:active{ + margin-bottom: 1px; + } + } + } + } + + p{ + &.option-description{ + margin: 0 0 1rem; + } + + &.hint-text{ + margin: 0 0 1rem; + font-style: italic; + }; + } + + table{ + width: 100%; + margin-top: 0.5rem; + margin-bottom: 1.5rem; + + th, td{ + border: none; + } + + td{ + padding: 5px; + } + + .td-left{ + font-family: LexendDeca-Regular, sans-serif; + padding-right: 1rem; + width: 200px; + } + + .td-middle{ + display: flex; + flex-direction: column; + justify-content: space-evenly; + padding-right: 1rem; + } + + .td-right{ + width: 4rem; + text-align: right; + } + + .td-delete{ + width: 50px; + text-align: right; + } + + .range-option-delete{ + cursor: pointer; + } + } + + #weighted-options-button-row{ + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 15px; + } + + #user-message{ + display: none; + width: calc(100% - 8px); + background-color: #ffe86b; + border-radius: 4px; + color: #000000; + padding: 4px; + text-align: center; + + &.visible{ + display: block; + cursor: pointer; + } + } + + h1{ + font-size: 2.5rem; + font-weight: normal; + width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; + } + + h2, details summary.h2{ + font-size: 2rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: none; + text-shadow: 1px 1px 2px #000000; + } + + h3, h4, h5, h6{ + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); + text-transform: none; + cursor: unset; + } + + h3{ + &.option-group-header{ + margin-top: 0.75rem; + font-weight: bold; + } + } + + a{ + color: #ffef00; + cursor: pointer; + } + + input:not([type]){ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + + &:focus{ + border: 1px solid #ffffff; + } + } + + .invisible{ + display: none; + } + + .unsupported-option{ + margin-top: 0.5rem; + } + + .set-container, .dict-container, .list-container{ + display: flex; + flex-direction: column; + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(20, 20, 20, 0.25); + border-radius: 3px; + color: #ffffff; + max-height: 15rem; + min-width: 14.5rem; + overflow-y: auto; + padding-right: 0.25rem; + padding-left: 0.25rem; + margin-top: 0.5rem; + + .divider{ + width: 100%; + height: 2px; + background-color: rgba(20, 20, 20, 0.25); + margin-top: 0.125rem; + margin-bottom: 0.125rem; + } + + .set-entry, .dict-entry, .list-entry{ + display: flex; + flex-direction: row; + align-items: flex-start; + padding-bottom: 0.25rem; + padding-top: 0.25rem; + user-select: none; + line-height: 1rem; + + &:hover{ + background-color: rgba(20, 20, 20, 0.25); + } + + input[type=checkbox]{ + margin-right: 0.25rem; + } + + input[type=number]{ + max-width: 1.5rem; + max-height: 1rem; + margin-left: 0.125rem; + text-align: center; + + /* Hide arrows on input[type=number] fields */ + -moz-appearance: textfield; + &::-webkit-outer-spin-button, &::-webkit-inner-spin-button{ + -webkit-appearance: none; + margin: 0; + } + } + + label{ + flex-grow: 1; + margin-right: 0; + min-width: unset; + display: unset; + } + } + } +} + +.hidden{ + display: none; +} + +@media all and (max-width: 1000px), all and (orientation: portrait){ + #weighted-options .game-options{ + justify-content: flex-start; + flex-wrap: wrap; + } + + #game-options table label{ + display: block; + min-width: 200px; + } +} diff --git a/WebHostLib/templates/hostRoom.html b/WebHostLib/templates/hostRoom.html index 2981c41452f0..fa8e26c2cbf8 100644 --- a/WebHostLib/templates/hostRoom.html +++ b/WebHostLib/templates/hostRoom.html @@ -24,7 +24,8 @@
{% endif %} {% if room.tracker %} - This room has a
Multiworld Tracker enabled. + This room has a Multiworld Tracker + and a Sphere Tracker enabled.
{% endif %} The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity. @@ -43,7 +44,7 @@ {{ macros.list_patches_room(room) }} {% if room.owner == session["_id"] %}

-
+
-
- {% endif %}
diff --git a/WebHostLib/templates/lttpTracker.html b/WebHostLib/templates/lttpTracker.html deleted file mode 100644 index 3f1c35793eeb..000000000000 --- a/WebHostLib/templates/lttpTracker.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - {{ player_name }}'s Tracker - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - {% if key_locations and "Universal" not in key_locations %} - - {% endif %} - {% if big_key_locations %} - - {% endif %} - - {% for area in sp_areas %} - - - - {% if key_locations and "Universal" not in key_locations %} - - {% endif %} - {% if big_key_locations %} - - {% endif %} - - {% endfor %} -
{{ area }}{{ checks_done[area] }} / {{ checks_in_area[area] }} - {{ inventory[small_key_ids[area]] if area in key_locations else '—' }} - - {{ '✔' if area in big_key_locations and inventory[big_key_ids[area]] else ('—' if area not in big_key_locations else '') }} -
-
- - diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html index 9cb48009a427..7bbb894de090 100644 --- a/WebHostLib/templates/macros.html +++ b/WebHostLib/templates/macros.html @@ -47,9 +47,6 @@ {% elif patch.game | supports_apdeltapatch %} Download Patch File... - {% elif patch.game == "Dark Souls III" %} - - Download JSON File... {% elif patch.game == "Final Fantasy Mystic Quest" %} Download APMQ File... diff --git a/WebHostLib/templates/multispheretracker.html b/WebHostLib/templates/multispheretracker.html new file mode 100644 index 000000000000..a86697498396 --- /dev/null +++ b/WebHostLib/templates/multispheretracker.html @@ -0,0 +1,72 @@ +{% extends "tablepage.html" %} +{% block head %} + {{ super() }} + Multiworld Sphere Tracker + + +{% endblock %} + +{% block body %} + {% include "header/dirtHeader.html" %} + +
+
+ + +
+ {% if tracker_data.get_spheres() %} + This tracker lists already found locations by their logical access sphere. + It ignores items that cannot be sent + and will therefore differ from the sphere numbers in the spoiler playthrough. + This tracker will automatically update itself periodically. + {% else %} + This Multiworld has no Sphere data, likely due to being too old, cannot display data. + {% endif %} +
+
+ +
+ {%- for team, players in tracker_data.get_all_players().items() %} +
+ + + + + {#- Mimicking hint table header for familiarity. #} + + + + + + + + + {%- for sphere in tracker_data.get_spheres() %} + {%- set current_sphere = loop.index %} + {%- for player, sphere_location_ids in sphere.items() %} + {%- set checked_locations = tracker_data.get_player_checked_locations(team, player) %} + {%- set finder_game = tracker_data.get_player_game(team, player) %} + {%- set player_location_data = tracker_data.get_player_locations(team, player) %} + {%- for location_id in sphere_location_ids.intersection(checked_locations) %} + + {%- set item_id, receiver, item_flags = player_location_data[location_id] %} + {%- set receiver_game = tracker_data.get_player_game(team, receiver) %} + + + + + + + + {%- endfor %} + + {%- endfor %} + {%- endfor %} + +
SphereFinderReceiverItemLocationGame
{{ current_sphere }}{{ tracker_data.get_player_name(team, player) }}{{ tracker_data.get_player_name(team, receiver) }}{{ tracker_data.item_id_to_name[receiver_game][item_id] }}{{ tracker_data.location_id_to_name[finder_game][location_id] }}{{ finder_game }}
+
+ + {%- endfor -%} +
+
+{% endblock %} diff --git a/WebHostLib/templates/multitracker.html b/WebHostLib/templates/multitracker.html index b16d4714ec6a..1b371b1229e5 100644 --- a/WebHostLib/templates/multitracker.html +++ b/WebHostLib/templates/multitracker.html @@ -10,7 +10,7 @@ {% include "header/dirtHeader.html" %} {% include "multitrackerNavigation.html" %} -
+
diff --git a/WebHostLib/templates/multitracker__ALinkToThePast.html b/WebHostLib/templates/multitracker__ALinkToThePast.html index 8cea5ba05785..9b8f460c4cc3 100644 --- a/WebHostLib/templates/multitracker__ALinkToThePast.html +++ b/WebHostLib/templates/multitracker__ALinkToThePast.html @@ -6,52 +6,42 @@ {% endblock %} {# List all tracker-relevant icons. Format: (Name, Image URL) #} -{%- set icons = { - "Blue Shield": "https://www.zeldadungeon.net/wiki/images/8/85/Fighters-Shield.png", - "Red Shield": "https://www.zeldadungeon.net/wiki/images/5/55/Fire-Shield.png", - "Mirror Shield": "https://www.zeldadungeon.net/wiki/images/8/84/Mirror-Shield.png", - "Fighter Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/40/SFighterSword.png?width=1920", - "Master Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/SMasterSword.png?width=1920", - "Tempered Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/92/STemperedSword.png?width=1920", - "Golden Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/2/28/SGoldenSword.png?width=1920", - "Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=5f85a70e6366bf473544ef93b274f74c", - "Silver Bow": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/Bow.png?width=1920", - "Green Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c9/SGreenTunic.png?width=1920", - "Blue Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/98/SBlueTunic.png?width=1920", - "Red Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/7/74/SRedTunic.png?width=1920", - "Power Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/f/f5/SPowerGlove.png?width=1920", - "Titan Mitts": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920", - "Progressive Sword": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/cc/ALttP_Master_Sword_Sprite.png?version=55869db2a20e157cd3b5c8f556097725", - "Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png?version=405f42f97240c9dcd2b71ffc4bebc7f9", - "Progressive Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920", - "Flippers": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/4c/ZoraFlippers.png?width=1920", - "Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png?version=d601542d5abcc3e006ee163254bea77e", - "Progressive Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=cfb7648b3714cccc80e2b17b2adf00ed", - "Blue Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c3/ALttP_Boomerang_Sprite.png?version=96127d163759395eb510b81a556d500e", - "Red Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Magical_Boomerang_Sprite.png?version=47cddce7a07bc3e4c2c10727b491f400", - "Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png?version=c90bc8e07a52e8090377bd6ef854c18b", - "Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png?version=1f1acb30d71bd96b60a3491e54bbfe59", - "Magic Powder": "https://www.zeldadungeon.net/wiki/images/thumb/6/62/MagicPowder-ALttP-Sprite.png/86px-MagicPowder-ALttP-Sprite.png", - "Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png?version=6eabc9f24d25697e2c4cd43ddc8207c0", - "Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png?version=1f944148223d91cfc6a615c92286c3bc", - "Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png?version=f4d6aba47fb69375e090178f0fc33b26", - "Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png?version=34027651a5565fcc5a83189178ab17b5", - "Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png?version=efd64d451b1831bd59f7b7d6b61b5879", - "Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png?version=e76eaa1ec509c9a5efb2916698d5a4ce", - "Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png?version=e0adec227193818dcaedf587eba34500", - "Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png?version=e73d1ce0115c2c70eaca15b014bd6f05", - "Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png?version=ec4982b31c56da2c0c010905c5c60390", - "Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png?version=4d40e0ee015b687ff75b333b968d8be6", - "Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png?version=11e4632bba54f6b9bf921df06ac93744", - "Bottle": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png?version=fd98ab04db775270cbe79fce0235777b", - "Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png?version=8cc1900dfd887890badffc903bb87943", - "Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png?version=758b607c8cbe2cf1900d42a0b3d0fb54", - "Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png?version=6b77f0d609aab0c751307fc124736832", - "Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png?version=e035dbc9cbe2a3bd44aa6d047762b0cc", - "Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png?version=dc398e1293177581c16303e4f9d12a48", +{% set icons = { + "Blue Shield": "https://www.zeldadungeon.net/wiki/images/thumb/c/c3/FightersShield-ALttP-Sprite.png/100px-FightersShield-ALttP-Sprite.png", + "Red Shield": "https://www.zeldadungeon.net/wiki/images/thumb/9/9e/FireShield-ALttP-Sprite.png/111px-FireShield-ALttP-Sprite.png", + "Mirror Shield": "https://www.zeldadungeon.net/wiki/images/thumb/e/e3/MirrorShield-ALttP-Sprite.png/105px-MirrorShield-ALttP-Sprite.png", + "Progressive Sword": "https://static.wikia.nocookie.net/zelda_gamepedia_en/images/c/cc/ALttP_Master_Sword_Sprite.png", + "Progressive Bow": "https://www.zeldadungeon.net/wiki/images/thumb/8/8c/BowArrows-ALttP-Sprite.png/120px-BowArrows-ALttP-Sprite.png", + "Progressive Glove": "https://www.zeldadungeon.net/wiki/images/thumb/4/41/PowerGlove-ALttP-Sprite.png/105px-PowerGlove-ALttP-Sprite.png", + "Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png", + "Flippers": "https://www.zeldadungeon.net/wiki/images/thumb/b/bc/ZoraFlippers-ALttP-Sprite.png/112px-ZoraFlippers-ALttP-Sprite.png", + "Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png", + "Blue Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/f/f0/Boomerang-ALttP-Sprite.png/86px-Boomerang-ALttP-Sprite.png", + "Red Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/3/3c/MagicalBoomerang-ALttP-Sprite.png/86px-MagicalBoomerang-ALttP-Sprite.png", + "Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png", + "Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png", + "Magic Powder": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png", + "Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png", + "Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png", + "Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png", + "Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png", + "Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png", + "Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png", + "Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png", + "Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png", + "Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png", + "Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png", + "Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png", + "Bottles": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png", + "Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png", + "Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png", + "Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png", + "Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png", + "Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png", "Triforce Piece": "https://www.zeldadungeon.net/wiki/images/thumb/5/54/Triforce_Fragment_-_BS_Zelda.png/62px-Triforce_Fragment_-_BS_Zelda.png", - "Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png?version=4f35d92842f0de39d969181eea03774e", - "Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png?version=136dfa418ba76c8b4e270f466fc12f4d", + "Bombs": "https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/38/ALttP_Bomb_Sprite.png", + "Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png", + "Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png", "Chest": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Treasure_Chest_Sprite.png?version=5f530ecd98dcb22251e146e8049c0dda", "Light World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e7/ALttP_Soldier_Green_Sprite.png?version=d650d417934cd707a47e496489c268a6", "Dark World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/94/ALttP_Moblin_Sprite.png?version=ebf50e33f4657c377d1606bcc0886ddc", @@ -68,33 +58,93 @@ "Misery Mire": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/85/ALttP_Vitreous_Sprite.png?version=92b2e9cb0aa63f831760f08041d8d8d8", "Turtle Rock": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/91/ALttP_Trinexx_Sprite.png?version=0cc867d513952aa03edd155597a0c0be", "Ganons Tower": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Ganon_Sprite.png?version=956f51f054954dfff53c1a9d4f929c74", -} -%} +} %} + +{% set inventory_order = [ + "Progressive Sword", + "Progressive Bow", + "Blue Boomerang", + "Red Boomerang", + "Hookshot", + "Bombs", + "Mushroom", + "Magic Powder", + "Fire Rod", + "Ice Rod", + "Bombos", + "Ether", + "Quake", + "Lamp", + "Hammer", + "Flute", + "Bug Catching Net", + "Book of Mudora", + "Cane of Somaria", + "Cane of Byrna", + "Cape", + "Magic Mirror", + "Shovel", + "Pegasus Boots", + "Flippers", + "Progressive Glove", + "Moon Pearl", + "Bottles", + "Triforce Piece", + "Triforce", +] %} + +{% set dungeon_keys = { + "Hyrule Castle": ("Small Key (Hyrule Castle)", "Big Key (Hyrule Castle)"), + "Agahnims Tower": ("Small Key (Agahnims Tower)", "Big Key (Agahnims Tower)"), + "Eastern Palace": ("Small Key (Eastern Palace)", "Big Key (Eastern Palace)"), + "Desert Palace": ("Small Key (Desert Palace)", "Big Key (Desert Palace)"), + "Tower of Hera": ("Small Key (Tower of Hera)", "Big Key (Tower of Hera)"), + "Palace of Darkness": ("Small Key (Palace of Darkness)", "Big Key (Palace of Darkness)"), + "Thieves Town": ("Small Key (Thieves Town)", "Big Key (Thieves Town)"), + "Skull Woods": ("Small Key (Skull Woods)", "Big Key (Skull Woods)"), + "Swamp Palace": ("Small Key (Swamp Palace)", "Big Key (Swamp Palace)"), + "Ice Palace": ("Small Key (Ice Palace)", "Big Key (Ice Palace)"), + "Misery Mire": ("Small Key (Misery Mire)", "Big Key (Misery Mire)"), + "Turtle Rock": ("Small Key (Turtle Rock)", "Big Key (Turtle Rock)"), + "Ganons Tower": ("Small Key (Ganons Tower)", "Big Key (Ganons Tower)"), +} %} + +{% set multi_items = [ + "Progressive Sword", + "Progressive Glove", + "Progressive Bow", + "Bottles", + "Triforce Piece", +] %} {%- block custom_table_headers %} -{#- macro that creates a table header with display name and image -#} -{%- macro make_header(name, img_src) %} - - {{ name }} - -{% endmacro -%} - -{#- call the macro to build the table header -#} -{%- for name in tracking_names %} - {%- if name in icons -%} + {#- macro that creates a table header with display name and image -#} + {%- macro make_header(name, img_src) %} - {{ name | e }} + {{ name }} - {%- endif %} -{% endfor -%} + {% endmacro -%} + + {#- call the macro to build the table header -#} + {%- for item in inventory_order %} + {%- if item in icons -%} + + {{ item | e }} + + {%- endif %} + {% endfor -%} {% endblock %} {# build each row of custom entries #} {% block custom_table_row scoped %} - {%- for id in tracking_ids -%} -{# {{ checks }}#} - {%- if inventories[(team, player)][id] -%} + {%- for item in inventory_order -%} + {%- if inventories[(team, player)][item] -%} - {% if id in multi_items %}{{ inventories[(team, player)][id] }}{% else %}✔ï¸{% endif %} + {% if item in multi_items %} + {{ inventories[(team, player)][item] }} + {% else %} + âœ”ï¸ + {% endif %} {%- else -%} @@ -104,102 +154,95 @@ {% block custom_tables %} -{% for team, _ in total_team_locations.items() %} -
- - - - - - {% for area in ordered_areas %} - {% set colspan = 1 %} - {% if area in key_locations %} - {% set colspan = colspan + 1 %} - {% endif %} - {% if area in big_key_locations %} - {% set colspan = colspan + 1 %} - {% endif %} - {% if area in icons %} - - {%- else -%} - - {%- endif -%} - {%- endfor -%} - - - - - {% for area in ordered_areas %} +{% for team in total_team_locations %} +
+
#Name - {{ area }}{{ area }}%Last
Activity
+ + + + + {% for region in known_regions %} + {% set colspan = 1 %} + {% if region == "Agahnims Tower" %} + {% set colspan = 2 %} + {% elif region in dungeon_keys %} + {% set colspan = 3 %} + {% endif %} + + {% if region in icons %} + + {% else %} + + {% endif %} + {% endfor %} + + + + + {% for region in known_regions %} + + + {% if region in dungeon_keys %} + + + {# Special check just for Agahnims Tower, which has no big keys. #} + {% if region != "Agahnims Tower" %} + + {% endif %} + {% endif %} + {% endfor %} + + {# For "total" checks #} - {% if area in key_locations %} - - {% endif %} - {% if area in big_key_locations %} - - {%- endif -%} - {%- endfor -%} - - - - {%- for (checks_team, player), area_checks in checks_done.items() if games[(team, player)] == current_tracker and team == checks_team -%} - - - - {%- for area in ordered_areas -%} - {% if (team, player) in checks_in_area and area in checks_in_area[(team, player)] %} - {%- set checks_done = area_checks[area] -%} - {%- set checks_total = checks_in_area[(team, player)][area] -%} - {%- if checks_done == checks_total -%} + + + + + {% for (player_team, player), player_regions in regions.items() if team == player_team %} + + + + + {% for region, counts in player_regions.items() %} - {%- else -%} - - {%- endif -%} - {%- if area in key_locations -%} - - {%- endif -%} - {%- if area in big_key_locations -%} - - {%- endif -%} - {% else %} - - {%- if area in key_locations -%} - - {%- endif -%} - {%- if area in big_key_locations -%} - - {%- endif -%} - {% endif %} - {%- endfor -%} - - - - {%- if activity_timers[(team, player)] -%} - - {%- else -%} - - {%- endif -%} - - {%- endfor -%} - -
#Name + {{ region }} + {{ region }}Total
+ Checks + + Small Key + + Big Key + - Checks + Checks - Small Key - - Big Key -
{{ player }}{{ player_names_with_alias[(team, player)] | e }}
+ + {{ player }} + + {{ player_names_with_alias[(team, player)] | e }} - {{ checks_done }}/{{ checks_total }}{{ checks_done }}/{{ checks_total }}{{ inventories[(team, player)][small_key_ids[area]] }}{% if inventories[(team, player)][big_key_ids[area]] %}✔ï¸{% endif %} - {% set location_count = locations[(team, player)] | length %} - {%- if locations[(team, player)] | length > 0 -%} - {% set percentage_of_completion = locations_complete[(team, player)] / location_count * 100 %} - {{ "{0:.2f}".format(percentage_of_completion) }} - {%- else -%} - 100.00 - {%- endif -%} - {{ activity_timers[(team, player)].total_seconds() }}None
-
+ {{ counts.checked }}/{{ counts.total }} + + + {% if region in dungeon_keys %} + + {{ inventories[(team, player)][dungeon_keys[region][0]] }} + + + {# Special check just for Agahnims Tower, which has no big keys. #} + {% if region != "Agahnims Tower" %} + + {% if inventories[(team, player)][dungeon_keys[region][1]] %} + âœ”ï¸ + {% endif %} + + {% endif %} + {% endif %} + {% endfor %} + + {% endfor %} + + + +
{% endfor %} {% endblock %} diff --git a/WebHostLib/templates/ootTracker.html b/WebHostLib/templates/ootTracker.html deleted file mode 100644 index ea7a6d5a4c30..000000000000 --- a/WebHostLib/templates/ootTracker.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - {{ player_name }}'s Tracker - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
{{ hookshot_length }}
-
-
-
- -
{{ bottle_count if bottle_count > 0 else '' }}
-
-
-
- -
{{ wallet_size }}
-
-
-
- -
Zelda
-
-
-
- -
Epona
-
-
-
- -
Saria
-
-
-
- -
Sun
-
-
-
- -
Time
-
-
-
- -
Storms
-
-
-
- -
{{ token_count }}
-
-
-
- -
Min
-
-
-
- -
Bol
-
-
-
- -
Ser
-
-
-
- -
Req
-
-
-
- -
Noc
-
-
-
- -
Pre
-
-
-
- -
{{ piece_count if piece_count > 0 else '' }}
-
-
- - - - - - - - {% for area in checks_done %} - - - - - - - - {% for location in location_info[area] %} - - - - - - - {% endfor %} - - {% endfor %} -
Items
{{ area }} {{'â–¼' if area != 'Total'}}{{ small_key_counts.get(area, '-') }}{{ boss_key_counts.get(area, '-') }}{{ checks_done[area] }} / {{ checks_in_area[area] }}
{{ location }}{{ '✔' if location_info[area][location] else '' }}
-
- - diff --git a/WebHostLib/templates/player-options.html b/WebHostLib/templates/player-options.html deleted file mode 100644 index 4c749752882a..000000000000 --- a/WebHostLib/templates/player-options.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends 'pageWrapper.html' %} - -{% block head %} - {{ game }} Options - - - - - - -{% endblock %} - -{% block body %} - {% include 'header/'+theme+'Header.html' %} -
-
-

Player Options

-

Choose the options you would like to play with! You may generate a single-player game from this page, - or download an options file you can use to participate in a MultiWorld.

- -

- A more advanced options configuration for all games can be found on the - Weighted options page. -
- A list of all games you have generated can be found on the User Content Page. -
- You may also download the - template file for this game. -

- -
-
- - -
-
- - -
- -
- -

Game Options

-
-
-
-
- -
- - - -
-
-{% endblock %} diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html new file mode 100644 index 000000000000..30a4fc78dff3 --- /dev/null +++ b/WebHostLib/templates/playerOptions/macros.html @@ -0,0 +1,221 @@ +{% macro Toggle(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ + {{ RandomizeButton(option_name, option) }} +
+{% endmacro %} + +{% macro Choice(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ + {{ RandomizeButton(option_name, option) }} +
+{% endmacro %} + +{% macro Range(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ + + {{ option.default | default(option.range_start) if option.default != "random" else option.range_start }} + + {{ RandomizeButton(option_name, option) }} +
+{% endmacro %} + +{% macro NamedRange(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ +
+ + + {{ option.default | default(option.range_start) if option.default != "random" else option.range_start }} + + {{ RandomizeButton(option_name, option) }} +
+
+{% endmacro %} + +{% macro FreeText(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ +
+{% endmacro %} + +{% macro TextChoice(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+
+ + {{ RandomizeButton(option_name, option) }} +
+ +
+{% endmacro %} + +{% macro ItemDict(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro OptionList(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ {% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro LocationSet(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ {% for group_name in world.location_name_groups.keys()|sort %} + {% if group_name != "Everywhere" %} +
+ + +
+ {% endif %} + {% endfor %} + {% if world.location_name_groups.keys()|length > 1 %} +
 
+ {% endif %} + {% for location_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.location_names|sort) %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro ItemSet(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ {% for group_name in world.item_name_groups.keys()|sort %} + {% if group_name != "Everything" %} +
+ + +
+ {% endif %} + {% endfor %} + {% if world.item_name_groups.keys()|length > 1 %} +
 
+ {% endif %} + {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro OptionSet(option_name, option) %} + {{ OptionTitle(option_name, option) }} +
+ {% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %} +
+ + +
+ {% endfor %} +
+{% endmacro %} + +{% macro OptionTitle(option_name, option) %} + +{% endmacro %} + +{% macro RandomizeButton(option_name, option) %} +
+ +
+{% endmacro %} diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html new file mode 100644 index 000000000000..73de5d56eb20 --- /dev/null +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -0,0 +1,166 @@ +{% extends 'pageWrapper.html' %} +{% import 'playerOptions/macros.html' as inputs with context %} + +{% block head %} + {{ world_name }} Options + + + + + + +{% endblock %} + +{% block body %} + {% include 'header/'+theme+'Header.html' %} +
+ + +
{{ message }}
+ +
+

{{ world_name }}

+

Player Options

+
+

Choose the options you would like to play with! You may generate a single-player game from this page, + or download an options file you can use to participate in a MultiWorld.

+ +

+ A more advanced options configuration for all games can be found on the + Weighted options page. +
+ A list of all games you have generated can be found on the User Content Page. +
+ You may also download the + template file for this game. +

+ + +
+
+ + +
+
+ + +
+
+ +
+ {% for group_name, group_options in option_groups.items() %} +
+ {{ group_name }} +
+
+ {% for option_name, option in group_options.items() %} + {% if loop.index <= (loop.length / 2)|round(0,"ceil") %} + {% if issubclass(option, Options.Toggle) %} + {{ inputs.Toggle(option_name, option) }} + + {% elif issubclass(option, Options.TextChoice) %} + {{ inputs.TextChoice(option_name, option) }} + + {% elif issubclass(option, Options.Choice) %} + {{ inputs.Choice(option_name, option) }} + + {% elif issubclass(option, Options.NamedRange) %} + {{ inputs.NamedRange(option_name, option) }} + + {% elif issubclass(option, Options.Range) %} + {{ inputs.Range(option_name, option) }} + + {% elif issubclass(option, Options.FreeText) %} + {{ inputs.FreeText(option_name, option) }} + + {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} + {{ inputs.ItemDict(option_name, option) }} + + {% elif issubclass(option, Options.OptionList) and option.valid_keys %} + {{ inputs.OptionList(option_name, option) }} + + {% elif issubclass(option, Options.LocationSet) and option.verify_location_name %} + {{ inputs.LocationSet(option_name, option) }} + + {% elif issubclass(option, Options.ItemSet) and option.verify_item_name %} + {{ inputs.ItemSet(option_name, option) }} + + {% elif issubclass(option, Options.OptionSet) and option.valid_keys %} + {{ inputs.OptionSet(option_name, option) }} + + {% endif %} + {% endif %} + {% endfor %} +
+
+ {% for option_name, option in group_options.items() %} + {% if loop.index > (loop.length / 2)|round(0,"ceil") %} + {% if issubclass(option, Options.Toggle) %} + {{ inputs.Toggle(option_name, option) }} + + {% elif issubclass(option, Options.TextChoice) %} + {{ inputs.TextChoice(option_name, option) }} + + {% elif issubclass(option, Options.Choice) %} + {{ inputs.Choice(option_name, option) }} + + {% elif issubclass(option, Options.NamedRange) %} + {{ inputs.NamedRange(option_name, option) }} + + {% elif issubclass(option, Options.Range) %} + {{ inputs.Range(option_name, option) }} + + {% elif issubclass(option, Options.FreeText) %} + {{ inputs.FreeText(option_name, option) }} + + {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} + {{ inputs.ItemDict(option_name, option) }} + + {% elif issubclass(option, Options.OptionList) and option.valid_keys %} + {{ inputs.OptionList(option_name, option) }} + + {% elif issubclass(option, Options.LocationSet) and option.verify_location_name %} + {{ inputs.LocationSet(option_name, option) }} + + {% elif issubclass(option, Options.ItemSet) and option.verify_item_name %} + {{ inputs.ItemSet(option_name, option) }} + + {% elif issubclass(option, Options.OptionSet) and option.valid_keys %} + {{ inputs.OptionSet(option_name, option) }} + + {% endif %} + {% endif %} + {% endfor %} +
+
+
+ {% endfor %} +
+ +
+ + +
+ +
+{% endblock %} diff --git a/WebHostLib/templates/siteMap.html b/WebHostLib/templates/siteMap.html index 231ec83e2420..cdd6ad45eb27 100644 --- a/WebHostLib/templates/siteMap.html +++ b/WebHostLib/templates/siteMap.html @@ -24,7 +24,6 @@

Base Pages

  • Supported Games Page
  • Tutorials Page
  • User Content
  • -
  • Weighted Options Page
  • Game Statistics
  • Glossary
  • @@ -50,8 +49,12 @@

    Game Options Pages

    diff --git a/WebHostLib/templates/startPlaying.html b/WebHostLib/templates/startPlaying.html index 436af3df07e8..ab2f021d61d2 100644 --- a/WebHostLib/templates/startPlaying.html +++ b/WebHostLib/templates/startPlaying.html @@ -18,7 +18,7 @@

    Start Playing



    To start playing a game, you'll first need to generate a randomized game. - You'll need to upload either a config file or a zip file containing one more config files. + You'll need to upload one or more config files (YAMLs) or a zip file containing one or more config files.

    If you have already generated a game and just need to host it, this site can
    diff --git a/WebHostLib/templates/supportedGames.html b/WebHostLib/templates/supportedGames.html index 6666323c9387..b3f20d293543 100644 --- a/WebHostLib/templates/supportedGames.html +++ b/WebHostLib/templates/supportedGames.html @@ -41,28 +41,28 @@

    Currently Supported Games

    {% for game_name in worlds | title_sorted %} {% set world = worlds[game_name] %} -

    - â–¶{{ game_name }} -

    -
    + {{ game_name }} {{ world.__doc__ | default("No description provided.", true) }}
    Game Page {% if world.web.tutorials %} | - Setup Guides + Setup Guides {% endif %} {% if world.web.options_page is string %} | - Options Page + Options Page (External Link) {% elif world.web.options_page %} | Options Page + | + Advanced Options {% endif %} {% if world.web.bug_report_page %} | Report a Bug {% endif %} -

    +
    {% endfor %} {% endblock %} diff --git a/WebHostLib/templates/tracker__ALinkToThePast.html b/WebHostLib/templates/tracker__ALinkToThePast.html index b7bae26fd35b..99179797f443 100644 --- a/WebHostLib/templates/tracker__ALinkToThePast.html +++ b/WebHostLib/templates/tracker__ALinkToThePast.html @@ -1,73 +1,89 @@ -{%- set icons = { - "Blue Shield": "https://www.zeldadungeon.net/wiki/images/8/85/Fighters-Shield.png", - "Red Shield": "https://www.zeldadungeon.net/wiki/images/5/55/Fire-Shield.png", - "Mirror Shield": "https://www.zeldadungeon.net/wiki/images/8/84/Mirror-Shield.png", - "Fighter Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/40/SFighterSword.png?width=1920", - "Master Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/SMasterSword.png?width=1920", - "Tempered Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/92/STemperedSword.png?width=1920", - "Golden Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/2/28/SGoldenSword.png?width=1920", - "Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=5f85a70e6366bf473544ef93b274f74c", - "Silver Bow": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/Bow.png?width=1920", - "Green Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c9/SGreenTunic.png?width=1920", - "Blue Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/98/SBlueTunic.png?width=1920", - "Red Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/7/74/SRedTunic.png?width=1920", - "Power Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/f/f5/SPowerGlove.png?width=1920", - "Titan Mitts": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920", - "Progressive Sword": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/cc/ALttP_Master_Sword_Sprite.png?version=55869db2a20e157cd3b5c8f556097725", - "Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png?version=405f42f97240c9dcd2b71ffc4bebc7f9", - "Progressive Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920", - "Flippers": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/4c/ZoraFlippers.png?width=1920", - "Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png?version=d601542d5abcc3e006ee163254bea77e", - "Progressive Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=cfb7648b3714cccc80e2b17b2adf00ed", - "Blue Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c3/ALttP_Boomerang_Sprite.png?version=96127d163759395eb510b81a556d500e", - "Red Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Magical_Boomerang_Sprite.png?version=47cddce7a07bc3e4c2c10727b491f400", - "Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png?version=c90bc8e07a52e8090377bd6ef854c18b", - "Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png?version=1f1acb30d71bd96b60a3491e54bbfe59", - "Magic Powder": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png?version=c24e38effbd4f80496d35830ce8ff4ec", - "Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png?version=6eabc9f24d25697e2c4cd43ddc8207c0", - "Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png?version=1f944148223d91cfc6a615c92286c3bc", - "Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png?version=f4d6aba47fb69375e090178f0fc33b26", - "Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png?version=34027651a5565fcc5a83189178ab17b5", - "Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png?version=efd64d451b1831bd59f7b7d6b61b5879", - "Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png?version=e76eaa1ec509c9a5efb2916698d5a4ce", - "Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png?version=e0adec227193818dcaedf587eba34500", - "Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png?version=e73d1ce0115c2c70eaca15b014bd6f05", - "Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png?version=ec4982b31c56da2c0c010905c5c60390", - "Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png?version=4d40e0ee015b687ff75b333b968d8be6", - "Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png?version=11e4632bba54f6b9bf921df06ac93744", - "Bottle": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png?version=fd98ab04db775270cbe79fce0235777b", - "Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png?version=8cc1900dfd887890badffc903bb87943", - "Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png?version=758b607c8cbe2cf1900d42a0b3d0fb54", - "Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png?version=6b77f0d609aab0c751307fc124736832", - "Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png?version=e035dbc9cbe2a3bd44aa6d047762b0cc", - "Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png?version=dc398e1293177581c16303e4f9d12a48", +{% set icons = { + "Blue Shield": "https://www.zeldadungeon.net/wiki/images/thumb/c/c3/FightersShield-ALttP-Sprite.png/100px-FightersShield-ALttP-Sprite.png", + "Red Shield": "https://www.zeldadungeon.net/wiki/images/thumb/9/9e/FireShield-ALttP-Sprite.png/111px-FireShield-ALttP-Sprite.png", + "Mirror Shield": "https://www.zeldadungeon.net/wiki/images/thumb/e/e3/MirrorShield-ALttP-Sprite.png/105px-MirrorShield-ALttP-Sprite.png", + "Fighter Sword": "https://upload.wikimedia.org/wikibooks/en/8/8e/Zelda_ALttP_item_L-1_Sword.png", + "Master Sword": "https://upload.wikimedia.org/wikibooks/en/8/87/BS_Zelda_AST_item_L-2_Sword.png", + "Tempered Sword": "https://upload.wikimedia.org/wikibooks/en/c/cc/BS_Zelda_AST_item_L-3_Sword.png", + "Golden Sword": "https://upload.wikimedia.org/wikibooks/en/4/40/BS_Zelda_AST_item_L-4_Sword.png", + "Bow": "https://www.zeldadungeon.net/wiki/images/thumb/8/8c/BowArrows-ALttP-Sprite.png/120px-BowArrows-ALttP-Sprite.png", + "Silver Bow": "https://upload.wikimedia.org/wikibooks/en/6/69/Zelda_ALttP_item_Silver_Arrows.png", + "Green Mail": "https://upload.wikimedia.org/wikibooks/en/d/dd/Zelda_ALttP_item_Green_Mail.png", + "Blue Mail": "https://upload.wikimedia.org/wikibooks/en/b/b5/Zelda_ALttP_item_Blue_Mail.png", + "Red Mail": "https://upload.wikimedia.org/wikibooks/en/d/db/Zelda_ALttP_item_Red_Mail.png", + "Power Glove": "https://www.zeldadungeon.net/wiki/images/thumb/4/41/PowerGlove-ALttP-Sprite.png/105px-PowerGlove-ALttP-Sprite.png", + "Titan Mitts": "https://www.zeldadungeon.net/wiki/images/thumb/7/75/TitanMitt-ALttP-Sprite.png/105px-TitanMitt-ALttP-Sprite.png", + "Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png", + "Flippers": "https://www.zeldadungeon.net/wiki/images/thumb/b/bc/ZoraFlippers-ALttP-Sprite.png/112px-ZoraFlippers-ALttP-Sprite.png", + "Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png", + "Blue Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/f/f0/Boomerang-ALttP-Sprite.png/86px-Boomerang-ALttP-Sprite.png", + "Red Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/3/3c/MagicalBoomerang-ALttP-Sprite.png/86px-MagicalBoomerang-ALttP-Sprite.png", + "Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png", + "Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png", + "Magic Powder": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png", + "Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png", + "Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png", + "Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png", + "Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png", + "Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png", + "Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png", + "Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png", + "Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png", + "Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png", + "Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png", + "Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png", + "Bottles": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png", + "Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png", + "Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png", + "Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png", + "Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png", + "Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png", "Triforce Piece": "https://www.zeldadungeon.net/wiki/images/thumb/5/54/Triforce_Fragment_-_BS_Zelda.png/62px-Triforce_Fragment_-_BS_Zelda.png", - "Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png?version=4f35d92842f0de39d969181eea03774e", - "Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png?version=136dfa418ba76c8b4e270f466fc12f4d", - "Chest": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Treasure_Chest_Sprite.png?version=5f530ecd98dcb22251e146e8049c0dda", - "Light World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e7/ALttP_Soldier_Green_Sprite.png?version=d650d417934cd707a47e496489c268a6", - "Dark World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/94/ALttP_Moblin_Sprite.png?version=ebf50e33f4657c377d1606bcc0886ddc", - "Hyrule Castle": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d3/ALttP_Ball_and_Chain_Trooper_Sprite.png?version=1768a87c06d29cc8e7ddd80b9fa516be", - "Agahnims Tower": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1e/ALttP_Agahnim_Sprite.png?version=365956e61b0c2191eae4eddbe591dab5", - "Desert Palace": "https://www.zeldadungeon.net/wiki/images/2/25/Lanmola-ALTTP-Sprite.png", - "Eastern Palace": "https://www.zeldadungeon.net/wiki/images/d/dc/RedArmosKnight.png", - "Tower of Hera": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/ALttP_Moldorm_Sprite.png?version=c588257bdc2543468e008a6b30f262a7", - "Palace of Darkness": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Helmasaur_King_Sprite.png?version=ab8a4a1cfd91d4fc43466c56cba30022", - "Swamp Palace": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Arrghus_Sprite.png?version=b098be3122e53f751b74f4a5ef9184b5", - "Skull Woods": "https://alttp-wiki.net/images/6/6a/Mothula.png", - "Thieves Town": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/86/ALttP_Blind_the_Thief_Sprite.png?version=3833021bfcd112be54e7390679047222", - "Ice Palace": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Kholdstare_Sprite.png?version=e5a1b0e8b2298e550d85f90bf97045c0", - "Misery Mire": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/85/ALttP_Vitreous_Sprite.png?version=92b2e9cb0aa63f831760f08041d8d8d8", - "Turtle Rock": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/91/ALttP_Trinexx_Sprite.png?version=0cc867d513952aa03edd155597a0c0be", - "Ganons Tower": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Ganon_Sprite.png?version=956f51f054954dfff53c1a9d4f929c74", -} -%} + "Bombs": "https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/38/ALttP_Bomb_Sprite.png", + "Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png", + "Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png", +} %} - +{% set inventory_order = [ + "Progressive Bow", "Boomerangs", "Hookshot", "Bombs", "Mushroom", "Magic Powder", + "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Progressive Mail", + "Lamp", "Hammer", "Flute", "Bug Catching Net", "Book of Mudora", "Progressive Shield", + "Bottles", "Cane of Somaria", "Cane of Byrna", "Cape", "Magic Mirror", "Progressive Sword", + "Shovel", "Pegasus Boots", "Progressive Glove", "Flippers", "Moon Pearl", "Triforce Piece", +] %} + +{# Most have a duplicated 0th entry for when we have none of that item to still load the correct icon/name. #} +{% set progressive_order = { + "Progressive Bow": ["Bow", "Bow", "Silver Bow"], + "Progressive Mail": ["Green Mail", "Blue Mail", "Red Mail"], + "Progressive Shield": ["Blue Shield", "Blue Shield", "Red Shield", "Mirror Shield"], + "Progressive Sword": ["Fighter Sword", "Fighter Sword", "Master Sword", "Tempered Sword", "Golden Sword"], + "Progressive Glove": ["Power Glove", "Power Glove", "Titan Mitts"], +} %} + +{% set dungeon_keys = { + "Hyrule Castle": ("Small Key (Hyrule Castle)", "Big Key (Hyrule Castle)"), + "Agahnims Tower": ("Small Key (Agahnims Tower)", "Big Key (Agahnims Tower)"), + "Eastern Palace": ("Small Key (Eastern Palace)", "Big Key (Eastern Palace)"), + "Desert Palace": ("Small Key (Desert Palace)", "Big Key (Desert Palace)"), + "Tower of Hera": ("Small Key (Tower of Hera)", "Big Key (Tower of Hera)"), + "Palace of Darkness": ("Small Key (Palace of Darkness)", "Big Key (Palace of Darkness)"), + "Swamp Palace": ("Small Key (Swamp Palace)", "Big Key (Swamp Palace)"), + "Thieves Town": ("Small Key (Thieves Town)", "Big Key (Thieves Town)"), + "Skull Woods": ("Small Key (Skull Woods)", "Big Key (Skull Woods)"), + "Ice Palace": ("Small Key (Ice Palace)", "Big Key (Ice Palace)"), + "Misery Mire": ("Small Key (Misery Mire)", "Big Key (Misery Mire)"), + "Turtle Rock": ("Small Key (Turtle Rock)", "Big Key (Turtle Rock)"), + "Ganons Tower": ("Small Key (Ganons Tower)", "Big Key (Ganons Tower)"), +} %} + + + + {{ player_name }}'s Tracker - - + @@ -76,79 +92,128 @@ Switch To Generic Tracker -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - {% if key_locations and "Universal" not in key_locations %} - +
    + {# Inventory Grid #} +
    + {% for item in inventory_order %} + {% if item in progressive_order %} + {% set non_prog_item = progressive_order[item][inventory[item]] %} +
    + {{ non_prog_item }} +
    + {% elif item == "Boomerangs" %} +
    + Blue Boomerang + Red Boomerang +
    + {% else %} +
    + {{ item }} + {% if item == "Bottles" or item == "Triforce Piece" %} +
    {{ inventory[item] }}
    + {% endif %} +
    {% endif %} - {% if big_key_locations %} -
    + {% endfor %} + + +
    +
    +
    +
    +
    SK
    +
    BK
    +
    + + {% for region_name in known_regions %} + {% set region_data = regions[region_name] %} + {% if region_data["locations"] | length > 0 %} +
    + + {% if region_name in dungeon_keys %} +
    + {{ region_name }} + {{ region_data["checked"] }} / {{ region_data["locations"] | length }} + {{ inventory[dungeon_keys[region_name][0]] }} + + {% if region_name == "Agahnims Tower" %} + — + {% elif inventory[dungeon_keys[region_name][1]] %} + ✔ + {% endif %} + +
    + {% else %} +
    + {{ region_name }} + {{ region_data["checked"] }} / {{ region_data["locations"] | length }} + + +
    + {% endif %} +
    + +
    + {% for location, checked in region_data["locations"] %} +
    {{ location }}
    +
    {% if checked %}✔{% endif %}
    + {% endfor %} +
    +
    {% endif %} -
    - {% for area in sp_areas %} - - - - {% if key_locations and "Universal" not in key_locations %} - - {% endif %} - {% if big_key_locations %} - - {% endif %} - {% endfor %} -
    {{ area }}{{ checks_done[area] }} / {{ checks_in_area[area] }} - {{ inventory[small_key_ids[area]] if area in key_locations else '—' }} - - {{ '✔' if area in big_key_locations and inventory[big_key_ids[area]] else ('—' if area not in big_key_locations else '') }} -
    +
    + + diff --git a/WebHostLib/templates/tracker__Starcraft2.html b/WebHostLib/templates/tracker__Starcraft2.html new file mode 100644 index 000000000000..d365d126338d --- /dev/null +++ b/WebHostLib/templates/tracker__Starcraft2.html @@ -0,0 +1,1092 @@ + +{% macro sc2_icon(name) -%} + +{% endmacro -%} +{% macro sc2_progressive_icon(name, url, level) -%} + +{% endmacro -%} +{% macro sc2_progressive_icon_with_custom_name(item_name, url, title) -%} + +{% endmacro -%} +{%+ macro sc2_tint_level(level) %} + tint-level-{{ level }} +{%+ endmacro %} +{% macro sc2_render_area(area) %} + + {{ area }} {{'▼' if area != 'Total'}} + {{ checks_done[area] }} / {{ checks_in_area[area] }} + + + {% for location in location_info[area] %} + + {{ location }} + {{ '✔' if location_info[area][location] else '' }} + + {% endfor %} + +{% endmacro -%} +{% macro sc2_loop_areas(column_index, column_count) %} + {% for area in checks_in_area if checks_in_area[area] > 0 and area != 'Total' %} + {% if loop.index0 < (loop.length / column_count) * (column_index + 1) + and loop.index0 >= (loop.length / column_count) * (column_index) %} + {{ sc2_render_area(area) }} + {% endif %} + {% endfor %} +{% endmacro -%} + + + {{ player_name }}'s Tracker + + + + + + + {# TODO: Replace this with a proper wrapper for each tracker when developing TrackerAPI. #} + + +
    + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    +

    {{ player_name }}'s Starcraft 2 Tracker

    + Starting Resources +
    +{{ minerals_count }}
    +{{ vespene_count }}
    +{{ supply_count }}
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Terran +
    + Weapon & Armor Upgrades +
    {{ sc2_progressive_icon('Progressive Terran Infantry Weapon', terran_infantry_weapon_url, terran_infantry_weapon_level) }}{{ sc2_progressive_icon('Progressive Terran Infantry Armor', terran_infantry_armor_url, terran_infantry_armor_level) }}{{ sc2_progressive_icon('Progressive Terran Vehicle Weapon', terran_vehicle_weapon_url, terran_vehicle_weapon_level) }}{{ sc2_progressive_icon('Progressive Terran Vehicle Armor', terran_vehicle_armor_url, terran_vehicle_armor_level) }}{{ sc2_progressive_icon('Progressive Terran Ship Weapon', terran_ship_weapon_url, terran_ship_weapon_level) }}{{ sc2_progressive_icon('Progressive Terran Ship Armor', terran_ship_armor_url, terran_ship_armor_level) }}{{ sc2_icon('Ultra-Capacitors') }}{{ sc2_icon('Vanadium Plating') }}
    + Base +
    {{ sc2_icon('Bunker') }}{{ sc2_icon('Projectile Accelerator (Bunker)') }}{{ sc2_icon('Neosteel Bunker (Bunker)') }}{{ sc2_icon('Shrike Turret (Bunker)') }}{{ sc2_icon('Fortified Bunker (Bunker)') }}{{ sc2_icon('Missile Turret') }}{{ sc2_icon('Titanium Housing (Missile Turret)') }}{{ sc2_icon('Hellstorm Batteries (Missile Turret)') }}{{ sc2_icon('Tech Reactor') }}{{ sc2_icon('Orbital Depots') }}
    {{ sc2_icon('Command Center Reactor') }}{{ sc2_progressive_icon_with_custom_name('Progressive Orbital Command', orbital_command_url, orbital_command_name) }}{{ sc2_icon('Planetary Fortress') }}{{ sc2_progressive_icon_with_custom_name('Progressive Augmented Thrusters (Planetary Fortress)', augmented_thrusters_planetary_fortress_url, augmented_thrusters_planetary_fortress_name) }}{{ sc2_icon('Advanced Targeting (Planetary Fortress)') }}{{ sc2_icon('Micro-Filtering') }}{{ sc2_icon('Automated Refinery') }}{{ sc2_icon('Advanced Construction (SCV)') }}{{ sc2_icon('Dual-Fusion Welders (SCV)') }}{{ sc2_icon('Hostile Environment Adaptation (SCV)') }}
    {{ sc2_icon('Sensor Tower') }}{{ sc2_icon('Perdition Turret') }}{{ sc2_icon('Hive Mind Emulator') }}{{ sc2_icon('Psi Disrupter') }}
    + Infantry + + Vehicles +
    {{ sc2_icon('Marine') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Marine)', stimpack_marine_url, stimpack_marine_name) }}{{ sc2_icon('Combat Shield (Marine)') }}{{ sc2_icon('Laser Targeting System (Marine)') }}{{ sc2_icon('Magrail Munitions (Marine)') }}{{ sc2_icon('Optimized Logistics (Marine)') }}{{ sc2_icon('Hellion') }}{{ sc2_icon('Twin-Linked Flamethrower (Hellion)') }}{{ sc2_icon('Thermite Filaments (Hellion)') }}{{ sc2_icon('Hellbat Aspect (Hellion)') }}{{ sc2_icon('Smart Servos (Hellion)') }}{{ sc2_icon('Optimized Logistics (Hellion)') }}{{ sc2_icon('Jump Jets (Hellion)') }}
    {{ sc2_icon('Medic') }}{{ sc2_icon('Advanced Medic Facilities (Medic)') }}{{ sc2_icon('Stabilizer Medpacks (Medic)') }}{{ sc2_icon('Restoration (Medic)') }}{{ sc2_icon('Optical Flare (Medic)') }}{{ sc2_icon('Resource Efficiency (Medic)') }}{{ sc2_icon('Adaptive Medpacks (Medic)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Hellion)', stimpack_hellion_url, stimpack_hellion_name) }}{{ sc2_icon('Infernal Plating (Hellion)') }}
    {{ sc2_icon('Nano Projector (Medic)') }}{{ sc2_icon('Vulture') }}{{ sc2_progressive_icon_with_custom_name('Progressive Replenishable Magazine (Vulture)', replenishable_magazine_vulture_url, replenishable_magazine_vulture_name) }}{{ sc2_icon('Ion Thrusters (Vulture)') }}{{ sc2_icon('Auto Launchers (Vulture)') }}{{ sc2_icon('Auto-Repair (Vulture)') }}
    {{ sc2_icon('Firebat') }}{{ sc2_icon('Incinerator Gauntlets (Firebat)') }}{{ sc2_icon('Juggernaut Plating (Firebat)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Firebat)', stimpack_firebat_url, stimpack_firebat_name) }}{{ sc2_icon('Resource Efficiency (Firebat)') }}{{ sc2_icon('Infernal Pre-Igniter (Firebat)') }}{{ sc2_icon('Kinetic Foam (Firebat)') }}{{ sc2_icon('Cerberus Mine (Spider Mine)') }}{{ sc2_icon('High Explosive Munition (Spider Mine)') }}
    {{ sc2_icon('Nano Projectors (Firebat)') }}{{ sc2_icon('Goliath') }}{{ sc2_icon('Multi-Lock Weapons System (Goliath)') }}{{ sc2_icon('Ares-Class Targeting System (Goliath)') }}{{ sc2_icon('Jump Jets (Goliath)') }}{{ sc2_icon('Shaped Hull (Goliath)') }}{{ sc2_icon('Optimized Logistics (Goliath)') }}{{ sc2_icon('Resource Efficiency (Goliath)') }}
    {{ sc2_icon('Marauder') }}{{ sc2_icon('Concussive Shells (Marauder)') }}{{ sc2_icon('Kinetic Foam (Marauder)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Marauder)', stimpack_marauder_url, stimpack_marauder_name) }}{{ sc2_icon('Laser Targeting System (Marauder)') }}{{ sc2_icon('Magrail Munitions (Marauder)') }}{{ sc2_icon('Internal Tech Module (Marauder)') }}{{ sc2_icon('Internal Tech Module (Goliath)') }}
    {{ sc2_icon('Juggernaut Plating (Marauder)') }}{{ sc2_icon('Diamondback') }}{{ sc2_progressive_icon_with_custom_name('Progressive Tri-Lithium Power Cell (Diamondback)', trilithium_power_cell_diamondback_url, trilithium_power_cell_diamondback_name) }}{{ sc2_icon('Shaped Hull (Diamondback)') }}{{ sc2_icon('Hyperfluxor (Diamondback)') }}{{ sc2_icon('Burst Capacitors (Diamondback)') }}{{ sc2_icon('Ion Thrusters (Diamondback)') }}{{ sc2_icon('Resource Efficiency (Diamondback)') }}
    {{ sc2_icon('Reaper') }}{{ sc2_icon('U-238 Rounds (Reaper)') }}{{ sc2_icon('G-4 Clusterbomb (Reaper)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Reaper)', stimpack_reaper_url, stimpack_reaper_name) }}{{ sc2_icon('Laser Targeting System (Reaper)') }}{{ sc2_icon('Advanced Cloaking Field (Reaper)') }}{{ sc2_icon('Spider Mines (Reaper)') }}{{ sc2_icon('Siege Tank') }}{{ sc2_icon('Maelstrom Rounds (Siege Tank)') }}{{ sc2_icon('Shaped Blast (Siege Tank)') }}{{ sc2_icon('Jump Jets (Siege Tank)') }}{{ sc2_icon('Spider Mines (Siege Tank)') }}{{ sc2_icon('Smart Servos (Siege Tank)') }}{{ sc2_icon('Graduating Range (Siege Tank)') }}
    {{ sc2_icon('Combat Drugs (Reaper)') }}{{ sc2_icon('Jet Pack Overdrive (Reaper)') }}{{ sc2_icon('Laser Targeting System (Siege Tank)') }}{{ sc2_icon('Advanced Siege Tech (Siege Tank)') }}{{ sc2_icon('Internal Tech Module (Siege Tank)') }}{{ sc2_icon('Shaped Hull (Siege Tank)') }}{{ sc2_icon('Resource Efficiency (Siege Tank)') }}
    {{ sc2_icon('Ghost') }}{{ sc2_icon('Ocular Implants (Ghost)') }}{{ sc2_icon('Crius Suit (Ghost)') }}{{ sc2_icon('EMP Rounds (Ghost)') }}{{ sc2_icon('Lockdown (Ghost)') }}{{ sc2_icon('Resource Efficiency (Ghost)') }}{{ sc2_icon('Thor') }}{{ sc2_icon('330mm Barrage Cannon (Thor)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Immortality Protocol (Thor)', immortality_protocol_thor_url, immortality_protocol_thor_name) }}{{ sc2_progressive_icon_with_custom_name('Progressive High Impact Payload (Thor)', high_impact_payload_thor_url, high_impact_payload_thor_name) }}{{ sc2_icon('Button With a Skull on It (Thor)') }}{{ sc2_icon('Laser Targeting System (Thor)') }}{{ sc2_icon('Large Scale Field Construction (Thor)') }}
    {{ sc2_icon('Spectre') }}{{ sc2_icon('Psionic Lash (Spectre)') }}{{ sc2_icon('Nyx-Class Cloaking Module (Spectre)') }}{{ sc2_icon('Impaler Rounds (Spectre)') }}{{ sc2_icon('Resource Efficiency (Spectre)') }}{{ sc2_icon('Predator') }}{{ sc2_icon('Resource Efficiency (Predator)') }}{{ sc2_icon('Cloak (Predator)') }}{{ sc2_icon('Charge (Predator)') }}{{ sc2_icon('Predator\'s Fury (Predator)') }}
    {{ sc2_icon('HERC') }}{{ sc2_icon('Juggernaut Plating (HERC)') }}{{ sc2_icon('Kinetic Foam (HERC)') }}{{ sc2_icon('Resource Efficiency (HERC)') }}{{ sc2_icon('Widow Mine') }}{{ sc2_icon('Drilling Claws (Widow Mine)') }}{{ sc2_icon('Concealment (Widow Mine)') }}{{ sc2_icon('Black Market Launchers (Widow Mine)') }}{{ sc2_icon('Executioner Missiles (Widow Mine)') }}
    {{ sc2_icon('Cyclone') }}{{ sc2_icon('Mag-Field Accelerators (Cyclone)') }}{{ sc2_icon('Mag-Field Launchers (Cyclone)') }}{{ sc2_icon('Targeting Optics (Cyclone)') }}{{ sc2_icon('Rapid Fire Launchers (Cyclone)') }}{{ sc2_icon('Resource Efficiency (Cyclone)') }}{{ sc2_icon('Internal Tech Module (Cyclone)') }}
    {{ sc2_icon('Warhound') }}{{ sc2_icon('Resource Efficiency (Warhound)') }}{{ sc2_icon('Reinforced Plating (Warhound)') }}
    + Starships +
    {{ sc2_icon('Medivac') }}{{ sc2_icon('Rapid Deployment Tube (Medivac)') }}{{ sc2_icon('Advanced Healing AI (Medivac)') }}{{ sc2_icon('Expanded Hull (Medivac)') }}{{ sc2_icon('Afterburners (Medivac)') }}{{ sc2_icon('Scatter Veil (Medivac)') }}{{ sc2_icon('Advanced Cloaking Field (Medivac)') }}{{ sc2_icon('Raven') }}{{ sc2_icon('Bio Mechanical Repair Drone (Raven)') }}{{ sc2_icon('Spider Mines (Raven)') }}{{ sc2_icon('Railgun Turret (Raven)') }}{{ sc2_icon('Hunter-Seeker Weapon (Raven)') }}{{ sc2_icon('Interference Matrix (Raven)') }}{{ sc2_icon('Anti-Armor Missile (Raven)') }}
    {{ sc2_icon('Wraith') }}{{ sc2_progressive_icon_with_custom_name('Progressive Tomahawk Power Cells (Wraith)', tomahawk_power_cells_wraith_url, tomahawk_power_cells_wraith_name) }}{{ sc2_icon('Displacement Field (Wraith)') }}{{ sc2_icon('Advanced Laser Technology (Wraith)') }}{{ sc2_icon('Trigger Override (Wraith)') }}{{ sc2_icon('Internal Tech Module (Wraith)') }}{{ sc2_icon('Resource Efficiency (Wraith)') }}{{ sc2_icon('Internal Tech Module (Raven)') }}{{ sc2_icon('Resource Efficiency (Raven)') }}{{ sc2_icon('Durable Materials (Raven)') }}
    {{ sc2_icon('Viking') }}{{ sc2_icon('Ripwave Missiles (Viking)') }}{{ sc2_icon('Phobos-Class Weapons System (Viking)') }}{{ sc2_icon('Smart Servos (Viking)') }}{{ sc2_icon('Anti-Mechanical Munition (Viking)') }}{{ sc2_icon('Shredder Rounds (Viking)') }}{{ sc2_icon('W.I.L.D. Missiles (Viking)') }}{{ sc2_icon('Science Vessel') }}{{ sc2_icon('EMP Shockwave (Science Vessel)') }}{{ sc2_icon('Defensive Matrix (Science Vessel)') }}{{ sc2_icon('Improved Nano-Repair (Science Vessel)') }}{{ sc2_icon('Advanced AI Systems (Science Vessel)') }}
    {{ sc2_icon('Banshee') }}{{ sc2_progressive_icon_with_custom_name('Progressive Cross-Spectrum Dampeners (Banshee)', crossspectrum_dampeners_banshee_url, crossspectrum_dampeners_banshee_name) }}{{ sc2_icon('Shockwave Missile Battery (Banshee)') }}{{ sc2_icon('Hyperflight Rotors (Banshee)') }}{{ sc2_icon('Laser Targeting System (Banshee)') }}{{ sc2_icon('Internal Tech Module (Banshee)') }}{{ sc2_icon('Shaped Hull (Banshee)') }}{{ sc2_icon('Hercules') }}{{ sc2_icon('Internal Fusion Module (Hercules)') }}{{ sc2_icon('Tactical Jump (Hercules)') }}
    {{ sc2_icon('Advanced Targeting Optics (Banshee)') }}{{ sc2_icon('Distortion Blasters (Banshee)') }}{{ sc2_icon('Rocket Barrage (Banshee)') }}{{ sc2_icon('Liberator') }}{{ sc2_icon('Advanced Ballistics (Liberator)') }}{{ sc2_icon('Raid Artillery (Liberator)') }}{{ sc2_icon('Cloak (Liberator)') }}{{ sc2_icon('Laser Targeting System (Liberator)') }}{{ sc2_icon('Optimized Logistics (Liberator)') }}{{ sc2_icon('Smart Servos (Liberator)') }}
    {{ sc2_icon('Battlecruiser') }}{{ sc2_progressive_icon('Progressive Missile Pods (Battlecruiser)', missile_pods_battlecruiser_url, missile_pods_battlecruiser_level) }}{{ sc2_progressive_icon_with_custom_name('Progressive Defensive Matrix (Battlecruiser)', defensive_matrix_battlecruiser_url, defensive_matrix_battlecruiser_name) }}{{ sc2_icon('Tactical Jump (Battlecruiser)') }}{{ sc2_icon('Cloak (Battlecruiser)') }}{{ sc2_icon('ATX Laser Battery (Battlecruiser)') }}{{ sc2_icon('Optimized Logistics (Battlecruiser)') }}{{ sc2_icon('Resource Efficiency (Liberator)') }}
    {{ sc2_icon('Internal Tech Module (Battlecruiser)') }}{{ sc2_icon('Behemoth Plating (Battlecruiser)') }}{{ sc2_icon('Covert Ops Engines (Battlecruiser)') }}{{ sc2_icon('Valkyrie') }}{{ sc2_icon('Enhanced Cluster Launchers (Valkyrie)') }}{{ sc2_icon('Shaped Hull (Valkyrie)') }}{{ sc2_icon('Flechette Missiles (Valkyrie)') }}{{ sc2_icon('Afterburners (Valkyrie)') }}{{ sc2_icon('Launching Vector Compensator (Valkyrie)') }}{{ sc2_icon('Resource Efficiency (Valkyrie)') }}
    + Mercenaries +
    {{ sc2_icon('War Pigs') }}{{ sc2_icon('Devil Dogs') }}{{ sc2_icon('Hammer Securities') }}{{ sc2_icon('Spartan Company') }}{{ sc2_icon('Siege Breakers') }}{{ sc2_icon('Hel\'s Angels') }}{{ sc2_icon('Dusk Wings') }}{{ sc2_icon('Jackson\'s Revenge') }}{{ sc2_icon('Skibi\'s Angels') }}{{ sc2_icon('Death Heads') }}{{ sc2_icon('Winged Nightmares') }}{{ sc2_icon('Midnight Riders') }}{{ sc2_icon('Brynhilds') }}{{ sc2_icon('Jotun') }}
    + General Upgrades +
    {{ sc2_progressive_icon('Progressive Fire-Suppression System', firesuppression_system_url, firesuppression_system_level) }}{{ sc2_icon('Orbital Strike') }}{{ sc2_icon('Cellular Reactor') }}{{ sc2_progressive_icon('Progressive Regenerative Bio-Steel', regenerative_biosteel_url, regenerative_biosteel_level) }}{{ sc2_icon('Structure Armor') }}{{ sc2_icon('Hi-Sec Auto Tracking') }}{{ sc2_icon('Advanced Optics') }}{{ sc2_icon('Rogue Forces') }}
    + Nova Equipment +
    {{ sc2_icon('C20A Canister Rifle (Nova Weapon)') }}{{ sc2_icon('Hellfire Shotgun (Nova Weapon)') }}{{ sc2_icon('Plasma Rifle (Nova Weapon)') }}{{ sc2_icon('Monomolecular Blade (Nova Weapon)') }}{{ sc2_icon('Blazefire Gunblade (Nova Weapon)') }}{{ sc2_icon('Stim Infusion (Nova Gadget)') }}{{ sc2_icon('Pulse Grenades (Nova Gadget)') }}{{ sc2_icon('Flashbang Grenades (Nova Gadget)') }}{{ sc2_icon('Ionic Force Field (Nova Gadget)') }}{{ sc2_icon('Holo Decoy (Nova Gadget)') }}
    {{ sc2_progressive_icon_with_custom_name('Progressive Stealth Suit Module (Nova Suit Module)', stealth_suit_module_nova_suit_module_url, stealth_suit_module_nova_suit_module_name) }}{{ sc2_icon('Energy Suit Module (Nova Suit Module)') }}{{ sc2_icon('Armored Suit Module (Nova Suit Module)') }}{{ sc2_icon('Jump Suit Module (Nova Suit Module)') }}{{ sc2_icon('Ghost Visor (Nova Equipment)') }}{{ sc2_icon('Rangefinder Oculus (Nova Equipment)') }}{{ sc2_icon('Domination (Nova Ability)') }}{{ sc2_icon('Blink (Nova Ability)') }}{{ sc2_icon('Tac Nuke Strike (Nova Ability)') }}
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Zerg +
    + Weapon & Armor Upgrades +
    {{ sc2_progressive_icon('Progressive Zerg Melee Attack', zerg_melee_attack_url, zerg_melee_attack_level) }}{{ sc2_progressive_icon('Progressive Zerg Missile Attack', zerg_missile_attack_url, zerg_missile_attack_level) }}{{ sc2_progressive_icon('Progressive Zerg Ground Carapace', zerg_ground_carapace_url, zerg_ground_carapace_level) }}{{ sc2_progressive_icon('Progressive Zerg Flyer Attack', zerg_flyer_attack_url, zerg_flyer_attack_level) }}{{ sc2_progressive_icon('Progressive Zerg Flyer Carapace', zerg_flyer_carapace_url, zerg_flyer_carapace_level) }}
    + Base +
    {{ sc2_icon('Automated Extractors (Kerrigan Tier 3)') }}{{ sc2_icon('Vespene Efficiency (Kerrigan Tier 5)') }}{{ sc2_icon('Twin Drones (Kerrigan Tier 5)') }}{{ sc2_icon('Improved Overlords (Kerrigan Tier 3)') }}{{ sc2_icon('Ventral Sacs (Overlord)') }}
    {{ sc2_icon('Malignant Creep (Kerrigan Tier 5)') }}{{ sc2_icon('Spine Crawler') }}{{ sc2_icon('Spore Crawler') }}
    + Units +
    {{ sc2_icon('Zergling') }}{{ sc2_icon('Raptor Strain (Zergling)') }}{{ sc2_icon('Swarmling Strain (Zergling)') }}{{ sc2_icon('Hardened Carapace (Zergling)') }}{{ sc2_icon('Adrenal Overload (Zergling)') }}{{ sc2_icon('Metabolic Boost (Zergling)') }}{{ sc2_icon('Shredding Claws (Zergling)') }}{{ sc2_icon('Zergling Reconstitution (Kerrigan Tier 3)') }}
    {{ sc2_icon('Baneling Aspect (Zergling)') }}{{ sc2_icon('Splitter Strain (Baneling)') }}{{ sc2_icon('Hunter Strain (Baneling)') }}{{ sc2_icon('Corrosive Acid (Baneling)') }}{{ sc2_icon('Rupture (Baneling)') }}{{ sc2_icon('Regenerative Acid (Baneling)') }}{{ sc2_icon('Centrifugal Hooks (Baneling)') }}
    {{ sc2_icon('Tunneling Jaws (Baneling)') }}{{ sc2_icon('Rapid Metamorph (Baneling)') }}
    {{ sc2_icon('Swarm Queen') }}{{ sc2_icon('Spawn Larvae (Swarm Queen)') }}{{ sc2_icon('Deep Tunnel (Swarm Queen)') }}{{ sc2_icon('Organic Carapace (Swarm Queen)') }}{{ sc2_icon('Bio-Mechanical Transfusion (Swarm Queen)') }}{{ sc2_icon('Resource Efficiency (Swarm Queen)') }}{{ sc2_icon('Incubator Chamber (Swarm Queen)') }}
    {{ sc2_icon('Roach') }}{{ sc2_icon('Vile Strain (Roach)') }}{{ sc2_icon('Corpser Strain (Roach)') }}{{ sc2_icon('Hydriodic Bile (Roach)') }}{{ sc2_icon('Adaptive Plating (Roach)') }}{{ sc2_icon('Tunneling Claws (Roach)') }}{{ sc2_icon('Glial Reconstitution (Roach)') }}{{ sc2_icon('Organic Carapace (Roach)') }}
    {{ sc2_icon('Ravager Aspect (Roach)') }}{{ sc2_icon('Potent Bile (Ravager)') }}{{ sc2_icon('Bloated Bile Ducts (Ravager)') }}{{ sc2_icon('Deep Tunnel (Ravager)') }}
    {{ sc2_icon('Hydralisk') }}{{ sc2_icon('Frenzy (Hydralisk)') }}{{ sc2_icon('Ancillary Carapace (Hydralisk)') }}{{ sc2_icon('Grooved Spines (Hydralisk)') }}{{ sc2_icon('Muscular Augments (Hydralisk)') }}{{ sc2_icon('Resource Efficiency (Hydralisk)') }}
    {{ sc2_icon('Impaler Aspect (Hydralisk)') }}{{ sc2_icon('Adaptive Talons (Impaler)') }}{{ sc2_icon('Secretion Glands (Impaler)') }}{{ sc2_icon('Hardened Tentacle Spines (Impaler)') }}
    {{ sc2_icon('Lurker Aspect (Hydralisk)') }}{{ sc2_icon('Seismic Spines (Lurker)') }}{{ sc2_icon('Adapted Spines (Lurker)') }}
    {{ sc2_icon('Aberration') }}
    {{ sc2_icon('Swarm Host') }}{{ sc2_icon('Carrion Strain (Swarm Host)') }}{{ sc2_icon('Creeper Strain (Swarm Host)') }}{{ sc2_icon('Burrow (Swarm Host)') }}{{ sc2_icon('Rapid Incubation (Swarm Host)') }}{{ sc2_icon('Pressurized Glands (Swarm Host)') }}{{ sc2_icon('Locust Metabolic Boost (Swarm Host)') }}{{ sc2_icon('Enduring Locusts (Swarm Host)') }}
    {{ sc2_icon('Organic Carapace (Swarm Host)') }}{{ sc2_icon('Resource Efficiency (Swarm Host)') }}
    {{ sc2_icon('Infestor') }}{{ sc2_icon('Infested Terran (Infestor)') }}{{ sc2_icon('Microbial Shroud (Infestor)') }}
    {{ sc2_icon('Defiler') }}
    {{ sc2_icon('Ultralisk') }}{{ sc2_icon('Noxious Strain (Ultralisk)') }}{{ sc2_icon('Torrasque Strain (Ultralisk)') }}{{ sc2_icon('Burrow Charge (Ultralisk)') }}{{ sc2_icon('Tissue Assimilation (Ultralisk)') }}{{ sc2_icon('Monarch Blades (Ultralisk)') }}{{ sc2_icon('Anabolic Synthesis (Ultralisk)') }}{{ sc2_icon('Chitinous Plating (Ultralisk)') }}
    {{ sc2_icon('Organic Carapace (Ultralisk)') }}{{ sc2_icon('Resource Efficiency (Ultralisk)') }}
    {{ sc2_icon('Mutalisk') }}{{ sc2_icon('Rapid Regeneration (Mutalisk)') }}{{ sc2_icon('Sundering Glaive (Mutalisk)') }}{{ sc2_icon('Vicious Glaive (Mutalisk)') }}{{ sc2_icon('Severing Glaive (Mutalisk)') }}{{ sc2_icon('Aerodynamic Glaive Shape (Mutalisk)') }}
    {{ sc2_icon('Corruptor') }}{{ sc2_icon('Corruption (Corruptor)') }}{{ sc2_icon('Caustic Spray (Corruptor)') }}
    {{ sc2_icon('Brood Lord Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Porous Cartilage (Brood Lord)') }}{{ sc2_icon('Evolved Carapace (Brood Lord)') }}{{ sc2_icon('Splitter Mitosis (Brood Lord)') }}{{ sc2_icon('Resource Efficiency (Brood Lord)') }}
    {{ sc2_icon('Viper Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Parasitic Bomb (Viper)') }}{{ sc2_icon('Paralytic Barbs (Viper)') }}{{ sc2_icon('Virulent Microbes (Viper)') }}
    {{ sc2_icon('Guardian Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Prolonged Dispersion (Guardian)') }}{{ sc2_icon('Primal Adaptation (Guardian)') }}{{ sc2_icon('Soronan Acid (Guardian)') }}
    {{ sc2_icon('Devourer Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Corrosive Spray (Devourer)') }}{{ sc2_icon('Gaping Maw (Devourer)') }}{{ sc2_icon('Improved Osmosis (Devourer)') }}{{ sc2_icon('Prescient Spores (Devourer)') }}
    {{ sc2_icon('Brood Queen') }}{{ sc2_icon('Fungal Growth (Brood Queen)') }}{{ sc2_icon('Ensnare (Brood Queen)') }}{{ sc2_icon('Enhanced Mitochondria (Brood Queen)') }}
    {{ sc2_icon('Scourge') }}{{ sc2_icon('Virulent Spores (Scourge)') }}{{ sc2_icon('Resource Efficiency (Scourge)') }}{{ sc2_icon('Swarm Scourge (Scourge)') }}
    + Mercenaries +
    {{ sc2_icon('Infested Medics') }}{{ sc2_icon('Infested Siege Tanks') }}{{ sc2_icon('Infested Banshees') }}
    + Kerrigan +
    Level: {{ kerrigan_level }}
    {{ sc2_icon('Primal Form (Kerrigan)') }}
    {{ sc2_icon('Kinetic Blast (Kerrigan Tier 1)') }}{{ sc2_icon('Heroic Fortitude (Kerrigan Tier 1)') }}{{ sc2_icon('Leaping Strike (Kerrigan Tier 1)') }}{{ sc2_icon('Crushing Grip (Kerrigan Tier 2)') }}{{ sc2_icon('Chain Reaction (Kerrigan Tier 2)') }}{{ sc2_icon('Psionic Shift (Kerrigan Tier 2)') }}
    {{ sc2_icon('Wild Mutation (Kerrigan Tier 4)') }}{{ sc2_icon('Spawn Banelings (Kerrigan Tier 4)') }}{{ sc2_icon('Mend (Kerrigan Tier 4)') }}{{ sc2_icon('Infest Broodlings (Kerrigan Tier 6)') }}{{ sc2_icon('Fury (Kerrigan Tier 6)') }}{{ sc2_icon('Ability Efficiency (Kerrigan Tier 6)') }}
    {{ sc2_icon('Apocalypse (Kerrigan Tier 7)') }}{{ sc2_icon('Spawn Leviathan (Kerrigan Tier 7)') }}{{ sc2_icon('Drop-Pods (Kerrigan Tier 7)') }}
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Protoss +
    + Weapon & Armor Upgrades +
    {{ sc2_progressive_icon('Progressive Protoss Ground Weapon', protoss_ground_weapon_url, protoss_ground_weapon_level) }}{{ sc2_progressive_icon('Progressive Protoss Ground Armor', protoss_ground_armor_url, protoss_ground_armor_level) }}{{ sc2_progressive_icon('Progressive Protoss Air Weapon', protoss_air_weapon_url, protoss_air_weapon_level) }}{{ sc2_progressive_icon('Progressive Protoss Air Armor', protoss_air_armor_url, protoss_air_armor_level) }}{{ sc2_progressive_icon('Progressive Protoss Shields', protoss_shields_url, protoss_shields_level) }}{{ sc2_icon('Quatro') }}
    + Base +
    {{ sc2_icon('Photon Cannon') }}{{ sc2_icon('Khaydarin Monolith') }}{{ sc2_icon('Shield Battery') }}{{ sc2_icon('Enhanced Targeting') }}{{ sc2_icon('Optimized Ordnance') }}{{ sc2_icon('Khalai Ingenuity') }}{{ sc2_icon('Orbital Assimilators') }}{{ sc2_icon('Amplified Assimilators') }}
    {{ sc2_icon('Warp Harmonization') }}{{ sc2_icon('Superior Warp Gates') }}{{ sc2_icon('Nexus Overcharge') }}
    + Gateway +
    {{ sc2_icon('Zealot') }}{{ sc2_icon('Centurion') }}{{ sc2_icon('Sentinel') }}{{ sc2_icon('Leg Enhancements (Zealot/Sentinel/Centurion)') }}{{ sc2_icon('Shield Capacity (Zealot/Sentinel/Centurion)') }}
    {{ sc2_icon('Supplicant') }}{{ sc2_icon('Blood Shield (Supplicant)') }}{{ sc2_icon('Soul Augmentation (Supplicant)') }}{{ sc2_icon('Shield Regeneration (Supplicant)') }}
    {{ sc2_icon('Sentry') }}{{ sc2_icon('Force Field (Sentry)') }}{{ sc2_icon('Hallucination (Sentry)') }}
    {{ sc2_icon('Energizer') }}{{ sc2_icon('Reclamation (Energizer)') }}{{ sc2_icon('Forged Chassis (Energizer)') }}{{ sc2_icon('Cloaking Module (Sentry/Energizer/Havoc)') }}{{ sc2_icon('Rapid Recharging (Sentry/Energizer/Havoc/Shield Battery)') }}
    {{ sc2_icon('Havoc') }}{{ sc2_icon('Detect Weakness (Havoc)') }}{{ sc2_icon('Bloodshard Resonance (Havoc)') }}
    {{ sc2_icon('Stalker') }}{{ sc2_icon('Instigator') }}{{ sc2_icon('Slayer') }}{{ sc2_icon('Disintegrating Particles (Stalker/Instigator/Slayer)') }}{{ sc2_icon('Particle Reflection (Stalker/Instigator/Slayer)') }}
    {{ sc2_icon('Dragoon') }}{{ sc2_icon('High Impact Phase Disruptor (Dragoon)') }}{{ sc2_icon('Trillic Compression System (Dragoon)') }}{{ sc2_icon('Singularity Charge (Dragoon)') }}{{ sc2_icon('Enhanced Strider Servos (Dragoon)') }}
    {{ sc2_icon('Adept') }}{{ sc2_icon('Shockwave (Adept)') }}{{ sc2_icon('Resonating Glaives (Adept)') }}{{ sc2_icon('Phase Bulwark (Adept)') }}
    {{ sc2_icon('High Templar') }}{{ sc2_icon('Signifier') }}{{ sc2_icon('Unshackled Psionic Storm (High Templar/Signifier)') }}{{ sc2_icon('Hallucination (High Templar/Signifier)') }}{{ sc2_icon('Khaydarin Amulet (High Templar/Signifier)') }}{{ sc2_icon('High Archon (Archon)') }}
    {{ sc2_icon('Ascendant') }}{{ sc2_icon('Power Overwhelming (Ascendant)') }}{{ sc2_icon('Chaotic Attunement (Ascendant)') }}{{ sc2_icon('Blood Amulet (Ascendant)') }}
    {{ sc2_icon('Dark Archon') }}{{ sc2_icon('Feedback (Dark Archon)') }}{{ sc2_icon('Maelstrom (Dark Archon)') }}{{ sc2_icon('Argus Talisman (Dark Archon)') }}
    {{ sc2_icon('Dark Templar') }}{{ sc2_icon('Dark Archon Meld (Dark Templar)') }}
    {{ sc2_icon('Avenger') }}{{ sc2_icon('Blood Hunter') }}{{ sc2_icon('Shroud of Adun (Dark Templar/Avenger/Blood Hunter)') }}{{ sc2_icon('Shadow Guard Training (Dark Templar/Avenger/Blood Hunter)') }}{{ sc2_icon('Blink (Dark Templar/Avenger/Blood Hunter)') }}{{ sc2_icon('Resource Efficiency (Dark Templar/Avenger/Blood Hunter)') }}
    + Robotics Facility +
    {{ sc2_icon('Warp Prism') }}{{ sc2_icon('Gravitic Drive (Warp Prism)') }}{{ sc2_icon('Phase Blaster (Warp Prism)') }}{{ sc2_icon('War Configuration (Warp Prism)') }}
    {{ sc2_icon('Immortal') }}{{ sc2_icon('Annihilator') }}{{ sc2_icon('Singularity Charge (Immortal/Annihilator)') }}{{ sc2_icon('Advanced Targeting Mechanics (Immortal/Annihilator)') }}
    {{ sc2_icon('Vanguard') }}{{ sc2_icon('Agony Launchers (Vanguard)') }}{{ sc2_icon('Matter Dispersion (Vanguard)') }}
    {{ sc2_icon('Colossus') }}{{ sc2_icon('Pacification Protocol (Colossus)') }}
    {{ sc2_icon('Wrathwalker') }}{{ sc2_icon('Rapid Power Cycling (Wrathwalker)') }}{{ sc2_icon('Eye of Wrath (Wrathwalker)') }}
    {{ sc2_icon('Observer') }}{{ sc2_icon('Gravitic Boosters (Observer)') }}{{ sc2_icon('Sensor Array (Observer)') }}
    {{ sc2_icon('Reaver') }}{{ sc2_icon('Scarab Damage (Reaver)') }}{{ sc2_icon('Solarite Payload (Reaver)') }}{{ sc2_icon('Reaver Capacity (Reaver)') }}{{ sc2_icon('Resource Efficiency (Reaver)') }}
    {{ sc2_icon('Disruptor') }}
    + Stargate +
    {{ sc2_icon('Phoenix') }}{{ sc2_icon('Mirage') }}{{ sc2_icon('Ionic Wavelength Flux (Phoenix/Mirage)') }}{{ sc2_icon('Anion Pulse-Crystals (Phoenix/Mirage)') }}
    {{ sc2_icon('Corsair') }}{{ sc2_icon('Stealth Drive (Corsair)') }}{{ sc2_icon('Argus Jewel (Corsair)') }}{{ sc2_icon('Sustaining Disruption (Corsair)') }}{{ sc2_icon('Neutron Shields (Corsair)') }}
    {{ sc2_icon('Destroyer') }}{{ sc2_icon('Reforged Bloodshard Core (Destroyer)') }}
    {{ sc2_icon('Void Ray') }}{{ sc2_icon('Flux Vanes (Void Ray/Destroyer)') }}
    {{ sc2_icon('Carrier') }}{{ sc2_icon('Graviton Catapult (Carrier)') }}{{ sc2_icon('Hull of Past Glories (Carrier)') }}
    {{ sc2_icon('Scout') }}{{ sc2_icon('Combat Sensor Array (Scout)') }}{{ sc2_icon('Apial Sensors (Scout)') }}{{ sc2_icon('Gravitic Thrusters (Scout)') }}{{ sc2_icon('Advanced Photon Blasters (Scout)') }}
    {{ sc2_icon('Tempest') }}{{ sc2_icon('Tectonic Destabilizers (Tempest)') }}{{ sc2_icon('Quantic Reactor (Tempest)') }}{{ sc2_icon('Gravity Sling (Tempest)') }}
    {{ sc2_icon('Mothership') }}
    {{ sc2_icon('Arbiter') }}{{ sc2_icon('Chronostatic Reinforcement (Arbiter)') }}{{ sc2_icon('Khaydarin Core (Arbiter)') }}{{ sc2_icon('Spacetime Anchor (Arbiter)') }}{{ sc2_icon('Resource Efficiency (Arbiter)') }}{{ sc2_icon('Enhanced Cloak Field (Arbiter)') }}
    {{ sc2_icon('Oracle') }}{{ sc2_icon('Stealth Drive (Oracle)') }}{{ sc2_icon('Stasis Calibration (Oracle)') }}{{ sc2_icon('Temporal Acceleration Beam (Oracle)') }}
    + General Upgrades +
    {{ sc2_icon('Matrix Overload') }}{{ sc2_icon('Guardian Shell') }}
    + Spear of Adun +
    {{ sc2_icon('Chrono Surge (Spear of Adun Calldown)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Proxy Pylon (Spear of Adun Calldown)', proxy_pylon_spear_of_adun_calldown_url, proxy_pylon_spear_of_adun_calldown_name) }}{{ sc2_icon('Pylon Overcharge (Spear of Adun Calldown)') }}{{ sc2_icon('Mass Recall (Spear of Adun Calldown)') }}{{ sc2_icon('Shield Overcharge (Spear of Adun Calldown)') }}{{ sc2_icon('Deploy Fenix (Spear of Adun Calldown)') }}{{ sc2_icon('Reconstruction Beam (Spear of Adun Auto-Cast)') }}
    {{ sc2_icon('Orbital Strike (Spear of Adun Calldown)') }}{{ sc2_icon('Temporal Field (Spear of Adun Calldown)') }}{{ sc2_icon('Solar Lance (Spear of Adun Calldown)') }}{{ sc2_icon('Purifier Beam (Spear of Adun Calldown)') }}{{ sc2_icon('Time Stop (Spear of Adun Calldown)') }}{{ sc2_icon('Solar Bombardment (Spear of Adun Calldown)') }}{{ sc2_icon('Overwatch (Spear of Adun Auto-Cast)') }}
    +
    + + + + + + +
    + + {{ sc2_loop_areas(0, 3) }} +
    +
    + + {{ sc2_loop_areas(1, 3) }} +
    +
    + + {{ sc2_loop_areas(2, 3) }} + + {{ sc2_render_area('Total') }} +
     
    +
    +
    +
    + + diff --git a/WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html b/WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html deleted file mode 100644 index c27f690dfd36..000000000000 --- a/WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html +++ /dev/null @@ -1,366 +0,0 @@ - - - - {{ player_name }}'s Tracker - - - - - - - {# TODO: Replace this with a proper wrapper for each tracker when developing TrackerAPI. #} - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Starting Resources -
    +{{ minerals_count }}
    +{{ vespene_count }}
    - Weapon & Armor Upgrades -
    - Base -
    - Infantry - - Vehicles -
    - Starships -
    - Mercenaries -
    - General Upgrades -
    - Protoss Units -
    - - {% for area in checks_in_area %} - {% if checks_in_area[area] > 0 %} - - - - - - {% for location in location_info[area] %} - - - - - {% endfor %} - - {% endif %} - {% endfor %} -
    {{ area }} {{'â–¼' if area != 'Total'}}{{ checks_done[area] }} / {{ checks_in_area[area] }}
    {{ location }}{{ '✔' if location_info[area][location] else '' }}
    -
    - - diff --git a/WebHostLib/templates/userContent.html b/WebHostLib/templates/userContent.html index c20d39f46d82..3603d4112d20 100644 --- a/WebHostLib/templates/userContent.html +++ b/WebHostLib/templates/userContent.html @@ -25,6 +25,7 @@

    Your Rooms

    Players Created (UTC) Last Activity (UTC) + Mark for deletion @@ -35,6 +36,7 @@

    Your Rooms

    {{ room.seed.slots|length }} {{ room.creation_time.strftime("%Y-%m-%d %H:%M") }} {{ room.last_activity.strftime("%Y-%m-%d %H:%M") }} + Delete next maintenance. {% endfor %} @@ -51,6 +53,7 @@

    Your Seeds

    Seed Players Created (UTC) + Mark for deletion @@ -60,6 +63,7 @@

    Your Seeds

    {% if seed.multidata %}{{ seed.slots|length }}{% else %}1{% endif %} {{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }} +
    Delete next maintenance. {% endfor %} diff --git a/WebHostLib/templates/weighted-options.html b/WebHostLib/templates/weighted-options.html deleted file mode 100644 index 032a4eeb905c..000000000000 --- a/WebHostLib/templates/weighted-options.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends 'pageWrapper.html' %} - -{% block head %} - {{ game }} Options - - - - - - -{% endblock %} - -{% block body %} - {% include 'header/grassHeader.html' %} - -{% endblock %} diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html new file mode 100644 index 000000000000..a1d319697154 --- /dev/null +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -0,0 +1,264 @@ +{% macro Toggle(option_name, option) %} + + + {{ RangeRow(option_name, option, "No", "false", False, "true" if option.default else "false") }} + {{ RangeRow(option_name, option, "Yes", "true", False, "true" if option.default else "false") }} + {{ RandomRow(option_name, option) }} + +
    +{% endmacro %} + +{% macro DefaultOnToggle(option_name, option) %} + + {{ Toggle(option_name, option) }} +{% endmacro %} + +{% macro Choice(option_name, option) %} + + + {% for id, name in option.name_lookup.items() %} + {% if name != 'random' %} + {% if option.default != 'random' %} + {{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.default == id else None) }} + {% else %} + {{ RangeRow(option_name, option, option.get_option_name(id), name) }} + {% endif %} + {% endif %} + {% endfor %} + {{ RandomRow(option_name, option) }} + +
    +{% endmacro %} + +{% macro Range(option_name, option) %} +
    + This is a range option. +

    + Accepted values:
    + Normal range: {{ option.range_start }} - {{ option.range_end }} + {% if option.special_range_names %} +

    + The following values have special meanings, and may fall outside the normal range. +
      + {% for name, value in option.special_range_names.items() %} +
    • {{ value }}: {{ name|replace("_", " ")|title }}
    • + {% endfor %} +
    + {% endif %} +
    + + +
    +
    + + + {{ RangeRow(option_name, option, option.range_start, option.range_start, True) }} + {% if option.range_start < option.default < option.range_end %} + {{ RangeRow(option_name, option, option.default, option.default, True) }} + {% endif %} + {{ RangeRow(option_name, option, option.range_end, option.range_end, True) }} + {{ RandomRows(option_name, option) }} + +
    +{% endmacro %} + +{% macro NamedRange(option_name, option) %} + + {{ Range(option_name, option) }} +{% endmacro %} + +{% macro FreeText(option_name, option) %} +
    + This option allows custom values only. Please enter your desired values below. +
    + + +
    + + + {% if option.default %} + {{ RangeRow(option_name, option, option.default, option.default) }} + {% endif %} + +
    +
    +{% endmacro %} + +{% macro TextChoice(option_name, option) %} +
    + Custom values are also allowed for this option. To create one, enter it into the input box below. +
    + + +
    +
    + + + {% for id, name in option.name_lookup.items() %} + {% if name != 'random' %} + {% if option.default != 'random' %} + {{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.default == id else None) }} + {% else %} + {{ RangeRow(option_name, option, option.get_option_name(id), name) }} + {% endif %} + {% endif %} + {% endfor %} + {{ RandomRow(option_name, option) }} + +
    +{% endmacro %} + +{% macro PlandoBosses(option_name, option) %} + + {{ TextChoice(option_name, option) }} +{% endmacro %} + +{% macro ItemDict(option_name, option, world) %} +
    + {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} +
    + + +
    + {% endfor %} +
    +{% endmacro %} + +{% macro OptionList(option_name, option) %} +
    + {% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %} +
    + + +
    + {% endfor %} +
    +{% endmacro %} + +{% macro LocationSet(option_name, option, world) %} +
    + {% for group_name in world.location_name_groups.keys()|sort %} + {% if group_name != "Everywhere" %} +
    + + +
    + {% endif %} + {% endfor %} + {% if world.location_name_groups.keys()|length > 1 %} +
     
    + {% endif %} + {% for location_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.location_names|sort) %} +
    + + +
    + {% endfor %} +
    +{% endmacro %} + +{% macro ItemSet(option_name, option, world) %} +
    + {% for group_name in world.item_name_groups.keys()|sort %} + {% if group_name != "Everything" %} +
    + + +
    + {% endif %} + {% endfor %} + {% if world.item_name_groups.keys()|length > 1 %} +
     
    + {% endif %} + {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} +
    + + +
    + {% endfor %} +
    +{% endmacro %} + +{% macro OptionSet(option_name, option) %} +
    + {% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %} +
    + + +
    + {% endfor %} +
    +{% endmacro %} + +{% macro OptionTitleTd(option_name, value) %} + + + +{% endmacro %} + +{% macro RandomRow(option_name, option, extra_column=False) %} + {{ RangeRow(option_name, option, "Random", "random") }} +{% endmacro %} + +{% macro RandomRows(option_name, option, extra_column=False) %} + {% for key, value in {"Random": "random", "Random (Low)": "random-low", "Random (Middle)": "random-middle", "Random (High)": "random-high"}.items() %} + {{ RangeRow(option_name, option, key, value) }} + {% endfor %} +{% endmacro %} + +{% macro RangeRow(option_name, option, display_value, value, can_delete=False, default_override=None) %} + + + + + + + + + + {% if option.default == value or default_override == value %} + 25 + {% else %} + 0 + {% endif %} + + + {% if can_delete %} + + + ⌠+ + + {% else %} + + {% endif %} + +{% endmacro %} diff --git a/WebHostLib/templates/weightedOptions/weightedOptions.html b/WebHostLib/templates/weightedOptions/weightedOptions.html new file mode 100644 index 000000000000..b3aefd483535 --- /dev/null +++ b/WebHostLib/templates/weightedOptions/weightedOptions.html @@ -0,0 +1,119 @@ +{% extends 'pageWrapper.html' %} +{% import 'weightedOptions/macros.html' as inputs %} + +{% block head %} + {{ world_name }} Weighted Options + + + + +{% endblock %} + +{% block body %} + {% include 'header/'+theme+'Header.html' %} +
    + + +
    + +
    +

    {{ world_name }}

    +

    Weighted Options

    +
    + +
    + +

    Weighted options allow you to choose how likely a particular option's value is to be used in game + generation. The higher a value is weighted, the more likely the option will be chosen. Think of them like + entries in a raffle.

    + +

    Choose the options you would like to play with! You may generate a single-player game from + this page, or download an options file you can use to participate in a MultiWorld.

    + +

    A list of all games you have generated can be found on the User Content + page.

    + + +


    + +

    + +
    + {% for group_name, group_options in option_groups.items() %} +
    + {{ group_name }} + {% for option_name, option in group_options.items() %} +
    +

    {{ option.display_name|default(option_name) }}

    +
    + {{ option.__doc__ }} +
    + {% if issubclass(option, Options.Toggle) %} + {{ inputs.Toggle(option_name, option) }} + + {% elif issubclass(option, Options.DefaultOnToggle) %} + {{ inputs.DefaultOnToggle(option_name, option) }} + + {% elif issubclass(option, Options.PlandoBosses) %} + {{ inputs.PlandoBosses(option_name, option) }} + + {% elif issubclass(option, Options.TextChoice) %} + {{ inputs.TextChoice(option_name, option) }} + + {% elif issubclass(option, Options.Choice) %} + {{ inputs.Choice(option_name, option) }} + + {% elif issubclass(option, Options.NamedRange) %} + {{ inputs.NamedRange(option_name, option) }} + + {% elif issubclass(option, Options.Range) %} + {{ inputs.Range(option_name, option) }} + + {% elif issubclass(option, Options.FreeText) %} + {{ inputs.FreeText(option_name, option) }} + + {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} + {{ inputs.ItemDict(option_name, option, world) }} + + {% elif issubclass(option, Options.OptionList) and option.valid_keys %} + {{ inputs.OptionList(option_name, option) }} + + {% elif issubclass(option, Options.LocationSet) and option.verify_location_name %} + {{ inputs.LocationSet(option_name, option, world) }} + + {% elif issubclass(option, Options.ItemSet) and option.verify_item_name %} + {{ inputs.ItemSet(option_name, option, world) }} + + {% elif issubclass(option, Options.OptionSet) and option.valid_keys %} + {{ inputs.OptionSet(option_name, option) }} + + {% else %} +
    + This option is not supported. Please edit your .yaml file manually. +
    + + {% endif %} +
    + {% endfor %} +
    + {% endfor %} +
    + +
    + + +
    +
    +
    +{% endblock %} diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index e8b1ae5b3171..75b5fb0202d9 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -1,10 +1,11 @@ import datetime import collections from dataclasses import dataclass -from typing import Any, Callable, Dict, List, Optional, Set, Tuple +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, NamedTuple, Counter from uuid import UUID +from email.utils import parsedate_to_datetime -from flask import render_template +from flask import render_template, make_response, Response, request from werkzeug.exceptions import abort from MultiServer import Context, get_saving_second @@ -78,7 +79,7 @@ def __init__(self, room: Room): # Normal lookup tables as well. self.item_name_to_id[game] = game_package["item_name_to_id"] - self.location_name_to_id[game] = game_package["item_name_to_id"] + self.location_name_to_id[game] = game_package["location_name_to_id"] def get_seed_name(self) -> str: """Retrieves the seed name.""" @@ -124,10 +125,13 @@ def get_player_received_items(self, team: int, player: int) -> List[NetworkItem] @_cache_results def get_player_inventory_counts(self, team: int, player: int) -> collections.Counter: """Retrieves a dictionary of all items received by their id and their received count.""" - items = self.get_player_received_items(team, player) + received_items = self.get_player_received_items(team, player) + starting_items = self.get_player_starting_inventory(team, player) inventory = collections.Counter() - for item in items: + for item in received_items: inventory[item.item] += 1 + for item in starting_items: + inventory[item] += 1 return inventory @@ -288,47 +292,47 @@ def get_room_videos(self) -> Dict[TeamPlayer, Tuple[str, str]]: return video_feeds + @_cache_results + def get_spheres(self) -> List[List[int]]: + """ each sphere is { player: { location_id, ... } } """ + return self._multidata.get("spheres", []) -@app.route("/tracker///") -def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> str: - key = f"{tracker}_{tracked_team}_{tracked_player}_{generic}" - tracker_page = cache.get(key) - if tracker_page: - return tracker_page - timeout, tracker_page = get_timeout_and_tracker(tracker, tracked_team, tracked_player, generic) - cache.set(key, tracker_page, timeout) - return tracker_page +def _process_if_request_valid(incoming_request, room: Optional[Room]) -> Optional[Response]: + if not room: + abort(404) + if_modified = incoming_request.headers.get("If-Modified-Since", None) + if if_modified: + if_modified = parsedate_to_datetime(if_modified) + # if_modified has less precision than last_activity, so we bring them to same precision + if if_modified >= room.last_activity.replace(microsecond=0): + return make_response("", 304) -@app.route("/generic_tracker///") -def get_generic_game_tracker(tracker: UUID, tracked_team: int, tracked_player: int) -> str: - return get_player_tracker(tracker, tracked_team, tracked_player, True) +@app.route("/tracker///") +def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> Response: + key = f"{tracker}_{tracked_team}_{tracked_player}_{generic}" + response: Optional[Response] = cache.get(key) + if response: + return response -@app.route("/tracker/", defaults={"game": "Generic"}) -@app.route("/tracker//") -@cache.memoize(timeout=TRACKER_CACHE_TIMEOUT_IN_SECONDS) -def get_multiworld_tracker(tracker: UUID, game: str): # Room must exist. room = Room.get(tracker=tracker) - if not room: - abort(404) - tracker_data = TrackerData(room) - enabled_trackers = list(get_enabled_multiworld_trackers(room).keys()) - if game not in _multiworld_trackers: - return render_generic_multiworld_tracker(tracker_data, enabled_trackers) + response = _process_if_request_valid(request, room) + if response: + return response - return _multiworld_trackers[game](tracker_data, enabled_trackers) + timeout, last_modified, tracker_page = get_timeout_and_player_tracker(room, tracked_team, tracked_player, generic) + response = make_response(tracker_page) + response.last_modified = last_modified + cache.set(key, response, timeout) + return response -def get_timeout_and_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool) -> Tuple[int, str]: - # Room must exist. - room = Room.get(tracker=tracker) - if not room: - abort(404) - +def get_timeout_and_player_tracker(room: Room, tracked_team: int, tracked_player: int, generic: bool)\ + -> Tuple[int, datetime.datetime, str]: tracker_data = TrackerData(room) # Load and render the game-specific player tracker, or fallback to generic tracker if none exists. @@ -338,7 +342,48 @@ def get_timeout_and_tracker(tracker: UUID, tracked_team: int, tracked_player: in else: tracker = render_generic_tracker(tracker_data, tracked_team, tracked_player) - return (tracker_data.get_room_saving_second() - datetime.datetime.now().second) % 60 or 60, tracker + return ((tracker_data.get_room_saving_second() - datetime.datetime.now().second) + % TRACKER_CACHE_TIMEOUT_IN_SECONDS or TRACKER_CACHE_TIMEOUT_IN_SECONDS, room.last_activity, tracker) + + +@app.route("/generic_tracker///") +def get_generic_game_tracker(tracker: UUID, tracked_team: int, tracked_player: int) -> Response: + return get_player_tracker(tracker, tracked_team, tracked_player, True) + + +@app.route("/tracker/", defaults={"game": "Generic"}) +@app.route("/tracker//") +def get_multiworld_tracker(tracker: UUID, game: str) -> Response: + key = f"{tracker}_{game}" + response: Optional[Response] = cache.get(key) + if response: + return response + + # Room must exist. + room = Room.get(tracker=tracker) + + response = _process_if_request_valid(request, room) + if response: + return response + + timeout, last_modified, tracker_page = get_timeout_and_multiworld_tracker(room, game) + response = make_response(tracker_page) + response.last_modified = last_modified + cache.set(key, response, timeout) + return response + + +def get_timeout_and_multiworld_tracker(room: Room, game: str)\ + -> Tuple[int, datetime.datetime, str]: + tracker_data = TrackerData(room) + enabled_trackers = list(get_enabled_multiworld_trackers(room).keys()) + if game in _multiworld_trackers: + tracker = _multiworld_trackers[game](tracker_data, enabled_trackers) + else: + tracker = render_generic_multiworld_tracker(tracker_data, enabled_trackers) + + return ((tracker_data.get_room_saving_second() - datetime.datetime.now().second) + % TRACKER_CACHE_TIMEOUT_IN_SECONDS or TRACKER_CACHE_TIMEOUT_IN_SECONDS, room.last_activity, tracker) def get_enabled_multiworld_trackers(room: Room) -> Dict[str, Callable]: @@ -358,10 +403,13 @@ def get_enabled_multiworld_trackers(room: Room) -> Dict[str, Callable]: def render_generic_tracker(tracker_data: TrackerData, team: int, player: int) -> str: game = tracker_data.get_player_game(team, player) - # Add received index to all received items, excluding starting inventory. received_items_in_order = {} - for received_index, network_item in enumerate(tracker_data.get_player_received_items(team, player), start=1): - received_items_in_order[network_item.item] = received_index + starting_inventory = tracker_data.get_player_starting_inventory(team, player) + for index, item in enumerate(starting_inventory): + received_items_in_order[item] = index + for index, network_item in enumerate(tracker_data.get_player_received_items(team, player), + start=len(starting_inventory)): + received_items_in_order[network_item.item] = index return render_template( template_name_or_list="genericTracker.html", @@ -405,9 +453,30 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker videos=tracker_data.get_room_videos(), item_id_to_name=tracker_data.item_id_to_name, location_id_to_name=tracker_data.location_id_to_name, + saving_second=tracker_data.get_room_saving_second(), ) +def render_generic_multiworld_sphere_tracker(tracker_data: TrackerData) -> str: + return render_template( + "multispheretracker.html", + room=tracker_data.room, + tracker_data=tracker_data, + ) + + +@app.route("/sphere_tracker/") +@cache.memoize(timeout=TRACKER_CACHE_TIMEOUT_IN_SECONDS) +def get_multiworld_sphere_tracker(tracker: UUID): + # Room must exist. + room = Room.get(tracker=tracker) + if not room: + abort(404) + + tracker_data = TrackerData(room) + return render_generic_multiworld_sphere_tracker(tracker_data) + + # TODO: This is a temporary solution until a proper Tracker API can be implemented for tracker templates and data to # live in their respective world folders. @@ -416,11 +485,11 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker if "Factorio" in network_data_package["games"]: def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_trackers: List[str]): - inventories: Dict[TeamPlayer, Dict[int, int]] = { - (team, player): { + inventories: Dict[TeamPlayer, collections.Counter[str]] = { + (team, player): collections.Counter({ tracker_data.item_id_to_name["Factorio"][item_id]: count for item_id, count in tracker_data.get_player_inventory_counts(team, player).items() - } for team, players in tracker_data.get_all_slots().items() for player in players + }) for team, players in tracker_data.get_all_slots().items() for player in players if tracker_data.get_player_game(team, player) == "Factorio" } @@ -450,210 +519,111 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke _multiworld_trackers["Factorio"] = render_Factorio_multiworld_tracker if "A Link to the Past" in network_data_package["games"]: - def render_ALinkToThePast_multiworld_tracker(tracker_data: TrackerData, enabled_trackers: List[str]): - # Helper objects. - alttp_id_lookup = tracker_data.item_name_to_id["A Link to the Past"] + # Mapping from non-progressive item to progressive name and max level. + non_progressive_items = { + "Fighter Sword": ("Progressive Sword", 1), + "Master Sword": ("Progressive Sword", 2), + "Tempered Sword": ("Progressive Sword", 3), + "Golden Sword": ("Progressive Sword", 4), + "Power Glove": ("Progressive Glove", 1), + "Titans Mitts": ("Progressive Glove", 2), + "Bow": ("Progressive Bow", 1), + "Silver Bow": ("Progressive Bow", 2), + "Blue Mail": ("Progressive Mail", 1), + "Red Mail": ("Progressive Mail", 2), + "Blue Shield": ("Progressive Shield", 1), + "Red Shield": ("Progressive Shield", 2), + "Mirror Shield": ("Progressive Shield", 3), + } - multi_items = { - alttp_id_lookup[name] - for name in ("Progressive Sword", "Progressive Bow", "Bottle", "Progressive Glove", "Triforce Piece") - } - links = { - "Bow": "Progressive Bow", - "Silver Arrows": "Progressive Bow", - "Silver Bow": "Progressive Bow", - "Progressive Bow (Alt)": "Progressive Bow", - "Bottle (Red Potion)": "Bottle", - "Bottle (Green Potion)": "Bottle", - "Bottle (Blue Potion)": "Bottle", - "Bottle (Fairy)": "Bottle", - "Bottle (Bee)": "Bottle", - "Bottle (Good Bee)": "Bottle", - "Fighter Sword": "Progressive Sword", - "Master Sword": "Progressive Sword", - "Tempered Sword": "Progressive Sword", - "Golden Sword": "Progressive Sword", - "Power Glove": "Progressive Glove", - "Titans Mitts": "Progressive Glove", - } - links = {alttp_id_lookup[key]: alttp_id_lookup[value] for key, value in links.items()} - levels = { - "Fighter Sword": 1, - "Master Sword": 2, - "Tempered Sword": 3, - "Golden Sword": 4, - "Power Glove": 1, - "Titans Mitts": 2, - "Bow": 1, - "Silver Bow": 2, - "Triforce Piece": 90, - } - tracking_names = [ - "Progressive Sword", "Progressive Bow", "Book of Mudora", "Hammer", "Hookshot", "Magic Mirror", "Flute", - "Pegasus Boots", "Progressive Glove", "Flippers", "Moon Pearl", "Blue Boomerang", "Red Boomerang", - "Bug Catching Net", "Cape", "Shovel", "Lamp", "Mushroom", "Magic Powder", "Cane of Somaria", - "Cane of Byrna", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Bottle", "Triforce Piece", "Triforce", - ] - default_locations = { - "Light World": { - 1572864, 1572865, 60034, 1572867, 1572868, 60037, 1572869, 1572866, 60040, 59788, 60046, 60175, - 1572880, 60049, 60178, 1572883, 60052, 60181, 1572885, 60055, 60184, 191256, 60058, 60187, 1572884, - 1572886, 1572887, 1572906, 60202, 60205, 59824, 166320, 1010170, 60208, 60211, 60214, 60217, 59836, - 60220, 60223, 59839, 1573184, 60226, 975299, 1573188, 1573189, 188229, 60229, 60232, 1573193, - 1573194, 60235, 1573187, 59845, 59854, 211407, 60238, 59857, 1573185, 1573186, 1572882, 212328, - 59881, 59761, 59890, 59770, 193020, 212605 - }, - "Dark World": { - 59776, 59779, 975237, 1572870, 60043, 1572881, 60190, 60193, 60196, 60199, 60840, 1573190, 209095, - 1573192, 1573191, 60241, 60244, 60247, 60250, 59884, 59887, 60019, 60022, 60028, 60031 - }, - "Desert Palace": {1573216, 59842, 59851, 59791, 1573201, 59830}, - "Eastern Palace": {1573200, 59827, 59893, 59767, 59833, 59773}, - "Hyrule Castle": {60256, 60259, 60169, 60172, 59758, 59764, 60025, 60253}, - "Agahnims Tower": {60082, 60085}, - "Tower of Hera": {1573218, 59878, 59821, 1573202, 59896, 59899}, - "Swamp Palace": {60064, 60067, 60070, 59782, 59785, 60073, 60076, 60079, 1573204, 60061}, - "Thieves Town": {59905, 59908, 59911, 59914, 59917, 59920, 59923, 1573206}, - "Skull Woods": {59809, 59902, 59848, 59794, 1573205, 59800, 59803, 59806}, - "Ice Palace": {59872, 59875, 59812, 59818, 59860, 59797, 1573207, 59869}, - "Misery Mire": {60001, 60004, 60007, 60010, 60013, 1573208, 59866, 59998}, - "Turtle Rock": {59938, 59941, 59944, 1573209, 59947, 59950, 59953, 59956, 59926, 59929, 59932, 59935}, - "Palace of Darkness": { - 59968, 59971, 59974, 59977, 59980, 59983, 59986, 1573203, 59989, 59959, 59992, 59962, 59995, - 59965 - }, - "Ganons Tower": { - 60160, 60163, 60166, 60088, 60091, 60094, 60097, 60100, 60103, 60106, 60109, 60112, 60115, 60118, - 60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157 - }, - "Total": set() - } - key_only_locations = { - "Light World": set(), - "Dark World": set(), - "Desert Palace": {0x140031, 0x14002b, 0x140061, 0x140028}, - "Eastern Palace": {0x14005b, 0x140049}, - "Hyrule Castle": {0x140037, 0x140034, 0x14000d, 0x14003d}, - "Agahnims Tower": {0x140061, 0x140052}, - "Tower of Hera": set(), - "Swamp Palace": {0x140019, 0x140016, 0x140013, 0x140010, 0x14000a}, - "Thieves Town": {0x14005e, 0x14004f}, - "Skull Woods": {0x14002e, 0x14001c}, - "Ice Palace": {0x140004, 0x140022, 0x140025, 0x140046}, - "Misery Mire": {0x140055, 0x14004c, 0x140064}, - "Turtle Rock": {0x140058, 0x140007}, - "Palace of Darkness": set(), - "Ganons Tower": {0x140040, 0x140043, 0x14003a, 0x14001f}, - "Total": set() - } - location_to_area = {} - for area, locations in default_locations.items(): - for location in locations: - location_to_area[location] = area - for area, locations in key_only_locations.items(): - for location in locations: - location_to_area[location] = area - - checks_in_area = {area: len(checks) for area, checks in default_locations.items()} - checks_in_area["Total"] = 216 - ordered_areas = ( - "Light World", "Dark World", "Hyrule Castle", "Agahnims Tower", "Eastern Palace", "Desert Palace", - "Tower of Hera", "Palace of Darkness", "Swamp Palace", "Skull Woods", "Thieves Town", "Ice Palace", - "Misery Mire", "Turtle Rock", "Ganons Tower", "Total" - ) + progressive_item_max = { + "Progressive Sword": 4, + "Progressive Glove": 2, + "Progressive Bow": 2, + "Progressive Mail": 2, + "Progressive Shield": 3, + } - player_checks_in_area = { - (team, player): { - area_name: len(tracker_data._multidata["checks_in_area"][player][area_name]) - if area_name != "Total" else tracker_data._multidata["checks_in_area"][player]["Total"] - for area_name in ordered_areas - } - for team, players in tracker_data.get_all_slots().items() - for player in players - if tracker_data.get_slot_info(team, player).type != SlotType.group and - tracker_data.get_slot_info(team, player).game == "A Link to the Past" - } + bottle_items = [ + "Bottle", + "Bottle (Bee)", + "Bottle (Blue Potion)", + "Bottle (Fairy)", + "Bottle (Good Bee)", + "Bottle (Green Potion)", + "Bottle (Red Potion)", + ] + + known_regions = [ + "Light World", "Dark World", "Hyrule Castle", "Agahnims Tower", "Eastern Palace", "Desert Palace", + "Tower of Hera", "Palace of Darkness", "Swamp Palace", "Thieves Town", "Skull Woods", "Ice Palace", + "Misery Mire", "Turtle Rock", "Ganons Tower" + ] + + class RegionCounts(NamedTuple): + total: int + checked: int + + def prepare_inventories(team: int, player: int, inventory: Counter[str], tracker_data: TrackerData): + for item, (prog_item, level) in non_progressive_items.items(): + if item in inventory: + inventory[prog_item] = min(max(inventory[prog_item], level), progressive_item_max[prog_item]) + + for bottle in bottle_items: + inventory["Bottles"] = min(inventory["Bottles"] + inventory[bottle], 4) + + if "Progressive Bow (Alt)" in inventory: + inventory["Progressive Bow"] += inventory["Progressive Bow (Alt)"] + inventory["Progressive Bow"] = min(inventory["Progressive Bow"], progressive_item_max["Progressive Bow"]) + + # Highlight 'bombs' if we received any bomb upgrades in bombless start. + # In race mode, we'll just assume bombless start for simplicity. + if tracker_data.get_slot_data(team, player).get("bombless_start", True): + inventory["Bombs"] = sum(count for item, count in inventory.items() if item.startswith("Bomb Upgrade")) + else: + inventory["Bombs"] = 1 + + # Triforce item if we meet goal. + if tracker_data.get_room_client_statuses()[team, player] == ClientStatus.CLIENT_GOAL: + inventory["Triforce"] = 1 - tracking_ids = [] - for item in tracking_names: - tracking_ids.append(alttp_id_lookup[item]) - - # Can't wait to get this into the apworld. Oof. - from worlds.alttp import Items - - small_key_ids = {} - big_key_ids = {} - ids_small_key = {} - ids_big_key = {} - for item_name, data in Items.item_table.items(): - if "Key" in item_name: - area = item_name.split("(")[1][:-1] - if "Small" in item_name: - small_key_ids[area] = data[2] - ids_small_key[data[2]] = area - else: - big_key_ids[area] = data[2] - ids_big_key[data[2]] = area - - def _get_location_table(checks_table: dict) -> dict: - loc_to_area = {} - for area, locations in checks_table.items(): - if area == "Total": - continue - for location in locations: - loc_to_area[location] = area - return loc_to_area - - player_location_to_area = { - (team, player): _get_location_table(tracker_data._multidata["checks_in_area"][player]) - for team, players in tracker_data.get_all_slots().items() - for player in players - if tracker_data.get_slot_info(team, player).type != SlotType.group and - tracker_data.get_slot_info(team, player).game == "A Link to the Past" + def render_ALinkToThePast_multiworld_tracker(tracker_data: TrackerData, enabled_trackers: List[str]): + inventories: Dict[Tuple[int, int], Counter[str]] = { + (team, player): collections.Counter({ + tracker_data.item_id_to_name["A Link to the Past"][code]: count + for code, count in tracker_data.get_player_inventory_counts(team, player).items() + }) + for team, players in tracker_data.get_all_players().items() + for player in players if tracker_data.get_slot_info(team, player).game == "A Link to the Past" } - checks_done: Dict[TeamPlayer, Dict[str: int]] = { - (team, player): {location_name: 0 for location_name in default_locations} - for team, players in tracker_data.get_all_slots().items() - for player in players - if tracker_data.get_slot_info(team, player).type != SlotType.group and - tracker_data.get_slot_info(team, player).game == "A Link to the Past" - } + # Translate non-progression items to progression items for tracker simplicity. + for (team, player), inventory in inventories.items(): + prepare_inventories(team, player, inventory, tracker_data) - inventories: Dict[TeamPlayer, Dict[int, int]] = {} - player_big_key_locations = {(player): set() for player in tracker_data.get_all_slots()[0]} - player_small_key_locations = {player: set() for player in tracker_data.get_all_slots()[0]} - group_big_key_locations = set() - group_key_locations = set() - - for (team, player), locations in checks_done.items(): - # Check if game complete. - if tracker_data.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL: - inventories[team, player][106] = 1 # Triforce - - # Count number of locations checked. - for location in tracker_data.get_player_checked_locations(team, player): - checks_done[team, player][player_location_to_area[team, player][location]] += 1 - checks_done[team, player]["Total"] += 1 - - # Count keys. - for location, (item, receiving, _) in tracker_data.get_player_locations(team, player).items(): - if item in ids_big_key: - player_big_key_locations[receiving].add(ids_big_key[item]) - elif item in ids_small_key: - player_small_key_locations[receiving].add(ids_small_key[item]) - - # Iterate over received items and build inventory/key counts. - inventories[team, player] = collections.Counter() - for network_item in tracker_data.get_player_received_items(team, player): - target_item = links.get(network_item.item, network_item.item) - if network_item.item in levels: # non-progressive - inventories[team, player][target_item] = (max(inventories[team, player][target_item], levels[network_item.item])) - else: - inventories[team, player][target_item] += 1 + regions: Dict[Tuple[int, int], Dict[str, RegionCounts]] = { + (team, player): { + region_name: RegionCounts( + total=len(tracker_data._multidata["checks_in_area"][player][region_name]), + checked=sum( + 1 for location in tracker_data._multidata["checks_in_area"][player][region_name] + if location in tracker_data.get_player_checked_locations(team, player) + ), + ) + for region_name in known_regions + } + for team, players in tracker_data.get_all_players().items() + for player in players if tracker_data.get_slot_info(team, player).game == "A Link to the Past" + } - group_key_locations |= player_small_key_locations[player] - group_big_key_locations |= player_big_key_locations[player] + # Get a totals count. + for player, player_regions in regions.items(): + total = 0 + checked = 0 + for region, region_counts in player_regions.items(): + total += region_counts.total + checked += region_counts.checked + regions[player]["Total"] = RegionCounts(total, checked) return render_template( "multitracker__ALinkToThePast.html", @@ -676,209 +646,39 @@ def _get_location_table(checks_table: dict) -> dict: item_id_to_name=tracker_data.item_id_to_name, location_id_to_name=tracker_data.location_id_to_name, inventories=inventories, - tracking_names=tracking_names, - tracking_ids=tracking_ids, - multi_items=multi_items, - checks_done=checks_done, - ordered_areas=ordered_areas, - checks_in_area=player_checks_in_area, - key_locations=group_key_locations, - big_key_locations=group_big_key_locations, - small_key_ids=small_key_ids, - big_key_ids=big_key_ids, + regions=regions, + known_regions=known_regions, ) def render_ALinkToThePast_tracker(tracker_data: TrackerData, team: int, player: int) -> str: - # Helper objects. - alttp_id_lookup = tracker_data.item_name_to_id["A Link to the Past"] - - links = { - "Bow": "Progressive Bow", - "Silver Arrows": "Progressive Bow", - "Silver Bow": "Progressive Bow", - "Progressive Bow (Alt)": "Progressive Bow", - "Bottle (Red Potion)": "Bottle", - "Bottle (Green Potion)": "Bottle", - "Bottle (Blue Potion)": "Bottle", - "Bottle (Fairy)": "Bottle", - "Bottle (Bee)": "Bottle", - "Bottle (Good Bee)": "Bottle", - "Fighter Sword": "Progressive Sword", - "Master Sword": "Progressive Sword", - "Tempered Sword": "Progressive Sword", - "Golden Sword": "Progressive Sword", - "Power Glove": "Progressive Glove", - "Titans Mitts": "Progressive Glove", - } - links = {alttp_id_lookup[key]: alttp_id_lookup[value] for key, value in links.items()} - levels = { - "Fighter Sword": 1, - "Master Sword": 2, - "Tempered Sword": 3, - "Golden Sword": 4, - "Power Glove": 1, - "Titans Mitts": 2, - "Bow": 1, - "Silver Bow": 2, - "Triforce Piece": 90, - } - tracking_names = [ - "Progressive Sword", "Progressive Bow", "Book of Mudora", "Hammer", "Hookshot", "Magic Mirror", "Flute", - "Pegasus Boots", "Progressive Glove", "Flippers", "Moon Pearl", "Blue Boomerang", "Red Boomerang", - "Bug Catching Net", "Cape", "Shovel", "Lamp", "Mushroom", "Magic Powder", "Cane of Somaria", - "Cane of Byrna", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Bottle", "Triforce Piece", "Triforce", - ] - default_locations = { - "Light World": { - 1572864, 1572865, 60034, 1572867, 1572868, 60037, 1572869, 1572866, 60040, 59788, 60046, 60175, - 1572880, 60049, 60178, 1572883, 60052, 60181, 1572885, 60055, 60184, 191256, 60058, 60187, 1572884, - 1572886, 1572887, 1572906, 60202, 60205, 59824, 166320, 1010170, 60208, 60211, 60214, 60217, 59836, - 60220, 60223, 59839, 1573184, 60226, 975299, 1573188, 1573189, 188229, 60229, 60232, 1573193, - 1573194, 60235, 1573187, 59845, 59854, 211407, 60238, 59857, 1573185, 1573186, 1572882, 212328, - 59881, 59761, 59890, 59770, 193020, 212605 - }, - "Dark World": { - 59776, 59779, 975237, 1572870, 60043, 1572881, 60190, 60193, 60196, 60199, 60840, 1573190, 209095, - 1573192, 1573191, 60241, 60244, 60247, 60250, 59884, 59887, 60019, 60022, 60028, 60031 - }, - "Desert Palace": {1573216, 59842, 59851, 59791, 1573201, 59830}, - "Eastern Palace": {1573200, 59827, 59893, 59767, 59833, 59773}, - "Hyrule Castle": {60256, 60259, 60169, 60172, 59758, 59764, 60025, 60253}, - "Agahnims Tower": {60082, 60085}, - "Tower of Hera": {1573218, 59878, 59821, 1573202, 59896, 59899}, - "Swamp Palace": {60064, 60067, 60070, 59782, 59785, 60073, 60076, 60079, 1573204, 60061}, - "Thieves Town": {59905, 59908, 59911, 59914, 59917, 59920, 59923, 1573206}, - "Skull Woods": {59809, 59902, 59848, 59794, 1573205, 59800, 59803, 59806}, - "Ice Palace": {59872, 59875, 59812, 59818, 59860, 59797, 1573207, 59869}, - "Misery Mire": {60001, 60004, 60007, 60010, 60013, 1573208, 59866, 59998}, - "Turtle Rock": {59938, 59941, 59944, 1573209, 59947, 59950, 59953, 59956, 59926, 59929, 59932, 59935}, - "Palace of Darkness": { - 59968, 59971, 59974, 59977, 59980, 59983, 59986, 1573203, 59989, 59959, 59992, 59962, 59995, - 59965 - }, - "Ganons Tower": { - 60160, 60163, 60166, 60088, 60091, 60094, 60097, 60100, 60103, 60106, 60109, 60112, 60115, 60118, - 60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157 - }, - "Total": set() - } - key_only_locations = { - "Light World": set(), - "Dark World": set(), - "Desert Palace": {0x140031, 0x14002b, 0x140061, 0x140028}, - "Eastern Palace": {0x14005b, 0x140049}, - "Hyrule Castle": {0x140037, 0x140034, 0x14000d, 0x14003d}, - "Agahnims Tower": {0x140061, 0x140052}, - "Tower of Hera": set(), - "Swamp Palace": {0x140019, 0x140016, 0x140013, 0x140010, 0x14000a}, - "Thieves Town": {0x14005e, 0x14004f}, - "Skull Woods": {0x14002e, 0x14001c}, - "Ice Palace": {0x140004, 0x140022, 0x140025, 0x140046}, - "Misery Mire": {0x140055, 0x14004c, 0x140064}, - "Turtle Rock": {0x140058, 0x140007}, - "Palace of Darkness": set(), - "Ganons Tower": {0x140040, 0x140043, 0x14003a, 0x14001f}, - "Total": set() - } - location_to_area = {} - for area, locations in default_locations.items(): - for checked_location in locations: - location_to_area[checked_location] = area - for area, locations in key_only_locations.items(): - for checked_location in locations: - location_to_area[checked_location] = area - - checks_in_area = {area: len(checks) for area, checks in default_locations.items()} - checks_in_area["Total"] = 216 - ordered_areas = ( - "Light World", "Dark World", "Hyrule Castle", "Agahnims Tower", "Eastern Palace", "Desert Palace", - "Tower of Hera", "Palace of Darkness", "Swamp Palace", "Skull Woods", "Thieves Town", "Ice Palace", - "Misery Mire", "Turtle Rock", "Ganons Tower", "Total" - ) - - tracking_ids = [] - for item in tracking_names: - tracking_ids.append(alttp_id_lookup[item]) - - # Can't wait to get this into the apworld. Oof. - from worlds.alttp import Items - - small_key_ids = {} - big_key_ids = {} - ids_small_key = {} - ids_big_key = {} - for item_name, data in Items.item_table.items(): - if "Key" in item_name: - area = item_name.split("(")[1][:-1] - if "Small" in item_name: - small_key_ids[area] = data[2] - ids_small_key[data[2]] = area - else: - big_key_ids[area] = data[2] - ids_big_key[data[2]] = area - - inventory = collections.Counter() - checks_done = {loc_name: 0 for loc_name in default_locations} - player_big_key_locations = set() - player_small_key_locations = set() - - player_locations = tracker_data.get_player_locations(team, player) - for checked_location in tracker_data.get_player_checked_locations(team, player): - if checked_location in player_locations: - area_name = location_to_area.get(checked_location, None) - if area_name: - checks_done[area_name] += 1 - - checks_done["Total"] += 1 - - for received_item in tracker_data.get_player_received_items(team, player): - target_item = links.get(received_item.item, received_item.item) - if received_item.item in levels: # non-progressive - inventory[target_item] = max(inventory[target_item], levels[received_item.item]) - else: - inventory[target_item] += 1 - - for location, (item_id, _, _) in player_locations.items(): - if item_id in ids_big_key: - player_big_key_locations.add(ids_big_key[item_id]) - elif item_id in ids_small_key: - player_small_key_locations.add(ids_small_key[item_id]) - - # Note the presence of the triforce item - if tracker_data.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL: - inventory[106] = 1 # Triforce + inventory = collections.Counter({ + tracker_data.item_id_to_name["A Link to the Past"][code]: count + for code, count in tracker_data.get_player_inventory_counts(team, player).items() + }) - # Progressive items need special handling for icons and class - progressive_items = { - "Progressive Sword": 94, - "Progressive Glove": 97, - "Progressive Bow": 100, - "Progressive Mail": 96, - "Progressive Shield": 95, - } - progressive_names = { - "Progressive Sword": [None, "Fighter Sword", "Master Sword", "Tempered Sword", "Golden Sword"], - "Progressive Glove": [None, "Power Glove", "Titan Mitts"], - "Progressive Bow": [None, "Bow", "Silver Bow"], - "Progressive Mail": ["Green Mail", "Blue Mail", "Red Mail"], - "Progressive Shield": [None, "Blue Shield", "Red Shield", "Mirror Shield"] + # Translate non-progression items to progression items for tracker simplicity. + prepare_inventories(team, player, inventory, tracker_data) + + regions = { + region_name: { + "checked": sum( + 1 for location in tracker_data._multidata["checks_in_area"][player][region_name] + if location in tracker_data.get_player_checked_locations(team, player) + ), + "locations": [ + ( + tracker_data.location_id_to_name["A Link to the Past"][location], + location in tracker_data.get_player_checked_locations(team, player) + ) + for location in tracker_data._multidata["checks_in_area"][player][region_name] + ], + } + for region_name in known_regions } - # Determine which icon to use - display_data = {} - for item_name, item_id in progressive_items.items(): - level = min(inventory[item_id], len(progressive_names[item_name]) - 1) - display_name = progressive_names[item_name][level] - acquired = True - if not display_name: - acquired = False - display_name = progressive_names[item_name][level + 1] - base_name = item_name.split(maxsplit=1)[1].lower() - display_data[base_name + "_acquired"] = acquired - display_data[base_name + "_icon"] = display_name - - # The single player tracker doesn't care about overworld, underworld, and total checks. Maybe it should? - sp_areas = ordered_areas[2:15] + # Sort locations in regions by name + for region in regions: + regions[region]["locations"].sort() return render_template( template_name_or_list="tracker__ALinkToThePast.html", @@ -887,15 +687,8 @@ def render_ALinkToThePast_tracker(tracker_data: TrackerData, team: int, player: player=player, inventory=inventory, player_name=tracker_data.get_player_name(team, player), - checks_done=checks_done, - checks_in_area=checks_in_area, - acquired_items={tracker_data.item_id_to_name["A Link to the Past"][id] for id in inventory}, - sp_areas=sp_areas, - small_key_ids=small_key_ids, - key_locations=player_small_key_locations, - big_key_ids=big_key_ids, - big_key_locations=player_big_key_locations, - **display_data, + regions=regions, + known_regions=known_regions, ) _multiworld_trackers["A Link to the Past"] = render_ALinkToThePast_multiworld_tracker @@ -1553,212 +1346,300 @@ def render_ChecksFinder_tracker(tracker_data: TrackerData, team: int, player: in _player_trackers["ChecksFinder"] = render_ChecksFinder_tracker -if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: - def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int, player: int) -> str: +if "Starcraft 2" in network_data_package["games"]: + def render_Starcraft2_tracker(tracker_data: TrackerData, team: int, player: int) -> str: SC2WOL_LOC_ID_OFFSET = 1000 + SC2HOTS_LOC_ID_OFFSET = 20000000 # Avoid clashes with The Legend of Zelda + SC2LOTV_LOC_ID_OFFSET = SC2HOTS_LOC_ID_OFFSET + 2000 + SC2NCO_LOC_ID_OFFSET = SC2LOTV_LOC_ID_OFFSET + 2500 + SC2WOL_ITEM_ID_OFFSET = 1000 + SC2HOTS_ITEM_ID_OFFSET = SC2WOL_ITEM_ID_OFFSET + 1000 + SC2LOTV_ITEM_ID_OFFSET = SC2HOTS_ITEM_ID_OFFSET + 1000 + + slot_data = tracker_data.get_slot_data(team, player) + minerals_per_item = slot_data.get("minerals_per_item", 15) + vespene_per_item = slot_data.get("vespene_per_item", 15) + starting_supply_per_item = slot_data.get("starting_supply_per_item", 2) + + github_icon_base_url = "https://matthewmarinets.github.io/ap_sc2_icons/icons/" + organics_icon_base_url = "https://0rganics.org/archipelago/sc2wol/" icons = { - "Starting Minerals": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-mineral-protoss.png", - "Starting Vespene": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-gas-terran.png", - "Starting Supply": "https://static.wikia.nocookie.net/starcraft/images/d/d3/TerranSupply_SC2_Icon1.gif", - - "Infantry Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel1.png", - "Infantry Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel2.png", - "Infantry Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel3.png", - "Infantry Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel1.png", - "Infantry Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel2.png", - "Infantry Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel3.png", - "Vehicle Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel1.png", - "Vehicle Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel2.png", - "Vehicle Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel3.png", - "Vehicle Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel1.png", - "Vehicle Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel2.png", - "Vehicle Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel3.png", - "Ship Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel1.png", - "Ship Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel2.png", - "Ship Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel3.png", - "Ship Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel1.png", - "Ship Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel2.png", - "Ship Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel3.png", + "Starting Minerals": github_icon_base_url + "blizzard/icon-mineral-nobg.png", + "Starting Vespene": github_icon_base_url + "blizzard/icon-gas-terran-nobg.png", + "Starting Supply": github_icon_base_url + "blizzard/icon-supply-terran_nobg.png", + + "Terran Infantry Weapons Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryweaponslevel1.png", + "Terran Infantry Weapons Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryweaponslevel2.png", + "Terran Infantry Weapons Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryweaponslevel3.png", + "Terran Infantry Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryarmorlevel1.png", + "Terran Infantry Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryarmorlevel2.png", + "Terran Infantry Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-infantryarmorlevel3.png", + "Terran Vehicle Weapons Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleweaponslevel1.png", + "Terran Vehicle Weapons Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleweaponslevel2.png", + "Terran Vehicle Weapons Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleweaponslevel3.png", + "Terran Vehicle Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleplatinglevel1.png", + "Terran Vehicle Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleplatinglevel2.png", + "Terran Vehicle Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-vehicleplatinglevel3.png", + "Terran Ship Weapons Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-shipweaponslevel1.png", + "Terran Ship Weapons Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-shipweaponslevel2.png", + "Terran Ship Weapons Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-shipweaponslevel3.png", + "Terran Ship Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-terran-shipplatinglevel1.png", + "Terran Ship Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-shipplatinglevel2.png", + "Terran Ship Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-terran-shipplatinglevel3.png", "Bunker": "https://static.wikia.nocookie.net/starcraft/images/c/c5/Bunker_SC2_Icon1.jpg", "Missile Turret": "https://static.wikia.nocookie.net/starcraft/images/5/5f/MissileTurret_SC2_Icon1.jpg", "Sensor Tower": "https://static.wikia.nocookie.net/starcraft/images/d/d2/SensorTower_SC2_Icon1.jpg", - "Projectile Accelerator (Bunker)": "https://0rganics.org/archipelago/sc2wol/ProjectileAccelerator.png", - "Neosteel Bunker (Bunker)": "https://0rganics.org/archipelago/sc2wol/NeosteelBunker.png", - "Titanium Housing (Missile Turret)": "https://0rganics.org/archipelago/sc2wol/TitaniumHousing.png", - "Hellstorm Batteries (Missile Turret)": "https://0rganics.org/archipelago/sc2wol/HellstormBatteries.png", - "Advanced Construction (SCV)": "https://0rganics.org/archipelago/sc2wol/AdvancedConstruction.png", - "Dual-Fusion Welders (SCV)": "https://0rganics.org/archipelago/sc2wol/Dual-FusionWelders.png", - "Fire-Suppression System (Building)": "https://0rganics.org/archipelago/sc2wol/Fire-SuppressionSystem.png", - "Orbital Command (Building)": "https://0rganics.org/archipelago/sc2wol/OrbitalCommandCampaign.png", + "Projectile Accelerator (Bunker)": github_icon_base_url + "blizzard/btn-upgrade-zerg-stukov-bunkerresearchbundle_05.png", + "Neosteel Bunker (Bunker)": organics_icon_base_url + "NeosteelBunker.png", + "Titanium Housing (Missile Turret)": organics_icon_base_url + "TitaniumHousing.png", + "Hellstorm Batteries (Missile Turret)": github_icon_base_url + "blizzard/btn-ability-stetmann-corruptormissilebarrage.png", + "Advanced Construction (SCV)": github_icon_base_url + "blizzard/btn-ability-mengsk-trooper-advancedconstruction.png", + "Dual-Fusion Welders (SCV)": github_icon_base_url + "blizzard/btn-upgrade-swann-scvdoublerepair.png", + "Hostile Environment Adaptation (SCV)": github_icon_base_url + "blizzard/btn-upgrade-swann-hellarmor.png", + "Fire-Suppression System Level 1": organics_icon_base_url + "Fire-SuppressionSystem.png", + "Fire-Suppression System Level 2": github_icon_base_url + "blizzard/btn-upgrade-swann-firesuppressionsystem.png", + + "Orbital Command": organics_icon_base_url + "OrbitalCommandCampaign.png", + "Planetary Command Module": github_icon_base_url + "original/btn-orbital-fortress.png", + "Lift Off (Planetary Fortress)": github_icon_base_url + "blizzard/btn-ability-terran-liftoff.png", + "Armament Stabilizers (Planetary Fortress)": github_icon_base_url + "blizzard/btn-ability-mengsk-siegetank-flyingtankarmament.png", + "Advanced Targeting (Planetary Fortress)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", "Marine": "https://static.wikia.nocookie.net/starcraft/images/4/47/Marine_SC2_Icon1.jpg", - "Medic": "https://static.wikia.nocookie.net/starcraft/images/7/74/Medic_SC2_Rend1.jpg", - "Firebat": "https://static.wikia.nocookie.net/starcraft/images/3/3c/Firebat_SC2_Rend1.jpg", + "Medic": github_icon_base_url + "blizzard/btn-unit-terran-medic.png", + "Firebat": github_icon_base_url + "blizzard/btn-unit-terran-firebat.png", "Marauder": "https://static.wikia.nocookie.net/starcraft/images/b/ba/Marauder_SC2_Icon1.jpg", "Reaper": "https://static.wikia.nocookie.net/starcraft/images/7/7d/Reaper_SC2_Icon1.jpg", - - "Stimpack (Marine)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Marine)": "/static/static/icons/sc2/superstimpack.png", - "Combat Shield (Marine)": "https://0rganics.org/archipelago/sc2wol/CombatShieldCampaign.png", - "Laser Targeting System (Marine)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Magrail Munitions (Marine)": "/static/static/icons/sc2/magrailmunitions.png", - "Optimized Logistics (Marine)": "/static/static/icons/sc2/optimizedlogistics.png", - "Advanced Medic Facilities (Medic)": "https://0rganics.org/archipelago/sc2wol/AdvancedMedicFacilities.png", - "Stabilizer Medpacks (Medic)": "https://0rganics.org/archipelago/sc2wol/StabilizerMedpacks.png", - "Restoration (Medic)": "/static/static/icons/sc2/restoration.png", - "Optical Flare (Medic)": "/static/static/icons/sc2/opticalflare.png", - "Optimized Logistics (Medic)": "/static/static/icons/sc2/optimizedlogistics.png", - "Incinerator Gauntlets (Firebat)": "https://0rganics.org/archipelago/sc2wol/IncineratorGauntlets.png", - "Juggernaut Plating (Firebat)": "https://0rganics.org/archipelago/sc2wol/JuggernautPlating.png", - "Stimpack (Firebat)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Firebat)": "/static/static/icons/sc2/superstimpack.png", - "Optimized Logistics (Firebat)": "/static/static/icons/sc2/optimizedlogistics.png", - "Concussive Shells (Marauder)": "https://0rganics.org/archipelago/sc2wol/ConcussiveShellsCampaign.png", - "Kinetic Foam (Marauder)": "https://0rganics.org/archipelago/sc2wol/KineticFoam.png", - "Stimpack (Marauder)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Marauder)": "/static/static/icons/sc2/superstimpack.png", - "Laser Targeting System (Marauder)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Magrail Munitions (Marauder)": "/static/static/icons/sc2/magrailmunitions.png", - "Internal Tech Module (Marauder)": "/static/static/icons/sc2/internalizedtechmodule.png", - "U-238 Rounds (Reaper)": "https://0rganics.org/archipelago/sc2wol/U-238Rounds.png", - "G-4 Clusterbomb (Reaper)": "https://0rganics.org/archipelago/sc2wol/G-4Clusterbomb.png", - "Stimpack (Reaper)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Reaper)": "/static/static/icons/sc2/superstimpack.png", - "Laser Targeting System (Reaper)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Advanced Cloaking Field (Reaper)": "/static/static/icons/sc2/terran-cloak-color.png", - "Spider Mines (Reaper)": "/static/static/icons/sc2/spidermine.png", - "Combat Drugs (Reaper)": "/static/static/icons/sc2/reapercombatdrugs.png", + "Ghost": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Ghost_SC2_Icon1.jpg", + "Spectre": github_icon_base_url + "original/btn-unit-terran-spectre.png", + "HERC": github_icon_base_url + "blizzard/btn-unit-terran-herc.png", + + "Stimpack (Marine)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Combat Shield (Marine)": github_icon_base_url + "blizzard/btn-techupgrade-terran-combatshield-color.png", + "Laser Targeting System (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Magrail Munitions (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-magrailmunitions.png", + "Optimized Logistics (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Advanced Medic Facilities (Medic)": organics_icon_base_url + "AdvancedMedicFacilities.png", + "Stabilizer Medpacks (Medic)": github_icon_base_url + "blizzard/btn-upgrade-raynor-stabilizermedpacks.png", + "Restoration (Medic)": github_icon_base_url + "original/btn-ability-terran-restoration@scbw.png", + "Optical Flare (Medic)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-dragoonsolariteflare.png", + "Resource Efficiency (Medic)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Adaptive Medpacks (Medic)": github_icon_base_url + "blizzard/btn-ability-terran-heal-color.png", + "Nano Projector (Medic)": github_icon_base_url + "blizzard/talent-raynor-level03-firebatmedicrange.png", + "Incinerator Gauntlets (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-raynor-incineratorgauntlets.png", + "Juggernaut Plating (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-raynor-juggernautplating.png", + "Stimpack (Firebat)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Resource Efficiency (Firebat)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Infernal Pre-Igniter (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-terran-infernalpreigniter.png", + "Kinetic Foam (Firebat)": organics_icon_base_url + "KineticFoam.png", + "Nano Projectors (Firebat)": github_icon_base_url + "blizzard/talent-raynor-level03-firebatmedicrange.png", + "Concussive Shells (Marauder)": github_icon_base_url + "blizzard/btn-ability-terran-punishergrenade-color.png", + "Kinetic Foam (Marauder)": organics_icon_base_url + "KineticFoam.png", + "Stimpack (Marauder)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Laser Targeting System (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Magrail Munitions (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-magrailmunitions.png", + "Internal Tech Module (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Juggernaut Plating (Marauder)": organics_icon_base_url + "JuggernautPlating.png", + "U-238 Rounds (Reaper)": organics_icon_base_url + "U-238Rounds.png", + "G-4 Clusterbomb (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-kd8chargeex3.png", + "Stimpack (Reaper)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Laser Targeting System (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Advanced Cloaking Field (Reaper)": github_icon_base_url + "original/btn-permacloak-reaper.png", + "Spider Mines (Reaper)": github_icon_base_url + "original/btn-ability-terran-spidermine.png", + "Combat Drugs (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-reapercombatdrugs.png", + "Jet Pack Overdrive (Reaper)": github_icon_base_url + "blizzard/btn-ability-hornerhan-reaper-flightmode.png", + "Ocular Implants (Ghost)": organics_icon_base_url + "OcularImplants.png", + "Crius Suit (Ghost)": github_icon_base_url + "original/btn-permacloak-ghost.png", + "EMP Rounds (Ghost)": github_icon_base_url + "blizzard/btn-ability-terran-emp-color.png", + "Lockdown (Ghost)": github_icon_base_url + "original/btn-abilty-terran-lockdown@scbw.png", + "Resource Efficiency (Ghost)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Psionic Lash (Spectre)": organics_icon_base_url + "PsionicLash.png", + "Nyx-Class Cloaking Module (Spectre)": github_icon_base_url + "original/btn-permacloak-spectre.png", + "Impaler Rounds (Spectre)": github_icon_base_url + "blizzard/btn-techupgrade-terran-impalerrounds.png", + "Resource Efficiency (Spectre)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Juggernaut Plating (HERC)": organics_icon_base_url + "JuggernautPlating.png", + "Kinetic Foam (HERC)": organics_icon_base_url + "KineticFoam.png", + "Resource Efficiency (HERC)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", "Hellion": "https://static.wikia.nocookie.net/starcraft/images/5/56/Hellion_SC2_Icon1.jpg", - "Vulture": "https://static.wikia.nocookie.net/starcraft/images/d/da/Vulture_WoL.jpg", - "Goliath": "https://static.wikia.nocookie.net/starcraft/images/e/eb/Goliath_WoL.jpg", - "Diamondback": "https://static.wikia.nocookie.net/starcraft/images/a/a6/Diamondback_WoL.jpg", + "Vulture": github_icon_base_url + "blizzard/btn-unit-terran-vulture.png", + "Goliath": github_icon_base_url + "blizzard/btn-unit-terran-goliath.png", + "Diamondback": github_icon_base_url + "blizzard/btn-unit-terran-cobra.png", "Siege Tank": "https://static.wikia.nocookie.net/starcraft/images/5/57/SiegeTank_SC2_Icon1.jpg", - - "Twin-Linked Flamethrower (Hellion)": "https://0rganics.org/archipelago/sc2wol/Twin-LinkedFlamethrower.png", - "Thermite Filaments (Hellion)": "https://0rganics.org/archipelago/sc2wol/ThermiteFilaments.png", - "Hellbat Aspect (Hellion)": "/static/static/icons/sc2/hellionbattlemode.png", - "Smart Servos (Hellion)": "/static/static/icons/sc2/transformationservos.png", - "Optimized Logistics (Hellion)": "/static/static/icons/sc2/optimizedlogistics.png", - "Jump Jets (Hellion)": "/static/static/icons/sc2/jumpjets.png", - "Stimpack (Hellion)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Hellion)": "/static/static/icons/sc2/superstimpack.png", - "Cerberus Mine (Spider Mine)": "https://0rganics.org/archipelago/sc2wol/CerberusMine.png", - "High Explosive Munition (Spider Mine)": "/static/static/icons/sc2/high-explosive-spidermine.png", - "Replenishable Magazine (Vulture)": "https://0rganics.org/archipelago/sc2wol/ReplenishableMagazine.png", - "Ion Thrusters (Vulture)": "/static/static/icons/sc2/emergencythrusters.png", - "Auto Launchers (Vulture)": "/static/static/icons/sc2/jotunboosters.png", - "Multi-Lock Weapons System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Multi-LockWeaponsSystem.png", - "Ares-Class Targeting System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Ares-ClassTargetingSystem.png", - "Jump Jets (Goliath)": "/static/static/icons/sc2/jumpjets.png", - "Optimized Logistics (Goliath)": "/static/static/icons/sc2/optimizedlogistics.png", - "Tri-Lithium Power Cell (Diamondback)": "https://0rganics.org/archipelago/sc2wol/Tri-LithiumPowerCell.png", - "Shaped Hull (Diamondback)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png", - "Hyperfluxor (Diamondback)": "/static/static/icons/sc2/hyperfluxor.png", - "Burst Capacitors (Diamondback)": "/static/static/icons/sc2/burstcapacitors.png", - "Optimized Logistics (Diamondback)": "/static/static/icons/sc2/optimizedlogistics.png", - "Maelstrom Rounds (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/MaelstromRounds.png", - "Shaped Blast (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/ShapedBlast.png", - "Jump Jets (Siege Tank)": "/static/static/icons/sc2/jumpjets.png", - "Spider Mines (Siege Tank)": "/static/static/icons/sc2/siegetank-spidermines.png", - "Smart Servos (Siege Tank)": "/static/static/icons/sc2/transformationservos.png", - "Graduating Range (Siege Tank)": "/static/static/icons/sc2/siegetankrange.png", - "Laser Targeting System (Siege Tank)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Advanced Siege Tech (Siege Tank)": "/static/static/icons/sc2/improvedsiegemode.png", - "Internal Tech Module (Siege Tank)": "/static/static/icons/sc2/internalizedtechmodule.png", + "Thor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/Thor_SC2_Icon1.jpg", + "Predator": github_icon_base_url + "original/btn-unit-terran-predator.png", + "Widow Mine": github_icon_base_url + "blizzard/btn-unit-terran-widowmine.png", + "Cyclone": github_icon_base_url + "blizzard/btn-unit-terran-cyclone.png", + "Warhound": github_icon_base_url + "blizzard/btn-unit-terran-warhound.png", + + "Twin-Linked Flamethrower (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-mengsk-trooper-flamethrower.png", + "Thermite Filaments (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-infernalpreigniter.png", + "Hellbat Aspect (Hellion)": github_icon_base_url + "blizzard/btn-unit-terran-hellionbattlemode.png", + "Smart Servos (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Optimized Logistics (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Jump Jets (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-jumpjets.png", + "Stimpack (Hellion)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Infernal Plating (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-swann-hellarmor.png", + "Cerberus Mine (Spider Mine)": github_icon_base_url + "blizzard/btn-upgrade-raynor-cerberusmines.png", + "High Explosive Munition (Spider Mine)": github_icon_base_url + "original/btn-ability-terran-spidermine.png", + "Replenishable Magazine (Vulture)": github_icon_base_url + "blizzard/btn-upgrade-raynor-replenishablemagazine.png", + "Replenishable Magazine (Free) (Vulture)": github_icon_base_url + "blizzard/btn-upgrade-raynor-replenishablemagazine.png", + "Ion Thrusters (Vulture)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Auto Launchers (Vulture)": github_icon_base_url + "blizzard/btn-upgrade-terran-jotunboosters.png", + "Auto-Repair (Vulture)": github_icon_base_url + "blizzard/ui_tipicon_campaign_space01-repair.png", + "Multi-Lock Weapons System (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-swann-multilockweaponsystem.png", + "Ares-Class Targeting System (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-swann-aresclasstargetingsystem.png", + "Jump Jets (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-terran-jumpjets.png", + "Optimized Logistics (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Shaped Hull (Goliath)": organics_icon_base_url + "ShapedHull.png", + "Resource Efficiency (Goliath)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Internal Tech Module (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Tri-Lithium Power Cell (Diamondback)": github_icon_base_url + "original/btn-upgrade-terran-trilithium-power-cell.png", + "Tungsten Spikes (Diamondback)": github_icon_base_url + "original/btn-upgrade-terran-tungsten-spikes.png", + "Shaped Hull (Diamondback)": organics_icon_base_url + "ShapedHull.png", + "Hyperfluxor (Diamondback)": github_icon_base_url + "blizzard/btn-upgrade-mengsk-engineeringbay-orbitaldrop.png", + "Burst Capacitors (Diamondback)": github_icon_base_url + "blizzard/btn-ability-terran-electricfield.png", + "Ion Thrusters (Diamondback)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Resource Efficiency (Diamondback)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Maelstrom Rounds (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-raynor-maelstromrounds.png", + "Shaped Blast (Siege Tank)": organics_icon_base_url + "ShapedBlast.png", + "Jump Jets (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-jumpjets.png", + "Spider Mines (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-siegetank-spidermines.png", + "Smart Servos (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Graduating Range (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-siegetankrange.png", + "Laser Targeting System (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Advanced Siege Tech (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-raynor-improvedsiegemode.png", + "Internal Tech Module (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Shaped Hull (Siege Tank)": organics_icon_base_url + "ShapedHull.png", + "Resource Efficiency (Siege Tank)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "330mm Barrage Cannon (Thor)": github_icon_base_url + "original/btn-ability-thor-330mm.png", + "Immortality Protocol (Thor)": github_icon_base_url + "blizzard/btn-techupgrade-terran-immortalityprotocol.png", + "Immortality Protocol (Free) (Thor)": github_icon_base_url + "blizzard/btn-techupgrade-terran-immortalityprotocol.png", + "High Impact Payload (Thor)": github_icon_base_url + "blizzard/btn-unit-terran-thorsiegemode.png", + "Smart Servos (Thor)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Button With a Skull on It (Thor)": github_icon_base_url + "blizzard/btn-ability-terran-nuclearstrike-color.png", + "Laser Targeting System (Thor)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Large Scale Field Construction (Thor)": github_icon_base_url + "blizzard/talent-swann-level12-immortalityprotocol.png", + "Resource Efficiency (Predator)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Cloak (Predator)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "Charge (Predator)": github_icon_base_url + "blizzard/btn-ability-protoss-charge-color.png", + "Predator's Fury (Predator)": github_icon_base_url + "blizzard/btn-ability-protoss-shadowfury.png", + "Drilling Claws (Widow Mine)": github_icon_base_url + "blizzard/btn-upgrade-terran-researchdrillingclaws.png", + "Concealment (Widow Mine)": github_icon_base_url + "blizzard/btn-ability-terran-widowminehidden.png", + "Black Market Launchers (Widow Mine)": github_icon_base_url + "blizzard/btn-ability-hornerhan-widowmine-attackrange.png", + "Executioner Missiles (Widow Mine)": github_icon_base_url + "blizzard/btn-ability-hornerhan-widowmine-deathblossom.png", + "Mag-Field Accelerators (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-terran-magfieldaccelerator.png", + "Mag-Field Launchers (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-terran-cyclonerangeupgrade.png", + "Targeting Optics (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-swann-targetingoptics.png", + "Rapid Fire Launchers (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-raynor-ripwavemissiles.png", + "Resource Efficiency (Cyclone)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Internal Tech Module (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Resource Efficiency (Warhound)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Reinforced Plating (Warhound)": github_icon_base_url + "original/btn-research-zerg-fortifiedbunker.png", "Medivac": "https://static.wikia.nocookie.net/starcraft/images/d/db/Medivac_SC2_Icon1.jpg", - "Wraith": "https://static.wikia.nocookie.net/starcraft/images/7/75/Wraith_WoL.jpg", + "Wraith": github_icon_base_url + "blizzard/btn-unit-terran-wraith.png", "Viking": "https://static.wikia.nocookie.net/starcraft/images/2/2a/Viking_SC2_Icon1.jpg", "Banshee": "https://static.wikia.nocookie.net/starcraft/images/3/32/Banshee_SC2_Icon1.jpg", "Battlecruiser": "https://static.wikia.nocookie.net/starcraft/images/f/f5/Battlecruiser_SC2_Icon1.jpg", - - "Rapid Deployment Tube (Medivac)": "https://0rganics.org/archipelago/sc2wol/RapidDeploymentTube.png", - "Advanced Healing AI (Medivac)": "https://0rganics.org/archipelago/sc2wol/AdvancedHealingAI.png", - "Expanded Hull (Medivac)": "/static/static/icons/sc2/neosteelfortifiedarmor.png", - "Afterburners (Medivac)": "/static/static/icons/sc2/medivacemergencythrusters.png", - "Tomahawk Power Cells (Wraith)": "https://0rganics.org/archipelago/sc2wol/TomahawkPowerCells.png", - "Displacement Field (Wraith)": "https://0rganics.org/archipelago/sc2wol/DisplacementField.png", - "Advanced Laser Technology (Wraith)": "/static/static/icons/sc2/improvedburstlaser.png", - "Ripwave Missiles (Viking)": "https://0rganics.org/archipelago/sc2wol/RipwaveMissiles.png", - "Phobos-Class Weapons System (Viking)": "https://0rganics.org/archipelago/sc2wol/Phobos-ClassWeaponsSystem.png", - "Smart Servos (Viking)": "/static/static/icons/sc2/transformationservos.png", - "Magrail Munitions (Viking)": "/static/static/icons/sc2/magrailmunitions.png", - "Cross-Spectrum Dampeners (Banshee)": "/static/static/icons/sc2/crossspectrumdampeners.png", - "Advanced Cross-Spectrum Dampeners (Banshee)": "https://0rganics.org/archipelago/sc2wol/Cross-SpectrumDampeners.png", - "Shockwave Missile Battery (Banshee)": "https://0rganics.org/archipelago/sc2wol/ShockwaveMissileBattery.png", - "Hyperflight Rotors (Banshee)": "/static/static/icons/sc2/hyperflightrotors.png", - "Laser Targeting System (Banshee)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Internal Tech Module (Banshee)": "/static/static/icons/sc2/internalizedtechmodule.png", - "Missile Pods (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/MissilePods.png", - "Defensive Matrix (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png", - "Tactical Jump (Battlecruiser)": "/static/static/icons/sc2/warpjump.png", - "Cloak (Battlecruiser)": "/static/static/icons/sc2/terran-cloak-color.png", - "ATX Laser Battery (Battlecruiser)": "/static/static/icons/sc2/specialordance.png", - "Optimized Logistics (Battlecruiser)": "/static/static/icons/sc2/optimizedlogistics.png", - "Internal Tech Module (Battlecruiser)": "/static/static/icons/sc2/internalizedtechmodule.png", - - "Ghost": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Ghost_SC2_Icon1.jpg", - "Spectre": "https://static.wikia.nocookie.net/starcraft/images/0/0d/Spectre_WoL.jpg", - "Thor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/Thor_SC2_Icon1.jpg", - - "Widow Mine": "/static/static/icons/sc2/widowmine.png", - "Cyclone": "/static/static/icons/sc2/cyclone.png", - "Liberator": "/static/static/icons/sc2/liberator.png", - "Valkyrie": "/static/static/icons/sc2/valkyrie.png", - - "Ocular Implants (Ghost)": "https://0rganics.org/archipelago/sc2wol/OcularImplants.png", - "Crius Suit (Ghost)": "https://0rganics.org/archipelago/sc2wol/CriusSuit.png", - "EMP Rounds (Ghost)": "/static/static/icons/sc2/terran-emp-color.png", - "Lockdown (Ghost)": "/static/static/icons/sc2/lockdown.png", - "Psionic Lash (Spectre)": "https://0rganics.org/archipelago/sc2wol/PsionicLash.png", - "Nyx-Class Cloaking Module (Spectre)": "https://0rganics.org/archipelago/sc2wol/Nyx-ClassCloakingModule.png", - "Impaler Rounds (Spectre)": "/static/static/icons/sc2/impalerrounds.png", - "330mm Barrage Cannon (Thor)": "https://0rganics.org/archipelago/sc2wol/330mmBarrageCannon.png", - "Immortality Protocol (Thor)": "https://0rganics.org/archipelago/sc2wol/ImmortalityProtocol.png", - "High Impact Payload (Thor)": "/static/static/icons/sc2/thorsiegemode.png", - "Smart Servos (Thor)": "/static/static/icons/sc2/transformationservos.png", - - "Optimized Logistics (Predator)": "/static/static/icons/sc2/optimizedlogistics.png", - "Drilling Claws (Widow Mine)": "/static/static/icons/sc2/drillingclaws.png", - "Concealment (Widow Mine)": "/static/static/icons/sc2/widowminehidden.png", - "Black Market Launchers (Widow Mine)": "/static/static/icons/sc2/widowmine-attackrange.png", - "Executioner Missiles (Widow Mine)": "/static/static/icons/sc2/widowmine-deathblossom.png", - "Mag-Field Accelerators (Cyclone)": "/static/static/icons/sc2/magfieldaccelerator.png", - "Mag-Field Launchers (Cyclone)": "/static/static/icons/sc2/cyclonerangeupgrade.png", - "Targeting Optics (Cyclone)": "/static/static/icons/sc2/targetingoptics.png", - "Rapid Fire Launchers (Cyclone)": "/static/static/icons/sc2/ripwavemissiles.png", - "Bio Mechanical Repair Drone (Raven)": "/static/static/icons/sc2/biomechanicaldrone.png", - "Spider Mines (Raven)": "/static/static/icons/sc2/siegetank-spidermines.png", - "Railgun Turret (Raven)": "/static/static/icons/sc2/autoturretblackops.png", - "Hunter-Seeker Weapon (Raven)": "/static/static/icons/sc2/specialordance.png", - "Interference Matrix (Raven)": "/static/static/icons/sc2/interferencematrix.png", - "Anti-Armor Missile (Raven)": "/static/static/icons/sc2/shreddermissile.png", - "Internal Tech Module (Raven)": "/static/static/icons/sc2/internalizedtechmodule.png", - "EMP Shockwave (Science Vessel)": "/static/static/icons/sc2/staticempblast.png", - "Defensive Matrix (Science Vessel)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png", - "Advanced Ballistics (Liberator)": "/static/static/icons/sc2/advanceballistics.png", - "Raid Artillery (Liberator)": "/static/static/icons/sc2/terrandefendermodestructureattack.png", - "Cloak (Liberator)": "/static/static/icons/sc2/terran-cloak-color.png", - "Laser Targeting System (Liberator)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Optimized Logistics (Liberator)": "/static/static/icons/sc2/optimizedlogistics.png", - "Enhanced Cluster Launchers (Valkyrie)": "https://0rganics.org/archipelago/sc2wol/HellstormBatteries.png", - "Shaped Hull (Valkyrie)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png", - "Burst Lasers (Valkyrie)": "/static/static/icons/sc2/improvedburstlaser.png", - "Afterburners (Valkyrie)": "/static/static/icons/sc2/medivacemergencythrusters.png", + "Raven": "https://static.wikia.nocookie.net/starcraft/images/1/19/SC2_Lab_Raven_Icon.png", + "Science Vessel": "https://static.wikia.nocookie.net/starcraft/images/c/c3/SC2_Lab_SciVes_Icon.png", + "Hercules": "https://static.wikia.nocookie.net/starcraft/images/4/40/SC2_Lab_Hercules_Icon.png", + "Liberator": github_icon_base_url + "blizzard/btn-unit-terran-liberator.png", + "Valkyrie": github_icon_base_url + "original/btn-unit-terran-valkyrie@scbw.png", + + "Rapid Deployment Tube (Medivac)": organics_icon_base_url + "RapidDeploymentTube.png", + "Advanced Healing AI (Medivac)": github_icon_base_url + "blizzard/btn-ability-mengsk-medivac-doublehealbeam.png", + "Expanded Hull (Medivac)": github_icon_base_url + "blizzard/btn-upgrade-mengsk-engineeringbay-neosteelfortifiedarmor.png", + "Afterburners (Medivac)": github_icon_base_url + "blizzard/btn-upgrade-terran-medivacemergencythrusters.png", + "Scatter Veil (Medivac)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Advanced Cloaking Field (Medivac)": github_icon_base_url + "original/btn-permacloak-medivac.png", + "Tomahawk Power Cells (Wraith)": organics_icon_base_url + "TomahawkPowerCells.png", + "Unregistered Cloaking Module (Wraith)": github_icon_base_url + "original/btn-permacloak-wraith.png", + "Trigger Override (Wraith)": github_icon_base_url + "blizzard/btn-ability-hornerhan-wraith-attackspeed.png", + "Internal Tech Module (Wraith)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Resource Efficiency (Wraith)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Displacement Field (Wraith)": github_icon_base_url + "blizzard/btn-upgrade-swann-displacementfield.png", + "Advanced Laser Technology (Wraith)": github_icon_base_url + "blizzard/btn-upgrade-swann-improvedburstlaser.png", + "Ripwave Missiles (Viking)": github_icon_base_url + "blizzard/btn-upgrade-raynor-ripwavemissiles.png", + "Phobos-Class Weapons System (Viking)": github_icon_base_url + "blizzard/btn-upgrade-raynor-phobosclassweaponssystem.png", + "Smart Servos (Viking)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Anti-Mechanical Munition (Viking)": github_icon_base_url + "blizzard/btn-ability-terran-ignorearmor.png", + "Shredder Rounds (Viking)": github_icon_base_url + "blizzard/btn-ability-hornerhan-viking-piercingattacks.png", + "W.I.L.D. Missiles (Viking)": github_icon_base_url + "blizzard/btn-ability-hornerhan-viking-missileupgrade.png", + "Cross-Spectrum Dampeners (Banshee)": github_icon_base_url + "original/btn-banshee-cross-spectrum-dampeners.png", + "Advanced Cross-Spectrum Dampeners (Banshee)": github_icon_base_url + "original/btn-permacloak-banshee.png", + "Shockwave Missile Battery (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-raynor-shockwavemissilebattery.png", + "Hyperflight Rotors (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-hyperflightrotors.png", + "Laser Targeting System (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Internal Tech Module (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Shaped Hull (Banshee)": organics_icon_base_url + "ShapedHull.png", + "Advanced Targeting Optics (Banshee)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", + "Distortion Blasters (Banshee)": github_icon_base_url + "blizzard/btn-techupgrade-terran-cloakdistortionfield.png", + "Rocket Barrage (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-bansheemissilestrik.png", + "Missile Pods (Battlecruiser) Level 1": organics_icon_base_url + "MissilePods.png", + "Missile Pods (Battlecruiser) Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-bansheemissilestrik.png", + "Defensive Matrix (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Advanced Defensive Matrix (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Tactical Jump (Battlecruiser)": github_icon_base_url + "blizzard/btn-ability-terran-warpjump.png", + "Cloak (Battlecruiser)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "ATX Laser Battery (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-specialordance.png", + "Optimized Logistics (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Internal Tech Module (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Behemoth Plating (Battlecruiser)": github_icon_base_url + "original/btn-research-zerg-fortifiedbunker.png", + "Covert Ops Engines (Battlecruiser)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Bio Mechanical Repair Drone (Raven)": github_icon_base_url + "blizzard/btn-unit-biomechanicaldrone.png", + "Spider Mines (Raven)": github_icon_base_url + "blizzard/btn-upgrade-siegetank-spidermines.png", + "Railgun Turret (Raven)": github_icon_base_url + "blizzard/btn-unit-terran-autoturretblackops.png", + "Hunter-Seeker Weapon (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-specialordance.png", + "Interference Matrix (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-interferencematrix.png", + "Anti-Armor Missile (Raven)": github_icon_base_url + "blizzard/btn-ability-terran-shreddermissile-color.png", + "Internal Tech Module (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Resource Efficiency (Raven)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Durable Materials (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-durablematerials.png", + "EMP Shockwave (Science Vessel)": github_icon_base_url + "blizzard/btn-ability-mengsk-ghost-staticempblast.png", + "Defensive Matrix (Science Vessel)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Improved Nano-Repair (Science Vessel)": github_icon_base_url + "blizzard/btn-upgrade-swann-improvednanorepair.png", + "Advanced AI Systems (Science Vessel)": github_icon_base_url + "blizzard/btn-ability-mengsk-medivac-doublehealbeam.png", + "Internal Fusion Module (Hercules)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Tactical Jump (Hercules)": github_icon_base_url + "blizzard/btn-ability-terran-hercules-tacticaljump.png", + "Advanced Ballistics (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-advanceballistics.png", + "Raid Artillery (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-terrandefendermodestructureattack.png", + "Cloak (Liberator)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "Laser Targeting System (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Optimized Logistics (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Smart Servos (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Resource Efficiency (Liberator)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Enhanced Cluster Launchers (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-stetmann-corruptormissilebarrage.png", + "Shaped Hull (Valkyrie)": organics_icon_base_url + "ShapedHull.png", + "Flechette Missiles (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-hornerhan-viking-missileupgrade.png", + "Afterburners (Valkyrie)": github_icon_base_url + "blizzard/btn-upgrade-terran-medivacemergencythrusters.png", + "Launching Vector Compensator (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Resource Efficiency (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", "War Pigs": "https://static.wikia.nocookie.net/starcraft/images/e/ed/WarPigs_SC2_Icon1.jpg", "Devil Dogs": "https://static.wikia.nocookie.net/starcraft/images/3/33/DevilDogs_SC2_Icon1.jpg", "Hammer Securities": "https://static.wikia.nocookie.net/starcraft/images/3/3b/HammerSecurity_SC2_Icon1.jpg", "Spartan Company": "https://static.wikia.nocookie.net/starcraft/images/b/be/SpartanCompany_SC2_Icon1.jpg", "Siege Breakers": "https://static.wikia.nocookie.net/starcraft/images/3/31/SiegeBreakers_SC2_Icon1.jpg", - "Hel's Angel": "https://static.wikia.nocookie.net/starcraft/images/6/63/HelsAngels_SC2_Icon1.jpg", + "Hel's Angels": "https://static.wikia.nocookie.net/starcraft/images/6/63/HelsAngels_SC2_Icon1.jpg", "Dusk Wings": "https://static.wikia.nocookie.net/starcraft/images/5/52/DuskWings_SC2_Icon1.jpg", "Jackson's Revenge": "https://static.wikia.nocookie.net/starcraft/images/9/95/JacksonsRevenge_SC2_Icon1.jpg", + "Skibi's Angels": github_icon_base_url + "blizzard/btn-unit-terran-medicelite.png", + "Death Heads": github_icon_base_url + "blizzard/btn-unit-terran-deathhead.png", + "Winged Nightmares": github_icon_base_url + "blizzard/btn-unit-collection-wraith-junker.png", + "Midnight Riders": github_icon_base_url + "blizzard/btn-unit-terran-liberatorblackops.png", + "Brynhilds": github_icon_base_url + "blizzard/btn-unit-collection-vikingfighter-covertops.png", + "Jotun": github_icon_base_url + "blizzard/btn-unit-terran-thormengsk.png", "Ultra-Capacitors": "https://static.wikia.nocookie.net/starcraft/images/2/23/SC2_Lab_Ultra_Capacitors_Icon.png", "Vanadium Plating": "https://static.wikia.nocookie.net/starcraft/images/6/67/SC2_Lab_VanPlating_Icon.png", @@ -1766,8 +1647,6 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int "Micro-Filtering": "https://static.wikia.nocookie.net/starcraft/images/2/20/SC2_Lab_MicroFilter_Icon.png", "Automated Refinery": "https://static.wikia.nocookie.net/starcraft/images/7/71/SC2_Lab_Auto_Refinery_Icon.png", "Command Center Reactor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/SC2_Lab_CC_Reactor_Icon.png", - "Raven": "https://static.wikia.nocookie.net/starcraft/images/1/19/SC2_Lab_Raven_Icon.png", - "Science Vessel": "https://static.wikia.nocookie.net/starcraft/images/c/c3/SC2_Lab_SciVes_Icon.png", "Tech Reactor": "https://static.wikia.nocookie.net/starcraft/images/c/c5/SC2_Lab_Tech_Reactor_Icon.png", "Orbital Strike": "https://static.wikia.nocookie.net/starcraft/images/d/df/SC2_Lab_Orb_Strike_Icon.png", @@ -1775,23 +1654,372 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int "Fortified Bunker (Bunker)": "https://static.wikia.nocookie.net/starcraft/images/4/4f/SC2_Lab_FortBunker_Icon.png", "Planetary Fortress": "https://static.wikia.nocookie.net/starcraft/images/0/0b/SC2_Lab_PlanetFortress_Icon.png", "Perdition Turret": "https://static.wikia.nocookie.net/starcraft/images/a/af/SC2_Lab_PerdTurret_Icon.png", - "Predator": "https://static.wikia.nocookie.net/starcraft/images/8/83/SC2_Lab_Predator_Icon.png", - "Hercules": "https://static.wikia.nocookie.net/starcraft/images/4/40/SC2_Lab_Hercules_Icon.png", "Cellular Reactor": "https://static.wikia.nocookie.net/starcraft/images/d/d8/SC2_Lab_CellReactor_Icon.png", - "Regenerative Bio-Steel Level 1": "/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png", - "Regenerative Bio-Steel Level 2": "/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png", + "Regenerative Bio-Steel Level 1": github_icon_base_url + "original/btn-regenerativebiosteel-green.png", + "Regenerative Bio-Steel Level 2": github_icon_base_url + "original/btn-regenerativebiosteel-blue.png", + "Regenerative Bio-Steel Level 3": github_icon_base_url + "blizzard/btn-research-zerg-regenerativebio-steel.png", "Hive Mind Emulator": "https://static.wikia.nocookie.net/starcraft/images/b/bc/SC2_Lab_Hive_Emulator_Icon.png", "Psi Disrupter": "https://static.wikia.nocookie.net/starcraft/images/c/cf/SC2_Lab_Psi_Disruptor_Icon.png", - "Zealot": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Icon_Protoss_Zealot.jpg", + "Structure Armor": github_icon_base_url + "blizzard/btn-upgrade-terran-buildingarmor.png", + "Hi-Sec Auto Tracking": github_icon_base_url + "blizzard/btn-upgrade-terran-hisecautotracking.png", + "Advanced Optics": github_icon_base_url + "blizzard/btn-upgrade-swann-vehiclerangeincrease.png", + "Rogue Forces": github_icon_base_url + "blizzard/btn-unit-terran-tosh.png", + + "Ghost Visor (Nova Equipment)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-ghostvisor.png", + "Rangefinder Oculus (Nova Equipment)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-rangefinderoculus.png", + "Domination (Nova Ability)": github_icon_base_url + "blizzard/btn-ability-nova-domination.png", + "Blink (Nova Ability)": github_icon_base_url + "blizzard/btn-upgrade-nova-blink.png", + "Stealth Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-stealthsuit.png", + "Cloak (Nova Suit Module)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "Permanently Cloaked (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-tacticalstealthsuit.png", + "Energy Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-apolloinfantrysuit.png", + "Armored Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-blinksuit.png", + "Jump Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-jetpack.png", + "C20A Canister Rifle (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-canisterrifle.png", + "Hellfire Shotgun (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-shotgun.png", + "Plasma Rifle (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-plasmagun.png", + "Monomolecular Blade (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-monomolecularblade.png", + "Blazefire Gunblade (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-gunblade_sword.png", + "Stim Infusion (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Pulse Grenades (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-nova-btn-upgrade-nova-pulsegrenade.png", + "Flashbang Grenades (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-nova-btn-upgrade-nova-flashgrenade.png", + "Ionic Force Field (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-personaldefensivematrix.png", + "Holo Decoy (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-nova-holographicdecoy.png", + "Tac Nuke Strike (Nova Ability)": github_icon_base_url + "blizzard/btn-ability-terran-nuclearstrike-color.png", + + "Zerg Melee Attack Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-meleeattacks-level1.png", + "Zerg Melee Attack Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-meleeattacks-level2.png", + "Zerg Melee Attack Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-meleeattacks-level3.png", + "Zerg Missile Attack Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-missileattacks-level1.png", + "Zerg Missile Attack Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-missileattacks-level2.png", + "Zerg Missile Attack Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-missileattacks-level3.png", + "Zerg Ground Carapace Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-groundcarapace-level1.png", + "Zerg Ground Carapace Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-groundcarapace-level2.png", + "Zerg Ground Carapace Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-groundcarapace-level3.png", + "Zerg Flyer Attack Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level1.png", + "Zerg Flyer Attack Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level2.png", + "Zerg Flyer Attack Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level3.png", + "Zerg Flyer Carapace Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-flyercarapace-level1.png", + "Zerg Flyer Carapace Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-flyercarapace-level2.png", + "Zerg Flyer Carapace Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-flyercarapace-level3.png", + + "Automated Extractors (Kerrigan Tier 3)": github_icon_base_url + "blizzard/btn-ability-kerrigan-automatedextractors.png", + "Vespene Efficiency (Kerrigan Tier 5)": github_icon_base_url + "blizzard/btn-ability-kerrigan-vespeneefficiency.png", + "Twin Drones (Kerrigan Tier 5)": github_icon_base_url + "blizzard/btn-ability-kerrigan-twindrones.png", + "Improved Overlords (Kerrigan Tier 3)": github_icon_base_url + "blizzard/btn-ability-kerrigan-improvedoverlords.png", + "Ventral Sacs (Overlord)": github_icon_base_url + "blizzard/btn-upgrade-zerg-ventralsacs.png", + "Malignant Creep (Kerrigan Tier 5)": github_icon_base_url + "blizzard/btn-ability-kerrigan-malignantcreep.png", + + "Spine Crawler": github_icon_base_url + "blizzard/btn-building-zerg-spinecrawler.png", + "Spore Crawler": github_icon_base_url + "blizzard/btn-building-zerg-sporecrawler.png", + + "Zergling": github_icon_base_url + "blizzard/btn-unit-zerg-zergling.png", + "Swarm Queen": github_icon_base_url + "blizzard/btn-unit-zerg-broodqueen.png", + "Roach": github_icon_base_url + "blizzard/btn-unit-zerg-roach.png", + "Hydralisk": github_icon_base_url + "blizzard/btn-unit-zerg-hydralisk.png", + "Aberration": github_icon_base_url + "blizzard/btn-unit-zerg-aberration.png", + "Mutalisk": github_icon_base_url + "blizzard/btn-unit-zerg-mutalisk.png", + "Corruptor": github_icon_base_url + "blizzard/btn-unit-zerg-corruptor.png", + "Swarm Host": github_icon_base_url + "blizzard/btn-unit-zerg-swarmhost.png", + "Infestor": github_icon_base_url + "blizzard/btn-unit-zerg-infestor.png", + "Defiler": github_icon_base_url + "original/btn-unit-zerg-defiler@scbw.png", + "Ultralisk": github_icon_base_url + "blizzard/btn-unit-zerg-ultralisk.png", + "Brood Queen": github_icon_base_url + "blizzard/btn-unit-zerg-classicqueen.png", + "Scourge": github_icon_base_url + "blizzard/btn-unit-zerg-scourge.png", + + "Baneling Aspect (Zergling)": github_icon_base_url + "blizzard/btn-unit-zerg-baneling.png", + "Ravager Aspect (Roach)": github_icon_base_url + "blizzard/btn-unit-zerg-ravager.png", + "Impaler Aspect (Hydralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-impaler.png", + "Lurker Aspect (Hydralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-lurker.png", + "Brood Lord Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-broodlord.png", + "Viper Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-viper.png", + "Guardian Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-primalguardian.png", + "Devourer Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-devourerex3.png", + + "Raptor Strain (Zergling)": github_icon_base_url + "blizzard/btn-unit-zerg-zergling-raptor.png", + "Swarmling Strain (Zergling)": github_icon_base_url + "blizzard/btn-unit-zerg-zergling-swarmling.png", + "Hardened Carapace (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hardenedcarapace.png", + "Adrenal Overload (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-adrenaloverload.png", + "Metabolic Boost (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hotsmetabolicboost.png", + "Shredding Claws (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zergling-armorshredding.png", + "Zergling Reconstitution (Kerrigan Tier 3)": github_icon_base_url + "blizzard/btn-ability-kerrigan-zerglingreconstitution.png", + "Splitter Strain (Baneling)": github_icon_base_url + "blizzard/talent-zagara-level14-unlocksplitterling.png", + "Hunter Strain (Baneling)": github_icon_base_url + "blizzard/btn-ability-zerg-cliffjump-baneling.png", + "Corrosive Acid (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-corrosiveacid.png", + "Rupture (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-rupture.png", + "Regenerative Acid (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-regenerativebile.png", + "Centrifugal Hooks (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-centrifugalhooks.png", + "Tunneling Jaws (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-tunnelingjaws.png", + "Rapid Metamorph (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Spawn Larvae (Swarm Queen)": github_icon_base_url + "blizzard/btn-unit-zerg-larva.png", + "Deep Tunnel (Swarm Queen)": github_icon_base_url + "blizzard/btn-ability-zerg-deeptunnel.png", + "Organic Carapace (Swarm Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Bio-Mechanical Transfusion (Swarm Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-biomechanicaltransfusion.png", + "Resource Efficiency (Swarm Queen)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Incubator Chamber (Swarm Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-incubationchamber.png", + "Vile Strain (Roach)": github_icon_base_url + "blizzard/btn-unit-zerg-roach-vile.png", + "Corpser Strain (Roach)": github_icon_base_url + "blizzard/btn-unit-zerg-roach-corpser.png", + "Hydriodic Bile (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hydriaticacid.png", + "Adaptive Plating (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-adaptivecarapace.png", + "Tunneling Claws (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hotstunnelingclaws.png", + "Glial Reconstitution (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-glialreconstitution.png", + "Organic Carapace (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Potent Bile (Ravager)": github_icon_base_url + "blizzard/potentbile_coop.png", + "Bloated Bile Ducts (Ravager)": github_icon_base_url + "blizzard/btn-ability-zerg-abathur-corrosivebilelarge.png", + "Deep Tunnel (Ravager)": github_icon_base_url + "blizzard/btn-ability-zerg-deeptunnel.png", + "Frenzy (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-frenzy.png", + "Ancillary Carapace (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-ancillaryarmor.png", + "Grooved Spines (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hotsgroovedspines.png", + "Muscular Augments (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-evolvemuscularaugments.png", + "Resource Efficiency (Hydralisk)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Adaptive Talons (Impaler)": github_icon_base_url + "blizzard/btn-upgrade-zerg-adaptivetalons.png", + "Secretion Glands (Impaler)": github_icon_base_url + "blizzard/btn-ability-zerg-creepspread.png", + "Hardened Tentacle Spines (Impaler)": github_icon_base_url + "blizzard/btn-ability-zerg-dehaka-impaler-tenderize.png", + "Seismic Spines (Lurker)": github_icon_base_url + "blizzard/btn-upgrade-kerrigan-seismicspines.png", + "Adapted Spines (Lurker)": github_icon_base_url + "blizzard/btn-upgrade-zerg-groovedspines.png", + "Vicious Glaive (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-viciousglaive.png", + "Rapid Regeneration (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-rapidregeneration.png", + "Sundering Glaive (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-explosiveglaive.png", + "Severing Glaive (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-explosiveglaive.png", + "Aerodynamic Glaive Shape (Mutalisk)": github_icon_base_url + "blizzard/btn-ability-dehaka-airbonusdamage.png", + "Corruption (Corruptor)": github_icon_base_url + "blizzard/btn-ability-zerg-causticspray.png", + "Caustic Spray (Corruptor)": github_icon_base_url + "blizzard/btn-ability-zerg-corruption-color.png", + "Porous Cartilage (Brood Lord)": github_icon_base_url + "blizzard/btn-upgrade-kerrigan-broodlordspeed.png", + "Evolved Carapace (Brood Lord)": github_icon_base_url + "blizzard/btn-upgrade-zerg-chitinousplating.png", + "Splitter Mitosis (Brood Lord)": github_icon_base_url + "blizzard/abilityicon_spawnbroodlings_square.png", + "Resource Efficiency (Brood Lord)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Parasitic Bomb (Viper)": github_icon_base_url + "blizzard/btn-ability-zerg-parasiticbomb.png", + "Paralytic Barbs (Viper)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-abduct.png", + "Virulent Microbes (Viper)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-castrange.png", + "Prolonged Dispersion (Guardian)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-prolongeddispersion.png", + "Primal Adaptation (Guardian)": github_icon_base_url + "blizzard/biomassrecovery_coop.png", + "Soronan Acid (Guardian)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-biomass.png", + "Corrosive Spray (Devourer)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-devourer-corrosivespray.png", + "Gaping Maw (Devourer)": github_icon_base_url + "blizzard/btn-ability-zerg-explode-color.png", + "Improved Osmosis (Devourer)": github_icon_base_url + "blizzard/btn-upgrade-zerg-pneumatizedcarapace.png", + "Prescient Spores (Devourer)": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level2.png", + "Carrion Strain (Swarm Host)": github_icon_base_url + "blizzard/btn-unit-zerg-swarmhost-carrion.png", + "Creeper Strain (Swarm Host)": github_icon_base_url + "blizzard/btn-unit-zerg-swarmhost-creeper.png", + "Burrow (Swarm Host)": github_icon_base_url + "blizzard/btn-ability-zerg-burrow-color.png", + "Rapid Incubation (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-rapidincubation.png", + "Pressurized Glands (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-pressurizedglands.png", + "Locust Metabolic Boost (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-glialreconstitution.png", + "Enduring Locusts (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-evolveincreasedlocustlifetime.png", + "Organic Carapace (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Resource Efficiency (Swarm Host)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Infested Terran (Infestor)": github_icon_base_url + "blizzard/btn-unit-zerg-infestedmarine.png", + "Microbial Shroud (Infestor)": github_icon_base_url + "blizzard/btn-ability-zerg-darkswarm.png", + "Noxious Strain (Ultralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-ultralisk-noxious.png", + "Torrasque Strain (Ultralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-ultralisk-torrasque.png", + "Burrow Charge (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-burrowcharge.png", + "Tissue Assimilation (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-tissueassimilation.png", + "Monarch Blades (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-monarchblades.png", + "Anabolic Synthesis (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-anabolicsynthesis.png", + "Chitinous Plating (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-chitinousplating.png", + "Organic Carapace (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Resource Efficiency (Ultralisk)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Fungal Growth (Brood Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-stukov-researchqueenfungalgrowth.png", + "Ensnare (Brood Queen)": github_icon_base_url + "blizzard/btn-ability-zerg-fungalgrowth-color.png", + "Enhanced Mitochondria (Brood Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-stukov-queenenergyregen.png", + "Virulent Spores (Scourge)": github_icon_base_url + "blizzard/btn-upgrade-zagara-scourgesplashdamage.png", + "Resource Efficiency (Scourge)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Swarm Scourge (Scourge)": github_icon_base_url + "original/btn-upgrade-custom-triple-scourge.png", + + "Infested Medics": github_icon_base_url + "blizzard/btn-unit-terran-medicelite.png", + "Infested Siege Tanks": github_icon_base_url + "original/btn-unit-terran-siegetankmercenary-tank.png", + "Infested Banshees": github_icon_base_url + "original/btn-unit-terran-bansheemercenary.png", + + "Primal Form (Kerrigan)": github_icon_base_url + "blizzard/btn-unit-zerg-kerriganinfested.png", + "Kinetic Blast (Kerrigan Tier 1)": github_icon_base_url + "blizzard/btn-ability-kerrigan-kineticblast.png", + "Heroic Fortitude (Kerrigan Tier 1)": github_icon_base_url + "blizzard/btn-ability-kerrigan-heroicfortitude.png", + "Leaping Strike (Kerrigan Tier 1)": github_icon_base_url + "blizzard/btn-ability-kerrigan-leapingstrike.png", + "Crushing Grip (Kerrigan Tier 2)": github_icon_base_url + "blizzard/btn-ability-swarm-kerrigan-crushinggrip.png", + "Chain Reaction (Kerrigan Tier 2)": github_icon_base_url + "blizzard/btn-ability-swarm-kerrigan-chainreaction.png", + "Psionic Shift (Kerrigan Tier 2)": github_icon_base_url + "blizzard/btn-ability-kerrigan-psychicshift.png", + "Wild Mutation (Kerrigan Tier 4)": github_icon_base_url + "blizzard/btn-ability-kerrigan-wildmutation.png", + "Spawn Banelings (Kerrigan Tier 4)": github_icon_base_url + "blizzard/abilityicon_spawnbanelings_square.png", + "Mend (Kerrigan Tier 4)": github_icon_base_url + "blizzard/btn-ability-zerg-transfusion-color.png", + "Infest Broodlings (Kerrigan Tier 6)": github_icon_base_url + "blizzard/abilityicon_spawnbroodlings_square.png", + "Fury (Kerrigan Tier 6)": github_icon_base_url + "blizzard/btn-ability-kerrigan-fury.png", + "Ability Efficiency (Kerrigan Tier 6)": github_icon_base_url + "blizzard/btn-ability-kerrigan-abilityefficiency.png", + "Apocalypse (Kerrigan Tier 7)": github_icon_base_url + "blizzard/btn-ability-kerrigan-apocalypse.png", + "Spawn Leviathan (Kerrigan Tier 7)": github_icon_base_url + "blizzard/btn-unit-zerg-leviathan.png", + "Drop-Pods (Kerrigan Tier 7)": github_icon_base_url + "blizzard/btn-ability-kerrigan-droppods.png", + + "Protoss Ground Weapon Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundweaponslevel1.png", + "Protoss Ground Weapon Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundweaponslevel2.png", + "Protoss Ground Weapon Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundweaponslevel3.png", + "Protoss Ground Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel1.png", + "Protoss Ground Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel2.png", + "Protoss Ground Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel3.png", + "Protoss Shields Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel1.png", + "Protoss Shields Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel2.png", + "Protoss Shields Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel3.png", + "Protoss Air Weapon Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel1.png", + "Protoss Air Weapon Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel2.png", + "Protoss Air Weapon Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel3.png", + "Protoss Air Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel1.png", + "Protoss Air Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel2.png", + "Protoss Air Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel3.png", + + "Quatro": github_icon_base_url + "blizzard/btn-progression-protoss-fenix-6-forgeresearch.png", + + "Photon Cannon": github_icon_base_url + "blizzard/btn-building-protoss-photoncannon.png", + "Khaydarin Monolith": github_icon_base_url + "blizzard/btn-unit-protoss-khaydarinmonolith.png", + "Shield Battery": github_icon_base_url + "blizzard/btn-building-protoss-shieldbattery.png", + + "Enhanced Targeting": github_icon_base_url + "blizzard/btn-upgrade-karax-turretrange.png", + "Optimized Ordnance": github_icon_base_url + "blizzard/btn-upgrade-karax-turretattackspeed.png", + "Khalai Ingenuity": github_icon_base_url + "blizzard/btn-upgrade-karax-pylonwarpininstantly.png", + "Orbital Assimilators": github_icon_base_url + "blizzard/btn-ability-spearofadun-orbitalassimilator.png", + "Amplified Assimilators": github_icon_base_url + "original/btn-research-terran-microfiltering.png", + "Warp Harmonization": github_icon_base_url + "blizzard/btn-ability-spearofadun-warpharmonization.png", + "Superior Warp Gates": github_icon_base_url + "blizzard/talent-artanis-level03-warpgatecharges.png", + "Nexus Overcharge": github_icon_base_url + "blizzard/btn-ability-spearofadun-nexusovercharge.png", + + "Zealot": github_icon_base_url + "blizzard/btn-unit-protoss-zealot-aiur.png", + "Centurion": github_icon_base_url + "blizzard/btn-unit-protoss-zealot-nerazim.png", + "Sentinel": github_icon_base_url + "blizzard/btn-unit-protoss-zealot-purifier.png", + "Supplicant": github_icon_base_url + "blizzard/btn-unit-protoss-alarak-taldarim-supplicant.png", + "Sentry": github_icon_base_url + "blizzard/btn-unit-protoss-sentry.png", + "Energizer": github_icon_base_url + "blizzard/btn-unit-protoss-sentry-purifier.png", + "Havoc": github_icon_base_url + "blizzard/btn-unit-protoss-sentry-taldarim.png", "Stalker": "https://static.wikia.nocookie.net/starcraft/images/0/0d/Icon_Protoss_Stalker.jpg", + "Instigator": github_icon_base_url + "blizzard/btn-unit-protoss-stalker-purifier.png", + "Slayer": github_icon_base_url + "blizzard/btn-unit-protoss-alarak-taldarim-stalker.png", + "Dragoon": github_icon_base_url + "blizzard/btn-unit-protoss-dragoon-void.png", + "Adept": github_icon_base_url + "blizzard/btn-unit-protoss-adept-purifier.png", "High Templar": "https://static.wikia.nocookie.net/starcraft/images/a/a0/Icon_Protoss_High_Templar.jpg", + "Signifier": github_icon_base_url + "original/btn-unit-protoss-hightemplar-nerazim.png", + "Ascendant": github_icon_base_url + "blizzard/btn-unit-protoss-hightemplar-taldarim.png", + "Dark Archon": github_icon_base_url + "blizzard/talent-vorazun-level05-unlockdarkarchon.png", "Dark Templar": "https://static.wikia.nocookie.net/starcraft/images/9/90/Icon_Protoss_Dark_Templar.jpg", + "Avenger": github_icon_base_url + "blizzard/btn-unit-protoss-darktemplar-aiur.png", + "Blood Hunter": github_icon_base_url + "blizzard/btn-unit-protoss-darktemplar-taldarim.png", + + "Leg Enhancements (Zealot/Sentinel/Centurion)": github_icon_base_url + "blizzard/btn-ability-protoss-charge-color.png", + "Shield Capacity (Zealot/Sentinel/Centurion)": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel1.png", + "Blood Shield (Supplicant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-supplicantarmor.png", + "Soul Augmentation (Supplicant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-supplicantextrashields.png", + "Shield Regeneration (Supplicant)": github_icon_base_url + "blizzard/btn-ability-protoss-voidarmor.png", + "Force Field (Sentry)": github_icon_base_url + "blizzard/btn-ability-protoss-forcefield-color.png", + "Hallucination (Sentry)": github_icon_base_url + "blizzard/btn-ability-protoss-hallucination-color.png", + "Reclamation (Energizer)": github_icon_base_url + "blizzard/btn-ability-protoss-reclamation.png", + "Forged Chassis (Energizer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel0.png", + "Detect Weakness (Havoc)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-havoctargetlockbuffed.png", + "Bloodshard Resonance (Havoc)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-rangeincrease.png", + "Cloaking Module (Sentry/Energizer/Havoc)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-permanentcloak.png", + "Rapid Recharging (Sentry/Energizer/Havoc/Shield Battery)": github_icon_base_url + "blizzard/btn-upgrade-karax-energyregen200.png", + "Disintegrating Particles (Stalker/Instigator/Slayer)": github_icon_base_url + "blizzard/btn-ability-protoss-phasedisruptor.png", + "Particle Reflection (Stalker/Instigator/Slayer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-adeptchampionbounceattack.png", + "High Impact Phase Disruptor (Dragoon)": github_icon_base_url + "blizzard/btn-ability-protoss-phasedisruptor.png", + "Trillic Compression System (Dragoon)": github_icon_base_url + "blizzard/btn-ability-protoss-dragoonchassis.png", + "Singularity Charge (Dragoon)": github_icon_base_url + "blizzard/btn-upgrade-artanis-singularitycharge.png", + "Enhanced Strider Servos (Dragoon)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Shockwave (Adept)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-adept-recochetglaiveupgraded.png", + "Resonating Glaives (Adept)": github_icon_base_url + "blizzard/btn-upgrade-protoss-resonatingglaives.png", + "Phase Bulwark (Adept)": github_icon_base_url + "blizzard/btn-upgrade-protoss-adeptshieldupgrade.png", + "Unshackled Psionic Storm (High Templar/Signifier)": github_icon_base_url + "blizzard/btn-ability-protoss-psistorm.png", + "Hallucination (High Templar/Signifier)": github_icon_base_url + "blizzard/btn-ability-protoss-hallucination-color.png", + "Khaydarin Amulet (High Templar/Signifier)": github_icon_base_url + "blizzard/btn-upgrade-protoss-khaydarinamulet.png", + "High Archon (Archon)": github_icon_base_url + "blizzard/btn-upgrade-artanis-healingpsionicstorm.png", + "Power Overwhelming (Ascendant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-ascendantspermanentlybetter.png", + "Chaotic Attunement (Ascendant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-ascendant'spsiorbtravelsfurther.png", + "Blood Amulet (Ascendant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-wrathwalker-chargetimeimproved.png", + "Feedback (Dark Archon)": github_icon_base_url + "blizzard/btn-ability-protoss-feedback-color.png", + "Maelstrom (Dark Archon)": github_icon_base_url + "blizzard/btn-ability-protoss-voidstasis.png", + "Argus Talisman (Dark Archon)": github_icon_base_url + "original/btn-upgrade-protoss-argustalisman@scbw.png", + "Dark Archon Meld (Dark Templar)": github_icon_base_url + "blizzard/talent-vorazun-level05-unlockdarkarchon.png", + "Shroud of Adun (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/talent-vorazun-level01-shadowstalk.png", + "Shadow Guard Training (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/btn-ability-terran-heal-color.png", + "Blink (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/btn-ability-protoss-shadowdash.png", + "Resource Efficiency (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + + "Warp Prism": github_icon_base_url + "blizzard/btn-unit-protoss-warpprism.png", "Immortal": "https://static.wikia.nocookie.net/starcraft/images/c/c1/Icon_Protoss_Immortal.jpg", - "Colossus": "https://static.wikia.nocookie.net/starcraft/images/4/40/Icon_Protoss_Colossus.jpg", + "Annihilator": github_icon_base_url + "blizzard/btn-unit-protoss-immortal-nerazim.png", + "Vanguard": github_icon_base_url + "blizzard/btn-unit-protoss-immortal-taldarim.png", + "Colossus": github_icon_base_url + "blizzard/btn-unit-protoss-colossus-purifier.png", + "Wrathwalker": github_icon_base_url + "blizzard/btn-unit-protoss-colossus-taldarim.png", + "Observer": github_icon_base_url + "blizzard/btn-unit-protoss-observer.png", + "Reaver": github_icon_base_url + "blizzard/btn-unit-protoss-reaver.png", + "Disruptor": github_icon_base_url + "blizzard/btn-unit-protoss-disruptor.png", + + "Gravitic Drive (Warp Prism)": github_icon_base_url + "blizzard/btn-upgrade-protoss-graviticdrive.png", + "Phase Blaster (Warp Prism)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel0.png", + "War Configuration (Warp Prism)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-graviticdrive.png", + "Singularity Charge (Immortal/Annihilator)": github_icon_base_url + "blizzard/btn-upgrade-artanis-singularitycharge.png", + "Advanced Targeting Mechanics (Immortal/Annihilator)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", + "Agony Launchers (Vanguard)": github_icon_base_url + "blizzard/btn-upgrade-protoss-vanguard-aoeradiusincreased.png", + "Matter Dispersion (Vanguard)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", + "Pacification Protocol (Colossus)": github_icon_base_url + "blizzard/btn-ability-protoss-chargedblast.png", + "Rapid Power Cycling (Wrathwalker)": github_icon_base_url + "blizzard/btn-upgrade-protoss-wrathwalker-chargetimeimproved.png", + "Eye of Wrath (Wrathwalker)": github_icon_base_url + "blizzard/btn-upgrade-protoss-extendedthermallance.png", + "Gravitic Boosters (Observer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-graviticbooster.png", + "Sensor Array (Observer)": github_icon_base_url + "blizzard/btn-ability-zeratul-observer-sensorarray.png", + "Scarab Damage (Reaver)": github_icon_base_url + "blizzard/btn-ability-protoss-scarabshot.png", + "Solarite Payload (Reaver)": github_icon_base_url + "blizzard/btn-upgrade-artanis-scarabsplashradius.png", + "Reaver Capacity (Reaver)": github_icon_base_url + "original/btn-upgrade-protoss-increasedscarabcapacity@scbw.png", + "Resource Efficiency (Reaver)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Phoenix": "https://static.wikia.nocookie.net/starcraft/images/b/b1/Icon_Protoss_Phoenix.jpg", - "Void Ray": "https://static.wikia.nocookie.net/starcraft/images/1/1d/VoidRay_SC2_Rend1.jpg", + "Mirage": github_icon_base_url + "blizzard/btn-unit-protoss-phoenix-purifier.png", + "Corsair": github_icon_base_url + "blizzard/btn-unit-protoss-corsair.png", + "Destroyer": github_icon_base_url + "blizzard/btn-unit-protoss-voidray-taldarim.png", + "Void Ray": github_icon_base_url + "blizzard/btn-unit-protoss-voidray-nerazim.png", "Carrier": "https://static.wikia.nocookie.net/starcraft/images/2/2c/Icon_Protoss_Carrier.jpg", + "Scout": github_icon_base_url + "original/btn-unit-protoss-scout.png", + "Tempest": github_icon_base_url + "blizzard/btn-unit-protoss-tempest-purifier.png", + "Mothership": github_icon_base_url + "blizzard/btn-unit-protoss-mothership-taldarim.png", + "Arbiter": github_icon_base_url + "blizzard/btn-unit-protoss-arbiter.png", + "Oracle": github_icon_base_url + "blizzard/btn-unit-protoss-oracle.png", + + "Ionic Wavelength Flux (Phoenix/Mirage)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel0.png", + "Anion Pulse-Crystals (Phoenix/Mirage)": github_icon_base_url + "blizzard/btn-upgrade-protoss-phoenixrange.png", + "Stealth Drive (Corsair)": github_icon_base_url + "blizzard/btn-upgrade-vorazun-corsairpermanentlycloaked.png", + "Argus Jewel (Corsair)": github_icon_base_url + "blizzard/btn-ability-protoss-stasistrap.png", + "Sustaining Disruption (Corsair)": github_icon_base_url + "blizzard/btn-ability-protoss-disruptionweb.png", + "Neutron Shields (Corsair)": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel1.png", + "Reforged Bloodshard Core (Destroyer)": github_icon_base_url + "blizzard/btn-amonshardsarmor.png", + "Flux Vanes (Void Ray/Destroyer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fluxvanes.png", + "Graviton Catapult (Carrier)": github_icon_base_url + "blizzard/btn-upgrade-protoss-gravitoncatapult.png", + "Hull of Past Glories (Carrier)": github_icon_base_url + "blizzard/btn-progression-protoss-fenix-14-colossusandcarrierchampionsresearch.png", + "Combat Sensor Array (Scout)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-scoutchampionrange.png", + "Apial Sensors (Scout)": github_icon_base_url + "blizzard/btn-upgrade-tychus-detection.png", + "Gravitic Thrusters (Scout)": github_icon_base_url + "blizzard/btn-upgrade-protoss-graviticbooster.png", + "Advanced Photon Blasters (Scout)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel3.png", + "Tectonic Destabilizers (Tempest)": github_icon_base_url + "blizzard/btn-ability-protoss-disruptionblast.png", + "Quantic Reactor (Tempest)": github_icon_base_url + "blizzard/btn-upgrade-protoss-researchgravitysling.png", + "Gravity Sling (Tempest)": github_icon_base_url + "blizzard/btn-upgrade-protoss-tectonicdisruptors.png", + "Chronostatic Reinforcement (Arbiter)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel2.png", + "Khaydarin Core (Arbiter)": github_icon_base_url + "blizzard/btn-upgrade-protoss-adeptshieldupgrade.png", + "Spacetime Anchor (Arbiter)": github_icon_base_url + "blizzard/btn-ability-protoss-stasisfield.png", + "Resource Efficiency (Arbiter)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Enhanced Cloak Field (Arbiter)": github_icon_base_url + "blizzard/btn-ability-stetmann-stetzonegenerator-speed.png", + "Stealth Drive (Oracle)": github_icon_base_url + "blizzard/btn-upgrade-vorazun-oraclepermanentlycloaked.png", + "Stasis Calibration (Oracle)": github_icon_base_url + "blizzard/btn-ability-protoss-oracle-stasiscalibration.png", + "Temporal Acceleration Beam (Oracle)": github_icon_base_url + "blizzard/btn-ability-protoss-oraclepulsarcannonon.png", + + "Matrix Overload": github_icon_base_url + "blizzard/btn-ability-spearofadun-matrixoverload.png", + "Guardian Shell": github_icon_base_url + "blizzard/btn-ability-spearofadun-guardianshell.png", + + "Chrono Surge (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-chronosurge.png", + "Proxy Pylon (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-deploypylon.png", + "Warp In Reinforcements (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-warpinreinforcements.png", + "Pylon Overcharge (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-protoss-purify.png", + "Orbital Strike (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-orbitalstrike.png", + "Temporal Field (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-temporalfield.png", + "Solar Lance (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-solarlance.png", + "Mass Recall (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-massrecall.png", + "Shield Overcharge (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-shieldovercharge.png", + "Deploy Fenix (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-unit-protoss-fenix.png", + "Purifier Beam (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-purifierbeam.png", + "Time Stop (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-timestop.png", + "Solar Bombardment (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-solarbombardment.png", + + "Reconstruction Beam (Spear of Adun Auto-Cast)": github_icon_base_url + "blizzard/btn-ability-spearofadun-reconstructionbeam.png", + "Overwatch (Spear of Adun Auto-Cast)": github_icon_base_url + "blizzard/btn-ability-zeratul-chargedcrystal-psionicwinds.png", "Nothing": "", } @@ -1824,97 +2052,310 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int "Gates of Hell": range(SC2WOL_LOC_ID_OFFSET + 2600, SC2WOL_LOC_ID_OFFSET + 2700), "Belly of the Beast": range(SC2WOL_LOC_ID_OFFSET + 2700, SC2WOL_LOC_ID_OFFSET + 2800), "Shatter the Sky": range(SC2WOL_LOC_ID_OFFSET + 2800, SC2WOL_LOC_ID_OFFSET + 2900), + "All-In": range(SC2WOL_LOC_ID_OFFSET + 2900, SC2WOL_LOC_ID_OFFSET + 3000), + + "Lab Rat": range(SC2HOTS_LOC_ID_OFFSET + 100, SC2HOTS_LOC_ID_OFFSET + 200), + "Back in the Saddle": range(SC2HOTS_LOC_ID_OFFSET + 200, SC2HOTS_LOC_ID_OFFSET + 300), + "Rendezvous": range(SC2HOTS_LOC_ID_OFFSET + 300, SC2HOTS_LOC_ID_OFFSET + 400), + "Harvest of Screams": range(SC2HOTS_LOC_ID_OFFSET + 400, SC2HOTS_LOC_ID_OFFSET + 500), + "Shoot the Messenger": range(SC2HOTS_LOC_ID_OFFSET + 500, SC2HOTS_LOC_ID_OFFSET + 600), + "Enemy Within": range(SC2HOTS_LOC_ID_OFFSET + 600, SC2HOTS_LOC_ID_OFFSET + 700), + "Domination": range(SC2HOTS_LOC_ID_OFFSET + 700, SC2HOTS_LOC_ID_OFFSET + 800), + "Fire in the Sky": range(SC2HOTS_LOC_ID_OFFSET + 800, SC2HOTS_LOC_ID_OFFSET + 900), + "Old Soldiers": range(SC2HOTS_LOC_ID_OFFSET + 900, SC2HOTS_LOC_ID_OFFSET + 1000), + "Waking the Ancient": range(SC2HOTS_LOC_ID_OFFSET + 1000, SC2HOTS_LOC_ID_OFFSET + 1100), + "The Crucible": range(SC2HOTS_LOC_ID_OFFSET + 1100, SC2HOTS_LOC_ID_OFFSET + 1200), + "Supreme": range(SC2HOTS_LOC_ID_OFFSET + 1200, SC2HOTS_LOC_ID_OFFSET + 1300), + "Infested": range(SC2HOTS_LOC_ID_OFFSET + 1300, SC2HOTS_LOC_ID_OFFSET + 1400), + "Hand of Darkness": range(SC2HOTS_LOC_ID_OFFSET + 1400, SC2HOTS_LOC_ID_OFFSET + 1500), + "Phantoms of the Void": range(SC2HOTS_LOC_ID_OFFSET + 1500, SC2HOTS_LOC_ID_OFFSET + 1600), + "With Friends Like These": range(SC2HOTS_LOC_ID_OFFSET + 1600, SC2HOTS_LOC_ID_OFFSET + 1700), + "Conviction": range(SC2HOTS_LOC_ID_OFFSET + 1700, SC2HOTS_LOC_ID_OFFSET + 1800), + "Planetfall": range(SC2HOTS_LOC_ID_OFFSET + 1800, SC2HOTS_LOC_ID_OFFSET + 1900), + "Death From Above": range(SC2HOTS_LOC_ID_OFFSET + 1900, SC2HOTS_LOC_ID_OFFSET + 2000), + "The Reckoning": range(SC2HOTS_LOC_ID_OFFSET + 2000, SC2HOTS_LOC_ID_OFFSET + 2100), + + "Dark Whispers": range(SC2LOTV_LOC_ID_OFFSET + 100, SC2LOTV_LOC_ID_OFFSET + 200), + "Ghosts in the Fog": range(SC2LOTV_LOC_ID_OFFSET + 200, SC2LOTV_LOC_ID_OFFSET + 300), + "Evil Awoken": range(SC2LOTV_LOC_ID_OFFSET + 300, SC2LOTV_LOC_ID_OFFSET + 400), + + "For Aiur!": range(SC2LOTV_LOC_ID_OFFSET + 400, SC2LOTV_LOC_ID_OFFSET + 500), + "The Growing Shadow": range(SC2LOTV_LOC_ID_OFFSET + 500, SC2LOTV_LOC_ID_OFFSET + 600), + "The Spear of Adun": range(SC2LOTV_LOC_ID_OFFSET + 600, SC2LOTV_LOC_ID_OFFSET + 700), + "Sky Shield": range(SC2LOTV_LOC_ID_OFFSET + 700, SC2LOTV_LOC_ID_OFFSET + 800), + "Brothers in Arms": range(SC2LOTV_LOC_ID_OFFSET + 800, SC2LOTV_LOC_ID_OFFSET + 900), + "Amon's Reach": range(SC2LOTV_LOC_ID_OFFSET + 900, SC2LOTV_LOC_ID_OFFSET + 1000), + "Last Stand": range(SC2LOTV_LOC_ID_OFFSET + 1000, SC2LOTV_LOC_ID_OFFSET + 1100), + "Forbidden Weapon": range(SC2LOTV_LOC_ID_OFFSET + 1100, SC2LOTV_LOC_ID_OFFSET + 1200), + "Temple of Unification": range(SC2LOTV_LOC_ID_OFFSET + 1200, SC2LOTV_LOC_ID_OFFSET + 1300), + "The Infinite Cycle": range(SC2LOTV_LOC_ID_OFFSET + 1300, SC2LOTV_LOC_ID_OFFSET + 1400), + "Harbinger of Oblivion": range(SC2LOTV_LOC_ID_OFFSET + 1400, SC2LOTV_LOC_ID_OFFSET + 1500), + "Unsealing the Past": range(SC2LOTV_LOC_ID_OFFSET + 1500, SC2LOTV_LOC_ID_OFFSET + 1600), + "Purification": range(SC2LOTV_LOC_ID_OFFSET + 1600, SC2LOTV_LOC_ID_OFFSET + 1700), + "Steps of the Rite": range(SC2LOTV_LOC_ID_OFFSET + 1700, SC2LOTV_LOC_ID_OFFSET + 1800), + "Rak'Shir": range(SC2LOTV_LOC_ID_OFFSET + 1800, SC2LOTV_LOC_ID_OFFSET + 1900), + "Templar's Charge": range(SC2LOTV_LOC_ID_OFFSET + 1900, SC2LOTV_LOC_ID_OFFSET + 2000), + "Templar's Return": range(SC2LOTV_LOC_ID_OFFSET + 2000, SC2LOTV_LOC_ID_OFFSET + 2100), + "The Host": range(SC2LOTV_LOC_ID_OFFSET + 2100, SC2LOTV_LOC_ID_OFFSET + 2200), + "Salvation": range(SC2LOTV_LOC_ID_OFFSET + 2200, SC2LOTV_LOC_ID_OFFSET + 2300), + + "Into the Void": range(SC2LOTV_LOC_ID_OFFSET + 2300, SC2LOTV_LOC_ID_OFFSET + 2400), + "The Essence of Eternity": range(SC2LOTV_LOC_ID_OFFSET + 2400, SC2LOTV_LOC_ID_OFFSET + 2500), + "Amon's Fall": range(SC2LOTV_LOC_ID_OFFSET + 2500, SC2LOTV_LOC_ID_OFFSET + 2600), + + "The Escape": range(SC2NCO_LOC_ID_OFFSET + 100, SC2NCO_LOC_ID_OFFSET + 200), + "Sudden Strike": range(SC2NCO_LOC_ID_OFFSET + 200, SC2NCO_LOC_ID_OFFSET + 300), + "Enemy Intelligence": range(SC2NCO_LOC_ID_OFFSET + 300, SC2NCO_LOC_ID_OFFSET + 400), + "Trouble In Paradise": range(SC2NCO_LOC_ID_OFFSET + 400, SC2NCO_LOC_ID_OFFSET + 500), + "Night Terrors": range(SC2NCO_LOC_ID_OFFSET + 500, SC2NCO_LOC_ID_OFFSET + 600), + "Flashpoint": range(SC2NCO_LOC_ID_OFFSET + 600, SC2NCO_LOC_ID_OFFSET + 700), + "In the Enemy's Shadow": range(SC2NCO_LOC_ID_OFFSET + 700, SC2NCO_LOC_ID_OFFSET + 800), + "Dark Skies": range(SC2NCO_LOC_ID_OFFSET + 800, SC2NCO_LOC_ID_OFFSET + 900), + "End Game": range(SC2NCO_LOC_ID_OFFSET + 900, SC2NCO_LOC_ID_OFFSET + 1000), } display_data = {} # Grouped Items grouped_item_ids = { - "Progressive Weapon Upgrade": 107 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Armor Upgrade": 108 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Infantry Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Upgrade": 110 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Upgrade": 111 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Weapon/Armor Upgrade": 112 + SC2WOL_ITEM_ID_OFFSET + "Progressive Terran Weapon Upgrade": 107 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Armor Upgrade": 108 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Upgrade": 110 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Upgrade": 111 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Weapon/Armor Upgrade": 112 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Zerg Weapon Upgrade": 105 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Armor Upgrade": 106 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Ground Upgrade": 107 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Upgrade": 108 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Weapon/Armor Upgrade": 109 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Weapon Upgrade": 105 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Armor Upgrade": 106 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Ground Upgrade": 107 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Upgrade": 108 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Weapon/Armor Upgrade": 109 + SC2LOTV_ITEM_ID_OFFSET, } grouped_item_replacements = { - "Progressive Weapon Upgrade": ["Progressive Infantry Weapon", "Progressive Vehicle Weapon", - "Progressive Ship Weapon"], - "Progressive Armor Upgrade": ["Progressive Infantry Armor", "Progressive Vehicle Armor", - "Progressive Ship Armor"], - "Progressive Infantry Upgrade": ["Progressive Infantry Weapon", "Progressive Infantry Armor"], - "Progressive Vehicle Upgrade": ["Progressive Vehicle Weapon", "Progressive Vehicle Armor"], - "Progressive Ship Upgrade": ["Progressive Ship Weapon", "Progressive Ship Armor"] + "Progressive Terran Weapon Upgrade": ["Progressive Terran Infantry Weapon", + "Progressive Terran Vehicle Weapon", + "Progressive Terran Ship Weapon"], + "Progressive Terran Armor Upgrade": ["Progressive Terran Infantry Armor", + "Progressive Terran Vehicle Armor", + "Progressive Terran Ship Armor"], + "Progressive Terran Infantry Upgrade": ["Progressive Terran Infantry Weapon", + "Progressive Terran Infantry Armor"], + "Progressive Terran Vehicle Upgrade": ["Progressive Terran Vehicle Weapon", + "Progressive Terran Vehicle Armor"], + "Progressive Terran Ship Upgrade": ["Progressive Terran Ship Weapon", "Progressive Terran Ship Armor"], + "Progressive Zerg Weapon Upgrade": ["Progressive Zerg Melee Attack", "Progressive Zerg Missile Attack", + "Progressive Zerg Flyer Attack"], + "Progressive Zerg Armor Upgrade": ["Progressive Zerg Ground Carapace", + "Progressive Zerg Flyer Carapace"], + "Progressive Zerg Ground Upgrade": ["Progressive Zerg Melee Attack", "Progressive Zerg Missile Attack", + "Progressive Zerg Ground Carapace"], + "Progressive Zerg Flyer Upgrade": ["Progressive Zerg Flyer Attack", "Progressive Zerg Flyer Carapace"], + "Progressive Protoss Weapon Upgrade": ["Progressive Protoss Ground Weapon", + "Progressive Protoss Air Weapon"], + "Progressive Protoss Armor Upgrade": ["Progressive Protoss Ground Armor", "Progressive Protoss Shields", + "Progressive Protoss Air Armor"], + "Progressive Protoss Ground Upgrade": ["Progressive Protoss Ground Weapon", + "Progressive Protoss Ground Armor", + "Progressive Protoss Shields"], + "Progressive Protoss Air Upgrade": ["Progressive Protoss Air Weapon", "Progressive Protoss Air Armor", + "Progressive Protoss Shields"] } - grouped_item_replacements["Progressive Weapon/Armor Upgrade"] = grouped_item_replacements[ - "Progressive Weapon Upgrade"] + \ - grouped_item_replacements[ - "Progressive Armor Upgrade"] + grouped_item_replacements["Progressive Terran Weapon/Armor Upgrade"] = \ + grouped_item_replacements["Progressive Terran Weapon Upgrade"] \ + + grouped_item_replacements["Progressive Terran Armor Upgrade"] + grouped_item_replacements["Progressive Zerg Weapon/Armor Upgrade"] = \ + grouped_item_replacements["Progressive Zerg Weapon Upgrade"] \ + + grouped_item_replacements["Progressive Zerg Armor Upgrade"] + grouped_item_replacements["Progressive Protoss Weapon/Armor Upgrade"] = \ + grouped_item_replacements["Progressive Protoss Weapon Upgrade"] \ + + grouped_item_replacements["Progressive Protoss Armor Upgrade"] replacement_item_ids = { - "Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Zerg Melee Attack": 100 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Missile Attack": 101 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Ground Carapace": 102 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Attack": 103 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Carapace": 104 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Ground Weapon": 100 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Ground Armor": 101 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Shields": 102 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Weapon": 103 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Armor": 104 + SC2LOTV_ITEM_ID_OFFSET, } - inventory = tracker_data.get_player_inventory_counts(team, player) + inventory: collections.Counter = tracker_data.get_player_inventory_counts(team, player) for grouped_item_name, grouped_item_id in grouped_item_ids.items(): count: int = inventory[grouped_item_id] if count > 0: for replacement_item in grouped_item_replacements[grouped_item_name]: replacement_id: int = replacement_item_ids[replacement_item] - inventory[replacement_id] = count + if replacement_id not in inventory or count > inventory[replacement_id]: + # If two groups provide the same individual item, maximum is used + # (this behavior is used for Protoss Shields) + inventory[replacement_id] = count # Determine display for progressive items progressive_items = { - "Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Marine)": 208 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Firebat)": 226 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Marauder)": 228 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Reaper)": 250 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Hellion)": 259 + SC2WOL_ITEM_ID_OFFSET, - "Progressive High Impact Payload (Thor)": 361 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Cross-Spectrum Dampeners (Banshee)": 316 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Regenerative Bio-Steel": 617 + SC2WOL_ITEM_ID_OFFSET + "Progressive Terran Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Fire-Suppression System": 206 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Orbital Command": 207 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Marine)": 208 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Firebat)": 226 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Marauder)": 228 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Reaper)": 250 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Hellion)": 259 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Replenishable Magazine (Vulture)": 303 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Tri-Lithium Power Cell (Diamondback)": 306 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Tomahawk Power Cells (Wraith)": 312 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Cross-Spectrum Dampeners (Banshee)": 316 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Missile Pods (Battlecruiser)": 318 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Defensive Matrix (Battlecruiser)": 319 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Immortality Protocol (Thor)": 325 + SC2WOL_ITEM_ID_OFFSET, + "Progressive High Impact Payload (Thor)": 361 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Augmented Thrusters (Planetary Fortress)": 388 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Regenerative Bio-Steel": 617 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stealth Suit Module (Nova Suit Module)": 904 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Zerg Melee Attack": 100 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Missile Attack": 101 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Ground Carapace": 102 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Attack": 103 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Carapace": 104 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Ground Weapon": 100 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Ground Armor": 101 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Shields": 102 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Weapon": 103 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Armor": 104 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Proxy Pylon (Spear of Adun Calldown)": 701 + SC2LOTV_ITEM_ID_OFFSET, } + # Format: L0, L1, L2, L3 progressive_names = { - "Progressive Infantry Weapon": ["Infantry Weapons Level 1", "Infantry Weapons Level 1", - "Infantry Weapons Level 2", "Infantry Weapons Level 3"], - "Progressive Infantry Armor": ["Infantry Armor Level 1", "Infantry Armor Level 1", - "Infantry Armor Level 2", "Infantry Armor Level 3"], - "Progressive Vehicle Weapon": ["Vehicle Weapons Level 1", "Vehicle Weapons Level 1", - "Vehicle Weapons Level 2", "Vehicle Weapons Level 3"], - "Progressive Vehicle Armor": ["Vehicle Armor Level 1", "Vehicle Armor Level 1", - "Vehicle Armor Level 2", "Vehicle Armor Level 3"], - "Progressive Ship Weapon": ["Ship Weapons Level 1", "Ship Weapons Level 1", - "Ship Weapons Level 2", "Ship Weapons Level 3"], - "Progressive Ship Armor": ["Ship Armor Level 1", "Ship Armor Level 1", - "Ship Armor Level 2", "Ship Armor Level 3"], - "Progressive Stimpack (Marine)": ["Stimpack (Marine)", "Stimpack (Marine)", - "Super Stimpack (Marine)"], - "Progressive Stimpack (Firebat)": ["Stimpack (Firebat)", "Stimpack (Firebat)", - "Super Stimpack (Firebat)"], - "Progressive Stimpack (Marauder)": ["Stimpack (Marauder)", "Stimpack (Marauder)", - "Super Stimpack (Marauder)"], - "Progressive Stimpack (Reaper)": ["Stimpack (Reaper)", "Stimpack (Reaper)", - "Super Stimpack (Reaper)"], - "Progressive Stimpack (Hellion)": ["Stimpack (Hellion)", "Stimpack (Hellion)", - "Super Stimpack (Hellion)"], - "Progressive High Impact Payload (Thor)": ["High Impact Payload (Thor)", - "High Impact Payload (Thor)", "Smart Servos (Thor)"], - "Progressive Cross-Spectrum Dampeners (Banshee)": ["Cross-Spectrum Dampeners (Banshee)", - "Cross-Spectrum Dampeners (Banshee)", - "Advanced Cross-Spectrum Dampeners (Banshee)"], - "Progressive Regenerative Bio-Steel": ["Regenerative Bio-Steel Level 1", - "Regenerative Bio-Steel Level 1", - "Regenerative Bio-Steel Level 2"] + "Progressive Terran Infantry Weapon": ["Terran Infantry Weapons Level 1", + "Terran Infantry Weapons Level 1", + "Terran Infantry Weapons Level 2", + "Terran Infantry Weapons Level 3"], + "Progressive Terran Infantry Armor": ["Terran Infantry Armor Level 1", + "Terran Infantry Armor Level 1", + "Terran Infantry Armor Level 2", + "Terran Infantry Armor Level 3"], + "Progressive Terran Vehicle Weapon": ["Terran Vehicle Weapons Level 1", + "Terran Vehicle Weapons Level 1", + "Terran Vehicle Weapons Level 2", + "Terran Vehicle Weapons Level 3"], + "Progressive Terran Vehicle Armor": ["Terran Vehicle Armor Level 1", + "Terran Vehicle Armor Level 1", + "Terran Vehicle Armor Level 2", + "Terran Vehicle Armor Level 3"], + "Progressive Terran Ship Weapon": ["Terran Ship Weapons Level 1", + "Terran Ship Weapons Level 1", + "Terran Ship Weapons Level 2", + "Terran Ship Weapons Level 3"], + "Progressive Terran Ship Armor": ["Terran Ship Armor Level 1", + "Terran Ship Armor Level 1", + "Terran Ship Armor Level 2", + "Terran Ship Armor Level 3"], + "Progressive Fire-Suppression System": ["Fire-Suppression System Level 1", + "Fire-Suppression System Level 1", + "Fire-Suppression System Level 2"], + "Progressive Orbital Command": ["Orbital Command", "Orbital Command", + "Planetary Command Module"], + "Progressive Stimpack (Marine)": ["Stimpack (Marine)", "Stimpack (Marine)", + "Super Stimpack (Marine)"], + "Progressive Stimpack (Firebat)": ["Stimpack (Firebat)", "Stimpack (Firebat)", + "Super Stimpack (Firebat)"], + "Progressive Stimpack (Marauder)": ["Stimpack (Marauder)", "Stimpack (Marauder)", + "Super Stimpack (Marauder)"], + "Progressive Stimpack (Reaper)": ["Stimpack (Reaper)", "Stimpack (Reaper)", + "Super Stimpack (Reaper)"], + "Progressive Stimpack (Hellion)": ["Stimpack (Hellion)", "Stimpack (Hellion)", + "Super Stimpack (Hellion)"], + "Progressive Replenishable Magazine (Vulture)": ["Replenishable Magazine (Vulture)", + "Replenishable Magazine (Vulture)", + "Replenishable Magazine (Free) (Vulture)"], + "Progressive Tri-Lithium Power Cell (Diamondback)": ["Tri-Lithium Power Cell (Diamondback)", + "Tri-Lithium Power Cell (Diamondback)", + "Tungsten Spikes (Diamondback)"], + "Progressive Tomahawk Power Cells (Wraith)": ["Tomahawk Power Cells (Wraith)", + "Tomahawk Power Cells (Wraith)", + "Unregistered Cloaking Module (Wraith)"], + "Progressive Cross-Spectrum Dampeners (Banshee)": ["Cross-Spectrum Dampeners (Banshee)", + "Cross-Spectrum Dampeners (Banshee)", + "Advanced Cross-Spectrum Dampeners (Banshee)"], + "Progressive Missile Pods (Battlecruiser)": ["Missile Pods (Battlecruiser) Level 1", + "Missile Pods (Battlecruiser) Level 1", + "Missile Pods (Battlecruiser) Level 2"], + "Progressive Defensive Matrix (Battlecruiser)": ["Defensive Matrix (Battlecruiser)", + "Defensive Matrix (Battlecruiser)", + "Advanced Defensive Matrix (Battlecruiser)"], + "Progressive Immortality Protocol (Thor)": ["Immortality Protocol (Thor)", + "Immortality Protocol (Thor)", + "Immortality Protocol (Free) (Thor)"], + "Progressive High Impact Payload (Thor)": ["High Impact Payload (Thor)", + "High Impact Payload (Thor)", "Smart Servos (Thor)"], + "Progressive Augmented Thrusters (Planetary Fortress)": ["Lift Off (Planetary Fortress)", + "Lift Off (Planetary Fortress)", + "Armament Stabilizers (Planetary Fortress)"], + "Progressive Regenerative Bio-Steel": ["Regenerative Bio-Steel Level 1", + "Regenerative Bio-Steel Level 1", + "Regenerative Bio-Steel Level 2", + "Regenerative Bio-Steel Level 3"], + "Progressive Stealth Suit Module (Nova Suit Module)": ["Stealth Suit Module (Nova Suit Module)", + "Cloak (Nova Suit Module)", + "Permanently Cloaked (Nova Suit Module)"], + "Progressive Zerg Melee Attack": ["Zerg Melee Attack Level 1", + "Zerg Melee Attack Level 1", + "Zerg Melee Attack Level 2", + "Zerg Melee Attack Level 3"], + "Progressive Zerg Missile Attack": ["Zerg Missile Attack Level 1", + "Zerg Missile Attack Level 1", + "Zerg Missile Attack Level 2", + "Zerg Missile Attack Level 3"], + "Progressive Zerg Ground Carapace": ["Zerg Ground Carapace Level 1", + "Zerg Ground Carapace Level 1", + "Zerg Ground Carapace Level 2", + "Zerg Ground Carapace Level 3"], + "Progressive Zerg Flyer Attack": ["Zerg Flyer Attack Level 1", + "Zerg Flyer Attack Level 1", + "Zerg Flyer Attack Level 2", + "Zerg Flyer Attack Level 3"], + "Progressive Zerg Flyer Carapace": ["Zerg Flyer Carapace Level 1", + "Zerg Flyer Carapace Level 1", + "Zerg Flyer Carapace Level 2", + "Zerg Flyer Carapace Level 3"], + "Progressive Protoss Ground Weapon": ["Protoss Ground Weapon Level 1", + "Protoss Ground Weapon Level 1", + "Protoss Ground Weapon Level 2", + "Protoss Ground Weapon Level 3"], + "Progressive Protoss Ground Armor": ["Protoss Ground Armor Level 1", + "Protoss Ground Armor Level 1", + "Protoss Ground Armor Level 2", + "Protoss Ground Armor Level 3"], + "Progressive Protoss Shields": ["Protoss Shields Level 1", "Protoss Shields Level 1", + "Protoss Shields Level 2", "Protoss Shields Level 3"], + "Progressive Protoss Air Weapon": ["Protoss Air Weapon Level 1", + "Protoss Air Weapon Level 1", + "Protoss Air Weapon Level 2", + "Protoss Air Weapon Level 3"], + "Progressive Protoss Air Armor": ["Protoss Air Armor Level 1", + "Protoss Air Armor Level 1", + "Protoss Air Armor Level 2", + "Protoss Air Armor Level 3"], + "Progressive Proxy Pylon (Spear of Adun Calldown)": ["Proxy Pylon (Spear of Adun Calldown)", + "Proxy Pylon (Spear of Adun Calldown)", + "Warp In Reinforcements (Spear of Adun Calldown)"] } for item_name, item_id in progressive_items.items(): level = min(inventory[item_id], len(progressive_names[item_name]) - 1) @@ -1925,24 +2366,62 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int .replace("(", "") .replace(")", "")) display_data[base_name + "_level"] = level - display_data[base_name + "_url"] = icons[display_name] + display_data[base_name + "_url"] = icons[display_name] if display_name in icons else "FIXME" display_data[base_name + "_name"] = display_name # Multi-items multi_items = { - "+15 Starting Minerals": 800 + SC2WOL_ITEM_ID_OFFSET, - "+15 Starting Vespene": 801 + SC2WOL_ITEM_ID_OFFSET, - "+2 Starting Supply": 802 + SC2WOL_ITEM_ID_OFFSET + "Additional Starting Minerals": 800 + SC2WOL_ITEM_ID_OFFSET, + "Additional Starting Vespene": 801 + SC2WOL_ITEM_ID_OFFSET, + "Additional Starting Supply": 802 + SC2WOL_ITEM_ID_OFFSET } for item_name, item_id in multi_items.items(): base_name = item_name.split()[-1].lower() count = inventory[item_id] if base_name == "supply": - count = count * 2 - display_data[base_name + "_count"] = count - else: - count = count * 15 - display_data[base_name + "_count"] = count + count = count * starting_supply_per_item + elif base_name == "minerals": + count = count * minerals_per_item + elif base_name == "vespene": + count = count * vespene_per_item + display_data[base_name + "_count"] = count + # Kerrigan level + level_items = { + "1 Kerrigan Level": 509 + SC2HOTS_ITEM_ID_OFFSET, + "2 Kerrigan Levels": 508 + SC2HOTS_ITEM_ID_OFFSET, + "3 Kerrigan Levels": 507 + SC2HOTS_ITEM_ID_OFFSET, + "4 Kerrigan Levels": 506 + SC2HOTS_ITEM_ID_OFFSET, + "5 Kerrigan Levels": 505 + SC2HOTS_ITEM_ID_OFFSET, + "6 Kerrigan Levels": 504 + SC2HOTS_ITEM_ID_OFFSET, + "7 Kerrigan Levels": 503 + SC2HOTS_ITEM_ID_OFFSET, + "8 Kerrigan Levels": 502 + SC2HOTS_ITEM_ID_OFFSET, + "9 Kerrigan Levels": 501 + SC2HOTS_ITEM_ID_OFFSET, + "10 Kerrigan Levels": 500 + SC2HOTS_ITEM_ID_OFFSET, + "14 Kerrigan Levels": 510 + SC2HOTS_ITEM_ID_OFFSET, + "35 Kerrigan Levels": 511 + SC2HOTS_ITEM_ID_OFFSET, + "70 Kerrigan Levels": 512 + SC2HOTS_ITEM_ID_OFFSET, + } + level_amounts = { + "1 Kerrigan Level": 1, + "2 Kerrigan Levels": 2, + "3 Kerrigan Levels": 3, + "4 Kerrigan Levels": 4, + "5 Kerrigan Levels": 5, + "6 Kerrigan Levels": 6, + "7 Kerrigan Levels": 7, + "8 Kerrigan Levels": 8, + "9 Kerrigan Levels": 9, + "10 Kerrigan Levels": 10, + "14 Kerrigan Levels": 14, + "35 Kerrigan Levels": 35, + "70 Kerrigan Levels": 70, + } + kerrigan_level = 0 + for item_name, item_id in level_items.items(): + count = inventory[item_id] + amount = level_amounts[item_name] + kerrigan_level += count * amount + display_data["kerrigan_level"] = kerrigan_level # Victory condition game_state = tracker_data.get_player_client_status(team, player) @@ -1951,7 +2430,7 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int # Turn location IDs into mission objective counts locations = tracker_data.get_player_locations(team, player) checked_locations = tracker_data.get_player_checked_locations(team, player) - lookup_name = lambda id: tracker_data.location_id_to_name["Starcraft 2 Wings of Liberty"][id] + lookup_name = lambda id: tracker_data.location_id_to_name["Starcraft 2"][id] location_info = {mission_name: {lookup_name(id): (id in checked_locations) for id in mission_locations if id in set(locations)} for mission_name, mission_locations in sc2wol_location_ids.items()} @@ -1963,9 +2442,9 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int mission_name, mission_locations in sc2wol_location_ids.items()} checks_in_area['Total'] = sum(checks_in_area.values()) - lookup_any_item_id_to_name = tracker_data.item_id_to_name["Starcraft 2 Wings of Liberty"] + lookup_any_item_id_to_name = tracker_data.item_id_to_name["Starcraft 2"] return render_template( - "tracker__Starcraft2WingsOfLiberty.html", + "tracker__Starcraft2.html", inventory=inventory, icons=icons, acquired_items={lookup_any_item_id_to_name[id] for id, count in inventory.items() if count > 0}, @@ -1979,4 +2458,4 @@ def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int **display_data, ) - _player_trackers["Starcraft 2 Wings of Liberty"] = render_Starcraft2WingsOfLiberty_tracker + _player_trackers["Starcraft 2"] = render_Starcraft2_tracker diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index af4ed264aacd..45b26b175eb3 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -7,7 +7,7 @@ import zlib from io import BytesIO -from flask import request, flash, redirect, url_for, session, render_template +from flask import request, flash, redirect, url_for, session, render_template, abort from markupsafe import Markup from pony.orm import commit, flush, select, rollback from pony.orm.core import TransactionIntegrityError @@ -63,12 +63,13 @@ def process_multidata(compressed_multidata, files={}): game_data = games_package_schema.validate(game_data) game_data = {key: value for key, value in sorted(game_data.items())} game_data["checksum"] = data_package_checksum(game_data) - game_data_package = GameDataPackage(checksum=game_data["checksum"], - data=pickle.dumps(game_data)) if original_checksum != game_data["checksum"]: raise Exception(f"Original checksum {original_checksum} != " f"calculated checksum {game_data['checksum']} " f"for game {game}.") + + game_data_package = GameDataPackage(checksum=game_data["checksum"], + data=pickle.dumps(game_data)) decompressed_multidata["datapackage"][game] = { "version": game_data.get("version", 0), "checksum": game_data["checksum"], @@ -192,6 +193,8 @@ def uploads(): res = upload_zip_to_db(zfile) except VersionException: flash(f"Could not load multidata. Wrong Version detected.") + except Exception as e: + flash(f"Could not load multidata. File may be corrupted or incompatible. ({e})") else: if res is str: return res @@ -219,3 +222,29 @@ def user_content(): rooms = select(room for room in Room if room.owner == session["_id"]) seeds = select(seed for seed in Seed if seed.owner == session["_id"]) return render_template("userContent.html", rooms=rooms, seeds=seeds) + + +@app.route("/disown_seed/", methods=["GET"]) +def disown_seed(seed): + seed = Seed.get(id=seed) + if not seed: + return abort(404) + if seed.owner != session["_id"]: + return abort(403) + + seed.owner = 0 + + return redirect(url_for("user_content")) + + +@app.route("/disown_room/", methods=["GET"]) +def disown_room(room): + room = Room.get(id=room) + if not room: + return abort(404) + if room.owner != session["_id"]: + return abort(403) + + room.owner = 0 + + return redirect(url_for("user_content")) diff --git a/Zelda1Client.py b/Zelda1Client.py index cd76a0a5ca78..1154804fbf56 100644 --- a/Zelda1Client.py +++ b/Zelda1Client.py @@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext): def reconcile_shops(ctx: ZeldaContext): - checked_location_names = [ctx.location_names[location] for location in ctx.checked_locations] + checked_location_names = [ctx.location_names.lookup_in_game(location) for location in ctx.checked_locations] shops = [location for location in checked_location_names if "Shop" in location] left_slots = [shop for shop in shops if "Left" in shop] middle_slots = [shop for shop in shops if "Middle" in shop] @@ -190,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone= locations_checked = [] location = None for location in ctx.missing_locations: - location_name = ctx.location_names[location] + location_name = ctx.location_names.lookup_in_game(location) if location_name in Locations.overworld_locations and zone == "overworld": status = locations_array[Locations.major_location_offsets[location_name]] diff --git a/_speedups.pyx b/_speedups.pyx index b4167ec5aa1e..4b083c2f9aef 100644 --- a/_speedups.pyx +++ b/_speedups.pyx @@ -1,5 +1,6 @@ #cython: language_level=3 -#distutils: language = c++ +#distutils: language = c +#distutils: depends = intset.h """ Provides faster implementation of some core parts. @@ -13,7 +14,6 @@ from cpython cimport PyObject from typing import Any, Dict, Iterable, Iterator, Generator, Sequence, Tuple, TypeVar, Union, Set, List, TYPE_CHECKING from cymem.cymem cimport Pool from libc.stdint cimport int64_t, uint32_t -from libcpp.set cimport set as std_set from collections import defaultdict cdef extern from *: @@ -31,6 +31,27 @@ ctypedef int64_t ap_id_t cdef ap_player_t MAX_PLAYER_ID = 1000000 # limit the size of indexing array cdef size_t INVALID_SIZE = (-1) # this is all 0xff... adding 1 results in 0, but it's not negative +# configure INTSET for player +cdef extern from *: + """ + #define INTSET_NAME ap_player_set + #define INTSET_TYPE uint32_t // has to match ap_player_t + """ + +# create INTSET for player +cdef extern from "intset.h": + """ + #undef INTSET_NAME + #undef INTSET_TYPE + """ + ctypedef struct ap_player_set: + pass + + ap_player_set* ap_player_set_new(size_t bucket_count) nogil + void ap_player_set_free(ap_player_set* set) nogil + bint ap_player_set_add(ap_player_set* set, ap_player_t val) nogil + bint ap_player_set_contains(ap_player_set* set, ap_player_t val) nogil + cdef struct LocationEntry: # layout is so that @@ -185,7 +206,7 @@ cdef class LocationStore: def find_item(self, slots: Set[int], seeked_item_id: int) -> Generator[Tuple[int, int, int, int, int], None, None]: cdef ap_id_t item = seeked_item_id cdef ap_player_t receiver - cdef std_set[ap_player_t] receivers + cdef ap_player_set* receivers cdef size_t slot_count = len(slots) if slot_count == 1: # specialized implementation for single slot @@ -197,13 +218,20 @@ cdef class LocationStore: yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags elif slot_count: # generic implementation with lookup in set - for receiver in slots: - receivers.insert(receiver) - with nogil: - for entry in self.entries[:self.entry_count]: - if entry.item == item and receivers.count(entry.receiver): - with gil: - yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags + receivers = ap_player_set_new(min(1023, slot_count)) # limit top level struct to 16KB + if not receivers: + raise MemoryError() + try: + for receiver in slots: + if not ap_player_set_add(receivers, receiver): + raise MemoryError() + with nogil: + for entry in self.entries[:self.entry_count]: + if entry.item == item and ap_player_set_contains(receivers, entry.receiver): + with gil: + yield entry.sender, entry.location, entry.item, entry.receiver, entry.flags + finally: + ap_player_set_free(receivers) def get_for_player(self, slot: int) -> Dict[int, Set[int]]: cdef ap_player_t receiver = slot diff --git a/_speedups.pyxbld b/_speedups.pyxbld index e1fe19b2efc6..974eaed03b6a 100644 --- a/_speedups.pyxbld +++ b/_speedups.pyxbld @@ -1,8 +1,10 @@ -# This file is required to get pyximport to work with C++. -# Switching from std::set to a pure C implementation is still on the table to simplify everything. +# This file is used when doing pyximport +import os def make_ext(modname, pyxfilename): from distutils.extension import Extension return Extension(name=modname, sources=[pyxfilename], - language='c++') + depends=["intset.h"], + include_dirs=[os.getcwd()], + language="c") diff --git a/data/basepatch.bsdiff4 b/data/basepatch.bsdiff4 index aa8f1375c522b724ae1944ebb6de113a7e592a35..379eee80c6c083b1f933fb2a4c346d28cb16f711 100644 GIT binary patch delta 109064 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�k97hnimh-=*_FR;C;{$fwKe8c%>&m4md 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 z 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%0AD)bh% zXCGy+D6|fVdeKh|9WEqQ%?i%Z;b6~x3RU|!QwWrc!3ay54mkGW5XP_oViu)&DmgC; z{5VA@H9@D-JxtDFe?-EYpF!Iu@YD*gLkg(1_I*vj0lqS7oSK6z?+G~HA8>1gV2#M~ zJ((_Jdkf=~^ZHv%n>clB`rLjaz3z1I5a+W#u^0Tu<)f?j{r?yyyP3@a-3Qt*!HM62U2xV9Okj`0x8YcSA}$_;9;Sf5_V~iS8RSZ?REenrU~> z=UB0rlzd_~iOCQ`4j(X&r5e(M_`tKo)#!-8E~8U(HJduAgcGB;Ju&>T;phDDI=(8q z6A)MFU7^E3w!)&P)#fMW``dK{lr zNGR0HX*)Py14HYje`HiA31b3@fKU=Md*z;)o`)uhkZY2! zRu6mBuck(hBNaz>6$7IOX(wY&aDJMtk4|p6EC50KhFS}1F08%cV1n_$Q_!jcLe|W0 z=iI(c1^XhUEn?5PvMyecJ=HQg^I{^6jH`49l6u2%RYS-GAov3~aW9DlO@8pfvhR}M zf6B-D4&kK;LI>_8J?N@n62?@YuJMu0yfjtE~!ycA;qF2M829B_l1rEDOVE9 zYi_0Y*;ojy8KYc4hcwg3n497oi@DLhe@@>rLndyHUfkU;@Zs_b2jPBYPgLUX9P1fZ z!o_h9b~qBSh0#V8P7x3f9LYLGu!$;ixOzNTa#q|>n=N)tBhRCES9Gpz6lvw zsj&L?b<*)&?F?@4^kWMcELhH>c;s+I%4THiv;Pjyd7hCnVycY0CTC^pYvlB^EJGwS ziem&j8pRl5z2Dbhm{PNn#;{yZjxRSh!~yJS0ZJrlq=BC*2;IQOARvHGKv93fgi@9m z#YD^&j`{Uq06;}th0>P_!l3~TRB=RM$q*Ev%&LL_!>D`0HZBd4vl#3e;>*(DGD~U? zphnP(TS6^(Y zZD$K5K&eU&6~%-P6Z`=|$m&%$;zFPjID6VTms1e^ExfA!8o$inzYeRIl*3*>XseCd zTiBUe#oH^_!$9T{Ta%yNV4NNW4?gR7@{*YIu&wJcIVkZ)w*5pze^S;gvnINZ9vTlK zMti!Y6i5=V3M4UPpT+9v=23nE{^7D1CsrtYs`zwq_3u_e#_g?`v5^ zewF=(*rnz{b7XdA@Sve4?>yS;F&9|RUdiTHp1`pv5Tpw8_oXNpE)s;x6#JN!&=K~i ztjurIb>2A!DA9$Oe=AC>oGOl@9+hvAvq7^}q(LKPo);Q{47m>+f&OG9OfKc3#{ftg zs>E0*U48+QYqVuFHJzwniaB(0ZuI{e?_{s3XQ0_MYE^O-H6 z!0erIL=VSVd%BkE;Qi=iMlT)axrh?Fq!NT7t6;7*hh1|k13BGQ^4}1pF^?)?NT`#8 z(s7HD(wSa60^0SE#XuxsvD3WgOWrJ!f}a&o^F^uMQJ?m?sNN?Rf?60CFhO;a@FYs& zKBB!)RLG1Ke|~eHG}xUf79anyW^|)lvIWeSuND6>c}l! zB8`QS9jv!yv$~uLTM$GY+C80t4mnAUx`^)F*m1YBg`v~+Po`Zt%%nKSQ-7rKc*bKs z0`OfHPo~*5BNUcJt&mVpSBOb4snlulrlo?k#6x>Us*a&sDlAS%6j#y7j(<39R_?CQ zNaJOOe>$`>h-K6>Cma<=F7h{GPLt^_Xgn<^M#fXU8vSq=JUD|*@Mme4$u?`uGwn1g zir6p}tIAPs4lZ(y+q+q68-nMMZy+nH>`7 zKS}8o93BySzGqVDg?j`bQNiL#`TFw{##vJtfAk+IGpw5=i#8DX^^y^iG%tILTJAV< zvaoC+ZHk4R$b#fxF*~MSyDbZk@!K<4g@w7HbCu+ z1OB~}yVR;w1YaooRxrMGPU(y!nF;p27NM`T>e0oj>5+h zk`Qkv5M$07(@Ekn2!+>Sra>D&Pe93Paypeyoyd!5;$^$ALZ;G=y-f;em5i+r>{meT Z-zyJ%<)(s#ZB75h+>uTcBnI;Y9e_;*W486eizrbRMsY}R8NpZ6N#sU0`N^3FpaNt^bm)HRixCv~ONu;>& z;KzS%A$U5x}Q2R@@{{qGN`}3JHExY&5l=u;8cuHRgm51 z#!|ot5me-yRaEc4ots52Da$SkG>)F)Vn?Qb!l<{+DNa*gvEW=T9A-_UEl!s`XScOU zqhw8|0VX-fJ}c&qiL%1s(*M*UQJvuD*u*%eu_=Il#?K(^Vm`PmpJ zX(vJi3h6fK^m{05oO7mxDjUfT(#f~k#p&iNiiK~g>B12SpO-bqW2`4A#lvijv3g@gimX!FM5a^E6K`Jx`09QCJiiN?6Zk>FUP%c!Kq{yx; z!7Z*>?)7(cTZ**F*J)_ew2{_-$DoX84J05`jfdEi@+uHL0ovqR3n};m(A1^o2;fSE zKoBQDAX_2O20gDh`L*mATr6=**-v|dqA+K@k{vBoauTQdD_i!29Yu%+Xe*>xmNX(n zDb=rt!VWdZ>J^%cQfgnE#_1J90Hwg`KQ%jNZf$N;pfDA~&JLd(UsBe{q`b ztD%|8F{{||q!TIqeb%vaU}t}ctRvvHA#?t#{Edk}XHD@GSVS6)rH8t;03{ef21guqOFWowUSl>rA>V zj7qZ&S17PtmH!a3ciN&jtivX$?HLa{JcUJ5Nq0iWx0K{LrcFQsv8vfp)Ed;rnx+C6lY9#uDpK4il|dI@KA?tL(zXpb9JG6 zRaVL2Jzbh1hb?VU!1Pp*Xw?8xi_K9ks@l8CMhk3zQ1LElo_zdA3`1|SNIl9jCLb#u z7-hwcu+BVGk}DA4Y+dhoxVyjM&qB?$H!r5a_M_>s^3KMQqMP7XDC`?#Kq?k%Hld3Q zrTv92mZI19ipy2zdQz*NjNU<+lj;+`Z@-gZ^Vl4A0t3xLs{l_#cjv$^c z`Y6EZkO7Ikv7pi-JAsUD`70Eh9%u-oa7poE9(e^fegZ9b!Uew9lQ_cPlgkzwq+|Gh zAeahEBmn*ERLY9&{3cT9UHWx{a}Y*cZpITeg$csdM-HDNQB|V!jr@8^J z9uyZ_nnH0U(_=|0W0++|wWK#S7&7j6y-gOrumpwNFcnnCppQAVq>BW7y5ue_Y^>biCXT6xSp< zfF(ki1N|Q83ODH4CL2yeM`Pmr*&WJg1WBW-*PL5*OT>GPRTRG4<5(tP5Ap>6+}i?% zTjUq#v!88T!Jxj|v*gy>+cera(yS{S6}SV45=JX^@yD=Z-06)VvhfZ5bvUld)D z3dW#bJu@7^5MXayjSWqj!3&SD(+@!+2Y`zLKo$C@Bxo!QU`6?tXRFagO-o|#K>B-s zg(nLmg;7<~1&|>=|Hc+Y1439r^+~Hk^O0~&W-8!}_9ML>gf&){7lSA0%31=elf!8Q zL9?QWQ&`!77i87M2MR#@eM{`(XK4Uv@T;UQ$GgODi+*zDB#fcpdzExOQWI&({F)ERi!DPMbO zKp-iJZg@>e7y*QBBpBJ>@(l*PoV-2a(##ui3S9zc?TdzPT8x-lA{5zLum908XiV|U zHWaxIryNMv(kT}ZCJ3({-d_egA%r7tW9HdjS@uPr#kM0n)x9*Rfp01|VC>5+V?QHE z91JtrufVZps9GkUDC8q8i3f||j;)Xh^2&~lbh4a4#HHZD0@KGF*+NA{^3Gad!&yPO zVAw!qrR`FCstn_+?moC-p$F_pH2bI4RYpFuT!D_ZSj?DuCAn1~OX1vc`{m2Xj6v>v zCs4RiOyVUeedZv zKo2fT`n%05;7mVTf4!gq3AS9js%fT!QtXtgHM_sBOwml5W&(&6v^s3XEZ^tcaiUas zxUElVxllp5EpR`^{Hc&0E;k#0MyP8peA-IZPUsNEfRcGR9`Inch?K`39-|}r;>{Yw zmE=+l9WU?;J9n~E#px4X^#H3V`3e_mh|dpV+f|36NburqdG$Mv#Bf)>zL7+Q*=Ht~ z7TsVKkQlmyLB@vS+AkTgewST=axb^vpV)-$6LIoE6lZ41IIHMVY`IaEYddRSfxQU$ z7?{Ww2%2Y3K>C~_;tzVm_1KHOxk||psEtSU#REQ$>nAf=Gba{h6Cg(hJ|ucgX8CgXZPxcIyv%e z|KV(DE$j#eT)ENGHVKVnHW^0?8aa76ja}VqY{FmU=nv-XNjm&XZmB8mrrq7wubLai zMd_8mU2w^z>m&OBc~WEt=chOe>_YA-;rCxzqF`(xH_keyt*cF|j+Ld^P1W{}H37GlAN1wY76l4sZ#A8?3D_xzT9{KXgrkJ3PX4N4RjuWk2 z6$N~!qv`TYBy7q6k9xJ)e)ZiJwjH>R7=s(=k8oZM`|b3lJbfZp`l>>o=u89WBAuDU zpa*9k8k183%<+SfDDnc8sQ}ne`yB0En%WDSS!{Tt_FFB2&`?9pSu2I0#!@Dnnc4KL zl$FSxEO$>q!W59ApWc}pxGNbNX^~z@GXM_Kb$>&x&J~g_&)X44rD;C(fxh7D@RdKfKQi&Xe}vnrjaRXwQQ`E+riqtTZqGXYRHv^>F(NW>6}4CzVGDVz7`e? zt>sq!Su%@hoXqQ#kE0XlAQKkNXO^x?7E0O(WW?1#kYv%Fk#76xiUU)AS~rhv4+76$ zFWBzC-OtzKfA3xg9ULx1WY*$9lnR?t(}7gz9}t0|@f}SXSHH3-X=RA-5_A2(48Zgd z)jNOQ&1(J32Dixq6EPyN{M`qBUtFVIZjgbEnhKs!oXC|5^(f5mzdS>@4W5}dtKU6n-OgV;91KMrcqZ6A1UP{aC%QfM=ZLxwkDHjv)uW)PC;3}%<5R;5zClQc^f@!&Jq69__C|d0&7XX3y=>Vsb{q{O z{Poab8)X98;6S7)Y@HSheeh`)dhg|b$W@oHociy+e`y3 zOYqA6P)l{!ei+f+UQAIr2f4s{?vo3)G5JFTs=)cuc5vNO1byUV^^2Gyj|LJdjU$Mp zcu9v*kp9859uXxYE0#31VpyN4PEbdk2l)n=@g@u@k#SoD-F1vPVSx>|Y}esi#^I(a zI98eszfd%`w>T3@H%$nXC&k`hb+7$Mf{0;)#a{HXB4q{byJY-ci(ZB?dDkfF)`9x_sDztv~wO#y0!W{-f*drxuuR8ZuZk&3(`0S){j> zQ@z_OQAOwn<~T#}5o_6pHtk_fgX&c1iLJrY1{ZZ@I=z+-`8Eg z0uTnoiaoR!Sfb@28Y@c1qC`UYW5`VeJB!Q1m>qP8uUW?J-`62%gwit45a$?H^}6tA z8>-pB39{*2?+2r085-M?+pgc22R|`!&nJ#NCs8?@O_MKsP#zk;=NwGpX%P@vlvHCuo0pFXmkQJ*2- z@vdZkIEbfRX(^iTV%B|1bkKLeleF|osnTMbnCp_Or6;-U>?oL8%@~_2Kc1CO9(s(f z7W!N_tUJt-mS*NwK?ax~Jw5%Hn#5RzCohG&wD98Fo3dw!6s{P^Nm~UrcYcSlN=@ zEgVyvvo)w4Zw760FYtcd-TOSR^dR%`M-MEYkrn;SV$n>)ZdtIoE|D%ObeA}fd=`f^Xj0yklubP>1&lTWEH*RxpcT@{NsX@;aR1- z?>#P?0Xu6K=NG>hvGvnTyHg(_jSIRsx8%GxOA+^;L2VKT0!n*oz zjh{2zV({jG_+fhAFUb5KeKHBEdl|t-#H-(zj}=iA3SKliw!2KP!=sF3V;}aaG=w~0 z_zp?vP%@sw`R`DU^eKLmSmRB0u0w)q@cB)x-+BU|K)_onZ3qA@ zVvD}HS{h}hmNw9YU)WP613iU&8LFD`J`C& zq$E3#Ku((F$2sY8mcv=c}vD{9{cyo3R$BsSLLikhw#M)KaV5 zIZDK({GFK5IOu~kPGS6`PAy#`a7veKK9FF}@VAVkZ>~{|g6u zw=5Epu#kOkCO27%%wPe#$4@c8;}Fb}^bSv>jM$Eek^mTH2p%*ez?$_uK71Kn>n%m5 zXxNY6luuZC4II6$Q_&uc5rZs!m+bG#S zGM6s5iLo&CNH;c;8h88#o=7iaC)6C#`hAQ&f7Hxdu7Z~jLD|XNlp;Hf|(YJzncaUXs0*Uh5YFYx!ik*(CyI>{~_)L?yNW^DqraCtJKax!&=f zGpYK^c0z8l5axU_{N}*KJvd}SQ}wRfIZ27< z7hF6VMNoFwDjQ#$hp3QEO3}sy?6WR~kf{W~_^Tuo=W(AzJ@c#cWY8Sp^QWU%JD@MW zirBvl)EFpwM6;)X&G#d1vClMBgE?c$+Yt7tIChe zVItSKrYs2X&uN>6Rc!U(=FWAptE)ZZsawHa%Og@xTlRiI#_KPFFXtx#bJf?+LZxS) z4c>Jf+@epepqo(%rv|ZqOwOa?)N#oa$2@aM+R)HUb4&b$uuab(WR2E6DLCuipO+Ex()^j9E~{YVc2TTfOrE?ZG&w1TOX7!(c4=4L#*P5PVsdC_$N6hf zYm^+@fLX2OW%xdq_ZJxe1H!FGR(%5Pb-wUY*rZQQdB_imQC*zJjSZuen>p`weCp(b z{n`f$u!sXuS9SnT33cOugd!CIu2nQj5!y{5S1<_-8R&F0^XWpMdSBDKLZa0Q<$3H! zbtG+!NgzP7Na2YpQ3K-Y=(Q2l#prP@I~f@{^m!x@X&*ePaAAw`V337%o@Y?K*( z1a|yoEfrr=5x~;H0$5zV{T$yEZmqwotbim`0ymM-DkVcrYv;ILbWX$9iGR|_8x~do zE@At7+?%M!w>-_1iZjw$!Jb2&=xg9qf2P9w68eX|xm>C{4=Ai-&s@+ddlg^;w_<%n z6&cVi)}MSUzN2om`0F!SqVc$ zr6>d%wUK<`3oEhZL>3Rs!%dl|x@yodSdee@?9F-O55`LDFut(W;m!+5&BUG0!%7~m}zd6>lELZ9#A zalJKxUZ?W!3SS+ULBFR5KlToLx9Y>198Jg>IuiFkH;8$De@G6$*3?66rf~6O1s99E zTB5v?oH7}4BM3tf)u}Gd@C_R%WM;@Mk7y90E)au!4uAal=urB(<<`?$*C%$A3O_7F zYOHjQ16QKhh|)>?gISU7>jJ*auQAKl@YQ|7R@9Oz?{(dX^`SzEYDMXQA}{=zfq2p$ zz*y(Lp#6{Y!`8fhnxOWL!!wQTT8@`%pAYs{9F9x(K8wL-ziv#C!!-r)&V@_~A|{?R zAuLEt*P4$_REI`gCo*Z+6JS~kPGBPkZ*whqZk!djv!w$=|_F6J|` zZ+h>iW}0f8Cw*><=+(;A9u~`u?9cBuZt=cE%X@bE-fRjj1D;W3AOR5T>0C9W3`(tT z*7JBQqIy-Q$w*69#*6J+YiRoAVP>1Y4>fJoQhdnkORKhbtIIuj4KB<*RZ%G5h39n} zg6vFJkF^X>fvl{tHH~#C9*)-46lYZ6L&rv82IqN_d-K|aKr8pLrknlQdPduK97jiw zi&iAdq~=Qg!6x_Vn9mjV#mr53xzuoTc)Y$fjV)u#<#vDvYn6xQ%v!l$w)^?&zpB2w zJ2VXPdzvOJtxVmu6ct~!<+7fE9b8MM>m{j$CeOUr}sv{&Fsv?_ykr1C-<8ErT4P3?H2Zf zXO((e$)a$_o$&aiIjQhVzN3LbslTV6T*kmW`)}&Kuu{ z@tgez@4<&oU!Rvq&y^D@^-}Q5X-P8HDNvjs24cKICO=~&;fS)F1Rm!=X0TE0z8+99>YQY0BGxf z1#0~vfF?j70BmgyrO0XC-){ozk7W%6OaZK?{uSc|KxV_1N}$67;Qz+({-LRV^+OL& zDPUp2`lkX?qodPpa%~dg*-g!%#KomT(`icltq}vsK=4zr=>PzwA^?JO7SbB1005-I zCIKK|qhp|GV*bWT|3(rca;+h9r;~fl!F>{D>T@H3HpPfv;nlD6u)AkRL7)I3XtLS4 z>0==*av^pU*kVZg!fUT|Wx6{O)g3kh?j#DEeL~seKxOrjVDawa86i&FZ_NIic76he)>a@l2w_Kb+>p6HufPXqd;y3mT>|13}x=lUh403t~Rj<7iIB5!g zF!C$*mGoa>vU|7CHphy`%2a+yQh%e<=^0Wb$F#|_qu)e}ZOsRB|K#P~W+%-dqjS!n zqeJ{vODf2k;NRMUpCymygc2?G2Vni}#)33P$V-SQAnONOPlqeep99tJSG}QKtM_~S zN%xK?6Xb6h^` z_oVBvhFfnzl72cmdf(j;s-D@1%;W}KJ*SRZAsTQE)9>-m({PTy&evVNr+E=ZC+WEG}~*QT*oghk7>|< ze3-#zPg&wI$7N=~ooJfJ4kt~hd1tbAiuxIxuJM`7oio2A!qSh{mv?Q1pDUM1Wnt3{ zUk!*8I3vy9vB$w|ZF+ZW^Y{j`T=NQ%R{1*`4DlpR(%J56;x+X`Wy_9QiZgIhhY8s z9YD5;triX_AqNy%Vgn#mbXfN0IB93*@phn_t3J6*i1Ox>K~lhx^8k7n4~on8Ik~Hj z{ECU7Zn6uKJ+-g)#)t?C?30Gw8}*$I#&!jQx!cv&{x~JAAvW3E|^3I zM7B~6QP0SlJ^50uI#8}o80+i2N8?<0f^W+t8p1w`!iE_TaAivNlW|bM=RM$ zNr4yg5{EB_6_)35fiKV1#L^#4dgMA_Kke^uOMZQ=hXnz@K6mPD0i$JntO-9#0E7qi zy79g7gh})@kqf0HgIJ~38(&xq-{JRr1L&11<=Z~ShgM#^yUlm2|AE)CV%_WjtxsO4$!|q{(a|Y3Cb!T~PR7KxE1AE$@-f&T z%U1!T?r8x%v@^D>=5pCztXwE-X9eje|IqJFXg2igEVgv0dA9R@T!Np!*!H<+Z4}>m zZ3=zgxC}Mg+2ufM_39rkM#F7HW)Ax3r>xCg8$_sqw|{UVu`KR&Nw|)XFRg_v?Yo9= zHr|tq{=y(tXc9z{TguCp>d8r|7=iU^&I%E2P{jipQAoM)_lHz2Z$2hexTs}X^+f#& zDbum}2MyQsgpH!p@dWtCkE=e7b>t%hmTy$YwFn@SFx2rw?3HFJd!rwG{x`?%JgY6k z%*%PweDif`b9oFy7TTTf@J1CbRlY^LlRcxmx|q$g*$|=>TCLtvCl(j{esjBj?l&&G zNT>q%C4NxL3M?&sD!99jsU6uA*?du!PBdz{)Bi>EX*L+=%V%`3c7n|M0R{f)Y@=7< zTB-K&vT|#y*VaT2j%>>F3JRveh^pY+NFBv*LKxcztUnaS2pAF&e?A$2Ppnh6H5op` zNuWcf^YCg9qVN?&cMe(ai$ZVuHu<~qWFrV)bt*6wsMZNWiO@uKaRk$T?^>BD`FZ+H zgr~ah#GFE**1)C)O#h3$?2}(kiMQP@p3Jg0i8i$GwH%lh|HkauGG7Yq3)hF~24pEbYE1ZX1s7FK^04r3LDh zG+TU~4QecAVGvqfopU!inHa>o$FgzGeZQrfBb3jiN;Ijo2#8vq!lD}sfT%7?B<_MY z_j#Tjxwd#Zth{;o)}qDCZDUEj=u0`D09|VAU4*apvA_^)X|gvIl`s9`bnH;+&UD4W zz@IU1bCzWWj`51+Svp5YG2j=>HTH*yh6=xQ3Rgk54IslGMFpyjKber}?<+e^Fd9Im zq~QH1KOGF~Z7^+hP5C4$B{72v3qZ=0baqwLLY1w(T?;cE_Y40x#1<_boI{uKEZ0JX zv*=i>>hS3$Zbg&*^jtb_DvZgA5!HR!31}Xr7s-bK|32mjv-KB6W(-A6N^sw1mAuCD zexpp>a=`YSZ##jx1|ZD&|7Zq9K$Xyy_wqb@_ZgiD15Q0Nl5yNkmIXDxc=}pkY{7rZ z+G^lq(khDf>(o6O1}1^o+a-VBzu2vFq9_wTL8uJ0EH+mnho@0v#m;8Zjly~8j#MHp z-12IH@h$w_qD!0oQYvzKf7r!f><9@D9p?SYwK{*>G;1@s|06!Gl*g`IHUhkI!Btg~ zE*Nh~U>#Npo*|2E@}JpqFKahhERG#WSJ}W6*hlhGHT~34t*xo4rgH02WTV4NA>}e0 zEpLC@**rjJs0ff0D~eeA@)HC7>$LM`_JN}pcr~jSV)Qe+98`N}8{ycp8v(CNn) z8=lnjLzYX>@z|A~Q4Xj7o(8@8&wgl_-PwAR&BJY36*7ou^U8@ku=W402Q`*T6J;1mc54&6CRI2PwVmqoXJ59m5|1acBL@ zpmP_@bHJp=>s#>Cqzm7|cBI@N7rzB1*gpSP^!Pu2(7xik;cU8>{Brq8&Zl!^VDW(% z65ZQ9`|{BChK7oov3P0q#yqAmzaDB>1KxDEK*(|PAL3FtAh0#q3WDWKXyrx2ltV^E z3$?ndo_1~bwT9+3?5ssw(QJ}-9lykc+4?SuLbLVR)VaE_MxlV7J5+Gv2!k1N4B4&0 zvP=jo|EFMAU{(kTXG&b4Zc@f`OxKs22Arp zhjx+^b|^iB;}c7Yy7YV7jncc}zF?`fvys0q%kB!Rw1h|;A%lA#jmwqui_`k)t&J>x z1)lOCuoY9&+AC*tey1&tkM8xtQ3(;|P@m&Lk=c4pcNCTBWg5Z6#X4Su zzflMxaE)qe;Tgg|YU-b*d{w8H-*QOHYh?*=D(Se1a@G|g9|~aM%m+4If<5{)CREOJ z1%Y9*-gS-+h~aD5SOT8#OZq}0b2G~^9<#QX);*jVa1k!>MCA~o%AgoV*Kx4xL`gR= zZq*EHoiQzuaG0&?^w_NBC~IvN0?>b3BRc3spMl=O&Y|J`(xzCa%+xLl8#rK5b8NYt zIU!~DKnq85ek4X}?zi9t=A&(R@tTCiEj^IzqUwEm(3%#PIJ;Cu9Q&TcL@L5)@dwyC zqN|E*EP{U*JJ2;z9pxy9d95$jGcByawdQB^z_kl)vwHoyt4=3XkW}I6!+VB&vwXtn zZ5ACX?-{|uUJK=#T>R4M_QveX{xGsHv!nGP?Q&DcoO- zM~7JuJ2sZhP=ek899k>zhuM|@>#>@c8P znNYg+b`Y-3?3wq_s|RhO6p2^r%w&|_HxJcahxE^hrnHJqV4Q0n1K|ZNG{m`GVx9QI z-+T`)jBkp;COS`|+}w`rFC!)_eHtpLc#&@m?rNSZ--dHDu52gkqWj90Gtf%7hCL#3 z7Ri%2@sapMh_m}h&I~Hqsat*A5Xlz==*V~6JgNI)W>M`Iwdfy)l4=jD*r;dbW$+99 zIsGYhk_~0*!Em+qPtP#nVeYtIPP`5${!n#v5ab9T88IVG#>Fwx_K1645n z?c z2Mz-+1yhSon*4S(4iK^lXl3Sfpl&uj^sNZr0OE8xTLBZ_*ooT6zyy;i{vn?5D`0?VHE?A>XJUlq4mWdSJEx z(Vgb(8;4OE4xUZ^S_rES^F3xEphlMd4UB}6;*($RKU+-+@(AVuyLHIwFOA_4GfW_V za${~1scn0<2*x_=I7u2$sxl zLU!!GR-zG}t^3YYWs6Y|4m$ZQFoM}Nd3ooP-bxt5tZV!R3!of(Ma1$xdMiAXt z8bg$96qz{mQ{El&;B{0Ve4z3XLW~%ek!|&0!?=IDwuexlKrHSQt$h2O3yPQODT4%N8qTDcWYcVc9;TcU9zD8L^^ocZ!>h z(IS1_LDH>)nh(KB#k@Ti(Cyv5bB(rGj*56HH-7ZRRrR~p@KTD4;FKhY3^>jCB&G0B zF#z(n;3BHklu=C%^-IxF5j<_Btof zgf_}eR2raUHvVCL_ii;xU&7=$_xGW-t4H6>q3fi3F(S9Cy@K*T_nnJoH)^s}i5tm9 z^i+v|EC0@5jkTopxp}RI$Bjd&gACn;hAKPV?tlCA{wd+28i!JU9M?H{y)>>VU>QF7 z^qjQBLgEU5d!NvKFW}}_^(5KT1@EDJM+-uAAADtob` z=C81>=*JwV>+$!48L z{r}kIf6x>4Nxxk*b5+?H5;yw@6aQ-k{;!QLm9)r@s0&z9ID(%Iq1d>`ZI8A6Qa(@enTA2AJVGtRN^PTmsPnwIC)$^VQjBBtI4d zwBSL7l7lF}lM~`I+>&C@(p)k>Ej-(YO}@i!J@}K%&5Xh0Cp__)=+9L^M*fd< z?UTVZKll1jKSkFT(i~Qif0lTx;PD0Rr&C@rO!omklQYl=?{?dM_WI7nB0M@MGM?uV z0;LX|#E(b!%qAK{!{USWYrXtDjfRUD`_s{EpJ?5(J6aAYPj?jLDzMkBsK&tt2>2@))^&R zYINe9gZLSoK{eN!?|aL+tl}$QM3=!*|CFfP-*MuL__G53>}*uU7!q#nW-XB|<>sA# zPgy&2OLbI;O&Cs$EQ+#fy)iSnLk)7tA`8p_|E%yR5A*kxh%-@HfRMr7PDB6^p?+_4 zNUDkG?HL@{=0}&n|Hmv@6C@@A8br(iIGs|Am%nNEIbf5o8=rs?Mk}ga;-sLIWE)H)0EJ25;EbkbWJ+od*a@@R_U023ls84*+AOHX*m{`HL_V<@%6z*|e zrtT7Zj5W_UXTnTo_2eK^UbgydWCV`kTQJZ$C zP;_Q)s_k3PyJl93G~1ZHr^Qx>n?W%XXfY0{3SGDO}uW z)Hf2O?kJMKT+6t2NY2PW5MhEE95StZrP-bbl3Ctb$yf`k7(RSF*1L|wuRc$9ptY~3 zY$l`@#zJ>CR}xyXKRT;W)qn}qHO>aQ8$m=P1d&f*~e(i|~p#ODT8O@&wbn=vHPjyTo zyGFTj4*fBYj>m9L3LbtI;XyI7U?UFl-xn zoT?RJIn!8a4mHvU#xzUi2(^&W(*$-}yu!2Q^+yrrT|8Bjczq~Y^*xfk;zoC6N=nUF zsI#}8R{zayjDNR~zqtR{)|BsQ>O(CQVtlhFGg$3OI21j?GnB58dP2HI$_QZD0AO>>NXn~35t z+ln22D^Hh}o@zoe($0Znj$UWGU<{043AeY{)AiyxKVzdLTgO67e!EtDfZ){Q`3~a! zI*l*wp(O|`u`s9ISi{^a>O}r{r}Ks9V4~&MPb%&9D*Br2xx=f*7uq{UFj1sauetWD zVX=*u1|8}MBXZblNbVa%%VK0=dMHn}G*M3yymU(Wo_}24Pfgu}0Fzm}dPN{#=Op52 z`GV(8y*_dScSQwn0Oo$ZBF4MpM}lMK%2MBUt`Kr`C+j#u+FkS{tA`F>oMV08P)(P)|bUXgW z^E%*a5$CE48-hiJc&_Y%FJj%JO}CtyF7p_hJuTV%vAk#eGn;WuWF^sj5H8|ld0*RQ z^5i5~FmF1arlf^lUG1td_AolwcX$0=!)7k~_b+pY%=E{1SY87XFV`t z`Da$YNo-C1Nb$y9U;a7M za=}Y7@8AT-k^9Q;LmipX0{YW=gbP|Gp%Ujo(?neSP#tvq7_IiuXt*2x{jZtE{3qFj zB=Bnt=OK{QFVFT_n9|U-P>O9|Awy9TXe~zUmV4>a<1kq-m{0M7+%RvhFu!lOq#x3N zMJm5ti>hE(I+Q*WIGXm9zST`j3atw$;kNgp^t@m6`_X`*P(#^ zf@K1-fh^7Eq#X!&F+l6$G0Vg*;}h)H_wNHH++b;E1N-$aOKPl_(V;U2dJ<&`_9>Y$Vi-!U;CX&YRJ-noNdwWR>+_@PBDk39%o5GqBpKi57Z za!V6h`&I^7cmM)?IC}iQFUb$G?> zXvFK@i~?g27*h_R9|`Y~**X)nV2T6N0vS*!EBdL$;bb0$P1<1bb3Jrfy($@LyPA1Z zZB-FwE-llDq-+yMr4r!_pHuD4HID?<_K2!*n?@9YW@mT3n-aL=4maVMp=bo4$>r4t9&4PvEn>t|{ge(P0&2-2>M90S zl81i97~YYuwr3T-tMyMNyt7cLFpFX!^e~y$#O_N(+bnfk0%ENeNfM8rf2t8SNuR4! z80jh~%G>DLOoz*ryy4u)*BDt31m;h-QC~V`uDtblY<5#bgs38257%~L|N&O2^`f`^4>E96*6{<4T zwAZsLUZ)^Ek-+#&6P^8V3$=_YJ;o@1wKyW@Ec`pn#1zn*mbOx~cC*zdVJU<1RI2&` zUb;27^|ga5n&LXo@+m|J0C1oSb> zNYwAhyCbqbREv!WZTZ08WmXo1!kdh0?VUzEW((~*O5qVR@+JL-0QxGS>(G z|Ac_%op$cxTE=n!mVwDrH!KH#Fu=2{@n8C&BpMK!PO{DxgMd_=cP7RpB$X2wh!`mO zc2=Idpr*Q)?3Uj$6=S@52f=wS1BPpCS7dGy<^Ei~q-r={C91s;wb!{qRBl%<$3Ny_&R8@ z&sEH|n=`B;b8vk`&X9|tv(oZzNOcbe5N52W$|Ja^H;)1a=GLjukK4|>O{PRfGtii%_&vh=!b_=aumEb!u*c0%7>SaF}dEgSHbd)q~ z;Ek};$o%aMj{O{N zYG+H^rVz3qAya=71YpA)23?eGqU1!?+s03QEKahRkRooaSEK(J25n}VXM_+Kw$qLK z(&^jqr^CHf_CJq*LmqD-HqQ*9w>M6bag$eYb$m+?6QS!Qe-#nA{MO!+#`+_z_d^=@ z&$`4n@Uaax9bI@6ew&HeQof~yvvYH4!9lTdt4?M4z+gq>CNl$#|9dqChrSip`y^y# z&k^I3>o$WLyCVvZrazy{nr``#_lwO+!Ri+A9A+N6-js)TXT;u zLDs+UNu$tzZnBegPP)`WmT-uXMu6ND8QCESJRyL9c?`qkF<%ZLI&*$h{gB&sHp+kl z?w|G@Th-|k`p|kJB!G6BECuTr1BxNXcrm*&dE|xiu?_5xf9N?0PhTK0%yz*}7|e|f zYV5LrgbfE%=H~ganz*Oo9!`^ki^bzAuM+lGmng1(V+S^Px<{?GFF#Ya)aE`f@Si80 zzcSNzwr#KJ#5Q3!td31CTYN*rwhCeZx5WvOM?a&gH+2oVC?|MT6%pG0AH)5(=2uQ$ zxDqDc=M)G7mQ$%5j`5k6v5A{1P?RMTlr01X5QD+$6GPy;soi^Y{~%W~-I$G!_wg~$ zP#@=iTYLvr`m_Gmih-=|sxHmP)ULQ*>zhy5a67Us8mo)BXvgnCMbdn!lQS#H5QHrg zf)p8GVgNHwXkRTdGH3{hpE{;JOKSH!0pS`7H#9Xh{iUn zox@TljPBENx>rc;hlUzb<=vcyyRc_)3V&gELkeGNey>jdxyb&02!30ouU?Y_=I+gZ zmDx&7?no6+`KoT5S1iD%-c|Xy(f!+BlEC`klHS>m;%>&(d64z~zib;yOm8vnC)ndD zVE1uw(6{$HRd9p3=h#vf(d75_xt}+S?Rk*mN&sYx)Crda0e0~6>|kK4v@F@T+^Gz# zZztjTtOXWB-S*%8{r|mh9K^V1G9R{o2vTsd9)I;2>(_4=>O6ox@u28Sit`CEi|~OG ze~Ejj|BvP#$#ZUhFyg;=Bmm?0H`QH*^RglvhSwGjUa!K%^i6zx;qU^4sDIjD?|WX8 z-~8)4qYu;0qtF2h#I-KU9PvIvAzwJ~kC4-( zupLRg?@i|%@6X^kN{PzQA~@<4-5}lp-S;lgK=w+}kd&oAJLzq1fMud8RfS2qHjR1Y zb{)113T#Ry<>1l=5TI$`DzLmzIDx7zBNq-%0u(JY;|@(EfG4)4dr3rpx@_(sk~-qw z#mN1McKer67HJO=v-PlVvE-q{d4Ff~xnBz$e7;6! z_1ixazL)9^1KH{w9FW)?A`;(Jcw*(c zKa&Z*XT|0WhE7A_^-CXwH-JX~BH_`TgluJ53E_l{$f^L30zPk1= zmkkiV)L0J?D9j}gu*z|_@dv)4E>ept*8uZTR-HH!9E>j@#3Pa3V`qwF<<^~x12;tz za!j_C*~Gb_tseje$|~9JXn?j*Ti5_T#LqOpwm)s`9qD}0UpE+bQp`3t^VD?xN$fgK z(>UGx+qMROns7fU*lWL!NBear|7$e(^#}^6Ts&iQuUz#lZd*$(fa||txl+PdH?>zA zY+gC_m7hu^ zsgQ4JN{kqi66F$Jw%1%?sZhU6k)4{#XbR(|2P-H>Oc3 zWfmE@zuWuprV>fyu?E*=zN^LGzQINQ8+J9F%Df7(+A~ zoyOhhdY!+Lp4~imJp4ku?z6B5q5-g&oM6U57%=aPE;CKeN)>~G?UlUm-^vSt^!>ko z?)iQ!BiCTR_;b7>SIGy($(02bCwg9k8*Yg8Z+O}S;nuhyI}ZYYomvlV%_olOG64sZ z<$8YiukgzfE0uf`BnccNNMQlOywJyhRfAZx@HyZ+Ru2(~9t8u+r~x1chzVRmb$llW z0R!MLOk|C9pCLcsOY>c|UL6vKDsbX|AbyJLO^TV1&gh1#M#Z2k`7>OqZ4UiBcehG0 z1Bk~(PT5I+xIbZk0o+`BcNNcqeD`+=alYaF=@ix9BiU4nIl1kyw?9MO!Q49szAo-h zQx`AU$9a8T*m&jVleqM>S_mNkg*C=v0l7c*`7i7IZZY#$6qen4+Ten1w+S47N+jPC zLRW3|Z@u!o&A$ryddjn#9Q!YOTqmo_tBVy)c8j>awB~-=R96l_7Z;>L5JLz1bHUZ>pF^`6!nqez2>1wpbapm>2z+`gvvlnlea7`xyDt_${Tj~0-~aTRXGjM7 z{aJZJhABtoP}hM_qaCcT>bhOMKjW9H9jPP$U~dVXH@tiGNT4LKOpwt42NaM0a_&VncE|xo(z!%sWHH?jz@= z^{Hn9^yb}p2>PAK7;-iueo((N>Nxx){Lh`{mPv`>m+#NwNOZH>fQZb1{;%BL2<=a6 zxWXm;j^(|3sJ)nOLh@Zlih=C=Z#!}soDY8i!wq4Ob8WuIG(nktiRKYBg($)@C*Ua3 zj=YX$jzFYzKbCiDGV0*CkohRHJ_V-<$ zh0P^h5+xZ6C<_^X?B3E)#^_s!<*y(c$3{$3@G1sal5=|} zOTH)|0a)b@>&-HMG_&;3f@iR~VqE|f&i?+hpT8cxFUsSbzu61C*_v8 zKZerc@z~)Apb)kflXjHeeLpmBl@Kxz;1GiWTykt2O9wH3fcOx+`;G6DS9oF$8*+oa zuP^M0n_7HFsWx3n9BKXC$e*}pC%P*=wbw=7`&%!#Y;CiSw33HtxYXF+cCghOXZwwU z8;qni#5My5CNhjGC9k4;xS5-H(e(OS{iHMW#U&$~cJ1hBXXodpW$I)jq$6S`KjDx1 zAqYW?V*%NJ0KzAf+H!Jk2D~1U7$D?ac7gNcpHpuM`FN9HMuofGm%RU$S(`z9hr!_W zU-iLgcxsZSJYta#6C4NH)NB=Y?+mfff#Wx+)&J_2u{A^jLJqK_Nhk?IKw_w24Jg7$ z1fbC>xTyq!P!dRSRv$V&b1W=4cg^@*W&;HFS>4C%~ix6Bdp)kLUqP5 zQ4gkNb~`|yQDb_&@*tx0ei`DZqG zLJ16iVt~sQFa{CRu62M1fh3}G?XWfVn7P;emT$0c{`ZSp@RPXxJ{KKpi;Hla*MGtDS2SsskL1WE-o%VHy0205DPx2O#j=@-tVBhwb#Y>7Jd<(lT{dAAdko9y0B7Fk%ysRSY#C}i^f+|ALaZN@3H2RMAF!I zEW3h&e3y&p%vB`c)Gw4r``m5($C1hZjITz1TsOz+0&Fbka~$LJD^(Nb{W4h@XPF)X zHn=A*L%X4r+7v;WDW<$Z_C+kkHAlODIX!>oTZcOOciRv_cMru7VmMLmW65rJxt7S` zL<8h7mJ%P$F7-Yv#>7@5t7<{Cmv>BzV(-hf*1xz~Hz=@?>#t&uZ^HIhabLMwQiHUz zK9gLLgZGt5z{03YicLE0Gyg%=^=VxJ;7TVE`rTp2*|O;-0wnoBSAm$A)Ys zl;T7WX7RyWZ^kf2oRkbyVQNh(|XxsQd0qyI(a4W*EOt3rW*3HL#YG)4dp0hdInAM4*b_3YnXXWLw8FX`%o zpH?9}2w6iA2hN9pgd^5(sm2G2p6ms#uABS_@eBqq*6P1`p<0D^&DGCjJv`qa1R`Gt z4R_O4a&`4%2f0+{tbfFj>uq#6y8KYZs8DArk_#Z-$rTzS3I~NC-3Ew%2P)hWv?%c{~G=4Bj8Bo`TI5dH-4VWIDb?m5nyJyGZR zSs8pwJ$A~Sc!dkT*y?Z1!yft(qxYXod&}~`WCL>?1CCP!GI$Dqv|N+e!-bzMCeu%)<>`fKZq&8sPueOt{w@=Lml^_+v#yNf9i*44BDO@sIMvvSG-pFz`p z>@~3VA9zc#^#~Gw6+~fMP5~NF6Up-nG-EPEc@^M)x1W0({=bJ0yv(K!gKOuIta^^n zHeehRwO|qp9&yosVTD}7HSJU=x!t_oEkHhB>Dl@3MxrAlLM%<4pYT3a`&B|*Pg2zf zbi-OAY6MMPa>Lvx2#AsFK7&ov+Ph0}mW^q|4?^m%QI5-hYMy{VP6PO`2I=amSzc)# zQ0t6D4zgqH$nVyyjt`)(*Xd{-|eA!32Hb@fC<4#0TC~( zFx70Kq@O!FSd}9FuFI&(?ROm|puzBbueOO`iP97aU_yc|a9GRsiQh{M-^D=JYa}0|KCXj zm;7^olckBsGYab&xiO4nQAG56${%~j%_Jk9vM0|Kpo0{)LZup;1pK2cnVM3ynr%g> zRnRpt#YiFib-3x_FE^!EfG^W~q*tOnqDH@mA^YI7k0!r@0K@4B?y&e)-v|qbk8kPm z0pHSsxErpNH|cA8)7kk_R7;o0CX?v>%Ws2!tgXX$wwr#)2lVrLj&l9xc=&occ%zxt zwWvr8#i}Uz$O3~goS-f1t1vc|poX)x8J!T31NBQ)CP@*r+k z=H#98D9@*fdL`9WQ?|B2_W!TcoXjaZxH*7rODk)AVk^+71&yG<)JlMGy|Rr@h5C_y zYbaxpz+>nbq(@6d?|Rhu+#YM@MG!(cR~T)n^_^wh5Dl03A;oe(Vm{{t#AD0i>!{@= z86^AdKynX0dB<>Ri(e{UFiJdHlyVI+mBesY_m>kwgjAAi=lxSPowxzud6D~-sh{PMAfM3y&K&e=2%bJqf zK5y~Wlfv#<)^(V9Eqs&ZWN$Tp^SH>< zxUC@vY=*^!-IAVC!{l9uKH@Fc(h|B8+Gp34Vi2a!aZcfuc%e2TC#}*@%vO4ma#d+8 zZ?{|EIkHIvlq8TzmY^(jlrg_UI$(|96h;H!P|k>B=+)vcYPDI8f1CIO?XUUA#)#Vh za0CnC|5163FfAfaB%T|gv8(V%7-@T*33FL<``O(*c;JQlznyoiN2IM>_TM_ zx%OlbJ%|M?+5Bwd3Zn`MJx;Y1w~!ECMBc!F1p`XR4^Qj+mSzaPps=wliEqM!6qvXU zfmm4w*@ymi_jHzs(hnjU`(aZDC&(xtQ~~*&x8AXJQJ@%*TM`mK$>vRewvFNR`LKcp zJb8X+w)06KHxK{-ZbiDq#k#efFkw{|OpHH^nE(Q9_?ezhwKX?)I z_W(QS!iYV(N4BM+Sk556^e36kamNr~FraC#7>U(ou6S7ogVO*|s;b~Y0D=i97#KnX z!u2YF9OZHppU^g@f`R>i$PW}imZbs#iXb3i2oj%+P#n?y@I9#S_Y<&_b^Gpego(BE zSvM@Ex9)e!fynSjg-Lk%gaifXz}Q)U4kJ^_-!+x}PMAQ9VilTr=%>Q~)B_KxJ=4C) zti**XA*x~aKPKb8 zL$U?|!`&itEnRbR27(6TkLJll(KP&CAsxTcSq%f~z2vFWqqE)9(ZOOUOMJyobzI5Y zF>j&IG)Dwo|AyRu-nT7p8(ZLims3Zh69^?YU@?pcu^aj&%HLu_<1vz}XFm56gDNf) zrGB0ovG|QVTu7Cajwdm6*t&qS?|?wt41wcRHfzsUB~2C?AQIU!kPH1$!p9AbGLrl2 z4G;_m7pDx_q=ltUTB8rx;jia(EauQY$Jc9wYc|mO)%egA7Eewt*fA)4hibe-c*(<4|Cw)Xv3#RIF0EhFE0dV#}WTafL(-}kx$#s#tNAatFH0UKV=-WwK%gHbfvD%=9YH}>d zQ!XsfvSJ{BI<*&=DrhPROjdMK{8qSjFV{&bw$%?XaCUZY_Ldn&K?0J8ztGt|!^I1I z{p3J)RQui3yRs>YCpvB`p1X`g$v(<&aYB&5K&>Px&lZTh-ZhuJbtK5xx`=CS~0WO6qT{ZtV5ZWBKuw_xT;No(j} zR)iKNd-9MJfJRPF+=wo&_5S1rNud>Ixu=Uo>F9IGwX;Dbp+zap9~l>KDHUj5c+Bom{Qb(E_FOuJ2wX%o|5){Z ztMs)v>7K2)>x2oxrkA6{nz@|@BedpeGTphrN_kF%VjEp+XE4ofOi@v3j6~)j3O4m@ zx$kwMUg6?1cT;YvqxXb-=2TTNjxYV1^c z{smZo5;@YK1Wssny?O*hf!t7>v(*#1jy@)A0pm@vujUBDadleTNd9}IAg4xewVa1z z1b5Wbd(UfO-Fo)jz_ZD-Oy?n+paYvt1C^zzD7cz`d_1y{ zFC8FyjFUUhYACnv16xmlO2E8A5bq$R0?o%D?Kja0m@Q8U!T{;le-~e7$0u~K6n3N5 z^+b~G{>3r3h5}e-sh`L2t~Qizu#GubQEX3d;Mh)6@|K`5uZ4@fq%~mQLHSh7;TITq zAlz-LSRege%R2U=kV*(MN|hylF9=5w_8Oua7+RAs>)W`o>u2u&XNm;Vdq5kT-S|Jb z5fKp%@9Y?Swg6C|GL56^IpOr{Igp@z%{S!C0G>SelNS|M+Na`q2TdM+XZNEC6TukR zdhDLnoDw2`#~8LiQ3L+4 zaP&vja?Z=eMo>pY0<)Btshf>e#{J=cqJ(|Dbv$D6O5`!$3moTSTw#3huFo9LF$7Lo z5~$W`I|u{tTxjMj6Fit=xip0Pu+vd@6&|?@LWD@7>qVwh&u!t+8iWvhhFeq~Jg6lJ z1g0bdwcl6i>u%?xm(zBCD7T$T2SJC?R}3)%7&iB+K|26+v)OZKBsR>|HX3N_?^}&Z z!Gif@Ay~yc;#$<)PbdOb5DMLvg*(YWeSz}fshtde)88;h1NxTY(^7V~t-DLpdt}s^ zcZvYaAT$LY$2+tEt{5ai#^%|^ys~uigfep~AksluF$mcYXU=|q;(nG03?ubM2R2bM zMIaa{aFMsoM1XAyQ7Q-`1>P`gJ0_rE1fe9LDNWEPnWE07*QL+;se2kv2x@%R1nfq= z*nk*i3?Ts!2Yq<}pvq2PkRl+eDBk^}(A+^fCB?6*?4X~Ao=JO ze+71m3~H*iEFC_&9b-yxN6{3hITTZm9H|QYfDExt)nx`oY$4A=*gjv=&RnH<;mBg4 zpb^KRfuR)!W|QFzdaS4CnP3Q)7}e`TFKFogkZDi=_g|b1k~DFWU@p)Bf?V;)WcUKh zHb(>oPNxum#i?U7=5Bl1J-mq|RS@<@QiiofJf2~d&X_QSQ;ZLA!MikQC<;;Vgpm^h z+_B#m2Wi}3^H2WXCB*Ay^?3UU*483>DCh_QOrnDoOdGxIy6^pw%e?tRBO(BmvliDh zrd40-taS$fTfgkQ_t|}UF^9KR=E3VxkH;Gy&nK9F<2z#+4kEtxX$OB?`1nEUHu-rY zgOk<3?f5Y4%s+6w?}yt&TJZF`?-#EB$2CMgE<#K;L2bHFXdb~r-4n_f(v$AvSzlf1 zKVs+xZF?SCgMld1UVQS;$YCratt03-qkL1?`D8I5HH18He0H0meRe*U56`kuxIt^k zvk*pqqq!b4>+c+6*Cr)J*!P-72K4XrXs|9m)8Kq7F6;vYdg$CemcuZ~sNBit((pZ+ zr%#8&nsN!2R$by(}Hk6H7CB4*1 zwFex}=>j_ceT2BM*ma`=N`FC5m*2aVq&qQx>hb&@`zrQl+m}B3AB^f}SX5Ra1Of>q z5DYD}Wt~iGW1&5i1Zk~t3Xn~Zfj6U0ZKd_PxQhh9y(mI}IoIhTL05t^m;&u`nY{m% zk>^S%VyVX)Yk8Xeb>EI_`BDYCXUS2bH4PjX5YPG8rn<}1pC(9~Ujv+UaQQifvGy{5 z8%$1(<>K8S?AkjbhS+WCvAm5i|W)Z9wzZd38|7RCWq^$!=3osP;nt^ANhj6GlT+}3#mMF4k?EMxJD zlnem;Z8fJ83(1nlY1i*g&?vZj#C3mvfRlflg#O3~E9^tlX1Kk`0ToBFp))*l?&r{j zdlc{!_%si?f6nx4RP93^^qe}QrH}%``oS-X;rU6`An=s*9ng->dDX?^h5Q%HUIdi` z!xNX^{+&g3>;79J;eQ8tk>l=UJ^slcG81jcHK3rZD2{3x${)#r^Pr31cE>6Guw6qwn!DUlkB+{{*nfxLe4zTaLNNX>Dwc(WLB9ovKwrcB)xX7%a9qe| zR_Cvfb7^l)amT?>{%0Sc_sY`pRVW)6WtWOf3aBjDgB`L@O!%T~<0(i}h4KtI#|x{o zoE4#gk+gbKc~aPM0~ibdQ)69!R+|{taMD@xjnC}5aQ+5&HnLd>0f)^;2;G6LbTzEw zp3rt0PBPYLao^17@i)W`!018s=(lXc!TfFq+;u! zh#nLG5xig3qfgtSmm#Q+KD6fJzMw*iInz-vgqMXhe5U8w%3cmrwv9S69?b*>o1qtRCrQiDHZ~bT=_CkPRLVFoz zbGI*-c8Z1v1M}j)^Gip6pI>g{yNCJ|Z<4MB-7{9J0!Z5+U6ISfyCxkG7y$}>U>U`L zS-+E>S?To1Homisig&FP4c9qdJ5d0HE1=%FbIrSVqiQEl{t3NR`Q3$V9H0o{Q`J%P zD9Q-sg?ph8gAub5fug||G8DwYUP9uae1?D?_4u3th56zX58d#8-(t5(1gs`QJkYYj zq~puw3gm{H?3|xxE7#1-k9j2u?LRuN-|XCo8t$BDEcJv>-Lm_@&j`3wc`TfS9%q2l zJR1%$lR9E0V7lp{#ogp}ZeWBA;QuQeg?i?FM?X72r?>M}Vh7%f()ri8dI6@SVLlgE~ zu*D9WCDNQ+l@CAf#%IeLKJnnFvP=&H469fh3I@(FEA3Ly0)I^S-5;QaLWvLM1&~q;H17$Cf^;d9?Q=3X%;jaCgNx@ zj3M6J9xd#D*#ymUpl|r>q7cYEO()b8;mp4ESY9IneBPmh0hk$g?Kip>icAvm#10f< zlRY~w9@go5X?1{~$-$Ak_j(Yq!=2r_&W-ogjnw%yxXBjTUSe$eH6s{gZq73-)sYST zv`zBfdJpXun_b!1@ut^QKtmCdY!EegqCPD_h6#dyIdOgeQac6D{({M2Ces(aG1ggL zu>@SKNW=wdaY0$+8G8!xOc!JQq51WXiI$9fv|!9qHLpOp0HYJ>7)G?kJ4rBe#6OGS zGqwP^XlyZ&zKR|c_gkQgTXie4?oGw(~`V}*J9TRi=(pUdDY^C+OpW@=l( z{V%|Oa$DsNAwO}g#i){b0-a`htK{vwx{h7dZ;i`Bp+24B|Jx-RZ33g|R5>fg=%_C= zX`-D_+z!1(2+#zxzprcayvxoTD>5=AH}M~&$jF2l26j(~K2>NaBjrU&l?o;?@0yOP8ig$*Y*p`*I`2`q!4XRf=}b|=~6B@x_sE@>Ul0~$ojPUnwE=u+;Keo{5r zy%!d@9g5&p0;z6R(qV`N=zHCiPy<;Bk%mXBcv*^6M|8>?AK$i9tMQbKeiR)_ggcXc zFCiyrf;E}#WYIq##(tCJe&2rk23}Zy6y-zAdy*RX9-L6BYb`Fq@5`7M%-9Z6q5v;~ zD9R00W;9X!Y~ilPDQ-NMmSDIl;5dpll2zq9%HPf4F@wa6gldpepaObw%(nj39o@%P z{Hbt8mc74*B;EUXws2rb;$CZ-s2ANB-2^*yM!250N&;j96A5tbmUoD~iN{NS{(iT+ zrF3ftT}M1~#D4aUK*E+V_xlt|Qk9F4_1QtbLxE6aMmNF17&@fju^|Q+fo<|vE>~Hqv zul;A4$W5t(PQhUs;-+D);d$bOZ!K05An@xMgCQ4nZ5P~koI?sHP*TdBT4WK$GS16` zP3A*@LqSCq(5kTn@7~5CD0x0jfH5LB)ao+$eG3qE(iB24TyKx(@4D{a#JP%`in`WU zmP!PSOVp${pYRxs#MZoj0>fFv$2((L6q=tpOW;~Oh@=;f7h|AjQ>pc*ZG?+Oo43zY zTZ+tth-X^NMJNq-Q&X|jURb{a%BT)etVFbGghmuq3S3!e&;Yb3`SFkuUc?n;@&c%) z@t75`<0F0w-5s6-5y+alVBiSo+h(x4+VgT9FuED8Z z6)!Uztfswp&7b34GWTkd5TX!-DI$;xWxG*@`Pwu!cW5+G=s^(tduPeLl!^yof+g~c zIM-wUt9W`4!f%iPYJ#GPG>5ajciCmaNl(-q?MIE-jNnER&19t-!N*M?%P=Pyds2bV zFT367FJo|&^FDNc%0Hb8xWXhhhAGK0%iuEw6j4q$8p<|6N=p9Ci^`CvXW^B@9}RU7 z#jeoA>J)l!*Mw-;^uQ4Gz| zGedqs&37yP=q<6>$0>$|x3wtxFQ|dfBjQ-{`(qVQ!H6USw~LD)<+n80)1N`d1fHni z(c7d9dTO-PS65e8;y=PQ^E;pixwlBGDRO~RnjH_;TOEOwPa20gCb!W`Vo?99!!VbkNh zar#&yi!!-5JiJj^pNBLX+uNGWcEQ`jB<;BN)e?<=NF;lE{Z#nyPN`3^k;6Mdy5;w_ z(b?l=t3S3KxVvm1IoLA>3&vw=^u4l>F_X&7j7BFl%r8>I!i0MWSjnvsn6;NPar%A| zwPVB{YaRh$hxNJJnR;t*R1L!HD+5{+mVRx`H8%r((AxYI&ch)ThDan9Hf;<;#RicW zSlS$aHkifDMz}jGR-^qFc+<_%Y-?jF;PcRnx6f@eK-=&j1XZSa+WcEwQ!|2W%R+{v zUEw0;^U_LdbjW zA}0Odo}qy=TxL?Kxo0Tg6oc&T8;ZWxT*18mS-{Sp{V@f&A)sHp%09+3`EcuIQ0I>%5rTPDkx1h<+KydFa9uM(g1pxmt?T4sZ=_-u8?O!JDh#+Zy zbQ`vQ6f{AO)0`H5h04WSR`Xor!WKoS>iI%r88hEAG+kx6w6kbO>g zXtCMv(=Dw^YezTjBBTUm(yC`gpQpOUjg^9jmWFo>J>6ZSI1*DMhXz`5r3`d`*uxbH z$6UMA*2Ut3ay=mmUp5i?qv>)5+s;92a_5{_EQT$|oW1|)(R?MA1VQD0;I3|#mrM*x zYc!IMPNxS2VL4ry|K}wjMLB9Se*K~>ejID{bS>`o76gFIk^Q%?c4i6s zD${}m+3=!Q9%~xI+_XVxsPZ_J_EwcqT9YBs7&=7Y{s-&h z!#lYBOF$IX^x3d6sm>w+SJUIr>I}Hx4%WVz1<|4Ra1qnNup)&R~hxsDg zK1^t-U;;p0*K@jUv!#ZA5z;Uco62T%Z@+KfZkM4n0m6d`kut@p_13FS@q)Q?2LB<& z&76yZlXUUm#(nG~zptB{46?k9OjR#ywOPQB;bE%0Z@i7>f{&-Z`9<7TJ-1yqLahix zD-@0F1_73=%uH1DHb-v#mGuyaks?Hi5)?yKRaI6bSra76l`>?1`P1#2fLM$c^B`^E zhxb4L39A2v?Eb}m<3DlLP0lChsF2C%)12O zc4L0IR(+AxcZSA)gaj(t7B)E73p7SKPU$Gpu^8C~1O*O(e!GYnQfI9h1HcK~g1x5@ z)v&sFQ{?odZpI)~!kHyh65u%mgNh?Lls-6HWjUsobEj2m8hJmI-Bcq8u5I_8{NA zqvjb3tHvO%7uebu6GIAi+t70SQPvdhNsJt}Tr4-}Dz0!kj8 z$i>O~JAc8e)!{18K=o#t?KmDNAOU*B0p(A-VM1&KKs+~=2qFY7a0~)aX|VmixkU$O zvm+cL!xv%CTB8^c!8KZdzp_yG9tO}3H=YE7K_N1KuV$o#3KB((z|StIKBP1?FrtSs zL$eWC1hQb|#YI(6kU$aiQB`mOdd_zVi5(!JDvaSxP%3%=jwptDQDg4eu8JVCcl^Od z;SeM10$FP_)1l@;EMJjSU@|s20Y=zh^*wWxa$q2FmA%@4@A-&91SkVYy?~Ey{nX2& z*`ox1jbiEyfhI!lZhg?hS0wG!p9+Drfp8TwVN?R1vcXbRXFkAz>w1}i!$go>JNoD%Xfe+eN zWlI_J9`zB?vI7hhaQesaL#q!EczKx&&d1V!O!Qz->zrz=BOOsSvvuoe@x`&4@bQ^3 zg><1gZ@hD5Q2*locorV5btKhwp3h;@-f|uQ@qor4+spj!otP^Z&Qe0S6^CUVZ5l@! z01>OQcb!)Q#qmy)S&vJ%o@zM>4`6DSxfXS=w*FSa3(ddx-?^p)4PgTsIPq8nfCJfo z_7%ZCBnDOf6?UlL8Ff~Yt6pI?5UcCLyN1u@ta30}Kfa$rAkQ=W<+ClV?_V~xpM1t{ zh13qTSI?sm!Y@WpfT0XS0`#Nuh<(w9X3T-A|CEQEEh7g6XFO3m?ACxsT$mNk_LS4k z`~H8_|Ly(NB4LEt+%qs{V%d~aa`B#jQVg1AzV;*z@e)!9z(Qp0R16siz|>*{MjqXB zml^@`BgW?uz7`b%l;Q{AZTZVHY~=`9p%eBkDPfd+Pda|TxQ9$;qvf+R!%QU%c7~Lp zpBm<~${27HS25_Uj=1KI6U?8(TE#lHdEX+~;NR(Z7C94o7fZ$>nN}DpLkJ^(!Kg6= z?)0Sg$Bs0ESZ;1IpEz|0b=UYG5N&PQPynV8Y)QzYqivpqn1^oI zw|^FWIC}L5q^?u=%CEnf$THeKWpqm6goCQ|sY>-{=IubgP(+b5ZM>NyDW|ziY3}yg zmJz50NDnZt^w;;=xo_wG^nN6NPrPxWh^#62Gw#bjbvh4=qvLr^7ff+7w0qe)X~^2; zMJOc@EYGL$;O->j@O?(bz$X#*d-634cv8S15gJ1mj7i#u$BQsAj6TJ3mZ?JP+T5Ei zMsmYZ?TJLdXK2v!L$9gbGxcFIjG=o+e|rh41sA&lU$=Ei?>AV_qpzEPKKd~@fZ4vW z3yTf&1GI=+!RFTzdHcNZm|L3DfoA1vBG>8RyzI6uC_=~OK1V#Q{0|S!P43nJiZ^q+ zXtMLrTL7{it$N`#HEDyfR2~1bp^y6UG_gVREY#ab|0V1GQci!$(J>1&00L7$?T|L> zo=Z$9u14mIBWq#u%XX)K;kR!t%tnnQA9$j6#c3Au-+?hi$;lO{E@TCIR2-Yl;LMN@ zHh?PRt-8M#9uuGS=$Xj|Xt$pBiJAwTI60C2+6TkqV1Oc(rj3v*D)s2w;<7&Y5z{gG==-H@$y29Xy>SrJUy~{}r z0Xu+DIu;I{A;B5{DGB5|_M=Kga+gk#i6^{>AOLu!es)eK{*I1b_l1nE+B-_V-uifB z5CB;W3NZ-jGfEuDQ>V_Q+>u5P69BdJo!?ovMeW`y`M$MMAl{5SmATlp!*L)wmPv8g zj@E2~%Nsj39wQ!qbl4z8j%=|_VTQV3g?QH_6ODr&=ht>?J1%gA0Ag%|H;{tplvZ@{ z?D7&6-DS3hsspihsM7#$KxKs>E9rg8y2l0lgkXXwKiD8@@faJiH6SAt!=t?orK0Ar zT}#CB=IlOx?(yktSO_697`g_pLlAt5;4lJ<9ofQoI#?cmhgLh7gJ#x+j=>bbes}5V zF}(!sgX@mKgAj*a<|B6JCEpZQbD!sD0H1uaA3i@g<~5N zPHat#iEZ1q?d(_++qNdj#GZ+5+qQjkZr!I_b@oHQY}EhMy?gb)zU8XP@Hvb9*uJ#o z%{xdVu&{lk9hyf#RmxTTJEkZZmXWH}x<`U5eN{!lGr`*bzfNacMTkI(9>d(f^fI6nJ246zEKuF!}Zqy z>49QoVNM@7j3y{9Dl2-m8Rb*#_Vm?l8HvdkrxQCijU+;_ss0vE_eF9=!~3|GbS9`* z0aEJl7X+lJTBLGygCO=x6!+rTTM#$rNV5&8*|NptOd^J0CEKq4Mg4#g$fAbO0Wq|x zPJ9xC9lVT*EBM}8V}bpsPF3>y%`3|k1KjP#7LZRT^fzPIn<5-cByk~{1*rAYt z>k2{ve?eo(`!>l;ZzYl;08h{YBP3%@06@mso-_KZD|Zl5sLRr})&|c+v!9fHzr3jQ zrHZxju}AMi$KLiAUxSoT6;f3B5}ePMirtx2i)QvRro7o#{bEVM>DRl2hqiPba`Y!# zR+|*SEQrbW+tx~8xTLSQN7iT%Yz*CU%Te`+kmnT9&M*u?mLda8gyK(KT?noW0PKG~ zxrr8CDVoA@F8u<;aB6DWjkpjeHIt+)Ubjc^vYJLR3Im8lbTEpb)M2V3LNHY6L}jQL zAUmxVm)G1V#3;kxctzx6?`>D+$8i(;z);b}1cGhEF1UOay;=SHk&B-#TZBVu$6|JA z^5W4+xX!gO*_(3LO|9o?*sf7DfT>EbEph>QBC;NaD~tD`sD(!AwWoM1T?s*NQKE1P zZ65Y-5Ae2Ec2yy<^;#`5)rteX-K&oh8GvEQ18{&ZA&RNfiL~} zb#Rg^Sk1f4HsOTFZe08)gQcFvvgyN9#VP+XrYH`6Kburf9Hh*CkD~KEkP)VNArCh% z+LuRO5+qB!B(xRt@9#KwFD{5_W^kxSw(n|(oTnmMp&i(@0BP=$9fFGJ29v0)*=9k# zL9Xat?^F8u_u8Wd`S5Drdy}Bw$r~?Yb|t${rX-N7P&_c;-3%n9DQXxOk#3kwP~=ev z5d>UYwV-UIh<>{TTj0z)xNa>vFn!C;1uktSS8w}jZQNUJ{P)&+h33u1>yNAG3+G_@kT{$fb#&mIEy zjj(@?IL5!zz!rZHpl!$md-X+g#(#Rk7kBa-23H{bUJ+Y}33E)g3AlqQDtEOW1t?+1 zlJX%J1vb6t2v?d7;y}6tF3WK?)zzSjDaBY5qDKv!Fduv;+jWb;=~lgHmT=)ifDg!- zYu97PesP5hSjx@2&SedUu=Vn@uYIm2eG&!|qJ^l7)p1`iz=Jvj4GwY&?NC%WuG-T~ zJ*}C7ygquqKJH4;097|ugiA1l#Cx@Zi20U*2}iY$^N^d-ZjJ^Q!b&@6#8uMOl0nI& z32BWNx60dZ#;I}JTWqhoF;jG1zg1<`vbL^rTfV76ghj&G{@Y&pp|iN}a##lUbtpO; zOh<8Q@Mo?Yk9<0KU*x&>tohyl*{DoEq#sXbb43bkf#4y&i23 z;UprJ=82d(*2Ac+$2B~9ch@f1?N;wdxlVc15!;{}0Qw4^u6zJvuI8WRU_++BUbE}e z$)!aeZp4sap5wp92@*(n3HcJ4inupKRDBRXG7PXaVa-+360aR1R#mSxg+opn$|No< zxO#cI^zHbD2yZRUCW5q?WGrfrD)zUy`mNgf?-K-XWE41 zu4U_CO$4V70#{A=pCBVgy|Xh~!M43x)FeEa^9&ndciE2!3WT>oY8CBxgP1++bwSVi z|CGttZ0SiMRf(v-{)xf|*v3hqw;>rB=nWVp0QTA2nSsJ3RKaqOEzCdxP*nXO-GC%I zwSw~eBei{zN7WV>Lj}GGf<$N1?6)5erQ7bN=#+T|G4c+# zp+gzQq!#AlTKzF{F53Lu7OVk>0fx{$H2xwO_iz8K|nGQWF zK=wM>F3MXr!TcOSR^fDA%;-n@W6;;^`G;5Z)BT$P+>04f5M=&n<>~Z9_knYIpNiMn zf;Bxdq&_fyu+Xz;&l-;7{G5r2THSImO3H$>d!(>)yQ?Qn`T7bi0D6>de=*faDHz#C z<@5Z&C;&oi0B-L8Dz*qsio=>FP|aBjOfmkFU7am7%Wl(!&smDoG|b1QSdXm?ta#>> zK4j~X!&R~-Dfe*s6z5%(+K@ap>VY%8nsN&-hT*>Hh70iV5ej^)EC=ote|KscHOHrc zoQH2cz(KSu_;G8$P(XiPP)9tzYUj8yOKI=~rS9|ajhX3rNnXxwL%73I9WbK+Zm}=~ zkDR!Oy`k&-KC63*3J%Uw>%3C~O)9QD5=4dfM@%G+c$v_!v?QR23AlGxv{7auPwE38rWRZ{BV!wwQuj zzlVp1M;sg@q?gzs;vz10k&r~dY(bpS7*(}>00WLI4|E)X;VEb%beOOWgDMzeL`2=w z(|w3h39&HuvM+BqyE&z#MyUwpG$1+m)6geir7K|HG&mp#seQh zTP`l03E#1-hk8ivGbvYV72i1*Rt3@Z-10+qzoA&hO#zG^!|k_x-Q)uBZ%Cjg?>*SV z`O^u?_sWBYQEiJ#<2c={FQ2m%9XCDNsr~hB^xf_P`L5&PMHMF|-4h4LlZ%T(U+f38 zEZOHs$F(i1CN6zuikw_STs}MwVstpujW>bEjP5>}Q0=!B!8kI!>k2)N44)??x+k_X z+(P5yI8ms_ROPhsThA_lUzQVW>qt*Cz;9oL-?iEzF~9q+@>fVGB!YkiwzpG`=U@JU zVs=4PFWs`@&RZ_*pVyTJa{M8g2VPYQffAD#c(ke*5uoU_w=@tl0K5FfbDBP4YB2w< zX6{`^lx_@SiIV9hN4|%8$?v?(Z84IrZHt|YiXRr^t|`GxlMq zMYJ-+rNZ@UG8#%?tlQyUTuQK6?AouIN2+uT%lJI^E)Tu7Vx;}0!+Rj@g}3uY>!5xy zo-G3pq+@!c1rK73+R2V?pRu|1DrmqDVuG@=S=Kqg-4)7gq3-12+tHH^LsFjc*3OYS z%hJMWO`k#UbPn&`6!t~FE+l@qD)Y zS^i@*{a}!LXhy^aiKDe{&-?A*Dm3|Ywc-kRLs5t2E+Wso`T{aa&4gmXJ2Tz5PoO;h zx1Syp<#Cb> z0ulZcdS%G*3u}bf@2>o*P_V6}tO5j_JpyR2Hz0xEi!}{>)?>~0ZSnqwkn?@(0_%E( zmyD12RTRW5C)$f1GvfO&l-JD9^hvq3`k zJI!dx;N_GNzXTEaz(9C3A*cs?7^DE+Z&^t2r@SlaURbG3l^@hDeg_4YIuTAW=^n2i zG_I9(458H{@g>JC%2^Shm=%;)~iy@01|UFA$h93W2VVG}S&Q`dMOx zxD;UkLtVZID~I#bJSMLaA{NP`yQ%ClwrdjlH_ENC*xmF3T1y6QZ@PLtVIVKs%mE3aU7pSzzG8ARLqQQkV3fK5M>SF8( zZiZ){OQ^OP$`)V}tRT|OD=a8TS4C07gkZAZ@;BfZvNqUUt!zp^7O0fX)N>fSdlbZ@ z4Iy+tTNt2JqzQUZ^NAoQEu@iEGi~zMow>_kMBc#JkA8#y!C=pyh?2PL^vK!4w;1U5rhtwzx;sdN z?5AP5fv%;xdsih_Ti3LGG1RrCl#;5gb+tNc+Ugq#VqGWV+{xG1*GF630i?ZhsZ+Vx zwmR)fSF`Q4REGUE?u3hri~Wt15N)kR-LImmzlu_8tf|(I#ms)Bo;jtG`?|f>9=JWM ztU-H7EigQClnCWYZ;c%##uR0YJwW=+7DOg}M77!`L9MacRn{z-j9{UGL=pl`^2;lq zbrBz|jgAhkzWH8N;||dMvdMNGSS73Y&($=nDw3=s`CkVkOf};F|3D#1T@ul=kdmtA ze}C`)K4^^6ofUZSzcn&pM;ViIHdX`H`o3S3^I>vptwoid7-@nWoa+BuS-4qY3_K)J2&{OsD{eO0ig6Yz}(-xX}hvYaooSNr3nexuFr!GnjKj9_S0Xx{w zV11m-pt@PU^XS?9O9wadQ>IrrF=!?avyQ%hKjQ>b>tLpnz@k>`gtOsBZ*Ku4zQSB4 z5fji-hG+$Ikk({2ic*=ugj1&uFt!zXEK2kF80<8E!y!SP3?f!4R5T&3pW;S04&-`a z;vm7>ZfIt;P+v#=#m`jmpkx7=NX8IkR163TGvvkc{CV!iN#jOZwy}1QBA>Bs%_HQ9_;mzM*`Iae!*Ix=e6@+#~62ReDAl8H2 z&9WOT4}{Xa?{9TJ6#rCW)NX1x8`2-$jjy^|867AWV|(yd^haPJx(WlZVuigE4VAx+4AKUYCP&aDo1+8FK6{5$%3L! z!$S_v%_N5`o_eWDqlxQ5NeOO2$!}<}ORT}&3EsC$yGyxt>|Wwr-QmJ`i<7O>H)Y%* z{g4fuO_!Q73Xjz#Xna7n|Bm(u<%Gq{wzggyGeu;KVD2cO-y2`)I%{v>cXMrq(6ygj z-_`*ts2F3W^$&@-#oUOb*wf1U(s#M2vX1Tr_fRG5$Byw$LinPen*xw7Ym51QPLlHGs$2R0JeJK59V49dXuOe%SSroe7UDS&hT zE`$4iGt<5c{%x|zojLF@a|yXR#^sG&$qw6Kh|)1n+I|T%@Lv6#sL=c875PxBqw5t*U;NRD8~dt`;igp}d1Jv3hVky!F3NIy7ZWkg=L^TBHk~n65|=hv z2E7zvS!5VcR50KY4?mqpIfSK?oQ>sPm6um08wo9551MWttis$tPe=va?0(}y{25N8ur`WrFRaJva%*=QAIp7x35B5Ig%pxaiodz_JDl|2PwdrUoyPAqr<3Q- z*URTU|DHn*A*d`sDQ!AA;n#j(DdMy|o(OvuzWgs1)gL2Irk~QPNulS^x>9{SF0AxV zJi?e`n(8&C2Az>jsI_uxkE(H3sLe@nmraXUt<6k;er#jil1Sx|akd)AQ;OFTEp~p66?n2ub9mIk`T5K@=fX>r8jv_Ge?8V}H_A8#~p?qLeC8Lnv?q=VfR^DE;6+g#!yLR?ncQ-wDZNrqT#LReK7hL&7 z7QAdPp*fS1{T~DQ;$?ZQZ~J}@s=7@~I&59}tiFDr*WyA+DUZ;}n>`b0EUsdwNF2xF zvH+(6u^fUJrB!Hh*3@z~bda?+g~87H=bRu=1eNvHgWSTEV4^hcWA=oo!@Mvl|Uiv4H z^LCRl+tvo1s6S4k#Z#$vJPLpNO58;OgMt@*mHYkb1U>sq=yZlJ1Qx_J33NASJPFpj z&T6jHEFM}Q4+Tb!*Kf~i8jMjNMkCflU+|w}`5+d5q=B7`YLSEj+4IL4srM&6972y< z&f9hXgI`ho)|HOwv#Qo)UxTBkbeuEb=;{^DU69p#LUyi|_r+k?BMPt&Du`zXwsLRx z5cY?VE|>+vkx{rg(-&qPav->f60@j^`AGfl43ZSs;+d+&pMlQF!<^!?Uf0^x*Vm0$ zWO|T&py)D_Odu5@*y$M^=LW0hSO3Lo%^@7ik(Wx2)35MyB5H`kK4#>I(-7bmda(@T z(Ib)(e}g~;XPD#~j06J&UPT0dqS7(P&j3Jmxf@vROVSL=ETH`TvUJCZ(UgFb=?EcUC;=u3_R{M#T#CraKgw%p z@Zq>0)TRaoLLvzlT&O|?5iAfcj$gJ`C=C{nuA(y~3Mu%O^RFobIuZagy7!6IOcImJ z4xo0^dw`~v|2X!o8Tj-6YF2C zdkD71D}g-$8xmRFz?q%i#NVe`aYMJ$tF4}jptln^CSxdK>dxxl(}?%(>7B^lyXT+> zd_@L%)|a2v8xn3!+gX6>QjqV}qPqlUA(U3tcKRDG@A!(nYP*8F?Vd7TyGmo4wMUw- z*NB!BK3nnjb7gQx)bm(0FlWe(!CqZevB$qBT$c~ zAdE&R0UN8jnp~0+QKOxcQ(2^C?Oa5$1Dxf}KXPK>UM`Y|6o^6y7;P9)K*d|$W{;e3 zcR>X?t|e&1K!Lwz{KB}W;ock0Lpz^UpKDZHol8xHFGGLQBAGQN%u~nmf-PVUADQ}( zo#_qg=3_eeJ{Q}a#0?sy(i6_m&B%Bu3`3~SjYIe*xeZ5q60-Yoy>1hcV^Sr{yIxOIzDP6 z##|TPU1lv-TX-Fr>Qj~=D2~IGfj@qcQh+E|U2#f){JC*Np$DByobpDe6ZT2qLoP(D zeOQB^o%Tijr?$fFYVPsJ}Q%0_lIu^SSvPT$#&p+_c5UZu|RwGX!`MH)Au6-4_Y_$#t}20h&h^_d@&& zaW3GdUR0uY(BaSD6hZ$`a0RBgtl>Tv!Q~4h14=_L8hU1@uje8!vY5A>sp{z3xlRX-Z_R>OoBRQJdK?koIw`f@e0s|wMm8wHEF)2xBz9|( zGI#qrl^Q8#@NGDQ_31>%JzLigEceBudKW;DwM10Y(|q^=u|3MOt&@40-@z2O@dteY zGx%;$x?S&|9bJ(t!lQdh9tTze+f2rOF++?ffv^Mvz8ChfL+M?^5YqZ?{R>UMKSuya z$u~+Un}y3kv2&^C-I~ti*>_tO;Os8rqo~$j=R6-=U%i>u;rGB3qpw)gCYguNsf}GJ zugypbuf#Qn0xgasdU+(v&-66FJLMed{)apXh?HDSiOgA|Mkm_B#OrTK`I*TDLSzQ5Q^h_@gcT{KolM zTn%{ne~%Un(8XJs+hQZ(wdpx^2>EO9Sz*WQ7B-=Q1S0*;-8dJvx=Z>AcV7iJsLFGi zZAzLm-{6pTYdq$u)QfzW7``v~O4sZmLm^-K{qOUw-zdRHr7%l$#BeX%-Yf=yznTkm ze5!UF67|+SUV1GLH|~9IojD6Cc4}@KJmIgN05tBQ@g1JjdFz^z+B2KWrD4K(?nd0^F za)QmKW&Vsn`(^d@lHt2dMYtTrzT`^CK8PFX|Jbut&z&r4B5PZFW>mlU%4TpgH zIU|!EYr6zIn(y=GfqtkTjok`MLlw>@qq1CWm&eU(oDij?6BH&Nas`d+8-(Au*gh8u zI-0Q2Kc!KYWQj>Hg|^gzF6r-~aU=U8xSq1jL79p<3⁣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: color: "FFFFFF" : @@ -61,36 +62,42 @@ found_text: "Found?" TooltipLabel: id: receiving + sort_key: 'receiving' text: root.receiving_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: item + sort_key: 'item' text: root.item_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: finding + sort_key: 'finding' text: root.finding_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: location + sort_key: 'location' text: root.location_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: entrance + sort_key: 'entrance' text: root.entrance_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: found + sort_key: 'found' text: root.found_text halign: 'center' valign: 'center' diff --git a/data/lua/connector_mmbn3.lua b/data/lua/connector_mmbn3.lua index 8482bf85b1a8..fce38a4c1102 100644 --- a/data/lua/connector_mmbn3.lua +++ b/data/lua/connector_mmbn3.lua @@ -27,14 +27,9 @@ local mmbn3Socket = nil local frame = 0 -- States -local ITEMSTATE_NONINITIALIZED = "Game Not Yet Started" -- Game has not yet started local ITEMSTATE_NONITEM = "Non-Itemable State" -- Do not send item now. RAM is not capable of holding local ITEMSTATE_IDLE = "Item State Ready" -- Ready for the next item if there are any -local ITEMSTATE_SENT = "Item Sent Not Claimed" -- The ItemBit is set, but the dialog has not been closed yet -local itemState = ITEMSTATE_NONINITIALIZED - -local itemQueued = nil -local itemQueueCounter = 120 +local itemState = ITEMSTATE_NONITEM local debugEnabled = false local game_complete = false @@ -104,21 +99,24 @@ end local IsInBattle = function() return memory.read_u8(0x020097F8) == 0x08 end -local IsItemQueued = function() - return memory.read_u8(0x2000224) == 0x01 -end - -- This function actually determines when you're on ANY full-screen menu (navi cust, link battle, etc.) but we -- don't want to check any locations there either so it's fine. local IsOnTitle = function() return bit.band(memory.read_u8(0x020097F8),0x04) == 0 end + local IsItemable = function() - return not IsInMenu() and not IsInTransition() and not IsInDialog() and not IsInBattle() and not IsOnTitle() and not IsItemQueued() + return not IsInMenu() and not IsInTransition() and not IsInDialog() and not IsInBattle() and not IsOnTitle() end local is_game_complete = function() - if IsOnTitle() or itemState == ITEMSTATE_NONINITIALIZED then return game_complete end + -- If the Cannary Byte is 0xFF, then the save RAM is untrustworthy + if memory.read_u8(canary_byte) == 0xFF then + return game_complete + end + + -- If on the title screen don't read RAM, RAM can't be trusted yet + if IsOnTitle() then return game_complete end -- If the game is already marked complete, do not read memory if game_complete then return true end @@ -177,14 +175,6 @@ local Check_Progressive_Undernet_ID = function() end return 9 end -local GenerateTextBytes = function(message) - bytes = {} - for i = 1, #message do - local c = message:sub(i,i) - table.insert(bytes, charDict[c]) - end - return bytes -end -- Item Message Generation functions local Next_Progressive_Undernet_ID = function(index) @@ -196,150 +186,6 @@ local Next_Progressive_Undernet_ID = function(index) item_index=ordered_IDs[index] return item_index end -local Extra_Progressive_Undernet = function() - fragBytes = int32ToByteList_le(20) - bytes = { - 0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF - } - bytes = TableConcat(bytes, GenerateTextBytes("The extra data\ndecompiles into:\n\"20 BugFrags\"!!")) - return bytes -end - -local GenerateChipGet = function(chip, code, amt) - chipBytes = int16ToByteList_le(chip) - bytes = { - 0xF6, 0x10, chipBytes[1], chipBytes[2], code, amt, - charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['c'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'], - - } - if chip < 256 then - bytes = TableConcat(bytes, { - charDict['\"'], 0xF9,0x00,chipBytes[1],0x01,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!'] - }) - else - bytes = TableConcat(bytes, { - charDict['\"'], 0xF9,0x00,chipBytes[1],0x02,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!'] - }) - end - return bytes -end -local GenerateKeyItemGet = function(item, amt) - bytes = { - 0xF6, 0x00, item, amt, - charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], - charDict['\"'], 0xF9, 0x00, item, 0x00, charDict['\"'],charDict['!'],charDict['!'] - } - return bytes -end -local GenerateSubChipGet = function(subchip, amt) - -- SubChips have an extra bit of trouble. If you have too many, they're supposed to skip to another text bank that doesn't give you the item - -- Instead, I'm going to just let it get eaten - bytes = { - 0xF6, 0x20, subchip, amt, 0xFF, 0xFF, 0xFF, - charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], - charDict['S'], charDict['u'], charDict['b'], charDict['C'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'], - charDict['\"'], 0xF9, 0x00, subchip, 0x00, charDict['\"'],charDict['!'],charDict['!'] - } - return bytes -end -local GenerateZennyGet = function(amt) - zennyBytes = int32ToByteList_le(amt) - bytes = { - 0xF6, 0x30, zennyBytes[1], zennyBytes[2], zennyBytes[3], zennyBytes[4], 0xFF, 0xFF, 0xFF, - charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], charDict['\"'] - } - -- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it - zennyStr = tostring(amt) - for i = 1, #zennyStr do - local c = zennyStr:sub(i,i) - table.insert(bytes, charDict[c]) - end - bytes = TableConcat(bytes, { - charDict[' '], charDict['Z'], charDict['e'], charDict['n'], charDict['n'], charDict['y'], charDict['s'], charDict['\"'],charDict['!'],charDict['!'] - }) - return bytes -end -local GenerateProgramGet = function(program, color, amt) - bytes = { - 0xF6, 0x40, (program * 4), amt, color, - charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['N'], charDict['a'], charDict['v'], charDict['i'], charDict['\n'], - charDict['C'], charDict['u'], charDict['s'], charDict['t'], charDict['o'], charDict['m'], charDict['i'], charDict['z'], charDict['e'], charDict['r'], charDict[' '], charDict['P'], charDict['r'], charDict['o'], charDict['g'], charDict['r'], charDict['a'], charDict['m'], charDict[':'], charDict['\n'], - charDict['\"'], 0xF9, 0x00, program, 0x05, charDict['\"'],charDict['!'],charDict['!'] - } - - return bytes -end -local GenerateBugfragGet = function(amt) - fragBytes = int32ToByteList_le(amt) - bytes = { - 0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF, - charDict['G'], charDict['o'], charDict['t'], charDict[':'], charDict['\n'], charDict['\"'] - } - -- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it - bugFragStr = tostring(amt) - for i = 1, #bugFragStr do - local c = bugFragStr:sub(i,i) - table.insert(bytes, charDict[c]) - end - bytes = TableConcat(bytes, { - charDict[' '], charDict['B'], charDict['u'], charDict['g'], charDict['F'], charDict['r'], charDict['a'], charDict['g'], charDict['s'], charDict['\"'],charDict['!'],charDict['!'] - }) - return bytes -end -local GenerateGetMessageFromItem = function(item) - --Special case for progressive undernet - if item["type"] == "undernet" then - undernet_id = Check_Progressive_Undernet_ID() - if undernet_id > 8 then - return Extra_Progressive_Undernet() - end - return GenerateKeyItemGet(Next_Progressive_Undernet_ID(undernet_id),1) - elseif item["type"] == "chip" then - return GenerateChipGet(item["itemID"], item["subItemID"], item["count"]) - elseif item["type"] == "key" then - return GenerateKeyItemGet(item["itemID"], item["count"]) - elseif item["type"] == "subchip" then - return GenerateSubChipGet(item["itemID"], item["count"]) - elseif item["type"] == "zenny" then - return GenerateZennyGet(item["count"]) - elseif item["type"] == "program" then - return GenerateProgramGet(item["itemID"], item["subItemID"], item["count"]) - elseif item["type"] == "bugfrag" then - return GenerateBugfragGet(item["count"]) - end - - return GenerateTextBytes("Empty Message") -end - -local GetMessage = function(item) - startBytes = {0x02, 0x00} - playerLockBytes = {0xF8,0x00, 0xF8, 0x10} - msgOpenBytes = {0xF1, 0x02} - textBytes = GenerateTextBytes("Receiving\ndata from\n"..item["sender"]..".") - dotdotWaitBytes = {0xEA,0x00,0x0A,0x00,0x4D,0xEA,0x00,0x0A,0x00,0x4D} - continueBytes = {0xEB, 0xE9} - -- continueBytes = {0xE9} - playReceiveAnimationBytes = {0xF8,0x04,0x18} - chipGiveBytes = GenerateGetMessageFromItem(item) - playerFinishBytes = {0xF8, 0x0C} - playerUnlockBytes={0xEB, 0xF8, 0x08} - -- playerUnlockBytes={0xF8, 0x08} - endMessageBytes = {0xF8, 0x10, 0xE7} - - bytes = {} - bytes = TableConcat(bytes,startBytes) - bytes = TableConcat(bytes,playerLockBytes) - bytes = TableConcat(bytes,msgOpenBytes) - bytes = TableConcat(bytes,textBytes) - bytes = TableConcat(bytes,dotdotWaitBytes) - bytes = TableConcat(bytes,continueBytes) - bytes = TableConcat(bytes,playReceiveAnimationBytes) - bytes = TableConcat(bytes,chipGiveBytes) - bytes = TableConcat(bytes,playerFinishBytes) - bytes = TableConcat(bytes,playerUnlockBytes) - bytes = TableConcat(bytes,endMessageBytes) - return bytes -end local getChipCodeIndex = function(chip_id, chip_code) chipCodeArrayStartAddress = 0x8011510 + (0x20 * chip_id) @@ -353,6 +199,10 @@ local getChipCodeIndex = function(chip_id, chip_code) end local getProgramColorIndex = function(program_id, program_color) + -- For whatever reason, OilBody (ID 24) does not follow the rules and should be color index 3 + if program_id == 24 then + return 3 + end -- The general case, most programs use white pink or yellow. This is the values the enums already have if program_id >= 20 and program_id <= 47 then return program_color-1 @@ -401,11 +251,11 @@ local changeZenny = function(val) return 0 end if memory.read_u32_le(0x20018F4) <= math.abs(tonumber(val)) and tonumber(val) < 0 then - memory.write_u32_le(0x20018f4, 0) + memory.write_u32_le(0x20018F4, 0) val = 0 return "empty" end - memory.write_u32_le(0x20018f4, memory.read_u32_le(0x20018F4) + tonumber(val)) + memory.write_u32_le(0x20018F4, memory.read_u32_le(0x20018F4) + tonumber(val)) if memory.read_u32_le(0x20018F4) > 999999 then memory.write_u32_le(0x20018F4, 999999) end @@ -417,30 +267,17 @@ local changeFrags = function(val) return 0 end if memory.read_u16_le(0x20018F8) <= math.abs(tonumber(val)) and tonumber(val) < 0 then - memory.write_u16_le(0x20018f8, 0) + memory.write_u16_le(0x20018F8, 0) val = 0 return "empty" end - memory.write_u16_le(0x20018f8, memory.read_u16_le(0x20018F8) + tonumber(val)) + memory.write_u16_le(0x20018F8, memory.read_u16_le(0x20018F8) + tonumber(val)) if memory.read_u16_le(0x20018F8) > 9999 then memory.write_u16_le(0x20018F8, 9999) end return val end --- Fix Health Pools -local fix_hp = function() - -- Current Health fix - if IsInBattle() and not (memory.read_u16_le(0x20018A0) == memory.read_u16_le(0x2037294)) then - memory.write_u16_le(0x20018A0, memory.read_u16_le(0x2037294)) - end - - -- Max Health Fix - if IsInBattle() and not (memory.read_u16_le(0x20018A2) == memory.read_u16_le(0x2037296)) then - memory.write_u16_le(0x20018A2, memory.read_u16_le(0x2037296)) - end -end - local changeRegMemory = function(amt) regMemoryAddress = 0x02001897 currentRegMem = memory.read_u8(regMemoryAddress) @@ -448,34 +285,18 @@ local changeRegMemory = function(amt) end local changeMaxHealth = function(val) - fix_hp() - if val == nil then - fix_hp() + if val == nil then return 0 end - if math.abs(tonumber(val)) >= memory.read_u16_le(0x20018A2) and tonumber(val) < 0 then - memory.write_u16_le(0x20018A2, 0) - if IsInBattle() then - memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2)) - if memory.read_u16_le(0x2037296) >= memory.read_u16_le(0x20018A2) then - memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2)) - end - end - fix_hp() - return "lethal" - end + memory.write_u16_le(0x20018A2, memory.read_u16_le(0x20018A2) + tonumber(val)) if memory.read_u16_le(0x20018A2) > 9999 then memory.write_u16_le(0x20018A2, 9999) end - if IsInBattle() then - memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2)) - end - fix_hp() return val end -local SendItem = function(item) +local SendItemToGame = function(item) if item["type"] == "undernet" then undernet_id = Check_Progressive_Undernet_ID() if undernet_id > 8 then @@ -553,13 +374,6 @@ local OpenShortcuts = function() end end -local RestoreItemRam = function() - if backup_bytes ~= nil then - memory.write_bytes_as_array(0x203fe10, backup_bytes) - end - backup_bytes = nil -end - local process_block = function(block) -- Sometimes the block is nothing, if this is the case then quietly stop processing if block == nil then @@ -574,14 +388,7 @@ local process_block = function(block) end local itemStateMachineProcess = function() - if itemState == ITEMSTATE_NONINITIALIZED then - itemQueueCounter = 120 - -- Only exit this state the first time a dialog window pops up. This way we know for sure that we're ready to receive - if not IsInMenu() and (IsInDialog() or IsInTransition()) then - itemState = ITEMSTATE_NONITEM - end - elseif itemState == ITEMSTATE_NONITEM then - itemQueueCounter = 120 + if itemState == ITEMSTATE_NONITEM then -- Always attempt to restore the previously stored memory in this state -- Exit this state whenever the game is in an itemable status if IsItemable() then @@ -592,26 +399,11 @@ local itemStateMachineProcess = function() if not IsItemable() then itemState = ITEMSTATE_NONITEM end - if itemQueueCounter == 0 then - if #itemsReceived > loadItemIndexFromRAM() and not IsItemQueued() then - itemQueued = itemsReceived[loadItemIndexFromRAM()+1] - SendItem(itemQueued) - itemState = ITEMSTATE_SENT - end - else - itemQueueCounter = itemQueueCounter - 1 - end - elseif itemState == ITEMSTATE_SENT then - -- Once the item is sent, wait for the dialog to close. Then clear the item bit and be ready for the next item. - if IsInTransition() or IsInMenu() or IsOnTitle() then - itemState = ITEMSTATE_NONITEM - itemQueued = nil - RestoreItemRam() - elseif not IsInDialog() then - itemState = ITEMSTATE_IDLE + if #itemsReceived > loadItemIndexFromRAM() then + itemQueued = itemsReceived[loadItemIndexFromRAM()+1] + SendItemToGame(itemQueued) saveItemIndexToRAM(itemQueued["itemIndex"]) - itemQueued = nil - RestoreItemRam() + itemState = ITEMSTATE_NONITEM end end end @@ -702,18 +494,8 @@ function main() -- Handle the debug data display gui.cleartext() if debugEnabled then - -- gui.text(0,0,"Item Queued: "..tostring(IsItemQueued())) - -- gui.text(0,16,"In Battle: "..tostring(IsInBattle())) - -- gui.text(0,32,"In Dialog: "..tostring(IsInDialog())) - -- gui.text(0,48,"In Menu: "..tostring(IsInMenu())) - gui.text(0,48,"Item Wait Time: "..tostring(itemQueueCounter)) - gui.text(0,64,itemState) - if itemQueued == nil then - gui.text(0,80,"No item queued") - else - gui.text(0,80,itemQueued["type"].." "..itemQueued["itemID"]) - end - gui.text(0,96,"Item Index: "..loadItemIndexFromRAM()) + gui.text(0,0,itemState) + gui.text(0,16,"Item Index: "..loadItemIndexFromRAM()) end emu.frameadvance() diff --git a/data/options.yaml b/data/options.yaml index 30bd328f99a0..ee8866627d52 100644 --- a/data/options.yaml +++ b/data/options.yaml @@ -45,7 +45,10 @@ requires: {% endmacro %} {{ game }}: - {%- for option_key, option in options.items() %} + {%- for group_name, group_options in option_groups.items() %} + # {{ group_name }} + + {%- for option_key, option in group_options.items() %} {{ option_key }}: {%- if option.__doc__ %} # {{ option.__doc__ @@ -65,21 +68,22 @@ requires: {%- elif option.options -%} {%- for suboption_option_id, sub_option_name in option.name_lookup.items() %} - {{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %} + {{ yaml_dump(sub_option_name) }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %} {%- endfor -%} {%- if option.name_lookup[option.default] not in option.options %} - {{ option.default }}: 50 + {{ yaml_dump(option.default) }}: 50 {%- endif -%} {%- elif option.default is string %} - {{ option.default }}: 50 + {{ yaml_dump(option.default) }}: 50 {%- elif option.default is iterable and option.default is not mapping %} {{ option.default | list }} {%- else %} - {{ yaml_dump(option.default) | trim | indent(4, first=false) }} + {{ yaml_dump(option.default) | indent(4, first=false) }} {%- endif -%} {{ "\n" }} {%- endfor %} + {%- endfor %} diff --git a/data/yatta.ico b/data/yatta.ico new file mode 100644 index 0000000000000000000000000000000000000000..f87a0980f49c3cf346af8c288bab020eb77885c6 GIT binary patch literal 152484 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 HCB^>%Zp1Qi literal 0 HcmV?d00001 diff --git a/data/yatta.png b/data/yatta.png new file mode 100644 index 0000000000000000000000000000000000000000..4f230c86c727f4950a5c6e6b62b708b4d3d12f8b GIT binary patch literal 34873 zcmV)&K#aeMP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 0 HcmV?d00001 diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index d6730b7308ae..ab841e65ee4c 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -1,38 +1,46 @@ # Archipelago World Code Owners / Maintainers Document # -# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder. For any pull -# requests that modify these worlds, a code owner must approve the PR in addition to a core maintainer. This is not to -# be used for files/folders outside the /worlds folder, those will always need sign off from a core maintainer. +# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder as well as +# certain documentation. For any pull requests that modify these worlds/docs, a code owner must approve the PR in +# addition to a core maintainer. All other files and folders are owned and maintained by core maintainers directly. # # All usernames must be GitHub usernames (and are case sensitive). -################### -## Active Worlds ## -################### - # Adventure /worlds/adventure/ @JusticePS +# A Hat in Time +/worlds/ahit/ @CookieCat45 + # A Link to the Past /worlds/alttp/ @Berserker66 +# Sudoku (APSudoku) +/worlds/apsudoku/ @EmilyV99 + +# Aquaria +/worlds/aquaria/ @tioui + # ArchipIDLE /worlds/archipidle/ @LegendaryLinux -# Sudoku (BK Sudoku) -/worlds/bk_sudoku/ @Jarno458 - # Blasphemous /worlds/blasphemous/ @TRPG0 +# Bomb Rush Cyberfunk +/worlds/bomb_rush_cyberfunk/ @TRPG0 + # Bumper Stickers /worlds/bumpstik/ @FelicitusNeko +# Castlevania 64 +/worlds/cv64/ @LiquidCat64 + # Celeste 64 /worlds/celeste64/ @PoryGone # ChecksFinder -/worlds/checksfinder/ @jonloveslegos +/worlds/checksfinder/ @SunCatMC # Clique /worlds/clique/ @ThePhar @@ -55,9 +63,6 @@ # Factorio /worlds/factorio/ @Berserker66 -# Final Fantasy -/worlds/ff1/ @jtoyoda - # Final Fantasy Mystic Quest /worlds/ffmq/ @Alchav @wildham0 @@ -65,7 +70,7 @@ /worlds/heretic/ @Daivuk # Hollow Knight -/worlds/hk/ @BadMagic100 @ThePhar +/worlds/hk/ @BadMagic100 @qwint # Hylics 2 /worlds/hylics2/ @TRPG0 @@ -82,13 +87,13 @@ # Lingo /worlds/lingo/ @hatkirby -# Links Awakening DX -/worlds/ladx/ @zig-for - # Lufia II Ancient Cave /worlds/lufia2ac/ @el-u /worlds/lufia2ac/docs/ @wordfcuk @el-u +# Mario & Luigi: Superstar Saga +/worlds/mlss/ @jamesbrq + # Meritous /worlds/meritous/ @FelicitusNeko @@ -131,11 +136,14 @@ # Shivers /worlds/shivers/ @GodlFire +# A Short Hike +/worlds/shorthike/ @chandler05 + # Sonic Adventure 2 Battle /worlds/sa2b/ @PoryGone @RaspberrySpace -# Starcraft 2 Wings of Liberty -/worlds/sc2wol/ @Ziktofel +# Starcraft 2 +/worlds/sc2/ @Ziktofel # Super Metroid /worlds/sm/ @lordlou @@ -171,7 +179,7 @@ /worlds/tloz/ @Rosalie-A @t3hf1gm3nt # TUNIC -/worlds/tunic/ @silent-destroyer +/worlds/tunic/ @silent-destroyer @ScipioWright # Undertale /worlds/undertale/ @jonloveslegos @@ -185,12 +193,44 @@ # The Witness /worlds/witness/ @NewSoupVi @blastron +# Yoshi's Island +/worlds/yoshisisland/ @PinkSwitch + +#Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 +/worlds/yugioh06/ @Rensen3 + # Zillion /worlds/zillion/ @beauxq -################################## -## Disabled Unmaintained Worlds ## -################################## +# Zork Grand Inquisitor +/worlds/zork_grand_inquisitor/ @nbrochu + + +## Active Unmaintained Worlds + +# The following worlds in this repo are currently unmaintained, but currently still work in core. If any update breaks +# compatibility, these worlds may be moved to `worlds_disabled`. If you are interested in stepping up as maintainer for +# any of these worlds, please review `/docs/world maintainer.md` documentation. + +# Final Fantasy (1) +# /worlds/ff1/ + +# Links Awakening DX +# /worlds/ladx/ + +## Disabled Unmaintained Worlds + +# The following worlds in this repo are currently unmaintained and disabled as they do not work in core. If you are +# interested in stepping up as maintainer for any of these worlds, please review `/docs/world maintainer.md` +# documentation. # Ori and the Blind Forest -# /worlds_disabled/oribf/ +# /worlds_disabled/oribf/ + +################### +## Documentation ## +################### + +# Apworld Dev Faq +/docs/apworld_dev_faq.md @qwint @ScipioWright + diff --git a/docs/adding games.md b/docs/adding games.md index e9f7860fc650..9d2860b4a196 100644 --- a/docs/adding games.md +++ b/docs/adding games.md @@ -1,269 +1,78 @@ -# How do I add a game to Archipelago? - -This guide is going to try and be a broad summary of how you can do just that. -There are two key steps to incorporating a game into Archipelago: - -- Game Modification -- Archipelago Server Integration - -Refer to the following documents as well: - -- [network protocol.md](/docs/network%20protocol.md) for network communication between client and server. -- [world api.md](/docs/world%20api.md) for documentation on server side code and creating a world package. - -# Game Modification - -One half of the work required to integrate a game into Archipelago is the development of the game client. This is -typically done through a modding API or other modification process, described further down. - -As an example, modifications to a game typically include (more on this later): - -- Hooking into when a 'location check' is completed. -- Networking with the Archipelago server. -- Optionally, UI or HUD updates to show status of the multiworld session or Archipelago server connection. - -In order to determine how to modify a game, refer to the following sections. - -## Engine Identification - -This is a good way to make the modding process much easier. Being able to identify what engine a game was made in is -critical. The first step is to look at a game's files. Let's go over what some game files might look like. It’s -important that you be able to see file extensions, so be sure to enable that feature in your file viewer of choice. -Examples are provided below. - -### Creepy Castle - -![Creepy Castle Root Directory in Windows Explorer](/docs/img/creepy-castle-directory.png) - -This is the delightful title Creepy Castle, which is a fantastic game that I highly recommend. It’s also your worst-case -scenario as a modder. All that’s present here is an executable file and some meta-information that Steam uses. You have -basically nothing here to work with. If you want to change this game, the only option you have is to do some pretty -nasty disassembly and reverse engineering work, which is outside the scope of this tutorial. Let’s look at some other -examples of game releases. - -### Heavy Bullets - -![Heavy Bullets Root Directory in Window's Explorer](/docs/img/heavy-bullets-directory.png) - -Here’s the release files for another game, Heavy Bullets. We see a .exe file, like expected, and a few more files. -“hello.txt†is a text file, which we can quickly skim in any text editor. Many games have them in some form, usually -with a name like README.txt, and they may contain information about a game, such as a EULA, terms of service, licensing -information, credits, and general info about the game. You usually won’t find anything too helpful here, but it never -hurts to check. In this case, it contains some credits and a changelog for the game, so nothing too important. -“steam_api.dll†is a file you can safely ignore, it’s just some code used to interface with Steam. -The directory “HEAVY_BULLETS_Dataâ€, however, has some good news. - -![Heavy Bullets Data Directory in Window's Explorer](/docs/img/heavy-bullets-data-directory.png) - -Jackpot! It might not be obvious what you’re looking at here, but I can instantly tell from this folder’s contents that -what we have is a game made in the Unity Engine. If you look in the sub-folders, you’ll seem some .dll files which -affirm our suspicions. Telltale signs for this are directories titled “Managed†and “Monoâ€, as well as the numbered, -extension-less level files and the sharedassets files. If you've identified the game as a Unity game, some useful tools -and information to help you on your journey can be found at this -[Unity Game Hacking guide.](https://github.com/imadr/Unity-game-hacking) - -### Stardew Valley - -![Stardew Valley Root Directory in Window's Explorer](/docs/img/stardew-valley-directory.png) - -This is the game contents of Stardew Valley. A lot more to look at here, but some key takeaways. -Notice the .dll files which include “CSharp†in their name. This tells us that the game was made in C#, which is good -news. Many games made in C# can be modified using the same tools found in our Unity game hacking toolset; namely BepInEx -and MonoMod. - -### Gato Roboto - -![Gato Roboto Root Directory in Window's Explorer](/docs/img/gato-roboto-directory.png) - -Our last example is the game Gato Roboto. This game is made in GameMaker, which is another green flag to look out for. -The giveaway is the file titled "data.win". This immediately tips us off that this game was made in GameMaker. For -modifying GameMaker games the [Undertale Mod Tool](https://github.com/krzys-h/UndertaleModTool) is incredibly helpful. - -This isn't all you'll ever see looking at game files, but it's a good place to start. -As a general rule, the more files a game has out in plain sight, the more you'll be able to change. -This especially applies in the case of code or script files - always keep a lookout for anything you can use to your -advantage! - -## Open or Leaked Source Games - -As a side note, many games have either been made open source, or have had source files leaked at some point. -This can be a boon to any would-be modder, for obvious reasons. Always be sure to check - a quick internet search for -"(Game) Source Code" might not give results often, but when it does, you're going to have a much better time. - -Be sure never to distribute source code for games that you decompile or find if you do not have express permission to do -so, or to redistribute any materials obtained through similar methods, as this is illegal and unethical. - -## Modifying Release Versions of Games - -However, for now we'll assume you haven't been so lucky, and have to work with only what’s sitting in your install -directory. Some developers are kind enough to deliberately leave you ways to alter their games, like modding tools, -but these are often not geared to the kind of work you'll be doing and may not help much. - -As a general rule, any modding tool that lets you write actual code is something worth using. - -### Research - -The first step is to research your game. Even if you've been dealt the worst hand in terms of engine modification, -it's possible other motivated parties have concocted useful tools for your game already. -Always be sure to search the Internet for the efforts of other modders. - -### Other helpful tools - -Depending on the game’s underlying engine, there may be some tools you can use either in lieu of or in addition to -existing game tools. - -#### [CheatEngine](https://cheatengine.org/) - -CheatEngine is a tool with a very long and storied history. -Be warned that because it performs live modifications to the memory of other processes, it will likely be flagged as -malware (because this behavior is most commonly found in malware and rarely used by other programs). -If you use CheatEngine, you need to have a deep understanding of how computers work at the nuts and bolts level, -including binary data formats, addressing, and assembly language programming. - -The tool itself is highly complex and even I have not yet charted its expanses. -However, it can also be a very powerful tool in the right hands, allowing you to query and modify gamestate without ever -modifying the actual game itself. -In theory it is compatible with any piece of software you can run on your computer, but there is no "easy way" to do -anything with it. - -### What Modifications You Should Make to the Game - -We talked about this briefly in [Game Modification](#game-modification) section. -The next step is to know what you need to make the game do now that you can modify it. Here are your key goals: - -- Know when the player has checked a location, and react accordingly -- Be able to receive items from the server on the fly -- Keep an index for items received in order to resync from disconnections -- Add interface for connecting to the Archipelago server with passwords and sessions -- Add commands for manually rewarding, re-syncing, releasing, and other actions - -Refer to the [Network Protocol documentation](/docs/network%20protocol.md) for how to communicate with Archipelago's -servers. - -## But my Game is a console game. Can I still add it? - -That depends – what console? - -### My Game is a recent game for the PS4/Xbox-One/Nintendo Switch/etc - -Most games for recent generations of console platforms are inaccessible to the typical modder. It is generally advised -that you do not attempt to work with these games as they are difficult to modify and are protected by their copyright -holders. Most modern AAA game studios will provide a modding interface or otherwise deny modifications for their console -games. - -### My Game isn’t that old, it’s for the Wii/PS2/360/etc - -This is very complex, but doable. -If you don't have good knowledge of stuff like Assembly programming, this is not where you want to learn it. -There exist many disassembly and debugging tools, but more recent content may have lackluster support. - -### My Game is a classic for the SNES/Sega Genesis/etc - -That’s a lot more feasible. -There are many good tools available for understanding and modifying games on these older consoles, and the emulation -community will have figured out the bulk of the console’s secrets. -Look for debugging tools, but be ready to learn assembly. -Old consoles usually have their own unique dialects of ASM you’ll need to get used to. - -Also make sure there’s a good way to interface with a running emulator, since that’s the only way you can connect these -older consoles to the Internet. -There are also hardware mods and flash carts, which can do the same things an emulator would when connected to a -computer, but these will require the same sort of interface software to be written in order to work properly; from your -perspective the two won't really look any different. - -### My Game is an exclusive for the Super Baby Magic Dream Boy. It’s this console from the Soviet Union that- - -Unless you have a circuit schematic for the Super Baby Magic Dream Boy sitting on your desk, no. -Obscurity is your enemy – there will likely be little to no emulator or modding information, and you’d essentially be -working from scratch. - -## How to Distribute Game Modifications - -**NEVER EVER distribute anyone else's copyrighted work UNLESS THEY EXPLICITLY GIVE YOU PERMISSION TO DO SO!!!** - -This is a good way to get any project you're working on sued out from under you. -The right way to distribute modified versions of a game's binaries, assuming that the licensing terms do not allow you -to copy them wholesale, is as patches. - -There are many patch formats, which I'll cover in brief. The common theme is that you can’t distribute anything that -wasn't made by you. Patches are files that describe how your modified file differs from the original one, thus avoiding -the issue of distributing someone else’s original work. - -Users who have a copy of the game just need to apply the patch, and those who don’t are unable to play. - -### Patches - -#### IPS - -IPS patches are a simple list of chunks to replace in the original to generate the output. It is not possible to encode -moving of a chunk, so they may inadvertently contain copyrighted material and should be avoided unless you know it's -fine. - -#### UPS, BPS, VCDIFF (xdelta), bsdiff - -Other patch formats generate the difference between two streams (delta patches) with varying complexity. This way it is -possible to insert bytes or move chunks without including any original data. Bsdiff is highly optimized and includes -compression, so this format is used by APBP. - -Only a bsdiff module is integrated into AP. If the final patch requires or is based on any other patch, convert them to -bsdiff or APBP before adding it to the AP source code as "basepatch.bsdiff4" or "basepatch.apbp". - -#### APBP Archipelago Binary Patch - -Starting with version 4 of the APBP format, this is a ZIP file containing metadata in `archipelago.json` and additional -files required by the game / patching process. For ROM-based games the ZIP will include a `delta.bsdiff4` which is the -bsdiff between the original and the randomized ROM. - -To make using APBP easy, they can be generated by inheriting from `worlds.Files.APDeltaPatch`. - -### Mod files - -Games which support modding will usually just let you drag and drop the mod’s files into a folder somewhere. -Mod files come in many forms, but the rules about not distributing other people's content remain the same. -They can either be generic and modify the game using a seed or `slot_data` from the AP websocket, or they can be -generated per seed. If at all possible, it's generally best practice to collect your world information from `slot_data` -so that the users don't have to move files around in order to play. - -If the mod is generated by AP and is installed from a ZIP file, it may be possible to include APBP metadata for easy -integration into the Webhost by inheriting from `worlds.Files.APContainer`. - -## Archipelago Integration - -In order for your game to communicate with the Archipelago server and generate the necessary randomized information, -you must create a world package in the main Archipelago repo. This section will cover the requisites and expectations -and show the basics of a world. More in depth documentation on the available API can be read in -the [world api doc.](/docs/world%20api.md) -For setting up your working environment with Archipelago refer -to [running from source](/docs/running%20from%20source.md) and the [style guide](/docs/style.md). - -### Requirements - -A world implementation requires a few key things from its implementation - -- A folder within `worlds` that contains an `__init__.py` - - This is what defines it as a Python package and how it's able to be imported - into Archipelago's generation system. During generation time only code that is - defined within this file will be run. It's suggested to split up your information - into more files to improve readability, but all of that information can be - imported at its base level within your world. -- A `World` subclass where you create your world and define all of its rules - and the following requirements: - - Your items and locations need a `item_name_to_id` and `location_name_to_id`, - respectively, mapping. - - An `option_definitions` mapping of your game options with the format - `{name: Class}`, where `name` uses Python snake_case. - - You must define your world's `create_item` method, because this may be called - by the generator in certain circumstances - - When creating your world you submit items and regions to the Multiworld. - - These are lists of said objects which you can access at - `self.multiworld.itempool` and `self.multiworld.regions`. Best practice for - adding to these lists is with either `append` or `extend`, where `append` is a - single object and `extend` is a list. - - Do not use `=` as this will delete other worlds' items and regions. - - Regions are containers for holding your world's Locations. - - Locations are where players will "check" for items and must exist within - a region. It's also important for your world's submitted items to be the same as - its submitted locations count. - - You must always have a "Menu" Region from which the generation algorithm - uses to enter the game and access locations. -- Make sure to check out [world maintainer.md](/docs/world%20maintainer.md) before publishing. \ No newline at end of file +# Adding Games + +Adding a new game to Archipelago has two major parts: + +* Game Modification to communicate with Archipelago server (hereafter referred to as "client") +* Archipelago Generation and Server integration plugin (hereafter referred to as "world") + +This document will attempt to illustrate the bare minimum requirements and expectations of both parts of a new world +integration. As game modification wildly varies by system and engine, and has no bearing on the Archipelago protocol, +it will not be detailed here. + +## Client + +The client is an intermediary program between the game and the Archipelago server. This can either be a direct +modification to the game, an external program, or both. This can be implemented in nearly any modern language, but it +must fulfill a few requirements in order to function as expected. The specific requirements the game client must follow +to behave as expected are: + +* Handle both secure and unsecure websocket connections +* Detect and react when a location has been "checked" by the player by sending a network packet to the server +* Receive and parse network packets when the player receives an item from the server, and reward it to the player on +demand + * **Any** of your items can be received any number of times, up to and far surpassing those that the game might +normally expect from features such as starting inventory, item link replacement, or item cheating + * Players and the admin can cheat items to the player at any time with a server command, and these items may not have +a player or location attributed to them +* Be able to change the port for saved connection info + * Rooms hosted on the website attempt to reserve their port, but since there are a limited number of ports, this +privilege can be lost, requiring the room to be moved to a new port +* Reconnect if the connection is unstable and lost while playing +* Keep an index for items received in order to resync. The ItemsReceived Packets are a single list with guaranteed +order. +* Receive items that were sent to the player while they were not connected to the server + * The player being able to complete checks while offline and sending them when reconnecting is a good bonus, but not +strictly required +* Send a status update packet alerting the server that the player has completed their goal + +Libraries for most modern languages and the spec for various packets can be found in the +[network protocol](/docs/network%20protocol.md) API reference document. + +## World + +The world is your game integration for the Archipelago generator, webhost, and multiworld server. It contains all the +information necessary for creating the items and locations to be randomized, the logic for item placement, the +datapackage information so other game clients can recognize your game data, and documentation. Your world must be +written as a Python package to be loaded by Archipelago. This is currently done by creating a fork of the Archipelago +repository and creating a new world package in `/worlds/`. A bare minimum world implementation must satisfy the +following requirements: + +* A folder within `/worlds/` that contains an `__init__.py` +* A `World` subclass where you create your world and define all of its rules +* A unique game name +* For webhost documentation and behaviors, a `WebWorld` subclass that must be instantiated in the `World` class +definition + * The game_info doc must follow the format `{language_code}_{game_name}.md` +* A mapping for items and locations defining their names and ids for clients to be able to identify them. These are +`item_name_to_id` and `location_name_to_id`, respectively. +* Create an item when `create_item` is called both by your code and externally +* An `options_dataclass` defining the options players have available to them +* A `Region` for your player with the name "Menu" to start from +* Create a non-zero number of locations and add them to your regions +* Create a non-zero number of items **equal** to the number of locations and add them to the multiworld itempool +* All items submitted to the multiworld itempool must not be manually placed by the World. If you need to place specific +items, there are multiple ways to do so, but they should not be added to the multiworld itempool. + +Notable caveats: +* The "Menu" region will always be considered the "start" for the player +* The "Menu" region is *always* considered accessible; i.e. the player is expected to always be able to return to the +start of the game from anywhere +* When submitting regions or items to the multiworld (multiworld.regions and multiworld.itempool respectively), use +`append`, `extend`, or `+=`. **Do not use `=`** +* Regions are simply containers for locations that share similar access rules. They do not have to map to +concrete, physical areas within your game and can be more abstract like tech trees or a questline. + +The base World class can be found in [AutoWorld](/worlds/AutoWorld.py). Methods available for your world to call during +generation can be found in [BaseClasses](/BaseClasses.py) and [Fill](/Fill.py). Some examples and documentation +regarding the API can be found in the [world api doc](/docs/world%20api.md). +Before publishing, make sure to also check out [world maintainer.md](/docs/world%20maintainer.md). diff --git a/docs/apworld_dev_faq.md b/docs/apworld_dev_faq.md new file mode 100644 index 000000000000..8d9429afa321 --- /dev/null +++ b/docs/apworld_dev_faq.md @@ -0,0 +1,45 @@ +# APWorld Dev FAQ + +This document is meant as a reference tool to show solutions to common problems when developing an apworld. +It is not intended to answer every question about Archipelago and it assumes you have read the other docs, +including [Contributing](contributing.md), [Adding Games](), and [World API](). + +--- + +### My game has a restrictive start that leads to fill errors + +Hint to the Generator that an item needs to be in sphere one with local_early_items. Here, `1` represents the number of "Sword" items to attempt to place in sphere one. +```py +early_item_name = "Sword" +self.multiworld.local_early_items[self.player][early_item_name] = 1 +``` + +Some alternative ways to try to fix this problem are: +* Add more locations to sphere one of your world, potentially only when there would be a restrictive start +* Pre-place items yourself, such as during `create_items` +* Put items into the player's starting inventory using `push_precollected` +* Raise an exception, such as an `OptionError` during `generate_early`, to disallow options that would lead to a restrictive start + +--- + +### I have multiple settings that change the item/location pool counts and need to balance them out + +In an ideal situation your system for producing locations and items wouldn't leave any opportunity for them to be unbalanced. But in real, complex situations, that might be unfeasible. + +If that's the case, you can create extra filler based on the difference between your unfilled locations and your itempool by comparing [get_unfilled_locations](https://github.com/ArchipelagoMW/Archipelago/blob/main/BaseClasses.py#:~:text=get_unfilled_locations) to your list of items to submit + +Note: to use self.create_filler(), self.get_filler_item_name() should be defined to only return valid filler item names +```py +total_locations = len(self.multiworld.get_unfilled_locations(self.player)) +item_pool = self.create_non_filler_items() + +for _ in range(total_locations - len(item_pool)): + item_pool.append(self.create_filler()) + +self.multiworld.itempool += item_pool +``` + +A faster alternative to the `for` loop would be to use a [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions): +```py +item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))] +``` diff --git a/docs/contributing.md b/docs/contributing.md index e7f3516712d2..9fd21408eb7b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,43 +1,49 @@ # Contributing -Contributions are welcome. We have a few requests for new contributors: + +All contributions are welcome, though we have a few requests of contributors, whether they be for core, webhost, or new +game contributions: * **Follow styling guidelines.** Please take a look at the [code style documentation](/docs/style.md) to ensure ease of communication and uniformity. -* **Ensure that critical changes are covered by tests.** -It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working. -If you wish to contribute by adding a new game, please take a look at the [logic unit test documentation](/docs/tests.md). -If you wish to contribute to the website, please take a look at [these tests](/test/webhost). +* **Ensure that critical changes are covered by tests.** + It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working. + If you wish to contribute by adding a new game, please take a look at + the [logic unit test documentation](/docs/tests.md). + If you wish to contribute to the website, please take a look at [these tests](/test/webhost). * **Do not introduce unit test failures/regressions.** -Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test -your changes. Currently, the oldest supported version is [Python 3.8](https://www.python.org/downloads/release/python-380/). -It is recommended that automated github actions are turned on in your fork to have github run all of the unit tests after pushing. -You can turn them on here: -![Github actions example](./img/github-actions-example.png) + Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test + your changes. Currently, the oldest supported version + is [Python 3.8](https://www.python.org/downloads/release/python-380/). + It is recommended that automated github actions are turned on in your fork to have github run unit tests after + pushing. + You can turn them on here: + ![Github actions example](./img/github-actions-example.png) * **When reviewing PRs, please leave a message about what was done.** -We don't have full test coverage, so manual testing can help. -For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing -or checking if all code paths are covered by automated tests is desired. The original author may not have been able -to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to -state which games or settings were rolled, if any. -Please also tell us if you looked at code, just did functional testing, did both, or did neither. -If testing the PR depends on other PRs, please state what you merged into what for testing. -We cannot determine what "LGTM" means without additional context, so that should not be the norm. + We don't have full test coverage, so manual testing can help. + For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing + or checking if all code paths are covered by automated tests is desired. The original author may not have been able + to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to + state which games or settings were rolled, if any. + Please also tell us if you looked at code, just did functional testing, did both, or did neither. + If testing the PR depends on other PRs, please state what you merged into what for testing. + We cannot determine what "LGTM" means without additional context, so that should not be the norm. -Other than these requests, we tend to judge code on a case-by-case basis. +Other than these requests, we tend to judge code on a case-by-case basis. For contribution to the website, please refer to the [WebHost README](/WebHostLib/README.md). If you want to contribute to the core, you will be subject to stricter review on your pull requests. It is recommended that you get in touch with other core maintainers via the [Discord](https://archipelago.gg/discord). -If you want to add Archipelago support for a new game, please take a look at the [adding games documentation](/docs/adding%20games.md), which details what is required -to implement support for a game, as well as tips for how to get started. -If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a -[world maintainer](/docs/world%20maintainer.md). +If you want to add Archipelago support for a new game, please take a look at +the [adding games documentation](/docs/adding%20games.md) +which details what is required to implement support for a game, and has tips on to get started. +If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a +[world maintainer](/docs/world%20maintainer.md). -For other questions, feel free to explore the [main documentation folder](/docs/) and ask us questions in the #archipelago-dev channel -of the [Discord](https://archipelago.gg/discord). +For other questions, feel free to explore the [main documentation folder](/docs), and ask us questions in the +#ap-world-dev channel of the [Discord](https://archipelago.gg/discord). diff --git a/docs/img/creepy-castle-directory.png b/docs/img/creepy-castle-directory.png deleted file mode 100644 index af4fdc584dc00d6cc6ee1c5a6425b11cc419039b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35874 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 diff --git a/docs/img/gato-roboto-directory.png b/docs/img/gato-roboto-directory.png deleted file mode 100644 index fda0eb5ff992b9870224c1511e0f9108a69b18d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81020 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


    !Z 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

    (=yzUsWG71YEh&@^=lg!pCbeB=ke+@Gi4Fq) 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 z%0A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 diff --git a/docs/network protocol.md b/docs/network protocol.md index 9f2c07883b9d..da5c41431501 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -31,6 +31,9 @@ There are also a number of community-supported libraries available that implemen | GameMaker: Studio 2.x+ | [see Discord](https://discord.com/channels/731205301247803413/1166418532519653396) | | ## Synchronizing Items +After a client connects, it will receive all previously collected items for its associated slot in a [ReceivedItems](#ReceivedItems) packet. This will include items the client may have already processed in a previous play session. +To ensure the client is able to reject those items if it needs to, each item in the packet has an associated `index` argument. You will need to find a way to save the "last processed item index" to the player's local savegame, a local file, or something to that effect. Before connecting, you should load that "last processed item index" value and compare against it in your received items handling. + When the client receives a [ReceivedItems](#ReceivedItems) packet, if the `index` argument does not match the next index that the client expects then it is expected that the client will re-sync items with the server. This can be accomplished by sending the server a [Sync](#Sync) packet and then a [LocationChecks](#LocationChecks) packet. Even if the client detects a desync, it can still accept the items provided in this packet to prevent gameplay interruption. @@ -50,7 +53,7 @@ Example: ``` ## (Server -> Client) -These packets are are sent from the multiworld server to the client. They are not messages which the server accepts. +These packets are sent from the multiworld server to the client. They are not messages which the server accepts. * [RoomInfo](#RoomInfo) * [ConnectionRefused](#ConnectionRefused) * [Connected](#Connected) @@ -77,7 +80,6 @@ Sent to clients when they connect to an Archipelago server. | hint_cost | int | The percentage of total locations that need to be checked to receive a hint from the server. | | location_check_points | int | The amount of hint points you receive per item/location check completed. | | games | list\[str\] | List of games present in this multiworld. | -| datapackage_versions | dict\[str, int\] | Data versions of the individual games' data packages the server will send. Used to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents). **Deprecated. Use `datapackage_checksums` instead.** | | datapackage_checksums | dict[str, str] | Checksum hash of the individual games' data packages the server will send. Used by newer clients to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents) for more information. | | seed_name | str | Uniquely identifying name of this generation | | time | float | Unix time stamp of "now". Send for time synchronization if wanted for things like the DeathLink Bounce. | @@ -497,9 +499,9 @@ In JSON this may look like: {"item": 3, "location": 3, "player": 3, "flags": 0} ] ``` -`item` is the item id of the item. Item ids are in the range of ± 253-1. +`item` is the item id of the item. Item ids are only supported in the range of [-253, 253 - 1], with anything ≤ 0 reserved for Archipelago use. -`location` is the location id of the item inside the world. Location ids are in the range of ± 253-1. +`location` is the location id of the item inside the world. Location ids are only supported in the range of [-253, 253 - 1], with anything ≤ 0 reserved for Archipelago use. `player` is the player slot of the world the item is located in, except when inside an [LocationInfo](#LocationInfo) Packet then it will be the slot of the player to receive the item @@ -643,15 +645,47 @@ class Hint(typing.NamedTuple): ``` ### Data Package Contents -A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago server most easily. Currently, this package is used to send ID to name mappings so that clients need not maintain their own mappings. - -We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different version. A special case is datapackage version 0, where it is expected the package is custom and should not be cached. - -Note: - * Any ID is unique to its type across AP: Item 56 only exists once and Location 56 only exists once. - * Any Name is unique to its type across its own Game only: Single Arrow can exist in two games. - * The IDs from the game "Archipelago" may be used in any other game. - Especially Location ID -1: Cheat Console and -2: Server (typically Remote Start Inventory) +A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago +server most easily and not maintain their own mappings. Some contents include: + + - Name to ID mappings for items and locations. + - A checksum of each game's data package for clients to tell if a cached package is invalid. + +We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know +when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different checksum +than any locally cached ones. + +**Important Notes about IDs and Names**: + +* IDs ≤ 0 are reserved for "Archipelago" and should not be used by other world implementations. +* The IDs from the game "Archipelago" (in `worlds/generic`) may be used in any world. + * Especially Location ID `-1`: `Cheat Console` and `-2`: `Server` (typically Remote Start Inventory) +* Any names and IDs are only unique in its own world data package, but different games may reuse these names or IDs. + * At runtime, you will need to look up the game of the player to know which item or location ID/Name to lookup in the + data package. This can be easily achieved by reviewing the `slot_info` for a particular player ID prior to lookup. + * For example, a data package like this is valid (Some properties such as `checksum` were omitted): + ```json + { + "games": { + "Game A": { + "location_name_to_id": { + "Boss Chest": 40 + }, + "item_name_to_id": { + "Item X": 12 + } + }, + "Game B": { + "location_name_to_id": { + "Minigame Prize": 40 + }, + "item_name_to_id": { + "Item X": 40 + } + } + } + } + ``` #### Contents | Name | Type | Notes | @@ -665,7 +699,6 @@ GameData is a **dict** but contains these keys and values. It's broken out into |---------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------| | item_name_to_id | dict[str, int] | Mapping of all item names to their respective ID. | | location_name_to_id | dict[str, int] | Mapping of all location names to their respective ID. | -| version | int | Version number of this game's data. Deprecated. Used by older clients to request an updated datapackage if cache is outdated. | | checksum | str | A checksum hash of this game's data. | ### Tags diff --git a/docs/options api.md b/docs/options api.md index 1141528991df..7e479809ee6a 100644 --- a/docs/options api.md +++ b/docs/options api.md @@ -10,10 +10,9 @@ Archipelago will be abbreviated as "AP" from now on. ## Option Definitions Option parsing in AP is done using different Option classes. For each option you would like to have in your game, you need to create: -- A new option class with a docstring detailing what the option will do to your user. -- A `display_name` to be displayed on the webhost. -- A new entry in the `option_definitions` dict for your World. -By style and convention, the internal names should be snake_case. +- A new option class, with a docstring detailing what the option does, to be exposed to the user. +- A new entry in the `options_dataclass` definition for your World. +By style and convention, the dataclass attributes should be `snake_case`. ### Option Creation - If the option supports having multiple sub_options, such as Choice options, these can be defined with @@ -43,7 +42,7 @@ from Options import Toggle, Range, Choice, PerGameCommonOptions class StartingSword(Toggle): """Adds a sword to your starting inventory.""" - display_name = "Start With Sword" + display_name = "Start With Sword" # this is the option name as it's displayed to the user on the webhost and in the spoiler log class Difficulty(Choice): @@ -86,6 +85,81 @@ class ExampleWorld(World): options: ExampleGameOptions ``` +### Option Documentation + +Options' [docstrings] are used as their user-facing documentation. They're displayed on the WebHost setup page when a +user hovers over the yellow "(?)" icon, and included in the YAML templates generated for each game. + +[docstrings]: /docs/world%20api.md#docstrings + +The WebHost can display Option documentation either as plain text with all whitespace preserved (other than the base +indentation), or as HTML generated from the standard Python [reStructuredText] format. Although plain text is the +default for backwards compatibility, world authors are encouraged to write their Option documentation as +reStructuredText and enable rich text rendering by setting `World.rich_text_options_doc = True`. + +[reStructuredText]: https://docutils.sourceforge.io/rst.html + +```python +from worlds.AutoWorld import WebWorld + + +class ExampleWebWorld(WebWorld): + # Render all this world's options as rich text. + rich_text_options_doc = True +``` + +You can set a single option to use rich or plain text by setting +`Option.rich_text_doc`. + +```python +from Options import Toggle, Range, Choice, PerGameCommonOptions + + +class Difficulty(Choice): + """Sets overall game difficulty. + + - **Easy:** All enemies die in one hit. + - **Normal:** Enemies and the player both have normal health bars. + - **Hard:** The player dies in one hit.""" + display_name = "Difficulty" + rich_text_doc = True + option_easy = 0 + option_normal = 1 + option_hard = 2 + default = 1 +``` + +### Option Groups +Options may be categorized into groups for display on the WebHost. Option groups are displayed in the order specified +by your world on the player-options and weighted-options pages. In the generated template files, there will be a comment +with the group name at the beginning of each group of options. The `start_collapsed` Boolean only affects how the groups +appear on the WebHost, with the grouping being collapsed when this is `True`. + +Options without a group name are categorized into a generic "Game Options" group, which is always the first group. If +every option for your world is in a group, this group will be removed. There is also an "Items & Location Options" +group, which is automatically created using certain specified `item_and_loc_options`. These specified options cannot be +removed from this group. + +Both the "Game Options" and "Item & Location Options" groups can be overridden by creating your own groups with +those names, letting you add options to them and change whether they start collapsed. The "Item & +Location Options" group can also be moved to a different position in the group ordering, but "Game Options" will always +be first, regardless of where it is in your list. + +```python +from worlds.AutoWorld import WebWorld +from Options import OptionGroup +from . import Options + +class MyWorldWeb(WebWorld): + option_groups = [ + OptionGroup("Color Options", [ + Options.ColorblindMode, + Options.FlashReduction, + Options.UIColors, + ]), + ] +``` + ### Option Checking Options are parsed by `Generate.py` before the worlds are created, and then the option classes are created shortly after world instantiation. These are created as attributes on the MultiWorld and can be accessed with @@ -102,7 +176,8 @@ or if I need a boolean object, such as in my slot_data I can access it as: start_with_sword = bool(self.options.starting_sword.value) ``` All numeric options (i.e. Toggle, Choice, Range) can be compared to integers, strings that match their attributes, -strings that match the option attributes after "option_" is stripped, and the attributes themselves. +strings that match the option attributes after "option_" is stripped, and the attributes themselves. The option can +also be checked to see if it exists within a collection, but this will fail for a set of strings due to hashing. ```python # options.py class Logic(Choice): @@ -114,6 +189,12 @@ class Logic(Choice): alias_extra_hard = 2 crazy = 4 # won't be listed as an option and only exists as an attribute on the class +class Weapon(Choice): + option_none = 0 + option_sword = 1 + option_bow = 2 + option_hammer = 3 + # __init__.py from .options import Logic @@ -127,6 +208,16 @@ elif self.options.logic == Logic.option_extreme: do_extreme_things() elif self.options.logic == "crazy": do_insane_things() + +# check if the current option is in a collection of integers using the class attributes +if self.options.weapon in {Weapon.option_bow, Weapon.option_sword}: + do_stuff() +# in order to make a set of strings work, we have to compare against current_key +elif self.options.weapon.current_key in {"none", "hammer"}: + do_something_else() +# though it's usually better to just use a tuple instead +elif self.options.weapon in ("none", "hammer"): + do_something_else() ``` ## Generic Option Classes These options are generically available to every game automatically, but can be overridden for slightly different @@ -156,10 +247,12 @@ Gives the player starting hints for where the items defined here are. Gives the player starting hints for the items on locations defined here. ### ExcludeLocations -Marks locations given here as `LocationProgressType.Excluded` so that progression items can't be placed on them. +Marks locations given here as `LocationProgressType.Excluded` so that neither progression nor useful items can be +placed on them. ### PriorityLocations -Marks locations given here as `LocationProgressType.Priority` forcing progression items on them. +Marks locations given here as `LocationProgressType.Priority` forcing progression items on them if any are available in +the pool. ### ItemLinks Allows users to share their item pool with other players. Currently item links are per game. A link of one game between @@ -204,7 +297,7 @@ For example: ```python range_start = 1 range_end = 99 -special_range_names: { +special_range_names = { "normal": 20, "extreme": 99, "unlimited": -1, diff --git a/docs/running from source.md b/docs/running from source.md index b7367308d8db..34083a603d1b 100644 --- a/docs/running from source.md +++ b/docs/running from source.md @@ -17,13 +17,14 @@ Then run any of the starting point scripts, like Generate.py, and the included M required modules and after pressing enter proceed to install everything automatically. After this, you should be able to run the programs. + * `Launcher.py` gives access to many components, including clients registered in `worlds/LauncherComponents.py`. + * The Launcher button "Generate Template Options" will generate default yamls for all worlds. * With yaml(s) in the `Players` folder, `Generate.py` will generate the multiworld archive. * `MultiServer.py`, with the filename of the generated archive as a command line parameter, will host the multiworld locally. * `--log_network` is a command line parameter useful for debugging. * `WebHost.py` will host the website on your computer. * You can copy `docs/webhost configuration sample.yaml` to `config.yaml` to change WebHost options (like the web hosting port number). - * As a side effect, `WebHost.py` creates the template yamls for all the games in `WebHostLib/static/generated`. ## Windows diff --git a/docs/settings api.md b/docs/settings api.md index 41023879adf8..bfc642d4b50c 100644 --- a/docs/settings api.md +++ b/docs/settings api.md @@ -1,7 +1,7 @@ # Archipelago Settings API The settings API describes how to use installation-wide config and let the user configure them, like paths, etc. using -host.yaml. For the player settings / player yamls see [options api.md](options api.md). +host.yaml. For the player options / player yamls see [options api.md](options api.md). The settings API replaces `Utils.get_options()` and `Utils.get_default_options()` as well as the predefined `host.yaml` in the repository. diff --git a/docs/style.md b/docs/style.md index fbf681f28e97..81853f41725b 100644 --- a/docs/style.md +++ b/docs/style.md @@ -17,6 +17,15 @@ * Use type annotations where possible for function signatures and class members. * Use type annotations where appropriate for local variables (e.g. `var: List[int] = []`, or when the type is hard or impossible to deduce.) Clear annotations help developers look up and validate API calls. +* If a line ends with an open bracket/brace/parentheses, the matching closing bracket should be at the + beginning of a line at the same indentation as the beginning of the line with the open bracket. + ```python + stuff = { + x: y + for x, y in thing + if y > 2 + } + ``` * New classes, attributes, and methods in core code should have docstrings that follow [reST style](https://peps.python.org/pep-0287/). * Worlds that do not follow PEP8 should still have a consistent style across its files to make reading easier. diff --git a/docs/webhost configuration sample.yaml b/docs/webhost configuration sample.yaml index 70050b0590d6..afb87b399643 100644 --- a/docs/webhost configuration sample.yaml +++ b/docs/webhost configuration sample.yaml @@ -1,4 +1,4 @@ -# This is a sample configuration for the Web host. +# This is a sample configuration for the Web host. # If you wish to change any of these, rename this file to config.yaml # Default values are shown here. Uncomment and change the values as desired. @@ -25,7 +25,7 @@ # Secret key used to determine important things like cookie authentication of room/seed page ownership. # If you wish to deploy, uncomment the following line and set it to something not easily guessable. -# SECRET_KEY: "Your secret key here" +# SECRET_KEY: "Your secret key here" # TODO #JOB_THRESHOLD: 2 @@ -38,15 +38,16 @@ # provider: "sqlite" # filename: "ap.db3" # This MUST be the ABSOLUTE PATH to the file. # create_db: true - + # Maximum number of players that are allowed to be rolled on the server. After this limit, one should roll locally and upload the results. #MAX_ROLL: 20 # TODO #CACHE_TYPE: "simple" -# TODO -#JSON_AS_ASCII: false - # Host Address. This is the address encoded into the patch that will be used for client auto-connect. #HOST_ADDRESS: archipelago.gg + +# Asset redistribution rights. If true, the host affirms they have been given explicit permission to redistribute +# the proprietary assets in WebHostLib +#ASSET_RIGHTS: false diff --git a/docs/world api.md b/docs/world api.md index f82ef40a98f8..6551f2260416 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -56,6 +56,12 @@ webhost: * `options_page` can be changed to a link instead of an AP-generated options page. +* `rich_text_options_doc` controls whether [Option documentation] uses plain text (`False`) or rich text (`True`). It + defaults to `False`, but world authors are encouraged to set it to `True` for nicer-looking documentation that looks + good on both the WebHost and the YAML template. + + [Option documentation]: /docs/options%20api.md#option-documentation + * `theme` to be used for your game-specific AP pages. Available themes: | dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone | @@ -121,6 +127,53 @@ class RLWeb(WebWorld): # ... ``` +* `location_descriptions` (optional) WebWorlds can provide a map that contains human-friendly descriptions of locations +or location groups. + + ```python + # locations.py + location_descriptions = { + "Red Potion #6": "In a secret destructible block under the second stairway", + "L2 Spaceship": """ + The group of all items in the spaceship in Level 2. + + This doesn't include the item on the spaceship door, since it can be + accessed without the Spaceship Key. + """ + } + + # __init__.py + from worlds.AutoWorld import WebWorld + from .locations import location_descriptions + + + class MyGameWeb(WebWorld): + location_descriptions = location_descriptions + ``` + +* `item_descriptions` (optional) WebWorlds can provide a map that contains human-friendly descriptions of items or item +groups. + + ```python + # items.py + item_descriptions = { + "Red Potion": "A standard health potion", + "Spaceship Key": """ + The key to the spaceship in Level 2. + + This is necessary to get to the Star Realm. + """, + } + + # __init__.py + from worlds.AutoWorld import WebWorld + from .items import item_descriptions + + + class MyGameWeb(WebWorld): + item_descriptions = item_descriptions + ``` + ### MultiWorld Object The `MultiWorld` object references the whole multiworld (all items and locations for all players) and is accessible @@ -178,37 +231,6 @@ Classification is one of `LocationProgressType.DEFAULT`, `PRIORITY` or `EXCLUDED The Fill algorithm will force progression items to be placed at priority locations, giving a higher chance of them being required, and will prevent progression and useful items from being placed at excluded locations. -#### Documenting Locations - -Worlds can optionally provide a `location_descriptions` map which contains human-friendly descriptions of locations and -location groups. These descriptions will show up in location-selection options on the Weighted Options page. Extra -indentation and single newlines will be collapsed into spaces. - -```python -# locations.py - -location_descriptions = { - "Red Potion #6": "In a secret destructible block under the second stairway", - "L2 Spaceship": - """ - The group of all items in the spaceship in Level 2. - - This doesn't include the item on the spaceship door, since it can be accessed without the Spaceship Key. - """ -} -``` - -```python -# __init__.py - -from worlds.AutoWorld import World -from .locations import location_descriptions - - -class MyGameWorld(World): - location_descriptions = location_descriptions -``` - ### Items Items are all things that can "drop" for your game. This may be RPG items like weapons, or technologies you normally @@ -233,37 +255,6 @@ Other classifications include: * `progression_skip_balancing`: the combination of `progression` and `skip_balancing`, i.e., a progression item that will not be moved around by progression balancing; used, e.g., for currency or tokens, to not flood early spheres -#### Documenting Items - -Worlds can optionally provide an `item_descriptions` map which contains human-friendly descriptions of items and item -groups. These descriptions will show up in item-selection options on the Weighted Options page. Extra indentation and -single newlines will be collapsed into spaces. - -```python -# items.py - -item_descriptions = { - "Red Potion": "A standard health potion", - "Spaceship Key": - """ - The key to the spaceship in Level 2. - - This is necessary to get to the Star Realm. - """ -} -``` - -```python -# __init__.py - -from worlds.AutoWorld import World -from .items import item_descriptions - - -class MyGameWorld(World): - item_descriptions = item_descriptions -``` - ### Events An Event is a special combination of a Location and an Item, with both having an `id` of `None`. These can be used to @@ -380,11 +371,6 @@ from BaseClasses import Location class MyGameLocation(Location): game: str = "My Game" - - # override constructor to automatically mark event locations as such - def __init__(self, player: int, name="", code=None, parent=None) -> None: - super(MyGameLocation, self).__init__(player, name, code, parent) - self.event = code is None ``` in your `__init__.py` or your `locations.py`. @@ -470,8 +456,9 @@ In addition, the following methods can be implemented and are called in this ord called to place player's regions and their locations into the MultiWorld's regions list. If it's hard to separate, this can be done during `generate_early` or `create_items` as well. * `create_items(self)` - called to place player's items into the MultiWorld's itempool. After this step all regions - and items have to be in the MultiWorld's regions and itempool, and these lists should not be modified afterward. + called to place player's items into the MultiWorld's itempool. By the end of this step all regions, locations and + items have to be in the MultiWorld's regions and itempool. You cannot add or remove items, locations, or regions + after this step. Locations cannot be moved to different regions after this step. * `set_rules(self)` called to set access and item rules on locations and entrances. * `generate_basic(self)` diff --git a/inno_setup.iss b/inno_setup.iss index c1b634292fc9..f097500f7d7d 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -31,8 +31,11 @@ ArchitecturesAllowed=x64 arm64 AllowNoIcons=yes SetupIconFile={#MyAppIcon} UninstallDisplayIcon={app}\{#MyAppExeName} -; you will likely have to remove the following signtool line when testing/debugging locally. Don't include that change in PRs. +#ifndef NO_SIGNTOOL +; You will likely have to remove the SignTool= line when testing/debugging locally or run with iscc.exe /DNO_SIGNTOOL. +; Don't include that change in PRs. SignTool= signtool +#endif LicenseFile= LICENSE WizardStyle= modern SetupLogging=yes @@ -72,7 +75,7 @@ Name: "{commondesktop}\{#MyAppName} Launcher"; Filename: "{app}\ArchipelagoLaunc [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Flags: nowait; Components: lttp_sprites +Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: lttp_sprites Filename: "{app}\ArchipelagoLauncher"; Parameters: "--update_settings"; StatusMsg: "Updating host.yaml..."; Flags: runasoriginaluser runhidden Filename: "{app}\ArchipelagoLauncher"; Description: "{cm:LaunchProgram,{#StringChange('Launcher', '&', '&&')}}"; Flags: nowait postinstall skipifsilent @@ -84,7 +87,14 @@ Type: files; Name: "{app}\lib\worlds\_bizhawk.apworld" Type: files; Name: "{app}\ArchipelagoLttPClient.exe" Type: files; Name: "{app}\ArchipelagoPokemonClient.exe" Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua" -Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy*" +Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy" +Type: dirifempty; Name: "{app}\lib\worlds\rogue-legacy" +Type: files; Name: "{app}\lib\worlds\sc2wol.apworld" +Type: filesandordirs; Name: "{app}\lib\worlds\sc2wol" +Type: dirifempty; Name: "{app}\lib\worlds\sc2wol" +Type: filesandordirs; Name: "{app}\lib\worlds\bk_sudoku" +Type: dirifempty; Name: "{app}\lib\worlds\bk_sudoku" +Type: files; Name: "{app}\ArchipelagoLauncher(DEBUG).exe" Type: filesandordirs; Name: "{app}\SNI\lua*" Type: filesandordirs; Name: "{app}\EnemizerCLI*" #include "installdelete.iss" @@ -131,10 +141,10 @@ Root: HKCR; Subkey: "{#MyAppName}l2acpatch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}l2acpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}l2acpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; -Root: HKCR; Subkey: ".apkdl3"; ValueData: "{#MyAppName}kdl3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}kdl3patch"; ValueData: "Archipelago Kirby's Dream Land 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}kdl3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}kdl3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: ".apkdl3"; ValueData: "{#MyAppName}kdl3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}kdl3patch"; ValueData: "Archipelago Kirby's Dream Land 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; @@ -166,6 +176,16 @@ Root: HKCR; Subkey: "{#MyAppName}pkmnepatch"; ValueData: "Ar Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apmlss"; ValueData: "{#MyAppName}mlsspatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}mlsspatch"; ValueData: "Archipelago Mario & Luigi Superstar Saga Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}mlsspatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}mlsspatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + +Root: HKCR; Subkey: ".apcv64"; ValueData: "{#MyAppName}cv64patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}cv64patch"; ValueData: "Archipelago Castlevania 64 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}cv64patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}cv64patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: ""; @@ -181,11 +201,26 @@ Root: HKCR; Subkey: "{#MyAppName}advnpatch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}advnpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoAdventureClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}advnpatch\shell\open\command"; ValueData: """{app}\ArchipelagoAdventureClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apyi"; ValueData: "{#MyAppName}yipatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}yipatch"; ValueData: "Archipelago Yoshi's Island Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}yipatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}yipatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + +Root: HKCR; Subkey: ".apygo06"; ValueData: "{#MyAppName}ygo06patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}ygo06patch"; ValueData: "Archipelago Yu-Gi-Oh 2006 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}ygo06patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}ygo06patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apworld"; ValueData: "{#MyAppName}worlddata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}worlddata"; ValueData: "Archipelago World Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}worlddata\DefaultIcon"; ValueData: "{app}\ArchipelagoLauncher.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1"""; ValueType: string; ValueName: ""; + Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueData: "Archipegalo Protocol"; Flags: uninsdeletekey; Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: ""; Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoTextClient.exe,0"; diff --git a/intset.h b/intset.h new file mode 100644 index 000000000000..fac84fb6f890 --- /dev/null +++ b/intset.h @@ -0,0 +1,135 @@ +/* A specialized unordered_set implementation for literals, where bucket_count + * is defined at initialization rather than increased automatically. + */ +#include +#include +#include +#include + +#ifndef INTSET_NAME +#error "Please #define INTSET_NAME ... before including intset.h" +#endif + +#ifndef INTSET_TYPE +#error "Please #define INTSET_TYPE ... before including intset.h" +#endif + +/* macros to generate unique names from INTSET_NAME */ +#ifndef INTSET_CONCAT +#define INTSET_CONCAT_(a, b) a ## b +#define INTSET_CONCAT(a, b) INTSET_CONCAT_(a, b) +#define INTSET_FUNC_(a, b) INTSET_CONCAT(a, _ ## b) +#endif + +#define INTSET_FUNC(name) INTSET_FUNC_(INTSET_NAME, name) +#define INTSET_BUCKET INTSET_CONCAT(INTSET_NAME, Bucket) +#define INTSET_UNION INTSET_CONCAT(INTSET_NAME, Union) + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4200) +#endif + + +typedef struct { + size_t count; + union INTSET_UNION { + INTSET_TYPE val; + INTSET_TYPE *data; + } v; +} INTSET_BUCKET; + +typedef struct { + size_t bucket_count; + INTSET_BUCKET buckets[]; +} INTSET_NAME; + +static INTSET_NAME *INTSET_FUNC(new)(size_t buckets) +{ + size_t i, size; + INTSET_NAME *set; + + if (buckets < 1) + buckets = 1; + if ((SIZE_MAX - sizeof(INTSET_NAME)) / sizeof(INTSET_BUCKET) < buckets) + return NULL; + size = sizeof(INTSET_NAME) + buckets * sizeof(INTSET_BUCKET); + set = (INTSET_NAME*)malloc(size); + if (!set) + return NULL; + memset(set, 0, size); /* gcc -fanalyzer does not understand this sets all buckets' count to 0 */ + for (i = 0; i < buckets; i++) { + set->buckets[i].count = 0; + } + set->bucket_count = buckets; + return set; +} + +static void INTSET_FUNC(free)(INTSET_NAME *set) +{ + size_t i; + if (!set) + return; + for (i = 0; i < set->bucket_count; i++) { + if (set->buckets[i].count > 1) + free(set->buckets[i].v.data); + } + free(set); +} + +static bool INTSET_FUNC(contains)(INTSET_NAME *set, INTSET_TYPE val) +{ + size_t i; + INTSET_BUCKET* bucket = &set->buckets[(size_t)val % set->bucket_count]; + if (bucket->count == 1) + return bucket->v.val == val; + for (i = 0; i < bucket->count; ++i) { + if (bucket->v.data[i] == val) + return true; + } + return false; +} + +static bool INTSET_FUNC(add)(INTSET_NAME *set, INTSET_TYPE val) +{ + INTSET_BUCKET* bucket; + + if (INTSET_FUNC(contains)(set, val)) + return true; /* ok */ + + bucket = &set->buckets[(size_t)val % set->bucket_count]; + if (bucket->count == 0) { + bucket->v.val = val; + bucket->count = 1; + } else if (bucket->count == 1) { + INTSET_TYPE old = bucket->v.val; + bucket->v.data = (INTSET_TYPE*)malloc(2 * sizeof(INTSET_TYPE)); + if (!bucket->v.data) { + bucket->v.val = old; + return false; /* error */ + } + bucket->v.data[0] = old; + bucket->v.data[1] = val; + bucket->count = 2; + } else { + size_t new_bucket_size; + INTSET_TYPE* new_bucket_data; + + new_bucket_size = (bucket->count + 1) * sizeof(INTSET_TYPE); + new_bucket_data = (INTSET_TYPE*)realloc(bucket->v.data, new_bucket_size); + if (!new_bucket_data) + return false; /* error */ + bucket->v.data = new_bucket_data; + bucket->v.data[bucket->count++] = val; + } + return true; /* success */ +} + + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#undef INTSET_FUNC +#undef INTSET_BUCKET +#undef INTSET_UNION diff --git a/kvui.py b/kvui.py index 5e1b0fc03048..f83590a819d5 100644 --- a/kvui.py +++ b/kvui.py @@ -2,6 +2,8 @@ import logging import sys import typing +import re +from collections import deque if sys.platform == "win32": import ctypes @@ -63,7 +65,7 @@ fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25) from NetUtils import JSONtoTextParser, JSONMessagePart, SlotType -from Utils import async_start +from Utils import async_start, get_input_text_from_response if typing.TYPE_CHECKING: import CommonClient @@ -72,6 +74,8 @@ else: context_type = object +remove_between_brackets = re.compile(r"\[.*?]") + # I was surprised to find this didn't already exist in kivy :( class HoverBehavior(object): @@ -129,6 +133,8 @@ def __init__(self, *args, **kwargs): self.layout.bind(minimum_height=self.layout.setter("height")) self.add_widget(self.layout) self.effect_cls = ScrollEffect + self.bar_width = dp(12) + self.scroll_type = ["content", "bars"] class HovererableLabel(HoverBehavior, Label): @@ -280,16 +286,10 @@ def on_touch_down(self, touch): temp = MarkupLabel(text=self.text).markup text = "".join(part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]"))) cmdinput = App.get_running_app().textinput - if not cmdinput.text and " did you mean " in text: - for question in ("Didn't find something that closely matches, did you mean ", - "Too many close matches, did you mean "): - if text.startswith(question): - name = Utils.get_text_between(text, question, - "? (") - cmdinput.text = f"!{App.get_running_app().last_autofillable_command} {name}" - break - elif not cmdinput.text and text.startswith("Missing: "): - cmdinput.text = text.replace("Missing: ", "!hint_location ") + if not cmdinput.text: + input_text = get_input_text_from_response(text, App.get_running_app().last_autofillable_command) + if input_text is not None: + cmdinput.text = input_text Clipboard.copy(text.replace("&", "&").replace("&bl;", "[").replace("&br;", "]")) return self.parent.select_with_touch(self.index, touch) @@ -303,7 +303,6 @@ class HintLabel(RecycleDataViewBehavior, BoxLayout): selected = BooleanProperty(False) striped = BooleanProperty(False) index = None - no_select = [] def __init__(self): super(HintLabel, self).__init__() @@ -321,9 +320,7 @@ def set_height(self, instance, value): def refresh_view_attrs(self, rv, index, data): self.index = index - if "select" in data and not data["select"] and index not in self.no_select: - self.no_select.append(index) - self.striped = data["striped"] + self.striped = data.get("striped", False) self.receiving_text = data["receiving"]["text"] self.item_text = data["item"]["text"] self.finding_text = data["finding"]["text"] @@ -337,24 +334,44 @@ def on_touch_down(self, touch): """ Add selection on touch down """ if super(HintLabel, self).on_touch_down(touch): return True - if self.index not in self.no_select: + if self.index: # skip header if self.collide_point(*touch.pos): if self.selected: self.parent.clear_selection() else: - text = "".join([self.receiving_text, "\'s ", self.item_text, " is at ", self.location_text, " in ", + text = "".join((self.receiving_text, "\'s ", self.item_text, " is at ", self.location_text, " in ", self.finding_text, "\'s World", (" at " + self.entrance_text) if self.entrance_text != "Vanilla" - else "", ". (", self.found_text.lower(), ")"]) + else "", ". (", self.found_text.lower(), ")")) temp = MarkupLabel(text).markup text = "".join( part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]"))) Clipboard.copy(escape_markup(text).replace("&", "&").replace("&bl;", "[").replace("&br;", "]")) return self.parent.select_with_touch(self.index, touch) + else: + parent = self.parent + parent.clear_selection() + parent: HintLog = parent.parent + # find correct column + for child in self.children: + if child.collide_point(*touch.pos): + key = child.sort_key + parent.hint_sorter = lambda element: remove_between_brackets.sub("", element[key]["text"]).lower() + if key == parent.sort_key: + # second click reverses order + parent.reversed = not parent.reversed + else: + parent.sort_key = key + parent.reversed = False + break + else: + logging.warning("Did not find clicked header for sorting.") + + App.get_running_app().update_hints() def apply_selection(self, rv, index, is_selected): """ Respond to the selection of items in the view. """ - if self.index not in self.no_select: + if self.index: self.selected = is_selected @@ -364,6 +381,57 @@ def insert_text(self, substring, from_undo=False): return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo) +def is_command_input(string: str) -> bool: + return len(string) > 0 and string[0] in "/!" + + +class CommandPromptTextInput(TextInput): + MAXIMUM_HISTORY_MESSAGES = 50 + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + self._command_history_index = -1 + self._command_history: typing.Deque[str] = deque(maxlen=CommandPromptTextInput.MAXIMUM_HISTORY_MESSAGES) + + def update_history(self, new_entry: str) -> None: + self._command_history_index = -1 + if is_command_input(new_entry): + self._command_history.appendleft(new_entry) + + def keyboard_on_key_down( + self, + window, + keycode: typing.Tuple[int, str], + text: typing.Optional[str], + modifiers: typing.List[str] + ) -> bool: + """ + :param window: The kivy window object + :param keycode: A tuple of (keycode, keyname). Keynames are always lowercase + :param text: The text printed by this key, not accounting for modifiers, or `None` if no text. + Seems to pretty naively interpret the keycode as unicode, so numlock can return odd characters. + :param modifiers: A list of string modifiers, like `ctrl` or `numlock` + """ + if keycode[1] == 'up': + self._change_to_history_text_if_available(self._command_history_index + 1) + return True + if keycode[1] == 'down': + self._change_to_history_text_if_available(self._command_history_index - 1) + return True + return super().keyboard_on_key_down(window, keycode, text, modifiers) + + def _change_to_history_text_if_available(self, new_index: int) -> None: + if new_index < -1: + return + if new_index >= len(self._command_history): + return + self._command_history_index = new_index + if new_index == -1: + self.text = "" + return + self.text = self._command_history[self._command_history_index] + + class MessageBox(Popup): class MessageBoxLabel(Label): def __init__(self, **kwargs): @@ -399,7 +467,7 @@ def __init__(self, ctx: context_type): self.commandprocessor = ctx.command_processor(ctx) self.icon = r"data/icon.png" self.json_to_kivy_parser = KivyJSONtoTextParser(ctx) - self.log_panels = {} + self.log_panels: typing.Dict[str, Widget] = {} # keep track of last used command to autofill on click self.last_autofillable_command = "hint" @@ -483,7 +551,7 @@ def connect_bar_validate(sender): info_button = Button(size=(dp(100), dp(30)), text="Command:", size_hint_x=None) info_button.bind(on_release=self.command_button_action) bottom_layout.add_widget(info_button) - self.textinput = TextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False) + self.textinput = CommandPromptTextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False) self.textinput.bind(on_text_validate=self.on_message) self.textinput.text_validate_unfocus = False bottom_layout.add_widget(self.textinput) @@ -527,8 +595,9 @@ def command_button_action(self, button): "!help for server commands.") def connect_button_action(self, button): + self.ctx.username = None + self.ctx.password = None if self.ctx.server: - self.ctx.username = None async_start(self.ctx.disconnect()) else: async_start(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", ""))) @@ -541,14 +610,18 @@ def on_stop(self): self.ctx.exit_event.set() - def on_message(self, textinput: TextInput): + def on_message(self, textinput: CommandPromptTextInput): try: input_text = textinput.text.strip() textinput.text = "" + textinput.update_history(input_text) if self.ctx.input_requests > 0: self.ctx.input_requests -= 1 self.ctx.input_queue.put_nowait(input_text) + elif is_command_input(input_text): + self.ctx.on_ui_command(input_text) + self.commandprocessor(input_text) elif input_text: self.commandprocessor(input_text) @@ -646,25 +719,33 @@ class HintLog(RecycleView): "entrance": {"text": "[u]Entrance[/u]"}, "found": {"text": "[u]Status[/u]"}, "striped": True, - "select": False, } + sort_key: str = "" + reversed: bool = False + def __init__(self, parser): super(HintLog, self).__init__() self.data = [self.header] self.parser = parser def refresh_hints(self, hints): - self.data = [self.header] - striped = False + data = [] for hint in hints: - self.data.append({ - "striped": striped, + data.append({ "receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})}, - "item": {"text": self.parser.handle_node( - {"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})}, + "item": {"text": self.parser.handle_node({ + "type": "item_id", + "text": hint["item"], + "flags": hint["item_flags"], + "player": hint["receiving_player"], + })}, "finding": {"text": self.parser.handle_node({"type": "player_id", "text": hint["finding_player"]})}, - "location": {"text": self.parser.handle_node({"type": "location_id", "text": hint["location"]})}, + "location": {"text": self.parser.handle_node({ + "type": "location_id", + "text": hint["location"], + "player": hint["finding_player"], + })}, "entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text", "color": "blue", "text": hint["entrance"] if hint["entrance"] else "Vanilla"})}, @@ -672,7 +753,22 @@ def refresh_hints(self, hints): "text": self.parser.handle_node({"type": "color", "color": "green" if hint["found"] else "red", "text": "Found" if hint["found"] else "Not Found"})}, }) - striped = not striped + + data.sort(key=self.hint_sorter, reverse=self.reversed) + for i in range(0, len(data), 2): + data[i]["striped"] = True + data.insert(0, self.header) + self.data = data + + @staticmethod + def hint_sorter(element: dict) -> str: + return "" + + def fix_heights(self): + """Workaround fix for divergent texture and layout heights""" + for element in self.children[0].children: + max_height = max(child.texture_size[1] for child in element.children) + element.height = max_height class E(ExceptionHandler): @@ -703,15 +799,17 @@ def __call__(self, *args, **kwargs): def _handle_item_name(self, node: JSONMessagePart): flags = node.get("flags", 0) + item_types = [] if flags & 0b001: # advancement - itemtype = "progression" - elif flags & 0b010: # useful - itemtype = "useful" - elif flags & 0b100: # trap - itemtype = "trap" - else: - itemtype = "normal" - node.setdefault("refs", []).append("Item Class: " + itemtype) + item_types.append("progression") + if flags & 0b010: # useful + item_types.append("useful") + if flags & 0b100: # trap + item_types.append("trap") + if not item_types: + item_types.append("normal") + + node.setdefault("refs", []).append("Item Class: " + ", ".join(item_types)) return super(KivyJSONtoTextParser, self)._handle_item_name(node) def _handle_player_id(self, node: JSONMessagePart): @@ -721,8 +819,10 @@ def _handle_player_id(self, node: JSONMessagePart): text = f"Game: {slot_info.game}
    " \ f"Type: {SlotType(slot_info.type).name}" if slot_info.group_members: - text += f"
    Members:
    " + \ - "
    ".join(self.ctx.player_names[player] for player in slot_info.group_members) + text += f"
    Members:
    " + "
    ".join( + escape_markup(self.ctx.player_names[player]) + for player in slot_info.group_members + ) node.setdefault("refs", []).append(text) return super(KivyJSONtoTextParser, self)._handle_player_id(node) @@ -737,6 +837,10 @@ def _handle_color(self, node: JSONMessagePart): return self._handle_text(node) def _handle_text(self, node: JSONMessagePart): + # All other text goes through _handle_color, and we don't want to escape markup twice, + # or mess up text that already has intentional markup applied to it + if node.get("type", "text") == "text": + node["text"] = escape_markup(node["text"]) for ref in node.get("refs", []): node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]" self.ref_count += 1 diff --git a/playerSettings.yaml b/playerSettings.yaml deleted file mode 100644 index b6b474a9fffa..000000000000 --- a/playerSettings.yaml +++ /dev/null @@ -1,591 +0,0 @@ -# What is this file? -# This file contains options which allow you to configure your multiworld experience while allowing others -# to play how they want as well. - -# How do I use it? -# The options in this file are weighted. This means the higher number you assign to a value, the more -# chances you have for that option to be chosen. For example, an option like this: -# -# map_shuffle: -# on: 5 -# off: 15 -# -# Means you have 5 chances for map shuffle to occur, and 15 chances for map shuffle to be turned off - -# I've never seen a file like this before. What characters am I allowed to use? -# This is a .yaml file. You are allowed to use most characters. -# To test if your yaml is valid or not, you can use this website: -# http://www.yamllint.com/ - -description: Template Name # Used to describe your yaml. Useful if you have multiple files -name: YourName{number} # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit -#{player} will be replaced with the player's slot number. -#{PLAYER} will be replaced with the player's slot number if that slot number is greater than 1. -#{number} will be replaced with the counter value of the name. -#{NUMBER} will be replaced with the counter value of the name if the counter value is greater than 1. -game: # Pick a game to play - A Link to the Past: 1 -requires: - version: 0.4.4 # Version of Archipelago required for this yaml to work as expected. -A Link to the Past: - progression_balancing: - # A system that can move progression earlier, to try and prevent the player from getting stuck and bored early. - # A lower setting means more getting stuck. A higher setting means less getting stuck. - # - # You can define additional values between the minimum and maximum values. - # Minimum value is 0 - # Maximum value is 99 - random: 0 - random-low: 0 - random-high: 0 - disabled: 0 # equivalent to 0 - normal: 50 # equivalent to 50 - extreme: 0 # equivalent to 99 - - accessibility: - # Set rules for reachability of your items/locations. - # Locations: ensure everything can be reached and acquired. - # Items: ensure all logically relevant items can be acquired. - # Minimal: ensure what is needed to reach your goal can be acquired. - locations: 0 - items: 50 - minimal: 0 - - local_items: - # Forces these items to be in their native world. - [ ] - - non_local_items: - # Forces these items to be outside their native world. - [ ] - - start_inventory: - # Start with these items. - { } - - start_hints: - # Start with these item's locations prefilled into the !hint command. - [ ] - - start_location_hints: - # Start with these locations and their item prefilled into the !hint command - [ ] - - exclude_locations: - # Prevent these locations from having an important item - [ ] - - priority_locations: - # Prevent these locations from having an unimportant item - [ ] - - item_links: - # Share part of your item pool with other players. - [ ] - - ### Logic Section ### - glitches_required: # Determine the logic required to complete the seed - none: 50 # No glitches required - minor_glitches: 0 # Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic - overworld_glitches: 0 # Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches - hybrid_major_glitches: 0 # In addition to overworld glitches, also requires underworld clips between dungeons. - no_logic: 0 # Your own items are placed with no regard to any logic; such as your Fire Rod can be on your Trinexx. - # Other players items are placed into your world under HMG logic - dark_room_logic: # Logic for unlit dark rooms - lamp: 50 # require the Lamp for these rooms to be considered accessible. - torches: 0 # in addition to lamp, allow the fire rod and presence of easily accessible torches for access - none: 0 # all dark rooms are always considered doable, meaning this may force completion of rooms in complete darkness - restrict_dungeon_item_on_boss: # aka ambrosia boss items - on: 0 # prevents unshuffled compasses, maps and keys to be boss drops, they can still drop keysanity and other players' items - off: 50 - ### End of Logic Section ### - bigkey_shuffle: # Big Key Placement - original_dungeon: 50 - own_dungeons: 0 - own_world: 0 - any_world: 0 - different_world: 0 - start_with: 0 - smallkey_shuffle: # Small Key Placement - original_dungeon: 50 - own_dungeons: 0 - own_world: 0 - any_world: 0 - different_world: 0 - universal: 0 - start_with: 0 - key_drop_shuffle: # Shuffle keys found in pots or dropped from killed enemies - off: 50 - on: 0 - compass_shuffle: # Compass Placement - original_dungeon: 50 - own_dungeons: 0 - own_world: 0 - any_world: 0 - different_world: 0 - start_with: 0 - map_shuffle: # Map Placement - original_dungeon: 50 - own_dungeons: 0 - own_world: 0 - any_world: 0 - different_world: 0 - start_with: 0 - dungeon_counters: - on: 0 # Always display amount of items checked in a dungeon - pickup: 50 # Show when compass is picked up - default: 0 # Show when compass is picked up if the compass itself is shuffled - off: 0 # Never show item count in dungeons - progressive: # Enable or disable progressive items (swords, shields, bow) - on: 50 # All items are progressive - off: 0 # No items are progressive - grouped_random: 0 # Randomly decides for all items. Swords could be progressive, shields might not be - entrance_shuffle: - none: 50 # Vanilla game map. All entrances and exits lead to their original locations. You probably want this option - dungeonssimple: 0 # Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon - dungeonsfull: 0 # Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle can be 4 different dungeons, but keep dungeons to a specific world - dungeonscrossed: 0 # like dungeonsfull, but allow cross-world traversal through a dungeon. Warning: May force repeated dungeon traversal - simple: 0 # Entrances are grouped together before being randomized. Simple uses the most strict grouping rules - restricted: 0 # Less strict than simple - full: 0 # Less strict than restricted - crossed: 0 # Less strict than full - insanity: 0 # Very few grouping rules. Good luck - # you can also define entrance shuffle seed, like so: - crossed-1000: 0 # using this method, you can have the same layout as another player and share entrance information - # however, many other settings like logic, world state, retro etc. may affect the shuffle result as well. - crossed-group-myfriends: 0 # using this method, everyone with "group-myfriends" will share the same seed - goals: - ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon - crystals: 0 # Only killing Ganon is required. However, items may still be placed in GT - bosses: 0 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2) - pedestal: 0 # Pull the Triforce from the Master Sword pedestal - ganon_pedestal: 0 # Pull the Master Sword pedestal, then kill Ganon - triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle - local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle - ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon - local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon - ice_rod_hunt: 0 # You start with everything needed to 216 the seed. Find the Ice rod, then kill Trinexx at Turtle rock. - open_pyramid: - goal: 50 # Opens the pyramid if the goal requires you to kill Ganon, unless the goal is Slow Ganon or All Dungeons - auto: 0 # Same as Goal, but also is closed if holes are shuffled and ganon is part of the shuffle pool - open: 0 # Pyramid hole is always open. Ganon's vulnerable condition is still required before he can he hurt - closed: 0 # Pyramid hole is always closed until you defeat Agahnim atop Ganon's Tower - triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces. - extra: 0 # available = triforce_pieces_extra + triforce_pieces_required - percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required - available: 50 # available = triforce_pieces_available - triforce_pieces_extra: # Set to how many extra triforces pieces are available to collect in the world. - # Format "pieces: chance" - 0: 0 - 5: 50 - 10: 50 - 15: 0 - 20: 0 - triforce_pieces_percentage: # Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world. - # Format "pieces: chance" - 100: 0 #No extra - 150: 50 #Half the required will be added as extra - 200: 0 #There are the double of the required ones available. - triforce_pieces_available: # Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1 - # Format "pieces: chance" - 25: 0 - 30: 50 - 40: 0 - 50: 0 - triforce_pieces_required: # Set to how many out of X triforce pieces you need to win the game in a triforce hunt. Default is 20. Max is 90, Min is 1 - # Format "pieces: chance" - 15: 0 - 20: 50 - 30: 0 - 40: 0 - 50: 0 - crystals_needed_for_gt: # Crystals required to open GT - 0: 0 - 7: 50 - random: 0 - random-low: 0 # any valid number, weighted towards the lower end - random-middle: 0 # any valid number, weighted towards the central range - random-high: 0 # any valid number, weighted towards the higher end - crystals_needed_for_ganon: # Crystals required to hurt Ganon - 0: 0 - 7: 50 - random: 0 - random-low: 0 - random-middle: 0 - random-high: 0 - mode: - standard: 0 # Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary - open: 50 # Begin the game from your choice of Link's House or the Sanctuary - inverted: 0 # Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered - retro_bow: - on: 0 # Zelda-1 like mode. You have to purchase a quiver to shoot arrows using rupees. - off: 50 - retro_caves: - on: 0 # Zelda-1 like mode. There are randomly placed take-any caves that contain one Sword and choices of Heart Container/Blue Potion. - off: 50 - hints: # On/Full: Put item and entrance placement hints on telepathic tiles and some NPCs, Full removes joke hints. - 'on': 50 - 'off': 0 - full: 0 - scams: # If on, these Merchants will no longer tell you what they're selling. - 'off': 50 - 'king_zora': 0 - 'bottle_merchant': 0 - 'all': 0 - swordless: - on: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change - off: 1 - item_pool: - easy: 0 # Doubled upgrades, progressives, and etc - normal: 50 # Item availability remains unchanged from vanilla game - hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless) - expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless) - item_functionality: - easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere. - normal: 50 # Vanilla item functionality - hard: 0 # Reduced helpfulness of items (potions less effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs do not stun, silvers disabled outside ganon) - expert: 0 # Vastly reduces the helpfulness of items (potions barely effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs and hookshot do not stun, silvers disabled outside ganon) - tile_shuffle: # Randomize the tile layouts in flying tile rooms - on: 0 - off: 50 - misery_mire_medallion: # required medallion to open Misery Mire front entrance - random: 50 - Ether: 0 - Bombos: 0 - Quake: 0 - turtle_rock_medallion: # required medallion to open Turtle Rock front entrance - random: 50 - Ether: 0 - Bombos: 0 - Quake: 0 - ### Enemizer Section ### - boss_shuffle: - none: 50 # Vanilla bosses - basic: 0 # Existing bosses except Ganon and Agahnim are shuffled throughout dungeons - full: 0 # 3 bosses can occur twice - chaos: 0 # Any boss can appear any amount of times - singularity: 0 # Picks a boss, tries to put it everywhere that works, if there's spaces remaining it picks a boss to fill those - enemy_shuffle: # Randomize enemy placement - on: 0 - off: 50 - killable_thieves: # Make thieves killable - on: 0 # Usually turned on together with enemy_shuffle to make annoying thief placement more manageable - off: 50 - bush_shuffle: # Randomize the chance that bushes have enemies and the enemies under said bush - on: 0 - off: 50 - enemy_damage: - default: 50 # Vanilla enemy damage - shuffled: 0 # Enemies deal 0 to 4 hearts and armor helps - chaos: 0 # Enemies deal 0 to 8 hearts and armor just reshuffles the damage - enemy_health: - default: 50 # Vanilla enemy HP - easy: 0 # Enemies have reduced health - hard: 0 # Enemies have increased health - expert: 0 # Enemies have greatly increased health - pot_shuffle: - 'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile - 'off': 50 # Default pot item locations - ### End of Enemizer Section ### - ### Beemizer ### - # can add weights for any whole number between 0 and 100 - beemizer_total_chance: # Remove items from the global item pool and replace them with single bees (fill bottles) and bee traps - 0: 50 # No junk fill items are replaced (Beemizer is off) - 25: 0 # 25% chance for each junk fill item (rupees, bombs and arrows) to be replaced with bees - 50: 0 # 50% chance for each junk fill item (rupees, bombs and arrows) to be replaced with bees - 75: 0 # 75% chance for each junk fill item (rupees, bombs and arrows) to be replaced with bees - 100: 0 # All junk fill items (rupees, bombs and arrows) are replaced with bees - beemizer_trap_chance: - 60: 50 # 60% chance for each beemizer replacement to be a trap, 40% chance to be a single bee - 70: 0 # 70% chance for each beemizer replacement to be a trap, 30% chance to be a single bee - 80: 0 # 80% chance for each beemizer replacement to be a trap, 20% chance to be a single bee - 90: 0 # 90% chance for each beemizer replacement to be a trap, 10% chance to be a single bee - 100: 0 # All beemizer replacements are traps - ### Shop Settings ### - shop_item_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl) - 0: 50 - 5: 0 - 15: 0 - 30: 0 - random: 0 # 0 to 30 evenly distributed - shop_price_modifier: # Percentage modifier for shuffled item prices in shops - # you can add additional values between minimum and maximum - 0: 0 # minimum value - 400: 0 # maximum value - random: 0 - random-low: 0 - random-high: 0 - 100: 50 - shop_shuffle: - none: 50 - g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops - f: 0 # Generate new default inventories for every shop independently - i: 0 # Shuffle default inventories of the shops around - p: 0 # Randomize the prices of the items in shop inventories - u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld) - w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too - P: 0 # Prices of the items in shop inventories cost hearts, arrow, or bombs instead of rupees - ip: 0 # Shuffle inventories and randomize prices - fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool - uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool - # You can add more combos - ### End of Shop Section ### - shuffle_prizes: # aka drops - none: 0 # do not shuffle prize packs - g: 50 # shuffle "general" prize packs, as in enemy, tree pull, dig etc. - b: 0 # shuffle "bonk" prize packs - bg: 0 # shuffle both - timer: - none: 50 # No timer will be displayed. - timed: 0 # Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end. - timed_ohko: 0 # Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit. - ohko: 0 # Timer always at zero. Permanent OHKO. - timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though. - display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool. - countdown_start_time: # For timed_ohko and timed_countdown timer modes, the amount of time in minutes to start with - 0: 0 # For timed_ohko, starts in OHKO mode when starting the game - 10: 50 - 20: 0 - 30: 0 - 60: 0 - red_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock - -2: 50 - 1: 0 - blue_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock - 1: 0 - 2: 50 - green_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock - 4: 50 - 10: 0 - 15: 0 - glitch_boots: - on: 50 # Start with Pegasus Boots in any glitched logic mode that makes use of them - off: 0 - # rom options section - random_sprite_on_event: # An alternative to specifying randomonhit / randomonexit / etc... in sprite down below. - enabled: # If enabled, sprite down below is ignored completely, (although it may become the sprite pool) - on: 0 - off: 1 - on_hit: # Random sprite on hit. Being hit by things that cause 0 damage still counts. - on: 1 - off: 0 - on_enter: # Random sprite on underworld entry. Note that entering hobo counts. - on: 0 - off: 1 - on_exit: # Random sprite on underworld exit. Exiting hobo does not count. - on: 0 - off: 1 - on_slash: # Random sprite on sword slash. Note, it still counts if you attempt to slash while swordless. - on: 0 - off: 1 - on_item: # Random sprite on getting an item. Anything that causes you to hold an item above your head counts. - on: 0 - off: 1 - on_bonk: # Random sprite on bonk. - on: 0 - off: 1 - on_everything: # Random sprite on ALL currently implemented events, even if not documented at present time. - on: 0 - off: 1 - use_weighted_sprite_pool: # Always on if no sprite_pool exists, otherwise it controls whether to use sprite as a weighted sprite pool - on: 0 - off: 1 - #sprite_pool: # When specified, limits the pool of sprites used for randomon-event to the specified pool. Uncomment to use this. - # - link - # - pride link - # - penguin link - # - random # You can specify random multiple times for however many potentially unique random sprites you want in your pool. - sprite: # Enter the name of your preferred sprite and weight it appropriately - random: 0 - randomonhit: 0 # Random sprite on hit - randomonenter: 0 # Random sprite on entering the underworld. - randomonexit: 0 # Random sprite on exiting the underworld. - randomonslash: 0 # Random sprite on sword slashes - randomonitem: 0 # Random sprite on getting items. - randomonbonk: 0 # Random sprite on bonk. - # You can combine these events like this. randomonhit-enter-exit if you want it on hit, enter, exit. - randomonall: 0 # Random sprite on any and all currently supported events. Refer to above for the supported events. - Link: 50 # To add other sprites: open the gui/Creator, go to adjust, select a sprite and write down the name the gui calls it - music: # If "off", all in-game music will be disabled - on: 50 - off: 0 - quickswap: # Enable switching items by pressing the L+R shoulder buttons - on: 50 - off: 0 - triforcehud: # Disable visibility of the triforce hud unless collecting a piece or speaking to Murahadala - normal: 0 # original behavior (always visible) - hide_goal: 50 # hide counter until a piece is collected or speaking to Murahadala - hide_required: 0 # Always visible, but required amount is invisible until determined by Murahadala - hide_both: 0 # Hide both under above circumstances - reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more. - on: 50 - off: 0 - menuspeed: # Controls how fast the item menu opens and closes - normal: 50 - instant: 0 - double: 0 - triple: 0 - quadruple: 0 - half: 0 - heartcolor: # Controls the color of your health hearts - red: 50 - blue: 0 - green: 0 - yellow: 0 - random: 0 - heartbeep: # Controls the frequency of the low-health beeping - double: 0 - normal: 50 - half: 0 - quarter: 0 - off: 0 - ow_palettes: # Change the colors of the overworld - default: 50 # No changes - good: 0 # Shuffle the colors, with harmony in mind - blackout: 0 # everything black / blind mode - grayscale: 0 - negative: 0 - classic: 0 - dizzy: 0 - sick: 0 - puke: 0 - uw_palettes: # Change the colors of caves and dungeons - default: 50 # No changes - good: 0 # Shuffle the colors, with harmony in mind - blackout: 0 # everything black / blind mode - grayscale: 0 - negative: 0 - classic: 0 - dizzy: 0 - sick: 0 - puke: 0 - hud_palettes: # Change the colors of the hud - default: 50 # No changes - good: 0 # Shuffle the colors, with harmony in mind - blackout: 0 # everything black / blind mode - grayscale: 0 - negative: 0 - classic: 0 - dizzy: 0 - sick: 0 - puke: 0 - sword_palettes: # Change the colors of swords - default: 50 # No changes - good: 0 # Shuffle the colors, with harmony in mind - blackout: 0 # everything black / blind mode - grayscale: 0 - negative: 0 - classic: 0 - dizzy: 0 - sick: 0 - puke: 0 - shield_palettes: # Change the colors of shields - default: 50 # No changes - good: 0 # Shuffle the colors, with harmony in mind - blackout: 0 # everything black / blind mode - grayscale: 0 - negative: 0 - classic: 0 - dizzy: 0 - sick: 0 - puke: 0 - - # triggers that replace options upon rolling certain options - legacy_weapons: # this is not an actual option, just a set of weights to trigger from - trigger_disabled: 50 - randomized: 0 # Swords are placed randomly throughout the world - assured: 0 # Begin with a sword, the rest are placed randomly throughout the world - vanilla: 0 # Swords are placed in vanilla locations in your own game (Uncle, Pyramid Fairy, Smiths, Pedestal) - swordless: 0 # swordless mode - - death_link: - false: 50 - true: 0 - - allow_collect: # Allows for !collect / co-op to auto-open chests containing items for other players. - # Off by default, because it currently crashes on real hardware. - false: 50 - true: 0 - -linked_options: - - name: crosskeys - options: # These overwrite earlier options if the percentage chance triggers - A Link to the Past: - entrance_shuffle: crossed - bigkey_shuffle: true - compass_shuffle: true - map_shuffle: true - smallkey_shuffle: true - percentage: 0 # Set this to the percentage chance you want crosskeys - - name: localcrosskeys - options: # These overwrite earlier options if the percentage chance triggers - A Link to the Past: - entrance_shuffle: crossed - bigkey_shuffle: true - compass_shuffle: true - map_shuffle: true - smallkey_shuffle: true - local_items: # Forces keys to be local to your own world - - "Small Keys" - - "Big Keys" - percentage: 0 # Set this to the percentage chance you want local crosskeys - - name: enemizer - options: - A Link to the Past: - boss_shuffle: # Subchances can be injected too, which then get rolled - basic: 1 - full: 1 - chaos: 1 - singularity: 1 - enemy_damage: - shuffled: 1 - chaos: 1 - enemy_health: - easy: 1 - hard: 1 - expert: 1 - percentage: 0 # Set this to the percentage chance you want enemizer -triggers: - # trigger block for legacy weapons mode, to enable these add weights to legacy_weapons - - option_name: legacy_weapons - option_result: randomized - option_category: A Link to the Past - options: - A Link to the Past: - swordless: off - - option_name: legacy_weapons - option_result: assured - option_category: A Link to the Past - options: - A Link to the Past: - swordless: off - start_inventory: - Progressive Sword: 1 - - option_name: legacy_weapons - option_result: vanilla - option_category: A Link to the Past - options: - A Link to the Past: - swordless: off - plando_items: - - items: - Progressive Sword: 4 - locations: - - Master Sword Pedestal - - Pyramid Fairy - Left - - Blacksmith - - Link's Uncle - - option_name: legacy_weapons - option_result: swordless - option_category: A Link to the Past - options: - A Link to the Past: - swordless: on - # end of legacy weapons block - - option_name: enemy_damage # targets enemy_damage - option_category: A Link to the Past - option_result: shuffled # if it rolls shuffled - percentage: 0 # AND has a 0 percent chance (meaning this is default disabled, just to show how it works) - options: # then inserts these options - A Link to the Past: - swordless: off diff --git a/requirements.txt b/requirements.txt index 9531e3058e8a..db4f5445036a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,13 +2,13 @@ colorama>=0.4.6 websockets>=12.0 PyYAML>=6.0.1 jellyfish>=1.0.3 -jinja2>=3.1.3 -schema>=0.7.5 +jinja2>=3.1.4 +schema>=0.7.7 kivy>=2.3.0 bsdiff4>=1.2.4 -platformdirs>=4.1.0 -certifi>=2023.11.17 -cython>=3.0.8 +platformdirs>=4.2.2 +certifi>=2024.6.2 +cython>=3.0.10 cymem>=2.0.8 -orjson>=3.9.10 -typing-extensions>=4.7.0 +orjson>=3.10.3 +typing_extensions>=4.12.1 diff --git a/settings.py b/settings.py index c58eadf155d7..792770521459 100644 --- a/settings.py +++ b/settings.py @@ -1,8 +1,9 @@ """ Application settings / host.yaml interface using type hints. -This is different from player settings. +This is different from player options. """ +import os import os.path import shutil import sys @@ -11,7 +12,6 @@ from enum import IntEnum from threading import Lock from typing import cast, Any, BinaryIO, ClassVar, Dict, Iterator, List, Optional, TextIO, Tuple, Union, TypeVar -import os __all__ = [ "get_settings", "fmt_doc", "no_gui", @@ -200,7 +200,7 @@ def as_dict(self, *args: str, downcast: bool = True) -> Dict[str, Any]: def _dump_value(cls, value: Any, f: TextIO, indent: str) -> None: """Write a single yaml line to f""" from Utils import dump, Dumper as BaseDumper - yaml_line: str = dump(value, Dumper=cast(BaseDumper, cls._dumper)) + yaml_line: str = dump(value, Dumper=cast(BaseDumper, cls._dumper), width=2**31-1) assert yaml_line.count("\n") == 1, f"Unexpected input for yaml dumper: {value}" f.write(f"{indent}{yaml_line}") @@ -643,17 +643,6 @@ class Spoiler(IntEnum): PLAYTHROUGH = 2 FULL = 3 - class GlitchTriforceRoom(IntEnum): - """ - Glitch to Triforce room from Ganon - When disabled, you have to have a weapon that can hurt ganon (master sword or swordless/easy item functionality - + hammer) and have completed the goal required for killing ganon to be able to access the triforce room. - 1 -> Enabled. - 0 -> Disabled (except in no-logic) - """ - OFF = 0 - ON = 1 - class PlandoOptions(str): """ List of options that can be plando'd. Can be combined, for example "bosses, items" @@ -665,15 +654,23 @@ class Race(IntEnum): OFF = 0 ON = 1 + class PanicMethod(str): + """ + What to do if the current item placements appear unsolvable. + raise -> Raise an exception and abort. + swap -> Attempt to fix it by swapping prior placements around. (Default) + start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations. + """ + enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows player_files_path: PlayerFilesPath = PlayerFilesPath("Players") players: Players = Players(0) weights_file_path: WeightsFilePath = WeightsFilePath("weights.yaml") meta_file_path: MetaFilePath = MetaFilePath("meta.yaml") spoiler: Spoiler = Spoiler(3) - glitch_triforce_room: GlitchTriforceRoom = GlitchTriforceRoom(1) # why is this here? race: Race = Race(0) plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts") + panic_method: PanicMethod = PanicMethod("swap") class SNIOptions(Group): @@ -801,6 +798,7 @@ def autosave() -> None: atexit.register(autosave) def save(self, location: Optional[str] = None) -> None: # as above + from Utils import parse_yaml location = location or self._filename assert location, "No file specified" temp_location = location + ".tmp" # not using tempfile to test expected file access @@ -810,10 +808,18 @@ def save(self, location: Optional[str] = None) -> None: # as above # can't use utf-8-sig because it breaks backward compat: pyyaml on Windows with bytes does not strip the BOM with open(temp_location, "w", encoding="utf-8") as f: self.dump(f) - # replace old with new - if os.path.exists(location): + f.flush() + if hasattr(os, "fsync"): + os.fsync(f.fileno()) + # validate new file is valid yaml + with open(temp_location, encoding="utf-8") as f: + parse_yaml(f.read()) + # replace old with new, try atomic operation first + try: + os.rename(temp_location, location) + except (OSError, FileExistsError): os.unlink(location) - os.rename(temp_location, location) + os.rename(temp_location, location) self._filename = location def dump(self, f: TextIO, level: int = 0) -> None: @@ -835,7 +841,6 @@ def get_settings() -> Settings: with _lock: # make sure we only have one instance res = getattr(get_settings, "_cache", None) if not res: - import os from Utils import user_path, local_path filenames = ("options.yaml", "host.yaml") locations: List[str] = [] diff --git a/setup.py b/setup.py index 68cab1d5e25a..cb4d1a7511b6 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ # This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it try: - requirement = 'cx-Freeze>=6.15.10' + requirement = 'cx-Freeze==7.2.0' import pkg_resources try: pkg_resources.require(requirement) @@ -84,7 +84,6 @@ # LogicMixin is broken before 3.10 import revamp if sys.version_info < (3,10): non_apworlds.add("Hollow Knight") - non_apworlds.add("Starcraft 2 Wings of Liberty") def download_SNI(): print("Updating SNI") @@ -191,7 +190,7 @@ def resolve_icon(icon_name: str): c = next(component for component in components if component.script_name == "Launcher") exes.append(cx_Freeze.Executable( script=f"{c.script_name}.py", - target_name=f"{c.frozen_name}(DEBUG).exe", + target_name=f"{c.frozen_name}Debug.exe", icon=resolve_icon(c.icon), )) @@ -229,8 +228,8 @@ def finalize_options(self): # Override cx_Freeze's build_exe command for pre and post build steps -class BuildExeCommand(cx_Freeze.command.build_exe.BuildEXE): - user_options = cx_Freeze.command.build_exe.BuildEXE.user_options + [ +class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): + user_options = cx_Freeze.command.build_exe.build_exe.user_options + [ ('yes', 'y', 'Answer "yes" to all questions.'), ('extra-data=', None, 'Additional files to add.'), ] diff --git a/test/bases.py b/test/bases.py index 07a3e6008629..5c2d241cbbfe 100644 --- a/test/bases.py +++ b/test/bases.py @@ -221,7 +221,7 @@ def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None: if isinstance(items, Item): items = (items,) for item in items: - if item.location and item.location.event and item.location in self.multiworld.state.events: + if item.location and item.advancement and item.location in self.multiworld.state.events: self.multiworld.state.events.remove(item.location) self.multiworld.state.remove(item) @@ -292,12 +292,12 @@ def test_all_state_can_reach_everything(self): """Ensure all state can reach everything and complete the game with the defined options""" if not (self.run_default_tests and self.constructed): return - with self.subTest("Game", game=self.game): + with self.subTest("Game", game=self.game, seed=self.multiworld.seed): excluded = self.multiworld.worlds[self.player].options.exclude_locations.value state = self.multiworld.get_all_state(False) for location in self.multiworld.get_locations(): if location.name not in excluded: - with self.subTest("Location should be reached", location=location): + with self.subTest("Location should be reached", location=location.name): reachable = location.can_reach(state) self.assertTrue(reachable, f"{location.name} unreachable") with self.subTest("Beatable"): @@ -308,7 +308,7 @@ def test_empty_state_can_reach_something(self): """Ensure empty state can reach at least one location with the defined options""" if not (self.run_default_tests and self.constructed): return - with self.subTest("Game", game=self.game): + with self.subTest("Game", game=self.game, seed=self.multiworld.seed): state = CollectionState(self.multiworld) locations = self.multiworld.get_reachable_locations(state, self.player) self.assertGreater(len(locations), 0, @@ -329,7 +329,7 @@ def fulfills_accessibility() -> bool: for n in range(len(locations) - 1, -1, -1): if locations[n].can_reach(state): sphere.append(locations.pop(n)) - self.assertTrue(sphere or self.multiworld.accessibility[1] == "minimal", + self.assertTrue(sphere or self.multiworld.worlds[1].options.accessibility == "minimal", f"Unreachable locations: {locations}") if not sphere: break diff --git a/test/cpp/CMakeLists.txt b/test/cpp/CMakeLists.txt new file mode 100644 index 000000000000..927b7494dac4 --- /dev/null +++ b/test/cpp/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.5) +project(ap-cpp-tests) + +enable_testing() + +find_package(GTest REQUIRED) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_definitions("/source-charset:utf-8") + set(CMAKE_CXX_FLAGS_DEBUG "/MTd") + set(CMAKE_CXX_FLAGS_RELEASE "/MT") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # enable static analysis for gcc + add_compile_options(-fanalyzer -Werror) + # disable stuff that gets triggered by googletest + add_compile_options(-Wno-analyzer-malloc-leak) + # enable asan for gcc + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif () + +add_executable(test_default) + +target_include_directories(test_default + PRIVATE + ${GTEST_INCLUDE_DIRS} +) + +target_link_libraries(test_default + ${GTEST_BOTH_LIBRARIES} +) + +add_test( + NAME test_default + COMMAND test_default +) + +set_property( + TEST test_default + PROPERTY ENVIRONMENT "ASAN_OPTIONS=allocator_may_return_null=1" +) + +file(GLOB ITEMS *) +foreach(item ${ITEMS}) + if(IS_DIRECTORY ${item} AND EXISTS ${item}/CMakeLists.txt) + message(${item}) + add_subdirectory(${item}) + endif() +endforeach() diff --git a/test/cpp/README.md b/test/cpp/README.md new file mode 100644 index 000000000000..792b9be77e72 --- /dev/null +++ b/test/cpp/README.md @@ -0,0 +1,32 @@ +# C++ tests + +Test framework for C and C++ code in AP. + +## Adding a Test + +### GoogleTest + +Adding GoogleTests is as simple as creating a directory with +* one or more `test_*.cpp` files that define tests using + [GoogleTest API](https://google.github.io/googletest/) +* a `CMakeLists.txt` that adds the .cpp files to `test_default` target using + [target_sources](https://cmake.org/cmake/help/latest/command/target_sources.html) + +### CTest + +If either GoogleTest is not suitable for the test or the build flags / sources / libraries are incompatible, +you can add another CTest to the project using add_target and add_test, similar to how it's done for `test_default`. + +## Running Tests + +* Install [CMake](https://cmake.org/). +* Build and/or install GoogleTest and make sure + [CMake can find it](https://cmake.org/cmake/help/latest/module/FindGTest.html), or + [create a parent `CMakeLists.txt` that fetches GoogleTest](https://google.github.io/googletest/quickstart-cmake.html). +* Enter the directory with the top-most `CMakeLists.txt` and run + ```sh + mkdir build + cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Release + cmake --build build/ --config Release && \ + ctest --test-dir build/ -C Release --output-on-failure + ``` diff --git a/test/cpp/intset/CMakeLists.txt b/test/cpp/intset/CMakeLists.txt new file mode 100644 index 000000000000..175e0bd0b9e8 --- /dev/null +++ b/test/cpp/intset/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(test_default + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/test_intset.cpp +) diff --git a/test/cpp/intset/test_intset.cpp b/test/cpp/intset/test_intset.cpp new file mode 100644 index 000000000000..2f85bea960c4 --- /dev/null +++ b/test/cpp/intset/test_intset.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +// uint32Set +#define INTSET_NAME uint32Set +#define INTSET_TYPE uint32_t +#include "../../../intset.h" +#undef INTSET_NAME +#undef INTSET_TYPE + +// int64Set +#define INTSET_NAME int64Set +#define INTSET_TYPE int64_t +#include "../../../intset.h" + + +TEST(IntsetTest, ZeroBuckets) +{ + // trying to allocate with zero buckets has to either fail or be functioning + uint32Set *set = uint32Set_new(0); + if (!set) + return; // failed -> OK + + EXPECT_FALSE(uint32Set_contains(set, 1)); + EXPECT_TRUE(uint32Set_add(set, 1)); + EXPECT_TRUE(uint32Set_contains(set, 1)); + uint32Set_free(set); +} + +TEST(IntsetTest, Duplicate) +{ + // adding the same number again can't fail + uint32Set *set = uint32Set_new(2); + ASSERT_TRUE(set); + EXPECT_TRUE(uint32Set_add(set, 0)); + EXPECT_TRUE(uint32Set_add(set, 0)); + EXPECT_TRUE(uint32Set_contains(set, 0)); + uint32Set_free(set); +} + +TEST(IntsetTest, SetAllocFailure) +{ + // try to allocate 100TB of RAM, should fail and return NULL + if (sizeof(size_t) < 8) + GTEST_SKIP() << "Alloc error not testable on 32bit"; + int64Set *set = int64Set_new(6250000000000ULL); + EXPECT_FALSE(set); + int64Set_free(set); +} + +TEST(IntsetTest, SetAllocOverflow) +{ + // try to overflow argument passed to malloc + int64Set *set = int64Set_new(std::numeric_limits::max()); + EXPECT_FALSE(set); + int64Set_free(set); +} + +TEST(IntsetTest, NullFree) +{ + // free(NULL) should not try to free buckets + uint32Set_free(NULL); + int64Set_free(NULL); +} + +TEST(IntsetTest, BucketRealloc) +{ + // add a couple of values to the same bucket to test growing the bucket + uint32Set* set = uint32Set_new(1); + ASSERT_TRUE(set); + EXPECT_FALSE(uint32Set_contains(set, 0)); + EXPECT_TRUE(uint32Set_add(set, 0)); + EXPECT_TRUE(uint32Set_contains(set, 0)); + for (uint32_t i = 1; i < 32; ++i) { + EXPECT_TRUE(uint32Set_add(set, i)); + EXPECT_TRUE(uint32Set_contains(set, i - 1)); + EXPECT_TRUE(uint32Set_contains(set, i)); + EXPECT_FALSE(uint32Set_contains(set, i + 1)); + } + uint32Set_free(set); +} + +TEST(IntSet, Max) +{ + constexpr auto n = std::numeric_limits::max(); + uint32Set *set = uint32Set_new(1); + ASSERT_TRUE(set); + EXPECT_FALSE(uint32Set_contains(set, n)); + EXPECT_TRUE(uint32Set_add(set, n)); + EXPECT_TRUE(uint32Set_contains(set, n)); + uint32Set_free(set); +} + +TEST(InsetTest, Negative) +{ + constexpr auto n = std::numeric_limits::min(); + static_assert(n < 0, "n not negative"); + int64Set *set = int64Set_new(3); + ASSERT_TRUE(set); + EXPECT_FALSE(int64Set_contains(set, n)); + EXPECT_TRUE(int64Set_add(set, n)); + EXPECT_TRUE(int64Set_contains(set, n)); + int64Set_free(set); +} diff --git a/test/general/__init__.py b/test/general/__init__.py index fe890e0b340b..8afd84976540 100644 --- a/test/general/__init__.py +++ b/test/general/__init__.py @@ -1,7 +1,8 @@ from argparse import Namespace from typing import List, Optional, Tuple, Type, Union -from BaseClasses import CollectionState, MultiWorld +from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region +from worlds import network_data_package from worlds.AutoWorld import World, call_all gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill") @@ -17,19 +18,21 @@ def setup_solo_multiworld( :param steps: The gen steps that should be called on the generated multiworld before returning. Default calls steps through pre_fill :param seed: The seed to be used when creating this multiworld + :return: The generated multiworld """ return setup_multiworld(world_type, steps, seed) def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps, - seed: Optional[int] = None) -> MultiWorld: + seed: Optional[int] = None) -> MultiWorld: """ Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and calling the provided gen steps. - :param worlds: type/s of worlds to generate a multiworld for - :param steps: gen steps that should be called before returning. Default calls through pre_fill + :param worlds: Type/s of worlds to generate a multiworld for + :param steps: Gen steps that should be called before returning. Default calls through pre_fill :param seed: The seed to be used when creating this multiworld + :return: The generated multiworld """ if not isinstance(worlds, list): worlds = [worlds] @@ -49,3 +52,63 @@ def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple for step in steps: call_all(multiworld, step) return multiworld + + +class TestWorld(World): + game = f"Test Game" + item_name_to_id = {} + location_name_to_id = {} + hidden = True + + +# add our test world to the data package, so we can test it later +network_data_package["games"][TestWorld.game] = TestWorld.get_data_package_data() + + +def generate_test_multiworld(players: int = 1) -> MultiWorld: + """ + Generates a multiworld using a special Test Case World class, and seed of 0. + + :param players: Number of players to generate the multiworld for + :return: The generated test multiworld + """ + multiworld = setup_multiworld([TestWorld] * players, seed=0) + multiworld.regions += [Region("Menu", player_id + 1, multiworld) for player_id in range(players)] + + return multiworld + + +def generate_locations(count: int, player_id: int, region: Region, address: Optional[int] = None, + tag: str = "") -> List[Location]: + """ + Generates the specified amount of locations for the player and adds them to the specified region. + + :param count: Number of locations to create + :param player_id: ID of the player to create the locations for + :param address: Address for the specified locations. They will all share the same address if multiple are created + :param region: Parent region to add these locations to + :param tag: Tag to add to the name of the generated locations + :return: List containing the created locations + """ + prefix = f"player{player_id}{tag}_location" + + locations = [Location(player_id, f"{prefix}{i}", address, region) for i in range(count)] + region.locations += locations + return locations + + +def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]: + """ + Generates the specified amount of items for the target player. + + :param count: The amount of items to create + :param player_id: ID of the player to create the items for + :param advancement: Whether the created items should be advancement + :param code: The code the items should be created with + :return: List containing the created items + """ + item_type = "prog" if advancement else "" + classification = ItemClassification.progression if advancement else ItemClassification.filler + + items = [Item(f"player{player_id}_{item_type}item{i}", classification, code, player_id) for i in range(count)] + return items diff --git a/test/general/test_client_server_interaction.py b/test/general/test_client_server_interaction.py new file mode 100644 index 000000000000..17de91517409 --- /dev/null +++ b/test/general/test_client_server_interaction.py @@ -0,0 +1,23 @@ +import unittest + +from Utils import get_intended_text, get_input_text_from_response + + +class TestClient(unittest.TestCase): + def test_autofill_hint_from_fuzzy_hint(self) -> None: + tests = ( + ("item", ["item1", "item2"]), # Multiple close matches + ("itm", ["item1", "item21"]), # No close match, multiple option + ("item", ["item1"]), # No close match, single option + ("item", ["\"item\" 'item' (item)"]), # Testing different special characters + ) + + for input_text, possible_answers in tests: + item_name, usable, response = get_intended_text(input_text, possible_answers) + self.assertFalse(usable, "This test must be updated, it seems get_fuzzy_results behavior changed") + + hint_command = get_input_text_from_response(response, "hint") + self.assertIsNotNone(hint_command, + "The response to fuzzy hints is no longer recognized by the hint autofill") + self.assertEqual(hint_command, f"!hint {item_name}", + "The hint command autofilled by the response is not correct") diff --git a/test/general/test_fill.py b/test/general/test_fill.py index 70e9e822bff7..db24b706918c 100644 --- a/test/general/test_fill.py +++ b/test/general/test_fill.py @@ -1,41 +1,15 @@ from typing import List, Iterable import unittest -import Options from Options import Accessibility -from worlds.AutoWorld import World +from test.general import generate_items, generate_locations, generate_test_multiworld from Fill import FillError, balance_multiworld_progression, fill_restrictive, \ distribute_early_items, distribute_items_restrictive from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, Item, Location, \ - ItemClassification, CollectionState + ItemClassification from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule -def generate_multiworld(players: int = 1) -> MultiWorld: - multiworld = MultiWorld(players) - multiworld.set_seed(0) - multiworld.player_name = {} - multiworld.state = CollectionState(multiworld) - for i in range(players): - player_id = i+1 - world = World(multiworld, player_id) - multiworld.game[player_id] = f"Game {player_id}" - multiworld.worlds[player_id] = world - multiworld.player_name[player_id] = "Test Player " + str(player_id) - region = Region("Menu", player_id, multiworld, "Menu Region Hint") - multiworld.regions.append(region) - for option_key, option in Options.PerGameCommonOptions.type_hints.items(): - if hasattr(multiworld, option_key): - getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default"))) - else: - setattr(multiworld, option_key, {player_id: option.from_any(getattr(option, "default"))}) - # TODO - remove this loop once all worlds use options dataclasses - world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id] - for option_key in world.options_dataclass.type_hints}) - - return multiworld - - class PlayerDefinition(object): multiworld: MultiWorld id: int @@ -55,12 +29,12 @@ def __init__(self, multiworld: MultiWorld, id: int, menu: Region, locations: Lis self.regions = [menu] def generate_region(self, parent: Region, size: int, access_rule: CollectionRule = lambda state: True) -> Region: - region_tag = "_region" + str(len(self.regions)) - region_name = "player" + str(self.id) + region_tag - region = Region("player" + str(self.id) + region_tag, self.id, self.multiworld) - self.locations += generate_locations(size, self.id, None, region, region_tag) + region_tag = f"_region{len(self.regions)}" + region_name = f"player{self.id}{region_tag}" + region = Region(f"player{self.id}{region_tag}", self.id, self.multiworld) + self.locations += generate_locations(size, self.id, region, None, region_tag) - entrance = Entrance(self.id, region_name + "_entrance", parent) + entrance = Entrance(self.id, f"{region_name}_entrance", parent) parent.exits.append(entrance) entrance.connect(region) entrance.access_rule = access_rule @@ -80,7 +54,6 @@ def fill_region(multiworld: MultiWorld, region: Region, items: List[Item]) -> Li return items item = items.pop(0) multiworld.push_item(location, item, False) - location.event = item.advancement return items @@ -95,7 +68,7 @@ def region_contains(region: Region, item: Item) -> bool: def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition: menu = multiworld.get_region("Menu", player_id) - locations = generate_locations(location_count, player_id, None, menu) + locations = generate_locations(location_count, player_id, menu, None) prog_items = generate_items(prog_item_count, player_id, True) multiworld.itempool += prog_items basic_items = generate_items(basic_item_count, player_id, False) @@ -104,28 +77,6 @@ def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: return PlayerDefinition(multiworld, player_id, menu, locations, prog_items, basic_items) -def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]: - locations = [] - prefix = "player" + str(player_id) + tag + "_location" - for i in range(count): - name = prefix + str(i) - location = Location(player_id, name, address, region) - locations.append(location) - region.locations.append(location) - return locations - - -def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]: - items = [] - item_type = "prog" if advancement else "" - for i in range(count): - name = "player" + str(player_id) + "_" + item_type + "item" + str(i) - items.append(Item(name, - ItemClassification.progression if advancement else ItemClassification.filler, - code, player_id)) - return items - - def names(objs: list) -> Iterable[str]: return map(lambda o: o.name, objs) @@ -133,7 +84,7 @@ def names(objs: list) -> Iterable[str]: class TestFillRestrictive(unittest.TestCase): def test_basic_fill(self): """Tests `fill_restrictive` fills and removes the locations and items from their respective lists""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) item0 = player1.prog_items[0] @@ -151,7 +102,7 @@ def test_basic_fill(self): def test_ordered_fill(self): """Tests `fill_restrictive` fulfills set rules""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) items = player1.prog_items locations = player1.locations @@ -168,7 +119,7 @@ def test_ordered_fill(self): def test_partial_fill(self): """Tests that `fill_restrictive` returns unfilled locations""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 3, 2) item0 = player1.prog_items[0] @@ -194,7 +145,7 @@ def test_partial_fill(self): def test_minimal_fill(self): """Test that fill for minimal player can have unreachable items""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) items = player1.prog_items @@ -219,12 +170,12 @@ def test_minimal_mixed_fill(self): the non-minimal player get all items. """ - multiworld = generate_multiworld(2) + multiworld = generate_test_multiworld(2) player1 = generate_player_data(multiworld, 1, 3, 3) player2 = generate_player_data(multiworld, 2, 3, 3) - multiworld.accessibility[player1.id].value = multiworld.accessibility[player1.id].option_minimal - multiworld.accessibility[player2.id].value = multiworld.accessibility[player2.id].option_locations + multiworld.worlds[player1.id].options.accessibility.value = Accessibility.option_minimal + multiworld.worlds[player2.id].options.accessibility.value = Accessibility.option_full multiworld.completion_condition[player1.id] = lambda state: True multiworld.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id) @@ -246,11 +197,11 @@ def test_minimal_mixed_fill(self): # all of player2's locations and items should be accessible (not all of player1's) for item in player2.prog_items: self.assertTrue(multiworld.state.has(item.name, player2.id), - f'{item} is unreachable in {item.location}') + f"{item} is unreachable in {item.location}") def test_reversed_fill(self): """Test a different set of rules can be satisfied""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) item0 = player1.prog_items[0] @@ -269,7 +220,7 @@ def test_reversed_fill(self): def test_multi_step_fill(self): """Test that fill is able to satisfy multiple spheres""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 4, 4) items = player1.prog_items @@ -294,7 +245,7 @@ def test_multi_step_fill(self): def test_impossible_fill(self): """Test that fill raises an error when it can't place any items""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) items = player1.prog_items locations = player1.locations @@ -311,7 +262,7 @@ def test_impossible_fill(self): def test_circular_fill(self): """Test that fill raises an error when it can't place all items""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 3, 3) item0 = player1.prog_items[0] @@ -332,7 +283,7 @@ def test_circular_fill(self): def test_competing_fill(self): """Test that fill raises an error when it can't place items in a way to satisfy the conditions""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) item0 = player1.prog_items[0] @@ -349,7 +300,7 @@ def test_competing_fill(self): def test_multiplayer_fill(self): """Test that items can be placed across worlds""" - multiworld = generate_multiworld(2) + multiworld = generate_test_multiworld(2) player1 = generate_player_data(multiworld, 1, 2, 2) player2 = generate_player_data(multiworld, 2, 2, 2) @@ -370,7 +321,7 @@ def test_multiplayer_fill(self): def test_multiplayer_rules_fill(self): """Test that fill across worlds satisfies the rules""" - multiworld = generate_multiworld(2) + multiworld = generate_test_multiworld(2) player1 = generate_player_data(multiworld, 1, 2, 2) player2 = generate_player_data(multiworld, 2, 2, 2) @@ -394,7 +345,7 @@ def test_multiplayer_rules_fill(self): def test_restrictive_progress(self): """Test that various spheres with different requirements can be filled""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, prog_item_count=25) items = player1.prog_items.copy() multiworld.completion_condition[player1.id] = lambda state: state.has_all( @@ -418,7 +369,7 @@ def test_restrictive_progress(self): def test_swap_to_earlier_location_with_item_rule(self): """Test that item swap happens and works as intended""" # test for PR#1109 - multiworld = generate_multiworld(1) + multiworld = generate_test_multiworld(1) player1 = generate_player_data(multiworld, 1, 4, 4) locations = player1.locations[:] # copy required items = player1.prog_items[:] # copy required @@ -443,7 +394,7 @@ def test_swap_to_earlier_location_with_item_rule(self): def test_swap_to_earlier_location_with_item_rule2(self): """Test that swap works before all items are placed""" - multiworld = generate_multiworld(1) + multiworld = generate_test_multiworld(1) player1 = generate_player_data(multiworld, 1, 5, 5) locations = player1.locations[:] # copy required items = player1.prog_items[:] # copy required @@ -485,11 +436,10 @@ def test_swap_to_earlier_location_with_item_rule2(self): def test_double_sweep(self): """Test that sweep doesn't duplicate Event items when sweeping""" # test for PR1114 - multiworld = generate_multiworld(1) + multiworld = generate_test_multiworld(1) player1 = generate_player_data(multiworld, 1, 1, 1) location = player1.locations[0] location.address = None - location.event = True item = player1.prog_items[0] item.code = None location.place_locked_item(item) @@ -500,7 +450,7 @@ def test_double_sweep(self): def test_correct_item_instance_removed_from_pool(self): """Test that a placed item gets removed from the submitted pool""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data(multiworld, 1, 2, 2) player1.prog_items[0].name = "Different_item_instance_but_same_item_name" @@ -517,7 +467,7 @@ def test_correct_item_instance_removed_from_pool(self): class TestDistributeItemsRestrictive(unittest.TestCase): def test_basic_distribute(self): """Test that distribute_items_restrictive is deterministic""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -527,17 +477,17 @@ def test_basic_distribute(self): distribute_items_restrictive(multiworld) self.assertEqual(locations[0].item, basic_items[1]) - self.assertFalse(locations[0].event) + self.assertFalse(locations[0].advancement) self.assertEqual(locations[1].item, prog_items[0]) - self.assertTrue(locations[1].event) + self.assertTrue(locations[1].advancement) self.assertEqual(locations[2].item, prog_items[1]) - self.assertTrue(locations[2].event) + self.assertTrue(locations[2].advancement) self.assertEqual(locations[3].item, basic_items[0]) - self.assertFalse(locations[3].event) + self.assertFalse(locations[3].advancement) def test_excluded_distribute(self): """Test that distribute_items_restrictive doesn't put advancement items on excluded locations""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -552,7 +502,7 @@ def test_excluded_distribute(self): def test_non_excluded_item_distribute(self): """Test that useful items aren't placed on excluded locations""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -567,7 +517,7 @@ def test_non_excluded_item_distribute(self): def test_too_many_excluded_distribute(self): """Test that fill fails if it can't place all progression items due to too many excluded locations""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -580,7 +530,7 @@ def test_too_many_excluded_distribute(self): def test_non_excluded_item_must_distribute(self): """Test that fill fails if it can't place useful items due to too many excluded locations""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -595,7 +545,7 @@ def test_non_excluded_item_must_distribute(self): def test_priority_distribute(self): """Test that priority locations receive advancement items""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -610,7 +560,7 @@ def test_priority_distribute(self): def test_excess_priority_distribute(self): """Test that if there's more priority locations than advancement items, they can still fill""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations @@ -625,7 +575,7 @@ def test_excess_priority_distribute(self): def test_multiple_world_priority_distribute(self): """Test that priority fill can be satisfied for multiple worlds""" - multiworld = generate_multiworld(3) + multiworld = generate_test_multiworld(3) player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) player2 = generate_player_data( @@ -655,7 +605,7 @@ def test_multiple_world_priority_distribute(self): def test_can_remove_locations_in_fill_hook(self): """Test that distribute_items_restrictive calls the fill hook and allows for item and location removal""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, 4, prog_item_count=2, basic_item_count=2) @@ -675,12 +625,12 @@ def fill_hook(progitempool, usefulitempool, filleritempool, fill_locations): def test_seed_robust_to_item_order(self): """Test deterministic fill""" - mw1 = generate_multiworld() + mw1 = generate_test_multiworld() gen1 = generate_player_data( mw1, 1, 4, prog_item_count=2, basic_item_count=2) distribute_items_restrictive(mw1) - mw2 = generate_multiworld() + mw2 = generate_test_multiworld() gen2 = generate_player_data( mw2, 1, 4, prog_item_count=2, basic_item_count=2) mw2.itempool.append(mw2.itempool.pop(0)) @@ -693,12 +643,12 @@ def test_seed_robust_to_item_order(self): def test_seed_robust_to_location_order(self): """Test deterministic fill even if locations in a region are reordered""" - mw1 = generate_multiworld() + mw1 = generate_test_multiworld() gen1 = generate_player_data( mw1, 1, 4, prog_item_count=2, basic_item_count=2) distribute_items_restrictive(mw1) - mw2 = generate_multiworld() + mw2 = generate_test_multiworld() gen2 = generate_player_data( mw2, 1, 4, prog_item_count=2, basic_item_count=2) reg = mw2.get_region("Menu", gen2.id) @@ -712,7 +662,7 @@ def test_seed_robust_to_location_order(self): def test_can_reserve_advancement_items_for_general_fill(self): """Test that priority locations fill still satisfies item rules""" - multiworld = generate_multiworld() + multiworld = generate_test_multiworld() player1 = generate_player_data( multiworld, 1, location_count=5, prog_item_count=5) items = player1.prog_items @@ -729,7 +679,7 @@ def test_can_reserve_advancement_items_for_general_fill(self): def test_non_excluded_local_items(self): """Test that local items get placed locally in a multiworld""" - multiworld = generate_multiworld(2) + multiworld = generate_test_multiworld(2) player1 = generate_player_data( multiworld, 1, location_count=5, basic_item_count=5) player2 = generate_player_data( @@ -746,11 +696,11 @@ def test_non_excluded_local_items(self): for item in multiworld.get_items(): self.assertEqual(item.player, item.location.player) - self.assertFalse(item.location.event, False) + self.assertFalse(item.location.advancement, False) def test_early_items(self) -> None: """Test that the early items API successfully places items early""" - mw = generate_multiworld(2) + mw = generate_test_multiworld(2) player1 = generate_player_data(mw, 1, location_count=5, basic_item_count=5) player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5) mw.early_items[1][player1.basic_items[0].name] = 1 @@ -805,11 +755,11 @@ def assertRegionContains(self, region: Region, item: Item) -> bool: if location.item and location.item == item: return True - self.fail("Expected " + region.name + " to contain " + item.name + - "\n Contains" + str(list(map(lambda location: location.item, region.locations)))) + self.fail(f"Expected {region.name} to contain {item.name}.\n" + f"Contains{list(map(lambda location: location.item, region.locations))}") def setUp(self) -> None: - multiworld = generate_multiworld(2) + multiworld = generate_test_multiworld(2) self.multiworld = multiworld player1 = generate_player_data( multiworld, 1, prog_item_count=2, basic_item_count=40) diff --git a/test/general/test_host_yaml.py b/test/general/test_host_yaml.py index 79285d3a633a..3edbd34a51c5 100644 --- a/test/general/test_host_yaml.py +++ b/test/general/test_host_yaml.py @@ -1,18 +1,25 @@ import os +import os.path import unittest -from tempfile import TemporaryFile +from io import StringIO +from tempfile import TemporaryDirectory, TemporaryFile +from typing import Any, Dict, List, cast -from settings import Settings import Utils +from settings import Group, Settings, ServerOptions class TestIDs(unittest.TestCase): + yaml_options: Dict[Any, Any] + @classmethod def setUpClass(cls) -> None: with TemporaryFile("w+", encoding="utf-8") as f: Settings(None).dump(f) f.seek(0, os.SEEK_SET) - cls.yaml_options = Utils.parse_yaml(f.read()) + yaml_options = Utils.parse_yaml(f.read()) + assert isinstance(yaml_options, dict) + cls.yaml_options = yaml_options def test_utils_in_yaml(self) -> None: """Tests that the auto generated host.yaml has default settings in it""" @@ -30,3 +37,71 @@ def test_yaml_in_utils(self) -> None: self.assertIn(option_key, utils_options) for sub_option_key in option_set: self.assertIn(sub_option_key, utils_options[option_key]) + + +class TestSettingsDumper(unittest.TestCase): + def test_string_format(self) -> None: + """Test that dumping a string will yield the expected output""" + # By default, pyyaml has automatic line breaks in strings and quoting is optional. + # What we want for consistency instead is single-line strings and always quote them. + # Line breaks have to become \n in that quoting style. + class AGroup(Group): + key: str = " ".join(["x"] * 60) + "\n" # more than 120 chars, contains spaces and a line break + + with StringIO() as writer: + AGroup().dump(writer, 0) + expected_value = AGroup.key.replace("\n", "\\n") + self.assertEqual(writer.getvalue(), f"key: \"{expected_value}\"\n", + "dumped string has unexpected formatting") + + def test_indentation(self) -> None: + """Test that dumping items will add indentation""" + # NOTE: we don't care how many spaces there are, but it has to be a multiple of level + class AList(List[Any]): + __doc__ = None # make sure we get no doc string + + class AGroup(Group): + key: AList = cast(AList, ["a", "b", [1]]) + + for level in range(3): + with StringIO() as writer: + AGroup().dump(writer, level) + lines = writer.getvalue().split("\n", 5) + key_line = lines[0] + key_spaces = len(key_line) - len(key_line.lstrip(" ")) + value_lines = lines[1:-1] + value_spaces = [len(value_line) - len(value_line.lstrip(" ")) for value_line in value_lines] + if level == 0: + self.assertEqual(key_spaces, 0) + else: + self.assertGreaterEqual(key_spaces, level) + self.assertEqual(key_spaces % level, 0) + self.assertGreaterEqual(value_spaces[0], key_spaces) # a + self.assertEqual(value_spaces[1], value_spaces[0]) # b + self.assertEqual(value_spaces[2], value_spaces[0]) # start of sub-list + self.assertGreater(value_spaces[3], value_spaces[0], + f"{value_lines[3]} should have more indentation than {value_lines[0]} in {lines}") + + +class TestSettingsSave(unittest.TestCase): + def test_save(self) -> None: + """Test that saving and updating works""" + with TemporaryDirectory() as d: + filename = os.path.join(d, "host.yaml") + new_release_mode = ServerOptions.ReleaseMode("enabled") + # create default host.yaml + settings = Settings(None) + settings.save(filename) + self.assertTrue(os.path.exists(filename), + "Default settings could not be saved") + self.assertNotEqual(settings.server_options.release_mode, new_release_mode, + "Unexpected default release mode") + # update host.yaml + settings.server_options.release_mode = new_release_mode + settings.save(filename) + self.assertFalse(os.path.exists(filename + ".tmp"), + "Temp file was not removed during save") + # read back host.yaml + settings = Settings(filename) + self.assertEqual(settings.server_options.release_mode, new_release_mode, + "Settings were not overwritten") diff --git a/test/general/test_ids.py b/test/general/test_ids.py index 98c41b67b176..e51a070c1fd7 100644 --- a/test/general/test_ids.py +++ b/test/general/test_ids.py @@ -1,27 +1,12 @@ import unittest from Fill import distribute_items_restrictive +from worlds import network_data_package from worlds.AutoWorld import AutoWorldRegister, call_all from . import setup_solo_multiworld class TestIDs(unittest.TestCase): - def test_unique_items(self): - """Tests that every game has a unique ID per item in the datapackage""" - known_item_ids = set() - for gamename, world_type in AutoWorldRegister.world_types.items(): - current = len(known_item_ids) - known_item_ids |= set(world_type.item_id_to_name) - self.assertEqual(len(known_item_ids) - len(world_type.item_id_to_name), current) - - def test_unique_locations(self): - """Tests that every game has a unique ID per location in the datapackage""" - known_location_ids = set() - for gamename, world_type in AutoWorldRegister.world_types.items(): - current = len(known_location_ids) - known_location_ids |= set(world_type.location_id_to_name) - self.assertEqual(len(known_location_ids) - len(world_type.location_id_to_name), current) - def test_range_items(self): """There are Javascript clients, which are limited to Number.MAX_SAFE_INTEGER due to 64bit float precision.""" for gamename, world_type in AutoWorldRegister.world_types.items(): @@ -100,3 +85,4 @@ def test_postgen_datapackage(self): f"{loc_name} is not a valid item name for location_name_to_id") self.assertIsInstance(loc_id, int, f"{loc_id} for {loc_name} should be an int") + self.assertEqual(datapackage["checksum"], network_data_package["games"][gamename]["checksum"]) diff --git a/test/general/test_implemented.py b/test/general/test_implemented.py index 624be710185d..e76d539451ea 100644 --- a/test/general/test_implemented.py +++ b/test/general/test_implemented.py @@ -3,6 +3,7 @@ from Fill import distribute_items_restrictive from NetUtils import encode from worlds.AutoWorld import AutoWorldRegister, call_all +from worlds import failed_world_loads from . import setup_solo_multiworld @@ -47,3 +48,7 @@ def test_slot_data(self): for key, data in multiworld.worlds[1].fill_slot_data().items(): self.assertIsInstance(key, str, "keys in slot data must be a string") self.assertIsInstance(encode(data), str, f"object {type(data).__name__} not serializable.") + + def test_no_failed_world_loads(self): + if failed_world_loads: + self.fail(f"The following worlds failed to load: {failed_world_loads}") diff --git a/test/general/test_items.py b/test/general/test_items.py index 82b6030379d6..9cc91a1b00ef 100644 --- a/test/general/test_items.py +++ b/test/general/test_items.py @@ -23,8 +23,10 @@ def test_item_name_group_has_valid_item(self): {"Pendants", "Crystals"}, "Ocarina of Time": {"medallions", "stones", "rewards", "logic_bottles"}, - "Starcraft 2 Wings of Liberty": - {"Missions"}, + "Starcraft 2": + {"Missions", "WoL Missions"}, + "Yu-Gi-Oh! 2006": + {"Campaign Boss Beaten"} } for game_name, world_type in AutoWorldRegister.world_types.items(): with self.subTest(game_name, game_name=game_name): @@ -62,15 +64,6 @@ def test_items_in_datapackage(self): for item in multiworld.itempool: self.assertIn(item.name, world_type.item_name_to_id) - def test_item_descriptions_have_valid_names(self): - """Ensure all item descriptions match an item name or item group name""" - for game_name, world_type in AutoWorldRegister.world_types.items(): - valid_names = world_type.item_names.union(world_type.item_name_groups) - for name in world_type.item_descriptions: - with self.subTest("Name should be valid", game=game_name, item=name): - self.assertIn(name, valid_names, - "All item descriptions must match defined item names") - def test_itempool_not_modified(self): """Test that worlds don't modify the itempool after `create_items`""" gen_steps = ("generate_early", "create_regions", "create_items") diff --git a/test/general/test_locations.py b/test/general/test_locations.py index 2ac059312c17..4b95ebd22c90 100644 --- a/test/general/test_locations.py +++ b/test/general/test_locations.py @@ -66,12 +66,3 @@ def test_location_group(self): for location in locations: self.assertIn(location, world_type.location_name_to_id) self.assertNotIn(group_name, world_type.location_name_to_id) - - def test_location_descriptions_have_valid_names(self): - """Ensure all location descriptions match a location name or location group name""" - for game_name, world_type in AutoWorldRegister.world_types.items(): - valid_names = world_type.location_names.union(world_type.location_name_groups) - for name in world_type.location_descriptions: - with self.subTest("Name should be valid", game=game_name, location=name): - self.assertIn(name, valid_names, - "All location descriptions must match defined location names") diff --git a/test/general/test_options.py b/test/general/test_options.py index 211704dfe6ba..2229b7ea7e66 100644 --- a/test/general/test_options.py +++ b/test/general/test_options.py @@ -1,4 +1,7 @@ import unittest + +from BaseClasses import MultiWorld, PlandoOptions +from Options import ItemLinks from worlds.AutoWorld import AutoWorldRegister @@ -17,3 +20,42 @@ def test_options_are_not_set_by_world(self): with self.subTest(game=gamename): self.assertFalse(hasattr(world_type, "options"), f"Unexpected assignment to {world_type.__name__}.options!") + + def test_item_links_name_groups(self): + """Tests that item links successfully unfold item_name_groups""" + item_link_groups = [ + [{ + "name": "ItemLinkGroup", + "item_pool": ["Everything"], + "link_replacement": False, + "replacement_item": None, + }], + [{ + "name": "ItemLinkGroup", + "item_pool": ["Hammer", "Bow"], + "link_replacement": False, + "replacement_item": None, + }] + ] + # we really need some sort of test world but generic doesn't have enough items for this + world = AutoWorldRegister.world_types["A Link to the Past"] + plando_options = PlandoOptions.from_option_string("bosses") + item_links = [ItemLinks.from_any(item_link_groups[0]), ItemLinks.from_any(item_link_groups[1])] + for link in item_links: + link.verify(world, "tester", plando_options) + self.assertIn("Hammer", link.value[0]["item_pool"]) + self.assertIn("Bow", link.value[0]["item_pool"]) + + # TODO test that the group created using these options has the items + + def test_item_links_resolve(self): + """Test item link option resolves correctly.""" + item_link_group = [{ + "name": "ItemLinkTest", + "item_pool": ["Everything"], + "link_replacement": False, + "replacement_item": None, + }] + item_links = {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)} + for link in item_links.values(): + self.assertEqual(link.value[0], item_link_group[0]) diff --git a/test/general/test_player_options.py b/test/general/test_player_options.py new file mode 100644 index 000000000000..ea7f19e3d917 --- /dev/null +++ b/test/general/test_player_options.py @@ -0,0 +1,39 @@ +import unittest +import Generate + + +class TestPlayerOptions(unittest.TestCase): + + def test_update_weights(self): + original_weights = { + "scalar_1": 50, + "scalar_2": 25, + "list_1": ["string"], + "dict_1": {"option_a": 50, "option_b": 50}, + "dict_2": {"option_f": 50}, + "set_1": {"option_c"} + } + + # test that we don't allow +merge syntax on scalar variables + with self.assertRaises(BaseException): + Generate.update_weights(original_weights, {"+scalar_1": 0}, "Tested", "") + + new_weights = Generate.update_weights(original_weights, {"scalar_2": 0, + "+list_1": ["string_2"], + "+dict_1": {"option_b": 0, "option_c": 50}, + "+set_1": {"option_c", "option_d"}, + "dict_2": {"option_g": 50}, + "+list_2": ["string_3"]}, + "Tested", "") + + self.assertEqual(new_weights["scalar_1"], 50) + self.assertEqual(new_weights["scalar_2"], 0) + self.assertEqual(new_weights["list_2"], ["string_3"]) + self.assertEqual(new_weights["list_1"], ["string", "string_2"]) + self.assertEqual(new_weights["dict_1"]["option_a"], 50) + self.assertEqual(new_weights["dict_1"]["option_b"], 50) + self.assertEqual(new_weights["dict_1"]["option_c"], 50) + self.assertNotIn("option_f", new_weights["dict_2"]) + self.assertEqual(new_weights["dict_2"]["option_g"], 50) + self.assertEqual(len(new_weights["set_1"]), 2) + self.assertIn("option_d", new_weights["set_1"]) diff --git a/test/general/test_reachability.py b/test/general/test_reachability.py index e57c398b7beb..4b71762f77fe 100644 --- a/test/general/test_reachability.py +++ b/test/general/test_reachability.py @@ -41,15 +41,15 @@ def test_default_all_state_can_reach_everything(self): state = multiworld.get_all_state(False) for location in multiworld.get_locations(): if location.name not in excluded: - with self.subTest("Location should be reached", location=location): + with self.subTest("Location should be reached", location=location.name): self.assertTrue(location.can_reach(state), f"{location.name} unreachable") for region in multiworld.get_regions(): if region.name in unreachable_regions: - with self.subTest("Region should be unreachable", region=region): + with self.subTest("Region should be unreachable", region=region.name): self.assertFalse(region.can_reach(state)) else: - with self.subTest("Region should be reached", region=region): + with self.subTest("Region should be reached", region=region.name): self.assertTrue(region.can_reach(state)) with self.subTest("Completion Condition"): diff --git a/worlds/stardew_valley/test/checks/__init__.py b/test/hosting/__init__.py similarity index 100% rename from worlds/stardew_valley/test/checks/__init__.py rename to test/hosting/__init__.py diff --git a/test/hosting/__main__.py b/test/hosting/__main__.py new file mode 100644 index 000000000000..6640c637b5bd --- /dev/null +++ b/test/hosting/__main__.py @@ -0,0 +1,191 @@ +# A bunch of tests to verify MultiServer and custom webhost server work as expected. +# This spawns processes and may modify your local AP, so this is not run as part of unit testing. +# Run with `python test/hosting` instead, +import logging +import traceback +from tempfile import TemporaryDirectory +from time import sleep +from typing import Any + +from test.hosting.client import Client +from test.hosting.generate import generate_local +from test.hosting.serve import ServeGame, LocalServeGame, WebHostServeGame +from test.hosting.webhost import (create_room, get_app, get_multidata_for_room, set_multidata_for_room, start_room, + stop_autohost, upload_multidata) +from test.hosting.world import copy as copy_world, delete as delete_world + +failure = False +fail_fast = True + + +def assert_true(condition: Any, msg: str = "") -> None: + global failure + if not condition: + failure = True + msg = f": {msg}" if msg else "" + raise AssertionError(f"Assertion failed{msg}") + + +def assert_equal(first: Any, second: Any, msg: str = "") -> None: + global failure + if first != second: + failure = True + msg = f": {msg}" if msg else "" + raise AssertionError(f"Assertion failed: {first} == {second}{msg}") + + +if fail_fast: + expect_true = assert_true + expect_equal = assert_equal +else: + def expect_true(condition: Any, msg: str = "") -> None: + global failure + if not condition: + failure = True + tb = "".join(traceback.format_stack()[:-1]) + msg = f": {msg}" if msg else "" + logging.error(f"Expectation failed{msg}\n{tb}") + + def expect_equal(first: Any, second: Any, msg: str = "") -> None: + global failure + if first != second: + failure = True + tb = "".join(traceback.format_stack()[:-1]) + msg = f": {msg}" if msg else "" + logging.error(f"Expectation failed {first} == {second}{msg}\n{tb}") + + +if __name__ == "__main__": + import warnings + warnings.simplefilter("ignore", ResourceWarning) + warnings.simplefilter("ignore", UserWarning) + + spacer = '=' * 80 + + with TemporaryDirectory() as tempdir: + multis = [["Clique"], ["Temp World"], ["Clique", "Temp World"]] + p1_games = [] + data_paths = [] + rooms = [] + + copy_world("Clique", "Temp World") + try: + for n, games in enumerate(multis, 1): + print(f"Generating [{n}] {', '.join(games)}") + multidata = generate_local(games, tempdir) + print(f"Generated [{n}] {', '.join(games)} as {multidata}\n") + p1_games.append(games[0]) + data_paths.append(multidata) + finally: + delete_world("Temp World") + + webapp = get_app(tempdir) + webhost_client = webapp.test_client() + for n, multidata in enumerate(data_paths, 1): + seed = upload_multidata(webhost_client, multidata) + room = create_room(webhost_client, seed) + print(f"Uploaded [{n}] {multidata} as {room}\n") + rooms.append(room) + + print("Starting autohost") + from WebHostLib.autolauncher import autohost + try: + autohost(webapp.config) + + host: ServeGame + for n, (multidata, room, game, multi_games) in enumerate(zip(data_paths, rooms, p1_games, multis), 1): + involved_games = {"Archipelago"} | set(multi_games) + for collected_items in range(3): + print(f"\nTesting [{n}] {game} in {multidata} on MultiServer with {collected_items} items collected") + with LocalServeGame(multidata) as host: + with Client(host.address, game, "Player1") as client: + local_data_packages = client.games_packages + local_collected_items = len(client.checked_locations) + if collected_items < 2: # Clique only has 2 Locations + client.collect_any() + # TODO: Ctrl+C test here as well + + for game_name in sorted(involved_games): + expect_true(game_name in local_data_packages, + f"{game_name} missing from MultiServer datap ackage") + expect_true("item_name_groups" not in local_data_packages.get(game_name, {}), + f"item_name_groups are not supposed to be in MultiServer data for {game_name}") + expect_true("location_name_groups" not in local_data_packages.get(game_name, {}), + f"location_name_groups are not supposed to be in MultiServer data for {game_name}") + for game_name in local_data_packages: + expect_true(game_name in involved_games, + f"Received unexpected extra data package for {game_name} from MultiServer") + assert_equal(local_collected_items, collected_items, + "MultiServer did not load or save correctly") + + print(f"\nTesting [{n}] {game} in {multidata} on customserver with {collected_items} items collected") + prev_host_adr: str + with WebHostServeGame(webhost_client, room) as host: + prev_host_adr = host.address + with Client(host.address, game, "Player1") as client: + web_data_packages = client.games_packages + web_collected_items = len(client.checked_locations) + if collected_items < 2: # Clique only has 2 Locations + client.collect_any() + if collected_items == 1: + sleep(1) # wait for the server to collect the item + stop_autohost(True) # simulate Ctrl+C + sleep(3) + autohost(webapp.config) # this will spin the room right up again + sleep(1) # make log less annoying + # if saving failed, the next iteration will fail below + + # verify server shut down + try: + with Client(prev_host_adr, game, "Player1") as client: + assert_true(False, "Server did not shut down") + except ConnectionError: + pass + + for game_name in sorted(involved_games): + expect_true(game_name in web_data_packages, + f"{game_name} missing from customserver data package") + expect_true("item_name_groups" not in web_data_packages.get(game_name, {}), + f"item_name_groups are not supposed to be in customserver data for {game_name}") + expect_true("location_name_groups" not in web_data_packages.get(game_name, {}), + f"location_name_groups are not supposed to be in customserver data for {game_name}") + for game_name in web_data_packages: + expect_true(game_name in involved_games, + f"Received unexpected extra data package for {game_name} from customserver") + assert_equal(web_collected_items, collected_items, + "customserver did not load or save correctly during/after " + + ("Ctrl+C" if collected_items == 2 else "/exit")) + + # compare customserver to MultiServer + expect_equal(local_data_packages, web_data_packages, + "customserver datapackage differs from MultiServer") + + sleep(5.5) # make sure all tasks actually stopped + + # raise an exception in customserver and verify the save doesn't get destroyed + # local variables room is the last room's id here + old_data = get_multidata_for_room(webhost_client, room) + print(f"Destroying multidata for {room}") + set_multidata_for_room(webhost_client, room, bytes([0])) + try: + start_room(webhost_client, room, timeout=7) + except TimeoutError: + pass + else: + assert_true(False, "Room started with destroyed multidata") + print(f"Restoring multidata for {room}") + set_multidata_for_room(webhost_client, room, old_data) + with WebHostServeGame(webhost_client, room) as host: + with Client(host.address, game, "Player1") as client: + assert_equal(len(client.checked_locations), 2, + "Save was destroyed during exception in customserver") + print("Save file is not busted 🥳") + + finally: + print("Stopping autohost") + stop_autohost(False) + + if failure: + print("Some tests failed") + exit(1) + exit(0) diff --git a/test/hosting/client.py b/test/hosting/client.py new file mode 100644 index 000000000000..b805bb6a2638 --- /dev/null +++ b/test/hosting/client.py @@ -0,0 +1,110 @@ +import json +import sys +from typing import Any, Collection, Dict, Iterable, Optional +from websockets import ConnectionClosed +from websockets.sync.client import connect, ClientConnection +from threading import Thread + + +__all__ = [ + "Client" +] + + +class Client: + """Incomplete, minimalistic sync test client for AP network protocol""" + + recv_timeout = 1.0 + + host: str + game: str + slot: str + password: Optional[str] + + _ws: Optional[ClientConnection] + + games: Iterable[str] + data_package_checksums: Dict[str, Any] + games_packages: Dict[str, Any] + missing_locations: Collection[int] + checked_locations: Collection[int] + + def __init__(self, host: str, game: str, slot: str, password: Optional[str] = None) -> None: + self.host = host + self.game = game + self.slot = slot + self.password = password + self._ws = None + self.games = [] + self.data_package_checksums = {} + self.games_packages = {} + self.missing_locations = [] + self.checked_locations = [] + + def __enter__(self) -> "Client": + try: + self.connect() + except BaseException: + self.__exit__(*sys.exc_info()) + raise + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + self.close() + + def _poll(self) -> None: + assert self._ws + try: + while True: + self._ws.recv() + except (TimeoutError, ConnectionClosed, KeyboardInterrupt, SystemExit): + pass + + def connect(self) -> None: + self._ws = connect(f"ws://{self.host}") + room_info = json.loads(self._ws.recv(self.recv_timeout))[0] + self.games = sorted(room_info["games"]) + self.data_package_checksums = room_info["datapackage_checksums"] + self._ws.send(json.dumps([{ + "cmd": "GetDataPackage", + "games": list(self.games), + }])) + data_package_msg = json.loads(self._ws.recv(self.recv_timeout))[0] + self.games_packages = data_package_msg["data"]["games"] + self._ws.send(json.dumps([{ + "cmd": "Connect", + "game": self.game, + "name": self.slot, + "password": self.password, + "uuid": "", + "version": { + "class": "Version", + "major": 0, + "minor": 4, + "build": 6, + }, + "items_handling": 0, + "tags": [], + "slot_data": False, + }])) + connect_result_msg = json.loads(self._ws.recv(self.recv_timeout))[0] + if connect_result_msg["cmd"] != "Connected": + raise ConnectionError(", ".join(connect_result_msg.get("errors", [connect_result_msg["cmd"]]))) + self.missing_locations = connect_result_msg["missing_locations"] + self.checked_locations = connect_result_msg["checked_locations"] + + def close(self) -> None: + if self._ws: + Thread(target=self._poll).start() + self._ws.close() + + def collect(self, locations: Iterable[int]) -> None: + if not self._ws: + raise ValueError("Not connected") + self._ws.send(json.dumps([{ + "cmd": "LocationChecks", + "locations": locations, + }])) + + def collect_any(self) -> None: + self.collect([next(iter(self.missing_locations))]) diff --git a/test/hosting/generate.py b/test/hosting/generate.py new file mode 100644 index 000000000000..d5d39dc95ee0 --- /dev/null +++ b/test/hosting/generate.py @@ -0,0 +1,76 @@ +import json +import sys +import warnings +from pathlib import Path +from typing import Iterable, Union, TYPE_CHECKING + +if TYPE_CHECKING: + from multiprocessing.managers import ListProxy # noqa + +__all__ = [ + "generate_local", +] + + +def _generate_local_inner(games: Iterable[str], + dest: Union[Path, str], + results: "ListProxy[Union[Path, BaseException]]") -> None: + original_argv = sys.argv + warnings.simplefilter("ignore") + try: + from tempfile import TemporaryDirectory + + if not isinstance(dest, Path): + dest = Path(dest) + + with TemporaryDirectory() as players_dir: + with TemporaryDirectory() as output_dir: + import Generate + import Main + + for n, game in enumerate(games, 1): + player_path = Path(players_dir) / f"{n}.yaml" + with open(player_path, "w", encoding="utf-8") as f: + f.write(json.dumps({ + "name": f"Player{n}", + "game": game, + game: {"hard_mode": "true"}, + "description": f"generate_local slot {n} ('Player{n}'): {game}", + })) + + # this is basically copied from test/programs/test_generate.py + # uses a reproducible seed that is different for each set of games + sys.argv = [sys.argv[0], "--seed", str(hash(tuple(games))), + "--player_files_path", players_dir, + "--outputpath", output_dir] + Main.main(*Generate.main()) + output_files = list(Path(output_dir).glob('*.zip')) + assert len(output_files) == 1 + final_file = dest / output_files[0].name + output_files[0].rename(final_file) + results.append(final_file) + except BaseException as e: + results.append(e) + raise e + finally: + sys.argv = original_argv + + +def generate_local(games: Iterable[str], dest: Union[Path, str]) -> Path: + from multiprocessing import Manager, Process, set_start_method + + try: + set_start_method("spawn") + except RuntimeError: + pass + + manager = Manager() + results: "ListProxy[Union[Path, Exception]]" = manager.list() + + p = Process(target=_generate_local_inner, args=(games, dest, results)) + p.start() + p.join() + result = results[0] + if isinstance(result, BaseException): + raise Exception("Could not generate multiworld") from result + return result diff --git a/test/hosting/serve.py b/test/hosting/serve.py new file mode 100644 index 000000000000..c3eaac87cc08 --- /dev/null +++ b/test/hosting/serve.py @@ -0,0 +1,115 @@ +import sys +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from threading import Event + from werkzeug.test import Client as FlaskClient + +__all__ = [ + "ServeGame", + "LocalServeGame", + "WebHostServeGame", +] + + +class ServeGame: + address: str + + +def _launch_multiserver(multidata: Path, ready: "Event", stop: "Event") -> None: + import os + import warnings + + original_argv = sys.argv + original_stdin = sys.stdin + warnings.simplefilter("ignore") + try: + import asyncio + from MultiServer import main, parse_args + + sys.argv = [sys.argv[0], str(multidata), "--host", "127.0.0.1"] + r, w = os.pipe() + sys.stdin = os.fdopen(r, "r") + + async def set_ready() -> None: + await asyncio.sleep(.01) # switch back to other task once more + ready.set() # server should be up, set ready state + + async def wait_stop() -> None: + await asyncio.get_event_loop().run_in_executor(None, stop.wait) + os.fdopen(w, "w").write("/exit") + + async def run() -> None: + # this will run main() until first await, then switch to set_ready() + await asyncio.gather( + main(parse_args()), + set_ready(), + wait_stop(), + ) + + asyncio.run(run()) + finally: + sys.argv = original_argv + sys.stdin = original_stdin + + +class LocalServeGame(ServeGame): + from multiprocessing import Process + + _multidata: Path + _proc: Process + _stop: "Event" + + def __init__(self, multidata: Path) -> None: + self.address = "" + self._multidata = multidata + + def __enter__(self) -> "LocalServeGame": + from multiprocessing import Manager, Process, set_start_method + + try: + set_start_method("spawn") + except RuntimeError: + pass + + manager = Manager() + ready: "Event" = manager.Event() + self._stop = manager.Event() + + self._proc = Process(target=_launch_multiserver, args=(self._multidata, ready, self._stop)) + try: + self._proc.start() + ready.wait(30) + self.address = "localhost:38281" + return self + except BaseException: + self.__exit__(*sys.exc_info()) + raise + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + try: + self._stop.set() + self._proc.join(30) + except TimeoutError: + self._proc.terminate() + self._proc.join() + + +class WebHostServeGame(ServeGame): + _client: "FlaskClient" + _room: str + + def __init__(self, app_client: "FlaskClient", room: str) -> None: + self.address = "" + self._client = app_client + self._room = room + + def __enter__(self) -> "WebHostServeGame": + from .webhost import start_room + self.address = start_room(self._client, self._room) + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + from .webhost import stop_room + stop_room(self._client, self._room, timeout=30) diff --git a/test/hosting/webhost.py b/test/hosting/webhost.py new file mode 100644 index 000000000000..4db605e8c1ea --- /dev/null +++ b/test/hosting/webhost.py @@ -0,0 +1,208 @@ +import re +from pathlib import Path +from typing import TYPE_CHECKING, Optional, cast + +if TYPE_CHECKING: + from flask import Flask + from werkzeug.test import Client as FlaskClient + +__all__ = [ + "get_app", + "upload_multidata", + "create_room", + "start_room", + "stop_room", + "set_room_timeout", + "get_multidata_for_room", + "set_multidata_for_room", + "stop_autohost", +] + + +def get_app(tempdir: str) -> "Flask": + from WebHostLib import app as raw_app + from WebHost import get_app + raw_app.config["PONY"] = { + "provider": "sqlite", + "filename": str(Path(tempdir) / "host.db"), + "create_db": True, + } + raw_app.config.update({ + "TESTING": True, + "HOST_ADDRESS": "localhost", + "HOSTERS": 1, + }) + return get_app() + + +def upload_multidata(app_client: "FlaskClient", multidata: Path) -> str: + response = app_client.post("/uploads", data={ + "file": multidata.open("rb"), + }) + assert response.status_code < 400, f"Upload of {multidata} failed: status {response.status_code}" + assert "Location" in response.headers, f"Upload of {multidata} failed: no redirect" + location = response.headers["Location"] + assert isinstance(location, str) + assert location.startswith("/seed/"), f"Upload of {multidata} failed: unexpected redirect" + return location[6:] + + +def create_room(app_client: "FlaskClient", seed: str, auto_start: bool = False) -> str: + response = app_client.get(f"/new_room/{seed}") + assert response.status_code < 400, f"Creating room for {seed} failed: status {response.status_code}" + assert "Location" in response.headers, f"Creating room for {seed} failed: no redirect" + location = response.headers["Location"] + assert isinstance(location, str) + assert location.startswith("/room/"), f"Creating room for {seed} failed: unexpected redirect" + room_id = location[6:] + + if not auto_start: + # by default, creating a room will auto-start it, so we update last activity here + stop_room(app_client, room_id, simulate_idle=False) + + return room_id + + +def start_room(app_client: "FlaskClient", room_id: str, timeout: float = 30) -> str: + from time import sleep + + import pony.orm + + poll_interval = .2 + + print(f"Starting room {room_id}") + no_timeout = timeout <= 0 + while no_timeout or timeout > 0: + try: + response = app_client.get(f"/room/{room_id}") + except pony.orm.core.OptimisticCheckError: + # hoster wrote to room during our transaction + continue + + assert response.status_code == 200, f"Starting room for {room_id} failed: status {response.status_code}" + match = re.search(r"/connect ([\w:.\-]+)", response.text) + if match: + return match[1] + timeout -= poll_interval + sleep(poll_interval) + raise TimeoutError("Room did not start") + + +def stop_room(app_client: "FlaskClient", + room_id: str, + timeout: Optional[float] = None, + simulate_idle: bool = True) -> None: + from datetime import datetime, timedelta + from time import sleep + + from pony.orm import db_session + + from WebHostLib.models import Command, Room + from WebHostLib import app + + poll_interval = 2 + + print(f"Stopping room {room_id}") + room_uuid = app.url_map.converters["suuid"].to_python(None, room_id) # type: ignore[arg-type] + + if timeout is not None: + sleep(.1) # should not be required, but other things might use threading + + with db_session: + room: Room = Room.get(id=room_uuid) + if simulate_idle: + new_last_activity = datetime.utcnow() - timedelta(seconds=room.timeout + 5) + else: + new_last_activity = datetime.utcnow() - timedelta(days=3) + room.last_activity = new_last_activity + address = f"localhost:{room.last_port}" if room.last_port > 0 else None + if address: + original_timeout = room.timeout + room.timeout = 1 # avoid spinning it up again + Command(room=room, commandtext="/exit") + + try: + if address and timeout is not None: + print("waiting for shutdown") + import socket + host_str, port_str = tuple(address.split(":")) + address_tuple = host_str, int(port_str) + + no_timeout = timeout <= 0 + while no_timeout or timeout > 0: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(address_tuple) + s.close() + except ConnectionRefusedError: + return + sleep(poll_interval) + timeout -= poll_interval + + raise TimeoutError("Room did not stop") + finally: + with db_session: + room = Room.get(id=room_uuid) + room.last_port = 0 # easier to detect when the host is up this way + if address: + room.timeout = original_timeout + room.last_activity = new_last_activity + print("timeout restored") + + +def set_room_timeout(room_id: str, timeout: float) -> None: + from pony.orm import db_session + + from WebHostLib.models import Room + from WebHostLib import app + + room_uuid = app.url_map.converters["suuid"].to_python(None, room_id) # type: ignore[arg-type] + with db_session: + room: Room = Room.get(id=room_uuid) + room.timeout = timeout + + +def get_multidata_for_room(webhost_client: "FlaskClient", room_id: str) -> bytes: + from pony.orm import db_session + + from WebHostLib.models import Room + from WebHostLib import app + + room_uuid = app.url_map.converters["suuid"].to_python(None, room_id) # type: ignore[arg-type] + with db_session: + room: Room = Room.get(id=room_uuid) + return cast(bytes, room.seed.multidata) + + +def set_multidata_for_room(webhost_client: "FlaskClient", room_id: str, data: bytes) -> None: + from pony.orm import db_session + + from WebHostLib.models import Room + from WebHostLib import app + + room_uuid = app.url_map.converters["suuid"].to_python(None, room_id) # type: ignore[arg-type] + with db_session: + room: Room = Room.get(id=room_uuid) + room.seed.multidata = data + + +def stop_autohost(graceful: bool = True) -> None: + import os + import signal + + import multiprocessing + + from WebHostLib.autolauncher import stop + + stop() + proc: multiprocessing.process.BaseProcess + for proc in filter(lambda child: child.name.startswith("MultiHoster"), multiprocessing.active_children()): + if graceful and proc.pid: + os.kill(proc.pid, getattr(signal, "CTRL_C_EVENT", signal.SIGINT)) + else: + proc.kill() + try: + proc.join(30) + except TimeoutError: + proc.kill() + proc.join() diff --git a/test/hosting/world.py b/test/hosting/world.py new file mode 100644 index 000000000000..e083e027fee1 --- /dev/null +++ b/test/hosting/world.py @@ -0,0 +1,42 @@ +import re +import shutil +from pathlib import Path +from typing import Dict + + +__all__ = ["copy", "delete"] + + +_new_worlds: Dict[str, str] = {} + + +def copy(src: str, dst: str) -> None: + from Utils import get_file_safe_name + from worlds import AutoWorldRegister + + assert dst not in _new_worlds, "World already created" + if '"' in dst or "\\" in dst: # easier to reject than to escape + raise ValueError(f"Unsupported symbols in {dst}") + dst_folder_name = get_file_safe_name(dst.lower()) + src_cls = AutoWorldRegister.world_types[src] + src_folder = Path(src_cls.__file__).parent + worlds_folder = src_folder.parent + if (not src_cls.__file__.endswith("__init__.py") or not src_folder.is_dir() + or not (worlds_folder / "generic").is_dir()): + raise ValueError(f"Unsupported layout for copy_world from {src}") + dst_folder = worlds_folder / dst_folder_name + if dst_folder.is_dir(): + raise ValueError(f"Destination {dst_folder} already exists") + shutil.copytree(src_folder, dst_folder) + _new_worlds[dst] = str(dst_folder) + with open(dst_folder / "__init__.py", "r", encoding="utf-8-sig") as f: + contents = f.read() + contents = re.sub(r'game\s*=\s*[\'"]' + re.escape(src) + r'[\'"]', f'game = "{dst}"', contents) + with open(dst_folder / "__init__.py", "w", encoding="utf-8") as f: + f.write(contents) + + +def delete(name: str) -> None: + assert name in _new_worlds, "World not created by this script" + shutil.rmtree(_new_worlds[name]) + del _new_worlds[name] diff --git a/test/multiworld/test_multiworlds.py b/test/multiworld/test_multiworlds.py index 677f0de82930..5289cac6c357 100644 --- a/test/multiworld/test_multiworlds.py +++ b/test/multiworld/test_multiworlds.py @@ -69,7 +69,7 @@ def test_two_player_single_game_fills(self) -> None: for world in AutoWorldRegister.world_types.values(): self.multiworld = setup_multiworld([world, world], ()) for world in self.multiworld.worlds.values(): - world.options.accessibility.value = Accessibility.option_locations + world.options.accessibility.value = Accessibility.option_full self.assertSteps(gen_steps) with self.subTest("filling multiworld", seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) diff --git a/test/netutils/test_location_store.py b/test/netutils/test_location_store.py index a7f117255faa..f3e83989bea4 100644 --- a/test/netutils/test_location_store.py +++ b/test/netutils/test_location_store.py @@ -1,4 +1,5 @@ # Tests for _speedups.LocationStore and NetUtils._LocationStore +import os import typing import unittest import warnings @@ -7,6 +8,8 @@ State = typing.Dict[typing.Tuple[int, int], typing.Set[int]] RawLocations = typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int]]] +ci = bool(os.environ.get("CI")) # always set in GitHub actions + sample_data: RawLocations = { 1: { 11: (21, 2, 7), @@ -24,6 +27,9 @@ 3: { 9: (99, 4, 0), }, + 5: { + 9: (99, 5, 0), + } } empty_state: State = { @@ -45,14 +51,14 @@ class TestLocationStore(unittest.TestCase): store: typing.Union[LocationStore, _LocationStore] def test_len(self) -> None: - self.assertEqual(len(self.store), 4) + self.assertEqual(len(self.store), 5) self.assertEqual(len(self.store[1]), 3) def test_key_error(self) -> None: with self.assertRaises(KeyError): _ = self.store[0] with self.assertRaises(KeyError): - _ = self.store[5] + _ = self.store[6] locations = self.store[1] # no Exception with self.assertRaises(KeyError): _ = locations[7] @@ -71,7 +77,7 @@ def test_get(self) -> None: self.assertEqual(self.store[1].get(10, (None, None, None)), (None, None, None)) def test_iter(self) -> None: - self.assertEqual(sorted(self.store), [1, 2, 3, 4]) + self.assertEqual(sorted(self.store), [1, 2, 3, 4, 5]) self.assertEqual(len(self.store), len(sample_data)) self.assertEqual(list(self.store[1]), [11, 12, 13]) self.assertEqual(len(self.store[1]), len(sample_data[1])) @@ -85,13 +91,26 @@ def test_items(self) -> None: self.assertEqual(sorted(self.store[1].items())[0][1], self.store[1][11]) def test_find_item(self) -> None: + # empty player set self.assertEqual(sorted(self.store.find_item(set(), 99)), []) + # no such player, single + self.assertEqual(sorted(self.store.find_item({6}, 99)), []) + # no such player, set + self.assertEqual(sorted(self.store.find_item({7, 8, 9}, 99)), []) + # no such item self.assertEqual(sorted(self.store.find_item({3}, 1)), []) - self.assertEqual(sorted(self.store.find_item({5}, 99)), []) + # valid matches self.assertEqual(sorted(self.store.find_item({3}, 99)), [(4, 9, 99, 3, 0)]) self.assertEqual(sorted(self.store.find_item({3, 4}, 99)), [(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)]) + self.assertEqual(sorted(self.store.find_item({2, 3, 4}, 99)), + [(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)]) + # test hash collision in set + self.assertEqual(sorted(self.store.find_item({3, 5}, 99)), + [(4, 9, 99, 3, 0), (5, 9, 99, 5, 0)]) + self.assertEqual(sorted(self.store.find_item(set(range(2048)), 13)), + [(1, 13, 13, 1, 0)]) def test_get_for_player(self) -> None: self.assertEqual(self.store.get_for_player(3), {4: {9}}) @@ -196,18 +215,20 @@ def setUp(self) -> None: super().setUp() -@unittest.skipIf(LocationStore is _LocationStore, "_speedups not available") +@unittest.skipIf(LocationStore is _LocationStore and not ci, "_speedups not available") class TestSpeedupsLocationStore(Base.TestLocationStore): """Run base method tests for cython implementation.""" def setUp(self) -> None: + self.assertFalse(LocationStore is _LocationStore, "Failed to load _speedups") self.store = LocationStore(sample_data) super().setUp() -@unittest.skipIf(LocationStore is _LocationStore, "_speedups not available") +@unittest.skipIf(LocationStore is _LocationStore and not ci, "_speedups not available") class TestSpeedupsLocationStoreConstructor(Base.TestLocationStoreConstructor): """Run base constructor tests and tests the additional constraints for cython implementation.""" def setUp(self) -> None: + self.assertFalse(LocationStore is _LocationStore, "Failed to load _speedups") self.type = LocationStore super().setUp() diff --git a/test/options/__init__.py b/test/options/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/options/test_option_classes.py b/test/options/test_option_classes.py new file mode 100644 index 000000000000..8e2c4702c380 --- /dev/null +++ b/test/options/test_option_classes.py @@ -0,0 +1,67 @@ +import unittest + +from Options import Choice, DefaultOnToggle, Toggle + + +class TestNumericOptions(unittest.TestCase): + def test_numeric_option(self) -> None: + """Tests the initialization and equivalency comparisons of the base Numeric Option class.""" + class TestChoice(Choice): + option_zero = 0 + option_one = 1 + option_two = 2 + alias_three = 1 + non_option_attr = 2 + + class TestToggle(Toggle): + pass + + class TestDefaultOnToggle(DefaultOnToggle): + pass + + with self.subTest("choice"): + choice_option_default = TestChoice.from_any(TestChoice.default) + choice_option_string = TestChoice.from_any("one") + choice_option_int = TestChoice.from_any(2) + choice_option_alias = TestChoice.from_any("three") + choice_option_attr = TestChoice.from_any(TestChoice.option_two) + + self.assertEqual(choice_option_default, TestChoice.option_zero, + "assigning default didn't match default value") + self.assertEqual(choice_option_string, "one") + self.assertEqual(choice_option_int, 2) + self.assertEqual(choice_option_alias, TestChoice.alias_three) + self.assertEqual(choice_option_attr, TestChoice.non_option_attr) + + self.assertRaises(KeyError, TestChoice.from_any, "four") + + self.assertIn(choice_option_int, [1, 2, 3]) + self.assertIn(choice_option_int, {2}) + self.assertIn(choice_option_int, (2,)) + + self.assertIn(choice_option_string, ["one", "two", "three"]) + # this fails since the hash is derived from the value + self.assertNotIn(choice_option_string, {"one"}) + self.assertIn(choice_option_string, ("one",)) + + with self.subTest("toggle"): + toggle_default = TestToggle.from_any(TestToggle.default) + toggle_string = TestToggle.from_any("false") + toggle_int = TestToggle.from_any(0) + toggle_alias = TestToggle.from_any("off") + + self.assertFalse(toggle_default) + self.assertFalse(toggle_string) + self.assertFalse(toggle_int) + self.assertFalse(toggle_alias) + + with self.subTest("on toggle"): + toggle_default = TestDefaultOnToggle.from_any(TestDefaultOnToggle.default) + toggle_string = TestDefaultOnToggle.from_any("true") + toggle_int = TestDefaultOnToggle.from_any(1) + toggle_alias = TestDefaultOnToggle.from_any("on") + + self.assertTrue(toggle_default) + self.assertTrue(toggle_string) + self.assertTrue(toggle_int) + self.assertTrue(toggle_alias) diff --git a/test/programs/test_common_client.py b/test/programs/test_common_client.py new file mode 100644 index 000000000000..9936240d17b9 --- /dev/null +++ b/test/programs/test_common_client.py @@ -0,0 +1,106 @@ +import unittest + +import NetUtils +from CommonClient import CommonContext + + +class TestCommonContext(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.ctx = CommonContext() + self.ctx.slot = 1 # Pretend we're player 1 for this. + self.ctx.slot_info.update({ + 1: NetUtils.NetworkSlot("Player 1", "__TestGame1", NetUtils.SlotType.player), + 2: NetUtils.NetworkSlot("Player 2", "__TestGame1", NetUtils.SlotType.player), + 3: NetUtils.NetworkSlot("Player 3", "__TestGame2", NetUtils.SlotType.player), + }) + self.ctx.consume_players_package([ + NetUtils.NetworkPlayer(1, 1, "Player 1", "Player 1"), + NetUtils.NetworkPlayer(1, 2, "Player 2", "Player 2"), + NetUtils.NetworkPlayer(1, 3, "Player 3", "Player 3"), + ]) + # Using IDs outside the "safe range" for testing purposes only. If this fails unit tests, it's because + # another world is not following the spec for allowed ID ranges. + self.ctx.update_data_package({ + "games": { + "__TestGame1": { + "location_name_to_id": { + "Test Location 1 - Safe": 2**54 + 1, + "Test Location 2 - Duplicate": 2**54 + 2, + }, + "item_name_to_id": { + "Test Item 1 - Safe": 2**54 + 1, + "Test Item 2 - Duplicate": 2**54 + 2, + }, + }, + "__TestGame2": { + "location_name_to_id": { + "Test Location 3 - Duplicate": 2**54 + 2, + }, + "item_name_to_id": { + "Test Item 3 - Duplicate": 2**54 + 2, + }, + }, + }, + }) + + async def test_archipelago_datapackage_lookups_exist(self): + assert "Archipelago" in self.ctx.item_names, "Archipelago item names entry does not exist" + assert "Archipelago" in self.ctx.location_names, "Archipelago location names entry does not exist" + + async def test_implicit_name_lookups(self): + # Items + assert self.ctx.item_names[2**54 + 1] == "Test Item 1 - Safe" + assert self.ctx.item_names[2**54 + 3] == f"Unknown item (ID: {2**54+3})" + assert self.ctx.item_names[-1] == "Nothing" + + # Locations + assert self.ctx.location_names[2**54 + 1] == "Test Location 1 - Safe" + assert self.ctx.location_names[2**54 + 3] == f"Unknown location (ID: {2**54+3})" + assert self.ctx.location_names[-1] == "Cheat Console" + + async def test_explicit_name_lookups(self): + # Items + assert self.ctx.item_names["__TestGame1"][2**54+1] == "Test Item 1 - Safe" + assert self.ctx.item_names["__TestGame1"][2**54+2] == "Test Item 2 - Duplicate" + assert self.ctx.item_names["__TestGame1"][2**54+3] == f"Unknown item (ID: {2**54+3})" + assert self.ctx.item_names["__TestGame1"][-1] == "Nothing" + assert self.ctx.item_names["__TestGame2"][2**54+1] == f"Unknown item (ID: {2**54+1})" + assert self.ctx.item_names["__TestGame2"][2**54+2] == "Test Item 3 - Duplicate" + assert self.ctx.item_names["__TestGame2"][2**54+3] == f"Unknown item (ID: {2**54+3})" + assert self.ctx.item_names["__TestGame2"][-1] == "Nothing" + + # Locations + assert self.ctx.location_names["__TestGame1"][2**54+1] == "Test Location 1 - Safe" + assert self.ctx.location_names["__TestGame1"][2**54+2] == "Test Location 2 - Duplicate" + assert self.ctx.location_names["__TestGame1"][2**54+3] == f"Unknown location (ID: {2**54+3})" + assert self.ctx.location_names["__TestGame1"][-1] == "Cheat Console" + assert self.ctx.location_names["__TestGame2"][2**54+1] == f"Unknown location (ID: {2**54+1})" + assert self.ctx.location_names["__TestGame2"][2**54+2] == "Test Location 3 - Duplicate" + assert self.ctx.location_names["__TestGame2"][2**54+3] == f"Unknown location (ID: {2**54+3})" + assert self.ctx.location_names["__TestGame2"][-1] == "Cheat Console" + + async def test_lookup_helper_functions(self): + # Checking own slot. + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1) == "Test Item 1 - Safe" + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2) == "Test Item 2 - Duplicate" + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 3) == f"Unknown item (ID: {2 ** 54 + 3})" + assert self.ctx.item_names.lookup_in_slot(-1) == f"Nothing" + + # Checking others' slots. + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1, 2) == "Test Item 1 - Safe" + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2, 2) == "Test Item 2 - Duplicate" + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1, 3) == f"Unknown item (ID: {2 ** 54 + 1})" + assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2, 3) == "Test Item 3 - Duplicate" + + # Checking by game. + assert self.ctx.item_names.lookup_in_game(2 ** 54 + 1, "__TestGame1") == "Test Item 1 - Safe" + assert self.ctx.item_names.lookup_in_game(2 ** 54 + 2, "__TestGame1") == "Test Item 2 - Duplicate" + assert self.ctx.item_names.lookup_in_game(2 ** 54 + 3, "__TestGame1") == f"Unknown item (ID: {2 ** 54 + 3})" + assert self.ctx.item_names.lookup_in_game(2 ** 54 + 1, "__TestGame2") == f"Unknown item (ID: {2 ** 54 + 1})" + assert self.ctx.item_names.lookup_in_game(2 ** 54 + 2, "__TestGame2") == "Test Item 3 - Duplicate" + + # Checking with Archipelago ids are valid in any game package. + assert self.ctx.item_names.lookup_in_slot(-1, 2) == "Nothing" + assert self.ctx.item_names.lookup_in_slot(-1, 3) == "Nothing" + assert self.ctx.item_names.lookup_in_game(-1, "__TestGame1") == "Nothing" + assert self.ctx.item_names.lookup_in_game(-1, "__TestGame2") == "Nothing" diff --git a/test/programs/test_generate.py b/test/programs/test_generate.py index 887a417ec9f9..9281c9c753cd 100644 --- a/test/programs/test_generate.py +++ b/test/programs/test_generate.py @@ -9,6 +9,7 @@ from tempfile import TemporaryDirectory import Generate +import Main class TestGenerateMain(unittest.TestCase): @@ -58,7 +59,7 @@ def test_generate_absolute(self): '--player_files_path', str(self.abs_input_dir), '--outputpath', self.output_tempdir.name] print(f'Testing Generate.py {sys.argv} in {os.getcwd()}') - Generate.main() + Main.main(*Generate.main()) self.assertOutput(self.output_tempdir.name) @@ -67,7 +68,7 @@ def test_generate_relative(self): '--player_files_path', str(self.rel_input_dir), '--outputpath', self.output_tempdir.name] print(f'Testing Generate.py {sys.argv} in {os.getcwd()}') - Generate.main() + Main.main(*Generate.main()) self.assertOutput(self.output_tempdir.name) @@ -86,7 +87,7 @@ def test_generate_yaml(self): sys.argv = [sys.argv[0], '--seed', '0', '--outputpath', self.output_tempdir.name] print(f'Testing Generate.py {sys.argv} in {os.getcwd()}, player_files_path={self.yaml_input_dir}') - Generate.main() + Main.main(*Generate.main()) finally: user_path.cached_path = user_path_backup diff --git a/test/webhost/__init__.py b/test/webhost/__init__.py index e69de29bb2d1..2eb340722a3a 100644 --- a/test/webhost/__init__.py +++ b/test/webhost/__init__.py @@ -0,0 +1,36 @@ +import unittest +import typing +from uuid import uuid4 + +from flask import Flask +from flask.testing import FlaskClient + + +class TestBase(unittest.TestCase): + app: typing.ClassVar[Flask] + client: FlaskClient + + @classmethod + def setUpClass(cls) -> None: + from WebHostLib import app as raw_app + from WebHost import get_app + + raw_app.config["PONY"] = { + "provider": "sqlite", + "filename": ":memory:", + "create_db": True, + } + raw_app.config.update({ + "TESTING": True, + "DEBUG": True, + }) + try: + cls.app = get_app() + except AssertionError as e: + # since we only have 1 global app object, this might fail, but luckily all tests use the same config + if "register_blueprint" not in e.args[0]: + raise + cls.app = raw_app + + def setUp(self) -> None: + self.client = self.app.test_client() diff --git a/test/webhost/test_api_generate.py b/test/webhost/test_api_generate.py index bd78edd9c700..591c61d74880 100644 --- a/test/webhost/test_api_generate.py +++ b/test/webhost/test_api_generate.py @@ -1,31 +1,16 @@ import io -import unittest import json import yaml +from . import TestBase -class TestDocs(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - from WebHostLib import app as raw_app - from WebHost import get_app - raw_app.config["PONY"] = { - "provider": "sqlite", - "filename": ":memory:", - "create_db": True, - } - raw_app.config.update({ - "TESTING": True, - }) - app = get_app() - - cls.client = app.test_client() - def test_correct_error_empty_request(self): +class TestAPIGenerate(TestBase): + def test_correct_error_empty_request(self) -> None: response = self.client.post("/api/generate") self.assertIn("No options found. Expected file attachment or json weights.", response.text) - def test_generation_queued_weights(self): + def test_generation_queued_weights(self) -> None: options = { "Tester1": { @@ -43,7 +28,7 @@ def test_generation_queued_weights(self): self.assertTrue(json_data["text"].startswith("Generation of seed ")) self.assertTrue(json_data["text"].endswith(" started successfully.")) - def test_generation_queued_file(self): + def test_generation_queued_file(self) -> None: options = { "game": "Archipelago", "name": "Tester", diff --git a/test/webhost/test_descriptions.py b/test/webhost/test_descriptions.py new file mode 100644 index 000000000000..70f375b51cf0 --- /dev/null +++ b/test/webhost/test_descriptions.py @@ -0,0 +1,23 @@ +import unittest + +from worlds.AutoWorld import AutoWorldRegister + + +class TestWebDescriptions(unittest.TestCase): + def test_item_descriptions_have_valid_names(self) -> None: + """Ensure all item descriptions match an item name or item group name""" + for game_name, world_type in AutoWorldRegister.world_types.items(): + valid_names = world_type.item_names.union(world_type.item_name_groups) + for name in world_type.web.item_descriptions: + with self.subTest("Name should be valid", game=game_name, item=name): + self.assertIn(name, valid_names, + "All item descriptions must match defined item names") + + def test_location_descriptions_have_valid_names(self) -> None: + """Ensure all location descriptions match a location name or location group name""" + for game_name, world_type in AutoWorldRegister.world_types.items(): + valid_names = world_type.location_names.union(world_type.location_name_groups) + for name in world_type.web.location_descriptions: + with self.subTest("Name should be valid", game=game_name, location=name): + self.assertIn(name, valid_names, + "All location descriptions must match defined location names") diff --git a/test/webhost/test_host_room.py b/test/webhost/test_host_room.py new file mode 100644 index 000000000000..e9dae41dd06f --- /dev/null +++ b/test/webhost/test_host_room.py @@ -0,0 +1,192 @@ +import os +from uuid import UUID, uuid4, uuid5 + +from flask import url_for + +from . import TestBase + + +class TestHostFakeRoom(TestBase): + room_id: UUID + log_filename: str + + def setUp(self) -> None: + from pony.orm import db_session + from Utils import user_path + from WebHostLib.models import Room, Seed + + super().setUp() + + with self.client.session_transaction() as session: + session["_id"] = uuid4() + with db_session: + # create an empty seed and a room from it + seed = Seed(multidata=b"", owner=session["_id"]) + room = Room(seed=seed, owner=session["_id"], tracker=uuid4()) + self.room_id = room.id + self.log_filename = user_path("logs", f"{self.room_id}.txt") + + def tearDown(self) -> None: + from pony.orm import db_session, select + from WebHostLib.models import Command, Room + + with db_session: + for command in select(command for command in Command if command.room.id == self.room_id): # type: ignore + command.delete() + room: Room = Room.get(id=self.room_id) + room.seed.delete() + room.delete() + + try: + os.unlink(self.log_filename) + except FileNotFoundError: + pass + + def test_display_log_missing_full(self) -> None: + """ + Verify that we get a 200 response even if log is missing. + This is required to not get an error for fetch. + """ + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("display_log", room=self.room_id)) + self.assertEqual(response.status_code, 200) + + def test_display_log_missing_range(self) -> None: + """ + Verify that we get a full response for missing log even if we asked for range. + This is required for the JS logic to differentiate between log update and log error message. + """ + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("display_log", room=self.room_id), headers={ + "Range": "bytes=100-" + }) + self.assertEqual(response.status_code, 200) + + def test_display_log_denied(self) -> None: + """Verify that only the owner can see the log.""" + other_client = self.app.test_client() + with self.app.app_context(), self.app.test_request_context(): + response = other_client.get(url_for("display_log", room=self.room_id)) + self.assertEqual(response.status_code, 403) + + def test_display_log_missing_room(self) -> None: + """Verify log for missing room gives an error as opposed to missing log for existing room.""" + missing_room_id = uuid5(uuid4(), "") # rooms are always uuid4, so this can't exist + other_client = self.app.test_client() + with self.app.app_context(), self.app.test_request_context(): + response = other_client.get(url_for("display_log", room=missing_room_id)) + self.assertEqual(response.status_code, 404) + + def test_display_log_full(self) -> None: + """Verify full log response.""" + with open(self.log_filename, "w", encoding="utf-8") as f: + text = "x" * 200 + f.write(text) + + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("display_log", room=self.room_id)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.get_data(True), text) + + def test_display_log_range(self) -> None: + """Verify that Range header in request gives a range in response.""" + with open(self.log_filename, "w", encoding="utf-8") as f: + f.write(" " * 100) + text = "x" * 100 + f.write(text) + + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("display_log", room=self.room_id), headers={ + "Range": "bytes=100-" + }) + self.assertEqual(response.status_code, 206) + self.assertEqual(response.get_data(True), text) + + def test_display_log_range_bom(self) -> None: + """Verify that a BOM in the log file is skipped for range.""" + with open(self.log_filename, "w", encoding="utf-8-sig") as f: + f.write(" " * 100) + text = "x" * 100 + f.write(text) + self.assertEqual(f.tell(), 203) # including BOM + + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("display_log", room=self.room_id), headers={ + "Range": "bytes=100-" + }) + self.assertEqual(response.status_code, 206) + self.assertEqual(response.get_data(True), text) + + def test_host_room_missing(self) -> None: + """Verify that missing room gives a 404 response.""" + missing_room_id = uuid5(uuid4(), "") # rooms are always uuid4, so this can't exist + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("host_room", room=missing_room_id)) + self.assertEqual(response.status_code, 404) + + def test_host_room_own(self) -> None: + """Verify that own room gives the full output.""" + with open(self.log_filename, "w", encoding="utf-8-sig") as f: + text = "* should be visible *" + f.write(text) + + with self.app.app_context(), self.app.test_request_context(): + response = self.client.get(url_for("host_room", room=self.room_id)) + response_text = response.get_data(True) + self.assertEqual(response.status_code, 200) + self.assertIn("href=\"/seed/", response_text) + self.assertIn(text, response_text) + + def test_host_room_other(self) -> None: + """Verify that non-own room gives the reduced output.""" + from pony.orm import db_session + from WebHostLib.models import Room + + with db_session: + room: Room = Room.get(id=self.room_id) + room.last_port = 12345 + + with open(self.log_filename, "w", encoding="utf-8-sig") as f: + text = "* should not be visible *" + f.write(text) + + other_client = self.app.test_client() + with self.app.app_context(), self.app.test_request_context(): + response = other_client.get(url_for("host_room", room=self.room_id)) + response_text = response.get_data(True) + self.assertEqual(response.status_code, 200) + self.assertNotIn("href=\"/seed/", response_text) + self.assertNotIn(text, response_text) + self.assertIn("/connect ", response_text) + self.assertIn(":12345", response_text) + + def test_host_room_own_post(self) -> None: + """Verify command from owner gets queued for the server and response is redirect.""" + from pony.orm import db_session, select + from WebHostLib.models import Command + + with self.app.app_context(), self.app.test_request_context(): + response = self.client.post(url_for("host_room", room=self.room_id), data={ + "cmd": "/help" + }) + self.assertEqual(response.status_code, 302, response.text)\ + + with db_session: + commands = select(command for command in Command if command.room.id == self.room_id) # type: ignore + self.assertIn("/help", (command.commandtext for command in commands)) + + def test_host_room_other_post(self) -> None: + """Verify command from non-owner does not get queued for the server.""" + from pony.orm import db_session, select + from WebHostLib.models import Command + + other_client = self.app.test_client() + with self.app.app_context(), self.app.test_request_context(): + response = other_client.post(url_for("host_room", room=self.room_id), data={ + "cmd": "/help" + }) + self.assertLess(response.status_code, 500) + + with db_session: + commands = select(command for command in Command if command.room.id == self.room_id) # type: ignore + self.assertNotIn("/help", (command.commandtext for command in commands)) diff --git a/test/webhost/test_option_presets.py b/test/webhost/test_option_presets.py index 0c88b6c2ee6f..b0af8a871183 100644 --- a/test/webhost/test_option_presets.py +++ b/test/webhost/test_option_presets.py @@ -1,7 +1,7 @@ import unittest from worlds import AutoWorldRegister -from Options import Choice, NamedRange, Toggle, Range +from Options import ItemDict, NamedRange, NumericOption, OptionList, OptionSet class TestOptionPresets(unittest.TestCase): @@ -14,7 +14,7 @@ def test_option_presets_have_valid_options(self): with self.subTest(game=game_name, preset=preset_name, option=option_name): try: option = world_type.options_dataclass.type_hints[option_name].from_any(option_value) - supported_types = [Choice, Toggle, Range, NamedRange] + supported_types = [NumericOption, OptionSet, OptionList, ItemDict] if not any([issubclass(option.__class__, t) for t in supported_types]): self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' " f"is not a supported type for webhost. " diff --git a/typings/kivy/core/window.pyi b/typings/kivy/core/window.pyi new file mode 100644 index 000000000000..c133b986d6c9 --- /dev/null +++ b/typings/kivy/core/window.pyi @@ -0,0 +1,15 @@ +from typing import Callable, ClassVar + +from kivy.event import EventDispatcher + + +class WindowBase(EventDispatcher): + width: ClassVar[int] # readonly AliasProperty + height: ClassVar[int] # readonly AliasProperty + + @staticmethod + def bind(**kwargs: Callable[..., None]) -> None: ... + + +class Window(WindowBase): + ... diff --git a/typings/kivy/event.pyi b/typings/kivy/event.pyi new file mode 100644 index 000000000000..2e76adab0baf --- /dev/null +++ b/typings/kivy/event.pyi @@ -0,0 +1,2 @@ +class EventDispatcher: + ... diff --git a/typings/kivy/uix/boxlayout.pyi b/typings/kivy/uix/boxlayout.pyi new file mode 100644 index 000000000000..c63d691debdd --- /dev/null +++ b/typings/kivy/uix/boxlayout.pyi @@ -0,0 +1,6 @@ +from typing import Literal +from .layout import Layout + + +class BoxLayout(Layout): + orientation: Literal['horizontal', 'vertical'] diff --git a/typings/kivy/uix/layout.pyi b/typings/kivy/uix/layout.pyi index 2a418a1d8b50..c27f89086306 100644 --- a/typings/kivy/uix/layout.pyi +++ b/typings/kivy/uix/layout.pyi @@ -1,8 +1,14 @@ -from typing import Any +from typing import Any, Sequence + from .widget import Widget class Layout(Widget): + @property + def children(self) -> Sequence[Widget]: ... + def add_widget(self, widget: Widget) -> None: ... + def remove_widget(self, widget: Widget) -> None: ... + def do_layout(self, *largs: Any, **kwargs: Any) -> None: ... diff --git a/typings/kivy/uix/widget.pyi b/typings/kivy/uix/widget.pyi index 54e3b781ea01..bf736fae72fc 100644 --- a/typings/kivy/uix/widget.pyi +++ b/typings/kivy/uix/widget.pyi @@ -1,7 +1,7 @@ """ FillType_* is not a real kivy type - just something to fill unknown typing. """ from typing import Any, Optional, Protocol -from ..graphics import FillType_Drawable, FillType_Vec +from ..graphics.texture import FillType_Drawable, FillType_Vec class FillType_BindCallback(Protocol): diff --git a/typings/schema/__init__.pyi b/typings/schema/__init__.pyi new file mode 100644 index 000000000000..d993ec22745f --- /dev/null +++ b/typings/schema/__init__.pyi @@ -0,0 +1,17 @@ +from typing import Any, Callable + + +class And: + def __init__(self, __type: type, __func: Callable[[Any], bool]) -> None: ... + + +class Or: + def __init__(self, *args: object) -> None: ... + + +class Schema: + def __init__(self, __x: object) -> None: ... + + +class Optional(Schema): + ... diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 4d9b31d17d48..af067e5cb8a6 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -3,19 +3,17 @@ import hashlib import logging import pathlib -import random -import re import sys import time +from random import Random from dataclasses import make_dataclass -from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, - Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union) +from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple, + TYPE_CHECKING, Type, Union) -from Options import PerGameCommonOptions +from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions from BaseClasses import CollectionState if TYPE_CHECKING: - import random from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance from . import GamesPackage from settings import Group @@ -53,17 +51,12 @@ def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Aut dct["item_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set in dct.get("item_name_groups", {}).items()} dct["item_name_groups"]["Everything"] = dct["item_names"] - dct["item_descriptions"] = {name: _normalize_description(description) for name, description - in dct.get("item_descriptions", {}).items()} - dct["item_descriptions"]["Everything"] = "All items in the entire game." + dct["location_names"] = frozenset(dct["location_name_to_id"]) dct["location_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set in dct.get("location_name_groups", {}).items()} dct["location_name_groups"]["Everywhere"] = dct["location_names"] dct["all_item_and_group_names"] = frozenset(dct["item_names"] | set(dct.get("item_name_groups", {}))) - dct["location_descriptions"] = {name: _normalize_description(description) for name, description - in dct.get("location_descriptions", {}).items()} - dct["location_descriptions"]["Everywhere"] = "All locations in the entire game." # move away from get_required_client_version function if "game" in dct: @@ -118,6 +111,39 @@ def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Aut return new_class +class WebWorldRegister(type): + def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> WebWorldRegister: + # don't allow an option to appear in multiple groups, allow "Item & Location Options" to appear anywhere by the + # dev, putting it at the end if they don't define options in it + option_groups: List[OptionGroup] = dct.get("option_groups", []) + prebuilt_options = ["Game Options", "Item & Location Options"] + seen_options = [] + item_group_in_list = False + for group in option_groups: + assert group.options, "A custom defined Option Group must contain at least one Option." + # catch incorrectly titled versions of the prebuilt groups so they don't create extra groups + title_name = group.name.title() + assert title_name not in prebuilt_options or title_name == group.name, \ + f"Prebuilt group name \"{group.name}\" must be \"{title_name}\"" + + if group.name == "Item & Location Options": + assert not any(option in item_and_loc_options for option in group.options), \ + f"Item and Location Options cannot be specified multiple times" + group.options.extend(item_and_loc_options) + item_group_in_list = True + else: + for option in group.options: + assert option not in item_and_loc_options, \ + f"{option} cannot be moved out of the \"Item & Location Options\" Group" + assert len(group.options) == len(set(group.options)), f"Duplicate options in option group {group.name}" + for option in group.options: + assert option not in seen_options, f"{option} found in two option groups" + seen_options.append(option) + if not item_group_in_list: + option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options, True)) + return super().__new__(mcs, name, bases, dct) + + def _timed_call(method: Callable[..., Any], *args: Any, multiworld: Optional["MultiWorld"] = None, player: Optional[int] = None) -> Any: start = time.perf_counter() @@ -172,7 +198,7 @@ def call_stage(multiworld: "MultiWorld", method_name: str, *args: Any) -> None: _timed_call(stage_callable, multiworld, *args) -class WebWorld: +class WebWorld(metaclass=WebWorldRegister): """Webhost integration""" options_page: Union[bool, str] = True @@ -194,6 +220,30 @@ class WebWorld: options_presets: Dict[str, Dict[str, Any]] = {} """A dictionary containing a collection of developer-defined game option presets.""" + option_groups: ClassVar[List[OptionGroup]] = [] + """Ordered list of option groupings. Any options not set in a group will be placed in a pre-built "Game Options".""" + + rich_text_options_doc = False + """Whether the WebHost should render Options' docstrings as rich text. + + If this is True, Options' docstrings are interpreted as reStructuredText_, + the standard Python markup format. In the WebHost, they're rendered to HTML + so that lists, emphasis, and other rich text features are displayed + properly. + + If this is False, the docstrings are instead interpreted as plain text, and + displayed as-is on the WebHost with whitespace preserved. For backwards + compatibility, this is the default. + + .. _reStructuredText: https://docutils.sourceforge.io/rst.html + """ + + location_descriptions: Dict[str, str] = {} + """An optional map from location names (or location group names) to brief descriptions for users.""" + + item_descriptions: Dict[str, str] = {} + """An optional map from item names (or item group names) to brief descriptions for users.""" + class World(metaclass=AutoWorldRegister): """A World object encompasses a game's Items, Locations, Rules and additional data or functionality required. @@ -206,8 +256,8 @@ class World(metaclass=AutoWorldRegister): game: ClassVar[str] """name the game""" - topology_present: ClassVar[bool] = False - """indicate if world type has any meaningful layout/pathing""" + topology_present: bool = False + """indicate if this world has any meaningful layout/pathing""" all_item_and_group_names: ClassVar[FrozenSet[str]] = frozenset() """gets automatically populated with all item and item group names""" @@ -220,35 +270,9 @@ class World(metaclass=AutoWorldRegister): item_name_groups: ClassVar[Dict[str, Set[str]]] = {} """maps item group names to sets of items. Example: {"Weapons": {"Sword", "Bow"}}""" - item_descriptions: ClassVar[Dict[str, str]] = {} - """An optional map from item names (or item group names) to brief descriptions for users. - - Individual newlines and indentation will be collapsed into spaces before these descriptions are - displayed. This may cover only a subset of items. - """ - location_name_groups: ClassVar[Dict[str, Set[str]]] = {} """maps location group names to sets of locations. Example: {"Sewer": {"Sewer Key Drop 1", "Sewer Key Drop 2"}}""" - location_descriptions: ClassVar[Dict[str, str]] = {} - """An optional map from location names (or location group names) to brief descriptions for users. - - Individual newlines and indentation will be collapsed into spaces before these descriptions are - displayed. This may cover only a subset of locations. - """ - - data_version: ClassVar[int] = 0 - """ - Increment this every time something in your world's names/id mappings changes. - - When this is set to 0, that world's DataPackage is considered in "testing mode", which signals to servers/clients - that it should not be cached, and clients should request that world's DataPackage every connection. Not - recommended for production-ready worlds. - - Deprecated. Clients should utilize `checksum` to determine if DataPackage has changed since last connection and - request a new DataPackage, if necessary. - """ - required_client_version: Tuple[int, int, int] = (0, 1, 6) """ override this if changes to a world break forward-compatibility of the client @@ -256,7 +280,7 @@ class World(metaclass=AutoWorldRegister): future. Protocol level compatibility check moved to MultiServer.min_client_version. """ - required_server_version: Tuple[int, int, int] = (0, 2, 4) + required_server_version: Tuple[int, int, int] = (0, 5, 0) """update this if the resulting multidata breaks forward-compatibility of the server""" hint_blacklist: ClassVar[FrozenSet[str]] = frozenset() @@ -283,7 +307,7 @@ class World(metaclass=AutoWorldRegister): location_names: ClassVar[Set[str]] """set of all potential location names""" - random: random.Random + random: Random """This world's random object. Should be used for any randomization needed in world for this player slot.""" settings_key: ClassVar[str] @@ -300,7 +324,7 @@ def __init__(self, multiworld: "MultiWorld", player: int): assert multiworld is not None self.multiworld = multiworld self.player = player - self.random = random.Random(multiworld.random.getrandbits(64)) + self.random = Random(multiworld.random.getrandbits(64)) multiworld.per_slot_randoms[player] = self.random def __getattr__(self, item: str) -> Any: @@ -310,13 +334,15 @@ def __getattr__(self, item: str) -> Any: # overridable methods that get called by Main.py, sorted by execution order # can also be implemented as a classmethod and called "stage_", - # in that case the MultiWorld object is passed as an argument and it gets called once for the entire multiworld. + # in that case the MultiWorld object is passed as an argument, and it gets called once for the entire multiworld. # An example of this can be found in alttp as stage_pre_fill @classmethod def stage_assert_generate(cls, multiworld: "MultiWorld") -> None: - """Checks that a game is capable of generating, usually checks for some base file like a ROM. - This gets called once per present world type. Not run for unittests since they don't produce output""" + """ + Checks that a game is capable of generating, such as checking for some base file like a ROM. + This gets called once per present world type. Not run for unittests since they don't produce output. + """ pass def generate_early(self) -> None: @@ -361,16 +387,21 @@ def fill_hook(self, pass def post_fill(self) -> None: - """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. - This happens before progression balancing, so the items may not be in their final locations yet.""" + """ + Optional Method that is called after regular fill. Can be used to do adjustments before output generation. + This happens before progression balancing, so the items may not be in their final locations yet. + """ def generate_output(self, output_directory: str) -> None: - """This method gets called from a threadpool, do not use multiworld.random here. - If you need any last-second randomization, use self.random instead.""" + """ + This method gets called from a threadpool, do not use multiworld.random here. + If you need any last-second randomization, use self.random instead. + """ pass def fill_slot_data(self) -> Mapping[str, Any]: # json of WebHostLib.models.Slot - """What is returned from this function will be in the `slot_data` field + """ + What is returned from this function will be in the `slot_data` field in the `Connected` network package. It should be a `dict` with `str` keys, and should be serializable with json. @@ -378,15 +409,18 @@ def fill_slot_data(self) -> Mapping[str, Any]: # json of WebHostLib.models.Slot The client will receive this as JSON in the `Connected` response. The generation does not wait for `generate_output` to complete before calling this. - `threading.Event` can be used if you need to wait for something from `generate_output`.""" + `threading.Event` can be used if you need to wait for something from `generate_output`. + """ # The reason for the `Mapping` type annotation, rather than `dict` # is so that type checkers won't worry about the mutability of `dict`, # so you can have more specific typing in your world implementation. return {} def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): - """Fill in additional entrance information text into locations, which is displayed when hinted. - structure is {player_id: {location_id: text}} You will need to insert your own player_id.""" + """ + Fill in additional entrance information text into locations, which is displayed when hinted. + structure is {player_id: {location_id: text}} You will need to insert your own player_id. + """ pass def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata? @@ -395,13 +429,17 @@ def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDic # Spoiler writing is optional, these may not get called. def write_spoiler_header(self, spoiler_handle: TextIO) -> None: - """Write to the spoiler header. If individual it's right at the end of that player's options, - if as stage it's right under the common header before per-player options.""" + """ + Write to the spoiler header. If individual it's right at the end of that player's options, + if as stage it's right under the common header before per-player options. + """ pass def write_spoiler(self, spoiler_handle: TextIO) -> None: - """Write to the spoiler "middle", this is after the per-player options and before locations, - meant for useful or interesting info.""" + """ + Write to the spoiler "middle", this is after the per-player options and before locations, + meant for useful or interesting info. + """ pass def write_spoiler_end(self, spoiler_handle: TextIO) -> None: @@ -411,8 +449,10 @@ def write_spoiler_end(self, spoiler_handle: TextIO) -> None: # end of ordered Main.py calls def create_item(self, name: str) -> "Item": - """Create an item for this world type and player. - Warning: this may be called with self.world = None, for example by MultiServer""" + """ + Create an item for this world type and player. + Warning: this may be called with self.world = None, for example by MultiServer + """ raise NotImplementedError def get_filler_item_name(self) -> str: @@ -422,8 +462,10 @@ def get_filler_item_name(self) -> str: @classmethod def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World: - """Creates a group, which is an instance of World that is responsible for multiple others. - An example case is ItemLinks creating these.""" + """ + Creates a group, which is an instance of World that is responsible for multiple others. + An example case is ItemLinks creating these. + """ # TODO remove loop when worlds use options dataclass for option_key, option in cls.options_dataclass.type_hints.items(): getattr(multiworld, option_key)[new_player_id] = option.from_any(option.default) @@ -435,21 +477,27 @@ def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set # decent place to implement progressive items, in most cases can stay as-is def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]: - """Collect an item name into state. For speed reasons items that aren't logically useful get skipped. + """ + Collect an item name into state. For speed reasons items that aren't logically useful get skipped. Collect None to skip item. :param state: CollectionState to collect into :param item: Item to decide on if it should be collected into state - :param remove: indicate if this is meant to remove from state instead of adding.""" + :param remove: indicate if this is meant to remove from state instead of adding. + """ if item.advancement: return item.name return None - # called to create all_state, return Items that are created during pre_fill def get_pre_fill_items(self) -> List["Item"]: + """ + Used to return items that need to be collected when creating a fresh all_state, but don't exist in the + multiworld itempool. + """ return [] # these two methods can be extended for pseudo-items on state def collect(self, state: "CollectionState", item: "Item") -> bool: + """Called when an item is collected in to state. Useful for things such as progressive items or currency.""" name = self.collect_item(state, item) if name: state.prog_items[self.player][name] += 1 @@ -457,6 +505,7 @@ def collect(self, state: "CollectionState", item: "Item") -> bool: return False def remove(self, state: "CollectionState", item: "Item") -> bool: + """Called when an item is removed from to state. Useful for things such as progressive items or currency.""" name = self.collect_item(state, item, True) if name: state.prog_items[self.player][name] -= 1 @@ -465,6 +514,7 @@ def remove(self, state: "CollectionState", item: "Item") -> bool: return True return False + # following methods should not need to be overridden. def create_filler(self) -> "Item": return self.create_item(self.get_filler_item_name()) @@ -478,6 +528,10 @@ def get_entrance(self, entrance_name: str) -> "Entrance": def get_region(self, region_name: str) -> "Region": return self.multiworld.get_region(region_name, self.player) + @property + def player_name(self) -> str: + return self.multiworld.get_player_name(self.player) + @classmethod def get_data_package_data(cls) -> "GamesPackage": sorted_item_name_groups = { @@ -492,7 +546,6 @@ def get_data_package_data(cls) -> "GamesPackage": "item_name_to_id": cls.item_name_to_id, "location_name_groups": sorted_location_name_groups, "location_name_to_id": cls.location_name_to_id, - "version": cls.data_version, } res["checksum"] = data_package_checksum(res) return res @@ -510,17 +563,3 @@ def data_package_checksum(data: "GamesPackage") -> str: assert sorted(data) == list(data), "Data not ordered" from NetUtils import encode return hashlib.sha1(encode(data).encode()).hexdigest() - - -def _normalize_description(description): - """Normalizes a description in item_descriptions or location_descriptions. - - This allows authors to write descritions with nice indentation and line lengths in their world - definitions without having it affect the rendered format. - """ - # First, collapse the whitespace around newlines and the ends of the description. - description = re.sub(r' *\n *', '\n', description.strip()) - # Next, condense individual newlines into spaces. - description = re.sub(r'(? Optional[AutoPatchRegister]: return None -current_patch_version: int = 5 +class AutoPatchExtensionRegister(abc.ABCMeta): + extension_types: ClassVar[Dict[str, AutoPatchExtensionRegister]] = {} + required_extensions: Tuple[str, ...] = () + + def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoPatchExtensionRegister: + # construct class + new_class = super().__new__(mcs, name, bases, dct) + if "game" in dct: + AutoPatchExtensionRegister.extension_types[dct["game"]] = new_class + return new_class + + @staticmethod + def get_handler(game: Optional[str]) -> Union[AutoPatchExtensionRegister, List[AutoPatchExtensionRegister]]: + if not game: + return APPatchExtension + handler = AutoPatchExtensionRegister.extension_types.get(game, APPatchExtension) + if handler.required_extensions: + handlers = [handler] + for required in handler.required_extensions: + ext = AutoPatchExtensionRegister.extension_types.get(required) + if not ext: + raise NotImplementedError(f"No handler for {required}.") + handlers.append(ext) + return handlers + else: + return handler + + +container_version: int = 6 class InvalidDataError(Exception): @@ -50,7 +79,7 @@ class InvalidDataError(Exception): class APContainer: """A zipfile containing at least archipelago.json""" - version: int = current_patch_version + version: int = container_version compression_level: int = 9 compression_method: int = zipfile.ZIP_DEFLATED game: Optional[str] = None @@ -124,14 +153,31 @@ def get_manifest(self) -> Dict[str, Any]: "game": self.game, # minimum version of patch system expected for patching to be successful "compatible_version": 5, - "version": current_patch_version, + "version": container_version, } -class APPatch(APContainer, abc.ABC, metaclass=AutoPatchRegister): +class APPatch(APContainer): """ - An abstract `APContainer` that defines the requirements for an object - to be used by the `Patch.create_rom_file` function. + An `APContainer` that represents a patch file. + It includes the `procedure` key in the manifest to indicate that it is a patch. + + Your implementation should inherit from this if your output file + represents a patch file, but will not be applied with AP's `Patch.py` + """ + procedure: Union[Literal["custom"], List[Tuple[str, List[Any]]]] = "custom" + + def get_manifest(self) -> Dict[str, Any]: + manifest = super(APPatch, self).get_manifest() + manifest["procedure"] = self.procedure + manifest["compatible_version"] = 6 + return manifest + + +class APAutoPatchInterface(APPatch, abc.ABC, metaclass=AutoPatchRegister): + """ + An abstract `APPatch` that defines the requirements for a patch + to be applied with AP's `Patch.py` """ result_file_ending: str = ".sfc" @@ -140,25 +186,14 @@ def patch(self, target: str) -> None: """ create the output file with the file name `target` """ -class APDeltaPatch(APPatch): - """An APPatch that additionally has delta.bsdiff4 - containing a delta patch to get the desired file, often a rom.""" - +class APProcedurePatch(APAutoPatchInterface): + """ + An APPatch that defines a procedure to produce the desired file. + """ hash: Optional[str] # base checksum of source file - patch_file_ending: str = "" - delta: Optional[bytes] = None source_data: bytes - - def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: - self.patched_path = patched_path - super(APDeltaPatch, self).__init__(*args, **kwargs) - - def get_manifest(self) -> Dict[str, Any]: - manifest = super(APDeltaPatch, self).get_manifest() - manifest["base_checksum"] = self.hash - manifest["result_file_ending"] = self.result_file_ending - manifest["patch_file_ending"] = self.patch_file_ending - return manifest + patch_file_ending: str = "" + files: Dict[str, bytes] @classmethod def get_source_data(cls) -> bytes: @@ -171,21 +206,223 @@ def get_source_data_with_cache(cls) -> bytes: cls.source_data = cls.get_source_data() return cls.source_data - def write_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).write_contents(opened_zipfile) - # write Delta - opened_zipfile.writestr("delta.bsdiff4", - bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()), - compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression - - def read_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).read_contents(opened_zipfile) - self.delta = opened_zipfile.read("delta.bsdiff4") - - def patch(self, target: str): - """Base + Delta -> Patched""" - if not self.delta: + def __init__(self, *args: Any, **kwargs: Any): + super(APProcedurePatch, self).__init__(*args, **kwargs) + self.files = {} + + def get_manifest(self) -> Dict[str, Any]: + manifest = super(APProcedurePatch, self).get_manifest() + manifest["base_checksum"] = self.hash + manifest["result_file_ending"] = self.result_file_ending + manifest["patch_file_ending"] = self.patch_file_ending + manifest["procedure"] = self.procedure + if self.procedure == APDeltaPatch.procedure: + manifest["compatible_version"] = 5 + return manifest + + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super(APProcedurePatch, self).read_contents(opened_zipfile) + with opened_zipfile.open("archipelago.json", "r") as f: + manifest = json.load(f) + if "procedure" not in manifest: + # support patching files made before moving to procedures + self.procedure = [("apply_bsdiff4", ["delta.bsdiff4"])] + else: + self.procedure = manifest["procedure"] + for file in opened_zipfile.namelist(): + if file not in ["archipelago.json"]: + self.files[file] = opened_zipfile.read(file) + + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super(APProcedurePatch, self).write_contents(opened_zipfile) + for file in self.files: + opened_zipfile.writestr(file, self.files[file], + compress_type=zipfile.ZIP_STORED if file.endswith(".bsdiff4") else None) + + def get_file(self, file: str) -> bytes: + """ Retrieves a file from the patch container.""" + if file not in self.files: self.read() - result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta) - with open(target, "wb") as f: - f.write(result) + return self.files[file] + + def write_file(self, file_name: str, file: bytes) -> None: + """ Writes a file to the patch container, to be retrieved upon patching. """ + self.files[file_name] = file + + def patch(self, target: str) -> None: + self.read() + base_data = self.get_source_data_with_cache() + patch_extender = AutoPatchExtensionRegister.get_handler(self.game) + assert not isinstance(self.procedure, str), f"{type(self)} must define procedures" + for step, args in self.procedure: + if isinstance(patch_extender, list): + extension = next((item for item in [getattr(extender, step, None) for extender in patch_extender] + if item is not None), None) + else: + extension = getattr(patch_extender, step, None) + if extension is not None: + base_data = extension(self, base_data, *args) + else: + raise NotImplementedError(f"Unknown procedure {step} for {self.game}.") + with open(target, 'wb') as f: + f.write(base_data) + + +class APDeltaPatch(APProcedurePatch): + """An APProcedurePatch that additionally has delta.bsdiff4 + containing a delta patch to get the desired file, often a rom.""" + + procedure = [ + ("apply_bsdiff4", ["delta.bsdiff4"]) + ] + + def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: + super(APDeltaPatch, self).__init__(*args, **kwargs) + self.patched_path = patched_path + + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + self.write_file("delta.bsdiff4", + bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read())) + super(APDeltaPatch, self).write_contents(opened_zipfile) + + +class APTokenTypes(IntEnum): + WRITE = 0 + COPY = 1 + RLE = 2 + AND_8 = 3 + OR_8 = 4 + XOR_8 = 5 + + +class APTokenMixin: + """ + A class that defines functions for generating a token binary, for use in patches. + """ + _tokens: Sequence[ + Tuple[APTokenTypes, int, Union[ + bytes, # WRITE + Tuple[int, int], # COPY, RLE + int # AND_8, OR_8, XOR_8 + ]]] = () + + def get_token_binary(self) -> bytes: + """ + Returns the token binary created from stored tokens. + :return: A bytes object representing the token data. + """ + data = bytearray() + data.extend(len(self._tokens).to_bytes(4, "little")) + for token_type, offset, args in self._tokens: + data.append(token_type) + data.extend(offset.to_bytes(4, "little")) + if token_type in [APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8]: + assert isinstance(args, int), f"Arguments to AND/OR/XOR must be of type int, not {type(args)}" + data.extend(int.to_bytes(1, 4, "little")) + data.append(args) + elif token_type in [APTokenTypes.COPY, APTokenTypes.RLE]: + assert isinstance(args, tuple), f"Arguments to COPY/RLE must be of type tuple, not {type(args)}" + data.extend(int.to_bytes(8, 4, "little")) + data.extend(args[0].to_bytes(4, "little")) + data.extend(args[1].to_bytes(4, "little")) + elif token_type == APTokenTypes.WRITE: + assert isinstance(args, bytes), f"Arguments to WRITE must be of type bytes, not {type(args)}" + data.extend(len(args).to_bytes(4, "little")) + data.extend(args) + else: + raise ValueError(f"Unknown token type {token_type}") + return bytes(data) + + @overload + def write_token(self, + token_type: Literal[APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8], + offset: int, + data: int) -> None: + ... + + @overload + def write_token(self, + token_type: Literal[APTokenTypes.COPY, APTokenTypes.RLE], + offset: int, + data: Tuple[int, int]) -> None: + ... + + @overload + def write_token(self, + token_type: Literal[APTokenTypes.WRITE], + offset: int, + data: bytes) -> None: + ... + + def write_token(self, token_type: APTokenTypes, offset: int, data: Union[bytes, Tuple[int, int], int]) -> None: + """ + Stores a token to be used by patching. + """ + if not isinstance(self._tokens, list): + assert len(self._tokens) == 0, f"{type(self)}._tokens was tampered with." + self._tokens = [] + self._tokens.append((token_type, offset, data)) + + +class APPatchExtension(metaclass=AutoPatchExtensionRegister): + """Class that defines patch extension functions for a given game. + Patch extension functions must have the following two arguments in the following order: + + caller: APProcedurePatch (used to retrieve files from the patch container) + + rom: bytes (the data to patch) + + Further arguments are passed in from the procedure as defined. + + Patch extension functions must return the changed bytes. + """ + game: str + required_extensions: ClassVar[Tuple[str, ...]] = () + + @staticmethod + def apply_bsdiff4(caller: APProcedurePatch, rom: bytes, patch: str) -> bytes: + """Applies the given bsdiff4 from the patch onto the current file.""" + return bsdiff4.patch(rom, caller.get_file(patch)) + + @staticmethod + def apply_tokens(caller: APProcedurePatch, rom: bytes, token_file: str) -> bytes: + """Applies the given token file from the patch onto the current file.""" + token_data = caller.get_file(token_file) + rom_data = bytearray(rom) + token_count = int.from_bytes(token_data[0:4], "little") + bpr = 4 + for _ in range(token_count): + token_type = token_data[bpr:bpr + 1][0] + offset = int.from_bytes(token_data[bpr + 1:bpr + 5], "little") + size = int.from_bytes(token_data[bpr + 5:bpr + 9], "little") + data = token_data[bpr + 9:bpr + 9 + size] + if token_type in [APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8]: + arg = data[0] + if token_type == APTokenTypes.AND_8: + rom_data[offset] = rom_data[offset] & arg + elif token_type == APTokenTypes.OR_8: + rom_data[offset] = rom_data[offset] | arg + else: + rom_data[offset] = rom_data[offset] ^ arg + elif token_type in [APTokenTypes.COPY, APTokenTypes.RLE]: + length = int.from_bytes(data[:4], "little") + value = int.from_bytes(data[4:], "little") + if token_type == APTokenTypes.COPY: + rom_data[offset: offset + length] = rom_data[value: value + length] + else: + rom_data[offset: offset + length] = bytes([value] * length) + else: + rom_data[offset:offset + len(data)] = data + bpr += 9 + size + return bytes(rom_data) + + @staticmethod + def calc_snes_crc(caller: APProcedurePatch, rom: bytes) -> bytes: + """Calculates and applies a valid CRC for the SNES rom header.""" + rom_data = bytearray(rom) + if len(rom) < 0x8000: + raise Exception("Tried to calculate SNES CRC on file too small to be a SNES ROM.") + crc = (sum(rom_data[:0x7FDC] + rom_data[0x7FE0:]) + 0x01FE) & 0xFFFF + inv = crc ^ 0xFFFF + rom_data[0x7FDC:0x7FE0] = [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF] + return bytes(rom_data) diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index 41c0bb83295f..18c1a1661ef0 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -1,8 +1,11 @@ +import bisect +import logging +import pathlib import weakref from enum import Enum, auto -from typing import Optional, Callable, List, Iterable +from typing import Optional, Callable, List, Iterable, Tuple -from Utils import local_path +from Utils import local_path, open_filename class Type(Enum): @@ -49,8 +52,10 @@ def handles_file(self, path: str): def __repr__(self): return f"{self.__class__.__name__}({self.display_name})" + processes = weakref.WeakSet() + def launch_subprocess(func: Callable, name: str = None): global processes import multiprocessing @@ -58,13 +63,14 @@ def launch_subprocess(func: Callable, name: str = None): process.start() processes.add(process) + class SuffixIdentifier: suffixes: Iterable[str] def __init__(self, *args: str): self.suffixes = args - def __call__(self, path: str): + def __call__(self, path: str) -> bool: if isinstance(path, str): for suffix in self.suffixes: if path.endswith(suffix): @@ -77,6 +83,80 @@ def launch_textclient(): launch_subprocess(CommonClient.run_as_textclient, name="TextClient") +def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]: + if not apworld_src: + apworld_src = open_filename('Select APWorld file to install', (('APWorld', ('.apworld',)),)) + if not apworld_src: + # user closed menu + return + + if not apworld_src.endswith(".apworld"): + raise Exception(f"Wrong file format, looking for .apworld. File identified: {apworld_src}") + + apworld_path = pathlib.Path(apworld_src) + + module_name = pathlib.Path(apworld_path.name).stem + try: + import zipfile + zipfile.ZipFile(apworld_path).open(module_name + "/__init__.py") + except ValueError as e: + raise Exception("Archive appears invalid or damaged.") from e + except KeyError as e: + raise Exception("Archive appears to not be an apworld. (missing __init__.py)") from e + + import worlds + if worlds.user_folder is None: + raise Exception("Custom Worlds directory appears to not be writable.") + for world_source in worlds.world_sources: + if apworld_path.samefile(world_source.resolved_path): + # Note that this doesn't check if the same world is already installed. + # It only checks if the user is trying to install the apworld file + # that comes from the installation location (worlds or custom_worlds) + raise Exception(f"APWorld is already installed at {world_source.resolved_path}.") + + # TODO: run generic test suite over the apworld. + # TODO: have some kind of version system to tell from metadata if the apworld should be compatible. + + target = pathlib.Path(worlds.user_folder) / apworld_path.name + import shutil + shutil.copyfile(apworld_path, target) + + # If a module with this name is already loaded, then we can't load it now. + # TODO: We need to be able to unload a world module, + # so the user can update a world without restarting the application. + found_already_loaded = False + for loaded_world in worlds.world_sources: + loaded_name = pathlib.Path(loaded_world.path).stem + if module_name == loaded_name: + found_already_loaded = True + break + if found_already_loaded: + raise Exception(f"Installed APWorld successfully, but '{module_name}' is already loaded,\n" + "so a Launcher restart is required to use the new installation.") + world_source = worlds.WorldSource(str(target), is_zip=True) + bisect.insort(worlds.world_sources, world_source) + world_source.load() + + return apworld_path, target + + +def install_apworld(apworld_path: str = "") -> None: + try: + res = _install_apworld(apworld_path) + if res is None: + logging.info("Aborting APWorld installation.") + return + source, target = res + except Exception as e: + import Utils + Utils.messagebox(e.__class__.__name__, str(e), error=True) + logging.exception(e) + else: + import Utils + logging.info(f"Installed APWorld successfully, copied {source} to {target}.") + Utils.messagebox("Install complete.", f"Installed APWorld from {source}.") + + components: List[Component] = [ # Launcher Component('Launcher', 'Launcher', component_type=Type.HIDDEN), @@ -84,6 +164,7 @@ def launch_textclient(): Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True, file_identifier=SuffixIdentifier('.archipelago', '.zip')), Component('Generate', 'Generate', cli=True), + Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld")), Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient), Component('Links Awakening DX Client', 'LinksAwakeningClient', file_identifier=SuffixIdentifier('.apladx')), diff --git a/worlds/__init__.py b/worlds/__init__.py index 168bba7abf41..bb2fe866d02d 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -1,16 +1,22 @@ import importlib +import importlib.util +import logging import os import sys import warnings import zipimport import time import dataclasses -from typing import Dict, List, TypedDict, Optional +from typing import Dict, List, TypedDict from Utils import local_path, user_path local_folder = os.path.dirname(__file__) -user_folder = user_path("worlds") if user_path() != local_path() else None +user_folder = user_path("worlds") if user_path() != local_path() else user_path("custom_worlds") +try: + os.makedirs(user_folder, exist_ok=True) +except OSError: # can't access/write? + user_folder = None __all__ = { "network_data_package", @@ -20,16 +26,19 @@ "user_folder", "GamesPackage", "DataPackage", + "failed_world_loads", } +failed_world_loads: List[str] = [] + + class GamesPackage(TypedDict, total=False): item_name_groups: Dict[str, List[str]] item_name_to_id: Dict[str, int] location_name_groups: Dict[str, List[str]] location_name_to_id: Dict[str, int] checksum: str - version: int # TODO: Remove support after per game data packages API change. class DataPackage(TypedDict): @@ -41,7 +50,7 @@ class WorldSource: path: str # typically relative path from this module is_zip: bool = False relative: bool = True # relative to regular world import folder - time_taken: Optional[float] = None + time_taken: float = -1.0 def __repr__(self) -> str: return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})" @@ -85,8 +94,8 @@ def load(self) -> bool: print(f"Could not load world {self}:", file=file_like) traceback.print_exc(file=file_like) file_like.seek(0) - import logging logging.exception(file_like.read()) + failed_world_loads.append(os.path.basename(self.path).rsplit(".", 1)[0]) return False @@ -99,7 +108,12 @@ def load(self) -> bool: if not entry.name.startswith(("_", ".")): file_name = entry.name if relative else os.path.join(folder, entry.name) if entry.is_dir(): - world_sources.append(WorldSource(file_name, relative=relative)) + if os.path.isfile(os.path.join(entry.path, '__init__.py')): + world_sources.append(WorldSource(file_name, relative=relative)) + elif os.path.isfile(os.path.join(entry.path, '__init__.pyc')): + world_sources.append(WorldSource(file_name, relative=relative)) + else: + logging.warning(f"excluding {entry.name} from world sources because it has no __init__.py") elif entry.is_file() and entry.name.endswith(".apworld"): world_sources.append(WorldSource(file_name, is_zip=True, relative=relative)) @@ -114,3 +128,4 @@ def load(self) -> bool: network_data_package: DataPackage = { "games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()}, } + diff --git a/worlds/_bizhawk/__init__.py b/worlds/_bizhawk/__init__.py index 94a9ce1ddf04..74f2954b984b 100644 --- a/worlds/_bizhawk/__init__.py +++ b/worlds/_bizhawk/__init__.py @@ -103,7 +103,7 @@ async def connect(ctx: BizHawkContext) -> bool: return True except (TimeoutError, ConnectionRefusedError): continue - + # No ports worked ctx.streams = None ctx.connection_status = ConnectionStatus.NOT_CONNECTED diff --git a/worlds/_bizhawk/client.py b/worlds/_bizhawk/client.py index 32a6e3704e1e..00370c277a17 100644 --- a/worlds/_bizhawk/client.py +++ b/worlds/_bizhawk/client.py @@ -2,7 +2,6 @@ A module containing the BizHawkClient base class and metaclass """ - from __future__ import annotations import abc @@ -12,14 +11,13 @@ if TYPE_CHECKING: from .context import BizHawkClientContext -else: - BizHawkClientContext = object def launch_client(*args) -> None: from .context import launch launch_subprocess(launch, name="BizHawkClient") + component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client, file_identifier=SuffixIdentifier()) components.append(component) @@ -56,7 +54,7 @@ def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) return new_class @staticmethod - async def get_handler(ctx: BizHawkClientContext, system: str) -> Optional[BizHawkClient]: + async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]: for systems, handlers in AutoBizHawkClientRegister.game_handlers.items(): if system in systems: for handler in handlers.values(): @@ -77,7 +75,7 @@ class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister): """The file extension(s) this client is meant to open and patch (e.g. ".apz3")""" @abc.abstractmethod - async def validate_rom(self, ctx: BizHawkClientContext) -> bool: + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: """Should return whether the currently loaded ROM should be handled by this client. You might read the game name from the ROM header, for example. This function will only be asked to validate ROMs from the system set by the client class, so you do not need to check the system yourself. @@ -86,18 +84,18 @@ async def validate_rom(self, ctx: BizHawkClientContext) -> bool: as necessary (such as setting `ctx.game = self.game`, modifying `ctx.items_handling`, etc...).""" ... - async def set_auth(self, ctx: BizHawkClientContext) -> None: + async def set_auth(self, ctx: "BizHawkClientContext") -> None: """Should set ctx.auth in anticipation of sending a `Connected` packet. You may override this if you store slot name in your patched ROM. If ctx.auth is not set after calling, the player will be prompted to enter their username.""" pass @abc.abstractmethod - async def game_watcher(self, ctx: BizHawkClientContext) -> None: + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: """Runs on a loop with the approximate interval `ctx.watcher_timeout`. The currently loaded ROM is guaranteed to have passed your validator when this function is called, and the emulator is very likely to be connected.""" ... - def on_package(self, ctx: BizHawkClientContext, cmd: str, args: dict) -> None: + def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: """For handling packages from the server. Called from `BizHawkClientContext.on_package`.""" pass diff --git a/worlds/_bizhawk/context.py b/worlds/_bizhawk/context.py index 85e2c9909738..234faf3b65cf 100644 --- a/worlds/_bizhawk/context.py +++ b/worlds/_bizhawk/context.py @@ -3,7 +3,6 @@ checking or launching the client, otherwise it will probably cause circular import issues. """ - import asyncio import enum import subprocess @@ -77,7 +76,7 @@ def on_package(self, cmd, args): if self.client_handler is not None: self.client_handler.on_package(self, cmd, args) - async def server_auth(self, password_requested: bool = False): + async def server_auth(self, password_requested: bool=False): self.password_requested = password_requested if self.bizhawk_ctx.connection_status != ConnectionStatus.CONNECTED: @@ -103,7 +102,7 @@ async def server_auth(self, password_requested: bool = False): await self.send_connect() self.auth_status = AuthStatus.PENDING - async def disconnect(self, allow_autoreconnect: bool = False): + async def disconnect(self, allow_autoreconnect: bool=False): self.auth_status = AuthStatus.NOT_AUTHENTICATED await super().disconnect(allow_autoreconnect) @@ -148,7 +147,8 @@ async def _game_watcher(ctx: BizHawkClientContext): script_version = await get_script_version(ctx.bizhawk_ctx) if script_version != EXPECTED_SCRIPT_VERSION: - logger.info(f"Connector script is incompatible. Expected version {EXPECTED_SCRIPT_VERSION} but got {script_version}. Disconnecting.") + logger.info(f"Connector script is incompatible. Expected version {EXPECTED_SCRIPT_VERSION} but " + f"got {script_version}. Disconnecting.") disconnect(ctx.bizhawk_ctx) continue @@ -168,6 +168,7 @@ async def _game_watcher(ctx: BizHawkClientContext): ctx.auth = None ctx.username = None ctx.client_handler = None + ctx.finished_game = False await ctx.disconnect(False) ctx.rom_hash = rom_hash @@ -177,7 +178,8 @@ async def _game_watcher(ctx: BizHawkClientContext): if ctx.client_handler is None: if not showed_no_handler_message: - logger.info("No handler was found for this game") + logger.info("No handler was found for this game. Double-check that the apworld is installed " + "correctly and that you loaded the right ROM file.") showed_no_handler_message = True continue else: @@ -234,8 +236,11 @@ async def _run_game(rom: str): async def _patch_and_run_game(patch_file: str): - metadata, output_file = Patch.create_rom_file(patch_file) - Utils.async_start(_run_game(output_file)) + try: + metadata, output_file = Patch.create_rom_file(patch_file) + Utils.async_start(_run_game(output_file)) + except Exception as exc: + logger.exception(exc) def launch() -> None: diff --git a/worlds/_sc2common/bot/bot_ai.py b/worlds/_sc2common/bot/bot_ai.py index 79c11a5ad4a3..b08f8af29ac0 100644 --- a/worlds/_sc2common/bot/bot_ai.py +++ b/worlds/_sc2common/bot/bot_ai.py @@ -12,6 +12,8 @@ from .unit import Unit from .units import Units +from worlds._sc2common.bot import logger + if TYPE_CHECKING: from .game_info import Ramp @@ -310,6 +312,7 @@ async def chat_send(self, message: str, team_only: bool = False): :param message: :param team_only:""" assert isinstance(message, str), f"{message} is not a string" + logger.debug("Sending message: " + message) await self.client.chat_send(message, team_only) def in_map_bounds(self, pos: Union[Point2, tuple, list]) -> bool: diff --git a/worlds/_sc2common/bot/sc2process.py b/worlds/_sc2common/bot/sc2process.py index e36632165979..f74ed9c18f9f 100644 --- a/worlds/_sc2common/bot/sc2process.py +++ b/worlds/_sc2common/bot/sc2process.py @@ -28,6 +28,11 @@ def add(cls, value): logger.debug("kill_switch: Add switch") cls._to_kill.append(value) + @classmethod + def kill(cls, value): + logger.info(f"kill_switch: Process cleanup for 1 process") + value._clean(verbose=False) + @classmethod def kill_all(cls): logger.info(f"kill_switch: Process cleanup for {len(cls._to_kill)} processes") @@ -116,7 +121,7 @@ def signal_handler(*_args): async def __aexit__(self, *args): logger.exception("async exit") await self._close_connection() - kill_switch.kill_all() + kill_switch.kill(self) signal.signal(signal.SIGINT, signal.SIG_DFL) @property diff --git a/worlds/adventure/Locations.py b/worlds/adventure/Locations.py index 2ef561b1e3e1..27e504684cbf 100644 --- a/worlds/adventure/Locations.py +++ b/worlds/adventure/Locations.py @@ -19,9 +19,9 @@ def __init__(self, room_id: int, room_x: int = None, room_y: int = None): def get_position(self, random): if self.room_x is None or self.room_y is None: - return random.choice(standard_positions) + return self.room_id, random.choice(standard_positions) else: - return self.room_x, self.room_y + return self.room_id, (self.room_x, self.room_y) class LocationData: @@ -46,24 +46,26 @@ def __init__(self, region, name, location_id, world_positions: [WorldPosition] = self.needs_bat_logic: int = needs_bat_logic self.local_item: int = None - def get_position(self, random): + def get_random_position(self, random): + x: int = None + y: int = None if self.world_positions is None or len(self.world_positions) == 0: if self.room_id is None: return None - self.room_x, self.room_y = random.choice(standard_positions) - if self.room_id is None: + x, y = random.choice(standard_positions) + return self.room_id, x, y + else: selected_pos = random.choice(self.world_positions) - self.room_id = selected_pos.room_id - self.room_x, self.room_y = selected_pos.get_position(random) - return self.room_x, self.room_y + room_id, (x, y) = selected_pos.get_position(random) + return self.get_random_room_id(random), x, y - def get_room_id(self, random): + def get_random_room_id(self, random): if self.world_positions is None or len(self.world_positions) == 0: - return None + if self.room_id is None: + return None if self.room_id is None: selected_pos = random.choice(self.world_positions) - self.room_id = selected_pos.room_id - self.room_x, self.room_y = selected_pos.get_position(random) + return selected_pos.room_id return self.room_id @@ -97,7 +99,7 @@ def get_random_room_in_regions(regions: [str], random) -> int: possible_rooms = {} for locname in location_table: if location_table[locname].region in regions: - room = location_table[locname].get_room_id(random) + room = location_table[locname].get_random_room_id(random) if room is not None: possible_rooms[room] = location_table[locname].room_id return random.choice(list(possible_rooms.keys())) diff --git a/worlds/adventure/Options.py b/worlds/adventure/Options.py index fb09e5329b82..e6a8e4c20200 100644 --- a/worlds/adventure/Options.py +++ b/worlds/adventure/Options.py @@ -2,7 +2,8 @@ from typing import Dict -from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle +from dataclasses import dataclass +from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions class FreeincarnateMax(Range): @@ -223,22 +224,22 @@ class StartCastle(Choice): option_white = 2 default = option_yellow +@dataclass +class AdventureOptions(PerGameCommonOptions): + dragon_slay_check: DragonSlayCheck + death_link: DeathLink + bat_logic: BatLogic + freeincarnate_max: FreeincarnateMax + dragon_rando_type: DragonRandoType + connector_multi_slot: ConnectorMultiSlot + yorgle_speed: YorgleStartingSpeed + yorgle_min_speed: YorgleMinimumSpeed + grundle_speed: GrundleStartingSpeed + grundle_min_speed: GrundleMinimumSpeed + rhindle_speed: RhindleStartingSpeed + rhindle_min_speed: RhindleMinimumSpeed + difficulty_switch_a: DifficultySwitchA + difficulty_switch_b: DifficultySwitchB + start_castle: StartCastle + -adventure_option_definitions: Dict[str, type(Option)] = { - "dragon_slay_check": DragonSlayCheck, - "death_link": DeathLink, - "bat_logic": BatLogic, - "freeincarnate_max": FreeincarnateMax, - "dragon_rando_type": DragonRandoType, - "connector_multi_slot": ConnectorMultiSlot, - "yorgle_speed": YorgleStartingSpeed, - "yorgle_min_speed": YorgleMinimumSpeed, - "grundle_speed": GrundleStartingSpeed, - "grundle_min_speed": GrundleMinimumSpeed, - "rhindle_speed": RhindleStartingSpeed, - "rhindle_min_speed": RhindleMinimumSpeed, - "difficulty_switch_a": DifficultySwitchA, - "difficulty_switch_b": DifficultySwitchB, - "start_castle": StartCastle, - -} \ No newline at end of file diff --git a/worlds/adventure/Regions.py b/worlds/adventure/Regions.py index 4a62518fbd36..e72806ca454f 100644 --- a/worlds/adventure/Regions.py +++ b/worlds/adventure/Regions.py @@ -1,4 +1,5 @@ from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType +from Options import PerGameCommonOptions from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region @@ -24,9 +25,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call connect(world, player, target, source, rule, True) -def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> None: - for name, locdata in location_table.items(): - locdata.get_position(multiworld.random) +def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None: menu = Region("Menu", player, multiworld) @@ -76,7 +75,7 @@ def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> Non credits_room_far_side.exits.append(Entrance(player, "CreditsFromFarSide", credits_room_far_side)) multiworld.regions.append(credits_room_far_side) - dragon_slay_check = multiworld.dragon_slay_check[player].value + dragon_slay_check = options.dragon_slay_check.value priority_locations = determine_priority_locations(multiworld, dragon_slay_check) for name, location_data in location_table.items(): diff --git a/worlds/adventure/Rom.py b/worlds/adventure/Rom.py index 9f1ca3fe5eba..ca64e569716a 100644 --- a/worlds/adventure/Rom.py +++ b/worlds/adventure/Rom.py @@ -7,7 +7,7 @@ import Utils from .Locations import AdventureLocation, LocationData from settings import get_settings -from worlds.Files import APDeltaPatch, AutoPatchRegister, APContainer +from worlds.Files import APPatch, AutoPatchRegister import bsdiff4 @@ -78,7 +78,7 @@ def get_dict(self): return ret_dict -class AdventureDeltaPatch(APContainer, metaclass=AutoPatchRegister): +class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister): hash = ADVENTUREHASH game = "Adventure" patch_file_ending = ".apadvn" diff --git a/worlds/adventure/Rules.py b/worlds/adventure/Rules.py index 6f4b53faa11b..53617b039d78 100644 --- a/worlds/adventure/Rules.py +++ b/worlds/adventure/Rules.py @@ -1,12 +1,10 @@ -from worlds.adventure import location_table -from worlds.adventure.Options import BatLogic, DifficultySwitchB, DifficultySwitchA +from .Options import BatLogic, DifficultySwitchB from worlds.generic.Rules import add_rule, set_rule, forbid_item -from BaseClasses import LocationProgressType def set_rules(self) -> None: world = self.multiworld - use_bat_logic = world.bat_logic[self.player].value == BatLogic.option_use_logic + use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic set_rule(world.get_entrance("YellowCastlePort", self.player), lambda state: state.has("Yellow Key", self.player)) @@ -28,7 +26,7 @@ def set_rules(self) -> None: lambda state: state.has("Bridge", self.player) or state.has("Magnet", self.player)) - dragon_slay_check = world.dragon_slay_check[self.player].value + dragon_slay_check = self.options.dragon_slay_check.value if dragon_slay_check: if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item: set_rule(world.get_location("Slay Yorgle", self.player), diff --git a/worlds/adventure/__init__.py b/worlds/adventure/__init__.py index 9b9b0d77d800..ed5ebbd3dc56 100644 --- a/worlds/adventure/__init__.py +++ b/worlds/adventure/__init__.py @@ -15,7 +15,8 @@ from worlds.AutoWorld import WebWorld, World from Fill import fill_restrictive from worlds.generic.Rules import add_rule, set_rule -from .Options import adventure_option_definitions, DragonRandoType, DifficultySwitchA, DifficultySwitchB +from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \ + AdventureOptions from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \ AdventureAutoCollectLocation from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max @@ -109,11 +110,10 @@ class AdventureWorld(World): game: ClassVar[str] = "Adventure" web: ClassVar[WebWorld] = AdventureWeb() - option_definitions: ClassVar[Dict[str, AssembleOptions]] = adventure_option_definitions + options_dataclass = AdventureOptions settings: ClassVar[AdventureSettings] item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()} location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()} - data_version: ClassVar[int] = 1 required_client_version: Tuple[int, int, int] = (0, 3, 9) def __init__(self, world: MultiWorld, player: int): @@ -150,18 +150,18 @@ def generate_early(self) -> None: bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21] self.rom_name.extend([0] * (21 - len(self.rom_name))) - self.dragon_rando_type = self.multiworld.dragon_rando_type[self.player].value - self.dragon_slay_check = self.multiworld.dragon_slay_check[self.player].value - self.connector_multi_slot = self.multiworld.connector_multi_slot[self.player].value - self.yorgle_speed = self.multiworld.yorgle_speed[self.player].value - self.yorgle_min_speed = self.multiworld.yorgle_min_speed[self.player].value - self.grundle_speed = self.multiworld.grundle_speed[self.player].value - self.grundle_min_speed = self.multiworld.grundle_min_speed[self.player].value - self.rhindle_speed = self.multiworld.rhindle_speed[self.player].value - self.rhindle_min_speed = self.multiworld.rhindle_min_speed[self.player].value - self.difficulty_switch_a = self.multiworld.difficulty_switch_a[self.player].value - self.difficulty_switch_b = self.multiworld.difficulty_switch_b[self.player].value - self.start_castle = self.multiworld.start_castle[self.player].value + self.dragon_rando_type = self.options.dragon_rando_type.value + self.dragon_slay_check = self.options.dragon_slay_check.value + self.connector_multi_slot = self.options.connector_multi_slot.value + self.yorgle_speed = self.options.yorgle_speed.value + self.yorgle_min_speed = self.options.yorgle_min_speed.value + self.grundle_speed = self.options.grundle_speed.value + self.grundle_min_speed = self.options.grundle_min_speed.value + self.rhindle_speed = self.options.rhindle_speed.value + self.rhindle_min_speed = self.options.rhindle_min_speed.value + self.difficulty_switch_a = self.options.difficulty_switch_a.value + self.difficulty_switch_b = self.options.difficulty_switch_b.value + self.start_castle = self.options.start_castle.value self.created_items = 0 if self.dragon_slay_check == 0: @@ -228,7 +228,7 @@ def create_items(self) -> None: extra_filler_count = num_locations - self.created_items # traps would probably go here, if enabled - freeincarnate_max = self.multiworld.freeincarnate_max[self.player].value + freeincarnate_max = self.options.freeincarnate_max.value actual_freeincarnates = min(extra_filler_count, freeincarnate_max) self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)] self.created_items += actual_freeincarnates @@ -248,7 +248,7 @@ def create_dragon_slow_items(self, min_speed: int, speed: int, item_name: str, m self.created_items += 1 def create_regions(self) -> None: - create_regions(self.multiworld, self.player, self.dragon_rooms) + create_regions(self.options, self.multiworld, self.player, self.dragon_rooms) set_rules = set_rules @@ -355,7 +355,7 @@ def generate_output(self, output_directory: str) -> None: auto_collect_locations: [AdventureAutoCollectLocation] = [] local_item_to_location: {int, int} = {} bat_no_touch_locs: [LocationData] = [] - bat_logic: int = self.multiworld.bat_logic[self.player].value + bat_logic: int = self.options.bat_logic.value try: rom_deltas: { int, int } = {} self.place_dragons(rom_deltas) @@ -371,8 +371,9 @@ def generate_output(self, output_directory: str) -> None: if location.item.player == self.player and \ location.item.name == "nothing": location_data = location_table[location.name] + room_id = location_data.get_random_room_id(self.random) auto_collect_locations.append(AdventureAutoCollectLocation(location_data.short_location_id, - location_data.room_id)) + room_id)) # standard Adventure items, which are placed in the rom elif location.item.player == self.player and \ location.item.name != "nothing" and \ @@ -383,14 +384,18 @@ def generate_output(self, output_directory: str) -> None: item_ram_address = item_ram_addresses[item_table[location.item.name].table_index] item_position_data_start = item_position_table + item_ram_address - items_ram_start location_data = location_table[location.name] - room_x, room_y = location_data.get_position(self.multiworld.per_slot_randoms[self.player]) + (room_id, room_x, room_y) = \ + location_data.get_random_position(self.random) if location_data.needs_bat_logic and bat_logic == 0x0: copied_location = copy.copy(location_data) copied_location.local_item = item_ram_address + copied_location.room_id = room_id + copied_location.room_x = room_x + copied_location.room_y = room_y bat_no_touch_locs.append(copied_location) del unplaced_local_items[location.item.name] - rom_deltas[item_position_data_start] = location_data.room_id + rom_deltas[item_position_data_start] = room_id rom_deltas[item_position_data_start + 1] = room_x rom_deltas[item_position_data_start + 2] = room_y local_item_to_location[item_table_offset] = self.location_name_to_id[location.name] \ @@ -398,20 +403,26 @@ def generate_output(self, output_directory: str) -> None: # items from other worlds, and non-standard Adventure items handled by script, like difficulty switches elif location.item.code is not None: if location.item.code != nothing_item_id: - location_data = location_table[location.name] + location_data = copy.copy(location_table[location.name]) + (room_id, room_x, room_y) = \ + location_data.get_random_position(self.random) + location_data.room_id = room_id + location_data.room_x = room_x + location_data.room_y = room_y foreign_item_locations.append(location_data) if location_data.needs_bat_logic and bat_logic == 0x0: bat_no_touch_locs.append(location_data) else: location_data = location_table[location.name] + room_id = location_data.get_random_room_id(self.random) auto_collect_locations.append(AdventureAutoCollectLocation(location_data.short_location_id, - location_data.room_id)) + room_id)) # Adventure items that are in another world get put in an invalid room until needed for unplaced_item_name, unplaced_item in unplaced_local_items.items(): item_position_data_start = get_item_position_data_start(unplaced_item.table_index) rom_deltas[item_position_data_start] = 0xff - if self.multiworld.connector_multi_slot[self.player].value: + if self.options.connector_multi_slot.value: rom_deltas[connector_port_offset] = (self.player & 0xff) else: rom_deltas[connector_port_offset] = 0 diff --git a/worlds/adventure/docs/en_Adventure.md b/worlds/adventure/docs/en_Adventure.md index c39e0f7d919d..f5216e9145b2 100644 --- a/worlds/adventure/docs/en_Adventure.md +++ b/worlds/adventure/docs/en_Adventure.md @@ -1,11 +1,11 @@ # Adventure -## Where is the settings page? -The [player settings page for Adventure](../player-settings) contains all the options you need to configure and export a config file. +## Where is the options page? +The [player options page for Adventure](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? Adventure items may be distributed into additional locations not possible in the vanilla Adventure randomizer. All -Adventure items are added to the multiworld item pool. Depending on the settings, dragon locations may be randomized, +Adventure items are added to the multiworld item pool. Depending on the `dragon_rando_type` value, dragon locations may be randomized, slaying dragons may award items, difficulty switches may require items to unlock, and limited use 'freeincarnates' can allow reincarnation without resurrecting dragons. Dragon speeds may also be randomized, and items may exist to reduce their speeds. @@ -15,7 +15,7 @@ Same as vanilla; Find the Enchanted Chalice and return it to the Yellow Castle ## Which items can be in another player's world? All three keys, the chalice, the sword, the magnet, and the bridge can be found in another player's world. Depending on -settings, dragon slowdowns, difficulty switch unlocks, and freeincarnates may also be found. +options, dragon slowdowns, difficulty switch unlocks, and freeincarnates may also be found. ## What is considered a location check in Adventure? Most areas in Adventure have one or more locations which can contain an Adventure item or an Archipelago item. @@ -41,7 +41,7 @@ A message is shown in the client log. While empty handed, the player can press order they were received. Once an item is retrieved this way, it cannot be retrieved again until pressing select to return to the 'GO' screen or doing a hard reset, either one of which will reset all items to their original positions. -## What are recommended settings to tweak for beginners to the rando? +## What are recommended options to tweak for beginners to the rando? Setting difficulty_switch_a and lowering the dragons' speeds makes the dragons easier to avoid. Adding Chalice to local_items guarantees you'll visit at least one of the interesting castles, as it can only be placed in a castle or the credits room. diff --git a/worlds/adventure/docs/setup_en.md b/worlds/adventure/docs/setup_en.md index 7378a018c7c1..060225e3971a 100644 --- a/worlds/adventure/docs/setup_en.md +++ b/worlds/adventure/docs/setup_en.md @@ -41,9 +41,9 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -You can generate a yaml or download a template by visiting the [Adventure Settings Page](/games/Adventure/player-settings) +You can generate a yaml or download a template by visiting the [Adventure Options Page](/games/Adventure/player-options) -### What are recommended settings to tweak for beginners to the rando? +### What are recommended options to tweak for beginners to the rando? Setting difficulty_switch_a and lowering the dragons' speeds makes the dragons easier to avoid. Adding Chalice to local_items guarantees you'll visit at least one of the interesting castles, as it can only be placed in a castle or the credits room. diff --git a/worlds/adventure/docs/setup_fr.md b/worlds/adventure/docs/setup_fr.md index 07881ce94da4..e8346fe6f088 100644 --- a/worlds/adventure/docs/setup_fr.md +++ b/worlds/adventure/docs/setup_fr.md @@ -42,7 +42,7 @@ une expérience personnalisée à leur goût, et différents joueurs dans le mê ### Où puis-je obtenir un fichier YAML ? -Vous pouvez générer un yaml ou télécharger un modèle en visitant la [page des paramètres d'aventure](/games/Adventure/player-settings) +Vous pouvez générer un yaml ou télécharger un modèle en visitant la [page des paramètres d'aventure](/games/Adventure/player-options) ### Quels sont les paramètres recommandés pour s'initier à la rando ? Régler la difficulty_switch_a et réduire la vitesse des dragons rend les dragons plus faciles à éviter. Ajouter Calice à @@ -72,4 +72,4 @@ configuré pour le faire automatiquement. Pour connecter le client au multiserveur, mettez simplement `:` dans le champ de texte en haut et appuyez sur Entrée (si le le serveur utilise un mot de passe, saisissez dans le champ de texte inférieur `/connect  : [mot de passe]`) -Appuyez sur Réinitialiser et commencez à jouer \ No newline at end of file +Appuyez sur Réinitialiser et commencez à jouer diff --git a/worlds/ahit/Client.py b/worlds/ahit/Client.py new file mode 100644 index 000000000000..2cd67e468294 --- /dev/null +++ b/worlds/ahit/Client.py @@ -0,0 +1,232 @@ +import asyncio +import Utils +import websockets +import functools +from copy import deepcopy +from typing import List, Any, Iterable +from NetUtils import decode, encode, JSONtoTextParser, JSONMessagePart, NetworkItem +from MultiServer import Endpoint +from CommonClient import CommonContext, gui_enabled, ClientCommandProcessor, logger, get_base_parser + +DEBUG = False + + +class AHITJSONToTextParser(JSONtoTextParser): + def _handle_color(self, node: JSONMessagePart): + return self._handle_text(node) # No colors for the in-game text + + +class AHITCommandProcessor(ClientCommandProcessor): + def _cmd_ahit(self): + """Check AHIT Connection State""" + if isinstance(self.ctx, AHITContext): + logger.info(f"AHIT Status: {self.ctx.get_ahit_status()}") + + +class AHITContext(CommonContext): + command_processor = AHITCommandProcessor + game = "A Hat in Time" + + def __init__(self, server_address, password): + super().__init__(server_address, password) + self.proxy = None + self.proxy_task = None + self.gamejsontotext = AHITJSONToTextParser(self) + self.autoreconnect_task = None + self.endpoint = None + self.items_handling = 0b111 + self.room_info = None + self.connected_msg = None + self.game_connected = False + self.awaiting_info = False + self.full_inventory: List[Any] = [] + self.server_msgs: List[Any] = [] + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super(AHITContext, self).server_auth(password_requested) + + await self.get_username() + await self.send_connect() + + def get_ahit_status(self) -> str: + if not self.is_proxy_connected(): + return "Not connected to A Hat in Time" + + return "Connected to A Hat in Time" + + async def send_msgs_proxy(self, msgs: Iterable[dict]) -> bool: + """ `msgs` JSON serializable """ + if not self.endpoint or not self.endpoint.socket.open or self.endpoint.socket.closed: + return False + + if DEBUG: + logger.info(f"Outgoing message: {msgs}") + + await self.endpoint.socket.send(msgs) + return True + + async def disconnect(self, allow_autoreconnect: bool = False): + await super().disconnect(allow_autoreconnect) + + async def disconnect_proxy(self): + if self.endpoint and not self.endpoint.socket.closed: + await self.endpoint.socket.close() + if self.proxy_task is not None: + await self.proxy_task + + def is_connected(self) -> bool: + return self.server and self.server.socket.open + + def is_proxy_connected(self) -> bool: + return self.endpoint and self.endpoint.socket.open + + def on_print_json(self, args: dict): + text = self.gamejsontotext(deepcopy(args["data"])) + msg = {"cmd": "PrintJSON", "data": [{"text": text}], "type": "Chat"} + self.server_msgs.append(encode([msg])) + + if self.ui: + self.ui.print_json(args["data"]) + else: + text = self.jsontotextparser(args["data"]) + logger.info(text) + + def update_items(self): + # just to be safe - we might still have an inventory from a different room + if not self.is_connected(): + return + + self.server_msgs.append(encode([{"cmd": "ReceivedItems", "index": 0, "items": self.full_inventory}])) + + def on_package(self, cmd: str, args: dict): + if cmd == "Connected": + self.connected_msg = encode([args]) + if self.awaiting_info: + self.server_msgs.append(self.room_info) + self.update_items() + self.awaiting_info = False + + elif cmd == "ReceivedItems": + if args["index"] == 0: + self.full_inventory.clear() + + for item in args["items"]: + self.full_inventory.append(NetworkItem(*item)) + + self.server_msgs.append(encode([args])) + + elif cmd == "RoomInfo": + self.seed_name = args["seed_name"] + self.room_info = encode([args]) + + else: + if cmd != "PrintJSON": + self.server_msgs.append(encode([args])) + + def run_gui(self): + from kvui import GameManager + + class AHITManager(GameManager): + logging_pairs = [ + ("Client", "Archipelago") + ] + base_title = "Archipelago A Hat in Time Client" + + self.ui = AHITManager(self) + self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + + +async def proxy(websocket, path: str = "/", ctx: AHITContext = None): + ctx.endpoint = Endpoint(websocket) + try: + await on_client_connected(ctx) + + if ctx.is_proxy_connected(): + async for data in websocket: + if DEBUG: + logger.info(f"Incoming message: {data}") + + for msg in decode(data): + if msg["cmd"] == "Connect": + # Proxy is connecting, make sure it is valid + if msg["game"] != "A Hat in Time": + logger.info("Aborting proxy connection: game is not A Hat in Time") + await ctx.disconnect_proxy() + break + + if ctx.seed_name: + seed_name = msg.get("seed_name", "") + if seed_name != "" and seed_name != ctx.seed_name: + logger.info("Aborting proxy connection: seed mismatch from save file") + logger.info(f"Expected: {ctx.seed_name}, got: {seed_name}") + text = encode([{"cmd": "PrintJSON", + "data": [{"text": "Connection aborted - save file to seed mismatch"}]}]) + await ctx.send_msgs_proxy(text) + await ctx.disconnect_proxy() + break + + if ctx.connected_msg and ctx.is_connected(): + await ctx.send_msgs_proxy(ctx.connected_msg) + ctx.update_items() + continue + + if not ctx.is_proxy_connected(): + break + + await ctx.send_msgs([msg]) + + except Exception as e: + if not isinstance(e, websockets.WebSocketException): + logger.exception(e) + finally: + await ctx.disconnect_proxy() + + +async def on_client_connected(ctx: AHITContext): + if ctx.room_info and ctx.is_connected(): + await ctx.send_msgs_proxy(ctx.room_info) + else: + ctx.awaiting_info = True + + +async def proxy_loop(ctx: AHITContext): + try: + while not ctx.exit_event.is_set(): + if len(ctx.server_msgs) > 0: + for msg in ctx.server_msgs: + await ctx.send_msgs_proxy(msg) + + ctx.server_msgs.clear() + await asyncio.sleep(0.1) + except Exception as e: + logger.exception(e) + logger.info("Aborting AHIT Proxy Client due to errors") + + +def launch(): + async def main(): + parser = get_base_parser() + args = parser.parse_args() + + ctx = AHITContext(args.connect, args.password) + logger.info("Starting A Hat in Time proxy server") + ctx.proxy = websockets.serve(functools.partial(proxy, ctx=ctx), + host="localhost", port=11311, ping_timeout=999999, ping_interval=999999) + ctx.proxy_task = asyncio.create_task(proxy_loop(ctx), name="ProxyLoop") + + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + + await ctx.proxy + await ctx.proxy_task + await ctx.exit_event.wait() + + Utils.init_logging("AHITClient") + # options = Utils.get_options() + + import colorama + colorama.init() + asyncio.run(main()) + colorama.deinit() diff --git a/worlds/ahit/DeathWishLocations.py b/worlds/ahit/DeathWishLocations.py new file mode 100644 index 000000000000..ef74cadcaa53 --- /dev/null +++ b/worlds/ahit/DeathWishLocations.py @@ -0,0 +1,243 @@ +from .Types import HatInTimeLocation, HatInTimeItem +from .Regions import create_region +from BaseClasses import Region, LocationProgressType, ItemClassification +from worlds.generic.Rules import add_rule +from typing import List, TYPE_CHECKING +from .Locations import death_wishes +from .Options import EndGoal + +if TYPE_CHECKING: + from . import HatInTimeWorld + + +dw_prereqs = { + "So You're Back From Outer Space": ["Beat the Heat"], + "Snatcher's Hit List": ["Beat the Heat"], + "Snatcher Coins in Mafia Town": ["So You're Back From Outer Space"], + "Rift Collapse: Mafia of Cooks": ["So You're Back From Outer Space"], + "Collect-a-thon": ["So You're Back From Outer Space"], + "She Speedran from Outer Space": ["Rift Collapse: Mafia of Cooks"], + "Mafia's Jumps": ["She Speedran from Outer Space"], + "Vault Codes in the Wind": ["Collect-a-thon", "She Speedran from Outer Space"], + "Encore! Encore!": ["Collect-a-thon"], + + "Security Breach": ["Beat the Heat"], + "Rift Collapse: Dead Bird Studio": ["Security Breach"], + "The Great Big Hootenanny": ["Security Breach"], + "10 Seconds until Self-Destruct": ["The Great Big Hootenanny"], + "Killing Two Birds": ["Rift Collapse: Dead Bird Studio", "10 Seconds until Self-Destruct"], + "Community Rift: Rhythm Jump Studio": ["10 Seconds until Self-Destruct"], + "Snatcher Coins in Battle of the Birds": ["The Great Big Hootenanny"], + "Zero Jumps": ["Rift Collapse: Dead Bird Studio"], + "Snatcher Coins in Nyakuza Metro": ["Killing Two Birds"], + + "Speedrun Well": ["Beat the Heat"], + "Rift Collapse: Sleepy Subcon": ["Speedrun Well"], + "Boss Rush": ["Speedrun Well"], + "Quality Time with Snatcher": ["Rift Collapse: Sleepy Subcon"], + "Breaching the Contract": ["Boss Rush", "Quality Time with Snatcher"], + "Community Rift: Twilight Travels": ["Quality Time with Snatcher"], + "Snatcher Coins in Subcon Forest": ["Rift Collapse: Sleepy Subcon"], + + "Bird Sanctuary": ["Beat the Heat"], + "Snatcher Coins in Alpine Skyline": ["Bird Sanctuary"], + "Wound-Up Windmill": ["Bird Sanctuary"], + "Rift Collapse: Alpine Skyline": ["Bird Sanctuary"], + "Camera Tourist": ["Rift Collapse: Alpine Skyline"], + "Community Rift: The Mountain Rift": ["Rift Collapse: Alpine Skyline"], + "The Illness has Speedrun": ["Rift Collapse: Alpine Skyline", "Wound-Up Windmill"], + + "The Mustache Gauntlet": ["Wound-Up Windmill"], + "No More Bad Guys": ["The Mustache Gauntlet"], + "Seal the Deal": ["Encore! Encore!", "Killing Two Birds", + "Breaching the Contract", "No More Bad Guys"], + + "Rift Collapse: Deep Sea": ["Rift Collapse: Mafia of Cooks", "Rift Collapse: Dead Bird Studio", + "Rift Collapse: Sleepy Subcon", "Rift Collapse: Alpine Skyline"], + + "Cruisin' for a Bruisin'": ["Rift Collapse: Deep Sea"], +} + +dw_candles = [ + "Snatcher's Hit List", + "Zero Jumps", + "Camera Tourist", + "Snatcher Coins in Mafia Town", + "Snatcher Coins in Battle of the Birds", + "Snatcher Coins in Subcon Forest", + "Snatcher Coins in Alpine Skyline", + "Snatcher Coins in Nyakuza Metro", +] + +annoying_dws = [ + "Vault Codes in the Wind", + "Boss Rush", + "Camera Tourist", + "The Mustache Gauntlet", + "Rift Collapse: Deep Sea", + "Cruisin' for a Bruisin'", + "Seal the Deal", # Non-excluded if goal +] + +# includes the above as well +annoying_bonuses = [ + "So You're Back From Outer Space", + "Encore! Encore!", + "Snatcher's Hit List", + "Vault Codes in the Wind", + "10 Seconds until Self-Destruct", + "Killing Two Birds", + "Zero Jumps", + "Boss Rush", + "Bird Sanctuary", + "The Mustache Gauntlet", + "Wound-Up Windmill", + "Camera Tourist", + "Rift Collapse: Deep Sea", + "Cruisin' for a Bruisin'", + "Seal the Deal", +] + +dw_classes = { + "Beat the Heat": "Hat_SnatcherContract_DeathWish_HeatingUpHarder", + "So You're Back From Outer Space": "Hat_SnatcherContract_DeathWish_BackFromSpace", + "Snatcher's Hit List": "Hat_SnatcherContract_DeathWish_KillEverybody", + "Collect-a-thon": "Hat_SnatcherContract_DeathWish_PonFrenzy", + "Rift Collapse: Mafia of Cooks": "Hat_SnatcherContract_DeathWish_RiftCollapse_MafiaTown", + "Encore! Encore!": "Hat_SnatcherContract_DeathWish_MafiaBossEX", + "She Speedran from Outer Space": "Hat_SnatcherContract_DeathWish_Speedrun_MafiaAlien", + "Mafia's Jumps": "Hat_SnatcherContract_DeathWish_NoAPresses_MafiaAlien", + "Vault Codes in the Wind": "Hat_SnatcherContract_DeathWish_MovingVault", + "Snatcher Coins in Mafia Town": "Hat_SnatcherContract_DeathWish_Tokens_MafiaTown", + + "Security Breach": "Hat_SnatcherContract_DeathWish_DeadBirdStudioMoreGuards", + "The Great Big Hootenanny": "Hat_SnatcherContract_DeathWish_DifficultParade", + "Rift Collapse: Dead Bird Studio": "Hat_SnatcherContract_DeathWish_RiftCollapse_Birds", + "10 Seconds until Self-Destruct": "Hat_SnatcherContract_DeathWish_TrainRushShortTime", + "Killing Two Birds": "Hat_SnatcherContract_DeathWish_BirdBossEX", + "Snatcher Coins in Battle of the Birds": "Hat_SnatcherContract_DeathWish_Tokens_Birds", + "Zero Jumps": "Hat_SnatcherContract_DeathWish_NoAPresses", + + "Speedrun Well": "Hat_SnatcherContract_DeathWish_Speedrun_SubWell", + "Rift Collapse: Sleepy Subcon": "Hat_SnatcherContract_DeathWish_RiftCollapse_Subcon", + "Boss Rush": "Hat_SnatcherContract_DeathWish_BossRush", + "Quality Time with Snatcher": "Hat_SnatcherContract_DeathWish_SurvivalOfTheFittest", + "Breaching the Contract": "Hat_SnatcherContract_DeathWish_SnatcherEX", + "Snatcher Coins in Subcon Forest": "Hat_SnatcherContract_DeathWish_Tokens_Subcon", + + "Bird Sanctuary": "Hat_SnatcherContract_DeathWish_NiceBirdhouse", + "Rift Collapse: Alpine Skyline": "Hat_SnatcherContract_DeathWish_RiftCollapse_Alps", + "Wound-Up Windmill": "Hat_SnatcherContract_DeathWish_FastWindmill", + "The Illness has Speedrun": "Hat_SnatcherContract_DeathWish_Speedrun_Illness", + "Snatcher Coins in Alpine Skyline": "Hat_SnatcherContract_DeathWish_Tokens_Alps", + "Camera Tourist": "Hat_SnatcherContract_DeathWish_CameraTourist_1", + + "The Mustache Gauntlet": "Hat_SnatcherContract_DeathWish_HardCastle", + "No More Bad Guys": "Hat_SnatcherContract_DeathWish_MuGirlEX", + + "Seal the Deal": "Hat_SnatcherContract_DeathWish_BossRushEX", + "Rift Collapse: Deep Sea": "Hat_SnatcherContract_DeathWish_RiftCollapse_Cruise", + "Cruisin' for a Bruisin'": "Hat_SnatcherContract_DeathWish_EndlessTasks", + + "Community Rift: Rhythm Jump Studio": "Hat_SnatcherContract_DeathWish_CommunityRift_RhythmJump", + "Community Rift: Twilight Travels": "Hat_SnatcherContract_DeathWish_CommunityRift_TwilightTravels", + "Community Rift: The Mountain Rift": "Hat_SnatcherContract_DeathWish_CommunityRift_MountainRift", + + "Snatcher Coins in Nyakuza Metro": "Hat_SnatcherContract_DeathWish_Tokens_Metro", +} + + +def create_dw_regions(world: "HatInTimeWorld"): + if world.options.DWExcludeAnnoyingContracts: + for name in annoying_dws: + world.excluded_dws.append(name) + + if not world.options.DWEnableBonus or world.options.DWAutoCompleteBonuses: + for name in death_wishes: + world.excluded_bonuses.append(name) + elif world.options.DWExcludeAnnoyingBonuses: + for name in annoying_bonuses: + world.excluded_bonuses.append(name) + + if world.options.DWExcludeCandles: + for name in dw_candles: + if name not in world.excluded_dws: + world.excluded_dws.append(name) + + spaceship = world.multiworld.get_region("Spaceship", world.player) + dw_map: Region = create_region(world, "Death Wish Map") + entrance = spaceship.connect(dw_map, "-> Death Wish Map") + add_rule(entrance, lambda state: state.has("Time Piece", world.player, world.options.DWTimePieceRequirement)) + + if world.options.DWShuffle: + # Connect Death Wishes randomly to one another in a linear sequence + dw_list: List[str] = [] + for name in death_wishes.keys(): + # Don't shuffle excluded or invalid Death Wishes + if not world.is_dlc2() and name == "Snatcher Coins in Nyakuza Metro" or world.is_dw_excluded(name): + continue + + dw_list.append(name) + + world.random.shuffle(dw_list) + count = world.random.randint(world.options.DWShuffleCountMin.value, world.options.DWShuffleCountMax.value) + dw_shuffle: List[str] = [] + total = min(len(dw_list), count) + for i in range(total): + dw_shuffle.append(dw_list[i]) + + # Seal the Deal is always last if it's the goal + if world.options.EndGoal == EndGoal.option_seal_the_deal: + if "Seal the Deal" in dw_shuffle: + dw_shuffle.remove("Seal the Deal") + + dw_shuffle.append("Seal the Deal") + + world.dw_shuffle = dw_shuffle + prev_dw = dw_map + for death_wish_name in dw_shuffle: + dw = create_region(world, death_wish_name) + prev_dw.connect(dw) + create_dw_locations(world, dw) + prev_dw = dw + else: + # DWShuffle is disabled, use vanilla connections + for key in death_wishes.keys(): + if key == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2(): + world.excluded_dws.append(key) + continue + + dw = create_region(world, key) + if key == "Beat the Heat": + dw_map.connect(dw, f"{dw_map.name} -> Beat the Heat") + elif key in dw_prereqs.keys(): + for name in dw_prereqs[key]: + parent = world.multiworld.get_region(name, world.player) + parent.connect(dw, f"{parent.name} -> {key}") + + create_dw_locations(world, dw) + + +def create_dw_locations(world: "HatInTimeWorld", dw: Region): + loc_id = death_wishes[dw.name] + main_objective = HatInTimeLocation(world.player, f"{dw.name} - Main Objective", loc_id, dw) + full_clear = HatInTimeLocation(world.player, f"{dw.name} - All Clear", loc_id + 1, dw) + main_stamp = HatInTimeLocation(world.player, f"Main Stamp - {dw.name}", None, dw) + bonus_stamps = HatInTimeLocation(world.player, f"Bonus Stamps - {dw.name}", None, dw) + main_stamp.show_in_spoiler = False + bonus_stamps.show_in_spoiler = False + dw.locations.append(main_stamp) + dw.locations.append(bonus_stamps) + main_stamp.place_locked_item(HatInTimeItem(f"1 Stamp - {dw.name}", + ItemClassification.progression, None, world.player)) + bonus_stamps.place_locked_item(HatInTimeItem(f"2 Stamp - {dw.name}", + ItemClassification.progression, None, world.player)) + + if dw.name in world.excluded_dws: + main_objective.progress_type = LocationProgressType.EXCLUDED + full_clear.progress_type = LocationProgressType.EXCLUDED + elif world.is_bonus_excluded(dw.name): + full_clear.progress_type = LocationProgressType.EXCLUDED + + dw.locations.append(main_objective) + dw.locations.append(full_clear) diff --git a/worlds/ahit/DeathWishRules.py b/worlds/ahit/DeathWishRules.py new file mode 100644 index 000000000000..1432ef5c0d75 --- /dev/null +++ b/worlds/ahit/DeathWishRules.py @@ -0,0 +1,462 @@ +from worlds.AutoWorld import CollectionState +from .Rules import can_use_hat, can_use_hookshot, can_hit, zipline_logic, get_difficulty, has_paintings +from .Types import HatType, Difficulty, HatInTimeLocation, HatInTimeItem, LocData, HitType +from .DeathWishLocations import dw_prereqs, dw_candles +from BaseClasses import Entrance, Location, ItemClassification +from worlds.generic.Rules import add_rule, set_rule +from typing import List, Callable, TYPE_CHECKING +from .Locations import death_wishes +from .Options import EndGoal + +if TYPE_CHECKING: + from . import HatInTimeWorld + + +# Any speedruns expect the player to have Sprint Hat +dw_requirements = { + "Beat the Heat": LocData(hit_type=HitType.umbrella), + "So You're Back From Outer Space": LocData(hookshot=True), + "Mafia's Jumps": LocData(required_hats=[HatType.ICE]), + "Vault Codes in the Wind": LocData(required_hats=[HatType.SPRINT]), + + "Security Breach": LocData(hit_type=HitType.umbrella_or_brewing), + "10 Seconds until Self-Destruct": LocData(hookshot=True), + "Community Rift: Rhythm Jump Studio": LocData(required_hats=[HatType.ICE]), + + "Speedrun Well": LocData(hookshot=True, hit_type=HitType.umbrella_or_brewing), + "Boss Rush": LocData(hit_type=HitType.umbrella, hookshot=True), + "Community Rift: Twilight Travels": LocData(hookshot=True, required_hats=[HatType.DWELLER]), + + "Bird Sanctuary": LocData(hookshot=True), + "Wound-Up Windmill": LocData(hookshot=True), + "The Illness has Speedrun": LocData(hookshot=True), + "Community Rift: The Mountain Rift": LocData(hookshot=True, required_hats=[HatType.DWELLER]), + "Camera Tourist": LocData(misc_required=["Camera Badge"]), + + "The Mustache Gauntlet": LocData(hookshot=True, required_hats=[HatType.DWELLER]), + + "Rift Collapse: Deep Sea": LocData(hookshot=True), +} + +# Includes main objective requirements +dw_bonus_requirements = { + # Some One-Hit Hero requirements need badge pins as well because of Hookshot + "So You're Back From Outer Space": LocData(required_hats=[HatType.SPRINT]), + "Encore! Encore!": LocData(misc_required=["One-Hit Hero Badge"]), + + "10 Seconds until Self-Destruct": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]), + + "Boss Rush": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]), + "Community Rift: Twilight Travels": LocData(required_hats=[HatType.BREWING]), + + "Bird Sanctuary": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"], required_hats=[HatType.DWELLER]), + "Wound-Up Windmill": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]), + "The Illness has Speedrun": LocData(required_hats=[HatType.SPRINT]), + + "The Mustache Gauntlet": LocData(required_hats=[HatType.ICE]), + + "Rift Collapse: Deep Sea": LocData(required_hats=[HatType.DWELLER]), +} + +dw_stamp_costs = { + "So You're Back From Outer Space": 2, + "Collect-a-thon": 5, + "She Speedran from Outer Space": 8, + "Encore! Encore!": 10, + + "Security Breach": 4, + "The Great Big Hootenanny": 7, + "10 Seconds until Self-Destruct": 15, + "Killing Two Birds": 25, + "Snatcher Coins in Nyakuza Metro": 30, + + "Speedrun Well": 10, + "Boss Rush": 15, + "Quality Time with Snatcher": 20, + "Breaching the Contract": 40, + + "Bird Sanctuary": 15, + "Wound-Up Windmill": 30, + "The Illness has Speedrun": 35, + + "The Mustache Gauntlet": 35, + "No More Bad Guys": 50, + "Seal the Deal": 70, +} + +required_snatcher_coins = { + "Snatcher Coins in Mafia Town": ["Snatcher Coin - Top of HQ", "Snatcher Coin - Top of Tower", + "Snatcher Coin - Under Ruined Tower"], + + "Snatcher Coins in Battle of the Birds": ["Snatcher Coin - Top of Red House", "Snatcher Coin - Train Rush", + "Snatcher Coin - Picture Perfect"], + + "Snatcher Coins in Subcon Forest": ["Snatcher Coin - Swamp Tree", "Snatcher Coin - Manor Roof", + "Snatcher Coin - Giant Time Piece"], + + "Snatcher Coins in Alpine Skyline": ["Snatcher Coin - Goat Village Top", "Snatcher Coin - Lava Cake", + "Snatcher Coin - Windmill"], + + "Snatcher Coins in Nyakuza Metro": ["Snatcher Coin - Green Clean Tower", "Snatcher Coin - Bluefin Cat Train", + "Snatcher Coin - Pink Paw Fence"], +} + + +def set_dw_rules(world: "HatInTimeWorld"): + if "Snatcher's Hit List" not in world.excluded_dws or "Camera Tourist" not in world.excluded_dws: + set_enemy_rules(world) + + dw_list: List[str] = [] + if world.options.DWShuffle: + dw_list = world.dw_shuffle + else: + for name in death_wishes.keys(): + dw_list.append(name) + + for name in dw_list: + if name == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2(): + continue + + dw = world.multiworld.get_region(name, world.player) + if not world.options.DWShuffle and name in dw_stamp_costs.keys(): + for entrance in dw.entrances: + add_rule(entrance, lambda state, n=name: state.has("Stamps", world.player, dw_stamp_costs[n])) + + main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player) + all_clear = world.multiworld.get_location(f"{name} - All Clear", world.player) + main_stamp = world.multiworld.get_location(f"Main Stamp - {name}", world.player) + bonus_stamps = world.multiworld.get_location(f"Bonus Stamps - {name}", world.player) + if not world.options.DWEnableBonus: + # place nothing, but let the locations exist still, so we can use them for bonus stamp rules + all_clear.address = None + all_clear.place_locked_item(HatInTimeItem("Nothing", ItemClassification.filler, None, world.player)) + all_clear.show_in_spoiler = False + + # No need for rules if excluded - stamps will be auto-granted + if world.is_dw_excluded(name): + continue + + modify_dw_rules(world, name) + add_dw_rules(world, main_objective) + add_dw_rules(world, all_clear) + add_rule(main_stamp, main_objective.access_rule) + add_rule(all_clear, main_objective.access_rule) + # Only set bonus stamp rules if we don't auto complete bonuses + if not world.options.DWAutoCompleteBonuses and not world.is_bonus_excluded(all_clear.name): + add_rule(bonus_stamps, all_clear.access_rule) + + if world.options.DWShuffle: + for i in range(len(world.dw_shuffle)-1): + name = world.dw_shuffle[i+1] + prev_dw = world.multiworld.get_region(world.dw_shuffle[i], world.player) + entrance = world.multiworld.get_entrance(f"{prev_dw.name} -> {name}", world.player) + add_rule(entrance, lambda state, n=prev_dw.name: state.has(f"1 Stamp - {n}", world.player)) + else: + for key, reqs in dw_prereqs.items(): + if key == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2(): + continue + + access_rules: List[Callable[[CollectionState], bool]] = [] + entrances: List[Entrance] = [] + + for parent in reqs: + entrance = world.multiworld.get_entrance(f"{parent} -> {key}", world.player) + entrances.append(entrance) + + if not world.is_dw_excluded(parent): + access_rules.append(lambda state, n=parent: state.has(f"1 Stamp - {n}", world.player)) + + for entrance in entrances: + for rule in access_rules: + add_rule(entrance, rule) + + if world.options.EndGoal == EndGoal.option_seal_the_deal: + world.multiworld.completion_condition[world.player] = lambda state: \ + state.has("1 Stamp - Seal the Deal", world.player) + + +def add_dw_rules(world: "HatInTimeWorld", loc: Location): + bonus: bool = "All Clear" in loc.name + if not bonus: + data = dw_requirements.get(loc.parent_region.name) + else: + data = dw_bonus_requirements.get(loc.parent_region.name) + + if data is None: + return + + if data.hookshot: + add_rule(loc, lambda state: can_use_hookshot(state, world)) + + for hat in data.required_hats: + add_rule(loc, lambda state, h=hat: can_use_hat(state, world, h)) + + for misc in data.misc_required: + add_rule(loc, lambda state, item=misc: state.has(item, world.player)) + + if data.paintings > 0 and world.options.ShuffleSubconPaintings: + add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings)) + + if data.hit_type is not HitType.none and world.options.UmbrellaLogic: + if data.hit_type == HitType.umbrella: + add_rule(loc, lambda state: state.has("Umbrella", world.player)) + + elif data.hit_type == HitType.umbrella_or_brewing: + add_rule(loc, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.BREWING)) + + elif data.hit_type == HitType.dweller_bell: + add_rule(loc, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.BREWING) + or can_use_hat(state, world, HatType.DWELLER)) + + +def modify_dw_rules(world: "HatInTimeWorld", name: str): + difficulty: Difficulty = get_difficulty(world) + main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player) + full_clear = world.multiworld.get_location(f"{name} - All Clear", world.player) + + if name == "The Illness has Speedrun": + # All stamps with hookshot only in Expert + if difficulty >= Difficulty.EXPERT: + set_rule(full_clear, lambda state: True) + else: + add_rule(main_objective, lambda state: state.has("Umbrella", world.player)) + + elif name == "The Mustache Gauntlet": + add_rule(main_objective, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.ICE) or can_use_hat(state, world, HatType.BREWING)) + + elif name == "Vault Codes in the Wind": + # Sprint is normally expected here + if difficulty >= Difficulty.HARD: + set_rule(main_objective, lambda state: True) + + elif name == "Speedrun Well": + # All stamps with nothing :) + if difficulty >= Difficulty.EXPERT: + set_rule(main_objective, lambda state: True) + + elif name == "Mafia's Jumps": + if difficulty >= Difficulty.HARD: + set_rule(main_objective, lambda state: True) + set_rule(full_clear, lambda state: True) + + elif name == "So You're Back from Outer Space": + # Without Hookshot + if difficulty >= Difficulty.HARD: + set_rule(main_objective, lambda state: True) + + elif name == "Wound-Up Windmill": + # No badge pin required. Player can switch to One Hit Hero after the checkpoint and do level without it. + if difficulty >= Difficulty.MODERATE: + set_rule(full_clear, lambda state: can_use_hookshot(state, world) + and state.has("One-Hit Hero Badge", world.player)) + + if name in dw_candles: + set_candle_dw_rules(name, world) + + +def set_candle_dw_rules(name: str, world: "HatInTimeWorld"): + main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player) + full_clear = world.multiworld.get_location(f"{name} - All Clear", world.player) + + if name == "Zero Jumps": + add_rule(main_objective, lambda state: state.has("Zero Jumps", world.player)) + add_rule(full_clear, lambda state: state.has("Zero Jumps", world.player, 4) + and state.has("Train Rush (Zero Jumps)", world.player) and can_use_hat(state, world, HatType.ICE)) + + # No Ice Hat/painting required in Expert for Toilet Zero Jumps + # This painting wall can only be skipped via cherry hover. + if get_difficulty(world) < Difficulty.EXPERT or world.options.NoPaintingSkips: + set_rule(world.multiworld.get_location("Toilet of Doom (Zero Jumps)", world.player), + lambda state: can_use_hookshot(state, world) and can_hit(state, world) + and has_paintings(state, world, 1, False)) + else: + set_rule(world.multiworld.get_location("Toilet of Doom (Zero Jumps)", world.player), + lambda state: can_use_hookshot(state, world) and can_hit(state, world)) + + set_rule(world.multiworld.get_location("Contractual Obligations (Zero Jumps)", world.player), + lambda state: has_paintings(state, world, 1, False)) + + elif name == "Snatcher's Hit List": + add_rule(main_objective, lambda state: state.has("Mafia Goon", world.player)) + add_rule(full_clear, lambda state: state.has("Enemy", world.player, 12)) + + elif name == "Camera Tourist": + add_rule(main_objective, lambda state: state.has("Enemy", world.player, 8)) + add_rule(full_clear, lambda state: state.has("Boss", world.player, 6) + and state.has("Triple Enemy Photo", world.player)) + + elif "Snatcher Coins" in name: + coins: List[str] = [] + for coin in required_snatcher_coins[name]: + coins.append(coin) + add_rule(full_clear, lambda state, c=coin: state.has(c, world.player)) + + # any coin works for the main objective + add_rule(main_objective, lambda state: state.has(coins[0], world.player) + or state.has(coins[1], world.player) + or state.has(coins[2], world.player)) + + +def create_enemy_events(world: "HatInTimeWorld"): + no_tourist = "Camera Tourist" in world.excluded_dws + for enemy, regions in hit_list.items(): + if no_tourist and enemy in bosses: + continue + + for area in regions: + if (area == "Bon Voyage!" or area == "Time Rift - Deep Sea") and not world.is_dlc1(): + continue + + if area == "Time Rift - Tour" and (not world.is_dlc1() or world.options.ExcludeTour): + continue + + if area == "Bluefin Tunnel" and not world.is_dlc2(): + continue + + if world.options.DWShuffle and area in death_wishes.keys() and area not in world.dw_shuffle: + continue + + region = world.multiworld.get_region(area, world.player) + event = HatInTimeLocation(world.player, f"{enemy} - {area}", None, region) + event.place_locked_item(HatInTimeItem(enemy, ItemClassification.progression, None, world.player)) + region.locations.append(event) + event.show_in_spoiler = False + + for name in triple_enemy_locations: + if name == "Time Rift - Tour" and (not world.is_dlc1() or world.options.ExcludeTour): + continue + + if world.options.DWShuffle and name in death_wishes.keys() and name not in world.dw_shuffle: + continue + + region = world.multiworld.get_region(name, world.player) + event = HatInTimeLocation(world.player, f"Triple Enemy Photo - {name}", None, region) + event.place_locked_item(HatInTimeItem("Triple Enemy Photo", ItemClassification.progression, None, world.player)) + region.locations.append(event) + event.show_in_spoiler = False + if name == "The Mustache Gauntlet": + add_rule(event, lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER)) + + +def set_enemy_rules(world: "HatInTimeWorld"): + no_tourist = "Camera Tourist" in world.excluded_dws or "Camera Tourist" in world.excluded_bonuses + + for enemy, regions in hit_list.items(): + if no_tourist and enemy in bosses: + continue + + for area in regions: + if (area == "Bon Voyage!" or area == "Time Rift - Deep Sea") and not world.is_dlc1(): + continue + + if area == "Time Rift - Tour" and (not world.is_dlc1() or world.options.ExcludeTour): + continue + + if area == "Bluefin Tunnel" and not world.is_dlc2(): + continue + + if world.options.DWShuffle and area in death_wishes and area not in world.dw_shuffle: + continue + + event = world.multiworld.get_location(f"{enemy} - {area}", world.player) + + if enemy == "Toxic Flower": + add_rule(event, lambda state: can_use_hookshot(state, world)) + + if area == "The Illness has Spread": + add_rule(event, lambda state: not zipline_logic(world) or + state.has("Zipline Unlock - The Birdhouse Path", world.player) + or state.has("Zipline Unlock - The Lava Cake Path", world.player) + or state.has("Zipline Unlock - The Windmill Path", world.player)) + + elif enemy == "Director": + if area == "Dead Bird Studio Basement": + add_rule(event, lambda state: can_use_hookshot(state, world)) + + elif enemy == "Snatcher" or enemy == "Mustache Girl": + if area == "Boss Rush": + # need to be able to kill toilet and snatcher + add_rule(event, lambda state: can_hit(state, world) and can_use_hookshot(state, world)) + if enemy == "Mustache Girl": + add_rule(event, lambda state: can_hit(state, world, True) and can_use_hookshot(state, world)) + + elif area == "The Finale" and enemy == "Mustache Girl": + add_rule(event, lambda state: can_use_hookshot(state, world) + and can_use_hat(state, world, HatType.DWELLER)) + + elif enemy == "Shock Squid" or enemy == "Ninja Cat": + if area == "Time Rift - Deep Sea": + add_rule(event, lambda state: can_use_hookshot(state, world)) + + +# Enemies for Snatcher's Hit List/Camera Tourist, and where to find them +hit_list = { + "Mafia Goon": ["Mafia Town Area", "Time Rift - Mafia of Cooks", "Time Rift - Tour", + "Bon Voyage!", "The Mustache Gauntlet", "Rift Collapse: Mafia of Cooks", + "So You're Back From Outer Space"], + + "Sleepy Raccoon": ["She Came from Outer Space", "Down with the Mafia!", "The Twilight Bell", + "She Speedran from Outer Space", "Mafia's Jumps", "The Mustache Gauntlet", + "Time Rift - Sleepy Subcon", "Rift Collapse: Sleepy Subcon"], + + "UFO": ["Picture Perfect", "So You're Back From Outer Space", "Community Rift: Rhythm Jump Studio"], + + "Rat": ["Down with the Mafia!", "Bluefin Tunnel"], + + "Shock Squid": ["Bon Voyage!", "Time Rift - Sleepy Subcon", "Time Rift - Deep Sea", + "Rift Collapse: Sleepy Subcon"], + + "Shromb Egg": ["The Birdhouse", "Bird Sanctuary"], + + "Spider": ["Subcon Forest Area", "The Mustache Gauntlet", "Speedrun Well", + "The Lava Cake", "The Windmill"], + + "Crow": ["Mafia Town Area", "The Birdhouse", "Time Rift - Tour", "Bird Sanctuary", + "Time Rift - Alpine Skyline", "Rift Collapse: Alpine Skyline"], + + "Pompous Crow": ["The Birdhouse", "Time Rift - The Lab", "Bird Sanctuary", "The Mustache Gauntlet"], + + "Fiery Crow": ["The Finale", "The Lava Cake", "The Mustache Gauntlet"], + + "Express Owl": ["The Finale", "Time Rift - The Owl Express", "Time Rift - Deep Sea"], + + "Ninja Cat": ["The Birdhouse", "The Windmill", "Bluefin Tunnel", "The Mustache Gauntlet", + "Time Rift - Curly Tail Trail", "Time Rift - Alpine Skyline", "Time Rift - Deep Sea", + "Rift Collapse: Alpine Skyline"], + + # Bosses + "Mafia Boss": ["Down with the Mafia!", "Encore! Encore!", "Boss Rush"], + + "Conductor": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"], + "Toilet": ["Toilet of Doom", "Boss Rush"], + + "Snatcher": ["Your Contract has Expired", "Breaching the Contract", "Boss Rush", + "Quality Time with Snatcher"], + + "Toxic Flower": ["The Illness has Spread", "The Illness has Speedrun"], + + "Mustache Girl": ["The Finale", "Boss Rush", "No More Bad Guys"], +} + +# Camera Tourist has a bonus that requires getting three different types of enemies in one photo. +triple_enemy_locations = [ + "She Came from Outer Space", + "She Speedran from Outer Space", + "Mafia's Jumps", + "The Mustache Gauntlet", + "The Birdhouse", + "Bird Sanctuary", + "Time Rift - Tour", +] + +bosses = [ + "Mafia Boss", + "Conductor", + "Toilet", + "Snatcher", + "Toxic Flower", + "Mustache Girl", +] diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py new file mode 100644 index 000000000000..54c6e6b5d392 --- /dev/null +++ b/worlds/ahit/Items.py @@ -0,0 +1,302 @@ +from BaseClasses import Item, ItemClassification +from .Types import HatDLC, HatType, hat_type_to_item, Difficulty, ItemData, HatInTimeItem +from .Locations import get_total_locations +from .Rules import get_difficulty +from .Options import get_total_time_pieces, CTRLogic +from typing import List, Dict, TYPE_CHECKING + +if TYPE_CHECKING: + from . import HatInTimeWorld + + +def create_itempool(world: "HatInTimeWorld") -> List[Item]: + itempool: List[Item] = [] + if world.has_yarn(): + yarn_pool: List[Item] = create_multiple_items(world, "Yarn", + world.options.YarnAvailable.value, + ItemClassification.progression_skip_balancing) + + for i in range(int(len(yarn_pool) * (0.01 * world.options.YarnBalancePercent))): + yarn_pool[i].classification = ItemClassification.progression + + itempool += yarn_pool + + for name in item_table.keys(): + if name == "Yarn": + continue + + if not item_dlc_enabled(world, name): + continue + + if not world.options.HatItems and name in hat_type_to_item.values(): + continue + + item_type: ItemClassification = item_table.get(name).classification + + if world.is_dw_only(): + if item_type is ItemClassification.progression \ + or item_type is ItemClassification.progression_skip_balancing: + continue + else: + if name == "Scooter Badge": + if world.options.CTRLogic == CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE: + item_type = ItemClassification.progression + elif name == "No Bonk Badge" and world.is_dw(): + item_type = ItemClassification.progression + + # some death wish bonuses require one hit hero + hookshot + if world.is_dw() and name == "Badge Pin" and not world.is_dw_only(): + item_type = ItemClassification.progression + + if item_type is ItemClassification.filler or item_type is ItemClassification.trap: + continue + + if name in act_contracts.keys() and not world.options.ShuffleActContracts: + continue + + if name in alps_hooks.keys() and not world.options.ShuffleAlpineZiplines: + continue + + if name == "Progressive Painting Unlock" and not world.options.ShuffleSubconPaintings: + continue + + if world.options.StartWithCompassBadge and name == "Compass Badge": + continue + + if name == "Time Piece": + tp_list: List[Item] = create_multiple_items(world, name, get_total_time_pieces(world), item_type) + for i in range(int(len(tp_list) * (0.01 * world.options.TimePieceBalancePercent))): + tp_list[i].classification = ItemClassification.progression + + itempool += tp_list + continue + + itempool += create_multiple_items(world, name, item_frequencies.get(name, 1), item_type) + + itempool += create_junk_items(world, get_total_locations(world) - len(itempool)) + return itempool + + +def calculate_yarn_costs(world: "HatInTimeWorld"): + min_yarn_cost = int(min(world.options.YarnCostMin.value, world.options.YarnCostMax.value)) + max_yarn_cost = int(max(world.options.YarnCostMin.value, world.options.YarnCostMax.value)) + + max_cost = 0 + for i in range(5): + hat: HatType = HatType(i) + if not world.is_hat_precollected(hat): + cost: int = world.random.randint(min_yarn_cost, max_yarn_cost) + world.hat_yarn_costs[hat] = cost + max_cost += cost + else: + world.hat_yarn_costs[hat] = 0 + + available_yarn: int = world.options.YarnAvailable.value + if max_cost > available_yarn: + world.options.YarnAvailable.value = max_cost + available_yarn = max_cost + + extra_yarn = max_cost + world.options.MinExtraYarn - available_yarn + if extra_yarn > 0: + world.options.YarnAvailable.value += extra_yarn + + +def item_dlc_enabled(world: "HatInTimeWorld", name: str) -> bool: + data = item_table[name] + + if data.dlc_flags == HatDLC.none: + return True + elif data.dlc_flags == HatDLC.dlc1 and world.is_dlc1(): + return True + elif data.dlc_flags == HatDLC.dlc2 and world.is_dlc2(): + return True + elif data.dlc_flags == HatDLC.death_wish and world.is_dw(): + return True + + return False + + +def create_item(world: "HatInTimeWorld", name: str) -> Item: + data = item_table[name] + return HatInTimeItem(name, data.classification, data.code, world.player) + + +def create_multiple_items(world: "HatInTimeWorld", name: str, count: int = 1, + item_type: ItemClassification = ItemClassification.progression) -> List[Item]: + + data = item_table[name] + itemlist: List[Item] = [] + + for i in range(count): + itemlist += [HatInTimeItem(name, item_type, data.code, world.player)] + + return itemlist + + +def create_junk_items(world: "HatInTimeWorld", count: int) -> List[Item]: + trap_chance = world.options.TrapChance.value + junk_pool: List[Item] = [] + junk_list: Dict[str, int] = {} + trap_list: Dict[str, int] = {} + ic: ItemClassification + + for name in item_table.keys(): + ic = item_table[name].classification + if ic == ItemClassification.filler: + if world.is_dw_only() and "Pons" in name: + continue + + junk_list[name] = junk_weights.get(name) + + elif trap_chance > 0 and ic == ItemClassification.trap: + if name == "Baby Trap": + trap_list[name] = world.options.BabyTrapWeight.value + elif name == "Laser Trap": + trap_list[name] = world.options.LaserTrapWeight.value + elif name == "Parade Trap": + trap_list[name] = world.options.ParadeTrapWeight.value + + for i in range(count): + if trap_chance > 0 and world.random.randint(1, 100) <= trap_chance: + junk_pool.append(world.create_item( + world.random.choices(list(trap_list.keys()), weights=list(trap_list.values()), k=1)[0])) + else: + junk_pool.append(world.create_item( + world.random.choices(list(junk_list.keys()), weights=list(junk_list.values()), k=1)[0])) + + return junk_pool + + +def get_shop_trap_name(world: "HatInTimeWorld") -> str: + rand = world.random.randint(1, 9) + name = "" + if rand == 1: + name = "Time Plece" + elif rand == 2: + name = "Time Piece (Trust me bro)" + elif rand == 3: + name = "TimePiece" + elif rand == 4: + name = "Time Piece?" + elif rand == 5: + name = "Time Pizza" + elif rand == 6: + name = "Time piece" + elif rand == 7: + name = "TIme Piece" + elif rand == 8: + name = "Time Piece (maybe)" + elif rand == 9: + name = "Time Piece ;)" + + return name + + +ahit_items = { + "Yarn": ItemData(2000300001, ItemClassification.progression_skip_balancing), + "Time Piece": ItemData(2000300002, ItemClassification.progression_skip_balancing), + + # for HatItems option + "Sprint Hat": ItemData(2000300049, ItemClassification.progression), + "Brewing Hat": ItemData(2000300050, ItemClassification.progression), + "Ice Hat": ItemData(2000300051, ItemClassification.progression), + "Dweller Mask": ItemData(2000300052, ItemClassification.progression), + "Time Stop Hat": ItemData(2000300053, ItemClassification.progression), + + # Badges + "Projectile Badge": ItemData(2000300024, ItemClassification.useful), + "Fast Hatter Badge": ItemData(2000300025, ItemClassification.useful), + "Hover Badge": ItemData(2000300026, ItemClassification.useful), + "Hookshot Badge": ItemData(2000300027, ItemClassification.progression), + "Item Magnet Badge": ItemData(2000300028, ItemClassification.useful), + "No Bonk Badge": ItemData(2000300029, ItemClassification.useful), + "Compass Badge": ItemData(2000300030, ItemClassification.useful), + "Scooter Badge": ItemData(2000300031, ItemClassification.useful), + "One-Hit Hero Badge": ItemData(2000300038, ItemClassification.progression, HatDLC.death_wish), + "Camera Badge": ItemData(2000300042, ItemClassification.progression, HatDLC.death_wish), + + # Relics + "Relic (Burger Patty)": ItemData(2000300006, ItemClassification.progression), + "Relic (Burger Cushion)": ItemData(2000300007, ItemClassification.progression), + "Relic (Mountain Set)": ItemData(2000300008, ItemClassification.progression), + "Relic (Train)": ItemData(2000300009, ItemClassification.progression), + "Relic (UFO)": ItemData(2000300010, ItemClassification.progression), + "Relic (Cow)": ItemData(2000300011, ItemClassification.progression), + "Relic (Cool Cow)": ItemData(2000300012, ItemClassification.progression), + "Relic (Tin-foil Hat Cow)": ItemData(2000300013, ItemClassification.progression), + "Relic (Crayon Box)": ItemData(2000300014, ItemClassification.progression), + "Relic (Red Crayon)": ItemData(2000300015, ItemClassification.progression), + "Relic (Blue Crayon)": ItemData(2000300016, ItemClassification.progression), + "Relic (Green Crayon)": ItemData(2000300017, ItemClassification.progression), + # DLC + "Relic (Cake Stand)": ItemData(2000300018, ItemClassification.progression, HatDLC.dlc1), + "Relic (Shortcake)": ItemData(2000300019, ItemClassification.progression, HatDLC.dlc1), + "Relic (Chocolate Cake Slice)": ItemData(2000300020, ItemClassification.progression, HatDLC.dlc1), + "Relic (Chocolate Cake)": ItemData(2000300021, ItemClassification.progression, HatDLC.dlc1), + "Relic (Necklace Bust)": ItemData(2000300022, ItemClassification.progression, HatDLC.dlc2), + "Relic (Necklace)": ItemData(2000300023, ItemClassification.progression, HatDLC.dlc2), + + # Garbage items + "25 Pons": ItemData(2000300034, ItemClassification.filler), + "50 Pons": ItemData(2000300035, ItemClassification.filler), + "100 Pons": ItemData(2000300036, ItemClassification.filler), + "Health Pon": ItemData(2000300037, ItemClassification.filler), + "Random Cosmetic": ItemData(2000300044, ItemClassification.filler), + + # Traps + "Baby Trap": ItemData(2000300039, ItemClassification.trap), + "Laser Trap": ItemData(2000300040, ItemClassification.trap), + "Parade Trap": ItemData(2000300041, ItemClassification.trap), + + # Other + "Badge Pin": ItemData(2000300043, ItemClassification.useful), + "Umbrella": ItemData(2000300033, ItemClassification.progression), + "Progressive Painting Unlock": ItemData(2000300003, ItemClassification.progression), + # DLC + "Metro Ticket - Yellow": ItemData(2000300045, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Green": ItemData(2000300046, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Blue": ItemData(2000300047, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Pink": ItemData(2000300048, ItemClassification.progression, HatDLC.dlc2), +} + +act_contracts = { + "Snatcher's Contract - The Subcon Well": ItemData(2000300200, ItemClassification.progression), + "Snatcher's Contract - Toilet of Doom": ItemData(2000300201, ItemClassification.progression), + "Snatcher's Contract - Queen Vanessa's Manor": ItemData(2000300202, ItemClassification.progression), + "Snatcher's Contract - Mail Delivery Service": ItemData(2000300203, ItemClassification.progression), +} + +alps_hooks = { + "Zipline Unlock - The Birdhouse Path": ItemData(2000300204, ItemClassification.progression), + "Zipline Unlock - The Lava Cake Path": ItemData(2000300205, ItemClassification.progression), + "Zipline Unlock - The Windmill Path": ItemData(2000300206, ItemClassification.progression), + "Zipline Unlock - The Twilight Bell Path": ItemData(2000300207, ItemClassification.progression), +} + +relic_groups = { + "Burger": {"Relic (Burger Patty)", "Relic (Burger Cushion)"}, + "Train": {"Relic (Mountain Set)", "Relic (Train)"}, + "UFO": {"Relic (UFO)", "Relic (Cow)", "Relic (Cool Cow)", "Relic (Tin-foil Hat Cow)"}, + "Crayon": {"Relic (Crayon Box)", "Relic (Red Crayon)", "Relic (Blue Crayon)", "Relic (Green Crayon)"}, + "Cake": {"Relic (Cake Stand)", "Relic (Chocolate Cake)", "Relic (Chocolate Cake Slice)", "Relic (Shortcake)"}, + "Necklace": {"Relic (Necklace Bust)", "Relic (Necklace)"}, +} + +item_frequencies = { + "Badge Pin": 2, + "Progressive Painting Unlock": 3, +} + +junk_weights = { + "25 Pons": 50, + "50 Pons": 25, + "100 Pons": 10, + "Health Pon": 35, + "Random Cosmetic": 35, +} + +item_table = { + **ahit_items, + **act_contracts, + **alps_hooks, +} diff --git a/worlds/ahit/Locations.py b/worlds/ahit/Locations.py new file mode 100644 index 000000000000..9954514e8f3b --- /dev/null +++ b/worlds/ahit/Locations.py @@ -0,0 +1,1057 @@ +from .Types import HatDLC, HatType, LocData, Difficulty, HitType +from typing import Dict, TYPE_CHECKING +from .Options import TasksanityCheckCount + +if TYPE_CHECKING: + from . import HatInTimeWorld + +TASKSANITY_START_ID = 2000300204 + + +def get_total_locations(world: "HatInTimeWorld") -> int: + total = 0 + + if not world.is_dw_only(): + for name in location_table.keys(): + if is_location_valid(world, name): + total += 1 + + if world.is_dlc1() and world.options.Tasksanity: + total += world.options.TasksanityCheckCount + + if world.is_dw(): + if world.options.DWShuffle: + total += len(world.dw_shuffle) + if world.options.DWEnableBonus: + total += len(world.dw_shuffle) + else: + total += 37 + if world.is_dlc2(): + total += 1 + + if world.options.DWEnableBonus: + total += 37 + if world.is_dlc2(): + total += 1 + + return total + + +def location_dlc_enabled(world: "HatInTimeWorld", location: str) -> bool: + data = location_table.get(location) or event_locs.get(location) + + if data.dlc_flags == HatDLC.none: + return True + elif data.dlc_flags == HatDLC.dlc1 and world.is_dlc1(): + return True + elif data.dlc_flags == HatDLC.dlc2 and world.is_dlc2(): + return True + elif data.dlc_flags == HatDLC.death_wish and world.is_dw(): + return True + elif data.dlc_flags == HatDLC.dlc1_dw and world.is_dlc1() and world.is_dw(): + return True + elif data.dlc_flags == HatDLC.dlc2_dw and world.is_dlc2() and world.is_dw(): + return True + + return False + + +def is_location_valid(world: "HatInTimeWorld", location: str) -> bool: + if not location_dlc_enabled(world, location): + return False + + if not world.options.ShuffleStorybookPages and location in storybook_pages.keys(): + return False + + if not world.options.ShuffleActContracts and location in contract_locations.keys(): + return False + + if location not in world.shop_locs and location in shop_locations: + return False + + data = location_table.get(location) or event_locs.get(location) + if world.options.ExcludeTour and data.region == "Time Rift - Tour": + return False + + # No need for all those event items if we're not doing candles + if data.dlc_flags & HatDLC.death_wish: + if world.options.DWExcludeCandles and location in event_locs.keys(): + return False + + if world.options.DWShuffle and data.region in death_wishes and data.region not in world.dw_shuffle: + return False + + if location in zero_jumps: + if world.options.DWShuffle and "Zero Jumps" not in world.dw_shuffle: + return False + + difficulty: Difficulty = Difficulty(world.options.LogicDifficulty) + if location in zero_jumps_hard and difficulty < Difficulty.HARD: + return False + + if location in zero_jumps_expert and difficulty < Difficulty.EXPERT: + return False + + return True + + +def get_location_names() -> Dict[str, int]: + names = {name: data.id for name, data in location_table.items()} + id_start: int = TASKSANITY_START_ID + for i in range(TasksanityCheckCount.range_end): + names.setdefault(f"Tasksanity Check {i+1}", id_start+i) + + for (key, loc_id) in death_wishes.items(): + names.setdefault(f"{key} - Main Objective", loc_id) + names.setdefault(f"{key} - All Clear", loc_id+1) + + return names + + +ahit_locations = { + "Spaceship - Rumbi Abuse": LocData(2000301000, "Spaceship", hit_type=HitType.umbrella_or_brewing), + + # 300000 range - Mafia Town/Battle of the Birds + "Welcome to Mafia Town - Umbrella": LocData(2000301002, "Welcome to Mafia Town"), + "Mafia Town - Old Man (Seaside Spaghetti)": LocData(2000303833, "Mafia Town Area"), + "Mafia Town - Old Man (Steel Beams)": LocData(2000303832, "Mafia Town Area"), + "Mafia Town - Blue Vault": LocData(2000302850, "Mafia Town Area"), + "Mafia Town - Green Vault": LocData(2000302851, "Mafia Town Area"), + "Mafia Town - Red Vault": LocData(2000302848, "Mafia Town Area"), + "Mafia Town - Blue Vault Brewing Crate": LocData(2000305572, "Mafia Town Area", required_hats=[HatType.BREWING]), + "Mafia Town - Plaza Under Boxes": LocData(2000304458, "Mafia Town Area"), + "Mafia Town - Small Boat": LocData(2000304460, "Mafia Town Area"), + "Mafia Town - Staircase Pon Cluster": LocData(2000304611, "Mafia Town Area"), + "Mafia Town - Palm Tree": LocData(2000304609, "Mafia Town Area"), + "Mafia Town - Port": LocData(2000305219, "Mafia Town Area"), + "Mafia Town - Docks Chest": LocData(2000303534, "Mafia Town Area"), + "Mafia Town - Ice Hat Cage": LocData(2000304831, "Mafia Town Area", required_hats=[HatType.ICE]), + "Mafia Town - Hidden Buttons Chest": LocData(2000303483, "Mafia Town Area"), + + # These can be accessed from HUMT, the above locations can't be + "Mafia Town - Dweller Boxes": LocData(2000304462, "Mafia Town Area (HUMT)"), + "Mafia Town - Ledge Chest": LocData(2000303530, "Mafia Town Area (HUMT)"), + "Mafia Town - Yellow Sphere Building Chest": LocData(2000303535, "Mafia Town Area (HUMT)"), + "Mafia Town - Beneath Scaffolding": LocData(2000304456, "Mafia Town Area (HUMT)"), + "Mafia Town - On Scaffolding": LocData(2000304457, "Mafia Town Area (HUMT)"), + "Mafia Town - Cargo Ship": LocData(2000304459, "Mafia Town Area (HUMT)"), + "Mafia Town - Beach Alcove": LocData(2000304463, "Mafia Town Area (HUMT)"), + "Mafia Town - Wood Cage": LocData(2000304606, "Mafia Town Area (HUMT)"), + "Mafia Town - Beach Patio": LocData(2000304610, "Mafia Town Area (HUMT)"), + "Mafia Town - Steel Beam Nest": LocData(2000304608, "Mafia Town Area (HUMT)"), + "Mafia Town - Top of Ruined Tower": LocData(2000304607, "Mafia Town Area (HUMT)", required_hats=[HatType.ICE]), + "Mafia Town - Hot Air Balloon": LocData(2000304829, "Mafia Town Area (HUMT)", required_hats=[HatType.ICE]), + "Mafia Town - Camera Badge 1": LocData(2000302003, "Mafia Town Area (HUMT)"), + "Mafia Town - Camera Badge 2": LocData(2000302004, "Mafia Town Area (HUMT)"), + "Mafia Town - Chest Beneath Aqueduct": LocData(2000303489, "Mafia Town Area (HUMT)"), + "Mafia Town - Secret Cave": LocData(2000305220, "Mafia Town Area (HUMT)", required_hats=[HatType.BREWING]), + "Mafia Town - Crow Chest": LocData(2000303532, "Mafia Town Area (HUMT)"), + "Mafia Town - Above Boats": LocData(2000305218, "Mafia Town Area (HUMT)", hookshot=True), + "Mafia Town - Slip Slide Chest": LocData(2000303529, "Mafia Town Area (HUMT)"), + "Mafia Town - Behind Faucet": LocData(2000304214, "Mafia Town Area (HUMT)"), + "Mafia Town - Clock Tower Chest": LocData(2000303481, "Mafia Town Area (HUMT)", hookshot=True), + "Mafia Town - Top of Lighthouse": LocData(2000304213, "Mafia Town Area (HUMT)", hookshot=True), + "Mafia Town - Mafia Geek Platform": LocData(2000304212, "Mafia Town Area (HUMT)"), + "Mafia Town - Behind HQ Chest": LocData(2000303486, "Mafia Town Area (HUMT)"), + + "Mafia HQ - Hallway Brewing Crate": LocData(2000305387, "Down with the Mafia!", required_hats=[HatType.BREWING]), + "Mafia HQ - Freezer Chest": LocData(2000303241, "Down with the Mafia!"), + "Mafia HQ - Secret Room": LocData(2000304979, "Down with the Mafia!", required_hats=[HatType.ICE]), + "Mafia HQ - Bathroom Stall Chest": LocData(2000303243, "Down with the Mafia!"), + + "Dead Bird Studio - Up the Ladder": LocData(2000304874, "Dead Bird Studio - Elevator Area"), + "Dead Bird Studio - Red Building Top": LocData(2000305024, "Dead Bird Studio - Elevator Area"), + "Dead Bird Studio - Behind Water Tower": LocData(2000305248, "Dead Bird Studio - Elevator Area"), + "Dead Bird Studio - Side of House": LocData(2000305247, "Dead Bird Studio - Elevator Area"), + + "Dead Bird Studio - DJ Grooves Sign Chest": LocData(2000303901, "Dead Bird Studio - Post Elevator Area", + hit_type=HitType.umbrella_or_brewing), + + "Dead Bird Studio - Tightrope Chest": LocData(2000303898, "Dead Bird Studio - Post Elevator Area", + hit_type=HitType.umbrella_or_brewing), + + "Dead Bird Studio - Tepee Chest": LocData(2000303899, "Dead Bird Studio - Post Elevator Area", + hit_type=HitType.umbrella_or_brewing), + + "Dead Bird Studio - Conductor Chest": LocData(2000303900, "Dead Bird Studio - Post Elevator Area", + hit_type=HitType.umbrella_or_brewing), + + "Murder on the Owl Express - Cafeteria": LocData(2000305313, "Murder on the Owl Express"), + "Murder on the Owl Express - Luggage Room Top": LocData(2000305090, "Murder on the Owl Express"), + "Murder on the Owl Express - Luggage Room Bottom": LocData(2000305091, "Murder on the Owl Express"), + + "Murder on the Owl Express - Raven Suite Room": LocData(2000305701, "Murder on the Owl Express", + required_hats=[HatType.BREWING]), + + "Murder on the Owl Express - Raven Suite Top": LocData(2000305312, "Murder on the Owl Express"), + "Murder on the Owl Express - Lounge Chest": LocData(2000303963, "Murder on the Owl Express"), + + "Picture Perfect - Behind Badge Seller": LocData(2000304307, "Picture Perfect"), + "Picture Perfect - Hats Buy Building": LocData(2000304530, "Picture Perfect"), + + "Dead Bird Studio Basement - Window Platform": LocData(2000305432, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Cardboard Conductor": LocData(2000305059, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Above Conductor Sign": LocData(2000305057, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Logo Wall": LocData(2000305207, "Dead Bird Studio Basement"), + "Dead Bird Studio Basement - Disco Room": LocData(2000305061, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Small Room": LocData(2000304813, "Dead Bird Studio Basement"), + "Dead Bird Studio Basement - Vent Pipe": LocData(2000305430, "Dead Bird Studio Basement"), + "Dead Bird Studio Basement - Tightrope": LocData(2000305058, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Cameras": LocData(2000305431, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Locked Room": LocData(2000305819, "Dead Bird Studio Basement", hookshot=True), + + # Subcon Forest + "Contractual Obligations - Cherry Bomb Bone Cage": LocData(2000324761, "Contractual Obligations"), + "Subcon Village - Tree Top Ice Cube": LocData(2000325078, "Subcon Forest Area"), + "Subcon Village - Graveyard Ice Cube": LocData(2000325077, "Subcon Forest Area"), + "Subcon Village - House Top": LocData(2000325471, "Subcon Forest Area"), + "Subcon Village - Ice Cube House": LocData(2000325469, "Subcon Forest Area"), + "Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Area", paintings=1), + "Subcon Village - Stump Platform Chest": LocData(2000323729, "Subcon Forest Area"), + "Subcon Forest - Giant Tree Climb": LocData(2000325470, "Subcon Forest Area"), + + "Subcon Forest - Ice Cube Shack": LocData(2000324465, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Gravestone": LocData(2000326296, "Subcon Forest Area", + required_hats=[HatType.BREWING], paintings=1), + + "Subcon Forest - Swamp Near Well": LocData(2000324762, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Tree A": LocData(2000324763, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Tree B": LocData(2000324764, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Ice Wall": LocData(2000324706, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Treehouse": LocData(2000325468, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Tree Chest": LocData(2000323728, "Subcon Forest Area", paintings=1), + + "Subcon Forest - Burning House": LocData(2000324710, "Subcon Forest Area", paintings=2), + "Subcon Forest - Burning Tree Climb": LocData(2000325079, "Subcon Forest Area", paintings=2), + "Subcon Forest - Burning Stump Chest": LocData(2000323731, "Subcon Forest Area", paintings=2), + "Subcon Forest - Burning Forest Treehouse": LocData(2000325467, "Subcon Forest Area", paintings=2), + "Subcon Forest - Spider Bone Cage A": LocData(2000324462, "Subcon Forest Area", paintings=2), + "Subcon Forest - Spider Bone Cage B": LocData(2000325080, "Subcon Forest Area", paintings=2), + "Subcon Forest - Triple Spider Bounce": LocData(2000324765, "Subcon Forest Area", paintings=2), + "Subcon Forest - Noose Treehouse": LocData(2000324856, "Subcon Forest Area", hookshot=True, paintings=2), + + "Subcon Forest - Long Tree Climb Chest": LocData(2000323734, "Subcon Forest Area", + required_hats=[HatType.DWELLER], paintings=2), + + "Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Area"), + + "Subcon Forest - Manor Rooftop": LocData(2000325466, "Subcon Forest Area", + hit_type=HitType.dweller_bell, paintings=1), + + "Subcon Forest - Infinite Yarn Bush": LocData(2000325478, "Subcon Forest Area", + required_hats=[HatType.BREWING], paintings=2), + + "Subcon Forest - Magnet Badge Bush": LocData(2000325479, "Subcon Forest Area", + required_hats=[HatType.BREWING], paintings=3), + + "Subcon Forest - Dweller Stump": LocData(2000324767, "Subcon Forest Area", + required_hats=[HatType.DWELLER], paintings=3), + + "Subcon Forest - Dweller Floating Rocks": LocData(2000324464, "Subcon Forest Area", + required_hats=[HatType.DWELLER], paintings=3), + + "Subcon Forest - Dweller Platforming Tree A": LocData(2000324709, "Subcon Forest Area", paintings=3), + + "Subcon Forest - Dweller Platforming Tree B": LocData(2000324855, "Subcon Forest Area", + required_hats=[HatType.DWELLER], paintings=3), + + "Subcon Forest - Giant Time Piece": LocData(2000325473, "Subcon Forest Area", paintings=3), + "Subcon Forest - Gallows": LocData(2000325472, "Subcon Forest Area", paintings=3), + + "Subcon Forest - Green and Purple Dweller Rocks": LocData(2000325082, "Subcon Forest Area", paintings=3), + + "Subcon Forest - Dweller Shack": LocData(2000324463, "Subcon Forest Area", + required_hats=[HatType.DWELLER], paintings=3), + + "Subcon Forest - Tall Tree Hookshot Swing": LocData(2000324766, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + hookshot=True, + paintings=3), + + "Subcon Well - Hookshot Badge Chest": LocData(2000324114, "The Subcon Well", + hit_type=HitType.umbrella_or_brewing, paintings=1), + + "Subcon Well - Above Chest": LocData(2000324612, "The Subcon Well", + hit_type=HitType.umbrella_or_brewing, paintings=1), + + "Subcon Well - On Pipe": LocData(2000324311, "The Subcon Well", hookshot=True, + hit_type=HitType.umbrella_or_brewing, paintings=1), + + "Subcon Well - Mushroom": LocData(2000325318, "The Subcon Well", + hit_type=HitType.umbrella_or_brewing, paintings=1), + + "Queen Vanessa's Manor - Cellar": LocData(2000324841, "Queen Vanessa's Manor", + hit_type=HitType.dweller_bell, paintings=1), + + "Queen Vanessa's Manor - Bedroom Chest": LocData(2000323808, "Queen Vanessa's Manor", + hit_type=HitType.dweller_bell, paintings=1), + + "Queen Vanessa's Manor - Hall Chest": LocData(2000323896, "Queen Vanessa's Manor", + hit_type=HitType.dweller_bell, paintings=1), + + "Queen Vanessa's Manor - Chandelier": LocData(2000325546, "Queen Vanessa's Manor", + hit_type=HitType.dweller_bell, paintings=1), + + # Alpine Skyline + "Alpine Skyline - Goat Village: Below Hookpoint": LocData(2000334856, "Alpine Skyline Area (TIHS)"), + "Alpine Skyline - Goat Village: Hidden Branch": LocData(2000334855, "Alpine Skyline Area (TIHS)"), + "Alpine Skyline - Goat Refinery": LocData(2000333635, "Alpine Skyline Area (TIHS)", hookshot=True), + "Alpine Skyline - Bird Pass Fork": LocData(2000335911, "Alpine Skyline Area (TIHS)", hookshot=True), + + "Alpine Skyline - Yellow Band Hills": LocData(2000335756, "Alpine Skyline Area (TIHS)", hookshot=True, + required_hats=[HatType.BREWING]), + + "Alpine Skyline - The Purrloined Village: Horned Stone": LocData(2000335561, "Alpine Skyline Area"), + "Alpine Skyline - The Purrloined Village: Chest Reward": LocData(2000334831, "Alpine Skyline Area"), + "Alpine Skyline - The Birdhouse: Triple Crow Chest": LocData(2000334758, "The Birdhouse"), + + "Alpine Skyline - The Birdhouse: Dweller Platforms Relic": LocData(2000336497, "The Birdhouse", + required_hats=[HatType.DWELLER]), + + "Alpine Skyline - The Birdhouse: Brewing Crate House": LocData(2000336496, "The Birdhouse"), + "Alpine Skyline - The Birdhouse: Hay Bale": LocData(2000335885, "The Birdhouse"), + "Alpine Skyline - The Birdhouse: Alpine Crow Mini-Gauntlet": LocData(2000335886, "The Birdhouse"), + "Alpine Skyline - The Birdhouse: Outer Edge": LocData(2000335492, "The Birdhouse"), + + "Alpine Skyline - Mystifying Time Mesa: Zipline": LocData(2000337058, "Alpine Skyline Area"), + "Alpine Skyline - Mystifying Time Mesa: Gate Puzzle": LocData(2000336052, "Alpine Skyline Area"), + "Alpine Skyline - Ember Summit": LocData(2000336311, "Alpine Skyline Area (TIHS)", hookshot=True), + "Alpine Skyline - The Lava Cake: Center Fence Cage": LocData(2000335448, "The Lava Cake"), + "Alpine Skyline - The Lava Cake: Outer Island Chest": LocData(2000334291, "The Lava Cake"), + "Alpine Skyline - The Lava Cake: Dweller Pillars": LocData(2000335417, "The Lava Cake"), + "Alpine Skyline - The Lava Cake: Top Cake": LocData(2000335418, "The Lava Cake"), + "Alpine Skyline - The Twilight Path": LocData(2000334434, "Alpine Skyline Area", required_hats=[HatType.DWELLER]), + "Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(2000336478, "The Twilight Bell"), + "Alpine Skyline - The Twilight Bell: Ice Platform": LocData(2000335826, "The Twilight Bell"), + "Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area"), + "Alpine Skyline - Windy Passage": LocData(2000334776, "Alpine Skyline Area (TIHS)", hookshot=True), + "Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(2000336395, "The Windmill"), + "Alpine Skyline - The Windmill: Entrance": LocData(2000335783, "The Windmill"), + "Alpine Skyline - The Windmill: Dropdown": LocData(2000335815, "The Windmill"), + "Alpine Skyline - The Windmill: House Window": LocData(2000335389, "The Windmill"), + + "The Finale - Frozen Item": LocData(2000304108, "The Finale"), + + "Bon Voyage! - Lamp Post Top": LocData(2000305321, "Bon Voyage!", dlc_flags=HatDLC.dlc1), + "Bon Voyage! - Mafia Cargo Ship": LocData(2000304313, "Bon Voyage!", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Toilet": LocData(2000305109, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Bar": LocData(2000304251, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Dive Board Ledge": LocData(2000304254, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Top Balcony": LocData(2000304255, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Octopus Room": LocData(2000305253, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Octopus Room Top": LocData(2000304249, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Laundry Room": LocData(2000304250, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Ship Side": LocData(2000304247, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Silver Ring": LocData(2000305252, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Reception Room - Suitcase": LocData(2000304045, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Reception Room - Under Desk": LocData(2000304047, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Lamp Post": LocData(2000304048, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Iceberg Top": LocData(2000304046, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Post Captain Rescue": LocData(2000304049, "Rock the Boat", dlc_flags=HatDLC.dlc1, + required_hats=[HatType.ICE]), + + "Nyakuza Metro - Main Station Dining Area": LocData(2000304105, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), + "Nyakuza Metro - Top of Ramen Shop": LocData(2000304104, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), + + "Yellow Overpass Station - Brewing Crate": LocData(2000305413, "Yellow Overpass Station", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.BREWING]), + + "Bluefin Tunnel - Cat Vacuum": LocData(2000305111, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + + "Pink Paw Station - Cat Vacuum": LocData(2000305110, "Pink Paw Station", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.DWELLER]), + + "Pink Paw Station - Behind Fan": LocData(2000304106, "Pink Paw Station", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.TIME_STOP, HatType.DWELLER]), +} + +act_completions = { + "Act Completion (Time Rift - Gallery)": LocData(2000312758, "Time Rift - Gallery", required_hats=[HatType.BREWING]), + "Act Completion (Time Rift - The Lab)": LocData(2000312838, "Time Rift - The Lab"), + + "Act Completion (Welcome to Mafia Town)": LocData(2000311771, "Welcome to Mafia Town"), + "Act Completion (Barrel Battle)": LocData(2000311958, "Barrel Battle"), + "Act Completion (She Came from Outer Space)": LocData(2000312262, "She Came from Outer Space"), + "Act Completion (Down with the Mafia!)": LocData(2000311326, "Down with the Mafia!"), + "Act Completion (Cheating the Race)": LocData(2000312318, "Cheating the Race", required_hats=[HatType.TIME_STOP]), + "Act Completion (Heating Up Mafia Town)": LocData(2000311481, "Heating Up Mafia Town", hit_type=HitType.umbrella), + "Act Completion (The Golden Vault)": LocData(2000312250, "The Golden Vault"), + "Act Completion (Time Rift - Bazaar)": LocData(2000312465, "Time Rift - Bazaar"), + "Act Completion (Time Rift - Sewers)": LocData(2000312484, "Time Rift - Sewers"), + "Act Completion (Time Rift - Mafia of Cooks)": LocData(2000311855, "Time Rift - Mafia of Cooks"), + + "Act Completion (Dead Bird Studio)": LocData(2000311383, "Dead Bird Studio", + hit_type=HitType.umbrella_or_brewing), + + "Act Completion (Murder on the Owl Express)": LocData(2000311544, "Murder on the Owl Express"), + "Act Completion (Picture Perfect)": LocData(2000311587, "Picture Perfect"), + "Act Completion (Train Rush)": LocData(2000312481, "Train Rush", hookshot=True), + "Act Completion (The Big Parade)": LocData(2000311157, "The Big Parade", hit_type=HitType.umbrella), + "Act Completion (Award Ceremony)": LocData(2000311488, "Award Ceremony"), + "Act Completion (Dead Bird Studio Basement)": LocData(2000312253, "Dead Bird Studio Basement", hookshot=True), + "Act Completion (Time Rift - The Owl Express)": LocData(2000312807, "Time Rift - The Owl Express"), + "Act Completion (Time Rift - The Moon)": LocData(2000312785, "Time Rift - The Moon"), + "Act Completion (Time Rift - Dead Bird Studio)": LocData(2000312577, "Time Rift - Dead Bird Studio"), + + "Act Completion (Contractual Obligations)": LocData(2000312317, "Contractual Obligations", paintings=1), + + "Act Completion (The Subcon Well)": LocData(2000311160, "The Subcon Well", + hookshot=True, hit_type=HitType.umbrella_or_brewing, paintings=1), + + "Act Completion (Toilet of Doom)": LocData(2000311984, "Toilet of Doom", + hit_type=HitType.umbrella_or_brewing, hookshot=True, paintings=1), + + "Act Completion (Queen Vanessa's Manor)": LocData(2000312017, "Queen Vanessa's Manor", + hit_type=HitType.umbrella, paintings=1), + + "Act Completion (Mail Delivery Service)": LocData(2000312032, "Mail Delivery Service", + required_hats=[HatType.SPRINT]), + + "Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired", + hit_type=HitType.umbrella), + + "Act Completion (Time Rift - Pipe)": LocData(2000313069, "Time Rift - Pipe", hookshot=True), + "Act Completion (Time Rift - Village)": LocData(2000313056, "Time Rift - Village"), + "Act Completion (Time Rift - Sleepy Subcon)": LocData(2000312086, "Time Rift - Sleepy Subcon"), + + "Act Completion (The Birdhouse)": LocData(2000311428, "The Birdhouse"), + "Act Completion (The Lava Cake)": LocData(2000312509, "The Lava Cake"), + "Act Completion (The Twilight Bell)": LocData(2000311540, "The Twilight Bell"), + "Act Completion (The Windmill)": LocData(2000312263, "The Windmill"), + "Act Completion (The Illness has Spread)": LocData(2000312022, "The Illness has Spread", hookshot=True), + + "Act Completion (Time Rift - The Twilight Bell)": LocData(2000312399, "Time Rift - The Twilight Bell", + required_hats=[HatType.DWELLER]), + + "Act Completion (Time Rift - Curly Tail Trail)": LocData(2000313335, "Time Rift - Curly Tail Trail", + required_hats=[HatType.ICE]), + + "Act Completion (Time Rift - Alpine Skyline)": LocData(2000311777, "Time Rift - Alpine Skyline"), + + "Act Completion (The Finale)": LocData(2000311872, "The Finale", hookshot=True, required_hats=[HatType.DWELLER]), + "Act Completion (Time Rift - Tour)": LocData(2000311803, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + + "Act Completion (Bon Voyage!)": LocData(2000311520, "Bon Voyage!", dlc_flags=HatDLC.dlc1, hookshot=True), + "Act Completion (Ship Shape)": LocData(2000311451, "Ship Shape", dlc_flags=HatDLC.dlc1), + + "Act Completion (Rock the Boat)": LocData(2000311437, "Rock the Boat", dlc_flags=HatDLC.dlc1, + required_hats=[HatType.ICE]), + + "Act Completion (Time Rift - Balcony)": LocData(2000312226, "Time Rift - Balcony", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Act Completion (Time Rift - Deep Sea)": LocData(2000312434, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True, required_hats=[HatType.DWELLER, HatType.ICE]), + + "Act Completion (Nyakuza Metro Intro)": LocData(2000311138, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), + + "Act Completion (Yellow Overpass Station)": LocData(2000311206, "Yellow Overpass Station", + dlc_flags=HatDLC.dlc2, + hookshot=True), + + "Act Completion (Yellow Overpass Manhole)": LocData(2000311387, "Yellow Overpass Manhole", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE]), + + "Act Completion (Green Clean Station)": LocData(2000311207, "Green Clean Station", dlc_flags=HatDLC.dlc2), + + "Act Completion (Green Clean Manhole)": LocData(2000311388, "Green Clean Manhole", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE, HatType.DWELLER]), + + "Act Completion (Bluefin Tunnel)": LocData(2000311208, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + + "Act Completion (Pink Paw Station)": LocData(2000311209, "Pink Paw Station", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.DWELLER]), + + "Act Completion (Pink Paw Manhole)": LocData(2000311389, "Pink Paw Manhole", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE]), + + "Act Completion (Rush Hour)": LocData(2000311210, "Rush Hour", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.ICE, HatType.BREWING]), + + "Act Completion (Time Rift - Rumbi Factory)": LocData(2000312736, "Time Rift - Rumbi Factory", + dlc_flags=HatDLC.dlc2), +} + +storybook_pages = { + "Mafia of Cooks - Page: Fish Pile": LocData(2000345091, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Trash Mound": LocData(2000345090, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Beside Red Building": LocData(2000345092, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Behind Shipping Containers": LocData(2000345095, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Top of Boat": LocData(2000345093, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Below Dock": LocData(2000345094, "Time Rift - Mafia of Cooks"), + + "Dead Bird Studio (Rift) - Page: Behind Cardboard Planet": LocData(2000345449, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Near Time Rift Gate": LocData(2000345447, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Top of Metal Bar": LocData(2000345448, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Lava Lamp": LocData(2000345450, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Above Horse Picture": LocData(2000345451, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Green Screen": LocData(2000345452, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: In The Corner": LocData(2000345453, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Above TV Room": LocData(2000345445, "Time Rift - Dead Bird Studio"), + + "Sleepy Subcon - Page: Behind Entrance Area": LocData(2000345373, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Near Wrecking Ball": LocData(2000345327, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Behind Crane": LocData(2000345371, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Wrecked Treehouse": LocData(2000345326, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Behind 2nd Rift Gate": LocData(2000345372, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Rotating Platform": LocData(2000345328, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Behind 3rd Rift Gate": LocData(2000345329, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Frozen Tree": LocData(2000345330, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Secret Library": LocData(2000345370, "Time Rift - Sleepy Subcon"), + + "Alpine Skyline (Rift) - Page: Entrance Area Hidden Ledge": LocData(2000345016, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Windmill Island Ledge": LocData(2000345012, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Waterfall Wooden Pillar": LocData(2000345015, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Lonely Birdhouse Top": LocData(2000345014, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Below Aqueduct": LocData(2000345013, "Time Rift - Alpine Skyline"), + + "Deep Sea - Page: Starfish": LocData(2000346454, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1), + "Deep Sea - Page: Mini Castle": LocData(2000346452, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1), + "Deep Sea - Page: Urchins": LocData(2000346449, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1), + + "Deep Sea - Page: Big Castle": LocData(2000346450, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Deep Sea - Page: Castle Top Chest": LocData(2000304850, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Deep Sea - Page: Urchin Ledge": LocData(2000346451, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Deep Sea - Page: Hidden Castle Chest": LocData(2000304849, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Deep Sea - Page: Falling Platform": LocData(2000346456, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Deep Sea - Page: Lava Starfish": LocData(2000346453, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True), + + "Tour - Page: Mafia Town - Ledge": LocData(2000345038, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Mafia Town - Beach": LocData(2000345039, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Dead Bird Studio - C.A.W. Agents": LocData(2000345040, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Dead Bird Studio - Fragile Box": LocData(2000345041, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Subcon Forest - Giant Frozen Tree": LocData(2000345042, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Subcon Forest - Top of Pillar": LocData(2000345043, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Alpine Skyline - Birdhouse": LocData(2000345044, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Alpine Skyline - Behind Lava Isle": LocData(2000345047, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: The Finale - Near Entrance": LocData(2000345087, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + + "Rumbi Factory - Page: Manhole": LocData(2000345891, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Shutter Doors": LocData(2000345888, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + + "Rumbi Factory - Page: Toxic Waste Dispenser": LocData(2000345892, "Time Rift - Rumbi Factory", + dlc_flags=HatDLC.dlc2), + + "Rumbi Factory - Page: 3rd Area Ledge": LocData(2000345889, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + + "Rumbi Factory - Page: Green Box Assembly Line": LocData(2000345884, "Time Rift - Rumbi Factory", + dlc_flags=HatDLC.dlc2), + + "Rumbi Factory - Page: Broken Window": LocData(2000345885, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Money Vault": LocData(2000345890, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Warehouse Boxes": LocData(2000345887, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Glass Shelf": LocData(2000345886, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Last Area": LocData(2000345883, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), +} + +shop_locations = { + "Badge Seller - Item 1": LocData(2000301003, "Badge Seller"), + "Badge Seller - Item 2": LocData(2000301004, "Badge Seller"), + "Badge Seller - Item 3": LocData(2000301005, "Badge Seller"), + "Badge Seller - Item 4": LocData(2000301006, "Badge Seller"), + "Badge Seller - Item 5": LocData(2000301007, "Badge Seller"), + "Badge Seller - Item 6": LocData(2000301008, "Badge Seller"), + "Badge Seller - Item 7": LocData(2000301009, "Badge Seller"), + "Badge Seller - Item 8": LocData(2000301010, "Badge Seller"), + "Badge Seller - Item 9": LocData(2000301011, "Badge Seller"), + "Badge Seller - Item 10": LocData(2000301012, "Badge Seller"), + "Mafia Boss Shop Item": LocData(2000301013, "Spaceship"), + + "Yellow Overpass Station - Yellow Ticket Booth": LocData(2000301014, "Yellow Overpass Station", + dlc_flags=HatDLC.dlc2), + + "Green Clean Station - Green Ticket Booth": LocData(2000301015, "Green Clean Station", dlc_flags=HatDLC.dlc2), + "Bluefin Tunnel - Blue Ticket Booth": LocData(2000301016, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + + "Pink Paw Station - Pink Ticket Booth": LocData(2000301017, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + hookshot=True, required_hats=[HatType.DWELLER]), + + "Main Station Thug A - Item 1": LocData(2000301048, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 2": LocData(2000301049, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 3": LocData(2000301050, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 4": LocData(2000301051, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 5": LocData(2000301052, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + + "Main Station Thug B - Item 1": LocData(2000301053, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 2": LocData(2000301054, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 3": LocData(2000301055, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 4": LocData(2000301056, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 5": LocData(2000301057, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + + "Main Station Thug C - Item 1": LocData(2000301058, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 2": LocData(2000301059, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 3": LocData(2000301060, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 4": LocData(2000301061, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 5": LocData(2000301062, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + + "Yellow Overpass Thug A - Item 1": LocData(2000301018, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 2": LocData(2000301019, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 3": LocData(2000301020, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 4": LocData(2000301021, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 5": LocData(2000301022, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + + "Yellow Overpass Thug B - Item 1": LocData(2000301043, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 2": LocData(2000301044, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 3": LocData(2000301045, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 4": LocData(2000301046, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 5": LocData(2000301047, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + + "Yellow Overpass Thug C - Item 1": LocData(2000301063, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 2": LocData(2000301064, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 3": LocData(2000301065, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 4": LocData(2000301066, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 5": LocData(2000301067, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + + "Green Clean Station Thug A - Item 1": LocData(2000301033, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 2": LocData(2000301034, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 3": LocData(2000301035, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 4": LocData(2000301036, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 5": LocData(2000301037, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + + # This guy requires either the yellow ticket or the Ice Hat + "Green Clean Station Thug B - Item 1": LocData(2000301028, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 2": LocData(2000301029, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 3": LocData(2000301030, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 4": LocData(2000301031, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 5": LocData(2000301032, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + + "Bluefin Tunnel Thug - Item 1": LocData(2000301023, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 2": LocData(2000301024, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 3": LocData(2000301025, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 4": LocData(2000301026, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 5": LocData(2000301027, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + + "Pink Paw Station Thug - Item 1": LocData(2000301038, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 2": LocData(2000301039, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 3": LocData(2000301040, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 4": LocData(2000301041, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 5": LocData(2000301042, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + +} + +contract_locations = { + "Snatcher's Contract - The Subcon Well": LocData(2000300200, "Contractual Obligations"), + "Snatcher's Contract - Toilet of Doom": LocData(2000300201, "Subcon Forest Area", paintings=1), + "Snatcher's Contract - Queen Vanessa's Manor": LocData(2000300202, "Subcon Forest Area", paintings=1), + "Snatcher's Contract - Mail Delivery Service": LocData(2000300203, "Subcon Forest Area", paintings=1), +} + +# Don't put any of the locations from peaks here, the rules for their entrances are set already +zipline_unlocks = { + "Alpine Skyline - Bird Pass Fork": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - Yellow Band Hills": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - The Purrloined Village: Horned Stone": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - The Purrloined Village: Chest Reward": "Zipline Unlock - The Birdhouse Path", + + "Alpine Skyline - Mystifying Time Mesa: Zipline": "Zipline Unlock - The Lava Cake Path", + "Alpine Skyline - Mystifying Time Mesa: Gate Puzzle": "Zipline Unlock - The Lava Cake Path", + "Alpine Skyline - Ember Summit": "Zipline Unlock - The Lava Cake Path", + + "Alpine Skyline - Goat Outpost Horn": "Zipline Unlock - The Windmill Path", + "Alpine Skyline - Windy Passage": "Zipline Unlock - The Windmill Path", + + "Alpine Skyline - The Twilight Path": "Zipline Unlock - The Twilight Bell Path", +} + +# act completion rules should be set automatically as these are all event items +zero_jumps_hard = { + "Time Rift - Sewers (Zero Jumps)": LocData(0, "Time Rift - Sewers", + required_hats=[HatType.ICE], dlc_flags=HatDLC.death_wish), + + "Time Rift - Bazaar (Zero Jumps)": LocData(0, "Time Rift - Bazaar", + required_hats=[HatType.ICE], dlc_flags=HatDLC.death_wish), + + "The Big Parade (Zero Jumps)": LocData(0, "The Big Parade", + hit_type=HitType.umbrella, + required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "Time Rift - Pipe (Zero Jumps)": LocData(0, "Time Rift - Pipe", hookshot=True, dlc_flags=HatDLC.death_wish), + + "Time Rift - Curly Tail Trail (Zero Jumps)": LocData(0, "Time Rift - Curly Tail Trail", + required_hats=[HatType.ICE], dlc_flags=HatDLC.death_wish), + + "Time Rift - The Twilight Bell (Zero Jumps)": LocData(0, "Time Rift - The Twilight Bell", + required_hats=[HatType.ICE, HatType.DWELLER], + hit_type=HitType.umbrella_or_brewing, + dlc_flags=HatDLC.death_wish), + + "The Illness has Spread (Zero Jumps)": LocData(0, "The Illness has Spread", + required_hats=[HatType.ICE], hookshot=True, + hit_type=HitType.umbrella_or_brewing, dlc_flags=HatDLC.death_wish), + + "The Finale (Zero Jumps)": LocData(0, "The Finale", + required_hats=[HatType.ICE, HatType.DWELLER], + hookshot=True, + dlc_flags=HatDLC.death_wish), + + "Pink Paw Station (Zero Jumps)": LocData(0, "Pink Paw Station", + required_hats=[HatType.ICE], + hookshot=True, + dlc_flags=HatDLC.dlc2_dw), +} + +zero_jumps_expert = { + "The Birdhouse (Zero Jumps)": LocData(0, "The Birdhouse", + required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "The Lava Cake (Zero Jumps)": LocData(0, "The Lava Cake", dlc_flags=HatDLC.death_wish), + + "The Windmill (Zero Jumps)": LocData(0, "The Windmill", + required_hats=[HatType.ICE], + misc_required=["No Bonk Badge"], + dlc_flags=HatDLC.death_wish), + "The Twilight Bell (Zero Jumps)": LocData(0, "The Twilight Bell", + required_hats=[HatType.ICE, HatType.DWELLER], + hit_type=HitType.umbrella_or_brewing, + misc_required=["No Bonk Badge"], + dlc_flags=HatDLC.death_wish), + + "Sleepy Subcon (Zero Jumps)": LocData(0, "Time Rift - Sleepy Subcon", required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "Ship Shape (Zero Jumps)": LocData(0, "Ship Shape", required_hats=[HatType.ICE], dlc_flags=HatDLC.dlc1_dw), +} + +zero_jumps = { + **zero_jumps_hard, + **zero_jumps_expert, + "Welcome to Mafia Town (Zero Jumps)": LocData(0, "Welcome to Mafia Town", dlc_flags=HatDLC.death_wish), + + "Down with the Mafia! (Zero Jumps)": LocData(0, "Down with the Mafia!", + required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "Cheating the Race (Zero Jumps)": LocData(0, "Cheating the Race", + required_hats=[HatType.TIME_STOP], + dlc_flags=HatDLC.death_wish), + + "The Golden Vault (Zero Jumps)": LocData(0, "The Golden Vault", + required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "Dead Bird Studio (Zero Jumps)": LocData(0, "Dead Bird Studio", + required_hats=[HatType.ICE], + hit_type=HitType.umbrella_or_brewing, + dlc_flags=HatDLC.death_wish), + + "Murder on the Owl Express (Zero Jumps)": LocData(0, "Murder on the Owl Express", + required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "Picture Perfect (Zero Jumps)": LocData(0, "Picture Perfect", dlc_flags=HatDLC.death_wish), + + "Train Rush (Zero Jumps)": LocData(0, "Train Rush", + required_hats=[HatType.ICE], + hookshot=True, + dlc_flags=HatDLC.death_wish), + + "Contractual Obligations (Zero Jumps)": LocData(0, "Contractual Obligations", + paintings=1, + dlc_flags=HatDLC.death_wish), + + "Your Contract has Expired (Zero Jumps)": LocData(0, "Your Contract has Expired", + hit_type=HitType.umbrella, + dlc_flags=HatDLC.death_wish), + + # No ice hat/painting required in Expert + "Toilet of Doom (Zero Jumps)": LocData(0, "Toilet of Doom", + hookshot=True, + hit_type=HitType.umbrella_or_brewing, + required_hats=[HatType.ICE], + paintings=1, + dlc_flags=HatDLC.death_wish), + + "Mail Delivery Service (Zero Jumps)": LocData(0, "Mail Delivery Service", + required_hats=[HatType.SPRINT], + dlc_flags=HatDLC.death_wish), + + "Time Rift - Alpine Skyline (Zero Jumps)": LocData(0, "Time Rift - Alpine Skyline", + required_hats=[HatType.ICE], + hookshot=True, + dlc_flags=HatDLC.death_wish), + + "Time Rift - The Lab (Zero Jumps)": LocData(0, "Time Rift - The Lab", + required_hats=[HatType.ICE], + dlc_flags=HatDLC.death_wish), + + "Yellow Overpass Station (Zero Jumps)": LocData(0, "Yellow Overpass Station", + required_hats=[HatType.ICE], + hookshot=True, + dlc_flags=HatDLC.dlc2_dw), + + "Green Clean Station (Zero Jumps)": LocData(0, "Green Clean Station", + required_hats=[HatType.ICE], + dlc_flags=HatDLC.dlc2_dw), +} + +snatcher_coins = { + "Snatcher Coin - Top of HQ (DWTM)": LocData(0, "Down with the Mafia!", snatcher_coin="Snatcher Coin - Top of HQ", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of HQ (CTR)": LocData(0, "Cheating the Race", snatcher_coin="Snatcher Coin - Top of HQ", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of HQ (HUMT)": LocData(0, "Heating Up Mafia Town", snatcher_coin="Snatcher Coin - Top of HQ", + hit_type=HitType.umbrella, dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of HQ (TGV)": LocData(0, "The Golden Vault", snatcher_coin="Snatcher Coin - Top of HQ", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of HQ (DW: BTH)": LocData(0, "Beat the Heat", snatcher_coin="Snatcher Coin - Top of HQ", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Tower": LocData(0, "Mafia Town Area (HUMT)", snatcher_coin="Snatcher Coin - Top of Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Tower (DW: BTH)": LocData(0, "Beat the Heat", snatcher_coin="Snatcher Coin - Top of Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Tower (DW: CAT)": LocData(0, "Collect-a-thon", snatcher_coin="Snatcher Coin - Top of Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Tower (SSFOS)": LocData(0, "She Speedran from Outer Space", + snatcher_coin="Snatcher Coin - Top of Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Tower (DW: MJ)": LocData(0, "Mafia's Jumps", snatcher_coin="Snatcher Coin - Top of Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Under Ruined Tower": LocData(0, "Mafia Town Area", + snatcher_coin="Snatcher Coin - Under Ruined Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Under Ruined Tower (DW: CAT)": LocData(0, "Collect-a-thon", + snatcher_coin="Snatcher Coin - Under Ruined Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Under Ruined Tower (DW: SSFOS)": LocData(0, "She Speedran from Outer Space", + snatcher_coin="Snatcher Coin - Under Ruined Tower", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Red House (DBS)": LocData(0, "Dead Bird Studio - Elevator Area", + snatcher_coin="Snatcher Coin - Top of Red House", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Top of Red House (DW: SB)": LocData(0, "Security Breach", + snatcher_coin="Snatcher Coin - Top of Red House", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Train Rush": LocData(0, "Train Rush", snatcher_coin="Snatcher Coin - Train Rush", + hookshot=True, dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Train Rush (10 Seconds)": LocData(0, "10 Seconds until Self-Destruct", + snatcher_coin="Snatcher Coin - Train Rush", + hookshot=True, dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Picture Perfect": LocData(0, "Picture Perfect", snatcher_coin="Snatcher Coin - Picture Perfect", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Swamp Tree": LocData(0, "Subcon Forest Area", snatcher_coin="Snatcher Coin - Swamp Tree", + hookshot=True, paintings=1, + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Swamp Tree (Speedrun Well)": LocData(0, "Speedrun Well", + snatcher_coin="Snatcher Coin - Swamp Tree", + hookshot=True, dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Manor Roof": LocData(0, "Subcon Forest Area", snatcher_coin="Snatcher Coin - Manor Roof", + hit_type=HitType.dweller_bell, paintings=1, + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Giant Time Piece": LocData(0, "Subcon Forest Area", + snatcher_coin="Snatcher Coin - Giant Time Piece", + paintings=3, dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Goat Village Top": LocData(0, "Alpine Skyline Area (TIHS)", + snatcher_coin="Snatcher Coin - Goat Village Top", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Goat Village Top (Illness Speedrun)": LocData(0, "The Illness has Speedrun", + snatcher_coin="Snatcher Coin - Goat Village Top", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Lava Cake": LocData(0, "The Lava Cake", snatcher_coin="Snatcher Coin - Lava Cake", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Windmill": LocData(0, "The Windmill", snatcher_coin="Snatcher Coin - Windmill", + dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Windmill (DW: WUW)": LocData(0, "Wound-Up Windmill", snatcher_coin="Snatcher Coin - Windmill", + hookshot=True, dlc_flags=HatDLC.death_wish), + + "Snatcher Coin - Green Clean Tower": LocData(0, "Green Clean Station", + snatcher_coin="Snatcher Coin - Green Clean Tower", + dlc_flags=HatDLC.dlc2_dw), + + "Snatcher Coin - Bluefin Cat Train": LocData(0, "Bluefin Tunnel", + snatcher_coin="Snatcher Coin - Bluefin Cat Train", + dlc_flags=HatDLC.dlc2_dw), + + "Snatcher Coin - Pink Paw Fence": LocData(0, "Pink Paw Station", + snatcher_coin="Snatcher Coin - Pink Paw Fence", + dlc_flags=HatDLC.dlc2_dw), +} + +event_locs = { + **zero_jumps, + **snatcher_coins, + "HUMT Access": LocData(0, "Heating Up Mafia Town"), + "TOD Access": LocData(0, "Toilet of Doom"), + "YCHE Access": LocData(0, "Your Contract has Expired"), + "AFR Access": LocData(0, "Alpine Free Roam"), + "TIHS Access": LocData(0, "The Illness has Spread"), + + "Birdhouse Cleared": LocData(0, "The Birdhouse", act_event=True), + "Lava Cake Cleared": LocData(0, "The Lava Cake", act_event=True), + "Windmill Cleared": LocData(0, "The Windmill", act_event=True), + "Twilight Bell Cleared": LocData(0, "The Twilight Bell", act_event=True), + "Time Piece Cluster": LocData(0, "The Finale", act_event=True), + + # not really an act + "Nyakuza Intro Cleared": LocData(0, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), + + "Yellow Overpass Station Cleared": LocData(0, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, act_event=True), + "Green Clean Station Cleared": LocData(0, "Green Clean Station", dlc_flags=HatDLC.dlc2, act_event=True), + "Bluefin Tunnel Cleared": LocData(0, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, act_event=True), + "Pink Paw Station Cleared": LocData(0, "Pink Paw Station", dlc_flags=HatDLC.dlc2, act_event=True), + "Yellow Overpass Manhole Cleared": LocData(0, "Yellow Overpass Manhole", dlc_flags=HatDLC.dlc2, act_event=True), + "Green Clean Manhole Cleared": LocData(0, "Green Clean Manhole", dlc_flags=HatDLC.dlc2, act_event=True), + "Pink Paw Manhole Cleared": LocData(0, "Pink Paw Manhole", dlc_flags=HatDLC.dlc2, act_event=True), + "Rush Hour Cleared": LocData(0, "Rush Hour", dlc_flags=HatDLC.dlc2, act_event=True), +} + +# DO NOT ALTER THE ORDER OF THIS LIST +death_wishes = { + "Beat the Heat": 2000350000, + "Snatcher's Hit List": 2000350002, + "So You're Back From Outer Space": 2000350004, + "Collect-a-thon": 2000350006, + "Rift Collapse: Mafia of Cooks": 2000350008, + "She Speedran from Outer Space": 2000350010, + "Mafia's Jumps": 2000350012, + "Vault Codes in the Wind": 2000350014, + "Encore! Encore!": 2000350016, + "Snatcher Coins in Mafia Town": 2000350018, + + "Security Breach": 2000350020, + "The Great Big Hootenanny": 2000350022, + "Rift Collapse: Dead Bird Studio": 2000350024, + "10 Seconds until Self-Destruct": 2000350026, + "Killing Two Birds": 2000350028, + "Snatcher Coins in Battle of the Birds": 2000350030, + "Zero Jumps": 2000350032, + + "Speedrun Well": 2000350034, + "Rift Collapse: Sleepy Subcon": 2000350036, + "Boss Rush": 2000350038, + "Quality Time with Snatcher": 2000350040, + "Breaching the Contract": 2000350042, + "Snatcher Coins in Subcon Forest": 2000350044, + + "Bird Sanctuary": 2000350046, + "Rift Collapse: Alpine Skyline": 2000350048, + "Wound-Up Windmill": 2000350050, + "The Illness has Speedrun": 2000350052, + "Snatcher Coins in Alpine Skyline": 2000350054, + "Camera Tourist": 2000350056, + + "The Mustache Gauntlet": 2000350058, + "No More Bad Guys": 2000350060, + + "Seal the Deal": 2000350062, + "Rift Collapse: Deep Sea": 2000350064, + "Cruisin' for a Bruisin'": 2000350066, + + "Community Rift: Rhythm Jump Studio": 2000350068, + "Community Rift: Twilight Travels": 2000350070, + "Community Rift: The Mountain Rift": 2000350072, + "Snatcher Coins in Nyakuza Metro": 2000350074, +} + +location_table = { + **ahit_locations, + **act_completions, + **storybook_pages, + **contract_locations, + **shop_locations, +} diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py new file mode 100644 index 000000000000..17c4b95efc7a --- /dev/null +++ b/worlds/ahit/Options.py @@ -0,0 +1,770 @@ +from typing import List, TYPE_CHECKING, Dict, Any +from schema import Schema, Optional +from dataclasses import dataclass +from worlds.AutoWorld import PerGameCommonOptions +from Options import Range, Toggle, DeathLink, Choice, OptionDict, DefaultOnToggle, OptionGroup + +if TYPE_CHECKING: + from . import HatInTimeWorld + + +def create_option_groups() -> List[OptionGroup]: + option_group_list: List[OptionGroup] = [] + for name, options in ahit_option_groups.items(): + option_group_list.append(OptionGroup(name=name, options=options)) + + return option_group_list + + +def adjust_options(world: "HatInTimeWorld"): + if world.options.HighestChapterCost < world.options.LowestChapterCost: + world.options.HighestChapterCost.value, world.options.LowestChapterCost.value = \ + world.options.LowestChapterCost.value, world.options.HighestChapterCost.value + + if world.options.FinalChapterMaxCost < world.options.FinalChapterMinCost: + world.options.FinalChapterMaxCost.value, world.options.FinalChapterMinCost.value = \ + world.options.FinalChapterMinCost.value, world.options.FinalChapterMaxCost.value + + if world.options.BadgeSellerMaxItems < world.options.BadgeSellerMinItems: + world.options.BadgeSellerMaxItems.value, world.options.BadgeSellerMinItems.value = \ + world.options.BadgeSellerMinItems.value, world.options.BadgeSellerMaxItems.value + + if world.options.NyakuzaThugMaxShopItems < world.options.NyakuzaThugMinShopItems: + world.options.NyakuzaThugMaxShopItems.value, world.options.NyakuzaThugMinShopItems.value = \ + world.options.NyakuzaThugMinShopItems.value, world.options.NyakuzaThugMaxShopItems.value + + if world.options.DWShuffleCountMax < world.options.DWShuffleCountMin: + world.options.DWShuffleCountMax.value, world.options.DWShuffleCountMin.value = \ + world.options.DWShuffleCountMin.value, world.options.DWShuffleCountMax.value + + total_tps: int = get_total_time_pieces(world) + if world.options.HighestChapterCost > total_tps-5: + world.options.HighestChapterCost.value = min(45, total_tps-5) + + if world.options.LowestChapterCost > total_tps-5: + world.options.LowestChapterCost.value = min(45, total_tps-5) + + if world.options.FinalChapterMaxCost > total_tps: + world.options.FinalChapterMaxCost.value = min(50, total_tps) + + if world.options.FinalChapterMinCost > total_tps: + world.options.FinalChapterMinCost.value = min(50, total_tps) + + if world.is_dlc1() and world.options.ShipShapeCustomTaskGoal <= 0: + # automatically determine task count based on Tasksanity settings + if world.options.Tasksanity: + world.options.ShipShapeCustomTaskGoal.value = world.options.TasksanityCheckCount * world.options.TasksanityTaskStep + else: + world.options.ShipShapeCustomTaskGoal.value = 18 + + # Don't allow Rush Hour goal if DLC2 content is disabled + if world.options.EndGoal == EndGoal.option_rush_hour and not world.options.EnableDLC2: + world.options.EndGoal.value = EndGoal.option_finale + + # Don't allow Seal the Deal goal if Death Wish content is disabled + if world.options.EndGoal == EndGoal.option_seal_the_deal and not world.is_dw(): + world.options.EndGoal.value = EndGoal.option_finale + + if world.options.DWEnableBonus: + world.options.DWAutoCompleteBonuses.value = 0 + + if world.is_dw_only(): + world.options.EndGoal.value = EndGoal.option_seal_the_deal + world.options.ActRandomizer.value = 0 + world.options.ShuffleAlpineZiplines.value = 0 + world.options.ShuffleSubconPaintings.value = 0 + world.options.ShuffleStorybookPages.value = 0 + world.options.ShuffleActContracts.value = 0 + world.options.EnableDLC1.value = 0 + world.options.LogicDifficulty.value = LogicDifficulty.option_normal + world.options.DWTimePieceRequirement.value = 0 + + +def get_total_time_pieces(world: "HatInTimeWorld") -> int: + count: int = 40 + if world.is_dlc1(): + count += 6 + + if world.is_dlc2(): + count += 10 + + return min(40+world.options.MaxExtraTimePieces, count) + + +class EndGoal(Choice): + """The end goal required to beat the game. + Finale: Reach Time's End and beat Mustache Girl. The Finale will be in its vanilla location. + + Rush Hour: Reach and complete Rush Hour. The level will be in its vanilla location and Chapter 7 + will be the final chapter. You also must find Nyakuza Metro itself and complete all of its levels. + Requires DLC2 content to be enabled. + + Seal the Deal: Reach and complete the Seal the Deal death wish main objective. + Requires Death Wish content to be enabled.""" + display_name = "End Goal" + option_finale = 1 + option_rush_hour = 2 + option_seal_the_deal = 3 + default = 1 + + +class ActRandomizer(Choice): + """If enabled, shuffle the game's Acts between each other. + Light will cause Time Rifts to only be shuffled amongst each other, + and Blue Time Rifts and Purple Time Rifts to be shuffled separately.""" + display_name = "Shuffle Acts" + option_false = 0 + option_light = 1 + option_insanity = 2 + default = 1 + + +class ActPlando(OptionDict): + """Plando acts onto other acts. For example, \"Train Rush\": \"Alpine Free Roam\" will place Alpine Free Roam + at Train Rush.""" + display_name = "Act Plando" + schema = Schema({ + Optional(str): str + }) + + +class ActBlacklist(OptionDict): + """Blacklist acts from being shuffled onto other acts. Multiple can be listed per act. + For example, \"Barrel Battle\": [\"The Big Parade\", \"Dead Bird Studio\"] + will prevent The Big Parade and Dead Bird Studio from being shuffled onto Barrel Battle.""" + display_name = "Act Blacklist" + schema = Schema({ + Optional(str): list + }) + + +class FinaleShuffle(Toggle): + """If enabled, chapter finales will only be shuffled amongst each other in act shuffle.""" + display_name = "Finale Shuffle" + + +class LogicDifficulty(Choice): + """Choose the difficulty setting for logic. + For an exhaustive list of all logic tricks for each difficulty, see this Google Doc: + https://docs.google.com/document/d/1x9VLSQ5davfx1KGamR9T0mD5h69_lDXJ6H7Gq7knJRI/edit?usp=sharing""" + display_name = "Logic Difficulty" + option_normal = -1 + option_moderate = 0 + option_hard = 1 + option_expert = 2 + default = -1 + + +class CTRLogic(Choice): + """Choose how you want to logically clear Cheating the Race.""" + display_name = "Cheating the Race Logic" + option_time_stop_only = 0 + option_scooter = 1 + option_sprint = 2 + option_nothing = 3 + default = 0 + + +class RandomizeHatOrder(Choice): + """Randomize the order that hats are stitched in. + Time Stop Last will force Time Stop to be the last hat in the sequence.""" + display_name = "Randomize Hat Order" + option_false = 0 + option_true = 1 + option_time_stop_last = 2 + default = 1 + + +class YarnBalancePercent(Range): + """How much (in percentage) of the yarn in the pool that will be progression balanced.""" + display_name = "Yarn Balance Percentage" + default = 20 + range_start = 0 + range_end = 100 + + +class TimePieceBalancePercent(Range): + """How much (in percentage) of time pieces in the pool that will be progression balanced.""" + display_name = "Time Piece Balance Percentage" + default = 35 + range_start = 0 + range_end = 100 + + +class StartWithCompassBadge(DefaultOnToggle): + """If enabled, start with the Compass Badge. In Archipelago, the Compass Badge will track all items in the world + (instead of just Relics). Recommended if you're not familiar with where item locations are.""" + display_name = "Start with Compass Badge" + + +class CompassBadgeMode(Choice): + """closest - Compass Badge points to the closest item regardless of classification + important_only - Compass Badge points to progression/useful items only + important_first - Compass Badge points to progression/useful items first, then it will point to junk items""" + display_name = "Compass Badge Mode" + option_closest = 1 + option_important_only = 2 + option_important_first = 3 + default = 1 + + +class UmbrellaLogic(Toggle): + """Makes Hat Kid's default punch attack do absolutely nothing, making the Umbrella much more relevant and useful""" + display_name = "Umbrella Logic" + + +class ShuffleStorybookPages(DefaultOnToggle): + """If enabled, each storybook page in the purple Time Rifts is an item check. + The Compass Badge can track these down for you.""" + display_name = "Shuffle Storybook Pages" + + +class ShuffleActContracts(DefaultOnToggle): + """If enabled, shuffle Snatcher's act contracts into the pool as items""" + display_name = "Shuffle Contracts" + + +class ShuffleAlpineZiplines(Toggle): + """If enabled, Alpine's zipline paths leading to the peaks will be locked behind items.""" + display_name = "Shuffle Alpine Ziplines" + + +class ShuffleSubconPaintings(Toggle): + """If enabled, shuffle items into the pool that unlock Subcon Forest fire spirit paintings. + These items are progressive, with the order of Village-Swamp-Courtyard.""" + display_name = "Shuffle Subcon Paintings" + + +class NoPaintingSkips(Toggle): + """If enabled, prevent Subcon fire wall skips from being in logic on higher difficulty settings.""" + display_name = "No Subcon Fire Wall Skips" + + +class StartingChapter(Choice): + """Determines which chapter you will be guaranteed to be able to enter at the beginning of the game.""" + display_name = "Starting Chapter" + option_1 = 1 + option_2 = 2 + option_3 = 3 + option_4 = 4 + default = 1 + + +class ChapterCostIncrement(Range): + """Lower values mean chapter costs increase slower. Higher values make the cost differences more steep.""" + display_name = "Chapter Cost Increment" + range_start = 1 + range_end = 8 + default = 4 + + +class ChapterCostMinDifference(Range): + """The minimum difference between chapter costs.""" + display_name = "Minimum Chapter Cost Difference" + range_start = 1 + range_end = 8 + default = 4 + + +class LowestChapterCost(Range): + """Value determining the lowest possible cost for a chapter. + Chapter costs will, progressively, be calculated based on this value (except for the final chapter).""" + display_name = "Lowest Possible Chapter Cost" + range_start = 0 + range_end = 10 + default = 5 + + +class HighestChapterCost(Range): + """Value determining the highest possible cost for a chapter. + Chapter costs will, progressively, be calculated based on this value (except for the final chapter).""" + display_name = "Highest Possible Chapter Cost" + range_start = 15 + range_end = 45 + default = 25 + + +class FinalChapterMinCost(Range): + """Minimum Time Pieces required to enter the final chapter. This is part of your goal.""" + display_name = "Final Chapter Minimum Time Piece Cost" + range_start = 0 + range_end = 50 + default = 30 + + +class FinalChapterMaxCost(Range): + """Maximum Time Pieces required to enter the final chapter. This is part of your goal.""" + display_name = "Final Chapter Maximum Time Piece Cost" + range_start = 0 + range_end = 50 + default = 35 + + +class MaxExtraTimePieces(Range): + """Maximum number of extra Time Pieces from the DLCs. + Arctic Cruise will add up to 6. Nyakuza Metro will add up to 10. The absolute maximum is 56.""" + display_name = "Max Extra Time Pieces" + range_start = 0 + range_end = 16 + default = 16 + + +class YarnCostMin(Range): + """The minimum possible yarn needed to stitch a hat.""" + display_name = "Minimum Yarn Cost" + range_start = 1 + range_end = 12 + default = 4 + + +class YarnCostMax(Range): + """The maximum possible yarn needed to stitch a hat.""" + display_name = "Maximum Yarn Cost" + range_start = 1 + range_end = 12 + default = 8 + + +class YarnAvailable(Range): + """How much yarn is available to collect in the item pool.""" + display_name = "Yarn Available" + range_start = 30 + range_end = 80 + default = 50 + + +class MinExtraYarn(Range): + """The minimum number of extra yarn in the item pool. + There must be at least this much more yarn over the total number of yarn needed to craft all hats. + For example, if this option's value is 10, and the total yarn needed to craft all hats is 40, + there must be at least 50 yarn in the pool.""" + display_name = "Max Extra Yarn" + range_start = 5 + range_end = 15 + default = 10 + + +class HatItems(Toggle): + """Removes all yarn from the pool and turns the hats into individual items instead.""" + display_name = "Hat Items" + + +class MinPonCost(Range): + """The minimum number of Pons that any item in the Badge Seller's shop can cost.""" + display_name = "Minimum Shop Pon Cost" + range_start = 10 + range_end = 800 + default = 75 + + +class MaxPonCost(Range): + """The maximum number of Pons that any item in the Badge Seller's shop can cost.""" + display_name = "Maximum Shop Pon Cost" + range_start = 10 + range_end = 800 + default = 300 + + +class BadgeSellerMinItems(Range): + """The smallest number of items that the Badge Seller can have for sale.""" + display_name = "Badge Seller Minimum Items" + range_start = 0 + range_end = 10 + default = 4 + + +class BadgeSellerMaxItems(Range): + """The largest number of items that the Badge Seller can have for sale.""" + display_name = "Badge Seller Maximum Items" + range_start = 0 + range_end = 10 + default = 8 + + +class EnableDLC1(Toggle): + """Shuffle content from The Arctic Cruise (Chapter 6) into the game. This also includes the Tour time rift. + DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE SEAL THE DEAL DLC INSTALLED!!!""" + display_name = "Shuffle Chapter 6" + + +class Tasksanity(Toggle): + """If enabled, Ship Shape tasks will become checks. Requires DLC1 content to be enabled.""" + display_name = "Tasksanity" + + +class TasksanityTaskStep(Range): + """How many tasks the player must complete in Tasksanity to send a check.""" + display_name = "Tasksanity Task Step" + range_start = 1 + range_end = 3 + default = 1 + + +class TasksanityCheckCount(Range): + """How many Tasksanity checks there will be in total.""" + display_name = "Tasksanity Check Count" + range_start = 1 + range_end = 30 + default = 18 + + +class ExcludeTour(Toggle): + """Removes the Tour time rift from the game. This option is recommended if you don't want to deal with + important levels being shuffled onto the Tour time rift, or important items being shuffled onto Tour pages + when your goal is Time's End.""" + display_name = "Exclude Tour Time Rift" + + +class ShipShapeCustomTaskGoal(Range): + """Change the number of tasks required to complete Ship Shape. If this option's value is 0, the number of tasks + required will be TasksanityTaskStep x TasksanityCheckCount, if Tasksanity is enabled. If Tasksanity is disabled, + it will use the game's default of 18. + This option will not affect Cruisin' for a Bruisin'.""" + display_name = "Ship Shape Custom Task Goal" + range_start = 0 + range_end = 90 + default = 0 + + +class EnableDLC2(Toggle): + """Shuffle content from Nyakuza Metro (Chapter 7) into the game. + DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE NYAKUZA METRO DLC INSTALLED!!!""" + display_name = "Shuffle Chapter 7" + + +class MetroMinPonCost(Range): + """The cheapest an item can be in any Nyakuza Metro shop. Includes ticket booths.""" + display_name = "Metro Shops Minimum Pon Cost" + range_start = 10 + range_end = 800 + default = 50 + + +class MetroMaxPonCost(Range): + """The most expensive an item can be in any Nyakuza Metro shop. Includes ticket booths.""" + display_name = "Metro Shops Maximum Pon Cost" + range_start = 10 + range_end = 800 + default = 200 + + +class NyakuzaThugMinShopItems(Range): + """The smallest number of items that the thugs in Nyakuza Metro can have for sale.""" + display_name = "Nyakuza Thug Minimum Shop Items" + range_start = 0 + range_end = 5 + default = 2 + + +class NyakuzaThugMaxShopItems(Range): + """The largest number of items that the thugs in Nyakuza Metro can have for sale.""" + display_name = "Nyakuza Thug Maximum Shop Items" + range_start = 0 + range_end = 5 + default = 4 + + +class NoTicketSkips(Choice): + """Prevent metro gate skips from being in logic on higher difficulties. + Rush Hour option will only consider the ticket skips for Rush Hour in logic.""" + display_name = "No Ticket Skips" + option_false = 0 + option_true = 1 + option_rush_hour = 2 + + +class BaseballBat(Toggle): + """Replace the Umbrella with the baseball bat from Nyakuza Metro. + DLC2 content does not have to be shuffled for this option but Nyakuza Metro still needs to be installed.""" + display_name = "Baseball Bat" + + +class EnableDeathWish(Toggle): + """Shuffle Death Wish contracts into the game. Each contract by default will have 1 check granted upon completion. + DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE SEAL THE DEAL DLC INSTALLED!!!""" + display_name = "Enable Death Wish" + + +class DeathWishOnly(Toggle): + """An alternative gameplay mode that allows you to exclusively play Death Wish in a seed. + This has the following effects: + - Death Wish is instantly unlocked from the start + - All hats and other progression items are instantly given to you + - Useful items such as Fast Hatter Badge will still be in the item pool instead of in your inventory at the start + - All chapters and their levels are unlocked, act shuffle is forced off + - Any checks other than Death Wish contracts are completely removed + - All Pons in the item pool are replaced with Health Pons or random cosmetics + - The EndGoal option is forced to complete Seal the Deal""" + display_name = "Death Wish Only" + + +class DWShuffle(Toggle): + """An alternative mode for Death Wish where each contract is unlocked one by one, in a random order. + Stamp requirements to unlock contracts is removed. Any excluded contracts will not be shuffled into the sequence. + If Seal the Deal is the end goal, it will always be the last Death Wish in the sequence. + Disabling candles is highly recommended.""" + display_name = "Death Wish Shuffle" + + +class DWShuffleCountMin(Range): + """The minimum number of Death Wishes that can be in the Death Wish shuffle sequence. + The final result is clamped at the number of non-excluded Death Wishes.""" + display_name = "Death Wish Shuffle Minimum Count" + range_start = 5 + range_end = 38 + default = 18 + + +class DWShuffleCountMax(Range): + """The maximum number of Death Wishes that can be in the Death Wish shuffle sequence. + The final result is clamped at the number of non-excluded Death Wishes.""" + display_name = "Death Wish Shuffle Maximum Count" + range_start = 5 + range_end = 38 + default = 25 + + +class DWEnableBonus(Toggle): + """In Death Wish, add a location for completing all of a DW contract's bonuses, + in addition to the location for completing the DW contract normally. + WARNING!! Only for the brave! This option can create VERY DIFFICULT SEEDS! + ONLY turn this on if you know what you are doing to yourself and everyone else in the multiworld! + Using Peace and Tranquility to auto-complete the bonuses will NOT count!""" + display_name = "Shuffle Death Wish Full Completions" + + +class DWAutoCompleteBonuses(DefaultOnToggle): + """If enabled, auto complete all bonus stamps after completing the main objective in a Death Wish. + This option will have no effect if bonus checks (DWEnableBonus) are turned on.""" + display_name = "Auto Complete Bonus Stamps" + + +class DWExcludeAnnoyingContracts(DefaultOnToggle): + """Exclude Death Wish contracts from the pool that are particularly tedious or take a long time to reach/clear. + Excluded Death Wishes are automatically completed as soon as they are unlocked. + This option currently excludes the following contracts: + - Vault Codes in the Wind + - Boss Rush + - Camera Tourist + - The Mustache Gauntlet + - Rift Collapse: Deep Sea + - Cruisin' for a Bruisin' + - Seal the Deal (non-excluded if goal, but the checks are still excluded)""" + display_name = "Exclude Annoying Death Wish Contracts" + + +class DWExcludeAnnoyingBonuses(DefaultOnToggle): + """If Death Wish full completions are shuffled in, exclude tedious Death Wish full completions from the pool. + Excluded bonus Death Wishes automatically reward their bonus stamps upon completion of the main objective. + This option currently excludes the following bonuses: + - So You're Back From Outer Space + - Encore! Encore! + - Snatcher's Hit List + - 10 Seconds until Self-Destruct + - Killing Two Birds + - Zero Jumps + - Bird Sanctuary + - Wound-Up Windmill + - Vault Codes in the Wind + - Boss Rush + - Camera Tourist + - The Mustache Gauntlet + - Rift Collapse: Deep Sea + - Cruisin' for a Bruisin' + - Seal the Deal""" + display_name = "Exclude Annoying Death Wish Full Completions" + + +class DWExcludeCandles(DefaultOnToggle): + """If enabled, exclude all candle Death Wishes.""" + display_name = "Exclude Candle Death Wishes" + + +class DWTimePieceRequirement(Range): + """How many Time Pieces that will be required to unlock Death Wish.""" + display_name = "Death Wish Time Piece Requirement" + range_start = 0 + range_end = 35 + default = 15 + + +class TrapChance(Range): + """The chance for any junk item in the pool to be replaced by a trap.""" + display_name = "Trap Chance" + range_start = 0 + range_end = 100 + default = 0 + + +class BabyTrapWeight(Range): + """The weight of Baby Traps in the trap pool. + Baby Traps place a multitude of the Conductor's grandkids into Hat Kid's hands, causing her to lose her balance.""" + display_name = "Baby Trap Weight" + range_start = 0 + range_end = 100 + default = 40 + + +class LaserTrapWeight(Range): + """The weight of Laser Traps in the trap pool. + Laser Traps will spawn multiple giant lasers (from Snatcher's boss fight) at Hat Kid's location.""" + display_name = "Laser Trap Weight" + range_start = 0 + range_end = 100 + default = 40 + + +class ParadeTrapWeight(Range): + """The weight of Parade Traps in the trap pool. + Parade Traps will summon multiple Express Band owls with knives that chase Hat Kid by mimicking her movement.""" + display_name = "Parade Trap Weight" + range_start = 0 + range_end = 100 + default = 20 + + +@dataclass +class AHITOptions(PerGameCommonOptions): + EndGoal: EndGoal + ActRandomizer: ActRandomizer + ActPlando: ActPlando + ActBlacklist: ActBlacklist + ShuffleAlpineZiplines: ShuffleAlpineZiplines + FinaleShuffle: FinaleShuffle + LogicDifficulty: LogicDifficulty + YarnBalancePercent: YarnBalancePercent + TimePieceBalancePercent: TimePieceBalancePercent + RandomizeHatOrder: RandomizeHatOrder + UmbrellaLogic: UmbrellaLogic + StartWithCompassBadge: StartWithCompassBadge + CompassBadgeMode: CompassBadgeMode + ShuffleStorybookPages: ShuffleStorybookPages + ShuffleActContracts: ShuffleActContracts + ShuffleSubconPaintings: ShuffleSubconPaintings + NoPaintingSkips: NoPaintingSkips + StartingChapter: StartingChapter + CTRLogic: CTRLogic + + EnableDLC1: EnableDLC1 + Tasksanity: Tasksanity + TasksanityTaskStep: TasksanityTaskStep + TasksanityCheckCount: TasksanityCheckCount + ExcludeTour: ExcludeTour + ShipShapeCustomTaskGoal: ShipShapeCustomTaskGoal + + EnableDeathWish: EnableDeathWish + DWShuffle: DWShuffle + DWShuffleCountMin: DWShuffleCountMin + DWShuffleCountMax: DWShuffleCountMax + DeathWishOnly: DeathWishOnly + DWEnableBonus: DWEnableBonus + DWAutoCompleteBonuses: DWAutoCompleteBonuses + DWExcludeAnnoyingContracts: DWExcludeAnnoyingContracts + DWExcludeAnnoyingBonuses: DWExcludeAnnoyingBonuses + DWExcludeCandles: DWExcludeCandles + DWTimePieceRequirement: DWTimePieceRequirement + + EnableDLC2: EnableDLC2 + BaseballBat: BaseballBat + MetroMinPonCost: MetroMinPonCost + MetroMaxPonCost: MetroMaxPonCost + NyakuzaThugMinShopItems: NyakuzaThugMinShopItems + NyakuzaThugMaxShopItems: NyakuzaThugMaxShopItems + NoTicketSkips: NoTicketSkips + + LowestChapterCost: LowestChapterCost + HighestChapterCost: HighestChapterCost + ChapterCostIncrement: ChapterCostIncrement + ChapterCostMinDifference: ChapterCostMinDifference + MaxExtraTimePieces: MaxExtraTimePieces + + FinalChapterMinCost: FinalChapterMinCost + FinalChapterMaxCost: FinalChapterMaxCost + + YarnCostMin: YarnCostMin + YarnCostMax: YarnCostMax + YarnAvailable: YarnAvailable + MinExtraYarn: MinExtraYarn + HatItems: HatItems + + MinPonCost: MinPonCost + MaxPonCost: MaxPonCost + BadgeSellerMinItems: BadgeSellerMinItems + BadgeSellerMaxItems: BadgeSellerMaxItems + + TrapChance: TrapChance + BabyTrapWeight: BabyTrapWeight + LaserTrapWeight: LaserTrapWeight + ParadeTrapWeight: ParadeTrapWeight + + death_link: DeathLink + + +ahit_option_groups: Dict[str, List[Any]] = { + "General Options": [EndGoal, ShuffleStorybookPages, ShuffleAlpineZiplines, ShuffleSubconPaintings, + ShuffleActContracts, MinPonCost, MaxPonCost, BadgeSellerMinItems, BadgeSellerMaxItems, + LogicDifficulty, NoPaintingSkips, CTRLogic], + + "Act Options": [ActRandomizer, StartingChapter, LowestChapterCost, HighestChapterCost, + ChapterCostIncrement, ChapterCostMinDifference, FinalChapterMinCost, FinalChapterMaxCost, + FinaleShuffle, ActPlando, ActBlacklist], + + "Item Options": [StartWithCompassBadge, CompassBadgeMode, RandomizeHatOrder, YarnAvailable, YarnCostMin, + YarnCostMax, MinExtraYarn, HatItems, UmbrellaLogic, MaxExtraTimePieces, YarnBalancePercent, + TimePieceBalancePercent], + + "Arctic Cruise Options": [EnableDLC1, Tasksanity, TasksanityTaskStep, TasksanityCheckCount, + ShipShapeCustomTaskGoal, ExcludeTour], + + "Nyakuza Metro Options": [EnableDLC2, MetroMinPonCost, MetroMaxPonCost, NyakuzaThugMinShopItems, + NyakuzaThugMaxShopItems, BaseballBat, NoTicketSkips], + + "Death Wish Options": [EnableDeathWish, DWTimePieceRequirement, DWShuffle, DWShuffleCountMin, DWShuffleCountMax, + DWEnableBonus, DWAutoCompleteBonuses, DWExcludeAnnoyingContracts, DWExcludeAnnoyingBonuses, + DWExcludeCandles, DeathWishOnly], + + "Trap Options": [TrapChance, BabyTrapWeight, LaserTrapWeight, ParadeTrapWeight] +} + + +slot_data_options: List[str] = [ + "EndGoal", + "ActRandomizer", + "ShuffleAlpineZiplines", + "LogicDifficulty", + "CTRLogic", + "RandomizeHatOrder", + "UmbrellaLogic", + "StartWithCompassBadge", + "CompassBadgeMode", + "ShuffleStorybookPages", + "ShuffleActContracts", + "ShuffleSubconPaintings", + "NoPaintingSkips", + "HatItems", + + "EnableDLC1", + "Tasksanity", + "TasksanityTaskStep", + "TasksanityCheckCount", + "ShipShapeCustomTaskGoal", + "ExcludeTour", + + "EnableDeathWish", + "DWShuffle", + "DeathWishOnly", + "DWEnableBonus", + "DWAutoCompleteBonuses", + "DWTimePieceRequirement", + + "EnableDLC2", + "MetroMinPonCost", + "MetroMaxPonCost", + "BaseballBat", + "NoTicketSkips", + + "MinPonCost", + "MaxPonCost", + + "death_link", +] diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py new file mode 100644 index 000000000000..8cb3782bdec6 --- /dev/null +++ b/worlds/ahit/Regions.py @@ -0,0 +1,1036 @@ +from BaseClasses import Region, Entrance, ItemClassification, Location, LocationProgressType +from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem +from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \ + shop_locations, TASKSANITY_START_ID, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard +from typing import TYPE_CHECKING, List, Dict, Optional +from .Rules import set_rift_rules, get_difficulty +from .Options import ActRandomizer, EndGoal + +if TYPE_CHECKING: + from . import HatInTimeWorld + + +MIN_FIRST_SPHERE_LOCATIONS = 30 + + +# ChapterIndex: region +chapter_regions = { + ChapterIndex.SPACESHIP: "Spaceship", + ChapterIndex.MAFIA: "Mafia Town", + ChapterIndex.BIRDS: "Battle of the Birds", + ChapterIndex.SUBCON: "Subcon Forest", + ChapterIndex.ALPINE: "Alpine Skyline", + ChapterIndex.FINALE: "Time's End", + ChapterIndex.CRUISE: "The Arctic Cruise", + ChapterIndex.METRO: "Nyakuza Metro", +} + +# entrance: region +act_entrances = { + "Welcome to Mafia Town": "Mafia Town - Act 1", + "Barrel Battle": "Mafia Town - Act 2", + "She Came from Outer Space": "Mafia Town - Act 3", + "Down with the Mafia!": "Mafia Town - Act 4", + "Cheating the Race": "Mafia Town - Act 5", + "Heating Up Mafia Town": "Mafia Town - Act 6", + "The Golden Vault": "Mafia Town - Act 7", + + "Dead Bird Studio": "Battle of the Birds - Act 1", + "Murder on the Owl Express": "Battle of the Birds - Act 2", + "Picture Perfect": "Battle of the Birds - Act 3", + "Train Rush": "Battle of the Birds - Act 4", + "The Big Parade": "Battle of the Birds - Act 5", + "Award Ceremony": "Battle of the Birds - Finale A", + "Dead Bird Studio Basement": "Battle of the Birds - Finale B", + + "Contractual Obligations": "Subcon Forest - Act 1", + "The Subcon Well": "Subcon Forest - Act 2", + "Toilet of Doom": "Subcon Forest - Act 3", + "Queen Vanessa's Manor": "Subcon Forest - Act 4", + "Mail Delivery Service": "Subcon Forest - Act 5", + "Your Contract has Expired": "Subcon Forest - Finale", + + "Alpine Free Roam": "Alpine Skyline - Free Roam", + "The Illness has Spread": "Alpine Skyline - Finale", + + "The Finale": "Time's End - Act 1", + + "Bon Voyage!": "The Arctic Cruise - Act 1", + "Ship Shape": "The Arctic Cruise - Act 2", + "Rock the Boat": "The Arctic Cruise - Finale", + + "Nyakuza Free Roam": "Nyakuza Metro - Free Roam", + "Rush Hour": "Nyakuza Metro - Finale", +} + +act_chapters = { + "Time Rift - Gallery": "Spaceship", + "Time Rift - The Lab": "Spaceship", + + "Welcome to Mafia Town": "Mafia Town", + "Barrel Battle": "Mafia Town", + "She Came from Outer Space": "Mafia Town", + "Down with the Mafia!": "Mafia Town", + "Cheating the Race": "Mafia Town", + "Heating Up Mafia Town": "Mafia Town", + "The Golden Vault": "Mafia Town", + "Time Rift - Mafia of Cooks": "Mafia Town", + "Time Rift - Sewers": "Mafia Town", + "Time Rift - Bazaar": "Mafia Town", + + "Dead Bird Studio": "Battle of the Birds", + "Murder on the Owl Express": "Battle of the Birds", + "Picture Perfect": "Battle of the Birds", + "Train Rush": "Battle of the Birds", + "The Big Parade": "Battle of the Birds", + "Award Ceremony": "Battle of the Birds", + "Dead Bird Studio Basement": "Battle of the Birds", + "Time Rift - Dead Bird Studio": "Battle of the Birds", + "Time Rift - The Owl Express": "Battle of the Birds", + "Time Rift - The Moon": "Battle of the Birds", + + "Contractual Obligations": "Subcon Forest", + "The Subcon Well": "Subcon Forest", + "Toilet of Doom": "Subcon Forest", + "Queen Vanessa's Manor": "Subcon Forest", + "Mail Delivery Service": "Subcon Forest", + "Your Contract has Expired": "Subcon Forest", + "Time Rift - Sleepy Subcon": "Subcon Forest", + "Time Rift - Pipe": "Subcon Forest", + "Time Rift - Village": "Subcon Forest", + + "Alpine Free Roam": "Alpine Skyline", + "The Illness has Spread": "Alpine Skyline", + "Time Rift - Alpine Skyline": "Alpine Skyline", + "Time Rift - The Twilight Bell": "Alpine Skyline", + "Time Rift - Curly Tail Trail": "Alpine Skyline", + + "The Finale": "Time's End", + "Time Rift - Tour": "Time's End", + + "Bon Voyage!": "The Arctic Cruise", + "Ship Shape": "The Arctic Cruise", + "Rock the Boat": "The Arctic Cruise", + "Time Rift - Balcony": "The Arctic Cruise", + "Time Rift - Deep Sea": "The Arctic Cruise", + + "Nyakuza Free Roam": "Nyakuza Metro", + "Rush Hour": "Nyakuza Metro", + "Time Rift - Rumbi Factory": "Nyakuza Metro", +} + +# region: list[Region] +rift_access_regions = { + "Time Rift - Gallery": ["Spaceship"], + "Time Rift - The Lab": ["Spaceship"], + + "Time Rift - Sewers": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space", + "Down with the Mafia!", "Cheating the Race", "Heating Up Mafia Town", + "The Golden Vault"], + + "Time Rift - Bazaar": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space", + "Down with the Mafia!", "Cheating the Race", "Heating Up Mafia Town", + "The Golden Vault"], + + "Time Rift - Mafia of Cooks": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space", + "Down with the Mafia!", "Cheating the Race", "The Golden Vault"], + + "Time Rift - The Owl Express": ["Murder on the Owl Express"], + "Time Rift - The Moon": ["Picture Perfect", "The Big Parade"], + "Time Rift - Dead Bird Studio": ["Dead Bird Studio", "Dead Bird Studio Basement"], + + "Time Rift - Pipe": ["Contractual Obligations", "The Subcon Well", + "Toilet of Doom", "Queen Vanessa's Manor", + "Mail Delivery Service"], + + "Time Rift - Village": ["Contractual Obligations", "The Subcon Well", + "Toilet of Doom", "Queen Vanessa's Manor", + "Mail Delivery Service"], + + "Time Rift - Sleepy Subcon": ["Contractual Obligations", "The Subcon Well", + "Toilet of Doom", "Queen Vanessa's Manor", + "Mail Delivery Service"], + + "Time Rift - The Twilight Bell": ["Alpine Free Roam"], + "Time Rift - Curly Tail Trail": ["Alpine Free Roam"], + "Time Rift - Alpine Skyline": ["Alpine Free Roam", "The Illness has Spread"], + + "Time Rift - Tour": ["Time's End"], + + "Time Rift - Balcony": ["Cruise Ship"], + "Time Rift - Deep Sea": ["Bon Voyage!"], + + "Time Rift - Rumbi Factory": ["Nyakuza Free Roam"], +} + +# Time piece identifiers to be used in act shuffle +chapter_act_info = { + "Time Rift - Gallery": "Spaceship_WaterRift_Gallery", + "Time Rift - The Lab": "Spaceship_WaterRift_MailRoom", + + "Welcome to Mafia Town": "chapter1_tutorial", + "Barrel Battle": "chapter1_barrelboss", + "She Came from Outer Space": "chapter1_cannon_repair", + "Down with the Mafia!": "chapter1_boss", + "Cheating the Race": "harbor_impossible_race", + "Heating Up Mafia Town": "mafiatown_lava", + "The Golden Vault": "mafiatown_goldenvault", + "Time Rift - Mafia of Cooks": "TimeRift_Cave_Mafia", + "Time Rift - Sewers": "TimeRift_Water_Mafia_Easy", + "Time Rift - Bazaar": "TimeRift_Water_Mafia_Hard", + + "Dead Bird Studio": "DeadBirdStudio", + "Murder on the Owl Express": "chapter3_murder", + "Picture Perfect": "moon_camerasnap", + "Train Rush": "trainwreck_selfdestruct", + "The Big Parade": "moon_parade", + "Award Ceremony": "award_ceremony", + "Dead Bird Studio Basement": "chapter3_secret_finale", + "Time Rift - Dead Bird Studio": "TimeRift_Cave_BirdBasement", + "Time Rift - The Owl Express": "TimeRift_Water_TWreck_Panels", + "Time Rift - The Moon": "TimeRift_Water_TWreck_Parade", + + "Contractual Obligations": "subcon_village_icewall", + "The Subcon Well": "subcon_cave", + "Toilet of Doom": "chapter2_toiletboss", + "Queen Vanessa's Manor": "vanessa_manor_attic", + "Mail Delivery Service": "subcon_maildelivery", + "Your Contract has Expired": "snatcher_boss", + "Time Rift - Sleepy Subcon": "TimeRift_Cave_Raccoon", + "Time Rift - Pipe": "TimeRift_Water_Subcon_Hookshot", + "Time Rift - Village": "TimeRift_Water_Subcon_Dwellers", + + "Alpine Free Roam": "AlpineFreeRoam", # not an actual Time Piece + "The Illness has Spread": "AlpineSkyline_Finale", + "Time Rift - Alpine Skyline": "TimeRift_Cave_Alps", + "Time Rift - The Twilight Bell": "TimeRift_Water_Alp_Goats", + "Time Rift - Curly Tail Trail": "TimeRift_Water_AlpineSkyline_Cats", + + "The Finale": "TheFinale_FinalBoss", + "Time Rift - Tour": "TimeRift_Cave_Tour", + + "Bon Voyage!": "Cruise_Boarding", + "Ship Shape": "Cruise_Working", + "Rock the Boat": "Cruise_Sinking", + "Time Rift - Balcony": "Cruise_WaterRift_Slide", + "Time Rift - Deep Sea": "Cruise_CaveRift_Aquarium", + + "Nyakuza Free Roam": "MetroFreeRoam", # not an actual Time Piece + "Rush Hour": "Metro_Escape", + "Time Rift - Rumbi Factory": "Metro_CaveRift_RumbiFactory" +} + +# Some of these may vary depending on options. See is_valid_first_act() +guaranteed_first_acts = [ + "Welcome to Mafia Town", + "Barrel Battle", + "She Came from Outer Space", + "Down with the Mafia!", + "Heating Up Mafia Town", + "The Golden Vault", + + "Dead Bird Studio", + "Murder on the Owl Express", + "Dead Bird Studio Basement", + + "Contractual Obligations", + "The Subcon Well", + "Queen Vanessa's Manor", + "Your Contract has Expired", + + "Rock the Boat", + + "Time Rift - Mafia of Cooks", + "Time Rift - Dead Bird Studio", + "Time Rift - Sleepy Subcon", + "Time Rift - Alpine Skyline" + "Time Rift - Tour", + "Time Rift - Rumbi Factory", +] + +purple_time_rifts = [ + "Time Rift - Mafia of Cooks", + "Time Rift - Dead Bird Studio", + "Time Rift - Sleepy Subcon", + "Time Rift - Alpine Skyline", + "Time Rift - Deep Sea", + "Time Rift - Tour", + "Time Rift - Rumbi Factory", +] + +chapter_finales = [ + "Dead Bird Studio Basement", + "Your Contract has Expired", + "The Illness has Spread", + "Rock the Boat", + "Rush Hour", +] + +# Acts blacklisted in act shuffle +# entrance: region +blacklisted_acts = { + "Battle of the Birds - Finale A": "Award Ceremony", +} + +# Blacklisted act shuffle combinations to help prevent impossible layouts. Mostly for free roam acts. +blacklisted_combos = { + "The Illness has Spread": ["Nyakuza Free Roam", "Alpine Free Roam", "Contractual Obligations"], + "Rush Hour": ["Nyakuza Free Roam", "Alpine Free Roam", "Contractual Obligations"], + + # Bon Voyage is here to prevent the cycle: Owl Express -> Bon Voyage -> Deep Sea -> MOTOE -> Owl Express + # which would make them all inaccessible since those rifts have no other entrances + "Time Rift - The Owl Express": ["Alpine Free Roam", "Nyakuza Free Roam", "Bon Voyage!", + "Contractual Obligations"], + + "Time Rift - The Moon": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"], + "Time Rift - Dead Bird Studio": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"], + "Time Rift - Curly Tail Trail": ["Nyakuza Free Roam", "Contractual Obligations"], + "Time Rift - The Twilight Bell": ["Nyakuza Free Roam", "Contractual Obligations"], + "Time Rift - Alpine Skyline": ["Nyakuza Free Roam", "Contractual Obligations"], + "Time Rift - Rumbi Factory": ["Alpine Free Roam", "Contractual Obligations"], + + # See above comment + "Time Rift - Deep Sea": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations", + "Murder on the Owl Express"], + + # was causing test failures + "Time Rift - Balcony": ["Alpine Free Roam"], +} + + +def create_regions(world: "HatInTimeWorld"): + # ------------------------------------------- HUB -------------------------------------------------- # + menu = create_region(world, "Menu") + spaceship = create_region_and_connect(world, "Spaceship", "Save File -> Spaceship", menu) + + # we only need the menu and the spaceship regions + if world.is_dw_only(): + return + + create_rift_connections(world, create_region(world, "Time Rift - Gallery")) + create_rift_connections(world, create_region(world, "Time Rift - The Lab")) + + # ------------------------------------------- MAFIA TOWN ------------------------------------------- # + mafia_town = create_region_and_connect(world, "Mafia Town", "Telescope -> Mafia Town", spaceship) + mt_act1 = create_region_and_connect(world, "Welcome to Mafia Town", "Mafia Town - Act 1", mafia_town) + mt_act2 = create_region_and_connect(world, "Barrel Battle", "Mafia Town - Act 2", mafia_town) + mt_act3 = create_region_and_connect(world, "She Came from Outer Space", "Mafia Town - Act 3", mafia_town) + mt_act4 = create_region_and_connect(world, "Down with the Mafia!", "Mafia Town - Act 4", mafia_town) + mt_act6 = create_region_and_connect(world, "Heating Up Mafia Town", "Mafia Town - Act 6", mafia_town) + mt_act5 = create_region_and_connect(world, "Cheating the Race", "Mafia Town - Act 5", mafia_town) + mt_act7 = create_region_and_connect(world, "The Golden Vault", "Mafia Town - Act 7", mafia_town) + + # ------------------------------------------- BOTB ------------------------------------------------- # + botb = create_region_and_connect(world, "Battle of the Birds", "Telescope -> Battle of the Birds", spaceship) + dbs = create_region_and_connect(world, "Dead Bird Studio", "Battle of the Birds - Act 1", botb) + create_region_and_connect(world, "Murder on the Owl Express", "Battle of the Birds - Act 2", botb) + pp = create_region_and_connect(world, "Picture Perfect", "Battle of the Birds - Act 3", botb) + tr = create_region_and_connect(world, "Train Rush", "Battle of the Birds - Act 4", botb) + create_region_and_connect(world, "The Big Parade", "Battle of the Birds - Act 5", botb) + create_region_and_connect(world, "Award Ceremony", "Battle of the Birds - Finale A", botb) + basement = create_region_and_connect(world, "Dead Bird Studio Basement", "Battle of the Birds - Finale B", botb) + create_rift_connections(world, create_region(world, "Time Rift - Dead Bird Studio")) + create_rift_connections(world, create_region(world, "Time Rift - The Owl Express")) + create_rift_connections(world, create_region(world, "Time Rift - The Moon")) + + # Items near the Dead Bird Studio elevator can be reached from the basement act, and beyond in Expert + ev_area = create_region_and_connect(world, "Dead Bird Studio - Elevator Area", "DBS -> Elevator Area", dbs) + post_ev = create_region_and_connect(world, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs) + basement.connect(ev_area, "DBS Basement -> Elevator Area") + if world.options.LogicDifficulty >= int(Difficulty.EXPERT): + basement.connect(post_ev, "DBS Basement -> Post Elevator Area") + + # ------------------------------------------- SUBCON FOREST --------------------------------------- # + subcon_forest = create_region_and_connect(world, "Subcon Forest", "Telescope -> Subcon Forest", spaceship) + sf_act1 = create_region_and_connect(world, "Contractual Obligations", "Subcon Forest - Act 1", subcon_forest) + sf_act2 = create_region_and_connect(world, "The Subcon Well", "Subcon Forest - Act 2", subcon_forest) + sf_act3 = create_region_and_connect(world, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest) + sf_act4 = create_region_and_connect(world, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest) + sf_act5 = create_region_and_connect(world, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest) + create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest) + + # ------------------------------------------- ALPINE SKYLINE ------------------------------------------ # + alpine_skyline = create_region_and_connect(world, "Alpine Skyline", "Telescope -> Alpine Skyline", spaceship) + alpine_freeroam = create_region_and_connect(world, "Alpine Free Roam", "Alpine Skyline - Free Roam", alpine_skyline) + alpine_area = create_region_and_connect(world, "Alpine Skyline Area", "AFR -> Alpine Skyline Area", alpine_freeroam) + + # Needs to be separate because there are a lot of locations in Alpine that can't be accessed from Illness + alpine_area_tihs = create_region_and_connect(world, "Alpine Skyline Area (TIHS)", "-> Alpine Skyline Area (TIHS)", + alpine_area) + + create_region_and_connect(world, "The Birdhouse", "-> The Birdhouse", alpine_area) + create_region_and_connect(world, "The Lava Cake", "-> The Lava Cake", alpine_area) + create_region_and_connect(world, "The Windmill", "-> The Windmill", alpine_area) + create_region_and_connect(world, "The Twilight Bell", "-> The Twilight Bell", alpine_area) + + illness = create_region_and_connect(world, "The Illness has Spread", "Alpine Skyline - Finale", alpine_skyline) + illness.connect(alpine_area_tihs, "TIHS -> Alpine Skyline Area (TIHS)") + create_rift_connections(world, create_region(world, "Time Rift - Alpine Skyline")) + create_rift_connections(world, create_region(world, "Time Rift - The Twilight Bell")) + create_rift_connections(world, create_region(world, "Time Rift - Curly Tail Trail")) + + # ------------------------------------------- OTHER -------------------------------------------------- # + mt_area: Region = create_region(world, "Mafia Town Area") + mt_area_humt: Region = create_region(world, "Mafia Town Area (HUMT)") + mt_area.connect(mt_area_humt, "MT Area -> MT Area (HUMT)") + mt_act1.connect(mt_area, "Mafia Town Entrance WTMT") + mt_act2.connect(mt_area, "Mafia Town Entrance BB") + mt_act3.connect(mt_area, "Mafia Town Entrance SCFOS") + mt_act4.connect(mt_area, "Mafia Town Entrance DWTM") + mt_act5.connect(mt_area, "Mafia Town Entrance CTR") + mt_act6.connect(mt_area_humt, "Mafia Town Entrance HUMT") + mt_act7.connect(mt_area, "Mafia Town Entrance TGV") + + create_rift_connections(world, create_region(world, "Time Rift - Mafia of Cooks")) + create_rift_connections(world, create_region(world, "Time Rift - Sewers")) + create_rift_connections(world, create_region(world, "Time Rift - Bazaar")) + + sf_area: Region = create_region(world, "Subcon Forest Area") + sf_act1.connect(sf_area, "Subcon Forest Entrance CO") + sf_act2.connect(sf_area, "Subcon Forest Entrance SW") + sf_act3.connect(sf_area, "Subcon Forest Entrance TOD") + sf_act4.connect(sf_area, "Subcon Forest Entrance QVM") + sf_act5.connect(sf_area, "Subcon Forest Entrance MDS") + + create_rift_connections(world, create_region(world, "Time Rift - Sleepy Subcon")) + create_rift_connections(world, create_region(world, "Time Rift - Pipe")) + create_rift_connections(world, create_region(world, "Time Rift - Village")) + + badge_seller = create_badge_seller(world) + mt_area.connect(badge_seller, "MT Area -> Badge Seller") + mt_area_humt.connect(badge_seller, "MT Area (HUMT) -> Badge Seller") + sf_area.connect(badge_seller, "SF Area -> Badge Seller") + dbs.connect(badge_seller, "DBS -> Badge Seller") + pp.connect(badge_seller, "PP -> Badge Seller") + tr.connect(badge_seller, "TR -> Badge Seller") + alpine_area_tihs.connect(badge_seller, "ASA -> Badge Seller") + + times_end = create_region_and_connect(world, "Time's End", "Telescope -> Time's End", spaceship) + create_region_and_connect(world, "The Finale", "Time's End - Act 1", times_end) + + # ------------------------------------------- DLC1 ------------------------------------------------- # + if world.is_dlc1(): + arctic_cruise = create_region_and_connect(world, "The Arctic Cruise", "Telescope -> Arctic Cruise", spaceship) + cruise_ship = create_region(world, "Cruise Ship") + + ac_act1 = create_region_and_connect(world, "Bon Voyage!", "The Arctic Cruise - Act 1", arctic_cruise) + ac_act2 = create_region_and_connect(world, "Ship Shape", "The Arctic Cruise - Act 2", arctic_cruise) + ac_act3 = create_region_and_connect(world, "Rock the Boat", "The Arctic Cruise - Finale", arctic_cruise) + + ac_act1.connect(cruise_ship, "Cruise Ship Entrance BV") + ac_act2.connect(cruise_ship, "Cruise Ship Entrance SS") + ac_act3.connect(cruise_ship, "Cruise Ship Entrance RTB") + create_rift_connections(world, create_region(world, "Time Rift - Balcony")) + create_rift_connections(world, create_region(world, "Time Rift - Deep Sea")) + + if not world.options.ExcludeTour: + create_rift_connections(world, create_region(world, "Time Rift - Tour")) + + if world.options.Tasksanity: + create_tasksanity_locations(world) + + cruise_ship.connect(badge_seller, "CS -> Badge Seller") + + if world.is_dlc2(): + nyakuza = create_region_and_connect(world, "Nyakuza Metro", "Telescope -> Nyakuza Metro", spaceship) + metro_freeroam = create_region_and_connect(world, "Nyakuza Free Roam", "Nyakuza Metro - Free Roam", nyakuza) + create_region_and_connect(world, "Rush Hour", "Nyakuza Metro - Finale", nyakuza) + + yellow = create_region_and_connect(world, "Yellow Overpass Station", "-> Yellow Overpass Station", metro_freeroam) + green = create_region_and_connect(world, "Green Clean Station", "-> Green Clean Station", metro_freeroam) + pink = create_region_and_connect(world, "Pink Paw Station", "-> Pink Paw Station", metro_freeroam) + create_region_and_connect(world, "Bluefin Tunnel", "-> Bluefin Tunnel", metro_freeroam) # No manhole + + create_region_and_connect(world, "Yellow Overpass Manhole", "-> Yellow Overpass Manhole", yellow) + create_region_and_connect(world, "Green Clean Manhole", "-> Green Clean Manhole", green) + create_region_and_connect(world, "Pink Paw Manhole", "-> Pink Paw Manhole", pink) + + create_rift_connections(world, create_region(world, "Time Rift - Rumbi Factory")) + create_thug_shops(world) + + +def create_rift_connections(world: "HatInTimeWorld", region: Region): + for i, name in enumerate(rift_access_regions[region.name]): + act_region = world.multiworld.get_region(name, world.player) + entrance_name = f"{region.name} Portal - Entrance {i+1}" + act_region.connect(region, entrance_name) + + +def create_tasksanity_locations(world: "HatInTimeWorld"): + ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player) + id_start: int = TASKSANITY_START_ID + for i in range(world.options.TasksanityCheckCount): + location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape) + ship_shape.locations.append(location) + + +def randomize_act_entrances(world: "HatInTimeWorld"): + region_list: List[Region] = get_shuffleable_act_regions(world) + world.random.shuffle(region_list) + region_list.sort(key=sort_acts) + candidate_list: List[Region] = region_list.copy() + rift_dict: Dict[str, Region] = {} + + # Check if Plando's are valid, if so, map them + if world.options.ActPlando: + player_name = world.multiworld.get_player_name(world.player) + for (name1, name2) in world.options.ActPlando.items(): + region: Region + act: Region + try: + region = world.multiworld.get_region(name1, world.player) + except KeyError: + print(f"ActPlando ({player_name}) - " + f"Act \"{name1}\" does not exist in the multiworld. " + f"Possible reasons are typos, case-sensitivity, or DLC options.") + continue + + try: + act = world.multiworld.get_region(name2, world.player) + except KeyError: + print(f"ActPlando ({player_name}) - " + f"Act \"{name2}\" does not exist in the multiworld. " + f"Possible reasons are typos, case-sensitivity, or DLC options.") + continue + + if is_valid_plando(world, region.name, act.name): + region_list.remove(region) + candidate_list.remove(act) + connect_acts(world, region, act, rift_dict) + else: + print(f"ActPlando " + f"({player_name}) - " + f"\"{name1}: {name2}\" " + f"is an invalid or disallowed act plando combination!") + + # Decide what should be on the first few levels before randomizing the rest + first_acts: List[Region] = [] + first_chapter_name = chapter_regions[ChapterIndex(world.options.StartingChapter)] + first_acts.append(get_act_by_number(world, first_chapter_name, 1)) + # Chapter 3 and 4 only have one level accessible at the start + if first_chapter_name == "Mafia Town" or first_chapter_name == "Battle of the Birds": + first_acts.append(get_act_by_number(world, first_chapter_name, 2)) + first_acts.append(get_act_by_number(world, first_chapter_name, 3)) + + valid_first_acts: List[Region] = [] + for candidate in candidate_list: + if is_valid_first_act(world, candidate): + valid_first_acts.append(candidate) + + total_locations = 0 + for level in first_acts: + if level not in region_list: # make sure it hasn't been plando'd + continue + + candidate = valid_first_acts[world.random.randint(0, len(valid_first_acts)-1)] + region_list.remove(level) + candidate_list.remove(candidate) + valid_first_acts.remove(candidate) + connect_acts(world, level, candidate, rift_dict) + + # Only allow one purple rift + if candidate.name in purple_time_rifts: + for act in reversed(valid_first_acts): + if act.name in purple_time_rifts: + valid_first_acts.remove(act) + + total_locations += get_region_location_count(world, candidate.name) + if "Time Rift" not in candidate.name: + chapter = act_chapters.get(candidate.name) + if chapter == "Mafia Town": + total_locations += get_region_location_count(world, "Mafia Town Area (HUMT)") + if candidate.name != "Heating Up Mafia Town": + total_locations += get_region_location_count(world, "Mafia Town Area") + elif chapter == "Subcon Forest": + total_locations += get_region_location_count(world, "Subcon Forest Area") + elif chapter == "The Arctic Cruise": + total_locations += get_region_location_count(world, "Cruise Ship") + + # If we have enough Sphere 1 locations, we can allow the rest to be randomized + if total_locations >= MIN_FIRST_SPHERE_LOCATIONS: + break + + ignore_certain_rules: bool = False + while len(region_list) > 0: + region = region_list[0] + candidate: Region + valid_candidates: List[Region] = [] + + # Look for candidates to map this act to + for c in candidate_list: + if is_valid_act_combo(world, region, c, ignore_certain_rules): + valid_candidates.append(c) + + if len(valid_candidates) > 0: + candidate = valid_candidates[world.random.randint(0, len(valid_candidates)-1)] + else: + # If we fail here, try again with less shuffle rules. If we still somehow fail, there's an issue for sure + if ignore_certain_rules: + raise Exception(f"Failed to find act shuffle candidate for {region}" + f"\nRemaining acts to map to: {region_list}" + f"\nRemaining candidates: {candidate_list}") + + ignore_certain_rules = True + continue + + ignore_certain_rules = False + region_list.remove(region) + candidate_list.remove(candidate) + connect_acts(world, region, candidate, rift_dict) + + for name in blacklisted_acts.values(): + region: Region = world.multiworld.get_region(name, world.player) + update_chapter_act_info(world, region, region) + + set_rift_rules(world, rift_dict) + + +# Try to do levels that may have specific mapping rules first +def sort_acts(act: Region) -> int: + if "Time Rift" in act.name: + return -5 + + if act.name in chapter_finales: + return -4 + + # Free Roam + if (act_chapters[act.name] == "Alpine Skyline" or act_chapters[act.name] == "Nyakuza Metro") \ + and "Time Rift" not in act.name: + return -3 + + if act.name == "Contractual Obligations" or act.name == "The Subcon Well": + return -2 + + world = act.multiworld.worlds[act.player] + blacklist = world.options.ActBlacklist + if len(blacklist) > 0: + for name, act_list in blacklist.items(): + if act.name == name or act.name in act_list: + return -1 + + return 0 + + +def connect_acts(world: "HatInTimeWorld", entrance_act: Region, exit_act: Region, rift_dict: Dict[str, Region]): + # Vanilla + if exit_act.name == entrance_act.name: + if entrance_act.name in rift_access_regions.keys(): + rift_dict.setdefault(entrance_act.name, exit_act) + + update_chapter_act_info(world, entrance_act, exit_act) + return + + if entrance_act.name in rift_access_regions.keys(): + connect_time_rift(world, entrance_act, exit_act) + rift_dict.setdefault(entrance_act.name, exit_act) + else: + if exit_act.name in rift_access_regions.keys(): + for e in exit_act.entrances.copy(): + e.parent_region.exits.remove(e) + e.connected_region.entrances.remove(e) + + entrance = world.multiworld.get_entrance(act_entrances[entrance_act.name], world.player) + chapter = world.multiworld.get_region(act_chapters[entrance_act.name], world.player) + reconnect_regions(entrance, chapter, exit_act) + + update_chapter_act_info(world, entrance_act, exit_act) + + +def is_valid_act_combo(world: "HatInTimeWorld", entrance_act: Region, + exit_act: Region, ignore_certain_rules: bool = False) -> bool: + + # Ignore certain rules that aren't to prevent impossible combos. This is needed for ActPlando. + if not ignore_certain_rules: + if world.options.ActRandomizer == ActRandomizer.option_light and not ignore_certain_rules: + # Don't map Time Rifts to normal acts + if "Time Rift" in entrance_act.name and "Time Rift" not in exit_act.name: + return False + + # Don't map normal acts to Time Rifts + if "Time Rift" not in entrance_act.name and "Time Rift" in exit_act.name: + return False + + # Separate purple rifts + if entrance_act.name in purple_time_rifts and exit_act.name not in purple_time_rifts \ + or entrance_act.name not in purple_time_rifts and exit_act.name in purple_time_rifts: + return False + + if world.options.FinaleShuffle and entrance_act.name in chapter_finales: + if exit_act.name not in chapter_finales: + return False + + exit_chapter: str = act_chapters.get(exit_act.name) + # make sure that certain time rift combinations never happen + always_block: bool = exit_chapter != "Mafia Town" and exit_chapter != "Subcon Forest" + if not ignore_certain_rules or always_block: + if entrance_act.name in rift_access_regions and exit_act.name in rift_access_regions[entrance_act.name]: + return False + + # Blacklisted? + if entrance_act.name in blacklisted_combos.keys() and exit_act.name in blacklisted_combos[entrance_act.name]: + return False + + if world.options.ActBlacklist: + act_blacklist = world.options.ActBlacklist.get(entrance_act.name) + if act_blacklist is not None and exit_act.name in act_blacklist: + return False + + # Prevent Contractual Obligations from being inaccessible if contracts are not shuffled + if not world.options.ShuffleActContracts: + if (entrance_act.name == "Your Contract has Expired" or entrance_act.name == "The Subcon Well") \ + and exit_act.name == "Contractual Obligations": + return False + + return True + + +def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool: + if act.name not in guaranteed_first_acts: + return False + + if world.options.ActRandomizer == ActRandomizer.option_light and "Time Rift" in act.name: + return False + + # If there's only a single level in the starting chapter, only allow Mafia Town or Subcon Forest levels + start_chapter = world.options.StartingChapter + if start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON: + if "Time Rift" in act.name: + return False + + if act_chapters[act.name] != "Mafia Town" and act_chapters[act.name] != "Subcon Forest": + return False + + if act.name in purple_time_rifts and not world.options.ShuffleStorybookPages: + return False + + diff = get_difficulty(world) + # Not completable without Umbrella? + if world.options.UmbrellaLogic: + # Needs to be at least moderate to cross the big dweller wall + if act.name == "Queen Vanessa's Manor" and diff < Difficulty.MODERATE: + return False + elif act.name == "Heating Up Mafia Town": # Straight up impossible + return False + + # Need to be able to hover + if act.name == "Your Contract has Expired": + if diff < Difficulty.EXPERT or world.options.ShuffleSubconPaintings and world.options.NoPaintingSkips: + return False + + if act.name == "Dead Bird Studio": + # No umbrella logic = moderate, umbrella logic = expert. + if diff < Difficulty.MODERATE or world.options.UmbrellaLogic and diff < Difficulty.EXPERT: + return False + elif act.name == "Dead Bird Studio Basement" and (diff < Difficulty.EXPERT or world.options.FinaleShuffle): + return False + elif act.name == "Rock the Boat" and (diff < Difficulty.MODERATE or world.options.FinaleShuffle): + return False + elif act.name == "The Subcon Well" and diff < Difficulty.MODERATE: + return False + elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings: + return False + + if world.options.ShuffleSubconPaintings and "Time Rift" not in act.name \ + and act_chapters.get(act.name, "") == "Subcon Forest": + # Only allow Subcon levels if painting skips are allowed + if diff < Difficulty.MODERATE or world.options.NoPaintingSkips: + return False + + return True + + +def connect_time_rift(world: "HatInTimeWorld", time_rift: Region, exit_region: Region): + i = 1 + while i <= len(rift_access_regions[time_rift.name]): + name = f"{time_rift.name} Portal - Entrance {i}" + entrance: Entrance + try: + entrance = world.multiworld.get_entrance(name, world.player) + reconnect_regions(entrance, entrance.parent_region, exit_region) + except KeyError: + time_rift.connect(exit_region, name) + + i += 1 + + +def get_shuffleable_act_regions(world: "HatInTimeWorld") -> List[Region]: + act_list: List[Region] = [] + for region in world.multiworld.get_regions(world.player): + if region.name in chapter_act_info.keys(): + if not is_act_blacklisted(world, region.name): + act_list.append(region) + + return act_list + + +def is_act_blacklisted(world: "HatInTimeWorld", name: str) -> bool: + act_plando = world.options.ActPlando + plando: bool = name in act_plando.keys() and is_valid_plando(world, name, act_plando[name]) + if not plando and name in act_plando.values(): + for key in act_plando.keys(): + if act_plando[key] == name and is_valid_plando(world, key, name): + plando = True + break + + if name == "The Finale": + return not plando and world.options.EndGoal == EndGoal.option_finale + + if name == "Rush Hour": + return not plando and world.options.EndGoal == EndGoal.option_rush_hour + + if name == "Time Rift - Tour": + return bool(world.options.ExcludeTour) + + return name in blacklisted_acts.values() + + +def is_valid_plando(world: "HatInTimeWorld", region: str, act: str) -> bool: + # Duplicated keys will throw an exception for us, but we still need to check for duplicated values + found_count = 0 + for val in world.options.ActPlando.values(): + if val == act: + found_count += 1 + + if found_count > 1: + raise Exception(f"ActPlando ({world.multiworld.get_player_name(world.player)}) - " + f"Duplicated act plando mapping found for act: \"{act}\"") + + if region in blacklisted_acts.values() or (region not in act_entrances.keys() and "Time Rift" not in region): + return False + + if act in blacklisted_acts.values() or (act not in act_entrances.keys() and "Time Rift" not in act): + return False + + # Don't allow plando-ing things onto the first act that aren't permitted + entrance_name = act_entrances.get(region, "") + if entrance_name != "": + is_first_act: bool = act_chapters.get(region) == get_first_chapter_region(world).name \ + and ("Act 1" in entrance_name or "Free Roam" in entrance_name) + + if is_first_act and not is_valid_first_act(world, world.multiworld.get_region(act, world.player)): + return False + + # Don't allow straight up impossible mappings + if (region == "Time Rift - Curly Tail Trail" + or region == "Time Rift - The Twilight Bell" + or region == "The Illness has Spread") \ + and act == "Alpine Free Roam": + return False + + if (region == "Rush Hour" or region == "Time Rift - Rumbi Factory") and act == "Nyakuza Free Roam": + return False + + if region == "Time Rift - The Owl Express" and act == "Murder on the Owl Express": + return False + + if region == "Time Rift - Deep Sea" and act == "Bon Voyage!": + return False + + return any(a.name == world.options.ActPlando.get(region) for a in world.multiworld.get_regions(world.player)) + + +def create_region(world: "HatInTimeWorld", name: str) -> Region: + reg = Region(name, world.player, world.multiworld) + + for (key, data) in location_table.items(): + if world.is_dw_only(): + break + + if data.nyakuza_thug != "": + continue + + if data.region == name: + if key in storybook_pages.keys() and not world.options.ShuffleStorybookPages: + continue + + location = HatInTimeLocation(world.player, key, data.id, reg) + reg.locations.append(location) + if location.name in shop_locations: + world.shop_locs.append(location.name) + + world.multiworld.regions.append(reg) + return reg + + +def create_badge_seller(world: "HatInTimeWorld") -> Region: + badge_seller = Region("Badge Seller", world.player, world.multiworld) + world.multiworld.regions.append(badge_seller) + count = 0 + max_items = 0 + + if world.options.BadgeSellerMaxItems > 0: + max_items = world.random.randint(world.options.BadgeSellerMinItems.value, + world.options.BadgeSellerMaxItems.value) + + if max_items <= 0: + world.badge_seller_count = 0 + return badge_seller + + for (key, data) in shop_locations.items(): + if "Badge Seller" not in key: + continue + + location = HatInTimeLocation(world.player, key, data.id, badge_seller) + badge_seller.locations.append(location) + world.shop_locs.append(location.name) + + count += 1 + if count >= max_items: + break + + world.badge_seller_count = max_items + return badge_seller + + +# Takes an entrance, removes its old connections, and reconnects it between the two regions specified. +def reconnect_regions(entrance: Entrance, start_region: Region, exit_region: Region): + if entrance in entrance.connected_region.entrances: + entrance.connected_region.entrances.remove(entrance) + + if entrance in entrance.parent_region.exits: + entrance.parent_region.exits.remove(entrance) + + if entrance in start_region.exits: + start_region.exits.remove(entrance) + + if entrance in exit_region.entrances: + exit_region.entrances.remove(entrance) + + entrance.parent_region = start_region + start_region.exits.append(entrance) + entrance.connect(exit_region) + + +def create_region_and_connect(world: "HatInTimeWorld", + name: str, entrancename: str, connected_region: Region, is_exit: bool = True) -> Region: + + reg: Region = create_region(world, name) + entrance_region: Region + exit_region: Region + + if is_exit: + entrance_region = connected_region + exit_region = reg + else: + entrance_region = reg + exit_region = connected_region + + entrance_region.connect(exit_region, entrancename) + return reg + + +def get_first_chapter_region(world: "HatInTimeWorld") -> Region: + start_chapter: ChapterIndex = ChapterIndex(world.options.StartingChapter) + return world.multiworld.get_region(chapter_regions.get(start_chapter), world.player) + + +def get_act_original_chapter(world: "HatInTimeWorld", act_name: str) -> Region: + return world.multiworld.get_region(act_chapters[act_name], world.player) + + +# Sets an act entrance in slot data by specifying the Hat_ChapterActInfo, to be used in-game +def update_chapter_act_info(world: "HatInTimeWorld", original_region: Region, new_region: Region): + original_act_info = chapter_act_info[original_region.name] + new_act_info = chapter_act_info[new_region.name] + world.act_connections[original_act_info] = new_act_info + + +def get_shuffled_region(world: "HatInTimeWorld", region: str) -> str: + ci: str = chapter_act_info[region] + for key, val in world.act_connections.items(): + if val == ci: + for name in chapter_act_info.keys(): + if chapter_act_info[name] == key: + return name + + +def get_region_location_count(world: "HatInTimeWorld", region_name: str, included_only: bool = True) -> int: + count = 0 + region = world.multiworld.get_region(region_name, world.player) + for loc in region.locations: + if loc.address is not None and (not included_only or loc.progress_type is not LocationProgressType.EXCLUDED): + count += 1 + + return count + + +def get_act_by_number(world: "HatInTimeWorld", chapter_name: str, num: int) -> Region: + chapter = world.multiworld.get_region(chapter_name, world.player) + act: Optional[Region] = None + for e in chapter.exits: + if f"Act {num}" in e.name or num == 1 and "Free Roam" in e.name: + act = e.connected_region + break + + return act + + +def create_thug_shops(world: "HatInTimeWorld"): + min_items: int = world.options.NyakuzaThugMinShopItems.value + max_items: int = world.options.NyakuzaThugMaxShopItems.value + count = -1 + step = 0 + old_name = "" + + for key, data in shop_locations.items(): + if data.nyakuza_thug == "": + continue + + if old_name != "" and old_name == data.nyakuza_thug: + continue + + try: + if world.nyakuza_thug_items[data.nyakuza_thug] <= 0: + continue + except KeyError: + pass + + if count == -1: + count = world.random.randint(min_items, max_items) + world.nyakuza_thug_items.setdefault(data.nyakuza_thug, count) + if count <= 0: + continue + + if count >= 1: + region = world.multiworld.get_region(data.region, world.player) + loc = HatInTimeLocation(world.player, key, data.id, region) + region.locations.append(loc) + world.shop_locs.append(loc.name) + + step += 1 + if step >= count: + old_name = data.nyakuza_thug + step = 0 + count = -1 + + +def create_events(world: "HatInTimeWorld") -> int: + count = 0 + + for (name, data) in event_locs.items(): + if not is_location_valid(world, name): + continue + + item_name: str = name + if world.is_dw(): + if name in snatcher_coins.keys(): + item_name = data.snatcher_coin + elif name in zero_jumps: + if get_difficulty(world) < Difficulty.HARD and name in zero_jumps_hard: + continue + + if get_difficulty(world) < Difficulty.EXPERT and name in zero_jumps_expert: + continue + + event: Location = create_event(name, item_name, world.multiworld.get_region(data.region, world.player), world) + event.show_in_spoiler = False + count += 1 + + return count + + +def create_event(name: str, item_name: str, region: Region, world: "HatInTimeWorld") -> Location: + event = HatInTimeLocation(world.player, name, None, region) + region.locations.append(event) + event.place_locked_item(HatInTimeItem(item_name, ItemClassification.progression, None, world.player)) + return event diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py new file mode 100644 index 000000000000..b716b793a797 --- /dev/null +++ b/worlds/ahit/Rules.py @@ -0,0 +1,958 @@ +from worlds.AutoWorld import CollectionState +from worlds.generic.Rules import add_rule, set_rule +from .Locations import location_table, zipline_unlocks, is_location_valid, shop_locations, event_locs +from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty, HitType +from BaseClasses import Location, Entrance, Region +from typing import TYPE_CHECKING, List, Callable, Union, Dict +from .Options import EndGoal, CTRLogic, NoTicketSkips + +if TYPE_CHECKING: + from . import HatInTimeWorld + + +act_connections = { + "Mafia Town - Act 2": ["Mafia Town - Act 1"], + "Mafia Town - Act 3": ["Mafia Town - Act 1"], + "Mafia Town - Act 4": ["Mafia Town - Act 2", "Mafia Town - Act 3"], + "Mafia Town - Act 6": ["Mafia Town - Act 4"], + "Mafia Town - Act 7": ["Mafia Town - Act 4"], + "Mafia Town - Act 5": ["Mafia Town - Act 6", "Mafia Town - Act 7"], + + "Battle of the Birds - Act 2": ["Battle of the Birds - Act 1"], + "Battle of the Birds - Act 3": ["Battle of the Birds - Act 1"], + "Battle of the Birds - Act 4": ["Battle of the Birds - Act 2", "Battle of the Birds - Act 3"], + "Battle of the Birds - Act 5": ["Battle of the Birds - Act 2", "Battle of the Birds - Act 3"], + "Battle of the Birds - Finale A": ["Battle of the Birds - Act 4", "Battle of the Birds - Act 5"], + "Battle of the Birds - Finale B": ["Battle of the Birds - Finale A"], + + "Subcon Forest - Finale": ["Subcon Forest - Act 1", "Subcon Forest - Act 2", + "Subcon Forest - Act 3", "Subcon Forest - Act 4", + "Subcon Forest - Act 5"], + + "The Arctic Cruise - Act 2": ["The Arctic Cruise - Act 1"], + "The Arctic Cruise - Finale": ["The Arctic Cruise - Act 2"], +} + + +def can_use_hat(state: CollectionState, world: "HatInTimeWorld", hat: HatType) -> bool: + if world.options.HatItems: + return state.has(hat_type_to_item[hat], world.player) + + if world.hat_yarn_costs[hat] <= 0: # this means the hat was put into starting inventory + return True + + return state.has("Yarn", world.player, get_hat_cost(world, hat)) + + +def get_hat_cost(world: "HatInTimeWorld", hat: HatType) -> int: + cost = 0 + for h in world.hat_craft_order: + cost += world.hat_yarn_costs[h] + if h == hat: + break + + return cost + + +def painting_logic(world: "HatInTimeWorld") -> bool: + return bool(world.options.ShuffleSubconPaintings) + + +# -1 = Normal, 0 = Moderate, 1 = Hard, 2 = Expert +def get_difficulty(world: "HatInTimeWorld") -> Difficulty: + return Difficulty(world.options.LogicDifficulty) + + +def has_paintings(state: CollectionState, world: "HatInTimeWorld", count: int, allow_skip: bool = True) -> bool: + if not painting_logic(world): + return True + + if not world.options.NoPaintingSkips and allow_skip: + # In Moderate there is a very easy trick to skip all the walls, except for the one guarding the boss arena + if get_difficulty(world) >= Difficulty.MODERATE: + return True + + return state.has("Progressive Painting Unlock", world.player, count) + + +def zipline_logic(world: "HatInTimeWorld") -> bool: + return bool(world.options.ShuffleAlpineZiplines) + + +def can_use_hookshot(state: CollectionState, world: "HatInTimeWorld"): + return state.has("Hookshot Badge", world.player) + + +def can_hit(state: CollectionState, world: "HatInTimeWorld", umbrella_only: bool = False): + if not world.options.UmbrellaLogic: + return True + + return state.has("Umbrella", world.player) or not umbrella_only and can_use_hat(state, world, HatType.BREWING) + + +def has_relic_combo(state: CollectionState, world: "HatInTimeWorld", relic: str) -> bool: + return state.has_group(relic, world.player, len(world.item_name_groups[relic])) + + +def get_relic_count(state: CollectionState, world: "HatInTimeWorld", relic: str) -> int: + return state.count_group(relic, world.player) + + +# This is used to determine if the player can clear an act that's required to unlock a Time Rift +def can_clear_required_act(state: CollectionState, world: "HatInTimeWorld", act_entrance: str) -> bool: + entrance: Entrance = world.multiworld.get_entrance(act_entrance, world.player) + if not state.can_reach(entrance.connected_region, "Region", world.player): + return False + + if "Free Roam" in entrance.connected_region.name: + return True + + name: str = f"Act Completion ({entrance.connected_region.name})" + return world.multiworld.get_location(name, world.player).access_rule(state) + + +def can_clear_alpine(state: CollectionState, world: "HatInTimeWorld") -> bool: + return state.has("Birdhouse Cleared", world.player) and state.has("Lava Cake Cleared", world.player) \ + and state.has("Windmill Cleared", world.player) and state.has("Twilight Bell Cleared", world.player) + + +def can_clear_metro(state: CollectionState, world: "HatInTimeWorld") -> bool: + return state.has("Nyakuza Intro Cleared", world.player) \ + and state.has("Yellow Overpass Station Cleared", world.player) \ + and state.has("Yellow Overpass Manhole Cleared", world.player) \ + and state.has("Green Clean Station Cleared", world.player) \ + and state.has("Green Clean Manhole Cleared", world.player) \ + and state.has("Bluefin Tunnel Cleared", world.player) \ + and state.has("Pink Paw Station Cleared", world.player) \ + and state.has("Pink Paw Manhole Cleared", world.player) + + +def set_rules(world: "HatInTimeWorld"): + # First, chapter access + starting_chapter = ChapterIndex(world.options.StartingChapter) + world.chapter_timepiece_costs[starting_chapter] = 0 + + # Chapter costs increase progressively. Randomly decide the chapter order, except for Finale + chapter_list: List[ChapterIndex] = [ChapterIndex.MAFIA, ChapterIndex.BIRDS, + ChapterIndex.SUBCON, ChapterIndex.ALPINE] + + final_chapter = ChapterIndex.FINALE + if world.options.EndGoal == EndGoal.option_rush_hour: + final_chapter = ChapterIndex.METRO + chapter_list.append(ChapterIndex.FINALE) + elif world.options.EndGoal == EndGoal.option_seal_the_deal: + final_chapter = None + chapter_list.append(ChapterIndex.FINALE) + + if world.is_dlc1(): + chapter_list.append(ChapterIndex.CRUISE) + + if world.is_dlc2() and final_chapter != ChapterIndex.METRO: + chapter_list.append(ChapterIndex.METRO) + + chapter_list.remove(starting_chapter) + world.random.shuffle(chapter_list) + + # Make sure Alpine is unlocked before any DLC chapters are, as the Alpine door needs to be open to access them + if starting_chapter != ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()): + index1 = 69 + index2 = 69 + pos: int + lowest_index: int + chapter_list.remove(ChapterIndex.ALPINE) + + if world.is_dlc1(): + index1 = chapter_list.index(ChapterIndex.CRUISE) + + if world.is_dlc2() and final_chapter != ChapterIndex.METRO: + index2 = chapter_list.index(ChapterIndex.METRO) + + lowest_index = min(index1, index2) + if lowest_index == 0: + pos = 0 + else: + pos = world.random.randint(0, lowest_index) + + chapter_list.insert(pos, ChapterIndex.ALPINE) + + lowest_cost: int = world.options.LowestChapterCost.value + highest_cost: int = world.options.HighestChapterCost.value + cost_increment: int = world.options.ChapterCostIncrement.value + min_difference: int = world.options.ChapterCostMinDifference.value + last_cost = 0 + + for i, chapter in enumerate(chapter_list): + min_range: int = lowest_cost + (cost_increment * i) + if min_range >= highest_cost: + min_range = highest_cost-1 + + value: int = world.random.randint(min_range, min(highest_cost, max(lowest_cost, last_cost + cost_increment))) + cost = world.random.randint(value, min(value + cost_increment, highest_cost)) + if i >= 1: + if last_cost + min_difference > cost: + cost = last_cost + min_difference + + cost = min(cost, highest_cost) + world.chapter_timepiece_costs[chapter] = cost + last_cost = cost + + if final_chapter is not None: + final_chapter_cost: int + if world.options.FinalChapterMinCost == world.options.FinalChapterMaxCost: + final_chapter_cost = world.options.FinalChapterMaxCost.value + else: + final_chapter_cost = world.random.randint(world.options.FinalChapterMinCost.value, + world.options.FinalChapterMaxCost.value) + + world.chapter_timepiece_costs[final_chapter] = final_chapter_cost + + add_rule(world.multiworld.get_entrance("Telescope -> Mafia Town", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.MAFIA])) + + add_rule(world.multiworld.get_entrance("Telescope -> Battle of the Birds", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS])) + + add_rule(world.multiworld.get_entrance("Telescope -> Subcon Forest", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.SUBCON])) + + add_rule(world.multiworld.get_entrance("Telescope -> Alpine Skyline", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE])) + + add_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.FINALE]) + and can_use_hat(state, world, HatType.BREWING) and can_use_hat(state, world, HatType.DWELLER)) + + if world.is_dlc1(): + add_rule(world.multiworld.get_entrance("Telescope -> Arctic Cruise", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE]) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.CRUISE])) + + if world.is_dlc2(): + add_rule(world.multiworld.get_entrance("Telescope -> Nyakuza Metro", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE]) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.METRO]) + and can_use_hat(state, world, HatType.DWELLER) and can_use_hat(state, world, HatType.ICE)) + + if not world.options.ActRandomizer: + set_default_rift_rules(world) + + table = {**location_table, **event_locs} + for (key, data) in table.items(): + if not is_location_valid(world, key): + continue + + loc = world.multiworld.get_location(key, world.player) + + for hat in data.required_hats: + add_rule(loc, lambda state, h=hat: can_use_hat(state, world, h)) + + if data.hookshot: + add_rule(loc, lambda state: can_use_hookshot(state, world)) + + if data.paintings > 0 and world.options.ShuffleSubconPaintings: + add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings)) + + if data.hit_type != HitType.none and world.options.UmbrellaLogic: + if data.hit_type == HitType.umbrella: + add_rule(loc, lambda state: state.has("Umbrella", world.player)) + + elif data.hit_type == HitType.umbrella_or_brewing: + add_rule(loc, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.BREWING)) + + elif data.hit_type == HitType.dweller_bell: + add_rule(loc, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.BREWING) + or can_use_hat(state, world, HatType.DWELLER)) + + for misc in data.misc_required: + add_rule(loc, lambda state, item=misc: state.has(item, world.player)) + + set_specific_rules(world) + + # Putting all of this here, so it doesn't get overridden by anything + # Illness starts the player past the intro + alpine_entrance = world.multiworld.get_entrance("AFR -> Alpine Skyline Area", world.player) + add_rule(alpine_entrance, lambda state: can_use_hookshot(state, world)) + if world.options.UmbrellaLogic: + add_rule(alpine_entrance, lambda state: state.has("Umbrella", world.player)) + + if zipline_logic(world): + add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), + lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player)) + + add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player), + lambda state: state.has("Zipline Unlock - The Lava Cake Path", world.player)) + + add_rule(world.multiworld.get_entrance("-> The Windmill", world.player), + lambda state: state.has("Zipline Unlock - The Windmill Path", world.player)) + + add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), + lambda state: state.has("Zipline Unlock - The Twilight Bell Path", world.player)) + + add_rule(world.multiworld.get_location("Act Completion (The Illness has Spread)", world.player), + lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player) + and state.has("Zipline Unlock - The Lava Cake Path", world.player) + and state.has("Zipline Unlock - The Windmill Path", world.player)) + + if zipline_logic(world): + for (loc, zipline) in zipline_unlocks.items(): + add_rule(world.multiworld.get_location(loc, world.player), + lambda state, z=zipline: state.has(z, world.player)) + + dummy_entrances: List[Entrance] = [] + + for (key, acts) in act_connections.items(): + if "Arctic Cruise" in key and not world.is_dlc1(): + continue + + entrance: Entrance = world.multiworld.get_entrance(key, world.player) + region: Region = entrance.connected_region + access_rules: List[Callable[[CollectionState], bool]] = [] + dummy_entrances.append(entrance) + + # Entrances to this act that we have to set access_rules on + entrances: List[Entrance] = [] + + for i, act in enumerate(acts, start=1): + act_entrance: Entrance = world.multiworld.get_entrance(act, world.player) + access_rules.append(act_entrance.access_rule) + required_region = act_entrance.connected_region + name: str = f"{key}: Connection {i}" + new_entrance: Entrance = required_region.connect(region, name) + entrances.append(new_entrance) + + # Copy access rules from act completions + if "Free Roam" not in required_region.name: + rule: Callable[[CollectionState], bool] + name = f"Act Completion ({required_region.name})" + rule = world.multiworld.get_location(name, world.player).access_rule + access_rules.append(rule) + + for e in entrances: + for rules in access_rules: + add_rule(e, rules) + + for e in dummy_entrances: + set_rule(e, lambda state: False) + + set_event_rules(world) + + if world.options.EndGoal == EndGoal.option_finale: + world.multiworld.completion_condition[world.player] = lambda state: state.has("Time Piece Cluster", world.player) + elif world.options.EndGoal == EndGoal.option_rush_hour: + world.multiworld.completion_condition[world.player] = lambda state: state.has("Rush Hour Cleared", world.player) + + +def set_specific_rules(world: "HatInTimeWorld"): + add_rule(world.multiworld.get_location("Mafia Boss Shop Item", world.player), + lambda state: state.has("Time Piece", world.player, 12) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS])) + + set_mafia_town_rules(world) + set_botb_rules(world) + set_subcon_rules(world) + set_alps_rules(world) + + if world.is_dlc1(): + set_dlc1_rules(world) + + if world.is_dlc2(): + set_dlc2_rules(world) + + difficulty: Difficulty = get_difficulty(world) + + if difficulty >= Difficulty.MODERATE: + set_moderate_rules(world) + + if difficulty >= Difficulty.HARD: + set_hard_rules(world) + + if difficulty >= Difficulty.EXPERT: + set_expert_rules(world) + + +def set_moderate_rules(world: "HatInTimeWorld"): + # Moderate: Gallery without Brewing Hat + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Gallery)", world.player), lambda state: True) + + # Moderate: Above Boats via Ice Hat Sliding + add_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), + lambda state: can_use_hat(state, world, HatType.ICE), "or") + + # Moderate: Clock Tower Chest + Ruined Tower with nothing + add_rule(world.multiworld.get_location("Mafia Town - Clock Tower Chest", world.player), lambda state: True) + add_rule(world.multiworld.get_location("Mafia Town - Top of Ruined Tower", world.player), lambda state: True) + + # Moderate: enter and clear The Subcon Well without Hookshot and without hitting the bell + for loc in world.multiworld.get_region("The Subcon Well", world.player).locations: + set_rule(loc, lambda state: has_paintings(state, world, 1)) + + # Moderate: Vanessa Manor with nothing + for loc in world.multiworld.get_region("Queen Vanessa's Manor", world.player).locations: + set_rule(loc, lambda state: has_paintings(state, world, 1)) + + set_rule(world.multiworld.get_location("Subcon Forest - Manor Rooftop", world.player), + lambda state: has_paintings(state, world, 1)) + + # Moderate: Village Time Rift with nothing IF umbrella logic is off + if not world.options.UmbrellaLogic: + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), lambda state: True) + + # Moderate: get to Birdhouse/Yellow Band Hills without Brewing Hat + set_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), + lambda state: can_use_hookshot(state, world)) + set_rule(world.multiworld.get_location("Alpine Skyline - Yellow Band Hills", world.player), + lambda state: can_use_hookshot(state, world)) + + # Moderate: The Birdhouse - Dweller Platforms Relic with only Birdhouse access + set_rule(world.multiworld.get_location("Alpine Skyline - The Birdhouse: Dweller Platforms Relic", world.player), + lambda state: True) + + # Moderate: Twilight Path without Dweller Mask + set_rule(world.multiworld.get_location("Alpine Skyline - The Twilight Path", world.player), lambda state: True) + + # Moderate: Mystifying Time Mesa time trial without hats + set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player), + lambda state: can_use_hookshot(state, world)) + + # Moderate: Goat Refinery from TIHS with Sprint only + add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player), + lambda state: state.has("TIHS Access", world.player) + and can_use_hat(state, world, HatType.SPRINT), "or") + + # Moderate: Finale Telescope with only Ice Hat + add_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.FINALE]) + and can_use_hat(state, world, HatType.ICE), "or") + + # Moderate: Finale without Hookshot + set_rule(world.multiworld.get_location("Act Completion (The Finale)", world.player), + lambda state: can_use_hat(state, world, HatType.DWELLER)) + + if world.is_dlc1(): + # Moderate: clear Rock the Boat without Ice Hat + add_rule(world.multiworld.get_location("Rock the Boat - Post Captain Rescue", world.player), lambda state: True) + add_rule(world.multiworld.get_location("Act Completion (Rock the Boat)", world.player), lambda state: True) + + # Moderate: clear Deep Sea without Ice Hat + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player), + lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER)) + + # There is a glitched fall damage volume near the Yellow Overpass time piece that warps the player to Pink Paw. + # Yellow Overpass time piece can also be reached without Hookshot quite easily. + if world.is_dlc2(): + # No Hookshot + set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Station)", world.player), + lambda state: True) + + # No Dweller, Hookshot, or Time Stop for these + set_rule(world.multiworld.get_location("Pink Paw Station - Cat Vacuum", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Pink Paw Station - Behind Fan", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Pink Paw Station - Pink Ticket Booth", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Act Completion (Pink Paw Station)", world.player), lambda state: True) + for key in shop_locations.keys(): + if "Pink Paw Station Thug" in key and is_location_valid(world, key): + set_rule(world.multiworld.get_location(key, world.player), lambda state: True) + + # Moderate: clear Rush Hour without Hookshot + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: state.has("Metro Ticket - Pink", world.player) + and state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and can_use_hat(state, world, HatType.ICE) + and can_use_hat(state, world, HatType.BREWING)) + + # Moderate: Bluefin Tunnel + Pink Paw Station without tickets + if not world.options.NoTicketSkips: + set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True) + set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True) + + +def set_hard_rules(world: "HatInTimeWorld"): + # Hard: clear Time Rift - The Twilight Bell with Sprint+Scooter only + add_rule(world.multiworld.get_location("Act Completion (Time Rift - The Twilight Bell)", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT) + and state.has("Scooter Badge", world.player), "or") + + # No Dweller Mask required + set_rule(world.multiworld.get_location("Subcon Forest - Dweller Floating Rocks", world.player), + lambda state: has_paintings(state, world, 3)) + set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player), + lambda state: has_paintings(state, world, 3)) + + # Cherry bridge over boss arena gap (painting still expected) + set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), + lambda state: has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player)) + + set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player), + lambda state: has_paintings(state, world, 2, True)) + set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), + lambda state: has_paintings(state, world, 2, True)) + set_rule(world.multiworld.get_location("Subcon Forest - Tall Tree Hookshot Swing", world.player), + lambda state: has_paintings(state, world, 3, True)) + + # SDJ + add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT) and has_paintings(state, world, 2), "or") + + add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT), "or") + + # Hard: Goat Refinery from TIHS with nothing + add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player), + lambda state: state.has("TIHS Access", world.player), "or") + + if world.is_dlc1(): + # Hard: clear Deep Sea without Dweller Mask + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player), + lambda state: can_use_hookshot(state, world)) + + if world.is_dlc2(): + # Hard: clear Green Clean Manhole without Dweller Mask + set_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player), + lambda state: can_use_hat(state, world, HatType.ICE)) + + # Hard: clear Rush Hour with Brewing Hat only + if world.options.NoTicketSkips != NoTicketSkips.option_true: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING)) + else: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING) + and state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and state.has("Metro Ticket - Pink", world.player)) + + +def set_expert_rules(world: "HatInTimeWorld"): + # Finale Telescope with no hats + set_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player), + lambda state: state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.FINALE])) + + # Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing + set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player), lambda state: True) + + # Expert: Clear Dead Bird Studio with nothing + for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations: + set_rule(loc, lambda state: True) + + set_rule(world.multiworld.get_location("Act Completion (Dead Bird Studio)", world.player), lambda state: True) + + # Expert: Clear Dead Bird Studio Basement without Hookshot + for loc in world.multiworld.get_region("Dead Bird Studio Basement", world.player).locations: + set_rule(loc, lambda state: True) + + # Expert: get to and clear Twilight Bell without Dweller Mask. + # Dweller Mask OR Sprint Hat OR Brewing Hat OR Time Stop + Umbrella required to complete act. + add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), + lambda state: can_use_hookshot(state, world), "or") + + add_rule(world.multiworld.get_location("Act Completion (The Twilight Bell)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING) + or can_use_hat(state, world, HatType.DWELLER) + or can_use_hat(state, world, HatType.SPRINT) + or (can_use_hat(state, world, HatType.TIME_STOP) and state.has("Umbrella", world.player))) + + # Expert: Time Rift - Curly Tail Trail with nothing + # Time Rift - Twilight Bell and Time Rift - Village with nothing + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player), + lambda state: True) + + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Act Completion (Time Rift - The Twilight Bell)", world.player), + lambda state: True) + + # Expert: Cherry Hovering + subcon_area = world.multiworld.get_region("Subcon Forest Area", world.player) + yche = world.multiworld.get_region("Your Contract has Expired", world.player) + entrance = yche.connect(subcon_area, "Subcon Forest Entrance YCHE") + + if world.options.NoPaintingSkips: + add_rule(entrance, lambda state: has_paintings(state, world, 1)) + + set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player), + lambda state: can_use_hookshot(state, world) and can_hit(state, world) + and has_paintings(state, world, 1, True)) + + # Set painting rules only. Skipping paintings is determined in has_paintings + set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), + lambda state: has_paintings(state, world, 1, True)) + set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player), + lambda state: has_paintings(state, world, 3, True)) + + # You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him + subcon_area.connect(yche, "Snatcher Hover") + set_rule(world.multiworld.get_location("Act Completion (Your Contract has Expired)", world.player), + lambda state: True) + + if world.is_dlc2(): + # Expert: clear Rush Hour with nothing + if not world.options.NoTicketSkips: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True) + else: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and state.has("Metro Ticket - Pink", world.player)) + + # Expert: Yellow/Green Manhole with nothing using a Boop Clip + set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Manhole)", world.player), + lambda state: True) + set_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player), + lambda state: True) + + +def set_mafia_town_rules(world: "HatInTimeWorld"): + add_rule(world.multiworld.get_location("Mafia Town - Behind HQ Chest", world.player), + lambda state: state.can_reach("Act Completion (Heating Up Mafia Town)", "Location", world.player) + or state.can_reach("Down with the Mafia!", "Region", world.player) + or state.can_reach("Cheating the Race", "Region", world.player) + or state.can_reach("The Golden Vault", "Region", world.player)) + + # Old guys don't appear in SCFOS + add_rule(world.multiworld.get_location("Mafia Town - Old Man (Steel Beams)", world.player), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player) + or state.can_reach("Barrel Battle", "Region", world.player) + or state.can_reach("Cheating the Race", "Region", world.player) + or state.can_reach("The Golden Vault", "Region", world.player) + or state.can_reach("Down with the Mafia!", "Region", world.player)) + + add_rule(world.multiworld.get_location("Mafia Town - Old Man (Seaside Spaghetti)", world.player), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player) + or state.can_reach("Barrel Battle", "Region", world.player) + or state.can_reach("Cheating the Race", "Region", world.player) + or state.can_reach("The Golden Vault", "Region", world.player) + or state.can_reach("Down with the Mafia!", "Region", world.player)) + + # Only available outside She Came from Outer Space + add_rule(world.multiworld.get_location("Mafia Town - Mafia Geek Platform", world.player), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player) + or state.can_reach("Barrel Battle", "Region", world.player) + or state.can_reach("Down with the Mafia!", "Region", world.player) + or state.can_reach("Cheating the Race", "Region", world.player) + or state.can_reach("Heating Up Mafia Town", "Region", world.player) + or state.can_reach("The Golden Vault", "Region", world.player)) + + # Only available outside Down with the Mafia! (for some reason) + add_rule(world.multiworld.get_location("Mafia Town - On Scaffolding", world.player), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", world.player) + or state.can_reach("Barrel Battle", "Region", world.player) + or state.can_reach("She Came from Outer Space", "Region", world.player) + or state.can_reach("Cheating the Race", "Region", world.player) + or state.can_reach("Heating Up Mafia Town", "Region", world.player) + or state.can_reach("The Golden Vault", "Region", world.player)) + + # For some reason, the brewing crate is removed in HUMT + add_rule(world.multiworld.get_location("Mafia Town - Secret Cave", world.player), + lambda state: state.has("HUMT Access", world.player), "or") + + # Can bounce across the lava to get this without Hookshot (need to die though) + add_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), + lambda state: state.has("HUMT Access", world.player), "or") + + if world.options.CTRLogic == CTRLogic.option_nothing: + set_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), lambda state: True) + elif world.options.CTRLogic == CTRLogic.option_sprint: + add_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT), "or") + elif world.options.CTRLogic == CTRLogic.option_scooter: + add_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT) + and state.has("Scooter Badge", world.player), "or") + + +def set_botb_rules(world: "HatInTimeWorld"): + if not world.options.UmbrellaLogic and get_difficulty(world) < Difficulty.MODERATE: + set_rule(world.multiworld.get_location("Dead Bird Studio - DJ Grooves Sign Chest", world.player), + lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING)) + set_rule(world.multiworld.get_location("Dead Bird Studio - Tepee Chest", world.player), + lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING)) + set_rule(world.multiworld.get_location("Dead Bird Studio - Conductor Chest", world.player), + lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING)) + set_rule(world.multiworld.get_location("Act Completion (Dead Bird Studio)", world.player), + lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING)) + + +def set_subcon_rules(world: "HatInTimeWorld"): + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING) or state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.DWELLER)) + + # You can't skip over the boss arena wall without cherry hover, so these two need to be set this way + set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), + lambda state: state.has("TOD Access", world.player) and can_use_hookshot(state, world) + and has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player)) + + # The painting wall can't be skipped without cherry hover, which is Expert + set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player), + lambda state: can_use_hookshot(state, world) and can_hit(state, world) + and has_paintings(state, world, 1, False)) + + add_rule(world.multiworld.get_entrance("Subcon Forest - Act 2", world.player), + lambda state: state.has("Snatcher's Contract - The Subcon Well", world.player)) + + add_rule(world.multiworld.get_entrance("Subcon Forest - Act 3", world.player), + lambda state: state.has("Snatcher's Contract - Toilet of Doom", world.player)) + + add_rule(world.multiworld.get_entrance("Subcon Forest - Act 4", world.player), + lambda state: state.has("Snatcher's Contract - Queen Vanessa's Manor", world.player)) + + add_rule(world.multiworld.get_entrance("Subcon Forest - Act 5", world.player), + lambda state: state.has("Snatcher's Contract - Mail Delivery Service", world.player)) + + if painting_logic(world): + add_rule(world.multiworld.get_location("Act Completion (Contractual Obligations)", world.player), + lambda state: has_paintings(state, world, 1, False)) + + +def set_alps_rules(world: "HatInTimeWorld"): + add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), + lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.BREWING)) + + add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player), + lambda state: can_use_hookshot(state, world)) + + add_rule(world.multiworld.get_entrance("-> The Windmill", world.player), + lambda state: can_use_hookshot(state, world)) + + add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), + lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER)) + + add_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT) or can_use_hat(state, world, HatType.TIME_STOP)) + + add_rule(world.multiworld.get_entrance("Alpine Skyline - Finale", world.player), + lambda state: can_clear_alpine(state, world)) + + add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player), + lambda state: state.has("AFR Access", world.player) + and can_use_hookshot(state, world) + and can_hit(state, world, True)) + + +def set_dlc1_rules(world: "HatInTimeWorld"): + add_rule(world.multiworld.get_entrance("Cruise Ship Entrance BV", world.player), + lambda state: can_use_hookshot(state, world)) + + # This particular item isn't present in Act 3 for some reason, yes in vanilla too + add_rule(world.multiworld.get_location("The Arctic Cruise - Toilet", world.player), + lambda state: state.can_reach("Bon Voyage!", "Region", world.player) + or state.can_reach("Ship Shape", "Region", world.player)) + + +def set_dlc2_rules(world: "HatInTimeWorld"): + add_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), + lambda state: state.has("Metro Ticket - Green", world.player) + or state.has("Metro Ticket - Blue", world.player)) + + add_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), + lambda state: state.has("Metro Ticket - Pink", world.player) + or state.has("Metro Ticket - Yellow", world.player) and state.has("Metro Ticket - Blue", world.player)) + + add_rule(world.multiworld.get_entrance("Nyakuza Metro - Finale", world.player), + lambda state: can_clear_metro(state, world)) + + add_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and state.has("Metro Ticket - Pink", world.player)) + + for key in shop_locations.keys(): + if "Green Clean Station Thug B" in key and is_location_valid(world, key): + add_rule(world.multiworld.get_location(key, world.player), + lambda state: state.has("Metro Ticket - Yellow", world.player), "or") + + +def reg_act_connection(world: "HatInTimeWorld", region: Union[str, Region], unlocked_entrance: Union[str, Entrance]): + reg: Region + entrance: Entrance + if isinstance(region, str): + reg = world.multiworld.get_region(region, world.player) + else: + reg = region + + if isinstance(unlocked_entrance, str): + entrance = world.multiworld.get_entrance(unlocked_entrance, world.player) + else: + entrance = unlocked_entrance + + world.multiworld.register_indirect_condition(reg, entrance) + + +# See randomize_act_entrances in Regions.py +# Called before set_rules +def set_rift_rules(world: "HatInTimeWorld", regions: Dict[str, Region]): + + # This is accessing the regions in place of these time rifts, so we can set the rules on all the entrances. + for entrance in regions["Time Rift - Gallery"].entrances: + add_rule(entrance, lambda state: can_use_hat(state, world, HatType.BREWING) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS])) + + for entrance in regions["Time Rift - The Lab"].entrances: + add_rule(entrance, lambda state: can_use_hat(state, world, HatType.DWELLER) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE])) + + for entrance in regions["Time Rift - Sewers"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 4")) + reg_act_connection(world, world.multiworld.get_entrance("Mafia Town - Act 4", + world.player).connected_region, entrance) + + for entrance in regions["Time Rift - Bazaar"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 6")) + reg_act_connection(world, world.multiworld.get_entrance("Mafia Town - Act 6", + world.player).connected_region, entrance) + + for entrance in regions["Time Rift - Mafia of Cooks"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Burger")) + + for entrance in regions["Time Rift - The Owl Express"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 2")) + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 3")) + reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 2", + world.player).connected_region, entrance) + reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 3", + world.player).connected_region, entrance) + + for entrance in regions["Time Rift - The Moon"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 4")) + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 5")) + reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 4", + world.player).connected_region, entrance) + reg_act_connection(world, world.multiworld.get_entrance("Battle of the Birds - Act 5", + world.player).connected_region, entrance) + + for entrance in regions["Time Rift - Dead Bird Studio"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Train")) + + for entrance in regions["Time Rift - Pipe"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2")) + reg_act_connection(world, world.multiworld.get_entrance("Subcon Forest - Act 2", + world.player).connected_region, entrance) + if painting_logic(world): + add_rule(entrance, lambda state: has_paintings(state, world, 2)) + + for entrance in regions["Time Rift - Village"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4")) + reg_act_connection(world, world.multiworld.get_entrance("Subcon Forest - Act 4", + world.player).connected_region, entrance) + + if painting_logic(world): + add_rule(entrance, lambda state: has_paintings(state, world, 2)) + + for entrance in regions["Time Rift - Sleepy Subcon"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO")) + if painting_logic(world): + add_rule(entrance, lambda state: has_paintings(state, world, 3)) + + for entrance in regions["Time Rift - Curly Tail Trail"].entrances: + add_rule(entrance, lambda state: state.has("Windmill Cleared", world.player)) + + for entrance in regions["Time Rift - The Twilight Bell"].entrances: + add_rule(entrance, lambda state: state.has("Twilight Bell Cleared", world.player)) + + for entrance in regions["Time Rift - Alpine Skyline"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Crayon")) + + if world.is_dlc1(): + for entrance in regions["Time Rift - Balcony"].entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "The Arctic Cruise - Finale")) + reg_act_connection(world, world.multiworld.get_entrance("The Arctic Cruise - Finale", + world.player).connected_region, entrance) + + for entrance in regions["Time Rift - Deep Sea"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Cake")) + + if world.is_dlc2(): + for entrance in regions["Time Rift - Rumbi Factory"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Necklace")) + + +# Basically the same as above, but without the need of the dict since we are just setting defaults +# Called if Act Rando is disabled +def set_default_rift_rules(world: "HatInTimeWorld"): + + for entrance in world.multiworld.get_region("Time Rift - Gallery", world.player).entrances: + add_rule(entrance, lambda state: can_use_hat(state, world, HatType.BREWING) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.BIRDS])) + + for entrance in world.multiworld.get_region("Time Rift - The Lab", world.player).entrances: + add_rule(entrance, lambda state: can_use_hat(state, world, HatType.DWELLER) + and state.has("Time Piece", world.player, world.chapter_timepiece_costs[ChapterIndex.ALPINE])) + + for entrance in world.multiworld.get_region("Time Rift - Sewers", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 4")) + reg_act_connection(world, "Down with the Mafia!", entrance.name) + + for entrance in world.multiworld.get_region("Time Rift - Bazaar", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Mafia Town - Act 6")) + reg_act_connection(world, "Heating Up Mafia Town", entrance.name) + + for entrance in world.multiworld.get_region("Time Rift - Mafia of Cooks", world.player).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Burger")) + + for entrance in world.multiworld.get_region("Time Rift - The Owl Express", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 2")) + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 3")) + reg_act_connection(world, "Murder on the Owl Express", entrance.name) + reg_act_connection(world, "Picture Perfect", entrance.name) + + for entrance in world.multiworld.get_region("Time Rift - The Moon", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 4")) + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Battle of the Birds - Act 5")) + reg_act_connection(world, "Train Rush", entrance.name) + reg_act_connection(world, "The Big Parade", entrance.name) + + for entrance in world.multiworld.get_region("Time Rift - Dead Bird Studio", world.player).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Train")) + + for entrance in world.multiworld.get_region("Time Rift - Pipe", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2")) + reg_act_connection(world, "The Subcon Well", entrance.name) + if painting_logic(world): + add_rule(entrance, lambda state: has_paintings(state, world, 2)) + + for entrance in world.multiworld.get_region("Time Rift - Village", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4")) + reg_act_connection(world, "Queen Vanessa's Manor", entrance.name) + if painting_logic(world): + add_rule(entrance, lambda state: has_paintings(state, world, 2)) + + for entrance in world.multiworld.get_region("Time Rift - Sleepy Subcon", world.player).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO")) + if painting_logic(world): + add_rule(entrance, lambda state: has_paintings(state, world, 3)) + + for entrance in world.multiworld.get_region("Time Rift - Curly Tail Trail", world.player).entrances: + add_rule(entrance, lambda state: state.has("Windmill Cleared", world.player)) + + for entrance in world.multiworld.get_region("Time Rift - The Twilight Bell", world.player).entrances: + add_rule(entrance, lambda state: state.has("Twilight Bell Cleared", world.player)) + + for entrance in world.multiworld.get_region("Time Rift - Alpine Skyline", world.player).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Crayon")) + + if world.is_dlc1(): + for entrance in world.multiworld.get_region("Time Rift - Balcony", world.player).entrances: + add_rule(entrance, lambda state: can_clear_required_act(state, world, "The Arctic Cruise - Finale")) + reg_act_connection(world, "Rock the Boat", entrance.name) + + for entrance in world.multiworld.get_region("Time Rift - Deep Sea", world.player).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Cake")) + + if world.is_dlc2(): + for entrance in world.multiworld.get_region("Time Rift - Rumbi Factory", world.player).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, world, "Necklace")) + + +def set_event_rules(world: "HatInTimeWorld"): + for (name, data) in event_locs.items(): + if not is_location_valid(world, name): + continue + + event: Location = world.multiworld.get_location(name, world.player) + + if data.act_event: + add_rule(event, world.multiworld.get_location(f"Act Completion ({data.region})", world.player).access_rule) diff --git a/worlds/ahit/Types.py b/worlds/ahit/Types.py new file mode 100644 index 000000000000..468cfcb78ad3 --- /dev/null +++ b/worlds/ahit/Types.py @@ -0,0 +1,86 @@ +from enum import IntEnum, IntFlag +from typing import NamedTuple, Optional, List +from BaseClasses import Location, Item, ItemClassification + + +class HatInTimeLocation(Location): + game = "A Hat in Time" + + +class HatInTimeItem(Item): + game = "A Hat in Time" + + +class HatType(IntEnum): + SPRINT = 0 + BREWING = 1 + ICE = 2 + DWELLER = 3 + TIME_STOP = 4 + + +class HitType(IntEnum): + none = 0 + umbrella = 1 + umbrella_or_brewing = 2 + dweller_bell = 3 + + +class HatDLC(IntFlag): + none = 0b000 + dlc1 = 0b001 + dlc2 = 0b010 + death_wish = 0b100 + dlc1_dw = 0b101 + dlc2_dw = 0b110 + + +class ChapterIndex(IntEnum): + SPACESHIP = 0 + MAFIA = 1 + BIRDS = 2 + SUBCON = 3 + ALPINE = 4 + FINALE = 5 + CRUISE = 6 + METRO = 7 + + +class Difficulty(IntEnum): + NORMAL = -1 + MODERATE = 0 + HARD = 1 + EXPERT = 2 + + +class LocData(NamedTuple): + id: int = 0 + region: str = "" + required_hats: List[HatType] = [] + hookshot: bool = False + dlc_flags: HatDLC = HatDLC.none + paintings: int = 0 # Paintings required for Subcon painting shuffle + misc_required: List[str] = [] + + # For UmbrellaLogic setting only. + hit_type: HitType = HitType.none + + # Other + act_event: bool = False # Only used for event locations. Copy access rule from act completion + nyakuza_thug: str = "" # Name of Nyakuza thug NPC (for metro shops) + snatcher_coin: str = "" # Only for Snatcher Coin event locations, name of the Snatcher Coin item + + +class ItemData(NamedTuple): + code: Optional[int] + classification: ItemClassification + dlc_flags: Optional[HatDLC] = HatDLC.none + + +hat_type_to_item = { + HatType.SPRINT: "Sprint Hat", + HatType.BREWING: "Brewing Hat", + HatType.ICE: "Ice Hat", + HatType.DWELLER: "Dweller Mask", + HatType.TIME_STOP: "Time Stop Hat", +} diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py new file mode 100644 index 000000000000..dd5e88abbc66 --- /dev/null +++ b/worlds/ahit/__init__.py @@ -0,0 +1,386 @@ +from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld +from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \ + calculate_yarn_costs, alps_hooks +from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region +from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \ + get_total_locations +from .Rules import set_rules, has_paintings +from .Options import AHITOptions, slot_data_options, adjust_options, RandomizeHatOrder, EndGoal, create_option_groups +from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item, Difficulty +from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes +from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses +from worlds.AutoWorld import World, WebWorld, CollectionState +from worlds.generic.Rules import add_rule +from typing import List, Dict, TextIO +from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type +from Utils import local_path + + +def launch_client(): + from .Client import launch + launch_subprocess(launch, name="AHITClient") + + +components.append(Component("A Hat in Time Client", "AHITClient", func=launch_client, + component_type=Type.CLIENT, icon='yatta')) + +icon_paths['yatta'] = local_path('data', 'yatta.png') + + +class AWebInTime(WebWorld): + theme = "partyTime" + option_groups = create_option_groups() + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide for setting up A Hat in Time to be played in Archipelago.", + "English", + "ahit_en.md", + "setup/en", + ["CookieCat"] + )] + + +class HatInTimeWorld(World): + """ + A Hat in Time is a cute-as-peck 3D platformer featuring a little girl who stitches hats for wicked powers! + Freely explore giant worlds and recover Time Pieces to travel to new heights! + """ + + game = "A Hat in Time" + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = get_location_names() + options_dataclass = AHITOptions + options: AHITOptions + item_name_groups = relic_groups + web = AWebInTime() + + def __init__(self, multiworld: "MultiWorld", player: int): + super().__init__(multiworld, player) + self.act_connections: Dict[str, str] = {} + self.shop_locs: List[str] = [] + + self.hat_craft_order: List[HatType] = [HatType.SPRINT, HatType.BREWING, HatType.ICE, + HatType.DWELLER, HatType.TIME_STOP] + + self.hat_yarn_costs: Dict[HatType, int] = {HatType.SPRINT: -1, HatType.BREWING: -1, HatType.ICE: -1, + HatType.DWELLER: -1, HatType.TIME_STOP: -1} + + self.chapter_timepiece_costs: Dict[ChapterIndex, int] = {ChapterIndex.MAFIA: -1, + ChapterIndex.BIRDS: -1, + ChapterIndex.SUBCON: -1, + ChapterIndex.ALPINE: -1, + ChapterIndex.FINALE: -1, + ChapterIndex.CRUISE: -1, + ChapterIndex.METRO: -1} + self.excluded_dws: List[str] = [] + self.excluded_bonuses: List[str] = [] + self.dw_shuffle: List[str] = [] + self.nyakuza_thug_items: Dict[str, int] = {} + self.badge_seller_count: int = 0 + + def generate_early(self): + adjust_options(self) + + if self.options.StartWithCompassBadge: + self.multiworld.push_precollected(self.create_item("Compass Badge")) + + if self.is_dw_only(): + return + + # Take care of some extremely restrictive starts in other chapters with act shuffle off + if not self.options.ActRandomizer: + start_chapter = self.options.StartingChapter + if start_chapter == ChapterIndex.ALPINE: + self.multiworld.push_precollected(self.create_item("Hookshot Badge")) + if self.options.UmbrellaLogic: + self.multiworld.push_precollected(self.create_item("Umbrella")) + + if self.options.ShuffleAlpineZiplines: + ziplines = list(alps_hooks.keys()) + ziplines.remove("Zipline Unlock - The Twilight Bell Path") # not enough checks from this one + self.multiworld.push_precollected(self.create_item(self.random.choice(ziplines))) + elif start_chapter == ChapterIndex.SUBCON: + if self.options.ShuffleSubconPaintings: + self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock")) + elif start_chapter == ChapterIndex.BIRDS: + if self.options.UmbrellaLogic: + if self.options.LogicDifficulty < Difficulty.EXPERT: + self.multiworld.push_precollected(self.create_item("Umbrella")) + elif self.options.LogicDifficulty < Difficulty.MODERATE: + self.multiworld.push_precollected(self.create_item("Umbrella")) + + def create_regions(self): + # noinspection PyClassVar + self.topology_present = bool(self.options.ActRandomizer) + + create_regions(self) + if self.options.EnableDeathWish: + create_dw_regions(self) + + if self.is_dw_only(): + return + + create_events(self) + if self.is_dw(): + if "Snatcher's Hit List" not in self.excluded_dws or "Camera Tourist" not in self.excluded_dws: + create_enemy_events(self) + + # place vanilla contract locations if contract shuffle is off + if not self.options.ShuffleActContracts: + for name in contract_locations.keys(): + loc = self.get_location(name) + loc.place_locked_item(create_item(self, name)) + if self.options.ShuffleSubconPaintings and loc.name != "Snatcher's Contract - The Subcon Well": + add_rule(loc, lambda state: has_paintings(state, self, 1)) + + def create_items(self): + if self.has_yarn(): + calculate_yarn_costs(self) + + if self.options.RandomizeHatOrder: + self.random.shuffle(self.hat_craft_order) + if self.options.RandomizeHatOrder == RandomizeHatOrder.option_time_stop_last: + self.hat_craft_order.remove(HatType.TIME_STOP) + self.hat_craft_order.append(HatType.TIME_STOP) + + # move precollected hats to the start of the list + for i in range(5): + hat = HatType(i) + if self.is_hat_precollected(hat): + self.hat_craft_order.remove(hat) + self.hat_craft_order.insert(0, hat) + + self.multiworld.itempool += create_itempool(self) + + def set_rules(self): + if self.is_dw_only(): + # we already have all items if this is the case, no need for rules + self.multiworld.push_precollected(HatInTimeItem("Death Wish Only Mode", ItemClassification.progression, + None, self.player)) + + self.multiworld.completion_condition[self.player] = lambda state: state.has("Death Wish Only Mode", + self.player) + + if not self.options.DWEnableBonus: + for name in death_wishes: + if name == "Snatcher Coins in Nyakuza Metro" and not self.is_dlc2(): + continue + + if self.options.DWShuffle and name not in self.dw_shuffle: + continue + + full_clear = self.multiworld.get_location(f"{name} - All Clear", self.player) + full_clear.address = None + full_clear.place_locked_item(HatInTimeItem("Nothing", ItemClassification.filler, None, self.player)) + full_clear.show_in_spoiler = False + + return + + if self.options.ActRandomizer: + randomize_act_entrances(self) + + set_rules(self) + + if self.is_dw(): + set_dw_rules(self) + + def create_item(self, name: str) -> Item: + return create_item(self, name) + + def fill_slot_data(self) -> dict: + slot_data: dict = {"Chapter1Cost": self.chapter_timepiece_costs[ChapterIndex.MAFIA], + "Chapter2Cost": self.chapter_timepiece_costs[ChapterIndex.BIRDS], + "Chapter3Cost": self.chapter_timepiece_costs[ChapterIndex.SUBCON], + "Chapter4Cost": self.chapter_timepiece_costs[ChapterIndex.ALPINE], + "Chapter5Cost": self.chapter_timepiece_costs[ChapterIndex.FINALE], + "Chapter6Cost": self.chapter_timepiece_costs[ChapterIndex.CRUISE], + "Chapter7Cost": self.chapter_timepiece_costs[ChapterIndex.METRO], + "BadgeSellerItemCount": self.badge_seller_count, + "SeedNumber": str(self.multiworld.seed), # For shop prices + "SeedName": self.multiworld.seed_name, + "TotalLocations": get_total_locations(self)} + + if self.has_yarn(): + slot_data.setdefault("SprintYarnCost", self.hat_yarn_costs[HatType.SPRINT]) + slot_data.setdefault("BrewingYarnCost", self.hat_yarn_costs[HatType.BREWING]) + slot_data.setdefault("IceYarnCost", self.hat_yarn_costs[HatType.ICE]) + slot_data.setdefault("DwellerYarnCost", self.hat_yarn_costs[HatType.DWELLER]) + slot_data.setdefault("TimeStopYarnCost", self.hat_yarn_costs[HatType.TIME_STOP]) + slot_data.setdefault("Hat1", int(self.hat_craft_order[0])) + slot_data.setdefault("Hat2", int(self.hat_craft_order[1])) + slot_data.setdefault("Hat3", int(self.hat_craft_order[2])) + slot_data.setdefault("Hat4", int(self.hat_craft_order[3])) + slot_data.setdefault("Hat5", int(self.hat_craft_order[4])) + + if self.options.ActRandomizer: + for name in self.act_connections.keys(): + slot_data[name] = self.act_connections[name] + + if self.is_dlc2() and not self.is_dw_only(): + for name in self.nyakuza_thug_items.keys(): + slot_data[name] = self.nyakuza_thug_items[name] + + if self.is_dw(): + i = 0 + for name in self.excluded_dws: + if self.options.EndGoal.value == EndGoal.option_seal_the_deal and name == "Seal the Deal": + continue + + slot_data[f"excluded_dw{i}"] = dw_classes[name] + i += 1 + + i = 0 + if not self.options.DWAutoCompleteBonuses: + for name in self.excluded_bonuses: + if name in self.excluded_dws: + continue + + slot_data[f"excluded_bonus{i}"] = dw_classes[name] + i += 1 + + if self.options.DWShuffle: + shuffled_dws = self.dw_shuffle + for i in range(len(shuffled_dws)): + slot_data[f"dw_{i}"] = dw_classes[shuffled_dws[i]] + + shop_item_names: Dict[str, str] = {} + for name in self.shop_locs: + loc: Location = self.multiworld.get_location(name, self.player) + assert loc.item + item_name: str + if loc.item.classification is ItemClassification.trap and loc.item.game == "A Hat in Time": + item_name = get_shop_trap_name(self) + else: + item_name = loc.item.name + + shop_item_names.setdefault(str(loc.address), item_name) + + slot_data["ShopItemNames"] = shop_item_names + + for name, value in self.options.as_dict(*self.options_dataclass.type_hints).items(): + if name in slot_data_options: + slot_data[name] = value + + return slot_data + + def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): + if self.is_dw_only() or not self.options.ActRandomizer: + return + + new_hint_data = {} + alpine_regions = ["The Birdhouse", "The Lava Cake", "The Windmill", + "The Twilight Bell", "Alpine Skyline Area", "Alpine Skyline Area (TIHS)"] + + metro_regions = ["Yellow Overpass Station", "Green Clean Station", "Bluefin Tunnel", "Pink Paw Station"] + + for key, data in location_table.items(): + if not is_location_valid(self, key): + continue + + location = self.multiworld.get_location(key, self.player) + region_name: str + + if data.region in alpine_regions: + region_name = "Alpine Free Roam" + elif data.region in metro_regions: + region_name = "Nyakuza Free Roam" + elif "Dead Bird Studio - " in data.region: + region_name = "Dead Bird Studio" + elif data.region in chapter_act_info.keys(): + region_name = location.parent_region.name + else: + continue + + new_hint_data[location.address] = get_shuffled_region(self, region_name) + + if self.is_dlc1() and self.options.Tasksanity: + ship_shape_region = get_shuffled_region(self, "Ship Shape") + id_start: int = TASKSANITY_START_ID + for i in range(self.options.TasksanityCheckCount): + new_hint_data[id_start+i] = ship_shape_region + + hint_data[self.player] = new_hint_data + + def write_spoiler_header(self, spoiler_handle: TextIO): + for i in self.chapter_timepiece_costs: + spoiler_handle.write("Chapter %i Cost: %i\n" % (i, self.chapter_timepiece_costs[ChapterIndex(i)])) + + for hat in self.hat_craft_order: + spoiler_handle.write("Hat Cost: %s: %i\n" % (hat, self.hat_yarn_costs[hat])) + + def collect(self, state: "CollectionState", item: "Item") -> bool: + old_count: int = state.count(item.name, self.player) + change = super().collect(state, item) + if change and old_count == 0: + if "Stamp" in item.name: + if "2 Stamp" in item.name: + state.prog_items[self.player]["Stamps"] += 2 + else: + state.prog_items[self.player]["Stamps"] += 1 + elif "(Zero Jumps)" in item.name: + state.prog_items[self.player]["Zero Jumps"] += 1 + elif item.name in hit_list.keys(): + if item.name not in bosses: + state.prog_items[self.player]["Enemy"] += 1 + else: + state.prog_items[self.player]["Boss"] += 1 + + return change + + def remove(self, state: "CollectionState", item: "Item") -> bool: + old_count: int = state.count(item.name, self.player) + change = super().remove(state, item) + if change and old_count == 1: + if "Stamp" in item.name: + if "2 Stamp" in item.name: + state.prog_items[self.player]["Stamps"] -= 2 + else: + state.prog_items[self.player]["Stamps"] -= 1 + elif "(Zero Jumps)" in item.name: + state.prog_items[self.player]["Zero Jumps"] -= 1 + elif item.name in hit_list.keys(): + if item.name not in bosses: + state.prog_items[self.player]["Enemy"] -= 1 + else: + state.prog_items[self.player]["Boss"] -= 1 + + return change + + def has_yarn(self) -> bool: + return not self.is_dw_only() and not self.options.HatItems + + def is_hat_precollected(self, hat: HatType) -> bool: + for item in self.multiworld.precollected_items[self.player]: + if item.name == hat_type_to_item[hat]: + return True + + return False + + def is_dlc1(self) -> bool: + return bool(self.options.EnableDLC1) + + def is_dlc2(self) -> bool: + return bool(self.options.EnableDLC2) + + def is_dw(self) -> bool: + return bool(self.options.EnableDeathWish) + + def is_dw_only(self) -> bool: + return self.is_dw() and bool(self.options.DeathWishOnly) + + def is_dw_excluded(self, name: str) -> bool: + # don't exclude Seal the Deal if it's our goal + if self.options.EndGoal.value == EndGoal.option_seal_the_deal and name == "Seal the Deal" \ + and f"{name} - Main Objective" not in self.options.exclude_locations: + return False + + if name in self.excluded_dws: + return True + + return f"{name} - Main Objective" in self.options.exclude_locations + + def is_bonus_excluded(self, name: str) -> bool: + if self.is_dw_excluded(name) or name in self.excluded_bonuses: + return True + + return f"{name} - All Clear" in self.options.exclude_locations diff --git a/worlds/ahit/docs/en_A Hat in Time.md b/worlds/ahit/docs/en_A Hat in Time.md new file mode 100644 index 000000000000..9f1a593bbdd9 --- /dev/null +++ b/worlds/ahit/docs/en_A Hat in Time.md @@ -0,0 +1,53 @@ +# A Hat in Time + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? + +Items which the player would normally acquire throughout the game have been moved around. +Chapter costs are randomized in a progressive order based on your options, +so for example you could go to Subcon Forest -> Battle of the Birds -> Alpine Skyline, etc. in that order. +If act shuffle is turned on, the levels and Time Rifts in these chapters will be randomized as well. + +To unlock and access a chapter's Time Rift in act shuffle, +the levels in place of the original acts required to unlock the Time Rift in the vanilla game must be completed, +and then you must enter a level that allows you to access that Time Rift. +For example, Time Rift: Bazaar requires Heating Up Mafia Town to be completed in the vanilla game. +To unlock this Time Rift in act shuffle (and therefore the level it contains) +you must complete the level that was shuffled in place of Heating Up Mafia Town +and then enter the Time Rift through a Mafia Town level. + +## What items and locations get shuffled? + +Time Pieces, Relics, Yarn, Badges, and most other items are shuffled. +Unlike in the vanilla game, yarn is typeless, and hats will be automatically stitched +in a set order once you gather enough yarn for each hat. +Hats can also optionally be shuffled as individual items instead. +Any items in the world, shops, act completions, +and optionally storybook pages or Death Wish contracts are locations. + +Any freestanding items that are considered to be progression or useful +will have a rainbow streak particle attached to them. +Filler items will have a white glow attached to them instead. + +## Which items can be in another player's world? + +Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit +certain items to your own world. + +## What does another world's item look like in A Hat in Time? + +Items belonging to other worlds are represented by a badge with the Archipelago logo on it. + +## When the player receives an item, what happens? + +When the player receives an item, it will play the item collect effect and information about the item +will be printed on the screen and in the in-game developer console. + +## Is the DLC required to play A Hat in Time in Archipelago? + +No, the DLC expansions are not required to play. Their content can be enabled through certain options +that are disabled by default, but please don't turn them on if you don't own the respective DLC. diff --git a/worlds/ahit/docs/setup_en.md b/worlds/ahit/docs/setup_en.md new file mode 100644 index 000000000000..23b34907071c --- /dev/null +++ b/worlds/ahit/docs/setup_en.md @@ -0,0 +1,65 @@ +# Setup Guide for A Hat in Time in Archipelago + +## Required Software +- [Steam release of A Hat in Time](https://store.steampowered.com/app/253230/A_Hat_in_Time/) + +- [Archipelago Workshop Mod for A Hat in Time](https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601) + + +## Optional Software +- [A Hat in Time Archipelago Map Tracker](https://github.com/Mysteryem/ahit-poptracker/releases), for use with [PopTracker](https://github.com/black-sliver/PopTracker/releases) + + +## Instructions + +1. **BACK UP YOUR SAVE FILES IN YOUR MAIN INSTALL IF YOU CARE ABOUT THEM!!!** + Go to `steamapps/common/HatinTime/HatinTimeGame/SaveData/` and copy everything inside that folder over to a safe place. + **This is important! Changing the game version CAN and WILL break your existing save files!!!** + + +2. In your Steam library, right-click on **A Hat in Time** in the list of games and click on **Properties**. + + +3. Click the **Betas** tab. In the **Beta Participation** dropdown, select `tcplink`. + While it downloads, you can subscribe to the [Archipelago workshop mod.]((https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601)) + + +4. Once the game finishes downloading, start it up. + In Game Settings, make sure **Enable Developer Console** is checked. + + +5. You should now be good to go. See below for more details on how to use the mod and connect to an Archipelago game. + + +## Connecting to the Archipelago server + +To connect to the multiworld server, simply run the **Archipelago AHIT Client** from the Launcher +and connect it to the Archipelago server. +The game will connect to the client automatically when you create a new save file. + + +## Console Commands + +Commands will not work on the title screen, you must be in-game to use them. To use console commands, +make sure ***Enable Developer Console*** is checked in Game Settings and press the tilde key or TAB while in-game. + +`ap_say ` - Send a chat message to the server. Supports commands, such as `!hint` or `!release`. + +`ap_deathlink` - Toggle Death Link. + + +## FAQ/Common Issues + +### The game is not connecting when starting a new save! +For unknown reasons, the mod will randomly disable itself in the mod menu. To fix this, go to the Mods menu +(rocket icon) in-game, and re-enable the mod. + +### Why do relics disappear from the stands in the Spaceship after they're completed? +This is intentional behaviour. Because of how randomizer logic works, there is no way to predict the order that +a player will place their relics. Since there are a limited amount of relic stands in the Spaceship, relics are removed +after being completed to allow for the placement of more relics without being potentially locked out. +The level that the relic set unlocked will stay unlocked. + +### When I start a new save file, the intro cinematic doesn't get skipped, Hat Kid's body is missing and the mod doesn't work! +There is a bug on older versions of A Hat in Time that causes save file creation to fail to work properly +if you have too many save files. Delete them and it should fix the problem. \ No newline at end of file diff --git a/worlds/ahit/test/__init__.py b/worlds/ahit/test/__init__.py new file mode 100644 index 000000000000..67b750a65c7d --- /dev/null +++ b/worlds/ahit/test/__init__.py @@ -0,0 +1,5 @@ +from test.bases import WorldTestBase + + +class HatInTimeTestBase(WorldTestBase): + game = "A Hat in Time" diff --git a/worlds/ahit/test/test_acts.py b/worlds/ahit/test/test_acts.py new file mode 100644 index 000000000000..6502db1d9e6b --- /dev/null +++ b/worlds/ahit/test/test_acts.py @@ -0,0 +1,31 @@ +from ..Regions import act_chapters +from ..Rules import act_connections +from . import HatInTimeTestBase + + +class TestActs(HatInTimeTestBase): + run_default_tests = False + + options = { + "ActRandomizer": 2, + "EnableDLC1": 1, + "EnableDLC2": 1, + "ShuffleActContracts": 0, + } + + def test_act_shuffle(self): + for i in range(300): + self.world_setup() + self.collect_all_but([""]) + + for name in act_chapters.keys(): + region = self.multiworld.get_region(name, 1) + for entrance in region.entrances: + if entrance.name in act_connections.keys(): + continue + + self.assertTrue(self.can_reach_entrance(entrance.name), + f"Can't reach {name} from {entrance}\n" + f"{entrance.parent_region.entrances[0]} -> {entrance.parent_region} " + f"-> {entrance} -> {name}" + f" (expected method of access)") diff --git a/worlds/alttp/Client.py b/worlds/alttp/Client.py index 5b27f559efd7..a0b28829f4bb 100644 --- a/worlds/alttp/Client.py +++ b/worlds/alttp/Client.py @@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool: def new_check(location_id): new_locations.append(location_id) ctx.locations_checked.add(location_id) - location = ctx.location_names[location_id] + location = ctx.location_names.lookup_in_game(location_id) snes_logger.info( f'New Check: {location} ' + f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' + @@ -552,9 +552,9 @@ async def game_watcher(self, ctx): item = ctx.items_received[recv_index] recv_index += 1 logging.info('Received %s from %s (%s) (%d/%d in list)' % ( - color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - ctx.location_names[item.location], recv_index, len(ctx.items_received))) + ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received))) snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF])) @@ -682,7 +682,7 @@ def onButtonClick(answer: str = 'no'): if 'yes' in choice: import LttPAdjuster - from worlds.alttp.Rom import get_base_rom_path + from .Rom import get_base_rom_path last_settings.rom = romfile last_settings.baserom = get_base_rom_path() last_settings.world = None diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index f0b8c2d971b0..150d52cc6c58 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -264,7 +264,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld): if loc in all_state_base.events: all_state_base.events.remove(loc) - fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True, allow_excluded=True, + fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, lock=True, allow_excluded=True, name="LttP Dungeon Items") diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 37486a9cde07..e62088c1e05c 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -23,170 +23,7 @@ def defval(value): multiargs, _ = parser.parse_known_args(argv) parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument('--logic', default=defval('no_glitches'), const='no_glitches', nargs='?', choices=['no_glitches', 'minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'], - help='''\ - Select Enforcement of Item Requirements. (default: %(default)s) - No Glitches: - Minor Glitches: May require Fake Flippers, Bunny Revival - and Dark Room Navigation. - Overworld Glitches: May require overworld glitches. - Hybrid Major Glitches: May require both overworld and underworld clipping. - No Logic: Distribute items without regard for - item requirements. - ''') - parser.add_argument('--glitch_triforce', help='Allow glitching to Triforce from Ganon\'s room', action='store_true') - parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'], - help='''\ - Select game mode. (default: %(default)s) - Open: World starts with Zelda rescued. - Standard: Fixes Hyrule Castle Secret Entrance and Front Door - but may lead to weird rain state issues if you exit - through the Hyrule Castle side exits before rescuing - Zelda in a full shuffle. - Inverted: Starting locations are Dark Sanctuary in West Dark - World or at Link's House, which is shuffled freely. - Requires the moon pearl to be Link in the Light World - instead of a bunny. - ''') - parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?', - choices=['ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', 'ganon_pedestal'], - help='''\ - Select completion goal. (default: %(default)s) - Ganon: Collect all crystals, beat Agahnim 2 then - defeat Ganon. - Crystals: Collect all crystals then defeat Ganon. - Pedestal: Places the Triforce at the Master Sword Pedestal. - Ganon Pedestal: Pull the Master Sword Pedestal, then defeat Ganon. - All Dungeons: Collect all crystals, pendants, beat both - Agahnim fights and then defeat Ganon. - Triforce Hunt: Places 30 Triforce Pieces in the world, collect - 20 of them to beat the game. - Local Triforce Hunt: Places 30 Triforce Pieces in your world, collect - 20 of them to beat the game. - Ganon Triforce Hunt: Places 30 Triforce Pieces in the world, collect - 20 of them, then defeat Ganon. - Local Ganon Triforce Hunt: Places 30 Triforce Pieces in your world, - collect 20 of them, then defeat Ganon. - ''') - parser.add_argument('--triforce_pieces_available', default=defval(30), - type=lambda value: min(max(int(value), 1), 90), - help='''Set Triforce Pieces available in item pool.''') - parser.add_argument('--triforce_pieces_required', default=defval(20), - type=lambda value: min(max(int(value), 1), 90), - help='''Set Triforce Pieces required to win a Triforce Hunt''') - parser.add_argument('--difficulty', default=defval('normal'), const='normal', nargs='?', - choices=['easy', 'normal', 'hard', 'expert'], - help='''\ - Select game difficulty. Affects available itempool. (default: %(default)s) - Easy: An easier setting with some equipment duplicated and increased health. - Normal: Normal difficulty. - Hard: A harder setting with less equipment and reduced health. - Expert: A harder yet setting with minimum equipment and health. - ''') - parser.add_argument('--item_functionality', default=defval('normal'), const='normal', nargs='?', - choices=['easy', 'normal', 'hard', 'expert'], - help='''\ - Select limits on item functionality to increase difficulty. (default: %(default)s) - Easy: Easy functionality. (Medallions usable without sword) - Normal: Normal functionality. - Hard: Reduced functionality. - Expert: Greatly reduced functionality. - ''') - parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'], - help='''\ - Select game timer setting. Affects available itempool. (default: %(default)s) - None: No timer. - Display: Displays a timer but does not affect - the itempool. - Timed: Starts with clock at zero. Green Clocks - subtract 4 minutes (Total: 20), Blue Clocks - subtract 2 minutes (Total: 10), Red Clocks add - 2 minutes (Total: 10). Winner is player with - lowest time at the end. - Timed OHKO: Starts clock at 10 minutes. Green Clocks add - 5 minutes (Total: 25). As long as clock is at 0, - Link will die in one hit. - OHKO: Like Timed OHKO, but no clock items are present - and the clock is permenantly at zero. - Timed Countdown: Starts with clock at 40 minutes. Same clocks as - Timed mode. If time runs out, you lose (but can - still keep playing). - ''') - parser.add_argument('--countdown_start_time', default=defval(10), type=int, - help='''Set amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes''') - parser.add_argument('--red_clock_time', default=defval(-2), type=int, - help='''Set amount of time, in minutes, to add from picking up red clocks; negative removes time instead''') - parser.add_argument('--blue_clock_time', default=defval(2), type=int, - help='''Set amount of time, in minutes, to add from picking up blue clocks; negative removes time instead''') - parser.add_argument('--green_clock_time', default=defval(4), type=int, - help='''Set amount of time, in minutes, to add from picking up green clocks; negative removes time instead''') - parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'], - help='''\ - Select dungeon counter display settings. (default: %(default)s) - (Note, since timer takes up the same space on the hud as dungeon - counters, timer settings override dungeon counter settings.) - Default: Dungeon counters only show when the compass is - picked up, or otherwise sent, only when compass - shuffle is turned on. - On: Dungeon counters are always displayed. - Pickup: Dungeon counters are shown when the compass is - picked up, even when compass shuffle is turned - off. - Off: Dungeon counters are never shown. - ''') - parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?', - choices=['freshness', 'flood', 'vt25', 'vt26', 'balanced'], - help='''\ - Select item filling algorithm. (default: %(default)s - balanced: vt26 derivitive that aims to strike a balance between - the overworld heavy vt25 and the dungeon heavy vt26 - algorithm. - vt26: Shuffle items and place them in a random location - that it is not impossible to be in. This includes - dungeon keys and items. - vt25: Shuffle items and place them in a random location - that it is not impossible to be in. - Flood: Push out items starting from Link\'s House and - slightly biased to placing progression items with - less restrictions. - ''') - parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed'], - help='''\ - Select Entrance Shuffling Algorithm. (default: %(default)s) - Full: Mix cave and dungeon entrances freely while limiting - multi-entrance caves to one world. - Simple: Shuffle Dungeon Entrances/Exits between each other - and keep all 4-entrance dungeons confined to one - location. All caves outside of death mountain are - shuffled in pairs and matched by original type. - Restricted: Use Dungeons shuffling from Simple but freely - connect remaining entrances. - Crossed: Mix cave and dungeon entrances freely while allowing - caves to cross between worlds. - Insanity: Decouple entrances and exits from each other and - shuffle them freely. Caves that used to be single - entrance will still exit to the same location from - which they are entered. - Vanilla: All entrances are in the same locations they were - in the base game. - Legacy shuffles preserve behavior from older versions of the - entrance randomizer including significant technical limitations. - The dungeon variants only mix up dungeons and keep the rest of - the overworld vanilla. - ''') - parser.add_argument('--open_pyramid', default=defval('auto'), help='''\ - Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. - Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon. - fast ganon goals are crystals, ganon_triforce_hunt, local_ganon_triforce_hunt, pedestalganon - auto - Only opens pyramid hole if the goal specifies a fast ganon, and entrance shuffle - is vanilla, dungeons_simple or dungeons_full. - goal - Opens pyramid hole if the goal specifies a fast ganon. - yes - Always opens the pyramid hole. - no - Never opens the pyramid hole. - ''', choices=['auto', 'goal', 'yes', 'no']) - - parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') parser.add_argument('--seed', help='Define seed number to generate.', type=int) parser.add_argument('--count', help='''\ Use to batch generate multiple seeds with same settings. @@ -195,16 +32,6 @@ def defval(value): --seed given will produce the same 10 (different) roms each time). ''', type=int) - - parser.add_argument('--custom', default=defval(False), help='Not supported.') - parser.add_argument('--customitemarray', default=defval(False), help='Not supported.') - # included for backwards compatibility - parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(True)) - parser.add_argument('--no-shuffleganon', help='''\ - If set, the Pyramid Hole and Ganon's Tower are not - included entrance shuffle pool. - ''', action='store_false', dest='shuffleganon') - parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes, @@ -212,35 +39,12 @@ def defval(value): Alternatively, can be a ALttP Rom patched with a Link sprite that will be extracted. ''') - - parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos', - "singularity"]) - - parser.add_argument('--enemy_health', default=defval('default'), - choices=['default', 'easy', 'normal', 'hard', 'expert']) - parser.add_argument('--enemy_damage', default=defval('default'), choices=['default', 'shuffled', 'chaos']) - parser.add_argument('--beemizer_total_chance', default=defval(0), type=lambda value: min(max(int(value), 0), 100)) - parser.add_argument('--beemizer_trap_chance', default=defval(0), type=lambda value: min(max(int(value), 0), 100)) - parser.add_argument('--shop_shuffle', default='', help='''\ - combine letters for options: - g: generate default inventories for light and dark world shops, and unique shops - f: generate default inventories for each shop individually - i: shuffle the default inventories of the shops around - p: randomize the prices of the items in shop inventories - u: shuffle capacity upgrades into the item pool - w: consider witch's hut like any other shop and shuffle/randomize it too - ''') - parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb']) parser.add_argument('--sprite_pool', help='''\ Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''') - parser.add_argument('--dark_room_logic', default=('Lamp'), choices=["lamp", "torches", "none"], help='''\ - For unlit dark rooms, require the Lamp to be considered in logic by default. - Torches means additionally easily accessible Torches that can be lit with Fire Rod are considered doable. - None means full traversal through dark rooms without tools is considered doable.''') parser.add_argument('--multi', default=defval(1), type=lambda value: max(int(value), 1)) parser.add_argument('--names', default=defval('')) parser.add_argument('--outputpath') - parser.add_argument('--game', default="A Link to the Past") + parser.add_argument('--game', default="Archipelago") parser.add_argument('--race', default=defval(False), action='store_true') parser.add_argument('--outputname') if multiargs.multi: @@ -249,43 +53,21 @@ def defval(value): ret = parser.parse_args(argv) - # shuffle medallions - - ret.required_medallions = ("random", "random") # cannot be set through CLI currently ret.plando_items = [] ret.plando_texts = {} ret.plando_connections = [] - if ret.timer == "none": - ret.timer = False - if ret.dungeon_counters == 'on': - ret.dungeon_counters = True - elif ret.dungeon_counters == 'off': - ret.dungeon_counters = False - if multiargs.multi: defaults = copy.deepcopy(ret) for player in range(1, multiargs.multi + 1): playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True) - for name in ['logic', 'mode', 'goal', 'difficulty', 'item_functionality', - 'shuffle', 'open_pyramid', 'timer', - 'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time', - 'beemizer_total_chance', 'beemizer_trap_chance', - 'shufflebosses', 'enemy_health', 'enemy_damage', - 'sprite', - "triforce_pieces_available", - "triforce_pieces_required", "shop_shuffle", - "required_medallions", - "plando_items", "plando_texts", "plando_connections", - 'dungeon_counters', - 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', - 'game']: + for name in ["plando_items", "plando_texts", "plando_connections", "game", "sprite", "sprite_pool"]: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) else: getattr(ret, name)[player] = value - return ret \ No newline at end of file + return ret diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index fceba86a739e..f759b6309a0e 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -3,6 +3,8 @@ from .OverworldGlitchRules import overworld_glitch_connections from .UnderworldGlitchRules import underworld_glitch_connections +from .Regions import mark_light_world_regions +from .InvertedRegions import mark_dark_world_regions def link_entrances(world, player): @@ -552,19 +554,20 @@ def connect_reachable_exit(entrance, caves, doors): # check for swamp palace fix if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': - world.swamp_patch_required[player] = True + world.worlds[player].swamp_patch_required = True # check for potion shop location if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop': - world.powder_patch_required[player] = True + world.worlds[player].powder_patch_required = True # check for ganon location if world.get_entrance('Pyramid Hole', player).connected_region.name != 'Pyramid': - world.ganon_at_pyramid[player] = False + world.worlds[player].ganon_at_pyramid = False # check for Ganon's Tower location if world.get_entrance('Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)': - world.ganonstower_vanilla[player] = False + world.worlds[player].ganonstower_vanilla = False + def link_inverted_entrances(world, player): # Link's house shuffled freely, Houlihan set in mandatory_connections @@ -1259,19 +1262,19 @@ def connect_reachable_exit(entrance, caves, doors): # patch swamp drain if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': - world.swamp_patch_required[player] = True + world.worlds[player].swamp_patch_required = True # check for potion shop location if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop': - world.powder_patch_required[player] = True + world.worlds[player].powder_patch_required = True # check for ganon location if world.get_entrance('Inverted Pyramid Hole', player).connected_region.name != 'Pyramid': - world.ganon_at_pyramid[player] = False + world.worlds[player].ganon_at_pyramid = False # check for Ganon's Tower location if world.get_entrance('Inverted Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)': - world.ganonstower_vanilla[player] = False + world.worlds[player].ganonstower_vanilla = False def connect_simple(world, exitname, regionname, player): @@ -1434,7 +1437,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): invalid_cave_connections = defaultdict(set) if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: - from worlds.alttp import OverworldGlitchRules + from . import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): invalid_connections[entrance] = set() if entrance in must_be_exits: @@ -1827,6 +1830,10 @@ def plando_connect(world, player: int): func(world, connection.entrance, connection.exit, player) except Exception as e: raise Exception(f"Could not connect using {connection}") from e + if world.mode[player] != 'inverted': + mark_light_world_regions(world, player) + else: + mark_dark_world_regions(world, player) LW_Dungeon_Entrances = ['Desert Palace Entrance (South)', @@ -2651,6 +2658,10 @@ def plando_connect(world, player: int): ('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Crystaroller Room)'), ('Turtle Rock (Dark Room) (South)', 'Turtle Rock (Eye Bridge)'), ('Turtle Rock Dark Room (South)', 'Turtle Rock (Dark Room)'), + ('Turtle Rock Second Section Bomb Wall', 'Turtle Rock (Second Section Bomb Wall)'), + ('Turtle Rock Second Section from Bomb Wall', 'Turtle Rock (Second Section)'), + ('Turtle Rock Eye Bridge Bomb Wall', 'Turtle Rock (Eye Bridge Bomb Wall)'), + ('Turtle Rock Eye Bridge from Bomb Wall', 'Turtle Rock (Eye Bridge)'), ('Turtle Rock (Trinexx)', 'Turtle Rock (Trinexx)'), ('Palace of Darkness Bridge Room', 'Palace of Darkness (Center)'), ('Palace of Darkness Bonk Wall', 'Palace of Darkness (Bonk Section)'), @@ -2809,6 +2820,10 @@ def plando_connect(world, player: int): ('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Crystaroller Room)'), ('Turtle Rock (Dark Room) (South)', 'Turtle Rock (Eye Bridge)'), ('Turtle Rock Dark Room (South)', 'Turtle Rock (Dark Room)'), + ('Turtle Rock Second Section Bomb Wall', 'Turtle Rock (Second Section Bomb Wall)'), + ('Turtle Rock Second Section from Bomb Wall', 'Turtle Rock (Second Section)'), + ('Turtle Rock Eye Bridge Bomb Wall', 'Turtle Rock (Eye Bridge Bomb Wall)'), + ('Turtle Rock Eye Bridge from Bomb Wall', 'Turtle Rock (Eye Bridge)'), ('Turtle Rock (Trinexx)', 'Turtle Rock (Trinexx)'), ('Palace of Darkness Bridge Room', 'Palace of Darkness (Center)'), ('Palace of Darkness Bonk Wall', 'Palace of Darkness (Bonk Section)'), diff --git a/worlds/alttp/InvertedRegions.py b/worlds/alttp/InvertedRegions.py index 2e30fde8cc85..63a2d499e2d4 100644 --- a/worlds/alttp/InvertedRegions.py +++ b/worlds/alttp/InvertedRegions.py @@ -381,8 +381,8 @@ def create_inverted_regions(world, player): create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']), create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']), create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), - create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room', 'Skull Woods - Spike Corner Key Drop'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), - create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), + create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), + create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']), create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']), create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']), create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest', @@ -408,14 +408,16 @@ def create_inverted_regions(world, player): ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']), create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'], - ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', - 'Turtle Rock Big Key Door']), + ['Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door', + 'Turtle Rock Second Section Bomb Wall']), + create_dungeon_region(world, player, 'Turtle Rock (Second Section Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Second Section from Bomb Wall']), create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']), create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']), create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']), + create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Isolated Ledge Exit', 'Turtle Rock Eye Bridge from Bomb Wall']), create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'], - ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']), + ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Eye Bridge Bomb Wall']), create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']), create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']), create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'], diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 3929342aa56f..125475561bb2 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -238,7 +238,7 @@ def generate_itempool(world): raise NotImplementedError(f"Timer {multiworld.timer[player]} for player {player}") if multiworld.timer[player] in ['ohko', 'timed_ohko']: - multiworld.can_take_damage[player] = False + world.can_take_damage = False if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False) else: @@ -253,10 +253,8 @@ def generate_itempool(world): region.locations.append(loc) multiworld.push_item(loc, item_factory('Triforce', world), False) - loc.event = True loc.locked = True - multiworld.get_location('Ganon', player).event = True multiworld.get_location('Ganon', player).locked = True event_pairs = [ ('Agahnim 1', 'Beat Agahnim 1'), @@ -273,18 +271,19 @@ def generate_itempool(world): location = multiworld.get_location(location_name, player) event = item_factory(event_name, world) multiworld.push_item(location, event, False) - location.event = location.locked = True + location.locked = True # set up item pool additional_triforce_pieces = 0 + treasure_hunt_total = 0 if multiworld.custom: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, - treasure_hunt_icon) = make_custom_item_pool(multiworld, player) + pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = ( + make_custom_item_pool(multiworld, player)) multiworld.rupoor_cost = min(multiworld.customitemarray[67], 9999) else: - pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, \ - treasure_hunt_icon, additional_triforce_pieces = get_pool_core(multiworld, player) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total, + additional_triforce_pieces) = get_pool_core(multiworld, player) for item in precollected_items: multiworld.push_precollected(item_factory(item, world)) @@ -319,11 +318,11 @@ def generate_itempool(world): 'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']): if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]: if 'Bow' in placed_items["Link's Uncle"]: - multiworld.escape_assist[player].append('arrows') + multiworld.worlds[player].escape_assist.append('arrows') elif 'Cane' in placed_items["Link's Uncle"]: - multiworld.escape_assist[player].append('magic') + multiworld.worlds[player].escape_assist.append('magic') else: - multiworld.escape_assist[player].append('bombs') + multiworld.worlds[player].escape_assist.append('bombs') for (location, item) in placed_items.items(): multiworld.get_location(location, player).place_locked_item(item_factory(item, world)) @@ -336,13 +335,11 @@ def generate_itempool(world): item.code = 0x65 # Progressive Bow (Alt) break - if clock_mode is not None: - multiworld.clock_mode[player] = clock_mode + if clock_mode: + world.clock_mode = clock_mode - if treasure_hunt_count is not None: - multiworld.treasure_hunt_count[player] = treasure_hunt_count % 999 - if treasure_hunt_icon is not None: - multiworld.treasure_hunt_icon[player] = treasure_hunt_icon + multiworld.worlds[player].treasure_hunt_required = treasure_hunt_required % 999 + multiworld.worlds[player].treasure_hunt_total = treasure_hunt_total dungeon_items = [item for item in get_dungeon_item_pool_player(world) if item.name not in multiworld.worlds[player].dungeon_local_item_names] @@ -371,7 +368,7 @@ def generate_itempool(world): elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: # key drop shuffle and universal keys are on. Add universal keys in place of key drop keys. multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world)) - dungeon_item_replacements = sum(difficulties[multiworld.difficulty[player]].extras, []) * 2 + dungeon_item_replacements = sum(difficulties[world.options.item_pool.current_key].extras, []) * 2 multiworld.random.shuffle(dungeon_item_replacements) for x in range(len(dungeon_items)-1, -1, -1): @@ -466,8 +463,6 @@ def cut_item(items, item_to_cut, minimum_items): while len(items) > pool_count: items_were_cut = False for reduce_item in items_reduction_table: - if len(items) <= pool_count: - break if len(reduce_item) == 2: items_were_cut = items_were_cut or cut_item(items, *reduce_item) elif len(reduce_item) == 4: @@ -479,7 +474,10 @@ def cut_item(items, item_to_cut, minimum_items): items.remove(bottle) removed_filler.append(bottle) items_were_cut = True - assert items_were_cut, f"Failed to limit item pool size for player {player}" + if items_were_cut: + break + else: + raise Exception(f"Failed to limit item pool size for player {player}") if len(items) < pool_count: items += removed_filler[len(items) - pool_count:] @@ -500,15 +498,15 @@ def cut_item(items, item_to_cut, minimum_items): for i in range(4): next(adv_heart_pieces).classification = ItemClassification.progression - multiworld.required_medallions[player] = (multiworld.misery_mire_medallion[player].current_key.title(), - multiworld.turtle_rock_medallion[player].current_key.title()) + world.required_medallions = (multiworld.misery_mire_medallion[player].current_key.title(), + multiworld.turtle_rock_medallion[player].current_key.title()) place_bosses(world) multiworld.itempool += items if multiworld.retro_caves[player]: - set_up_take_anys(multiworld, player) # depends on world.itempool to be set + set_up_take_anys(multiworld, world, player) # depends on world.itempool to be set take_any_locations = { @@ -528,30 +526,30 @@ def cut_item(items, item_to_cut, minimum_items): take_any_locations.sort() -def set_up_take_anys(world, player): +def set_up_take_anys(multiworld, world, player): # these are references, do not modify these lists in-place - if world.mode[player] == 'inverted': + if multiworld.mode[player] == 'inverted': take_any_locs = take_any_locations_inverted else: take_any_locs = take_any_locations - regions = world.random.sample(take_any_locs, 5) + regions = multiworld.random.sample(take_any_locs, 5) - old_man_take_any = LTTPRegion("Old Man Sword Cave", LTTPRegionType.Cave, 'the sword cave', player, world) - world.regions.append(old_man_take_any) + old_man_take_any = LTTPRegion("Old Man Sword Cave", LTTPRegionType.Cave, 'the sword cave', player, multiworld) + multiworld.regions.append(old_man_take_any) reg = regions.pop() - entrance = world.get_region(reg, player).entrances[0] - connect_entrance(world, entrance.name, old_man_take_any.name, player) + entrance = multiworld.get_region(reg, player).entrances[0] + connect_entrance(multiworld, entrance.name, old_man_take_any.name, player) entrance.target = 0x58 old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True, total_shop_slots) - world.shops.append(old_man_take_any.shop) + multiworld.shops.append(old_man_take_any.shop) - swords = [item for item in world.itempool if item.player == player and item.type == 'Sword'] + swords = [item for item in multiworld.itempool if item.player == player and item.type == 'Sword'] if swords: - sword = world.random.choice(swords) - world.itempool.remove(sword) - world.itempool.append(item_factory('Rupees (20)', world)) + sword = multiworld.random.choice(swords) + multiworld.itempool.remove(sword) + multiworld.itempool.append(item_factory('Rupees (20)', world)) old_man_take_any.shop.add_inventory(0, sword.name, 0, 0) loc_name = "Old Man Sword Cave" location = ALttPLocation(player, loc_name, shop_table_by_location[loc_name], parent=old_man_take_any) @@ -562,16 +560,16 @@ def set_up_take_anys(world, player): old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0) for num in range(4): - take_any = LTTPRegion("Take-Any #{}".format(num+1), LTTPRegionType.Cave, 'a cave of choice', player, world) - world.regions.append(take_any) + take_any = LTTPRegion("Take-Any #{}".format(num+1), LTTPRegionType.Cave, 'a cave of choice', player, multiworld) + multiworld.regions.append(take_any) - target, room_id = world.random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) + target, room_id = multiworld.random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) reg = regions.pop() - entrance = world.get_region(reg, player).entrances[0] - connect_entrance(world, entrance.name, take_any.name, player) + entrance = multiworld.get_region(reg, player).entrances[0] + connect_entrance(multiworld, entrance.name, take_any.name, player) entrance.target = target take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1) - world.shops.append(take_any.shop) + multiworld.shops.append(take_any.shop) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0) take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0) location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any) @@ -593,9 +591,9 @@ def get_pool_core(world, player: int): pool = [] placed_items = {} precollected_items = [] - clock_mode = None - treasure_hunt_count = None - treasure_hunt_icon = None + clock_mode: str = "" + treasure_hunt_required: int = 0 + treasure_hunt_total: int = 0 diff = difficulties[difficulty] pool.extend(diff.alwaysitems) @@ -684,21 +682,21 @@ def place_item(loc, item): if 'triforce_hunt' in goal: if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra: - triforce_pieces = world.triforce_pieces_available[player].value + world.triforce_pieces_extra[player].value + treasure_hunt_total = (world.triforce_pieces_available[player].value + + world.triforce_pieces_extra[player].value) elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage: - percentage = float(max(100, world.triforce_pieces_percentage[player].value)) / 100 - triforce_pieces = int(round(world.triforce_pieces_required[player].value * percentage, 0)) + percentage = float(world.triforce_pieces_percentage[player].value) / 100 + treasure_hunt_total = int(round(world.triforce_pieces_required[player].value * percentage, 0)) else: # available - triforce_pieces = world.triforce_pieces_available[player].value + treasure_hunt_total = world.triforce_pieces_available[player].value - triforce_pieces = max(triforce_pieces, world.triforce_pieces_required[player].value) + triforce_pieces = min(90, max(treasure_hunt_total, world.triforce_pieces_required[player].value)) pieces_in_core = min(extraitems, triforce_pieces) additional_pieces_to_place = triforce_pieces - pieces_in_core pool.extend(["Triforce Piece"] * pieces_in_core) extraitems -= pieces_in_core - treasure_hunt_count = world.triforce_pieces_required[player].value - treasure_hunt_icon = 'Triforce Piece' + treasure_hunt_required = world.triforce_pieces_required[player].value for extra in diff.extras: if extraitems >= len(extra): @@ -739,7 +737,7 @@ def place_item(loc, item): place_item(key_location, "Small Key (Universal)") pool = pool[:-3] - return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, + return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total, additional_pieces_to_place) @@ -754,9 +752,9 @@ def make_custom_item_pool(world, player): pool = [] placed_items = {} precollected_items = [] - clock_mode = None - treasure_hunt_count = None - treasure_hunt_icon = None + clock_mode: str = "" + treasure_hunt_required: int = 0 + treasure_hunt_total: int = 0 def place_item(loc, item): assert loc not in placed_items, "cannot place item twice" @@ -851,8 +849,7 @@ def place_item(loc, item): if "triforce" in world.goal[player]: pool.extend(["Triforce Piece"] * world.triforce_pieces_available[player]) itemtotal += world.triforce_pieces_available[player] - treasure_hunt_count = world.triforce_pieces_required[player] - treasure_hunt_icon = 'Triforce Piece' + treasure_hunt_required = world.triforce_pieces_required[player] if timer in ['display', 'timed', 'timed_countdown']: clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch' @@ -897,4 +894,4 @@ def place_item(loc, item): pool.extend(['Nothing'] * (total_items_to_place - itemtotal)) logging.warning(f"Pool was filled up with {total_items_to_place - itemtotal} Nothing's for player {player}") - return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon) + return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index 2b23dc341c43..20dd18038a14 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -1,8 +1,11 @@ import typing from BaseClasses import MultiWorld -from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses,\ - FreeText +from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, Option, \ + PlandoBosses, PlandoConnections, PlandoTexts, Removed, StartInventoryPool, Toggle +from .EntranceShuffle import default_connections, default_dungeon_connections, \ + inverted_default_connections, inverted_default_dungeon_connections +from .Text import TextTable class GlitchesRequired(Choice): @@ -483,7 +486,7 @@ class LTTPBosses(PlandoBosses): @classmethod def can_place_boss(cls, boss: str, location: str) -> bool: - from worlds.alttp.Bosses import can_place_boss + from .Bosses import can_place_boss level = '' words = location.split(" ") if words[-1] in ("top", "middle", "bottom"): @@ -716,13 +719,33 @@ class BeemizerTrapChance(BeemizerRange): display_name = "Beemizer Trap Chance" -class AllowCollect(Toggle): - """Allows for !collect / co-op to auto-open chests containing items for other players. - Off by default, because it currently crashes on real hardware.""" +class AllowCollect(DefaultOnToggle): + """Allows for !collect / co-op to auto-open chests containing items for other players.""" display_name = "Allow Collection of checks for other players" +class ALttPPlandoConnections(PlandoConnections): + entrances = set([connection[0] for connection in ( + *default_connections, *default_dungeon_connections, *inverted_default_connections, + *inverted_default_dungeon_connections)]) + exits = set([connection[1] for connection in ( + *default_connections, *default_dungeon_connections, *inverted_default_connections, + *inverted_default_dungeon_connections)]) + + +class ALttPPlandoTexts(PlandoTexts): + """Text plando. Format is: + - text: 'This is your text' + at: text_key + percentage: 100 + Percentage is an integer from 1 to 100, and defaults to 100 when omitted.""" + valid_keys = TextTable.valid_keys + + alttp_options: typing.Dict[str, type(Option)] = { + "accessibility": ItemsAccessibility, + "plando_connections": ALttPPlandoConnections, + "plando_texts": ALttPPlandoTexts, "start_inventory_from_pool": StartInventoryPool, "goal": Goal, "mode": Mode, @@ -796,4 +819,9 @@ class AllowCollect(Toggle): "music": Music, "reduceflashing": ReduceFlashing, "triforcehud": TriforceHud, + + # removed: + "goals": Removed, + "smallkey_shuffle": Removed, + "bigkey_shuffle": Removed, } diff --git a/worlds/alttp/OverworldGlitchRules.py b/worlds/alttp/OverworldGlitchRules.py index 146fc2f0cac9..2da76234bd40 100644 --- a/worlds/alttp/OverworldGlitchRules.py +++ b/worlds/alttp/OverworldGlitchRules.py @@ -220,26 +220,7 @@ def get_invalid_bunny_revival_dungeons(): yield 'Sanctuary' -def no_logic_rules(world, player): - """ - Add OWG transitions to no logic player's world - """ - create_no_logic_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) - create_no_logic_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player)) - - # Glitched speed drops. - create_no_logic_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted')) - - # Mirror clip spots. - if world.mode[player] != 'inverted': - create_no_logic_connections(player, world, get_mirror_clip_spots_dw()) - create_no_logic_connections(player, world, get_mirror_offset_spots_dw()) - else: - create_no_logic_connections(player, world, get_mirror_offset_spots_lw(player)) - - def overworld_glitch_connections(world, player): - # Boots-accessible locations. create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player)) diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index dc3adb108af1..f3dbbdc059f1 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -336,13 +336,15 @@ def create_regions(world, player): ['Turtle Rock Entrance to Pokey Room', 'Turtle Rock Entrance Gap Reverse']), create_dungeon_region(world, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']), create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']), - create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']), + create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'], ['Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door', 'Turtle Rock Second Section Bomb Wall']), + create_dungeon_region(world, player, 'Turtle Rock (Second Section Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Second Section from Bomb Wall']), create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']), create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']), create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']), + create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Isolated Ledge Exit', 'Turtle Rock Eye Bridge from Bomb Wall']), create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'], - ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']), + ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Eye Bridge Bomb Wall']), create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']), create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']), create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'], @@ -404,7 +406,7 @@ def create_dungeon_region(world: MultiWorld, player: int, name: str, hint: str, def _create_region(world: MultiWorld, player: int, name: str, type: LTTPRegionType, hint: str, locations=None, exits=None): - from worlds.alttp.SubClasses import ALttPLocation + from .SubClasses import ALttPLocation ret = LTTPRegion(name, type, hint, player, world) if exits: for exit in exits: @@ -758,7 +760,7 @@ def mark_light_world_regions(world, player: int): 'Turtle Rock - Prize': ( [0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')} -from worlds.alttp.Shops import shop_table_by_location_id, shop_table_by_location +from .Shops import shop_table_by_location_id, shop_table_by_location lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int} lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}} lookup_id_to_name.update(shop_table_by_location_id) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 6ef1f0db1947..224de6aaf7f3 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -4,7 +4,7 @@ import worlds.Files LTTPJPN10HASH: str = "03a63945398191337e896e5771f77173" -RANDOMIZERBASEHASH: str = "35d010bc148e0ea0ee68e81e330223f1" +RANDOMIZERBASEHASH: str = "8704fb9b9fa4fad52d4d2f9a95fb5360" ROM_PLAYER_LIMIT: int = 255 import io @@ -18,7 +18,7 @@ import threading import concurrent.futures import bsdiff4 -from typing import Optional, List +from typing import Collection, Optional, List, SupportsIndex from BaseClasses import CollectionState, Region, Location, MultiWorld from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml, read_snes_rom @@ -52,7 +52,7 @@ enemizer_logger = logging.getLogger("Enemizer") -class LocalRom(object): +class LocalRom: def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): self.name = name @@ -71,13 +71,13 @@ def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): def read_byte(self, address: int) -> int: return self.buffer[address] - def read_bytes(self, startaddress: int, length: int) -> bytes: + def read_bytes(self, startaddress: int, length: int) -> bytearray: return self.buffer[startaddress:startaddress + length] def write_byte(self, address: int, value: int): self.buffer[address] = value - def write_bytes(self, startaddress: int, values): + def write_bytes(self, startaddress: int, values: Collection[SupportsIndex]) -> None: self.buffer[startaddress:startaddress + len(values)] = values def encrypt_range(self, startaddress: int, length: int, key: bytes): @@ -433,7 +433,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): if multiworld.key_drop_shuffle[player]: key_drop_enemies = { 0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201, - 0x4E20A, 0x4E326, 0x4E4F7, 0x4E686, 0x4E70C, 0x4E7C8, 0x4E7FA + 0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA } for enemy in key_drop_enemies: if rom.read_byte(enemy) == 0x12: @@ -868,11 +868,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}): # For exits that connot be reached from another, no need to apply offset fixes. rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else - elif room_id == 0x0059 and world.fix_skullwoods_exit[player]: + elif room_id == 0x0059 and local_world.fix_skullwoods_exit: rom.write_int16(0x15DB5 + 2 * offset, 0x00F8) - elif room_id == 0x004a and world.fix_palaceofdarkness_exit[player]: + elif room_id == 0x004a and local_world.fix_palaceofdarkness_exit: rom.write_int16(0x15DB5 + 2 * offset, 0x0640) - elif room_id == 0x00d6 and world.fix_trock_exit[player]: + elif room_id == 0x00d6 and local_world.fix_trock_exit: rom.write_int16(0x15DB5 + 2 * offset, 0x0134) elif room_id == 0x000c and world.shuffle_ganon: # fix ganons tower exit point rom.write_int16(0x15DB5 + 2 * offset, 0x00A4) @@ -945,22 +945,22 @@ def credits_digit(num): rom.write_bytes(0x118C64, [first_bot, mid_bot, last_bot]) # patch medallion requirements - if world.required_medallions[player][0] == 'Bombos': + if local_world.required_medallions[0] == 'Bombos': rom.write_byte(0x180022, 0x00) # requirement rom.write_byte(0x4FF2, 0x31) # sprite rom.write_byte(0x50D1, 0x80) rom.write_byte(0x51B0, 0x00) - elif world.required_medallions[player][0] == 'Quake': + elif local_world.required_medallions[0] == 'Quake': rom.write_byte(0x180022, 0x02) # requirement rom.write_byte(0x4FF2, 0x31) # sprite rom.write_byte(0x50D1, 0x88) rom.write_byte(0x51B0, 0x00) - if world.required_medallions[player][1] == 'Bombos': + if local_world.required_medallions[1] == 'Bombos': rom.write_byte(0x180023, 0x00) # requirement rom.write_byte(0x5020, 0x31) # sprite rom.write_byte(0x50FF, 0x90) rom.write_byte(0x51DE, 0x00) - elif world.required_medallions[player][1] == 'Ether': + elif local_world.required_medallions[1] == 'Ether': rom.write_byte(0x180023, 0x01) # requirement rom.write_byte(0x5020, 0x31) # sprite rom.write_byte(0x50FF, 0x98) @@ -1069,7 +1069,7 @@ def credits_digit(num): # Byrna residual magic cost rom.write_bytes(0x45C42, [0x04, 0x02, 0x01]) - difficulty = world.difficulty_requirements[player] + difficulty = local_world.difficulty_requirements # Set overflow items for progressive equipment rom.write_bytes(0x180090, @@ -1240,17 +1240,17 @@ def chunk(l, n): rom.write_byte(0x180044, 0x01) # hammer activates tablets # set up clocks for timed modes - if world.clock_mode[player] in ['ohko', 'countdown-ohko']: + if local_world.clock_mode in ['ohko', 'countdown-ohko']: rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality - elif world.clock_mode[player] == 'stopwatch': + elif local_world.clock_mode == 'stopwatch': rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode - elif world.clock_mode[player] == 'countdown': + elif local_world.clock_mode == 'countdown': rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available else: rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode # Set up requested clock settings - if world.clock_mode[player] in ['countdown-ohko', 'stopwatch', 'countdown']: + if local_world.clock_mode in ['countdown-ohko', 'stopwatch', 'countdown']: rom.write_int32(0x180200, world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180204, @@ -1263,14 +1263,15 @@ def chunk(l, n): rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32) # Set up requested start time for countdown modes - if world.clock_mode[player] in ['countdown-ohko', 'countdown']: + if local_world.clock_mode in ['countdown-ohko', 'countdown']: rom.write_int32(0x18020C, world.countdown_start_time[player] * 60 * 60) # starting time (in frames, sint32) else: rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) # set up goals for treasure hunt - rom.write_int16(0x180163, world.treasure_hunt_count[player]) - rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) + rom.write_int16(0x180163, max(0, local_world.treasure_hunt_required - + sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece"))) + rom.write_bytes(0x180165, [0x0E, 0x28]) # Triforce Piece Sprite rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed @@ -1283,14 +1284,14 @@ def chunk(l, n): rom.write_byte(0x180211, gametype) # Game type # assorted fixes - rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[ - player] else 0x00) # Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1 + # Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1 + rom.write_byte(0x1800A2, 0x01 if local_world.fix_fake_world else 0x00) # Lock or unlock aga tower door during escape sequence. rom.write_byte(0x180169, 0x00) if world.mode[player] == 'inverted': rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180171, - 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death + 0x01 if local_world.ganon_at_pyramid else 0x00) # Enable respawning on pyramid after ganon death rom.write_byte(0x180173, 0x01) # Bob is enabled rom.write_byte(0x180168, 0x08) # Spike Cave Damage rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) # Set spike cave and MM spike room Cape usage @@ -1306,7 +1307,7 @@ def chunk(l, n): rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp - rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) + rom.write_byte(0x180174, 0x01 if local_world.fix_fake_world else 0x00) rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles # Starting equipment @@ -1372,7 +1373,7 @@ def chunk(l, n): 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Mail', - 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)'}: + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', 'Triforce Piece'}: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), @@ -1448,7 +1449,7 @@ def chunk(l, n): for address in keys[item.name]: equip[address] = min(equip[address] + 1, 99) elif item.name in bottles: - if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit: + if equip[0x34F] < local_world.difficulty_requirements.progressive_bottle_limit: equip[0x35C + equip[0x34F]] = bottles[item.name] equip[0x34F] += 1 elif item.name in rupees: @@ -1507,9 +1508,9 @@ def chunk(l, n): rom.write_bytes(0x180080, [50, 50, 70, 70]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) - rom.write_byte(0x18004D, ((0x01 if 'arrows' in world.escape_assist[player] else 0x00) | - (0x02 if 'bombs' in world.escape_assist[player] else 0x00) | - (0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist + rom.write_byte(0x18004D, ((0x01 if 'arrows' in local_world.escape_assist else 0x00) | + (0x02 if 'bombs' in local_world.escape_assist else 0x00) | + (0x04 if 'magic' in local_world.escape_assist else 0x00))) # Escape assist if world.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: rom.write_byte(0x18003E, 0x01) # make ganon invincible @@ -1546,7 +1547,7 @@ def chunk(l, n): rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld # compasses showing dungeon count - if world.clock_mode[player] or not world.dungeon_counters[player]: + if local_world.clock_mode or not world.dungeon_counters[player]: rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location elif world.dungeon_counters[player] is True: rom.write_byte(0x18003C, 0x02) # always on @@ -1616,7 +1617,7 @@ def get_reveal_bytes(itemName): rom.write_byte(0xEFD95, digging_game_rng) rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A4, 0x01 if world.glitches_required[player] != 'no_logic' else 0x00) # enable POD EG fix - rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.glitches_required[ + rom.write_byte(0x186383, 0x01 if world.glitches_required[ player] == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill @@ -1653,13 +1654,13 @@ def get_reveal_bytes(itemName): rom.write_bytes(0x18018B, [0x20, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) # patch swamp: Need to enable permanent drain of water as dam or swamp were moved - rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00) + rom.write_byte(0x18003D, 0x01 if local_world.swamp_patch_required else 0x00) # powder patch: remove the need to leave the screen after powder, since it causes problems for potion shop at race game # temporarally we are just nopping out this check we will conver this to a rom fix soon. rom.write_bytes(0x02F539, - [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, - 0x4F]) + [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if local_world.powder_patch_required else [ + 0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) # allow smith into multi-entrance caves in appropriate shuffles if world.entrance_shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or ( @@ -1674,14 +1675,14 @@ def get_reveal_bytes(itemName): rom.write_byte(0x4E3BB, 0xEB) # fix trock doors for reverse entrances - if world.fix_trock_doors[player]: + if local_world.fix_trock_doors: rom.write_byte(0xFED31, 0x0E) # preopen bombable exit rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit # included unconditionally in base2current # rom.write_byte(0xFE465, 0x1E) # remove small key door on backside of big key door else: - rom.write_byte(0xFED31, 0x2A) # preopen bombable exit - rom.write_byte(0xFEE41, 0x2A) # preopen bombable exit + rom.write_byte(0xFED31, 0x2A) # bombable exit + rom.write_byte(0xFEE41, 0x2A) # bombable exit if world.tile_shuffle[player]: tile_set = TileSet.get_random_tile_set(world.per_slot_randoms[player]) @@ -1859,7 +1860,7 @@ def apply_oof_sfx(rom, oof: str): rom.write_bytes(0x12803A, oof_bytes) rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB]) - #Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT") + # Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT") rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08]) @@ -2397,6 +2398,9 @@ def hint_text(dest, ped_hint=False): if hint_count: locations = world.find_items_in_locations(items_to_hint, player, True) local_random.shuffle(locations) + # make locked locations less likely to appear as hint, + # chances are the lock means the player already knows. + locations.sort(key=lambda sorting_location: not sorting_location.locked) for x in range(min(hint_count, len(locations))): this_location = locations.pop() this_hint = this_location.item.hint_text + ' can be found ' + hint_text(this_location) + '.' @@ -2418,7 +2422,7 @@ def hint_text(dest, ped_hint=False): ' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint - if world.worlds[player].has_progressive_bows and (world.difficulty_requirements[player].progressive_bow_limit >= 2 or ( + if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or ( world.swordless[player] or world.glitches_required[player] == 'no_glitches')): prog_bow_locs = world.find_item_locations('Progressive Bow', player, True) world.per_slot_randoms[player].shuffle(prog_bow_locs) @@ -2472,6 +2476,9 @@ def hint_text(dest, ped_hint=False): tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)] tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)] + triforce_pieces_required = max(0, w.treasure_hunt_required - + sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece")) + if world.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' @@ -2479,16 +2486,16 @@ def hint_text(dest, ped_hint=False): tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!' else: tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' - if world.treasure_hunt_count[player] > 1: + if triforce_pieces_required > 1: tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \ "invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \ "hidden in a hollow tree. If you bring\n%d Triforce pieces out of %d, I can reassemble it." % \ - (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) + (triforce_pieces_required, w.treasure_hunt_total) else: tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \ "invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \ "hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \ - (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) + (triforce_pieces_required, w.treasure_hunt_total) elif world.goal[player] in ['pedestal']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' @@ -2497,20 +2504,20 @@ def hint_text(dest, ped_hint=False): tt['ganon_fall_in'] = Ganon1_texts[local_random.randint(0, len(Ganon1_texts) - 1)] tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' - if world.treasure_hunt_count[player] > 1: + if triforce_pieces_required > 1: if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \ - (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) + (triforce_pieces_required, w.treasure_hunt_total) elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \ - (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) + (triforce_pieces_required, w.treasure_hunt_total) else: if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \ - (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) + (triforce_pieces_required, w.treasure_hunt_total) elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \ - (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) + (triforce_pieces_required, w.treasure_hunt_total) tt['kakariko_tavern_fisherman'] = TavernMan_texts[local_random.randint(0, len(TavernMan_texts) - 1)] @@ -2535,12 +2542,12 @@ def hint_text(dest, ped_hint=False): tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}" tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}" - for at, text in world.plando_texts[player].items(): + for at, text, _ in world.plando_texts[player]: if at not in tt: raise Exception(f"No text target \"{at}\" found.") else: - tt[at] = text + tt[at] = "\n".join(text) rom.write_bytes(0xE0000, tt.getBytes()) @@ -3018,7 +3025,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: def get_base_rom_path(file_name: str = "") -> str: - options = Utils.get_options() + options = Utils.get_settings() if not file_name: file_name = options["lttp_options"]["rom_file"] if not os.path.exists(file_name): diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 320f9fe6fd6e..f596749ae669 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -2,6 +2,7 @@ import logging from typing import Iterator, Set +from Options import ItemsAccessibility from BaseClasses import Entrance, MultiWorld from worlds.generic.Rules import (add_item_rule, add_rule, forbid_item, item_name_in_location_names, location_item_name, set_rule, allow_self_locking_items) @@ -10,7 +11,7 @@ from .Bosses import GanonDefeatRule from .Items import item_factory, item_name_groups, item_table, progression_items from .Options import small_key_shuffle -from .OverworldGlitchRules import no_logic_rules, overworld_glitches_rules +from .OverworldGlitchRules import overworld_glitches_rules from .Regions import LTTPRegionType, location_table from .StateHelpers import (can_extend_magic, can_kill_most_things, can_lift_heavy_rocks, can_lift_rocks, @@ -18,7 +19,8 @@ can_shoot_arrows, has_beam_sword, has_crystals, has_fire_source, has_hearts, has_melee_weapon, has_misery_mire_medallion, has_sword, has_turtle_rock_medallion, - has_triforce_pieces, can_use_bombs, can_bomb_or_bonk) + has_triforce_pieces, can_use_bombs, can_bomb_or_bonk, + can_activate_crystal_switch) from .UnderworldGlitchRules import underworld_glitches_rules @@ -32,14 +34,13 @@ def set_rules(world): 'WARNING! Seeds generated under this logic often require major glitches and may be impossible!') if world.players == 1: - no_logic_rules(world, player) for exit in world.get_region('Menu', player).exits: exit.hide_path = True return else: # Set access rules according to max glitches for multiworld progression. # Set accessibility to none, and shuffle assuming the no logic players can always win - world.accessibility[player] = world.accessibility[player].from_text("minimal") + world.accessibility[player].value = ItemsAccessibility.option_minimal world.progression_balancing[player].value = 0 else: @@ -63,20 +64,24 @@ def set_rules(world): if world.glitches_required[player] == 'no_glitches': no_glitches_rules(world, player) + forbid_bomb_jump_requirements(world, player) elif world.glitches_required[player] == 'overworld_glitches': # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) + forbid_bomb_jump_requirements(world, player) elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) underworld_glitches_rules(world, player) + bomb_jump_requirements(world, player) elif world.glitches_required[player] == 'minor_glitches': no_glitches_rules(world, player) fake_flipper_rules(world, player) + forbid_bomb_jump_requirements(world, player) else: raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}') @@ -97,7 +102,7 @@ def set_rules(world): # if swamp and dam have not been moved we require mirror for swamp palace # however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself. - if not world.swamp_patch_required[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: + if not world.worlds[player].swamp_patch_required and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) # GT Entrance may be required for Turtle Rock for OWG and < 7 required @@ -186,244 +191,252 @@ def dungeon_boss_rules(world, player): set_defeat_dungeon_boss_rule(world.get_location(location, player)) -def global_rules(world, player): +def global_rules(multiworld: MultiWorld, player: int): + world = multiworld.worlds[player] # ganon can only carry triforce - add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) + add_item_rule(multiworld.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) # dungeon prizes can only be crystals/pendants crystals_and_pendants: Set[str] = \ {item for item, item_data in item_table.items() if item_data.type == "Crystal"} prize_locations: Iterator[str] = \ (locations for locations, location_data in location_table.items() if location_data[2] == True) for prize_location in prize_locations: - add_item_rule(world.get_location(prize_location, player), + add_item_rule(multiworld.get_location(prize_location, player), lambda item: item.name in crystals_and_pendants and item.player == player) # determines which S&Q locations are available - hide from paths since it isn't an in-game location - for exit in world.get_region('Menu', player).exits: + for exit in multiworld.get_region('Menu', player).exits: exit.hide_path = True try: - old_man_sq = world.get_entrance('Old Man S&Q', player) + old_man_sq = multiworld.get_entrance('Old Man S&Q', player) except KeyError: pass # it doesn't exist, should be dungeon-only unittests else: - old_man = world.get_location("Old Man", player) + old_man = multiworld.get_location("Old Man", player) set_rule(old_man_sq, lambda state: old_man.can_reach(state)) - set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) - set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) - set_rule(world.get_location('Purple Chest', player), + set_rule(multiworld.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) + set_rule(multiworld.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) + set_rule(multiworld.get_location('Purple Chest', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest - set_rule(world.get_location('Ether Tablet', player), lambda state: can_retrieve_tablet(state, player)) - set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) - - set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith - set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player)) - set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player)) - set_rule(world.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player)) - set_rule(world.get_location('Library', player), lambda state: state.has('Pegasus Boots', player)) - - if world.enemy_shuffle[player]: - set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and - can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Ether Tablet', player), lambda state: can_retrieve_tablet(state, player)) + set_rule(multiworld.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) + + set_rule(multiworld.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith + set_rule(multiworld.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player)) + set_rule(multiworld.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player)) + set_rule(multiworld.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player)) + set_rule(multiworld.get_location('Library', player), lambda state: state.has('Pegasus Boots', player)) + + if multiworld.enemy_shuffle[player]: + set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and + can_kill_most_things(state, player, 4)) else: - set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) - and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4)) + set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) + and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4)) or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player) or has_beam_sword(state, player))) - set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) - - set_rule(world.get_location('Aginah\'s Cave', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Blind\'s Hideout - Top', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Chicken House', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Kakariko Well - Top', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Graveyard Cave', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Sahasrahla\'s Hut - Left', player), lambda state: can_bomb_or_bonk(state, player)) - set_rule(world.get_location('Sahasrahla\'s Hut - Middle', player), lambda state: can_bomb_or_bonk(state, player)) - set_rule(world.get_location('Sahasrahla\'s Hut - Right', player), lambda state: can_bomb_or_bonk(state, player)) - set_rule(world.get_location('Paradox Cave Lower - Left', player), lambda state: can_use_bombs(state, player) - or has_beam_sword(state, player) or can_shoot_arrows(state, player) - or state.has_any(["Fire Rod", "Cane of Somaria"], player)) - set_rule(world.get_location('Paradox Cave Lower - Right', player), lambda state: can_use_bombs(state, player) - or has_beam_sword(state, player) or can_shoot_arrows(state, player) - or state.has_any(["Fire Rod", "Cane of Somaria"], player)) - set_rule(world.get_location('Paradox Cave Lower - Far Right', player), lambda state: can_use_bombs(state, player) - or has_beam_sword(state, player) or can_shoot_arrows(state, player) - or state.has_any(["Fire Rod", "Cane of Somaria"], player)) - set_rule(world.get_location('Paradox Cave Lower - Middle', player), lambda state: can_use_bombs(state, player) - or has_beam_sword(state, player) or can_shoot_arrows(state, player) - or state.has_any(["Fire Rod", "Cane of Somaria"], player)) - set_rule(world.get_location('Paradox Cave Lower - Far Left', player), lambda state: can_use_bombs(state, player) - or has_beam_sword(state, player) or can_shoot_arrows(state, player) - or state.has_any(["Fire Rod", "Cane of Somaria"], player)) - set_rule(world.get_location('Paradox Cave Upper - Left', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Paradox Cave Upper - Right', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Mini Moldorm Cave - Far Left', player), lambda state: can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Mini Moldorm Cave - Left', player), lambda state: can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Mini Moldorm Cave - Far Right', player), lambda state: can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Mini Moldorm Cave - Right', player), lambda state: can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Mini Moldorm Cave - Generous Guy', player), lambda state: can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Hype Cave - Bottom', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Hype Cave - Middle Left', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Hype Cave - Middle Right', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Hype Cave - Top', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_entrance('Light World Death Mountain Shop', player), lambda state: can_use_bombs(state, player)) - - set_rule(world.get_entrance('Two Brothers House Exit (West)', player), lambda state: can_bomb_or_bonk(state, player)) - set_rule(world.get_entrance('Two Brothers House Exit (East)', player), lambda state: can_bomb_or_bonk(state, player)) - - set_rule(world.get_location('Spike Cave', player), lambda state: + set_rule(multiworld.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) + + set_rule(multiworld.get_location('Aginah\'s Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Blind\'s Hideout - Top', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Chicken House', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Kakariko Well - Top', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Graveyard Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Sahasrahla\'s Hut - Left', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(multiworld.get_location('Sahasrahla\'s Hut - Middle', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(multiworld.get_location('Sahasrahla\'s Hut - Right', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(multiworld.get_location('Paradox Cave Lower - Left', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(multiworld.get_location('Paradox Cave Lower - Right', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(multiworld.get_location('Paradox Cave Lower - Far Right', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(multiworld.get_location('Paradox Cave Lower - Middle', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(multiworld.get_location('Paradox Cave Lower - Far Left', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(multiworld.get_location('Paradox Cave Upper - Left', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Paradox Cave Upper - Right', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Mini Moldorm Cave - Far Left', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Mini Moldorm Cave - Left', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Mini Moldorm Cave - Far Right', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Mini Moldorm Cave - Right', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Mini Moldorm Cave - Generous Guy', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Hype Cave - Bottom', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Hype Cave - Middle Left', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Hype Cave - Middle Right', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Hype Cave - Top', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Light World Death Mountain Shop', player), lambda state: can_use_bombs(state, player)) + + set_rule(multiworld.get_entrance('Two Brothers House Exit (West)', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(multiworld.get_entrance('Two Brothers House Exit (East)', player), lambda state: can_bomb_or_bonk(state, player)) + + set_rule(multiworld.get_location('Spike Cave', player), lambda state: state.has('Hammer', player) and can_lift_rocks(state, player) and ((state.has('Cape', player) and can_extend_magic(state, player, 16, True)) or (state.has('Cane of Byrna', player) and (can_extend_magic(state, player, 12, True) or - (state.multiworld.can_take_damage[player] and (state.has('Pegasus Boots', player) or has_hearts(state, player, 4)))))) + (world.can_take_damage and (state.has('Pegasus Boots', player) or has_hearts(state, player, 4)))))) ) - set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_location('Hookshot Cave - Top Left', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_location('Hookshot Cave - Bottom Right', player), + set_rule(multiworld.get_entrance('Hookshot Cave Bomb Wall (North)', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Hookshot Cave Bomb Wall (South)', player), lambda state: can_use_bombs(state, player)) + + set_rule(multiworld.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player)) + set_rule(multiworld.get_location('Hookshot Cave - Top Left', player), lambda state: state.has('Hookshot', player)) + set_rule(multiworld.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player)) - set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player)) + set_rule(multiworld.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_entrance('Sewers Door', player), + set_rule(multiworld.get_location('Hyrule Castle - Map Guard Key Drop', player), + lambda state: can_kill_most_things(state, player, 1)) + + set_rule(multiworld.get_entrance('Sewers Door', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or ( - world.small_key_shuffle[player] == small_key_shuffle.option_universal and world.mode[ + multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal and multiworld.mode[ player] == 'standard')) # standard universal small keys cannot access the shop - set_rule(world.get_entrance('Sewers Back Door', player), + set_rule(multiworld.get_entrance('Sewers Back Door', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)) - set_rule(world.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(multiworld.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player)) - set_rule(world.get_entrance('Agahnim 1', player), + set_rule(multiworld.get_entrance('Agahnim 1', player), lambda state: has_sword(state, player) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 4)) - set_rule(world.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Castle Tower - Dark Maze', player), + set_rule(multiworld.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Castle Tower - Dark Maze', player), lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)', player)) - set_rule(world.get_location('Castle Tower - Dark Archer Key Drop', player), + set_rule(multiworld.get_location('Castle Tower - Dark Archer Key Drop', player), lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 2)) - set_rule(world.get_location('Castle Tower - Circle of Pots Key Drop', player), + set_rule(multiworld.get_location('Castle Tower - Circle of Pots Key Drop', player), lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 3)) - set_always_allow(world.get_location('Eastern Palace - Big Key Chest', player), + set_always_allow(multiworld.get_location('Eastern Palace - Big Key Chest', player), lambda state, item: item.name == 'Big Key (Eastern Palace)' and item.player == player) - set_rule(world.get_location('Eastern Palace - Big Key Chest', player), + set_rule(multiworld.get_location('Eastern Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 5) and (state._lttp_has_key('Small Key (Eastern Palace)', player, 2) or ((location_item_name(state, 'Eastern Palace - Big Key Chest', player) == ('Big Key (Eastern Palace)', player) and state.has('Small Key (Eastern Palace)', player))))) - set_rule(world.get_location('Eastern Palace - Dark Eyegore Key Drop', player), + set_rule(multiworld.get_location('Eastern Palace - Dark Eyegore Key Drop', player), lambda state: state.has('Big Key (Eastern Palace)', player) and can_kill_most_things(state, player, 1)) - set_rule(world.get_location('Eastern Palace - Big Chest', player), + set_rule(multiworld.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) # not bothering to check for can_kill_most_things in the rooms leading to boss, as if you can kill a boss you should # be able to get through these rooms - ep_boss = world.get_location('Eastern Palace - Boss', player) + ep_boss = multiworld.get_location('Eastern Palace - Boss', player) add_rule(ep_boss, lambda state: state.has('Big Key (Eastern Palace)', player) and state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and ep_boss.parent_region.dungeon.boss.can_defeat(state)) - ep_prize = world.get_location('Eastern Palace - Prize', player) + ep_prize = multiworld.get_location('Eastern Palace - Prize', player) add_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and ep_prize.parent_region.dungeon.boss.can_defeat(state)) - if not world.enemy_shuffle[player]: + if not multiworld.enemy_shuffle[player]: add_rule(ep_boss, lambda state: can_shoot_arrows(state, player)) add_rule(ep_prize, lambda state: can_shoot_arrows(state, player)) # You can always kill the Stalfos' with the pots on easy/normal - if world.enemy_health[player] in ("hard", "expert") or world.enemy_shuffle[player]: + if multiworld.enemy_health[player] in ("hard", "expert") or multiworld.enemy_shuffle[player]: stalfos_rule = lambda state: can_kill_most_things(state, player, 4) for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop', 'Eastern Palace - Big Key Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize']: - add_rule(world.get_location(location, player), stalfos_rule) + add_rule(multiworld.get_location(location, player), stalfos_rule) - set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) - set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player)) + set_rule(multiworld.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) + set_rule(multiworld.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player)) - set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4)) - set_rule(world.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 3)) - set_rule(world.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player, 4)) - set_rule(world.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player, 4)) - add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) - add_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(multiworld.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4)) + set_rule(multiworld.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 3)) + set_rule(multiworld.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player, 4)) + set_rule(multiworld.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player, 4)) + add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + add_rule(multiworld.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) # logic patch to prevent placing a crystal in Desert that's required to reach the required keys - if not (world.small_key_shuffle[player] and world.big_key_shuffle[player]): - add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state)) - - set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player)) - set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player)) - if world.enemy_shuffle[player]: - add_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3)) + if not (multiworld.small_key_shuffle[player] and multiworld.big_key_shuffle[player]): + add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state)) + + set_rule(multiworld.get_location('Tower of Hera - Basement Cage', player), lambda state: can_activate_crystal_switch(state, player)) + set_rule(multiworld.get_location('Tower of Hera - Map Chest', player), lambda state: can_activate_crystal_switch(state, player)) + set_rule(multiworld.get_entrance('Tower of Hera Small Key Door', player), lambda state: can_activate_crystal_switch(state, player) and (state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))) + set_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_activate_crystal_switch(state, player) and state.has('Big Key (Tower of Hera)', player)) + if multiworld.enemy_shuffle[player]: + add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3)) else: - add_rule(world.get_entrance('Tower of Hera Big Key Door', player), + add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: (has_melee_weapon(state, player) or (state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or state.has("Cane of Byrna", player) or state.has("Cane of Somaria", player))) - set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) - set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) - if world.accessibility[player] != 'locations': - set_always_allow(world.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) - - set_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) - set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player)) - set_rule(world.get_location('Swamp Palace - Map Chest', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2)) - set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3)) - set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player)) - if world.pot_shuffle[player]: + set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) + set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) + if multiworld.accessibility[player] != 'full': + set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) + + set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) + set_rule(multiworld.get_entrance('Swamp Palace Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player)) + set_rule(multiworld.get_location('Swamp Palace - Map Chest', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2)) + set_rule(multiworld.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3)) + set_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player)) + if multiworld.pot_shuffle[player]: # it could move the key to the top right platform which can only be reached with bombs - add_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6) + add_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6) if state.has('Hookshot', player) else state._lttp_has_key('Small Key (Swamp Palace)', player, 4)) - set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) - if world.accessibility[player] != 'locations': - allow_self_locking_items(world.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') - set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5)) - if not world.small_key_shuffle[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: - forbid_item(world.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player) - set_rule(world.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) - set_rule(world.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) - if world.pot_shuffle[player]: + set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) + if multiworld.accessibility[player] != 'full': + allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') + set_rule(multiworld.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5)) + if not multiworld.small_key_shuffle[player] and multiworld.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: + forbid_item(multiworld.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player) + add_rule(multiworld.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) + add_rule(multiworld.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) + if multiworld.pot_shuffle[player]: # key can (and probably will) be moved behind bombable wall - set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player)) - - set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) - - if world.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind": - set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) + set_rule(multiworld.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_location('Thieves\' Town - Big Chest', player), + set_rule(multiworld.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) + if multiworld.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind": + set_rule(multiworld.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) + set_rule(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player)) - if world.accessibility[player] != 'locations': - set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player) - - set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) - set_rule(world.get_location('Thieves\' Town - Spike Switch Pot Key', player), + set_rule(multiworld.get_location('Thieves\' Town - Blind\'s Cell', player), + lambda state: state._lttp_has_key('Small Key (Thieves Town)', player)) + if multiworld.accessibility[player] != 'locations' and not multiworld.key_drop_shuffle[player]: + set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player) + set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) + set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player)) # We need so many keys in the SW doors because they are all reachable as the last door (except for the door to mothula) - set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) - if world.accessibility[player] != 'locations': - allow_self_locking_items(world.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') - set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain - add_rule(world.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - add_rule(world.get_location('Skull Woods - Boss', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - - set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player)) - set_rule(world.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player)) - set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player)) - - set_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2)) - set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) - set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5)))) + set_rule(multiworld.get_entrance('Skull Woods First Section South Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) + set_rule(multiworld.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) + set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) + set_rule(multiworld.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) + set_rule(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) + if multiworld.accessibility[player] != 'full': + allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') + set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain + add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) + add_rule(multiworld.get_location('Skull Woods - Boss', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) + + set_rule(multiworld.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player)) + set_rule(multiworld.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player)) + set_rule(multiworld.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player)) + + set_rule(multiworld.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2)) + set_rule(multiworld.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) + set_rule(multiworld.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5)))) # This is a complicated rule, so let's break it down. # Hookshot always suffices to get to the right side. # Also, once you get over there, you have to cross the spikes, so that's the last line. @@ -433,96 +446,104 @@ def global_rules(world, player): # Hence if big key is available then it's 6 keys, otherwise 4 keys. # If key_drop is off, then we have 3 drop keys available, and can never satisfy the 6 key requirement because one key is on right side, # so this reduces perfectly to original logic. - set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or - (state._lttp_has_key('Small Key (Ice Palace)', player, 4) + set_rule(multiworld.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or + (state._lttp_has_key('Small Key (Ice Palace)', player, 4) if item_name_in_location_names(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Hammer Block Key Drop', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) - else state._lttp_has_key('Small Key (Ice Palace)', player, 6))) and - (state.multiworld.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))) - set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player)) + else state._lttp_has_key('Small Key (Ice Palace)', player, 6))) and ( + world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))) + set_rule(multiworld.get_entrance('Ice Palace (East Top)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player)) - set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (has_sword(state, player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or can_shoot_arrows(state, player))) # need to defeat wizzrobes, bombs don't work ... - set_rule(world.get_location('Misery Mire - Fishbone Pot Key', player), lambda state: state.has('Big Key (Misery Mire)', player) or state._lttp_has_key('Small Key (Misery Mire)', player, 4)) + set_rule(multiworld.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (has_sword(state, player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or can_shoot_arrows(state, player))) # need to defeat wizzrobes, bombs don't work ... + set_rule(multiworld.get_location('Misery Mire - Fishbone Pot Key', player), lambda state: state.has('Big Key (Misery Mire)', player) or state._lttp_has_key('Small Key (Misery Mire)', player, 4)) - set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player)) - set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.multiworld.can_take_damage[player] and has_hearts(state, player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) - set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player)) + set_rule(multiworld.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player)) + set_rule(multiworld.get_location('Misery Mire - Spike Chest', player), lambda state: (world.can_take_damage and has_hearts(state, player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) + set_rule(multiworld.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player)) # How to access crystal switch: # If have big key: then you will need 2 small keys to be able to hit switch and return to main area, as you can burn key in dark room # If not big key: cannot burn key in dark room, hence need only 1 key. all doors immediately available lead to a crystal switch. # The listed chests are those which can be reached if you can reach a crystal switch. - set_rule(world.get_location('Misery Mire - Map Chest', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2)) - set_rule(world.get_location('Misery Mire - Main Lobby', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2)) + set_rule(multiworld.get_location('Misery Mire - Map Chest', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2)) + set_rule(multiworld.get_location('Misery Mire - Main Lobby', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2)) # we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet - set_rule(world.get_location('Misery Mire - Conveyor Crystal Key Drop', player), + set_rule(multiworld.get_location('Misery Mire - Conveyor Crystal Key Drop', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 4) if location_item_name(state, 'Misery Mire - Compass Chest', player) == ('Big Key (Misery Mire)', player) or location_item_name(state, 'Misery Mire - Big Key Chest', player) == ('Big Key (Misery Mire)', player) or location_item_name(state, 'Misery Mire - Conveyor Crystal Key Drop', player) == ('Big Key (Misery Mire)', player) else state._lttp_has_key('Small Key (Misery Mire)', player, 5)) - set_rule(world.get_entrance('Misery Mire (West)', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 5) + set_rule(multiworld.get_entrance('Misery Mire (West)', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 5) if ((location_item_name(state, 'Misery Mire - Compass Chest', player) in [('Big Key (Misery Mire)', player)]) or (location_item_name(state, 'Misery Mire - Big Key Chest', player) in [('Big Key (Misery Mire)', player)])) else state._lttp_has_key('Small Key (Misery Mire)', player, 6)) - set_rule(world.get_location('Misery Mire - Compass Chest', player), lambda state: has_fire_source(state, player)) - set_rule(world.get_location('Misery Mire - Big Key Chest', player), lambda state: has_fire_source(state, player)) - set_rule(world.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player) and can_use_bombs(state, player)) - - set_rule(world.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_location('Turtle Rock - Pokey 1 Key Drop', player), lambda state: can_kill_most_things(state, player, 5)) - set_rule(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), lambda state: can_kill_most_things(state, player, 5)) - set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) - set_rule(world.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) - set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player))) - set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) - set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10)) - set_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: can_use_bombs(state, player) and can_kill_most_things(state, player, 10)) - set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: can_use_bombs(state, player) or can_shoot_arrows(state, player) - or has_beam_sword(state, player) or state.has_any(["Blue Boomerang", "Red Boomerang", "Hookshot", "Cane of Somaria", "Fire Rod", "Ice Rod"], player)) - set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) - - if world.enemy_shuffle[player]: - set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3)) + set_rule(multiworld.get_location('Misery Mire - Compass Chest', player), lambda state: has_fire_source(state, player)) + set_rule(multiworld.get_location('Misery Mire - Big Key Chest', player), lambda state: has_fire_source(state, player)) + set_rule(multiworld.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player) and can_use_bombs(state, player)) + + set_rule(multiworld.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(multiworld.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(multiworld.get_location('Turtle Rock - Pokey 1 Key Drop', player), lambda state: can_kill_most_things(state, player, 5)) + set_rule(multiworld.get_location('Turtle Rock - Pokey 2 Key Drop', player), lambda state: can_kill_most_things(state, player, 5)) + set_rule(multiworld.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(multiworld.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + set_rule(multiworld.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + set_rule(multiworld.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player))) + set_rule(multiworld.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) + set_rule(multiworld.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10) and can_bomb_or_bonk(state, player)) + set_rule(multiworld.get_location('Turtle Rock - Chain Chomps', player), lambda state: can_use_bombs(state, player) or can_shoot_arrows(state, player) + or has_beam_sword(state, player) or state.has_any(["Blue Boomerang", "Red Boomerang", "Hookshot", "Cane of Somaria", "Fire Rod", "Ice Rod"], player)) + set_rule(multiworld.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(multiworld.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(multiworld.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) + set_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_kill_most_things(state, player, 10)) + + if not multiworld.worlds[player].fix_trock_doors: + add_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Turtle Rock Second Section from Bomb Wall', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge from Bomb Wall', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge Bomb Wall', player), lambda state: can_use_bombs(state, player)) + + if multiworld.enemy_shuffle[player]: + set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3)) else: - set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player)) - set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area - set_rule(world.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and can_shoot_arrows(state, player) and state.has('Hammer', player)) - set_rule(world.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)) - set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player)) - set_rule(world.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player)) - if world.pot_shuffle[player]: + set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player)) + set_rule(multiworld.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player)) + set_rule(multiworld.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area + set_rule(multiworld.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and can_shoot_arrows(state, player) and state.has('Hammer', player)) + set_rule(multiworld.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)) + set_rule(multiworld.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player)) + set_rule(multiworld.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player)) + if multiworld.pot_shuffle[player]: # chest switch may be up on ledge where bombs are required - set_rule(world.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( + set_rule(multiworld.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3)))) - if world.accessibility[player] != 'locations': - set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) + if multiworld.accessibility[player] != 'full': + set_always_allow(multiworld.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) - set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( + set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))) - if world.accessibility[player] != 'locations': - set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) + if multiworld.accessibility[player] != 'full': + set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) - set_rule(world.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6)) + set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6)) # these key rules are conservative, you might be able to get away with more lenient rules randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - Conveyor Star Pits Pot Key'] back_chests = ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest'] - set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player)) - set_rule(world.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) - set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( + set_rule(multiworld.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player)) + set_rule(multiworld.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(multiworld.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) + if multiworld.pot_shuffle[player]: + set_rule(multiworld.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) + set_rule(multiworld.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))) # this seemed to be causing generation failure, disable for now @@ -531,63 +552,63 @@ def global_rules(world, player): # It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements. # However we need to leave these at the lower values to derive that with 7 keys it is always possible to reach Bob and Ice Armos. - set_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 6)) + set_rule(multiworld.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 6)) # It is possible to need more than 7 keys .... - set_rule(world.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( + set_rule(multiworld.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests + back_chests, [player] * len(randomizer_room_chests + back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))) # The actual requirements for these rooms to avoid key-lock - set_rule(world.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or - ((item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))) + set_rule(multiworld.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or + ((item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))) for location in randomizer_room_chests: - set_rule(world.get_location(location, player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( - item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))) + set_rule(multiworld.get_location(location, player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( + item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))) # Once again it is possible to need more than 7 keys... - set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( + set_rule(multiworld.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))) - set_rule(world.get_entrance('Ganons Tower (Bottom) (East)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( + set_rule(multiworld.get_entrance('Ganons Tower (Bottom) (East)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(back_chests, [player] * len(back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))) # Actual requirements for location in compass_room_chests: - set_rule(world.get_location(location, player), lambda state: (can_use_bombs(state, player) or state.has("Cane of Somaria", player)) and state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( + set_rule(multiworld.get_location(location, player), lambda state: (can_use_bombs(state, player) or state.has("Cane of Somaria", player)) and state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))) - set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player)) + set_rule(multiworld.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player)) - set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player), + set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Left', player), lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) - set_rule(world.get_location('Ganons Tower - Big Key Chest', player), + set_rule(multiworld.get_location('Ganons Tower - Big Key Chest', player), lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) - set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player), + set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Right', player), lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) - if world.enemy_shuffle[player]: - set_rule(world.get_entrance('Ganons Tower Big Key Door', player), + if multiworld.enemy_shuffle[player]: + set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player)) else: - set_rule(world.get_entrance('Ganons Tower Big Key Door', player), + set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player) and can_shoot_arrows(state, player)) - set_rule(world.get_entrance('Ganons Tower Torch Rooms', player), + set_rule(multiworld.get_entrance('Ganons Tower Torch Rooms', player), lambda state: can_kill_most_things(state, player, 8) and has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state)) - set_rule(world.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1)) - set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player), + set_rule(multiworld.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1)) + set_rule(multiworld.get_location('Ganons Tower - Pre-Moldorm Chest', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7)) - set_rule(world.get_entrance('Ganons Tower Moldorm Door', player), + set_rule(multiworld.get_entrance('Ganons Tower Moldorm Door', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8)) - set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player), + set_rule(multiworld.get_entrance('Ganons Tower Moldorm Gap', player), lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state)) - set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) - ganon = world.get_location('Ganon', player) + set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player)) + ganon = multiworld.get_location('Ganon', player) set_rule(ganon, lambda state: GanonDefeatRule(state, player)) - if world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: + if multiworld.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: add_rule(ganon, lambda state: has_triforce_pieces(state, player)) - elif world.goal[player] == 'ganon_pedestal': - add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player)) + elif multiworld.goal[player] == 'ganon_pedestal': + add_rule(multiworld.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player)) else: add_rule(ganon, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_ganon[player], player)) - set_rule(world.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop + set_rule(multiworld.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop - set_rule(world.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player)) + set_rule(multiworld.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player)) def default_rules(world, player): @@ -886,7 +907,6 @@ def no_glitches_rules(world, player): add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override - forbid_bomb_jump_requirements(world, player) add_conditional_lamps(world, player) def fake_flipper_rules(world, player): @@ -914,12 +934,20 @@ def fake_flipper_rules(world, player): set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Moon Pearl', player)) -def forbid_bomb_jump_requirements(world, player): +def bomb_jump_requirements(multiworld, player): DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'] for location in DMs_room_chests: - add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) - set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False) + add_rule(multiworld.get_location(location, player), lambda state: can_use_bombs(state, player), combine="or") + set_rule(multiworld.get_entrance('Paradox Cave Bomb Jump', player), lambda state: can_use_bombs(state, player)) + set_rule(multiworld.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: can_use_bombs(state, player)) + + +def forbid_bomb_jump_requirements(multiworld, player): + DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'] + for location in DMs_room_chests: + add_rule(multiworld.get_location(location, player), lambda state: state.has('Hookshot', player)) + set_rule(multiworld.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) + set_rule(multiworld.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False) DW_Entrances = ['Bumper Cave (Bottom)', @@ -998,9 +1026,6 @@ def add_conditional_lamp(spot, region, spottype='Location', accessible_torch=Fal def open_rules(world, player): - set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), - lambda state: can_kill_most_things(state, player, 1)) - def basement_key_rule(state): if location_item_name(state, 'Sewers - Key Rat Key Drop', player) == ("Small Key (Hyrule Castle)", player): return state._lttp_has_key("Small Key (Hyrule Castle)", player, 2) @@ -1009,7 +1034,7 @@ def basement_key_rule(state): set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 2)) - set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), basement_key_rule) + set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 1)) set_rule(world.get_location('Sewers - Key Rat Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) and can_kill_most_things(state, player, 1)) @@ -1017,8 +1042,10 @@ def basement_key_rule(state): set_rule(world.get_location('Hyrule Castle - Big Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and can_kill_most_things(state, player, 1)) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and - state.has('Big Key (Hyrule Castle)', player)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) + and state.has('Big Key (Hyrule Castle)', player) + and (world.enemy_health[player] in ("easy", "default") + or can_kill_most_things(state, player, 1))) def swordless_rules(world, player): @@ -1048,6 +1075,7 @@ def add_connection(parent_name, target_name, entrance_name, world, player): parent.exits.append(connection) connection.connect(target) + def standard_rules(world, player): add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player) world.get_entrance('Uncle S&Q', player).hide_path = True @@ -1059,18 +1087,23 @@ def standard_rules(world, player): if world.small_key_shuffle[player] != small_key_shuffle.option_universal: set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1) + and can_kill_most_things(state, player, 2)) set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1) + and can_kill_most_things(state, player, 1)) set_rule(world.get_location('Hyrule Castle - Big Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2)) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) and - state.has('Big Key (Hyrule Castle)', player)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) + and state.has('Big Key (Hyrule Castle)', player) + and (world.enemy_health[player] in ("easy", "default") + or can_kill_most_things(state, player, 1))) set_rule(world.get_location('Sewers - Key Rat Key Drop', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) + and can_kill_most_things(state, player, 1)) else: set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has('Big Key (Hyrule Castle)', player)) @@ -1097,14 +1130,10 @@ def set_trock_key_rules(world, player): all_state.stale[player] = True # Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon. - can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge[player] is None else world.can_access_trock_eyebridge[player] - world.can_access_trock_eyebridge[player] = can_reach_back - can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) if world.can_access_trock_front[player] is None else world.can_access_trock_front[player] - world.can_access_trock_front[player] = can_reach_front - can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)) if world.can_access_trock_big_chest[player] is None else world.can_access_trock_big_chest[player] - world.can_access_trock_big_chest[player] = can_reach_big_chest - can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player)) if world.can_access_trock_middle[player] is None else world.can_access_trock_middle[player] - world.can_access_trock_middle[player] = can_reach_middle + can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) + can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) + can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)) + can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player)) # If you can't enter from the back, the door to the front of TR requires only 2 small keys if the big key is in one of these chests since 2 key doors are locked behind the big key door. # If you can only enter from the middle, this includes all locations that can only be reached by exiting the front. This can include Laser Bridge and Crystaroller if the front and back connect via Dark DM Ledge! @@ -1172,7 +1201,7 @@ def tr_big_key_chest_keys_needed(state): # Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player) - if world.accessibility[player] == 'locations': + if world.accessibility[player] == 'full': if world.big_key_shuffle[player] and can_reach_big_chest: # Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest', @@ -1184,10 +1213,9 @@ def tr_big_key_chest_keys_needed(state): item = item_factory('Small Key (Turtle Rock)', world.worlds[player]) location = world.get_location('Turtle Rock - Big Key Chest', player) location.place_locked_item(item) - location.event = True toss_junk_item(world, player) - if world.accessibility[player] != 'locations': + if world.accessibility[player] != 'full': set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player))) diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index dbe8cc1f9dfa..db2b5b680c1d 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -9,9 +9,9 @@ from BaseClasses import CollectionState from .SubClasses import ALttPLocation -from .EntranceShuffle import door_addresses + from .Items import item_name_groups -from .Options import small_key_shuffle, RandomizeShopInventories + from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows logger = logging.getLogger("Shops") @@ -66,6 +66,7 @@ def item_count(self) -> int: return 0 def get_bytes(self) -> List[int]: + from .EntranceShuffle import door_addresses # [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index] entrances = self.region.entrances config = self.item_count @@ -181,7 +182,7 @@ def push_shop_inventories(multiworld): def create_shops(multiworld, player: int): - + from .Options import RandomizeShopInventories player_shop_table = shop_table.copy() if multiworld.include_witch_hut[player]: player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False) @@ -304,6 +305,7 @@ class ShopData(NamedTuple): def set_up_shops(multiworld, player: int): + from .Options import small_key_shuffle # TODO: move hard+ mode changes for shields here, utilizing the new shops if multiworld.retro_bow[player]: @@ -426,7 +428,7 @@ def get_price_modifier(item): def get_price(multiworld, item, player: int, price_type=None): """Converts a raw Rupee price into a special price type""" - + from .Options import small_key_shuffle if price_type: price_types = [price_type] else: diff --git a/worlds/alttp/StateHelpers.py b/worlds/alttp/StateHelpers.py index 4ed1b1caf205..964a77fefbaf 100644 --- a/worlds/alttp/StateHelpers.py +++ b/worlds/alttp/StateHelpers.py @@ -30,7 +30,7 @@ def can_shoot_arrows(state: CollectionState, player: int) -> bool: def has_triforce_pieces(state: CollectionState, player: int) -> bool: - count = state.multiworld.treasure_hunt_count[player] + count = state.multiworld.worlds[player].treasure_hunt_required return state.count('Triforce Piece', player) + state.count('Power Star', player) >= count @@ -48,8 +48,8 @@ def can_lift_heavy_rocks(state: CollectionState, player: int) -> bool: def bottle_count(state: CollectionState, player: int) -> int: - return min(state.multiworld.difficulty_requirements[player].progressive_bottle_limit, - state.count_group("Bottles", player)) + return min(state.multiworld.worlds[player].difficulty_requirements.progressive_bottle_limit, + state.count_group("Bottles", player)) def has_hearts(state: CollectionState, player: int, count: int) -> int: @@ -59,7 +59,7 @@ def has_hearts(state: CollectionState, player: int, count: int) -> int: def heart_count(state: CollectionState, player: int) -> int: # Warning: This only considers items that are marked as advancement items - diff = state.multiworld.difficulty_requirements[player] + diff = state.multiworld.worlds[player].difficulty_requirements return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \ + state.count('Sanctuary Heart Container', player) \ + min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \ @@ -106,6 +106,12 @@ def can_bomb_or_bonk(state: CollectionState, player: int) -> bool: return state.has("Pegasus Boots", player) or can_use_bombs(state, player) +def can_activate_crystal_switch(state: CollectionState, player: int) -> bool: + return (has_melee_weapon(state, player) or can_use_bombs(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Hookshot", "Cane of Somaria", "Cane of Byrna", "Fire Rod", "Ice Rod", "Blue Boomerang", + "Red Boomerang"], player)) + + def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool: if state.multiworld.enemy_shuffle[player]: # I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any. @@ -171,10 +177,11 @@ def can_melt_things(state: CollectionState, player: int) -> bool: def has_misery_mire_medallion(state: CollectionState, player: int) -> bool: - return state.has(state.multiworld.required_medallions[player][0], player) + return state.has(state.multiworld.worlds[player].required_medallions[0], player) + def has_turtle_rock_medallion(state: CollectionState, player: int) -> bool: - return state.has(state.multiworld.required_medallions[player][1], player) + return state.has(state.multiworld.worlds[player].required_medallions[1], player) def can_boots_clip_lw(state: CollectionState, player: int) -> bool: diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py index 769dcc199852..328e28da9346 100644 --- a/worlds/alttp/SubClasses.py +++ b/worlds/alttp/SubClasses.py @@ -76,10 +76,6 @@ def dungeon_item(self) -> Optional[str]: if self.type in {"SmallKey", "BigKey", "Map", "Compass"}: return self.type - @property - def locked_dungeon_item(self): - return self.location.locked and self.dungeon_item - class LTTPRegionType(IntEnum): LightWorld = 1 diff --git a/worlds/alttp/Text.py b/worlds/alttp/Text.py index b479a9b8e002..c005cacd8f9f 100644 --- a/worlds/alttp/Text.py +++ b/worlds/alttp/Text.py @@ -1289,6 +1289,415 @@ class LargeCreditBottomMapper(CharTextMapper): class TextTable(object): SIZE = 0x7355 + valid_keys = [ + "set_cursor", + "set_cursor2", + "game_over_menu", + "var_test", + "follower_no_enter", + "choice_1_3", + "choice_2_3", + "choice_3_3", + "choice_1_2", + "choice_2_2", + "uncle_leaving_text", + "uncle_dying_sewer", + "tutorial_guard_1", + "tutorial_guard_2", + "tutorial_guard_3", + "tutorial_guard_4", + "tutorial_guard_5", + "tutorial_guard_6", + "tutorial_guard_7", + "priest_sanctuary_before_leave", + "sanctuary_enter", + "zelda_sanctuary_story", + "priest_sanctuary_before_pendants", + "priest_sanctuary_after_pendants_before_master_sword", + "priest_sanctuary_dying", + "zelda_save_sewers", + "priest_info", + "zelda_sanctuary_before_leave", + "telepathic_intro", + "telepathic_reminder", + "zelda_go_to_throne", + "zelda_push_throne", + "zelda_switch_room_pull", + "zelda_save_lets_go", + "zelda_save_repeat", + "zelda_before_pendants", + "zelda_after_pendants_before_master_sword", + "telepathic_zelda_right_after_master_sword", + "zelda_sewers", + "zelda_switch_room", + "kakariko_saharalasa_wife", + "kakariko_saharalasa_wife_sword_story", + "kakariko_saharalasa_wife_closing", + "kakariko_saharalasa_after_master_sword", + "kakariko_alert_guards", + "sahasrahla_quest_have_pendants", + "sahasrahla_quest_have_master_sword", + "sahasrahla_quest_information", + "sahasrahla_bring_courage", + "sahasrahla_have_ice_rod", + "telepathic_sahasrahla_beat_agahnim", + "telepathic_sahasrahla_beat_agahnim_no_pearl", + "sahasrahla_have_boots_no_icerod", + "sahasrahla_have_courage", + "sahasrahla_found", + "sign_rain_north_of_links_house", + "sign_north_of_links_house", + "sign_path_to_death_mountain", + "sign_lost_woods", + "sign_zoras", + "sign_outside_magic_shop", + "sign_death_mountain_cave_back", + "sign_east_of_links_house", + "sign_south_of_lumberjacks", + "sign_east_of_desert", + "sign_east_of_sanctuary", + "sign_east_of_castle", + "sign_north_of_lake", + "sign_desert_thief", + "sign_lumberjacks_house", + "sign_north_kakariko", + "witch_bring_mushroom", + "witch_brewing_the_item", + "witch_assistant_no_bottle", + "witch_assistant_no_empty_bottle", + "witch_assistant_informational", + "witch_assistant_no_bottle_buying", + "potion_shop_no_empty_bottles", + "item_get_lamp", + "item_get_boomerang", + "item_get_bow", + "item_get_shovel", + "item_get_magic_cape", + "item_get_powder", + "item_get_flippers", + "item_get_power_gloves", + "item_get_pendant_courage", + "item_get_pendant_power", + "item_get_pendant_wisdom", + "item_get_mushroom", + "item_get_book", + "item_get_moonpearl", + "item_get_compass", + "item_get_map", + "item_get_ice_rod", + "item_get_fire_rod", + "item_get_ether", + "item_get_bombos", + "item_get_quake", + "item_get_hammer", + "item_get_flute", + "item_get_cane_of_somaria", + "item_get_hookshot", + "item_get_bombs", + "item_get_bottle", + "item_get_big_key", + "item_get_titans_mitts", + "item_get_magic_mirror", + "item_get_fake_mastersword", + "post_item_get_mastersword", + "item_get_red_potion", + "item_get_green_potion", + "item_get_blue_potion", + "item_get_bug_net", + "item_get_blue_mail", + "item_get_red_mail", + "item_get_temperedsword", + "item_get_mirror_shield", + "item_get_cane_of_byrna", + "missing_big_key", + "missing_magic", + "item_get_pegasus_boots", + "talking_tree_info_start", + "talking_tree_info_1", + "talking_tree_info_2", + "talking_tree_info_3", + "talking_tree_info_4", + "talking_tree_other", + "item_get_pendant_power_alt", + "item_get_pendant_wisdom_alt", + "game_shooting_choice", + "game_shooting_yes", + "game_shooting_no", + "game_shooting_continue", + "pond_of_wishing", + "pond_item_select", + "pond_item_test", + "pond_will_upgrade", + "pond_item_test_no", + "pond_item_test_no_no", + "pond_item_boomerang", + "pond_item_shield", + "pond_item_silvers", + "pond_item_bottle_filled", + "pond_item_sword", + "pond_of_wishing_happiness", + "pond_of_wishing_choice", + "pond_of_wishing_bombs", + "pond_of_wishing_arrows", + "pond_of_wishing_full_upgrades", + "mountain_old_man_first", + "mountain_old_man_deadend", + "mountain_old_man_turn_right", + "mountain_old_man_lost_and_alone", + "mountain_old_man_drop_off", + "mountain_old_man_in_his_cave_pre_agahnim", + "mountain_old_man_in_his_cave", + "mountain_old_man_in_his_cave_post_agahnim", + "tavern_old_man_awake", + "tavern_old_man_unactivated_flute", + "tavern_old_man_know_tree_unactivated_flute", + "tavern_old_man_have_flute", + "chicken_hut_lady", + "running_man", + "game_race_sign", + "sign_bumper_cave", + "sign_catfish", + "sign_north_village_of_outcasts", + "sign_south_of_bumper_cave", + "sign_east_of_pyramid", + "sign_east_of_bomb_shop", + "sign_east_of_mire", + "sign_village_of_outcasts", + "sign_before_wishing_pond", + "sign_before_catfish_area", + "castle_wall_guard", + "gate_guard", + "telepathic_tile_eastern_palace", + "telepathic_tile_tower_of_hera_floor_4", + "hylian_text_1", + "mastersword_pedestal_translated", + "telepathic_tile_spectacle_rock", + "telepathic_tile_swamp_entrance", + "telepathic_tile_thieves_town_upstairs", + "telepathic_tile_misery_mire", + "hylian_text_2", + "desert_entry_translated", + "telepathic_tile_under_ganon", + "telepathic_tile_palace_of_darkness", + "telepathic_tile_desert_bonk_torch_room", + "telepathic_tile_castle_tower", + "telepathic_tile_ice_large_room", + "telepathic_tile_turtle_rock", + "telepathic_tile_ice_entrance", + "telepathic_tile_ice_stalfos_knights_room", + "telepathic_tile_tower_of_hera_entrance", + "houlihan_room", + "caught_a_bee", + "caught_a_fairy", + "no_empty_bottles", + "game_race_boy_time", + "game_race_girl", + "game_race_boy_success", + "game_race_boy_failure", + "game_race_boy_already_won", + "game_race_boy_sneaky", + "bottle_vendor_choice", + "bottle_vendor_get", + "bottle_vendor_no", + "bottle_vendor_already_collected", + "bottle_vendor_bee", + "bottle_vendor_fish", + "hobo_item_get_bottle", + "blacksmiths_what_you_want", + "blacksmiths_paywall", + "blacksmiths_extra_okay", + "blacksmiths_tempered_already", + "blacksmiths_temper_no", + "blacksmiths_bogart_sword", + "blacksmiths_get_sword", + "blacksmiths_shop_before_saving", + "blacksmiths_shop_saving", + "blacksmiths_collect_frog", + "blacksmiths_still_working", + "blacksmiths_saving_bows", + "blacksmiths_hammer_anvil", + "dark_flute_boy_storytime", + "dark_flute_boy_get_shovel", + "dark_flute_boy_no_get_shovel", + "dark_flute_boy_flute_not_found", + "dark_flute_boy_after_shovel_get", + "shop_fortune_teller_lw_hint_0", + "shop_fortune_teller_lw_hint_1", + "shop_fortune_teller_lw_hint_2", + "shop_fortune_teller_lw_hint_3", + "shop_fortune_teller_lw_hint_4", + "shop_fortune_teller_lw_hint_5", + "shop_fortune_teller_lw_hint_6", + "shop_fortune_teller_lw_hint_7", + "shop_fortune_teller_lw_no_rupees", + "shop_fortune_teller_lw", + "shop_fortune_teller_lw_post_hint", + "shop_fortune_teller_lw_no", + "shop_fortune_teller_lw_hint_8", + "shop_fortune_teller_lw_hint_9", + "shop_fortune_teller_lw_hint_10", + "shop_fortune_teller_lw_hint_11", + "shop_fortune_teller_lw_hint_12", + "shop_fortune_teller_lw_hint_13", + "shop_fortune_teller_lw_hint_14", + "shop_fortune_teller_lw_hint_15", + "dark_sanctuary", + "dark_sanctuary_hint_0", + "dark_sanctuary_no", + "dark_sanctuary_hint_1", + "dark_sanctuary_yes", + "dark_sanctuary_hint_2", + "sick_kid_no_bottle", + "sick_kid_trade", + "sick_kid_post_trade", + "desert_thief_sitting", + "desert_thief_following", + "desert_thief_question", + "desert_thief_question_yes", + "desert_thief_after_item_get", + "desert_thief_reassure", + "hylian_text_3", + "tablet_ether_book", + "tablet_bombos_book", + "magic_bat_wake", + "magic_bat_give_half_magic", + "intro_main", + "intro_throne_room", + "intro_zelda_cell", + "intro_agahnim", + "pickup_purple_chest", + "bomb_shop", + "bomb_shop_big_bomb", + "bomb_shop_big_bomb_buy", + "item_get_big_bomb", + "kiki_second_extortion", + "kiki_second_extortion_no", + "kiki_second_extortion_yes", + "kiki_first_extortion", + "kiki_first_extortion_yes", + "kiki_first_extortion_no", + "kiki_leaving_screen", + "blind_in_the_cell", + "blind_by_the_light", + "blind_not_that_way", + "aginah_l1sword_no_book", + "aginah_l1sword_with_pendants", + "aginah", + "aginah_need_better_sword", + "aginah_have_better_sword", + "catfish", + "catfish_after_item", + "lumberjack_right", + "lumberjack_left", + "lumberjack_left_post_agahnim", + "fighting_brothers_right", + "fighting_brothers_right_opened", + "fighting_brothers_left", + "maiden_crystal_1", + "maiden_crystal_2", + "maiden_crystal_3", + "maiden_crystal_4", + "maiden_crystal_5", + "maiden_crystal_6", + "maiden_crystal_7", + "maiden_ending", + "maiden_confirm_understood", + "barrier_breaking", + "maiden_crystal_7_again", + "agahnim_zelda_teleport", + "agahnim_magic_running_away", + "agahnim_hide_and_seek_found", + "agahnim_defeated", + "agahnim_final_meeting", + "zora_meeting", + "zora_tells_cost", + "zora_get_flippers", + "zora_no_cash", + "zora_no_buy_item", + "kakariko_saharalasa_grandson", + "kakariko_saharalasa_grandson_next", + "dark_palace_tree_dude", + "fairy_wishing_ponds", + "fairy_wishing_ponds_no", + "pond_of_wishing_no", + "pond_of_wishing_return_item", + "pond_of_wishing_throw", + "pond_pre_item_silvers", + "pond_of_wishing_great_luck", + "pond_of_wishing_good_luck", + "pond_of_wishing_meh_luck", + "pond_of_wishing_bad_luck", + "pond_of_wishing_fortune", + "item_get_14_heart", + "item_get_24_heart", + "item_get_34_heart", + "item_get_whole_heart", + "item_get_sanc_heart", + "fairy_fountain_refill", + "death_mountain_bullied_no_pearl", + "death_mountain_bullied_with_pearl", + "death_mountain_bully_no_pearl", + "death_mountain_bully_with_pearl", + "shop_darkworld_enter", + "game_chest_village_of_outcasts", + "game_chest_no_cash", + "game_chest_not_played", + "game_chest_played", + "game_chest_village_of_outcasts_play", + "shop_first_time", + "shop_already_have", + "shop_buy_shield", + "shop_buy_red_potion", + "shop_buy_arrows", + "shop_buy_bombs", + "shop_buy_bee", + "shop_buy_heart", + "shop_first_no_bottle_buy", + "shop_buy_no_space", + "ganon_fall_in", + "ganon_phase_3", + "lost_woods_thief", + "blinds_hut_dude", + "end_triforce", + "toppi_fallen", + "kakariko_tavern_fisherman", + "thief_money", + "thief_desert_rupee_cave", + "thief_ice_rupee_cave", + "telepathic_tile_south_east_darkworld_cave", + "cukeman", + "cukeman_2", + "potion_shop_no_cash", + "kakariko_powdered_chicken", + "game_chest_south_of_kakariko", + "game_chest_play_yes", + "game_chest_play_no", + "game_chest_lost_woods", + "kakariko_flophouse_man_no_flippers", + "kakariko_flophouse_man", + "menu_start_2", + "menu_start_3", + "menu_pause", + "game_digging_choice", + "game_digging_start", + "game_digging_no_cash", + "game_digging_end_time", + "game_digging_come_back_later", + "game_digging_no_follower", + "menu_start_4", + "ganon_fall_in_alt", + "ganon_phase_3_alt", + "sign_east_death_mountain_bridge", + "fish_money", + "sign_ganons_tower", + "sign_ganon", + "ganon_phase_3_no_bow", + "ganon_phase_3_no_silvers_alt", + "ganon_phase_3_no_silvers", + "ganon_phase_3_silvers", + "murahdahla", + ] + def __init__(self): self._text = OrderedDict() self.setDefaultText() diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 497d5de496c3..50397dea166c 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -15,7 +15,7 @@ def underworld_glitch_connections(world, player): specrock.exits.append(kikiskip) mire.exits.extend([mire_to_hera, mire_to_swamp]) - if world.fix_fake_world[player]: + if world.worlds[player].fix_fake_world: kikiskip.connect(world.get_entrance('Palace of Darkness Exit', player).connected_region) mire_to_hera.connect(world.get_entrance('Tower of Hera Exit', player).connected_region) mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit', player).connected_region) @@ -38,8 +38,8 @@ def fake_pearl_state(state, player): # Sets the rules on where we can actually go using this clip. # Behavior differs based on what type of ER shuffle we're playing. def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str): - fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] - fix_fake_worlds = world.fix_fake_world[player] + fix_dungeon_exits = world.worlds[player].fix_palaceofdarkness_exit + fix_fake_worlds = world.worlds[player].fix_fake_world dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0] if not fix_dungeon_exits: # vanilla, simple, restricted, dungeons_simple; should never have fake worlds fix @@ -52,7 +52,7 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du add_rule(clip, lambda state: state.has('Cape', player) or has_beam_sword(state, player) or state.has('Beat Agahnim 1', player)) # kill/bypass barrier # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) - elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix + elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) # exiting restriction @@ -62,9 +62,6 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du def underworld_glitches_rules(world, player): - fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] - fix_fake_worlds = world.fix_fake_world[player] - # Ice Palace Entrance Clip # This is the easiest one since it's a simple internal clip. # Need to also add melting to freezor chest since it's otherwise assumed. @@ -92,7 +89,7 @@ def underworld_glitches_rules(world, player): # Build the rule for SP moat. # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. # First we require a certain type of entrance shuffle, then build the rule from its pieces. - if not world.swamp_patch_required[player]: + if not world.worlds[player].swamp_patch_required: if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rule_map = { 'Misery Mire (Entrance)': (lambda state: True), diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 63c53007d861..3176f7a7fcce 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -213,7 +213,6 @@ class ALTTPWorld(World): item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int} location_name_to_id = lookup_name_to_id - data_version = 9 required_client_version = (0, 4, 1) web = ALTTPWeb() @@ -251,6 +250,18 @@ def enemizer_path(self) -> str: dungeons: typing.Dict[str, Dungeon] waterfall_fairy_bottle_fill: str pyramid_fairy_bottle_fill: str + escape_assist: list + + can_take_damage: bool = True + swamp_patch_required: bool = False + powder_patch_required: bool = False + ganon_at_pyramid: bool = True + ganonstower_vanilla: bool = True + fix_fake_world: bool = True + + clock_mode: str = "" + treasure_hunt_required: int = 0 + treasure_hunt_total: int = 0 def __init__(self, *args, **kwargs): self.dungeon_local_item_names = set() @@ -261,6 +272,12 @@ def __init__(self, *args, **kwargs): self.dungeons = {} self.waterfall_fairy_bottle_fill = "Bottle" self.pyramid_fairy_bottle_fill = "Bottle" + self.fix_trock_doors = None + self.fix_skullwoods_exit = None + self.fix_palaceofdarkness_exit = None + self.fix_trock_exit = None + self.required_medallions = ["Ether", "Quake"] + self.escape_assist = [] super(ALTTPWorld, self).__init__(*args, **kwargs) @classmethod @@ -280,12 +297,21 @@ def generate_early(self): player = self.player multiworld = self.multiworld + self.fix_trock_doors = (multiworld.entrance_shuffle[player] != 'vanilla' + or multiworld.mode[player] == 'inverted') + self.fix_skullwoods_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted', + 'dungeons_simple'] + self.fix_palaceofdarkness_exit = multiworld.entrance_shuffle[player] not in ['dungeons_simple', 'vanilla', + 'simple', 'restricted'] + self.fix_trock_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted', + 'dungeons_simple'] + # fairy bottle fills bottle_options = [ "Bottle (Red Potion)", "Bottle (Green Potion)", "Bottle (Blue Potion)", "Bottle (Bee)", "Bottle (Good Bee)" ] - if multiworld.difficulty[player] not in ["hard", "expert"]: + if multiworld.item_pool[player] not in ["hard", "expert"]: bottle_options.append("Bottle (Fairy)") self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options) self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options) @@ -331,7 +357,7 @@ def generate_early(self): if option == "original_dungeon": self.dungeon_specific_item_names |= self.item_name_groups[option.item_name_group] - multiworld.difficulty_requirements[player] = difficulties[multiworld.item_pool[player].current_key] + self.difficulty_requirements = difficulties[multiworld.item_pool[player].current_key] # enforce pre-defined local items. if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]: @@ -345,42 +371,43 @@ def generate_early(self): def create_regions(self): player = self.player - world = self.multiworld + multiworld = self.multiworld - if world.mode[player] != 'inverted': - create_regions(world, player) + if multiworld.mode[player] != 'inverted': + create_regions(multiworld, player) else: - create_inverted_regions(world, player) - create_shops(world, player) + create_inverted_regions(multiworld, player) + create_shops(multiworld, player) self.create_dungeons() - if world.glitches_required[player] not in ["no_glitches", "minor_glitches"] and world.entrance_shuffle[player] in \ - {"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"}: - world.fix_fake_world[player] = False + if (multiworld.glitches_required[player] not in ["no_glitches", "minor_glitches"] and + multiworld.entrance_shuffle[player] in [ + "vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]): + self.fix_fake_world = False # seeded entrance shuffle - old_random = world.random - world.random = random.Random(self.er_seed) + old_random = multiworld.random + multiworld.random = random.Random(self.er_seed) - if world.mode[player] != 'inverted': - link_entrances(world, player) - mark_light_world_regions(world, player) + if multiworld.mode[player] != 'inverted': + link_entrances(multiworld, player) + mark_light_world_regions(multiworld, player) for region_name, entrance_name in indirect_connections_not_inverted.items(): - world.register_indirect_condition(world.get_region(region_name, player), - world.get_entrance(entrance_name, player)) + multiworld.register_indirect_condition(multiworld.get_region(region_name, player), + multiworld.get_entrance(entrance_name, player)) else: - link_inverted_entrances(world, player) - mark_dark_world_regions(world, player) + link_inverted_entrances(multiworld, player) + mark_dark_world_regions(multiworld, player) for region_name, entrance_name in indirect_connections_inverted.items(): - world.register_indirect_condition(world.get_region(region_name, player), - world.get_entrance(entrance_name, player)) + multiworld.register_indirect_condition(multiworld.get_region(region_name, player), + multiworld.get_entrance(entrance_name, player)) - world.random = old_random - plando_connect(world, player) + multiworld.random = old_random + plando_connect(multiworld, player) for region_name, entrance_name in indirect_connections.items(): - world.register_indirect_condition(world.get_region(region_name, player), - world.get_entrance(entrance_name, player)) + multiworld.register_indirect_condition(multiworld.get_region(region_name, player), + multiworld.get_entrance(entrance_name, player)) def collect_item(self, state: CollectionState, item: Item, remove=False): item_name = item.name @@ -424,15 +451,16 @@ def collect_item(self, state: CollectionState, item: Item, remove=False): if 'Sword' in item_name: if state.has('Golden Sword', item.player): pass - elif state.has('Tempered Sword', item.player) and self.multiworld.difficulty_requirements[ - item.player].progressive_sword_limit >= 4: + elif (state.has('Tempered Sword', item.player) and + self.difficulty_requirements.progressive_sword_limit >= 4): return 'Golden Sword' - elif state.has('Master Sword', item.player) and self.multiworld.difficulty_requirements[ - item.player].progressive_sword_limit >= 3: + elif (state.has('Master Sword', item.player) and + self.difficulty_requirements.progressive_sword_limit >= 3): return 'Tempered Sword' - elif state.has('Fighter Sword', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 2: + elif (state.has('Fighter Sword', item.player) and + self.difficulty_requirements.progressive_sword_limit >= 2): return 'Master Sword' - elif self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 1: + elif self.difficulty_requirements.progressive_sword_limit >= 1: return 'Fighter Sword' elif 'Glove' in item_name: if state.has('Titans Mitts', item.player): @@ -444,20 +472,22 @@ def collect_item(self, state: CollectionState, item: Item, remove=False): elif 'Shield' in item_name: if state.has('Mirror Shield', item.player): return - elif state.has('Red Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 3: + elif (state.has('Red Shield', item.player) and + self.difficulty_requirements.progressive_shield_limit >= 3): return 'Mirror Shield' - elif state.has('Blue Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 2: + elif (state.has('Blue Shield', item.player) and + self.difficulty_requirements.progressive_shield_limit >= 2): return 'Red Shield' - elif self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 1: + elif self.difficulty_requirements.progressive_shield_limit >= 1: return 'Blue Shield' elif 'Bow' in item_name: if state.has('Silver Bow', item.player): return - elif state.has('Bow', item.player) and (self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 2 - or self.multiworld.glitches_required[item.player] == 'no_glitches' - or self.multiworld.swordless[item.player]): # modes where silver bow is always required for ganon + elif state.has('Bow', item.player) and (self.difficulty_requirements.progressive_bow_limit >= 2 + or self.multiworld.glitches_required[self.player] == 'no_glitches' + or self.multiworld.swordless[self.player]): # modes where silver bow is always required for ganon return 'Silver Bow' - elif self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 1: + elif self.difficulty_requirements.progressive_bow_limit >= 1: return 'Bow' elif item.advancement: return item_name @@ -646,10 +676,10 @@ def stage_fill_hook(cls, multiworld, progitempool, usefulitempool, filleritempoo trash_counts = {} for player in multiworld.get_game_players("A Link to the Past"): world = multiworld.worlds[player] - if not multiworld.ganonstower_vanilla[player] or \ + if not world.ganonstower_vanilla or \ world.options.glitches_required.current_key in {'overworld_glitches', 'hybrid_major_glitches', "no_logic"}: pass - elif 'triforce_hunt' in world.options.goal.current_key and ('local' in world.options.goal.current_key or world.players == 1): + elif 'triforce_hunt' in world.options.goal.current_key and ('local' in world.options.goal.current_key or multiworld.players == 1): trash_counts[player] = multiworld.random.randint(world.options.crystals_needed_for_gt * 2, world.options.crystals_needed_for_gt * 4) else: @@ -687,10 +717,10 @@ def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: player_name = self.multiworld.get_player_name(self.player) spoiler_handle.write("\n\nMedallions:\n") spoiler_handle.write(f"\nMisery Mire ({player_name}):" - f" {self.multiworld.required_medallions[self.player][0]}") + f" {self.required_medallions[0]}") spoiler_handle.write( f"\nTurtle Rock ({player_name}):" - f" {self.multiworld.required_medallions[self.player][1]}") + f" {self.required_medallions[1]}") spoiler_handle.write("\n\nFairy Fountain Bottle Fill:\n") spoiler_handle.write(f"\nPyramid Fairy ({player_name}):" f" {self.pyramid_fairy_bottle_fill}") @@ -801,8 +831,8 @@ def fill_slot_data(self): slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options} slot_data.update({ - 'mm_medalion': self.multiworld.required_medallions[self.player][0], - 'tr_medalion': self.multiworld.required_medallions[self.player][1], + 'mm_medalion': self.required_medallions[0], + 'tr_medalion': self.required_medallions[1], } ) return slot_data diff --git a/worlds/alttp/docs/en_A Link to the Past.md b/worlds/alttp/docs/en_A Link to the Past.md index 6808f69e759f..1a2cb310ce07 100644 --- a/worlds/alttp/docs/en_A Link to the Past.md +++ b/worlds/alttp/docs/en_A Link to the Past.md @@ -1,8 +1,8 @@ # A Link to the Past -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/alttp/docs/multiworld_de.md b/worlds/alttp/docs/multiworld_de.md index 8ccd1a87a6b7..c8c802d75040 100644 --- a/worlds/alttp/docs/multiworld_de.md +++ b/worlds/alttp/docs/multiworld_de.md @@ -47,12 +47,12 @@ wählen können! ### Wo bekomme ich so eine YAML-Datei her? -Die [Player Settings](/games/A Link to the Past/player-settings) Seite auf der Website ermöglicht das einfache Erstellen +Die [Player Options](/games/A Link to the Past/player-options) Seite auf der Website ermöglicht das einfache Erstellen und Herunterladen deiner eigenen `yaml` Datei. Drei verschiedene Voreinstellungen können dort gespeichert werden. ### Deine YAML-Datei ist gewichtet! -Die **Player Settings** Seite hat eine Menge Optionen, die man per Schieber einstellen kann. Das ermöglicht es, +Die **Player Options** Seite hat eine Menge Optionen, die man per Schieber einstellen kann. Das ermöglicht es, verschiedene Optionen mit unterschiedlichen Wahrscheinlichkeiten in einer Kategorie ausgewürfelt zu werden Als Beispiel kann man sich die Option "Map Shuffle" als einen Eimer mit Zetteln zur Abstimmung Vorstellen. So kann man diff --git a/worlds/alttp/docs/multiworld_en.md b/worlds/alttp/docs/multiworld_en.md index 7521def36ea1..5d7fc43e310f 100644 --- a/worlds/alttp/docs/multiworld_en.md +++ b/worlds/alttp/docs/multiworld_en.md @@ -5,11 +5,12 @@ - [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). - [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above. - SNI is not compatible with (Q)Usb2Snes. -- Hardware or software capable of loading and playing SNES ROM files +- Hardware or software capable of loading and playing SNES ROM files, including: - An emulator capable of connecting to SNI - ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), - [BizHawk](https://tasvideos.org/BizHawk), or - [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer). Or, + ([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases), + [BSNES-plus](https://github.com/black-sliver/bsnes-plus), + [BizHawk](http://tasvideos.org/BizHawk.html), or + [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer) - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note: modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system, but it is not supported.** @@ -47,6 +48,11 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +#### snes9x-nwa + +1. Click on the Network Menu and check **Enable Emu Network Control** +2. Load your ROM file if it hasn't already been loaded. + ##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. @@ -58,6 +64,11 @@ first time launching, you may be prompted to allow it to communicate through the 6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. +#### BSNES-Plus + +1. Load your ROM file if it hasn't already been loaded. +2. The emulator should automatically connect while SNI is running. + ##### BizHawk 1. Ensure you have the BSNES core loaded. This is done with the main menubar, under: diff --git a/worlds/alttp/docs/multiworld_es.md b/worlds/alttp/docs/multiworld_es.md index 37aeda2a63e5..a8ed11cd3202 100644 --- a/worlds/alttp/docs/multiworld_es.md +++ b/worlds/alttp/docs/multiworld_es.md @@ -59,12 +59,13 @@ de multiworld puede tener diferentes opciones. ### Donde puedo obtener un fichero YAML? -La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-settings)" en el sitio web te permite configurar tu +La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-options)" en el sitio web te permite configurar tu configuración personal y descargar un fichero "YAML". ### Configuración YAML avanzada -Una version mas avanzada del fichero Yaml puede ser creada usando la pagina ["Weighted settings"](/weighted-settings), +Una version mas avanzada del fichero Yaml puede ser creada usando la pagina +["Weighted settings"](/games/A Link to the Past/weighted-options), la cual te permite tener almacenadas hasta 3 preajustes. La pagina "Weighted Settings" tiene muchas opciones representadas con controles deslizantes. Esto permite elegir cuan probable los valores de una categoría pueden ser elegidos sobre otros de la misma. @@ -86,7 +87,7 @@ Si quieres validar que tu fichero YAML para asegurarte que funciona correctament ## Generar una partida para un jugador -1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-settings), configura tus opciones, haz +1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-options), configura tus opciones, haz click en el boton "Generate game". 2. Se te redigirá a una pagina "Seed Info", donde puedes descargar tu archivo de parche. 3. Haz doble click en tu fichero de parche, y el emulador debería ejecutar tu juego automáticamente. Como el Cliente no diff --git a/worlds/alttp/docs/multiworld_fr.md b/worlds/alttp/docs/multiworld_fr.md index 078a270f08b9..310f3a4f96c4 100644 --- a/worlds/alttp/docs/multiworld_fr.md +++ b/worlds/alttp/docs/multiworld_fr.md @@ -60,15 +60,16 @@ peuvent avoir différentes options. ### Où est-ce que j'obtiens un fichier YAML ? -La page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-settings) vous permet de configurer vos +La page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options) vous permet de configurer vos paramètres personnels et de les exporter vers un fichier YAML. ### Configuration avancée du fichier YAML Une version plus avancée du fichier YAML peut être créée en utilisant la page -des [paramètres de pondération](/weighted-settings), qui vous permet de configurer jusqu'à trois préréglages. Cette page -a de nombreuses options qui sont essentiellement représentées avec des curseurs glissants. Cela vous permet de choisir -quelles sont les chances qu'une certaine option apparaisse par rapport aux autres disponibles dans une même catégorie. +des [paramètres de pondération](/games/A Link to the Past/weighted-options), qui vous permet de configurer jusqu'à +trois préréglages. Cette page a de nombreuses options qui sont essentiellement représentées avec des curseurs +glissants. Cela vous permet de choisir quelles sont les chances qu'une certaine option apparaisse par rapport aux +autres disponibles dans une même catégorie. Par exemple, imaginez que le générateur crée un seau étiqueté "Mélange des cartes", et qu'il place un morceau de papier pour chaque sous-option. Imaginez également que la valeur pour "On" est 20 et la valeur pour "Off" est 40. @@ -87,7 +88,7 @@ Si vous voulez valider votre fichier YAML pour être sûr qu'il fonctionne, vous ## Générer une partie pour un joueur -1. Aller sur la page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-settings), configurez vos options, +1. Aller sur la page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options), configurez vos options, et cliquez sur le bouton "Generate Game". 2. Il vous sera alors présenté une page d'informations sur la seed, où vous pourrez télécharger votre patch. 3. Double-cliquez sur le patch et l'émulateur devrait se lancer automatiquement avec la seed. Etant donné que le client @@ -207,4 +208,4 @@ Le logiciel recommandé pour l'auto-tracking actuellement est 3. Sélectionnez votre appareil SNES dans la liste déroulante. 4. Si vous voulez tracquer les petites clés ainsi que les objets des donjons, cochez la case **Race Illegal Tracking** 5. Cliquez sur le bouton **Start Autotracking** -6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire \ No newline at end of file +6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire diff --git a/worlds/alttp/test/__init__.py b/worlds/alttp/test/__init__.py index 49033a6ce36a..307e75381d7e 100644 --- a/worlds/alttp/test/__init__.py +++ b/worlds/alttp/test/__init__.py @@ -7,7 +7,9 @@ class LTTPTestBase(unittest.TestCase): def world_setup(self): + from worlds.alttp.Options import Medallion self.multiworld = MultiWorld(1) + self.multiworld.game[1] = "A Link to the Past" self.multiworld.state = CollectionState(self.multiworld) self.multiworld.set_seed(None) args = Namespace() @@ -15,3 +17,6 @@ def world_setup(self): setattr(args, name, {1: option.from_any(getattr(option, "default"))}) self.multiworld.set_options(args) self.world = self.multiworld.worlds[1] + # by default medallion access is randomized, for unittests we set it to vanilla + self.world.options.misery_mire_medallion.value = Medallion.option_ether + self.world.options.turtle_rock_medallion.value = Medallion.option_quake diff --git a/worlds/alttp/test/dungeons/TestDungeon.py b/worlds/alttp/test/dungeons/TestDungeon.py index 128f8b41b75e..91fc462c4ecc 100644 --- a/worlds/alttp/test/dungeons/TestDungeon.py +++ b/worlds/alttp/test/dungeons/TestDungeon.py @@ -13,9 +13,9 @@ def setUp(self): self.world_setup() self.starting_regions = [] # Where to start exploring self.remove_exits = [] # Block dungeon exits - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = 2 create_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() create_shops(self.multiworld, 1) @@ -23,7 +23,7 @@ def setUp(self): connect_simple(self.multiworld, exitname, regionname, 1) connect_simple(self.multiworld, 'Big Bomb Shop', 'Big Bomb Shop', 1) self.multiworld.get_region('Menu', 1).exits = [] - self.multiworld.swamp_patch_required[1] = True + self.multiworld.worlds[1].swamp_patch_required = True self.world.set_rules() self.world.create_items() self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) diff --git a/worlds/alttp/test/dungeons/TestGanonsTower.py b/worlds/alttp/test/dungeons/TestGanonsTower.py index 1e70f580de4e..08274d0fe7d9 100644 --- a/worlds/alttp/test/dungeons/TestGanonsTower.py +++ b/worlds/alttp/test/dungeons/TestGanonsTower.py @@ -33,22 +33,26 @@ def testGanonsTower(self): ["Ganons Tower - Randomizer Room - Top Left", False, []], ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Bomb Upgrade (50)']], + ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']], ["Ganons Tower - Randomizer Room - Top Right", False, []], ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Bomb Upgrade (50)']], + ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']], ["Ganons Tower - Randomizer Room - Bottom Left", False, []], ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Bomb Upgrade (50)']], + ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']], ["Ganons Tower - Randomizer Room - Bottom Right", False, []], ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Bomb Upgrade (50)']], + ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']], ["Ganons Tower - Firesnake Room", False, []], ["Ganons Tower - Firesnake Room", False, [], ['Hammer']], diff --git a/worlds/alttp/test/dungeons/TestThievesTown.py b/worlds/alttp/test/dungeons/TestThievesTown.py index 342823c91007..1cd3f5ca8f39 100644 --- a/worlds/alttp/test/dungeons/TestThievesTown.py +++ b/worlds/alttp/test/dungeons/TestThievesTown.py @@ -37,7 +37,8 @@ def testThievesTown(self): ["Thieves' Town - Blind's Cell", False, []], ["Thieves' Town - Blind's Cell", False, [], ['Big Key (Thieves Town)']], - ["Thieves' Town - Blind's Cell", True, ['Big Key (Thieves Town)']], + ["Thieves' Town - Blind's Cell", False, [], ['Small Key (Thieves Town)']], + ["Thieves' Town - Blind's Cell", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']], ["Thieves' Town - Boss", False, []], ["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']], diff --git a/worlds/alttp/test/dungeons/TestTowerOfHera.py b/worlds/alttp/test/dungeons/TestTowerOfHera.py index 3299e20291b0..29cbcbf91fe2 100644 --- a/worlds/alttp/test/dungeons/TestTowerOfHera.py +++ b/worlds/alttp/test/dungeons/TestTowerOfHera.py @@ -9,12 +9,16 @@ def testTowerOfHera(self): ["Tower of Hera - Big Key Chest", False, []], ["Tower of Hera - Big Key Chest", False, [], ['Small Key (Tower of Hera)']], ["Tower of Hera - Big Key Chest", False, [], ['Lamp', 'Fire Rod']], - ["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Lamp']], + ["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Lamp', 'Bomb Upgrade (50)']], ["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Fire Rod']], - ["Tower of Hera - Basement Cage", True, []], + ["Tower of Hera - Basement Cage", False, []], + ["Tower of Hera - Basement Cage", True, ['Bomb Upgrade (50)']], + ["Tower of Hera - Basement Cage", True, ['Progressive Sword']], - ["Tower of Hera - Map Chest", True, []], + ["Tower of Hera - Map Chest", False, []], + ["Tower of Hera - Map Chest", True, ['Bomb Upgrade (50)']], + ["Tower of Hera - Map Chest", True, ['Progressive Sword']], ["Tower of Hera - Compass Chest", False, []], ["Tower of Hera - Compass Chest", False, [], ['Big Key (Tower of Hera)']], diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py index 069639e81b9d..a0a654991b43 100644 --- a/worlds/alttp/test/inverted/TestInverted.py +++ b/worlds/alttp/test/inverted/TestInverted.py @@ -1,11 +1,11 @@ -from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool +from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase @@ -13,16 +13,15 @@ class TestInverted(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.mode[1].value = 2 self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = 2 create_inverted_regions(self.multiworld, 1) self.world.create_dungeons() create_shops(self.multiworld, 1) link_inverted_entrances(self.multiworld, 1) self.world.create_items() - self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None diff --git a/worlds/alttp/test/inverted/TestInvertedBombRules.py b/worlds/alttp/test/inverted/TestInvertedBombRules.py index 83a25812c9b6..a33beca7a9f9 100644 --- a/worlds/alttp/test/inverted/TestInvertedBombRules.py +++ b/worlds/alttp/test/inverted/TestInvertedBombRules.py @@ -11,7 +11,7 @@ class TestInvertedBombRules(LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.mode[1].value = 2 create_inverted_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index 912cca4390c3..bf25c5c9a164 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -6,7 +6,7 @@ from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase @@ -17,14 +17,13 @@ def setUp(self): self.multiworld.mode[1].value = 2 self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] create_inverted_regions(self.multiworld, 1) self.world.create_dungeons() create_shops(self.multiworld, 1) link_inverted_entrances(self.multiworld, 1) self.world.create_items() - self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None diff --git a/worlds/alttp/test/inverted_owg/TestDeathMountain.py b/worlds/alttp/test/inverted_owg/TestDeathMountain.py index b509643d0c5e..5186ae9106df 100644 --- a/worlds/alttp/test/inverted_owg/TestDeathMountain.py +++ b/worlds/alttp/test/inverted_owg/TestDeathMountain.py @@ -101,20 +101,20 @@ def testEastDarkWorldDeathMountain(self): ["Hookshot Cave - Bottom Right", False, []], ["Hookshot Cave - Bottom Right", False, [], ['Hookshot', 'Pegasus Boots']], ["Hookshot Cave - Bottom Right", False, [], ['Progressive Glove', 'Pegasus Boots', 'Magic Mirror']], - ["Hookshot Cave - Bottom Right", True, ['Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Pegasus Boots', 'Bomb Upgrade (50)']], ["Hookshot Cave - Bottom Left", False, []], ["Hookshot Cave - Bottom Left", False, [], ['Hookshot']], ["Hookshot Cave - Bottom Left", False, [], ['Progressive Glove', 'Pegasus Boots', 'Magic Mirror']], - ["Hookshot Cave - Bottom Left", True, ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Pegasus Boots', 'Hookshot', 'Bomb Upgrade (50)']], ["Hookshot Cave - Top Left", False, []], ["Hookshot Cave - Top Left", False, [], ['Hookshot']], ["Hookshot Cave - Top Left", False, [], ['Progressive Glove', 'Pegasus Boots', 'Magic Mirror']], - ["Hookshot Cave - Top Left", True, ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Pegasus Boots', 'Hookshot', 'Bomb Upgrade (50)']], ["Hookshot Cave - Top Right", False, []], ["Hookshot Cave - Top Right", False, [], ['Hookshot']], ["Hookshot Cave - Top Right", False, [], ['Progressive Glove', 'Pegasus Boots', 'Magic Mirror']], - ["Hookshot Cave - Top Right", True, ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Pegasus Boots', 'Hookshot', 'Bomb Upgrade (50)']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/inverted_owg/TestDungeons.py b/worlds/alttp/test/inverted_owg/TestDungeons.py index 53b12bdf89d1..ada1b92fca49 100644 --- a/worlds/alttp/test/inverted_owg/TestDungeons.py +++ b/worlds/alttp/test/inverted_owg/TestDungeons.py @@ -41,7 +41,8 @@ def testFirstDungeonChests(self): ["Tower of Hera - Basement Cage", False, []], ["Tower of Hera - Basement Cage", False, [], ['Moon Pearl']], - ["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl']], + ["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl', 'Bomb Upgrade (50)']], + ["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Sword']], ["Castle Tower - Room 03", False, []], ["Castle Tower - Room 03", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index fc38437e3ed7..1de22b95e593 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -6,7 +6,7 @@ from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase @@ -17,14 +17,13 @@ def setUp(self): self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") self.multiworld.mode[1].value = 2 self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] create_inverted_regions(self.multiworld, 1) self.world.create_dungeons() create_shops(self.multiworld, 1) link_inverted_entrances(self.multiworld, 1) self.world.create_items() - self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None diff --git a/worlds/alttp/test/minor_glitches/TestMinor.py b/worlds/alttp/test/minor_glitches/TestMinor.py index a7b529382e5e..8432028bf007 100644 --- a/worlds/alttp/test/minor_glitches/TestMinor.py +++ b/worlds/alttp/test/minor_glitches/TestMinor.py @@ -13,12 +13,11 @@ def setUp(self): self.world_setup() self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.world.er_seed = 0 self.world.create_regions() self.world.create_items() - self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) self.multiworld.itempool.extend(item_factory( ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', diff --git a/worlds/alttp/test/owg/TestDeathMountain.py b/worlds/alttp/test/owg/TestDeathMountain.py index 0933b2881e2d..59308b65f092 100644 --- a/worlds/alttp/test/owg/TestDeathMountain.py +++ b/worlds/alttp/test/owg/TestDeathMountain.py @@ -177,7 +177,7 @@ def testEastDarkWorldDeathMountain(self): ["Hookshot Cave - Bottom Right", False, []], ["Hookshot Cave - Bottom Right", False, [], ['Progressive Glove', 'Pegasus Boots']], ["Hookshot Cave - Bottom Right", False, [], ['Moon Pearl']], - ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Pegasus Boots', 'Bomb Upgrade (50)']], ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], @@ -185,7 +185,7 @@ def testEastDarkWorldDeathMountain(self): ["Hookshot Cave - Bottom Left", False, [], ['Progressive Glove', 'Pegasus Boots']], ["Hookshot Cave - Bottom Left", False, [], ['Moon Pearl']], ["Hookshot Cave - Bottom Left", False, [], ['Hookshot']], - ["Hookshot Cave - Bottom Left", True, ['Moon Pearl', 'Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Moon Pearl', 'Pegasus Boots', 'Hookshot', 'Bomb Upgrade (50)']], ["Hookshot Cave - Bottom Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], ["Hookshot Cave - Bottom Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], @@ -193,7 +193,7 @@ def testEastDarkWorldDeathMountain(self): ["Hookshot Cave - Top Left", False, [], ['Progressive Glove', 'Pegasus Boots']], ["Hookshot Cave - Top Left", False, [], ['Moon Pearl']], ["Hookshot Cave - Top Left", False, [], ['Hookshot']], - ["Hookshot Cave - Top Left", True, ['Moon Pearl', 'Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Moon Pearl', 'Pegasus Boots', 'Hookshot', 'Bomb Upgrade (50)']], ["Hookshot Cave - Top Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], ["Hookshot Cave - Top Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], @@ -201,7 +201,7 @@ def testEastDarkWorldDeathMountain(self): ["Hookshot Cave - Top Right", False, [], ['Progressive Glove', 'Pegasus Boots']], ["Hookshot Cave - Top Right", False, [], ['Moon Pearl']], ["Hookshot Cave - Top Right", False, [], ['Hookshot']], - ["Hookshot Cave - Top Right", True, ['Moon Pearl', 'Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Moon Pearl', 'Pegasus Boots', 'Hookshot', 'Bomb Upgrade (50)']], ["Hookshot Cave - Top Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], ["Hookshot Cave - Top Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/owg/TestDungeons.py b/worlds/alttp/test/owg/TestDungeons.py index e43e18d16cf2..2e55b308d327 100644 --- a/worlds/alttp/test/owg/TestDungeons.py +++ b/worlds/alttp/test/owg/TestDungeons.py @@ -34,11 +34,11 @@ def testFirstDungeonChests(self): ["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Flute", "Lamp"]], ["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Magic Mirror", "Hammer"]], ["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Magic Mirror", "Hookshot"]], - ["Tower of Hera - Basement Cage", True, ['Pegasus Boots']], - ["Tower of Hera - Basement Cage", True, ["Flute", "Magic Mirror"]], - ["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror"]], + ["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Bomb Upgrade (50)']], + ["Tower of Hera - Basement Cage", True, ["Flute", "Magic Mirror", 'Bomb Upgrade (50)']], + ["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror", 'Bomb Upgrade (50)']], ["Tower of Hera - Basement Cage", True, ["Flute", "Hookshot", "Hammer"]], - ["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror"]], + ["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror", 'Bomb Upgrade (50)']], ["Castle Tower - Room 03", False, []], ["Castle Tower - Room 03", False, ['Progressive Sword'], ['Progressive Sword', 'Cape', 'Beat Agahnim 1']], diff --git a/worlds/alttp/test/owg/TestVanillaOWG.py b/worlds/alttp/test/owg/TestVanillaOWG.py index 3506154587e7..67156eb97275 100644 --- a/worlds/alttp/test/owg/TestVanillaOWG.py +++ b/worlds/alttp/test/owg/TestVanillaOWG.py @@ -11,14 +11,13 @@ class TestVanillaOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_items() - self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None diff --git a/worlds/alttp/test/vanilla/TestVanilla.py b/worlds/alttp/test/vanilla/TestVanilla.py index 5865ddf9873d..7eebc349d43f 100644 --- a/worlds/alttp/test/vanilla/TestVanilla.py +++ b/worlds/alttp/test/vanilla/TestVanilla.py @@ -11,13 +11,12 @@ class TestVanilla(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches") - self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_items() - self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None diff --git a/worlds/apsudoku/__init__.py b/worlds/apsudoku/__init__.py new file mode 100644 index 000000000000..c6bd02bdc262 --- /dev/null +++ b/worlds/apsudoku/__init__.py @@ -0,0 +1,34 @@ +from typing import Dict + +from BaseClasses import Tutorial +from ..AutoWorld import WebWorld, World + +class AP_SudokuWebWorld(WebWorld): + options_page = "games/Sudoku/info/en" + theme = 'partyTime' + + setup_en = Tutorial( + tutorial_name='Setup Guide', + description='A guide to playing APSudoku', + language='English', + file_name='setup_en.md', + link='setup/en', + authors=['EmilyV'] + ) + + tutorials = [setup_en] + +class AP_SudokuWorld(World): + """ + Play a little Sudoku while you're in BK mode to maybe get some useful hints + """ + game = "Sudoku" + web = AP_SudokuWebWorld() + + item_name_to_id: Dict[str, int] = {} + location_name_to_id: Dict[str, int] = {} + + @classmethod + def stage_assert_generate(cls, multiworld): + raise Exception("APSudoku cannot be used for generating worlds, the client can instead connect to any slot from any world") + diff --git a/worlds/apsudoku/docs/en_Sudoku.md b/worlds/apsudoku/docs/en_Sudoku.md new file mode 100644 index 000000000000..e81f773e0291 --- /dev/null +++ b/worlds/apsudoku/docs/en_Sudoku.md @@ -0,0 +1,13 @@ +# APSudoku + +## Hint Games + +HintGames do not need to be added at the start of a seed, and do not create a 'slot'- instead, you connect the HintGame client to a different game's slot. By playing a HintGame, you can earn hints for the connected slot. + +## What is this game? + +Play Sudoku puzzles of varying difficulties, earning a hint for each puzzle correctly solved. Harder puzzles are more likely to grant a hint towards a Progression item, though otherwise what hint is granted is random. + +## Where is the options page? + +There is no options page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld. diff --git a/worlds/apsudoku/docs/setup_en.md b/worlds/apsudoku/docs/setup_en.md new file mode 100644 index 000000000000..cf2c755bd837 --- /dev/null +++ b/worlds/apsudoku/docs/setup_en.md @@ -0,0 +1,37 @@ +# APSudoku Setup Guide + +## Required Software +- [APSudoku](https://github.com/EmilyV99/APSudoku) +- Windows (most tested on Win10) +- Other platforms might be able to build from source themselves; and may be included in the future. + +## General Concept + +This is a HintGame client, which can connect to any multiworld slot, allowing you to play Sudoku to unlock random hints for that slot's locations. + +Does not need to be added at the start of a seed, as it does not create any slots of its own, nor does it have any YAML files. + +## Installation Procedures + +Go to the latest release from the [APSudoku Releases page](https://github.com/EmilyV99/APSudoku/releases). Download and extract the `APSudoku.zip` file. + +## Joining a MultiWorld Game + +1. Run APSudoku.exe +2. Under the 'Archipelago' tab at the top-right: + - Enter the server url & port number + - Enter the name of the slot you wish to connect to + - Enter the room password (optional) + - Select DeathLink related settings (optional) + - Press connect +3. Go back to the 'Sudoku' tab + - Click the various '?' buttons for information on how to play / control +4. Choose puzzle difficulty +5. Try to solve the Sudoku. Click 'Check' when done. + +## DeathLink Support + +If 'DeathLink' is enabled when you click 'Connect': +- Lose a life if you check an incorrect puzzle (not an _incomplete_ puzzle- if any cells are empty, you get off with a warning), or quit a puzzle without solving it (including disconnecting). +- Life count customizable (default 0). Dying with 0 lives left kills linked players AND resets your puzzle. +- On receiving a DeathLink from another player, your puzzle resets. diff --git a/worlds/aquaria/Items.py b/worlds/aquaria/Items.py new file mode 100644 index 000000000000..34557d95d00d --- /dev/null +++ b/worlds/aquaria/Items.py @@ -0,0 +1,214 @@ +""" +Author: Louis M +Date: Fri, 15 Mar 2024 18:41:40 +0000 +Description: Manage items in the Aquaria game multiworld randomizer +""" + +from typing import Optional +from enum import Enum +from BaseClasses import Item, ItemClassification + + +class ItemType(Enum): + """ + Used to indicate to the multi-world if an item is useful or not + """ + NORMAL = 0 + PROGRESSION = 1 + JUNK = 2 + + +class ItemGroup(Enum): + """ + Used to group items + """ + COLLECTIBLE = 0 + INGREDIENT = 1 + RECIPE = 2 + HEALTH = 3 + UTILITY = 4 + SONG = 5 + TURTLE = 6 + + +class AquariaItem(Item): + """ + A single item in the Aquaria game. + """ + game: str = "Aquaria" + """The name of the game""" + + def __init__(self, name: str, classification: ItemClassification, + code: Optional[int], player: int): + """ + Initialisation of the Item + :param name: The name of the item + :param classification: If the item is useful or not + :param code: The ID of the item (if None, it is an event) + :param player: The ID of the player in the multiworld + """ + super().__init__(name, classification, code, player) + + +class ItemData: + """ + Data of an item. + """ + id: int + count: int + type: ItemType + group: ItemGroup + + def __init__(self, id: int, count: int, type: ItemType, group: ItemGroup): + """ + Initialisation of the item data + @param id: The item ID + @param count: the number of items in the pool + @param type: the importance type of the item + @param group: the usage of the item in the game + """ + self.id = id + self.count = count + self.type = type + self.group = group + + +"""Information data for every (not event) item.""" +item_table = { + # name: ID, Nb, Item Type, Item Group + "Anemone": ItemData(698000, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_anemone + "Arnassi Statue": ItemData(698001, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_arnassi_statue + "Big Seed": ItemData(698002, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_big_seed + "Glowing Seed": ItemData(698003, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_bio_seed + "Black Pearl": ItemData(698004, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_blackpearl + "Baby Blaster": ItemData(698005, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_blaster + "Crab Armor": ItemData(698006, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_crab_costume + "Baby Dumbo": ItemData(698007, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_dumbo + "Tooth": ItemData(698008, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_boss + "Energy Statue": ItemData(698009, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_statue + "Krotite Armor": ItemData(698010, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_temple + "Golden Starfish": ItemData(698011, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_gold_star + "Golden Gear": ItemData(698012, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_golden_gear + "Jelly Beacon": ItemData(698013, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_beacon + "Jelly Costume": ItemData(698014, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_jelly_costume + "Jelly Plant": ItemData(698015, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_plant + "Mithalas Doll": ItemData(698016, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithala_doll + "Mithalan Dress": ItemData(698017, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalan_costume + "Mithalas Banner": ItemData(698018, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_banner + "Mithalas Pot": ItemData(698019, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_pot + "Mutant Costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume + "Baby Nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus + "Baby Piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha + "Arnassi Armor": ItemData(698023, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_seahorse_costume + "Seed Bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag + "King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull + "Song Plant Spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed + "Stone Head": ItemData(698027, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_stone_head + "Sun Key": ItemData(698028, 1, ItemType.NORMAL, ItemGroup.COLLECTIBLE), # collectible_sun_key + "Girl Costume": ItemData(698029, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_teen_costume + "Odd Container": ItemData(698030, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_treasure_chest + "Trident": ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head + "Turtle Egg": ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg + "Jelly Egg": ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed + "Urchin Costume": ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume + "Baby Walker": ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker + "Vedha's Cure-All-All": ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All + "Zuuna's perogi": ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi + "Arcane poultice": ItemData(698038, 7, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_arcanepoultice + "Berry ice cream": ItemData(698039, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_berryicecream + "Buttery sea loaf": ItemData(698040, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_butterysealoaf + "Cold borscht": ItemData(698041, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldborscht + "Cold soup": ItemData(698042, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldsoup + "Crab cake": ItemData(698043, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_crabcake + "Divine soup": ItemData(698044, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_divinesoup + "Dumbo ice cream": ItemData(698045, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_dumboicecream + "Fish oil": ItemData(698046, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil + "Glowing egg": ItemData(698047, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg + "Hand roll": ItemData(698048, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_handroll + "Healing poultice": ItemData(698049, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice + "Hearty soup": ItemData(698050, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_heartysoup + "Hot borscht": ItemData(698051, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_hotborscht + "Hot soup": ItemData(698052, 3, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup + "Ice cream": ItemData(698053, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_icecream + "Leadership roll": ItemData(698054, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll + "Leaf poultice": ItemData(698055, 5, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice + "Leeching poultice": ItemData(698056, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leechingpoultice + "Legendary cake": ItemData(698057, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_legendarycake + "Loaf of life": ItemData(698058, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_loafoflife + "Long life soup": ItemData(698059, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_longlifesoup + "Magic soup": ItemData(698060, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_magicsoup + "Mushroom x 2": ItemData(698061, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_mushroom + "Perogi": ItemData(698062, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_perogi + "Plant leaf": ItemData(698063, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf + "Plump perogi": ItemData(698064, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_plumpperogi + "Poison loaf": ItemData(698065, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonloaf + "Poison soup": ItemData(698066, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonsoup + "Rainbow mushroom": ItemData(698067, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_rainbowmushroom + "Rainbow soup": ItemData(698068, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_rainbowsoup + "Red berry": ItemData(698069, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redberry + "Red bulb x 2": ItemData(698070, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redbulb + "Rotten cake": ItemData(698071, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottencake + "Rotten loaf x 8": ItemData(698072, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottenloaf + "Rotten meat": ItemData(698073, 5, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat + "Royal soup": ItemData(698074, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_royalsoup + "Sea cake": ItemData(698075, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_seacake + "Sea loaf": ItemData(698076, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf + "Shark fin soup": ItemData(698077, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sharkfinsoup + "Sight poultice": ItemData(698078, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sightpoultice + "Small bone x 2": ItemData(698079, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone + "Small egg": ItemData(698080, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg + "Small tentacle x 2": ItemData(698081, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smalltentacle + "Special bulb": ItemData(698082, 5, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_specialbulb + "Special cake": ItemData(698083, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_specialcake + "Spicy meat x 2": ItemData(698084, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_spicymeat + "Spicy roll": ItemData(698085, 11, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicyroll + "Spicy soup": ItemData(698086, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicysoup + "Spider roll": ItemData(698087, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spiderroll + "Swamp cake": ItemData(698088, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_swampcake + "Tasty cake": ItemData(698089, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastycake + "Tasty roll": ItemData(698090, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastyroll + "Tough cake": ItemData(698091, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_toughcake + "Turtle soup": ItemData(698092, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_turtlesoup + "Vedha sea crisp": ItemData(698093, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_vedhaseacrisp + "Veggie cake": ItemData(698094, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiecake + "Veggie ice cream": ItemData(698095, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggieicecream + "Veggie soup": ItemData(698096, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiesoup + "Volcano roll": ItemData(698097, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_volcanoroll + "Health upgrade": ItemData(698098, 5, ItemType.NORMAL, ItemGroup.HEALTH), # upgrade_health_? + "Wok": ItemData(698099, 1, ItemType.NORMAL, ItemGroup.UTILITY), # upgrade_wok + "Eel oil x 2": ItemData(698100, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_eeloil + "Fish meat x 2": ItemData(698101, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishmeat + "Fish oil x 3": ItemData(698102, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil + "Glowing egg x 2": ItemData(698103, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg + "Healing poultice x 2": ItemData(698104, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice + "Hot soup x 2": ItemData(698105, 1, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup + "Leadership roll x 2": ItemData(698106, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll + "Leaf poultice x 3": ItemData(698107, 2, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice + "Plant leaf x 2": ItemData(698108, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf + "Plant leaf x 3": ItemData(698109, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf + "Rotten meat x 2": ItemData(698110, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat + "Rotten meat x 8": ItemData(698111, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat + "Sea loaf x 2": ItemData(698112, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf + "Small bone x 3": ItemData(698113, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone + "Small egg x 2": ItemData(698114, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg + "Li and Li song": ItemData(698115, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_li + "Shield song": ItemData(698116, 1, ItemType.NORMAL, ItemGroup.SONG), # song_shield + "Beast form": ItemData(698117, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_beast + "Sun form": ItemData(698118, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_sun + "Nature form": ItemData(698119, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_nature + "Energy form": ItemData(698120, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_energy + "Bind song": ItemData(698121, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_bind + "Fish form": ItemData(698122, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_fish + "Spirit form": ItemData(698123, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_spirit + "Dual form": ItemData(698124, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_dual + "Transturtle Veil top left": ItemData(698125, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil01 + "Transturtle Veil top right": ItemData(698126, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil02 + "Transturtle Open Water top right": ItemData(698127, 1, ItemType.PROGRESSION, + ItemGroup.TURTLE), # transport_openwater03 + "Transturtle Forest bottom left": ItemData(698128, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest04 + "Transturtle Home Water": ItemData(698129, 1, ItemType.NORMAL, ItemGroup.TURTLE), # transport_mainarea + "Transturtle Abyss right": ItemData(698130, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_abyss03 + "Transturtle Final Boss": ItemData(698131, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_finalboss + "Transturtle Simon Says": ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05 + "Transturtle Arnassi Ruins": ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse +} diff --git a/worlds/aquaria/Locations.py b/worlds/aquaria/Locations.py new file mode 100644 index 000000000000..2eb9d1e9a29d --- /dev/null +++ b/worlds/aquaria/Locations.py @@ -0,0 +1,575 @@ +""" +Author: Louis M +Date: Fri, 15 Mar 2024 18:41:40 +0000 +Description: Manage locations in the Aquaria game multiworld randomizer +""" + +from BaseClasses import Location + + +class AquariaLocation(Location): + """ + A location in the game. + """ + game: str = "Aquaria" + """The name of the game""" + + def __init__(self, player: int, name="", code=None, parent=None) -> None: + """ + Initialisation of the object + :param player: the ID of the player + :param name: the name of the location + :param code: the ID (or address) of the location (Event if None) + :param parent: the Region that this location belongs to + """ + super(AquariaLocation, self).__init__(player, name, code, parent) + self.event = code is None + + +class AquariaLocations: + + locations_verse_cave_r = { + "Verse Cave, bulb in the skeleton room": 698107, + "Verse Cave, bulb in the path right of the skeleton room": 698108, + "Verse Cave right area, Big Seed": 698175, + } + + locations_verse_cave_l = { + "Verse Cave, the Naija hint about the shield ability": 698200, + "Verse Cave left area, bulb in the center part": 698021, + "Verse Cave left area, bulb in the right part": 698022, + "Verse Cave left area, bulb under the rock at the end of the path": 698023, + } + + locations_home_water = { + "Home Water, bulb below the grouper fish": 698058, + "Home Water, bulb in the path below Nautilus Prime": 698059, + "Home Water, bulb in the little room above the grouper fish": 698060, + "Home Water, bulb in the end of the left path from the Verse Cave": 698061, + "Home Water, bulb in the top left path": 698062, + "Home Water, bulb in the bottom left room": 698063, + "Home Water, bulb close to Naija's Home": 698064, + "Home Water, bulb under the rock in the left path from the Verse Cave": 698065, + } + + locations_home_water_nautilus = { + "Home Water, Nautilus Egg": 698194, + } + + locations_home_water_transturtle = { + "Home Water, Transturtle": 698213, + } + + locations_naija_home = { + "Naija's Home, bulb after the energy door": 698119, + "Naija's Home, bulb under the rock at the right of the main path": 698120, + } + + locations_song_cave = { + "Song Cave, Erulian spirit": 698206, + "Song Cave, bulb in the top left part": 698071, + "Song Cave, bulb in the big anemone room": 698072, + "Song Cave, bulb in the path to the singing statues": 698073, + "Song Cave, bulb under the rock in the path to the singing statues": 698074, + "Song Cave, bulb under the rock close to the song door": 698075, + "Song Cave, Verse Egg": 698160, + "Song Cave, Jelly Beacon": 698178, + "Song Cave, Anemone Seed": 698162, + } + + locations_energy_temple_1 = { + "Energy Temple first area, beating the Energy Statue": 698205, + "Energy Temple first area, bulb in the bottom room blocked by a rock": 698027, + } + + locations_energy_temple_idol = { + "Energy Temple first area, Energy Idol": 698170, + } + + locations_energy_temple_2 = { + "Energy Temple second area, bulb under the rock": 698028, + } + + locations_energy_temple_altar = { + "Energy Temple bottom entrance, Krotite Armor": 698163, + } + + locations_energy_temple_3 = { + "Energy Temple third area, bulb in the bottom path": 698029, + } + + locations_energy_temple_boss = { + "Energy Temple boss area, Fallen God Tooth": 698169, + } + + locations_energy_temple_blaster_room = { + "Energy Temple blaster room, Blaster Egg": 698195, + } + + locations_openwater_tl = { + "Open Water top left area, bulb under the rock in the right path": 698001, + "Open Water top left area, bulb under the rock in the left path": 698002, + "Open Water top left area, bulb to the right of the save crystal": 698003, + } + + locations_openwater_tr = { + "Open Water top right area, bulb in the small path before Mithalas": 698004, + "Open Water top right area, bulb in the path from the left entrance": 698005, + "Open Water top right area, bulb in the clearing close to the bottom exit": 698006, + "Open Water top right area, bulb in the big clearing close to the save crystal": 698007, + "Open Water top right area, bulb in the big clearing to the top exit": 698008, + "Open Water top right area, first urn in the Mithalas exit": 698148, + "Open Water top right area, second urn in the Mithalas exit": 698149, + "Open Water top right area, third urn in the Mithalas exit": 698150, + } + + locations_openwater_tr_turtle = { + "Open Water top right area, bulb in the turtle room": 698009, + "Open Water top right area, Transturtle": 698211, + } + + locations_openwater_bl = { + "Open Water bottom left area, bulb behind the chomper fish": 698011, + "Open Water bottom left area, bulb inside the lowest fish pass": 698010, + } + + locations_skeleton_path = { + "Open Water skeleton path, bulb close to the right exit": 698012, + "Open Water skeleton path, bulb behind the chomper fish": 698013, + } + + locations_skeleton_path_sc = { + "Open Water skeleton path, King Skull": 698177, + } + + locations_arnassi = { + "Arnassi Ruins, bulb in the right part": 698014, + "Arnassi Ruins, bulb in the left part": 698015, + "Arnassi Ruins, bulb in the center part": 698016, + "Arnassi Ruins, Song Plant Spore": 698179, + "Arnassi Ruins, Arnassi Armor": 698191, + } + + locations_arnassi_path = { + "Arnassi Ruins, Arnassi Statue": 698164, + "Arnassi Ruins, Transturtle": 698217, + } + + locations_arnassi_crab_boss = { + "Arnassi Ruins, Crab Armor": 698187, + } + + locations_simon = { + "Simon Says area, beating Simon Says": 698156, + "Simon Says area, Transturtle": 698216, + } + + locations_mithalas_city = { + "Mithalas City, first bulb in the left city part": 698030, + "Mithalas City, second bulb in the left city part": 698035, + "Mithalas City, bulb in the right part": 698031, + "Mithalas City, bulb at the top of the city": 698033, + "Mithalas City, first bulb in a broken home": 698034, + "Mithalas City, second bulb in a broken home": 698041, + "Mithalas City, bulb in the bottom left part": 698037, + "Mithalas City, first bulb in one of the homes": 698038, + "Mithalas City, second bulb in one of the homes": 698039, + "Mithalas City, first urn in one of the homes": 698123, + "Mithalas City, second urn in one of the homes": 698124, + "Mithalas City, first urn in the city reserve": 698125, + "Mithalas City, second urn in the city reserve": 698126, + "Mithalas City, third urn in the city reserve": 698127, + } + + locations_mithalas_city_top_path = { + "Mithalas City, first bulb at the end of the top path": 698032, + "Mithalas City, second bulb at the end of the top path": 698040, + "Mithalas City, bulb in the top path": 698036, + "Mithalas City, Mithalas Pot": 698174, + "Mithalas City, urn in the Castle flower tube entrance": 698128, + } + + locations_mithalas_city_fishpass = { + "Mithalas City, Doll": 698173, + "Mithalas City, urn inside a home fish pass": 698129, + } + + locations_cathedral_l = { + "Mithalas City Castle, bulb in the flesh hole": 698042, + "Mithalas City Castle, Blue Banner": 698165, + "Mithalas City Castle, urn in the bedroom": 698130, + "Mithalas City Castle, first urn of the single lamp path": 698131, + "Mithalas City Castle, second urn of the single lamp path": 698132, + "Mithalas City Castle, urn in the bottom room": 698133, + "Mithalas City Castle, first urn on the entrance path": 698134, + "Mithalas City Castle, second urn on the entrance path": 698135, + } + + locations_cathedral_l_tube = { + "Mithalas City Castle, beating the Priests": 698208, + } + + locations_cathedral_l_sc = { + "Mithalas City Castle, Trident Head": 698183, + } + + locations_cathedral_r = { + "Mithalas Cathedral, first urn in the top right room": 698136, + "Mithalas Cathedral, second urn in the top right room": 698137, + "Mithalas Cathedral, third urn in the top right room": 698138, + "Mithalas Cathedral, urn in the flesh room with fleas": 698139, + "Mithalas Cathedral, first urn in the bottom right path": 698140, + "Mithalas Cathedral, second urn in the bottom right path": 698141, + "Mithalas Cathedral, urn behind the flesh vein": 698142, + "Mithalas Cathedral, urn in the top left eyes boss room": 698143, + "Mithalas Cathedral, first urn in the path behind the flesh vein": 698144, + "Mithalas Cathedral, second urn in the path behind the flesh vein": 698145, + "Mithalas Cathedral, third urn in the path behind the flesh vein": 698146, + "Mithalas Cathedral, fourth urn in the top right room": 698147, + "Mithalas Cathedral, Mithalan Dress": 698189, + "Mithalas Cathedral, urn below the left entrance": 698198, + } + + locations_cathedral_underground = { + "Cathedral Underground, bulb in the center part": 698113, + "Cathedral Underground, first bulb in the top left part": 698114, + "Cathedral Underground, second bulb in the top left part": 698115, + "Cathedral Underground, third bulb in the top left part": 698116, + "Cathedral Underground, bulb close to the save crystal": 698117, + "Cathedral Underground, bulb in the bottom right path": 698118, + } + + locations_cathedral_boss = { + "Mithalas boss area, beating Mithalan God": 698202, + } + + locations_forest_tl = { + "Kelp Forest top left area, bulb in the bottom left clearing": 698044, + "Kelp Forest top left area, bulb in the path down from the top left clearing": 698045, + "Kelp Forest top left area, bulb in the top left clearing": 698046, + "Kelp Forest top left area, Jelly Egg": 698185, + } + + locations_forest_tl_fp = { + "Kelp Forest top left area, bulb close to the Verse Egg": 698047, + "Kelp Forest top left area, Verse Egg": 698158, + } + + locations_forest_tr = { + "Kelp Forest top right area, bulb under the rock in the right path": 698048, + "Kelp Forest top right area, bulb at the left of the center clearing": 698049, + "Kelp Forest top right area, bulb in the left path's big room": 698051, + "Kelp Forest top right area, bulb in the left path's small room": 698052, + "Kelp Forest top right area, bulb at the top of the center clearing": 698053, + "Kelp Forest top right area, Black Pearl": 698167, + } + + locations_forest_tr_fp = { + "Kelp Forest top right area, bulb in the top fish pass": 698050, + } + + locations_forest_bl = { + "Kelp Forest bottom left area, bulb close to the spirit crystals": 698054, + "Kelp Forest bottom left area, Walker Baby": 698186, + "Kelp Forest bottom left area, Transturtle": 698212, + } + + locations_forest_br = { + "Kelp Forest bottom right area, Odd Container": 698168, + } + + locations_forest_boss = { + "Kelp Forest boss area, beating Drunian God": 698204, + } + + locations_forest_boss_entrance = { + "Kelp Forest boss room, bulb at the bottom of the area": 698055, + } + + locations_forest_fish_cave = { + "Kelp Forest bottom left area, Fish Cave puzzle": 698207, + } + + locations_forest_sprite_cave = { + "Kelp Forest sprite cave, bulb inside the fish pass": 698056, + } + + locations_forest_sprite_cave_tube = { + "Kelp Forest sprite cave, bulb in the second room": 698057, + "Kelp Forest sprite cave, Seed Bag": 698176, + } + + locations_mermog_cave = { + "Mermog cave, bulb in the left part of the cave": 698121, + } + + locations_mermog_boss = { + "Mermog cave, Piranha Egg": 698197, + } + + locations_veil_tl = { + "The Veil top left area, In Li's cave": 698199, + "The Veil top left area, bulb under the rock in the top right path": 698078, + "The Veil top left area, bulb hidden behind the blocking rock": 698076, + "The Veil top left area, Transturtle": 698209, + } + + locations_veil_tl_fp = { + "The Veil top left area, bulb inside the fish pass": 698077, + } + + locations_turtle_cave = { + "Turtle cave, Turtle Egg": 698184, + } + + locations_turtle_cave_bubble = { + "Turtle cave, bulb in Bubble Cliff": 698000, + "Turtle cave, Urchin Costume": 698193, + } + + locations_veil_tr_r = { + "The Veil top right area, bulb in the middle of the wall jump cliff": 698079, + "The Veil top right area, Golden Starfish": 698180, + } + + locations_veil_tr_l = { + "The Veil top right area, bulb at the top of the waterfall": 698080, + "The Veil top right area, Transturtle": 698210, + } + + locations_veil_bl = { + "The Veil bottom area, bulb in the left path": 698082, + } + + locations_veil_b_sc = { + "The Veil bottom area, bulb in the spirit path": 698081, + } + + locations_veil_bl_fp = { + "The Veil bottom area, Verse Egg": 698157, + } + + locations_veil_br = { + "The Veil bottom area, Stone Head": 698181, + } + + locations_octo_cave_t = { + "Octopus Cave, Dumbo Egg": 698196, + } + + locations_octo_cave_b = { + "Octopus Cave, bulb in the path below the Octopus Cave path": 698122, + } + + locations_sun_temple_l = { + "Sun Temple, bulb in the top left part": 698094, + "Sun Temple, bulb in the top right part": 698095, + "Sun Temple, bulb at the top of the high dark room": 698096, + "Sun Temple, Golden Gear": 698171, + } + + locations_sun_temple_r = { + "Sun Temple, first bulb of the temple": 698091, + "Sun Temple, bulb on the left part": 698092, + "Sun Temple, bulb in the hidden room of the right part": 698093, + "Sun Temple, Sun Key": 698182, + } + + locations_sun_temple_boss_path = { + "Sun Worm path, first path bulb": 698017, + "Sun Worm path, second path bulb": 698018, + "Sun Worm path, first cliff bulb": 698019, + "Sun Worm path, second cliff bulb": 698020, + } + + locations_sun_temple_boss = { + "Sun Temple boss area, beating Sun God": 698203, + } + + locations_abyss_l = { + "Abyss left area, bulb in hidden path room": 698024, + "Abyss left area, bulb in the right part": 698025, + "Abyss left area, Glowing Seed": 698166, + "Abyss left area, Glowing Plant": 698172, + } + + locations_abyss_lb = { + "Abyss left area, bulb in the bottom fish pass": 698026, + } + + locations_abyss_r = { + "Abyss right area, bulb behind the rock in the whale room": 698109, + "Abyss right area, bulb in the middle path": 698110, + "Abyss right area, bulb behind the rock in the middle path": 698111, + "Abyss right area, bulb in the left green room": 698112, + "Abyss right area, Transturtle": 698214, + } + + locations_ice_cave = { + "Ice Cave, bulb in the room to the right": 698083, + "Ice Cave, first bulb in the top exit room": 698084, + "Ice Cave, second bulb in the top exit room": 698085, + "Ice Cave, third bulb in the top exit room": 698086, + "Ice Cave, bulb in the left room": 698087, + } + + locations_bubble_cave = { + "Bubble Cave, bulb in the left cave wall": 698089, + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)": 698090, + } + + locations_bubble_cave_boss = { + "Bubble Cave, Verse Egg": 698161, + } + + locations_king_jellyfish_cave = { + "King Jellyfish Cave, bulb in the right path from King Jelly": 698088, + "King Jellyfish Cave, Jellyfish Costume": 698188, + } + + locations_whale = { + "The Whale, Verse Egg": 698159, + } + + locations_sunken_city_r = { + "Sunken City right area, crate close to the save crystal": 698154, + "Sunken City right area, crate in the left bottom room": 698155, + } + + locations_sunken_city_l = { + "Sunken City left area, crate in the little pipe room": 698151, + "Sunken City left area, crate close to the save crystal": 698152, + "Sunken City left area, crate before the bedroom": 698153, + } + + locations_sunken_city_l_bedroom = { + "Sunken City left area, Girl Costume": 698192, + } + + locations_sunken_city_boss = { + "Sunken City, bulb on top of the boss area": 698043, + } + + locations_body_c = { + "The Body center area, breaking Li's cage": 698201, + "The Body center area, bulb on the main path blocking tube": 698097, + } + + locations_body_l = { + "The Body left area, first bulb in the top face room": 698066, + "The Body left area, second bulb in the top face room": 698069, + "The Body left area, bulb below the water stream": 698067, + "The Body left area, bulb in the top path to the top face room": 698068, + "The Body left area, bulb in the bottom face room": 698070, + } + + locations_body_rt = { + "The Body right area, bulb in the top face room": 698100, + } + + locations_body_rb = { + "The Body right area, bulb in the top path to the bottom face room": 698098, + "The Body right area, bulb in the bottom face room": 698099, + } + + locations_body_b = { + "The Body bottom area, bulb in the Jelly Zap room": 698101, + "The Body bottom area, bulb in the nautilus room": 698102, + "The Body bottom area, Mutant Costume": 698190, + } + + locations_final_boss_tube = { + "Final Boss area, first bulb in the turtle room": 698103, + "Final Boss area, second bulb in the turtle room": 698104, + "Final Boss area, third bulb in the turtle room": 698105, + "Final Boss area, Transturtle": 698215, + } + + locations_final_boss = { + "Final Boss area, bulb in the boss third form room": 698106, + } + + +location_table = { + **AquariaLocations.locations_openwater_tl, + **AquariaLocations.locations_openwater_tr, + **AquariaLocations.locations_openwater_tr_turtle, + **AquariaLocations.locations_openwater_bl, + **AquariaLocations.locations_skeleton_path, + **AquariaLocations.locations_skeleton_path_sc, + **AquariaLocations.locations_arnassi, + **AquariaLocations.locations_arnassi_path, + **AquariaLocations.locations_arnassi_crab_boss, + **AquariaLocations.locations_sun_temple_l, + **AquariaLocations.locations_sun_temple_r, + **AquariaLocations.locations_sun_temple_boss_path, + **AquariaLocations.locations_sun_temple_boss, + **AquariaLocations.locations_verse_cave_r, + **AquariaLocations.locations_verse_cave_l, + **AquariaLocations.locations_abyss_l, + **AquariaLocations.locations_abyss_lb, + **AquariaLocations.locations_abyss_r, + **AquariaLocations.locations_energy_temple_1, + **AquariaLocations.locations_energy_temple_2, + **AquariaLocations.locations_energy_temple_3, + **AquariaLocations.locations_energy_temple_boss, + **AquariaLocations.locations_energy_temple_blaster_room, + **AquariaLocations.locations_energy_temple_altar, + **AquariaLocations.locations_energy_temple_idol, + **AquariaLocations.locations_mithalas_city, + **AquariaLocations.locations_mithalas_city_top_path, + **AquariaLocations.locations_mithalas_city_fishpass, + **AquariaLocations.locations_cathedral_l, + **AquariaLocations.locations_cathedral_l_tube, + **AquariaLocations.locations_cathedral_l_sc, + **AquariaLocations.locations_cathedral_r, + **AquariaLocations.locations_cathedral_underground, + **AquariaLocations.locations_cathedral_boss, + **AquariaLocations.locations_forest_tl, + **AquariaLocations.locations_forest_tl_fp, + **AquariaLocations.locations_forest_tr, + **AquariaLocations.locations_forest_tr_fp, + **AquariaLocations.locations_forest_bl, + **AquariaLocations.locations_forest_br, + **AquariaLocations.locations_forest_boss, + **AquariaLocations.locations_forest_boss_entrance, + **AquariaLocations.locations_forest_sprite_cave, + **AquariaLocations.locations_forest_sprite_cave_tube, + **AquariaLocations.locations_forest_fish_cave, + **AquariaLocations.locations_home_water, + **AquariaLocations.locations_home_water_transturtle, + **AquariaLocations.locations_home_water_nautilus, + **AquariaLocations.locations_body_l, + **AquariaLocations.locations_body_rt, + **AquariaLocations.locations_body_rb, + **AquariaLocations.locations_body_c, + **AquariaLocations.locations_body_b, + **AquariaLocations.locations_final_boss_tube, + **AquariaLocations.locations_final_boss, + **AquariaLocations.locations_song_cave, + **AquariaLocations.locations_veil_tl, + **AquariaLocations.locations_veil_tl_fp, + **AquariaLocations.locations_turtle_cave, + **AquariaLocations.locations_turtle_cave_bubble, + **AquariaLocations.locations_veil_tr_r, + **AquariaLocations.locations_veil_tr_l, + **AquariaLocations.locations_veil_bl, + **AquariaLocations.locations_veil_b_sc, + **AquariaLocations.locations_veil_bl_fp, + **AquariaLocations.locations_veil_br, + **AquariaLocations.locations_ice_cave, + **AquariaLocations.locations_king_jellyfish_cave, + **AquariaLocations.locations_bubble_cave, + **AquariaLocations.locations_bubble_cave_boss, + **AquariaLocations.locations_naija_home, + **AquariaLocations.locations_mermog_cave, + **AquariaLocations.locations_mermog_boss, + **AquariaLocations.locations_octo_cave_t, + **AquariaLocations.locations_octo_cave_b, + **AquariaLocations.locations_sunken_city_l, + **AquariaLocations.locations_sunken_city_r, + **AquariaLocations.locations_sunken_city_boss, + **AquariaLocations.locations_sunken_city_l_bedroom, + **AquariaLocations.locations_simon, + **AquariaLocations.locations_whale, +} diff --git a/worlds/aquaria/Options.py b/worlds/aquaria/Options.py new file mode 100644 index 000000000000..8c0142debff0 --- /dev/null +++ b/worlds/aquaria/Options.py @@ -0,0 +1,153 @@ +""" +Author: Louis M +Date: Fri, 15 Mar 2024 18:41:40 +0000 +Description: Manage options in the Aquaria game multiworld randomizer +""" + +from dataclasses import dataclass +from Options import Toggle, Choice, Range, PerGameCommonOptions, DefaultOnToggle, StartInventoryPool + + +class IngredientRandomizer(Choice): + """ + Select if the simple ingredients (that do not have a recipe) should be randomized. + If "Common Ingredients" is selected, the randomization will exclude the "Red Bulb", "Special Bulb" and "Rukh Egg". + """ + display_name = "Randomize Ingredients" + option_off = 0 + option_common_ingredients = 1 + option_all_ingredients = 2 + default = 0 + + +class DishRandomizer(Toggle): + """Randomize the drop of Dishes (Ingredients with recipe).""" + display_name = "Dish Randomizer" + + +class TurtleRandomizer(Choice): + """Randomize the transportation turtle.""" + display_name = "Turtle Randomizer" + option_none = 0 + option_all = 1 + option_all_except_final = 2 + default = 2 + + +class EarlyEnergyForm(DefaultOnToggle): + """ Force the Energy Form to be in a location early in the game """ + display_name = "Early Energy Form" + + +class AquarianTranslation(Toggle): + """Translate the Aquarian scripture in the game into English.""" + display_name = "Translate Aquarian" + + +class BigBossesToBeat(Range): + """ + The number of big bosses to beat before having access to the creator (the final boss). The big bosses are + "Fallen God", "Mithalan God", "Drunian God", "Sun God" and "The Golem". + """ + display_name = "Big bosses to beat" + range_start = 0 + range_end = 5 + default = 0 + + +class MiniBossesToBeat(Range): + """ + The number of minibosses to beat before having access to the creator (the final boss). The minibosses are + "Nautilus Prime", "Blaster Peg Prime", "Mergog", "Mithalan priests", "Octopus Prime", "Crabbius Maximus", + "Mantis Shrimp Prime" and "King Jellyfish God Prime". + Note that the Energy Statue and Simon Says are not minibosses. + """ + display_name = "Minibosses to beat" + range_start = 0 + range_end = 8 + default = 0 + + +class Objective(Choice): + """ + The game objective can be to kill the creator or to kill the creator after obtaining all three secret memories. + """ + display_name = "Objective" + option_kill_the_creator = 0 + option_obtain_secrets_and_kill_the_creator = 1 + default = 0 + + +class SkipFirstVision(Toggle): + """ + The first vision in the game, where Naija transforms into Energy Form and gets flooded by enemies, is quite cool but + can be quite long when you already know what is going on. This option can be used to skip this vision. + """ + display_name = "Skip Naija's first vision" + + +class NoProgressionHardOrHiddenLocation(Toggle): + """ + Make sure that there are no progression items at hard-to-reach or hard-to-find locations. + Those locations are very High locations (that need beast form, soup and skill to get), + every location in the bubble cave, locations where need you to cross a false wall without any indication, + the Arnassi race, bosses and minibosses. Useful for those that want a more casual run. + """ + display_name = "No progression in hard or hidden locations" + + +class LightNeededToGetToDarkPlaces(DefaultOnToggle): + """ + Make sure that the sun form or the dumbo pet can be acquired before getting to dark places. + Be aware that navigating in dark places without light is extremely difficult. + """ + display_name = "Light needed to get to dark places" + + +class BindSongNeededToGetUnderRockBulb(Toggle): + """ + Make sure that the bind song can be acquired before having to obtain sing bulbs under rocks. + """ + display_name = "Bind song needed to get sing bulbs under rocks" + + +class BlindGoal(Toggle): + """ + Hide the goal's requirements from the help page so that you have to go to the last boss door to know + what is needed to access the boss. + """ + display_name = "Hide the goal's requirements" + + +class UnconfineHomeWater(Choice): + """ + Open the way out of the Home Water area so that Naija can go to open water and beyond without the bind song. + """ + display_name = "Unconfine Home Water Area" + option_off = 0 + option_via_energy_door = 1 + option_via_transturtle = 2 + option_via_both = 3 + default = 0 + + +@dataclass +class AquariaOptions(PerGameCommonOptions): + """ + Every option in the Aquaria randomizer + """ + start_inventory_from_pool: StartInventoryPool + objective: Objective + mini_bosses_to_beat: MiniBossesToBeat + big_bosses_to_beat: BigBossesToBeat + turtle_randomizer: TurtleRandomizer + early_energy_form: EarlyEnergyForm + light_needed_to_get_to_dark_places: LightNeededToGetToDarkPlaces + bind_song_needed_to_get_under_rock_bulb: BindSongNeededToGetUnderRockBulb + unconfine_home_water: UnconfineHomeWater + no_progression_hard_or_hidden_locations: NoProgressionHardOrHiddenLocation + ingredient_randomizer: IngredientRandomizer + dish_randomizer: DishRandomizer + aquarian_translation: AquarianTranslation + skip_first_vision: SkipFirstVision + blind_goal: BlindGoal diff --git a/worlds/aquaria/Regions.py b/worlds/aquaria/Regions.py new file mode 100755 index 000000000000..93c02d4e6766 --- /dev/null +++ b/worlds/aquaria/Regions.py @@ -0,0 +1,1391 @@ +""" +Author: Louis M +Date: Fri, 15 Mar 2024 18:41:40 +0000 +Description: Used to manage Regions in the Aquaria game multiworld randomizer +""" + +from typing import Dict, Optional +from BaseClasses import MultiWorld, Region, Entrance, ItemClassification, CollectionState +from .Items import AquariaItem +from .Locations import AquariaLocations, AquariaLocation +from .Options import AquariaOptions +from worlds.generic.Rules import add_rule, set_rule + + +# Every condition to connect regions + +def _has_hot_soup(state:CollectionState, player: int) -> bool: + """`player` in `state` has the hotsoup item""" + return state.has("Hot soup", player) + + +def _has_tongue_cleared(state:CollectionState, player: int) -> bool: + """`player` in `state` has the Body tongue cleared item""" + return state.has("Body tongue cleared", player) + + +def _has_sun_crystal(state:CollectionState, player: int) -> bool: + """`player` in `state` has the Sun crystal item""" + return state.has("Has sun crystal", player) and _has_bind_song(state, player) + + +def _has_li(state:CollectionState, player: int) -> bool: + """`player` in `state` has Li in its team""" + return state.has("Li and Li song", player) + + +def _has_damaging_item(state:CollectionState, player: int) -> bool: + """`player` in `state` has the shield song item""" + return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus", + "Baby Piranha", "Baby Blaster"}, player) + + +def _has_shield_song(state:CollectionState, player: int) -> bool: + """`player` in `state` has the shield song item""" + return state.has("Shield song", player) + + +def _has_bind_song(state:CollectionState, player: int) -> bool: + """`player` in `state` has the bind song item""" + return state.has("Bind song", player) + + +def _has_energy_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the energy form item""" + return state.has("Energy form", player) + + +def _has_beast_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the beast form item""" + return state.has("Beast form", player) + + +def _has_nature_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the nature form item""" + return state.has("Nature form", player) + + +def _has_sun_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the sun form item""" + return state.has("Sun form", player) + + +def _has_light(state:CollectionState, player: int) -> bool: + """`player` in `state` has the light item""" + return state.has("Baby Dumbo", player) or _has_sun_form(state, player) + + +def _has_dual_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the dual form item""" + return _has_li(state, player) and state.has("Dual form", player) + + +def _has_fish_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the fish form item""" + return state.has("Fish form", player) + + +def _has_spirit_form(state:CollectionState, player: int) -> bool: + """`player` in `state` has the spirit form item""" + return state.has("Spirit form", player) + + +def _has_big_bosses(state:CollectionState, player: int) -> bool: + """`player` in `state` has beated every big bosses""" + return state.has_all({"Fallen God beated", "Mithalan God beated", "Drunian God beated", + "Sun God beated", "The Golem beated"}, player) + + +def _has_mini_bosses(state:CollectionState, player: int) -> bool: + """`player` in `state` has beated every big bosses""" + return state.has_all({"Nautilus Prime beated", "Blaster Peg Prime beated", "Mergog beated", + "Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated", + "Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player) + + +def _has_secrets(state:CollectionState, player: int) -> bool: + return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"},player) + + +class AquariaRegions: + """ + Class used to create regions of the Aquaria game + """ + menu: Region + verse_cave_r: Region + verse_cave_l: Region + home_water: Region + home_water_nautilus: Region + home_water_transturtle: Region + naija_home: Region + song_cave: Region + energy_temple_1: Region + energy_temple_2: Region + energy_temple_3: Region + energy_temple_boss: Region + energy_temple_idol: Region + energy_temple_blaster_room: Region + energy_temple_altar: Region + openwater_tl: Region + openwater_tr: Region + openwater_tr_turtle: Region + openwater_bl: Region + openwater_br: Region + skeleton_path: Region + skeleton_path_sc: Region + arnassi: Region + arnassi_path: Region + arnassi_crab_boss: Region + simon: Region + mithalas_city: Region + mithalas_city_top_path: Region + mithalas_city_fishpass: Region + cathedral_l: Region + cathedral_l_tube: Region + cathedral_l_sc: Region + cathedral_r: Region + cathedral_underground: Region + cathedral_boss_l: Region + cathedral_boss_r: Region + forest_tl: Region + forest_tl_fp: Region + forest_tr: Region + forest_tr_fp: Region + forest_bl: Region + forest_br: Region + forest_boss: Region + forest_boss_entrance: Region + forest_sprite_cave: Region + forest_sprite_cave_tube: Region + mermog_cave: Region + mermog_boss: Region + forest_fish_cave: Region + veil_tl: Region + veil_tl_fp: Region + veil_tr_l: Region + veil_tr_r: Region + veil_bl: Region + veil_b_sc: Region + veil_bl_fp: Region + veil_br: Region + octo_cave_t: Region + octo_cave_b: Region + turtle_cave: Region + turtle_cave_bubble: Region + sun_temple_l: Region + sun_temple_r: Region + sun_temple_boss_path: Region + sun_temple_boss: Region + abyss_l: Region + abyss_lb: Region + abyss_r: Region + ice_cave: Region + bubble_cave: Region + bubble_cave_boss: Region + king_jellyfish_cave: Region + whale: Region + first_secret: Region + sunken_city_l: Region + sunken_city_r: Region + sunken_city_boss: Region + sunken_city_l_bedroom: Region + body_c: Region + body_l: Region + body_rt: Region + body_rb: Region + body_b: Region + final_boss_loby: Region + final_boss_tube: Region + final_boss: Region + final_boss_end: Region + """ + Every Region of the game + """ + + multiworld: MultiWorld + """ + The Current Multiworld game. + """ + + player: int + """ + The ID of the player + """ + + def __add_region(self, hint: str, + locations: Optional[Dict[str, Optional[int]]]) -> Region: + """ + Create a new Region, add it to the `world` regions and return it. + Be aware that this function have a side effect on ``world`.`regions` + """ + region: Region = Region(hint, self.player, self.multiworld, hint) + if locations is not None: + region.add_locations(locations, AquariaLocation) + return region + + def __create_home_water_area(self) -> None: + """ + Create the `verse_cave`, `home_water` and `song_cave*` regions + """ + self.menu = self.__add_region("Menu", None) + self.verse_cave_r = self.__add_region("Verse Cave right area", + AquariaLocations.locations_verse_cave_r) + self.verse_cave_l = self.__add_region("Verse Cave left area", + AquariaLocations.locations_verse_cave_l) + self.home_water = self.__add_region("Home Water", AquariaLocations.locations_home_water) + self.home_water_nautilus = self.__add_region("Home Water, Nautilus nest", + AquariaLocations.locations_home_water_nautilus) + self.home_water_transturtle = self.__add_region("Home Water, turtle room", + AquariaLocations.locations_home_water_transturtle) + self.naija_home = self.__add_region("Naija's Home", AquariaLocations.locations_naija_home) + self.song_cave = self.__add_region("Song Cave", AquariaLocations.locations_song_cave) + + def __create_energy_temple(self) -> None: + """ + Create the `energy_temple_*` regions + """ + self.energy_temple_1 = self.__add_region("Energy Temple first area", + AquariaLocations.locations_energy_temple_1) + self.energy_temple_2 = self.__add_region("Energy Temple second area", + AquariaLocations.locations_energy_temple_2) + self.energy_temple_3 = self.__add_region("Energy Temple third area", + AquariaLocations.locations_energy_temple_3) + self.energy_temple_altar = self.__add_region("Energy Temple bottom entrance", + AquariaLocations.locations_energy_temple_altar) + self.energy_temple_boss = self.__add_region("Energy Temple fallen God room", + AquariaLocations.locations_energy_temple_boss) + self.energy_temple_idol = self.__add_region("Energy Temple Idol room", + AquariaLocations.locations_energy_temple_idol) + self.energy_temple_blaster_room = self.__add_region("Energy Temple blaster room", + AquariaLocations.locations_energy_temple_blaster_room) + + def __create_openwater(self) -> None: + """ + Create the `openwater_*`, `skeleton_path`, `arnassi*` and `simon` + regions + """ + self.openwater_tl = self.__add_region("Open Water top left area", + AquariaLocations.locations_openwater_tl) + self.openwater_tr = self.__add_region("Open Water top right area", + AquariaLocations.locations_openwater_tr) + self.openwater_tr_turtle = self.__add_region("Open Water top right area, turtle room", + AquariaLocations.locations_openwater_tr_turtle) + self.openwater_bl = self.__add_region("Open Water bottom left area", + AquariaLocations.locations_openwater_bl) + self.openwater_br = self.__add_region("Open Water bottom right area", None) + self.skeleton_path = self.__add_region("Open Water skeleton path", + AquariaLocations.locations_skeleton_path) + self.skeleton_path_sc = self.__add_region("Open Water skeleton path spirit crystal", + AquariaLocations.locations_skeleton_path_sc) + self.arnassi = self.__add_region("Arnassi Ruins", AquariaLocations.locations_arnassi) + self.arnassi_path = self.__add_region("Arnassi Ruins, back entrance path", + AquariaLocations.locations_arnassi_path) + self.arnassi_crab_boss = self.__add_region("Arnassi Ruins, Crabbius Maximus lair", + AquariaLocations.locations_arnassi_crab_boss) + + def __create_mithalas(self) -> None: + """ + Create the `mithalas_city*` and `cathedral_*` regions + """ + self.mithalas_city = self.__add_region("Mithalas City", + AquariaLocations.locations_mithalas_city) + self.mithalas_city_fishpass = self.__add_region("Mithalas City fish pass", + AquariaLocations.locations_mithalas_city_fishpass) + self.mithalas_city_top_path = self.__add_region("Mithalas City top path", + AquariaLocations.locations_mithalas_city_top_path) + self.cathedral_l = self.__add_region("Mithalas castle", AquariaLocations.locations_cathedral_l) + self.cathedral_l_tube = self.__add_region("Mithalas castle, plant tube entrance", + AquariaLocations.locations_cathedral_l_tube) + self.cathedral_l_sc = self.__add_region("Mithalas castle spirit crystal", + AquariaLocations.locations_cathedral_l_sc) + self.cathedral_r = self.__add_region("Mithalas Cathedral", + AquariaLocations.locations_cathedral_r) + self.cathedral_underground = self.__add_region("Mithalas Cathedral underground", + AquariaLocations.locations_cathedral_underground) + self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room", + AquariaLocations.locations_cathedral_boss) + self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room", None) + + def __create_forest(self) -> None: + """ + Create the `forest_*` dans `mermog_cave` regions + """ + self.forest_tl = self.__add_region("Kelp Forest top left area", + AquariaLocations.locations_forest_tl) + self.forest_tl_fp = self.__add_region("Kelp Forest top left area fish pass", + AquariaLocations.locations_forest_tl_fp) + self.forest_tr = self.__add_region("Kelp Forest top right area", + AquariaLocations.locations_forest_tr) + self.forest_tr_fp = self.__add_region("Kelp Forest top right area fish pass", + AquariaLocations.locations_forest_tr_fp) + self.forest_bl = self.__add_region("Kelp Forest bottom left area", + AquariaLocations.locations_forest_bl) + self.forest_br = self.__add_region("Kelp Forest bottom right area", + AquariaLocations.locations_forest_br) + self.forest_sprite_cave = self.__add_region("Kelp Forest spirit cave", + AquariaLocations.locations_forest_sprite_cave) + self.forest_sprite_cave_tube = self.__add_region("Kelp Forest spirit cave after the plant tube", + AquariaLocations.locations_forest_sprite_cave_tube) + self.forest_boss = self.__add_region("Kelp Forest Drunian God room", + AquariaLocations.locations_forest_boss) + self.forest_boss_entrance = self.__add_region("Kelp Forest Drunian God room entrance", + AquariaLocations.locations_forest_boss_entrance) + self.mermog_cave = self.__add_region("Kelp Forest Mermog cave", + AquariaLocations.locations_mermog_cave) + self.mermog_boss = self.__add_region("Kelp Forest Mermog cave boss", + AquariaLocations.locations_mermog_boss) + self.forest_fish_cave = self.__add_region("Kelp Forest fish cave", + AquariaLocations.locations_forest_fish_cave) + self.simon = self.__add_region("Kelp Forest, Simon's room", AquariaLocations.locations_simon) + + def __create_veil(self) -> None: + """ + Create the `veil_*`, `octo_cave` and `turtle_cave` regions + """ + self.veil_tl = self.__add_region("The Veil top left area", AquariaLocations.locations_veil_tl) + self.veil_tl_fp = self.__add_region("The Veil top left area fish pass", + AquariaLocations.locations_veil_tl_fp) + self.turtle_cave = self.__add_region("The Veil top left area, turtle cave", + AquariaLocations.locations_turtle_cave) + self.turtle_cave_bubble = self.__add_region("The Veil top left area, turtle cave Bubble Cliff", + AquariaLocations.locations_turtle_cave_bubble) + self.veil_tr_l = self.__add_region("The Veil top right area, left of temple", + AquariaLocations.locations_veil_tr_l) + self.veil_tr_r = self.__add_region("The Veil top right area, right of temple", + AquariaLocations.locations_veil_tr_r) + self.octo_cave_t = self.__add_region("Octopus Cave top entrance", + AquariaLocations.locations_octo_cave_t) + self.octo_cave_b = self.__add_region("Octopus Cave bottom entrance", + AquariaLocations.locations_octo_cave_b) + self.veil_bl = self.__add_region("The Veil bottom left area", + AquariaLocations.locations_veil_bl) + self.veil_b_sc = self.__add_region("The Veil bottom spirit crystal area", + AquariaLocations.locations_veil_b_sc) + self.veil_bl_fp = self.__add_region("The Veil bottom left area, in the sunken ship", + AquariaLocations.locations_veil_bl_fp) + self.veil_br = self.__add_region("The Veil bottom right area", + AquariaLocations.locations_veil_br) + + def __create_sun_temple(self) -> None: + """ + Create the `sun_temple*` regions + """ + self.sun_temple_l = self.__add_region("Sun Temple left area", + AquariaLocations.locations_sun_temple_l) + self.sun_temple_r = self.__add_region("Sun Temple right area", + AquariaLocations.locations_sun_temple_r) + self.sun_temple_boss_path = self.__add_region("Sun Temple before boss area", + AquariaLocations.locations_sun_temple_boss_path) + self.sun_temple_boss = self.__add_region("Sun Temple boss area", + AquariaLocations.locations_sun_temple_boss) + + def __create_abyss(self) -> None: + """ + Create the `abyss_*`, `ice_cave`, `king_jellyfish_cave` and `whale` + regions + """ + self.abyss_l = self.__add_region("Abyss left area", + AquariaLocations.locations_abyss_l) + self.abyss_lb = self.__add_region("Abyss left bottom area", AquariaLocations.locations_abyss_lb) + self.abyss_r = self.__add_region("Abyss right area", AquariaLocations.locations_abyss_r) + self.ice_cave = self.__add_region("Ice Cave", AquariaLocations.locations_ice_cave) + self.bubble_cave = self.__add_region("Bubble Cave", AquariaLocations.locations_bubble_cave) + self.bubble_cave_boss = self.__add_region("Bubble Cave boss area", AquariaLocations.locations_bubble_cave_boss) + self.king_jellyfish_cave = self.__add_region("Abyss left area, King jellyfish cave", + AquariaLocations.locations_king_jellyfish_cave) + self.whale = self.__add_region("Inside the whale", AquariaLocations.locations_whale) + self.first_secret = self.__add_region("First secret area", None) + + def __create_sunken_city(self) -> None: + """ + Create the `sunken_city_*` regions + """ + self.sunken_city_l = self.__add_region("Sunken City left area", + AquariaLocations.locations_sunken_city_l) + self.sunken_city_l_bedroom = self.__add_region("Sunken City left area, bedroom", + AquariaLocations.locations_sunken_city_l_bedroom) + self.sunken_city_r = self.__add_region("Sunken City right area", + AquariaLocations.locations_sunken_city_r) + self.sunken_city_boss = self.__add_region("Sunken City boss area", + AquariaLocations.locations_sunken_city_boss) + + def __create_body(self) -> None: + """ + Create the `body_*` and `final_boss* regions + """ + self.body_c = self.__add_region("The Body center area", + AquariaLocations.locations_body_c) + self.body_l = self.__add_region("The Body left area", + AquariaLocations.locations_body_l) + self.body_rt = self.__add_region("The Body right area, top path", + AquariaLocations.locations_body_rt) + self.body_rb = self.__add_region("The Body right area, bottom path", + AquariaLocations.locations_body_rb) + self.body_b = self.__add_region("The Body bottom area", + AquariaLocations.locations_body_b) + self.final_boss_loby = self.__add_region("The Body, before final boss", None) + self.final_boss_tube = self.__add_region("The Body, final boss area turtle room", + AquariaLocations.locations_final_boss_tube) + self.final_boss = self.__add_region("The Body, final boss", + AquariaLocations.locations_final_boss) + self.final_boss_end = self.__add_region("The Body, final boss area", None) + + def __connect_one_way_regions(self, source_name: str, destination_name: str, + source_region: Region, + destination_region: Region, rule=None) -> None: + """ + Connect from the `source_region` to the `destination_region` + """ + entrance = Entrance(source_region.player, source_name + " to " + destination_name, source_region) + source_region.exits.append(entrance) + entrance.connect(destination_region) + if rule is not None: + set_rule(entrance, rule) + + def __connect_regions(self, source_name: str, destination_name: str, + source_region: Region, + destination_region: Region, rule=None) -> None: + """ + Connect the `source_region` and the `destination_region` (two-way) + """ + self.__connect_one_way_regions(source_name, destination_name, source_region, destination_region, rule) + self.__connect_one_way_regions(destination_name, source_name, destination_region, source_region, rule) + + def __connect_home_water_regions(self) -> None: + """ + Connect entrances of the different regions around `home_water` + """ + self.__connect_regions("Menu", "Verse Cave right area", + self.menu, self.verse_cave_r) + self.__connect_regions("Verse Cave left area", "Verse Cave right area", + self.verse_cave_l, self.verse_cave_r) + self.__connect_regions("Verse Cave", "Home Water", self.verse_cave_l, self.home_water) + self.__connect_regions("Home Water", "Haija's home", self.home_water, self.naija_home) + self.__connect_regions("Home Water", "Song Cave", self.home_water, self.song_cave) + self.__connect_regions("Home Water", "Home Water, nautilus nest", + self.home_water, self.home_water_nautilus, + lambda state: _has_energy_form(state, self.player) and _has_bind_song(state, self.player)) + self.__connect_regions("Home Water", "Home Water transturtle room", + self.home_water, self.home_water_transturtle) + self.__connect_regions("Home Water", "Energy Temple first area", + self.home_water, self.energy_temple_1, + lambda state: _has_bind_song(state, self.player)) + self.__connect_regions("Home Water", "Energy Temple_altar", + self.home_water, self.energy_temple_altar, + lambda state: _has_energy_form(state, self.player) and + _has_bind_song(state, self.player)) + self.__connect_regions("Energy Temple first area", "Energy Temple second area", + self.energy_temple_1, self.energy_temple_2, + lambda state: _has_energy_form(state, self.player)) + self.__connect_regions("Energy Temple first area", "Energy Temple idol room", + self.energy_temple_1, self.energy_temple_idol, + lambda state: _has_fish_form(state, self.player)) + self.__connect_regions("Energy Temple idol room", "Energy Temple boss area", + self.energy_temple_idol, self.energy_temple_boss, + lambda state: _has_energy_form(state, self.player)) + self.__connect_one_way_regions("Energy Temple first area", "Energy Temple boss area", + self.energy_temple_1, self.energy_temple_boss, + lambda state: _has_beast_form(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_one_way_regions("Energy Temple boss area", "Energy Temple first area", + self.energy_temple_boss, self.energy_temple_1, + lambda state: _has_energy_form(state, self.player)) + self.__connect_regions("Energy Temple second area", "Energy Temple third area", + self.energy_temple_2, self.energy_temple_3, + lambda state: _has_bind_song(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_regions("Energy Temple boss area", "Energy Temple blaster room", + self.energy_temple_boss, self.energy_temple_blaster_room, + lambda state: _has_nature_form(state, self.player) and + _has_bind_song(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_regions("Energy Temple first area", "Energy Temple blaster room", + self.energy_temple_1, self.energy_temple_blaster_room, + lambda state: _has_nature_form(state, self.player) and + _has_bind_song(state, self.player) and + _has_energy_form(state, self.player) and + _has_beast_form(state, self.player)) + self.__connect_regions("Home Water", "Open Water top left area", + self.home_water, self.openwater_tl) + + def __connect_open_water_regions(self) -> None: + """ + Connect entrances of the different regions around open water + """ + self.__connect_regions("Open Water top left area", "Open Water top right area", + self.openwater_tl, self.openwater_tr) + self.__connect_regions("Open Water top left area", "Open Water bottom left area", + self.openwater_tl, self.openwater_bl) + self.__connect_regions("Open Water top left area", "forest bottom right area", + self.openwater_tl, self.forest_br) + self.__connect_regions("Open Water top right area", "Open Water top right area, turtle room", + self.openwater_tr, self.openwater_tr_turtle, + lambda state: _has_beast_form(state, self.player)) + self.__connect_regions("Open Water top right area", "Open Water bottom right area", + self.openwater_tr, self.openwater_br) + self.__connect_regions("Open Water top right area", "Mithalas City", + self.openwater_tr, self.mithalas_city) + self.__connect_regions("Open Water top right area", "Veil bottom left area", + self.openwater_tr, self.veil_bl) + self.__connect_one_way_regions("Open Water top right area", "Veil bottom right", + self.openwater_tr, self.veil_br, + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Veil bottom right", "Open Water top right area", + self.veil_br, self.openwater_tr, + lambda state: _has_beast_form(state, self.player)) + self.__connect_regions("Open Water bottom left area", "Open Water bottom right area", + self.openwater_bl, self.openwater_br) + self.__connect_regions("Open Water bottom left area", "Skeleton path", + self.openwater_bl, self.skeleton_path) + self.__connect_regions("Abyss left area", "Open Water bottom left area", + self.abyss_l, self.openwater_bl) + self.__connect_regions("Skeleton path", "skeleton_path_sc", + self.skeleton_path, self.skeleton_path_sc, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Abyss right area", "Open Water bottom right area", + self.abyss_r, self.openwater_br) + self.__connect_one_way_regions("Open Water bottom right area", "Arnassi", + self.openwater_br, self.arnassi, + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Arnassi", "Open Water bottom right area", + self.arnassi, self.openwater_br) + self.__connect_regions("Arnassi", "Arnassi path", + self.arnassi, self.arnassi_path) + self.__connect_one_way_regions("Arnassi path", "Arnassi crab boss area", + self.arnassi_path, self.arnassi_crab_boss, + lambda state: _has_beast_form(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_one_way_regions("Arnassi crab boss area", "Arnassi path", + self.arnassi_crab_boss, self.arnassi_path) + + def __connect_mithalas_regions(self) -> None: + """ + Connect entrances of the different regions around Mithalas + """ + self.__connect_one_way_regions("Mithalas City", "Mithalas City top path", + self.mithalas_city, self.mithalas_city_top_path, + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Mithalas City_top_path", "Mithalas City", + self.mithalas_city_top_path, self.mithalas_city) + self.__connect_regions("Mithalas City", "Mithalas City home with fishpass", + self.mithalas_city, self.mithalas_city_fishpass, + lambda state: _has_fish_form(state, self.player)) + self.__connect_regions("Mithalas City", "Mithalas castle", + self.mithalas_city, self.cathedral_l, + lambda state: _has_fish_form(state, self.player)) + self.__connect_one_way_regions("Mithalas City top path", "Mithalas castle, flower tube", + self.mithalas_city_top_path, + self.cathedral_l_tube, + lambda state: _has_nature_form(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_one_way_regions("Mithalas castle, flower tube area", "Mithalas City top path", + self.cathedral_l_tube, + self.mithalas_city_top_path, + lambda state: _has_beast_form(state, self.player) and + _has_nature_form(state, self.player)) + self.__connect_one_way_regions("Mithalas castle flower tube area", "Mithalas castle, spirit crystals", + self.cathedral_l_tube, self.cathedral_l_sc, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_one_way_regions("Mithalas castle_flower tube area", "Mithalas castle", + self.cathedral_l_tube, self.cathedral_l, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Mithalas castle", "Mithalas castle, spirit crystals", + self.cathedral_l, self.cathedral_l_sc, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Mithalas castle", "Cathedral boss left area", + self.cathedral_l, self.cathedral_boss_l, + lambda state: _has_beast_form(state, self.player) and + _has_energy_form(state, self.player) and + _has_bind_song(state, self.player)) + self.__connect_regions("Mithalas castle", "Mithalas Cathedral underground", + self.cathedral_l, self.cathedral_underground, + lambda state: _has_beast_form(state, self.player) and + _has_bind_song(state, self.player)) + self.__connect_regions("Mithalas castle", "Mithalas Cathedral", + self.cathedral_l, self.cathedral_r, + lambda state: _has_bind_song(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_regions("Mithalas Cathedral", "Mithalas Cathedral underground", + self.cathedral_r, self.cathedral_underground, + lambda state: _has_energy_form(state, self.player)) + self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss left area", + self.cathedral_underground, self.cathedral_boss_r, + lambda state: _has_energy_form(state, self.player) and + _has_bind_song(state, self.player)) + self.__connect_one_way_regions("Cathedral boss left area", "Mithalas Cathedral underground", + self.cathedral_boss_r, self.cathedral_underground, + lambda state: _has_beast_form(state, self.player)) + self.__connect_regions("Cathedral boss right area", "Cathedral boss left area", + self.cathedral_boss_r, self.cathedral_boss_l, + lambda state: _has_bind_song(state, self.player) and + _has_energy_form(state, self.player)) + + def __connect_forest_regions(self) -> None: + """ + Connect entrances of the different regions around the Kelp Forest + """ + self.__connect_regions("Forest bottom right", "Veil bottom left area", + self.forest_br, self.veil_bl) + self.__connect_regions("Forest bottom right", "Forest bottom left area", + self.forest_br, self.forest_bl) + self.__connect_regions("Forest bottom right", "Forest top right area", + self.forest_br, self.forest_tr) + self.__connect_regions("Forest bottom left area", "Forest fish cave", + self.forest_bl, self.forest_fish_cave) + self.__connect_regions("Forest bottom left area", "Forest top left area", + self.forest_bl, self.forest_tl) + self.__connect_regions("Forest bottom left area", "Forest boss entrance", + self.forest_bl, self.forest_boss_entrance, + lambda state: _has_nature_form(state, self.player)) + self.__connect_regions("Forest top left area", "Forest top left area, fish pass", + self.forest_tl, self.forest_tl_fp, + lambda state: _has_nature_form(state, self.player) and + _has_bind_song(state, self.player) and + _has_energy_form(state, self.player) and + _has_fish_form(state, self.player)) + self.__connect_regions("Forest top left area", "Forest top right area", + self.forest_tl, self.forest_tr) + self.__connect_regions("Forest top left area", "Forest boss entrance", + self.forest_tl, self.forest_boss_entrance) + self.__connect_regions("Forest boss area", "Forest boss entrance", + self.forest_boss, self.forest_boss_entrance, + lambda state: _has_energy_form(state, self.player)) + self.__connect_regions("Forest top right area", "Forest top right area fish pass", + self.forest_tr, self.forest_tr_fp, + lambda state: _has_fish_form(state, self.player)) + self.__connect_regions("Forest top right area", "Forest sprite cave", + self.forest_tr, self.forest_sprite_cave) + self.__connect_regions("Forest sprite cave", "Forest sprite cave flower tube", + self.forest_sprite_cave, self.forest_sprite_cave_tube, + lambda state: _has_nature_form(state, self.player)) + self.__connect_regions("Forest top right area", "Mermog cave", + self.forest_tr_fp, self.mermog_cave) + self.__connect_regions("Fermog cave", "Fermog boss", + self.mermog_cave, self.mermog_boss, + lambda state: _has_beast_form(state, self.player) and + _has_energy_form(state, self.player)) + + def __connect_veil_regions(self) -> None: + """ + Connect entrances of the different regions around The Veil + """ + self.__connect_regions("Veil bottom left area", "Veil bottom left area, fish pass", + self.veil_bl, self.veil_bl_fp, + lambda state: _has_fish_form(state, self.player) and + _has_bind_song(state, self.player) and + _has_damaging_item(state, self.player)) + self.__connect_regions("Veil bottom left area", "Veil bottom area spirit crystals path", + self.veil_bl, self.veil_b_sc, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Veil bottom area spirit crystals path", "Veil bottom right", + self.veil_b_sc, self.veil_br, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Veil bottom right", "Veil top left area", + self.veil_br, self.veil_tl, + lambda state: _has_beast_form(state, self.player)) + self.__connect_regions("Veil top left area", "Veil_top left area, fish pass", + self.veil_tl, self.veil_tl_fp, + lambda state: _has_fish_form(state, self.player)) + self.__connect_regions("Veil top left area", "Veil right of sun temple", + self.veil_tl, self.veil_tr_r) + self.__connect_regions("Veil top left area", "Turtle cave", + self.veil_tl, self.turtle_cave) + self.__connect_regions("Turtle cave", "Turtle cave Bubble Cliff", + self.turtle_cave, self.turtle_cave_bubble, + lambda state: _has_beast_form(state, self.player)) + self.__connect_regions("Veil right of sun temple", "Sun Temple right area", + self.veil_tr_r, self.sun_temple_r) + self.__connect_regions("Sun Temple right area", "Sun Temple left area", + self.sun_temple_r, self.sun_temple_l, + lambda state: _has_bind_song(state, self.player)) + self.__connect_regions("Sun Temple left area", "Veil left of sun temple", + self.sun_temple_l, self.veil_tr_l) + self.__connect_regions("Sun Temple left area", "Sun Temple before boss area", + self.sun_temple_l, self.sun_temple_boss_path) + self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area", + self.sun_temple_boss_path, self.sun_temple_boss, + lambda state: _has_energy_form(state, self.player)) + self.__connect_one_way_regions("Sun Temple boss area", "Veil left of sun temple", + self.sun_temple_boss, self.veil_tr_l) + self.__connect_regions("Veil left of sun temple", "Octo cave top path", + self.veil_tr_l, self.octo_cave_t, + lambda state: _has_fish_form(state, self.player) and + _has_sun_form(state, self.player) and + _has_beast_form(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_regions("Veil left of sun temple", "Octo cave bottom path", + self.veil_tr_l, self.octo_cave_b, + lambda state: _has_fish_form(state, self.player)) + + def __connect_abyss_regions(self) -> None: + """ + Connect entrances of the different regions around The Abyss + """ + self.__connect_regions("Abyss left area", "Abyss bottom of left area", + self.abyss_l, self.abyss_lb, + lambda state: _has_nature_form(state, self.player)) + self.__connect_regions("Abyss left bottom area", "Sunken City right area", + self.abyss_lb, self.sunken_city_r, + lambda state: _has_li(state, self.player)) + self.__connect_one_way_regions("Abyss left bottom area", "Body center area", + self.abyss_lb, self.body_c, + lambda state: _has_tongue_cleared(state, self.player)) + self.__connect_one_way_regions("Body center area", "Abyss left bottom area", + self.body_c, self.abyss_lb) + self.__connect_regions("Abyss left area", "King jellyfish cave", + self.abyss_l, self.king_jellyfish_cave, + lambda state: _has_energy_form(state, self.player) and + _has_beast_form(state, self.player)) + self.__connect_regions("Abyss left area", "Abyss right area", + self.abyss_l, self.abyss_r) + self.__connect_regions("Abyss right area", "Inside the whale", + self.abyss_r, self.whale, + lambda state: _has_spirit_form(state, self.player) and + _has_sun_form(state, self.player)) + self.__connect_regions("Abyss right area", "First secret area", + self.abyss_r, self.first_secret, + lambda state: _has_spirit_form(state, self.player) and + _has_sun_form(state, self.player) and + _has_bind_song(state, self.player) and + _has_energy_form(state, self.player)) + self.__connect_regions("Abyss right area", "Ice Cave", + self.abyss_r, self.ice_cave, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Abyss right area", "Bubble Cave", + self.ice_cave, self.bubble_cave, + lambda state: _has_beast_form(state, self.player)) + self.__connect_regions("Bubble Cave boss area", "Bubble Cave", + self.bubble_cave, self.bubble_cave_boss, + lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) + ) + + def __connect_sunken_city_regions(self) -> None: + """ + Connect entrances of the different regions around The Sunken City + """ + self.__connect_regions("Sunken City right area", "Sunken City left area", + self.sunken_city_r, self.sunken_city_l) + self.__connect_regions("Sunken City left area", "Sunken City bedroom", + self.sunken_city_l, self.sunken_city_l_bedroom, + lambda state: _has_spirit_form(state, self.player)) + self.__connect_regions("Sunken City left area", "Sunken City boss area", + self.sunken_city_l, self.sunken_city_boss, + lambda state: _has_beast_form(state, self.player) and + _has_sun_form(state, self.player) and + _has_energy_form(state, self.player) and + _has_bind_song(state, self.player)) + + def __connect_body_regions(self) -> None: + """ + Connect entrances of the different regions around The Body + """ + self.__connect_regions("Body center area", "Body left area", + self.body_c, self.body_l) + self.__connect_regions("Body center area", "Body right area top path", + self.body_c, self.body_rt) + self.__connect_regions("Body center area", "Body right area bottom path", + self.body_c, self.body_rb) + self.__connect_regions("Body center area", "Body bottom area", + self.body_c, self.body_b, + lambda state: _has_dual_form(state, self.player)) + self.__connect_regions("Body bottom area", "Final Boss area", + self.body_b, self.final_boss_loby, + lambda state: _has_dual_form(state, self.player)) + self.__connect_regions("Before Final Boss", "Final Boss tube", + self.final_boss_loby, self.final_boss_tube, + lambda state: _has_nature_form(state, self.player)) + self.__connect_one_way_regions("Before Final Boss", "Final Boss", + self.final_boss_loby, self.final_boss, + lambda state: _has_energy_form(state, self.player) and + _has_dual_form(state, self.player) and + _has_sun_form(state, self.player) and + _has_bind_song(state, self.player)) + self.__connect_one_way_regions("final boss third form area", "final boss end", + self.final_boss, self.final_boss_end) + + def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region, region_target: Region, + rule=None) -> None: + """Connect a single transturtle to another one""" + if item_source != item_target: + if rule is None: + self.__connect_one_way_regions(item_source, item_target, region_source, region_target, + lambda state: state.has(item_target, self.player)) + else: + self.__connect_one_way_regions(item_source, item_target, region_source, region_target, rule) + + def __connect_arnassi_path_transturtle(self, item_source: str, item_target: str, region_source: Region, + region_target: Region) -> None: + """Connect the Arnassi Ruins transturtle to another one""" + self.__connect_one_way_regions(item_source, item_target, region_source, region_target, + lambda state: state.has(item_target, self.player) and + _has_fish_form(state, self.player)) + + def _connect_transturtle_to_other(self, item: str, region: Region) -> None: + """Connect a single transturtle to all others""" + self.__connect_transturtle(item, "Transturtle Veil top left", region, self.veil_tl) + self.__connect_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l) + self.__connect_transturtle(item, "Transturtle Open Water top right", region, self.openwater_tr_turtle) + self.__connect_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl) + self.__connect_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle) + self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r) + self.__connect_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube) + self.__connect_transturtle(item, "Transturtle Simon Says", region, self.simon) + self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_path, + lambda state: state.has("Transturtle Arnassi Ruins", self.player) and + _has_fish_form(state, self.player)) + + def _connect_arnassi_path_transturtle_to_other(self, item: str, region: Region) -> None: + """Connect the Arnassi Ruins transturtle to all others""" + self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top left", region, self.veil_tl) + self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l) + self.__connect_arnassi_path_transturtle(item, "Transturtle Open Water top right", region, + self.openwater_tr_turtle) + self.__connect_arnassi_path_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl) + self.__connect_arnassi_path_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle) + self.__connect_arnassi_path_transturtle(item, "Transturtle Abyss right", region, self.abyss_r) + self.__connect_arnassi_path_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube) + self.__connect_arnassi_path_transturtle(item, "Transturtle Simon Says", region, self.simon) + + def __connect_transturtles(self) -> None: + """Connect every transturtle with others""" + self._connect_transturtle_to_other("Transturtle Veil top left", self.veil_tl) + self._connect_transturtle_to_other("Transturtle Veil top right", self.veil_tr_l) + self._connect_transturtle_to_other("Transturtle Open Water top right", self.openwater_tr_turtle) + self._connect_transturtle_to_other("Transturtle Forest bottom left", self.forest_bl) + self._connect_transturtle_to_other("Transturtle Home Water", self.home_water_transturtle) + self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r) + self._connect_transturtle_to_other("Transturtle Final Boss", self.final_boss_tube) + self._connect_transturtle_to_other("Transturtle Simon Says", self.simon) + self._connect_arnassi_path_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_path) + + def connect_regions(self) -> None: + """ + Connect every region (entrances and exits) + """ + self.__connect_home_water_regions() + self.__connect_open_water_regions() + self.__connect_mithalas_regions() + self.__connect_forest_regions() + self.__connect_veil_regions() + self.__connect_abyss_regions() + self.__connect_sunken_city_regions() + self.__connect_body_regions() + self.__connect_transturtles() + + def __add_event_location(self, region: Region, name: str, event_name: str) -> None: + """ + Add an event to the `region` with the name `name` and the item + `event_name` + """ + location: AquariaLocation = AquariaLocation( + self.player, name, None, region + ) + region.locations.append(location) + location.place_locked_item(AquariaItem(event_name, + ItemClassification.progression, + None, + self.player)) + + def __add_event_big_bosses(self) -> None: + """ + Add every bit bosses (other than the creator) events to the `world` + """ + self.__add_event_location(self.energy_temple_boss, + "Beating Fallen God", + "Fallen God beated") + self.__add_event_location(self.cathedral_boss_r, + "Beating Mithalan God", + "Mithalan God beated") + self.__add_event_location(self.forest_boss, + "Beating Drunian God", + "Drunian God beated") + self.__add_event_location(self.sun_temple_boss, + "Beating Sun God", + "Sun God beated") + self.__add_event_location(self.sunken_city_boss, + "Beating the Golem", + "The Golem beated") + + def __add_event_mini_bosses(self) -> None: + """ + Add every mini bosses (excluding Energy Statue and Simon Says) + events to the `world` + """ + self.__add_event_location(self.home_water_nautilus, + "Beating Nautilus Prime", + "Nautilus Prime beated") + self.__add_event_location(self.energy_temple_blaster_room, + "Beating Blaster Peg Prime", + "Blaster Peg Prime beated") + self.__add_event_location(self.mermog_boss, + "Beating Mergog", + "Mergog beated") + self.__add_event_location(self.cathedral_l_tube, + "Beating Mithalan priests", + "Mithalan priests beated") + self.__add_event_location(self.octo_cave_t, + "Beating Octopus Prime", + "Octopus Prime beated") + self.__add_event_location(self.arnassi_crab_boss, + "Beating Crabbius Maximus", + "Crabbius Maximus beated") + self.__add_event_location(self.bubble_cave_boss, + "Beating Mantis Shrimp Prime", + "Mantis Shrimp Prime beated") + self.__add_event_location(self.king_jellyfish_cave, + "Beating King Jellyfish God Prime", + "King Jellyfish God Prime beated") + + def __add_event_secrets(self) -> None: + """ + Add secrets events to the `world` + """ + self.__add_event_location(self.first_secret, # Doit ajouter une région pour le "first secret" + "First secret", + "First secret obtained") + self.__add_event_location(self.mithalas_city, + "Second secret", + "Second secret obtained") + self.__add_event_location(self.sun_temple_l, + "Third secret", + "Third secret obtained") + + def add_event_locations(self) -> None: + """ + Add every event (locations and items) to the `world` + """ + self.__add_event_mini_bosses() + self.__add_event_big_bosses() + self.__add_event_secrets() + self.__add_event_location(self.sunken_city_boss, + "Sunken City cleared", + "Body tongue cleared") + self.__add_event_location(self.sun_temple_r, + "Sun Crystal", + "Has sun crystal") + self.__add_event_location(self.final_boss_end, "Objective complete", + "Victory") + + def __adjusting_urns_rules(self) -> None: + """Since Urns need to be broken, add a damaging item to rules""" + add_rule(self.multiworld.get_location("Open Water top right area, first urn in the Mithalas exit", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Open Water top right area, third urn in the Mithalas exit", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, first urn in one of the homes", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, second urn in one of the homes", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, first urn in the city reserve", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, second urn in the city reserve", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, third urn in the city reserve", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, urn in the Castle flower tube entrance", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bedroom", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City Castle, first urn of the single lamp path", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City Castle, second urn of the single lamp path", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bottom room", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City Castle, first urn on the entrance path", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City Castle, second urn on the entrance path", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, urn inside a home fish pass", self.player), + lambda state: _has_damaging_item(state, self.player)) + + def __adjusting_crates_rules(self) -> None: + """Since Crate need to be broken, add a damaging item to rules""" + add_rule(self.multiworld.get_location("Sunken City right area, crate close to the save crystal", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Sunken City right area, crate in the left bottom room", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Sunken City left area, crate in the little pipe room", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Sunken City left area, crate close to the save crystal", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Sunken City left area, crate before the bedroom", self.player), + lambda state: _has_damaging_item(state, self.player)) + + def __adjusting_soup_rules(self) -> None: + """ + Modify rules for location that need soup + """ + add_rule(self.multiworld.get_location("Turtle cave, Urchin Costume", self.player), + lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player), + lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player), + lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", self.player), + lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + + def __adjusting_under_rock_location(self) -> None: + """ + Modify rules implying bind song needed for bulb under rocks + """ + add_rule(self.multiworld.get_location("Home Water, bulb under the rock in the left path from the Verse Cave", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Verse Cave left area, bulb under the rock at the end of the path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Naija's Home, bulb under the rock at the right of the main path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Song Cave, bulb under the rock in the path to the singing statues", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Song Cave, bulb under the rock close to the song door", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Energy Temple second area, bulb under the rock", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the right path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the left path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Kelp Forest top right area, bulb under the rock in the right path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Abyss right area, bulb in the middle path", + self.player), lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path", + self.player), lambda state: _has_bind_song(state, self.player)) + + def __adjusting_light_in_dark_place_rules(self) -> None: + add_rule(self.multiworld.get_location("Kelp Forest top right area, Black Pearl", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_location("Kelp Forest bottom right area, Odd Container", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Veil top left to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Open Water top right to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Veil top right to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Forest bottom left to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Home Water to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Final Boss to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Simon Says to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Transturtle Arnassi Ruins to Transturtle Abyss right", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Body center area to Abyss left bottom area", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Veil left of sun temple to Octo cave top path", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Open Water bottom right area to Abyss right area", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Open Water bottom left area to Abyss left area", self.player), + lambda state: _has_light(state, self.player)) + add_rule(self.multiworld.get_entrance("Sun Temple left area to Sun Temple right area", self.player), + lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player)) + add_rule(self.multiworld.get_entrance("Sun Temple right area to Sun Temple left area", self.player), + lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player)) + add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun Temple left area", self.player), + lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player)) + + def __adjusting_manual_rules(self) -> None: + add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player), + lambda state: _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player), + lambda state: _has_fish_form(state, self.player)) + add_rule(self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", self.player), + lambda state: _has_spirit_form(state, self.player)) + add_rule(self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player), + lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Turtle cave, Turtle Egg", self.player), + lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Abyss left area, bulb in the bottom fish pass", self.player), + lambda state: _has_fish_form(state, self.player)) + add_rule(self.multiworld.get_location("Song Cave, Anemone Seed", self.player), + lambda state: _has_nature_form(state, self.player)) + add_rule(self.multiworld.get_location("Song Cave, Verse Egg", self.player), + lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Verse Cave right area, Big Seed", self.player), + lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Arnassi Ruins, Song Plant Spore", self.player), + lambda state: _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("Energy Temple first area, bulb in the bottom room blocked by a rock", + self.player), lambda state: _has_energy_form(state, self.player)) + add_rule(self.multiworld.get_location("Home Water, bulb in the bottom left room", self.player), + lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Home Water, bulb in the path below Nautilus Prime", self.player), + lambda state: _has_bind_song(state, self.player)) + add_rule(self.multiworld.get_location("Naija's Home, bulb after the energy door", self.player), + lambda state: _has_energy_form(state, self.player)) + add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", self.player), + lambda state: _has_spirit_form(state, self.player) and + _has_sun_form(state, self.player)) + add_rule(self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", self.player), + lambda state: _has_fish_form(state, self.player) and + _has_spirit_form(state, self.player)) + + def __no_progression_hard_or_hidden_location(self) -> None: + self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Mithalas boss area, beating Mithalan God", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Kelp Forest boss area, beating Drunian God", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Sun Temple boss area, beating Sun God", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Sunken City, bulb on top of the boss area", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Home Water, Nautilus Egg", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Energy Temple blaster room, Blaster Egg", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Mithalas City Castle, beating the Priests", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Mermog cave, Piranha Egg", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Octopus Cave, Dumbo Egg", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("King Jellyfish Cave, bulb in the right path from King Jelly", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("King Jellyfish Cave, Jellyfish Costume", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Final Boss area, bulb in the boss third form room", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Sun Worm path, first cliff bulb", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Sun Worm path, second cliff bulb", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Bubble Cave, bulb in the left cave wall", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Bubble Cave, Verse Egg", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Kelp Forest bottom left area, bulb close to the spirit crystals", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Sun Temple, Sun Key", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("The Body bottom area, Mutant Costume", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Sun Temple, bulb in the hidden room of the right part", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", + self.player).item_rule =\ + lambda item: item.classification != ItemClassification.progression + + def adjusting_rules(self, options: AquariaOptions) -> None: + """ + Modify rules for single location or optional rules + """ + self.__adjusting_urns_rules() + self.__adjusting_crates_rules() + self.__adjusting_soup_rules() + self.__adjusting_manual_rules() + if options.light_needed_to_get_to_dark_places: + self.__adjusting_light_in_dark_place_rules() + if options.bind_song_needed_to_get_under_rock_bulb: + self.__adjusting_under_rock_location() + + if options.mini_bosses_to_beat.value > 0: + add_rule(self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player), + lambda state: _has_mini_bosses(state, self.player)) + if options.big_bosses_to_beat.value > 0: + add_rule(self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player), + lambda state: _has_big_bosses(state, self.player)) + if options.objective.value == 1: + add_rule(self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player), + lambda state: _has_secrets(state, self.player)) + if options.unconfine_home_water.value in [0, 1]: + add_rule(self.multiworld.get_entrance("Home Water to Home Water transturtle room", self.player), + lambda state: _has_bind_song(state, self.player)) + if options.unconfine_home_water.value in [0, 2]: + add_rule(self.multiworld.get_entrance("Home Water to Open Water top left area", self.player), + lambda state: _has_bind_song(state, self.player) and _has_energy_form(state, self.player)) + if options.early_energy_form: + self.multiworld.early_items[self.player]["Energy form"] = 1 + + if options.no_progression_hard_or_hidden_locations: + self.__no_progression_hard_or_hidden_location() + + def __add_home_water_regions_to_world(self) -> None: + """ + Add every region around home water to the `world` + """ + self.multiworld.regions.append(self.menu) + self.multiworld.regions.append(self.verse_cave_r) + self.multiworld.regions.append(self.verse_cave_l) + self.multiworld.regions.append(self.home_water) + self.multiworld.regions.append(self.home_water_nautilus) + self.multiworld.regions.append(self.home_water_transturtle) + self.multiworld.regions.append(self.naija_home) + self.multiworld.regions.append(self.song_cave) + self.multiworld.regions.append(self.energy_temple_1) + self.multiworld.regions.append(self.energy_temple_2) + self.multiworld.regions.append(self.energy_temple_3) + self.multiworld.regions.append(self.energy_temple_boss) + self.multiworld.regions.append(self.energy_temple_blaster_room) + self.multiworld.regions.append(self.energy_temple_altar) + + def __add_open_water_regions_to_world(self) -> None: + """ + Add every region around open water to the `world` + """ + self.multiworld.regions.append(self.openwater_tl) + self.multiworld.regions.append(self.openwater_tr) + self.multiworld.regions.append(self.openwater_tr_turtle) + self.multiworld.regions.append(self.openwater_bl) + self.multiworld.regions.append(self.openwater_br) + self.multiworld.regions.append(self.skeleton_path) + self.multiworld.regions.append(self.skeleton_path_sc) + self.multiworld.regions.append(self.arnassi) + self.multiworld.regions.append(self.arnassi_path) + self.multiworld.regions.append(self.arnassi_crab_boss) + self.multiworld.regions.append(self.simon) + + def __add_mithalas_regions_to_world(self) -> None: + """ + Add every region around Mithalas to the `world` + """ + self.multiworld.regions.append(self.mithalas_city) + self.multiworld.regions.append(self.mithalas_city_top_path) + self.multiworld.regions.append(self.mithalas_city_fishpass) + self.multiworld.regions.append(self.cathedral_l) + self.multiworld.regions.append(self.cathedral_l_tube) + self.multiworld.regions.append(self.cathedral_l_sc) + self.multiworld.regions.append(self.cathedral_r) + self.multiworld.regions.append(self.cathedral_underground) + self.multiworld.regions.append(self.cathedral_boss_l) + self.multiworld.regions.append(self.cathedral_boss_r) + + def __add_forest_regions_to_world(self) -> None: + """ + Add every region around the kelp forest to the `world` + """ + self.multiworld.regions.append(self.forest_tl) + self.multiworld.regions.append(self.forest_tl_fp) + self.multiworld.regions.append(self.forest_tr) + self.multiworld.regions.append(self.forest_tr_fp) + self.multiworld.regions.append(self.forest_bl) + self.multiworld.regions.append(self.forest_br) + self.multiworld.regions.append(self.forest_boss) + self.multiworld.regions.append(self.forest_boss_entrance) + self.multiworld.regions.append(self.forest_sprite_cave) + self.multiworld.regions.append(self.forest_sprite_cave_tube) + self.multiworld.regions.append(self.mermog_cave) + self.multiworld.regions.append(self.mermog_boss) + self.multiworld.regions.append(self.forest_fish_cave) + + def __add_veil_regions_to_world(self) -> None: + """ + Add every region around the Veil to the `world` + """ + self.multiworld.regions.append(self.veil_tl) + self.multiworld.regions.append(self.veil_tl_fp) + self.multiworld.regions.append(self.veil_tr_l) + self.multiworld.regions.append(self.veil_tr_r) + self.multiworld.regions.append(self.veil_bl) + self.multiworld.regions.append(self.veil_b_sc) + self.multiworld.regions.append(self.veil_bl_fp) + self.multiworld.regions.append(self.veil_br) + self.multiworld.regions.append(self.octo_cave_t) + self.multiworld.regions.append(self.octo_cave_b) + self.multiworld.regions.append(self.turtle_cave) + self.multiworld.regions.append(self.turtle_cave_bubble) + self.multiworld.regions.append(self.sun_temple_l) + self.multiworld.regions.append(self.sun_temple_r) + self.multiworld.regions.append(self.sun_temple_boss_path) + self.multiworld.regions.append(self.sun_temple_boss) + + def __add_abyss_regions_to_world(self) -> None: + """ + Add every region around the Abyss to the `world` + """ + self.multiworld.regions.append(self.abyss_l) + self.multiworld.regions.append(self.abyss_lb) + self.multiworld.regions.append(self.abyss_r) + self.multiworld.regions.append(self.ice_cave) + self.multiworld.regions.append(self.bubble_cave) + self.multiworld.regions.append(self.bubble_cave_boss) + self.multiworld.regions.append(self.king_jellyfish_cave) + self.multiworld.regions.append(self.whale) + self.multiworld.regions.append(self.sunken_city_l) + self.multiworld.regions.append(self.sunken_city_r) + self.multiworld.regions.append(self.sunken_city_boss) + self.multiworld.regions.append(self.sunken_city_l_bedroom) + + def __add_body_regions_to_world(self) -> None: + """ + Add every region around the Body to the `world` + """ + self.multiworld.regions.append(self.body_c) + self.multiworld.regions.append(self.body_l) + self.multiworld.regions.append(self.body_rt) + self.multiworld.regions.append(self.body_rb) + self.multiworld.regions.append(self.body_b) + self.multiworld.regions.append(self.final_boss_loby) + self.multiworld.regions.append(self.final_boss_tube) + self.multiworld.regions.append(self.final_boss) + self.multiworld.regions.append(self.final_boss_end) + + def add_regions_to_world(self) -> None: + """ + Add every region to the `world` + """ + self.__add_home_water_regions_to_world() + self.__add_open_water_regions_to_world() + self.__add_mithalas_regions_to_world() + self.__add_forest_regions_to_world() + self.__add_veil_regions_to_world() + self.__add_abyss_regions_to_world() + self.__add_body_regions_to_world() + + def __init__(self, multiworld: MultiWorld, player: int): + """ + Initialisation of the regions + """ + self.multiworld = multiworld + self.player = player + self.__create_home_water_area() + self.__create_energy_temple() + self.__create_openwater() + self.__create_mithalas() + self.__create_forest() + self.__create_veil() + self.__create_sun_temple() + self.__create_abyss() + self.__create_sunken_city() + self.__create_body() diff --git a/worlds/aquaria/__init__.py b/worlds/aquaria/__init__.py new file mode 100644 index 000000000000..1fb04036d81b --- /dev/null +++ b/worlds/aquaria/__init__.py @@ -0,0 +1,215 @@ +""" +Author: Louis M +Date: Fri, 15 Mar 2024 18:41:40 +0000 +Description: Main module for Aquaria game multiworld randomizer +""" + +from typing import List, Dict, ClassVar, Any +from worlds.AutoWorld import World, WebWorld +from BaseClasses import Tutorial, MultiWorld, ItemClassification +from .Items import item_table, AquariaItem, ItemType, ItemGroup +from .Locations import location_table +from .Options import AquariaOptions +from .Regions import AquariaRegions + + +class AquariaWeb(WebWorld): + """ + Class used to generate the Aquaria Game Web pages (setup, tutorial, etc.) + """ + theme = "ocean" + + bug_report_page = "https://github.com/tioui/Aquaria_Randomizer/issues" + + setup = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up Aquaria for MultiWorld.", + "English", + "setup_en.md", + "setup/en", + ["Tioui"] + ) + + setup_fr = Tutorial( + "Guide de configuration Multimonde", + "Un guide pour configurer Aquaria MultiWorld", + "Français", + "setup_fr.md", + "setup/fr", + ["Tioui"] + ) + + tutorials = [setup, setup_fr] + + +class AquariaWorld(World): + """ + Aquaria is a side-scrolling action-adventure game. It follows Naija, an + aquatic humanoid woman, as she explores the underwater world of Aquaria. + Along her journey, she learns about the history of the world she inhabits + as well as her own past. The gameplay focuses on a combination of swimming, + singing, and combat, through which Naija can interact with the world. Her + songs can move items, affect plants and animals, and change her physical + appearance into other forms that have different abilities, like firing + projectiles at hostile creatures, or passing through barriers inaccessible + to her in her natural form. + From: https://en.wikipedia.org/wiki/Aquaria_(video_game) + """ + + game: str = "Aquaria" + "The name of the game" + + topology_present = True + "show path to required location checks in spoiler" + + web: WebWorld = AquariaWeb() + "The web page generation informations" + + item_name_to_id: ClassVar[Dict[str, int]] =\ + {name: data.id for name, data in item_table.items()} + "The name and associated ID of each item of the world" + + item_name_groups = { + "Damage": {"Energy form", "Nature form", "Beast form", + "Li and Li song", "Baby Nautilus", "Baby Piranha", + "Baby Blaster"}, + "Light": {"Sun form", "Baby Dumbo"} + } + """Grouping item make it easier to find them""" + + location_name_to_id = location_table + "The name and associated ID of each location of the world" + + base_id = 698000 + "The starting ID of the items and locations of the world" + + ingredients_substitution: List[int] + "Used to randomize ingredient drop" + + options_dataclass = AquariaOptions + "Used to manage world options" + + options: AquariaOptions + "Every options of the world" + + regions: AquariaRegions + "Used to manage Regions" + + exclude: List[str] + + def __init__(self, multiworld: MultiWorld, player: int): + """Initialisation of the Aquaria World""" + super(AquariaWorld, self).__init__(multiworld, player) + self.regions = AquariaRegions(multiworld, player) + self.ingredients_substitution = [] + self.exclude = [] + + def create_regions(self) -> None: + """ + Create every Region in `regions` + """ + self.regions.add_regions_to_world() + self.regions.connect_regions() + self.regions.add_event_locations() + + def create_item(self, name: str) -> AquariaItem: + """ + Create an AquariaItem using 'name' as item name. + """ + result: AquariaItem + try: + data = item_table[name] + classification: ItemClassification = ItemClassification.useful + if data.type == ItemType.JUNK: + classification = ItemClassification.filler + elif data.type == ItemType.PROGRESSION: + classification = ItemClassification.progression + result = AquariaItem(name, classification, data.id, self.player) + except BaseException: + raise Exception('The item ' + name + ' is not valid.') + + return result + + def __pre_fill_item(self, item_name: str, location_name: str, precollected) -> None: + """Pre-assign an item to a location""" + if item_name not in precollected: + self.exclude.append(item_name) + data = item_table[item_name] + item = AquariaItem(item_name, ItemClassification.useful, data.id, self.player) + self.multiworld.get_location(location_name, self.player).place_locked_item(item) + + def get_filler_item_name(self): + """Getting a random ingredient item as filler""" + ingredients = [] + for name, data in item_table.items(): + if data.group == ItemGroup.INGREDIENT: + ingredients.append(name) + filler_item_name = self.random.choice(ingredients) + return filler_item_name + + def create_items(self) -> None: + """Create every item in the world""" + precollected = [item.name for item in self.multiworld.precollected_items[self.player]] + if self.options.turtle_randomizer.value > 0: + if self.options.turtle_randomizer.value == 2: + self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected) + else: + self.__pre_fill_item("Transturtle Veil top left", "The Veil top left area, Transturtle", precollected) + self.__pre_fill_item("Transturtle Veil top right", "The Veil top right area, Transturtle", precollected) + self.__pre_fill_item("Transturtle Open Water top right", "Open Water top right area, Transturtle", + precollected) + self.__pre_fill_item("Transturtle Forest bottom left", "Kelp Forest bottom left area, Transturtle", + precollected) + self.__pre_fill_item("Transturtle Home Water", "Home Water, Transturtle", precollected) + self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected) + self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected) + # The last two are inverted because in the original game, they are special turtle that communicate directly + self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected) + self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected) + for name, data in item_table.items(): + if name not in self.exclude: + for i in range(data.count): + item = self.create_item(name) + self.multiworld.itempool.append(item) + + def set_rules(self) -> None: + """ + Launched when the Multiworld generator is ready to generate rules + """ + + self.regions.adjusting_rules(self.options) + self.multiworld.completion_condition[self.player] = lambda \ + state: state.has("Victory", self.player) + + def generate_basic(self) -> None: + """ + Player-specific randomization that does not affect logic. + Used to fill then `ingredients_substitution` list + """ + simple_ingredients_substitution = [i for i in range(27)] + if self.options.ingredient_randomizer.value > 0: + if self.options.ingredient_randomizer.value == 1: + simple_ingredients_substitution.pop(-1) + simple_ingredients_substitution.pop(-1) + simple_ingredients_substitution.pop(-1) + self.random.shuffle(simple_ingredients_substitution) + if self.options.ingredient_randomizer.value == 1: + simple_ingredients_substitution.extend([24, 25, 26]) + dishes_substitution = [i for i in range(27, 76)] + if self.options.dish_randomizer: + self.random.shuffle(dishes_substitution) + self.ingredients_substitution.clear() + self.ingredients_substitution.extend(simple_ingredients_substitution) + self.ingredients_substitution.extend(dishes_substitution) + + def fill_slot_data(self) -> Dict[str, Any]: + return {"ingredientReplacement": self.ingredients_substitution, + "aquarian_translate": bool(self.options.aquarian_translation.value), + "blind_goal": bool(self.options.blind_goal.value), + "secret_needed": self.options.objective.value > 0, + "minibosses_to_kill": self.options.mini_bosses_to_beat.value, + "bigbosses_to_kill": self.options.big_bosses_to_beat.value, + "skip_first_vision": bool(self.options.skip_first_vision.value), + "unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3], + "unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3], + } diff --git a/worlds/aquaria/docs/en_Aquaria.md b/worlds/aquaria/docs/en_Aquaria.md new file mode 100644 index 000000000000..c3e5f54dd66a --- /dev/null +++ b/worlds/aquaria/docs/en_Aquaria.md @@ -0,0 +1,64 @@ +# Aquaria + +## Game page in other languages: +* [Français](/games/Aquaria/info/fr) + +## Where is the options page? + +The player options page for this game contains all the options you need to configure and export a config file. Player +options page link: [Aquaria Player Options Page](../player-options). + +## What does randomization do to this game? +The locations in the randomizer are: + +- All sing bulbs +- All Mithalas Urns +- All Sunken City crates +- Collectible treasure locations (including pet eggs and costumes) +- Beating Simon Says +- Li cave +- Every Transportation Turtle (also called transturtle) +- Locations where you get songs: + * Erulian spirit crystal + * Energy status mini-boss + * Beating Mithalan God boss + * Fish Cave puzzle + * Beating Drunian God boss + * Beating Sun God boss + * Breaking Li cage in the body + +Note that, unlike the vanilla game, when opening sing bulbs, Mithalas urns and Sunken City crates, +nothing will come out of them. The moment those bulbs, urns and crates are opened, the location is considered checked. + +The items in the randomizer are: +- Dishes (used to learn recipes)* +- Some ingredients +- The Wok (third plate used to cook 3-ingredient recipes everywhere) +- All collectible treasure (including pet eggs and costumes) +- Li and Li's song +- All songs (other than Li's song since it is learned when Li is obtained) +- Transportation to transturtles + +Also, there is the option to randomize every ingredient drops (from fishes, monsters +or plants). + +* Note that, unlike in the vanilla game, the recipes for dishes (other than the Sea Loaf) +cannot be cooked (or learned) before being obtained as randomized items. Also, enemies and plants +that drop dishes that have not been learned before will drop ingredients of this dish instead. + +## What is the goal of the game? +The goal of the Aquaria game is to beat the creator. You can also add other goals like getting +secret memories, beating a number of mini-bosses and beating a number of bosses. + +## Which items can be in another player's world? +Any items specified above can be in another player's world. + +## What does another world's item look like in Aquaria? +No visuals are shown when finding locations other than collectible treasure. +For those treasures, the visual of the treasure is visually unchanged. +After collecting a location check, a message will be shown to inform the player +what has been collected and who will receive it. + +## When the player receives an item, what happens? +When you receive an item, a message will pop up to inform you where you received +the item from and which one it was. diff --git a/worlds/aquaria/docs/fr_Aquaria.md b/worlds/aquaria/docs/fr_Aquaria.md new file mode 100644 index 000000000000..4395b6dff95e --- /dev/null +++ b/worlds/aquaria/docs/fr_Aquaria.md @@ -0,0 +1,65 @@ +# Aquaria + +## Où se trouve la page des options ? + +La [page des options du joueur pour ce jeu](../player-options) contient tous +les options dont vous avez besoin pour configurer et exporter le fichier. + +## Quel est l'effet de la randomisation sur ce jeu ? + +Les localisations du "Ransomizer" sont: + +- tous les bulbes musicaux; +- toutes les urnes de Mithalas; +- toutes les caisses de la cité engloutie; +- les localisations des trésors de collections (incluant les oeufs d'animaux de compagnie et les costumes); +- Battre Simom dit; +- La caverne de Li; +- Les tortues de transportation (transturtle); +- Localisation ou on obtient normalement les musiques, + * cristal de l'esprit Erulien, + * le mini-boss de la statue de l'énergie, + * battre le dieu de Mithalas, + * résoudre l'énigme de la caverne des poissons, + * battre le dieu Drunien, + * battre le dieu du soleil, + * détruire la cage de Li dans le corps, + +À noter que, contrairement au jeu original, lors de l'ouverture d'un bulbe musical, d'une urne de Mithalas ou +d'une caisse de la cité engloutie, aucun objet n'en sortira. La localisation représentée par l'objet ouvert est reçue +dès l'ouverture. + +Les objets pouvant être obtenus sont: +- les recettes (permettant d'apprendre les recettes*); +- certains ingrédients; +- le Wok (la troisième assiette permettant de cuisiner avec trois ingrédients n'importe où); +- Tous les trésors de collection (incluant les oeufs d'animal de compagnie et les costumes); +- Li et la musique de Li; +- Toutes les musiques (autre que la musique de Li puisque cette dernière est apprise en obtenant Li); +- Les localisations de transportation. + +Il y a également l'option pour mélanger les ingrédients obtenus en éliminant des monstres, des poissons ou des plantes. + +*À noter que, contrairement au jeu original, il est impossible de cuisiner une recette qui n'a pas préalablement +été apprise en obtenant un repas en tant qu'objet. À noter également que les ennemies et plantes qui +donnent un repas dont la recette n'a pas préalablement été apprise vont donner les ingrédients de cette +recette. + +## Quel est le but de Aquaria ? + +Dans Aquaria, le but est de battre le monstre final (le créateur). Il est également possible d'ajouter +des buts comme obtenir les trois souvenirs secrets, ou devoir battre une quantité de boss ou de mini-boss. + +## Quels objets peuvent se trouver dans le monde d'un autre joueur ? + +Tous les objets indiqués plus haut peuvent être obtenus à partir du monde d'un autre joueur. + +## À quoi ressemble un objet d'un autre monde dans ce jeu + +Autre que pour les trésors de collection (dont le visuel demeure inchangé), +les autres localisations n'ont aucun visuel. Lorsqu'une localisation randomisée est obtenue, +un message est affiché à l'écran pour indiquer quel objet a été trouvé et pour quel joueur. + +## Que se passe-t-il lorsque le joueur reçoit un objet ? + +Chaque fois qu'un objet est reçu, un message apparaît à l'écran pour en informer le joueur. diff --git a/worlds/aquaria/docs/setup_en.md b/worlds/aquaria/docs/setup_en.md new file mode 100644 index 000000000000..34196757a31c --- /dev/null +++ b/worlds/aquaria/docs/setup_en.md @@ -0,0 +1,115 @@ +# Aquaria Randomizer Setup Guide + +## Required Software + +- The original Aquaria Game (purchasable from most online game stores) +- The [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases) + +## Optional Software + +- For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) + +## Installation and execution Procedures + +### Windows + +First, you should copy the original Aquaria folder game. The randomizer will possibly modify the game so that +the original game will stop working. Copying the folder will guarantee that the original game keeps on working. +Also, in Windows, the save files are stored in the Aquaria folder. So copying the Aquaria folder for every Multiworld +game you play will make sure that every game has its own save game. + +Unzip the Aquaria randomizer release and copy all unzipped files in the Aquaria game folder. The unzipped files are: +- aquaria_randomizer.exe +- OpenAL32.dll +- override (directory) +- SDL2.dll +- usersettings.xml +- wrap_oal.dll +- cacert.pem + +If there is a conflict between files in the original game folder and the unzipped files, you should overwrite +the original files with the ones from the unzipped randomizer. + +Finally, to launch the randomizer, you must use the command line interface (you can open the command line interface +by typing `cmd` in the address bar of the Windows File Explorer). Here is the command line used to start the +randomizer: + +```bash +aquaria_randomizer.exe --name YourName --server theServer:thePort +``` + +or, if the room has a password: + +```bash +aquaria_randomizer.exe --name YourName --server theServer:thePort --password thePassword +``` + +### Linux when using the AppImage + +If you use the AppImage, just copy it into the Aquaria game folder. You then have to make it executable. You +can do that from command line by using: + +```bash +chmod +x Aquaria_Randomizer-*.AppImage +``` + +or by using the Graphical Explorer of your system. + +To launch the randomizer, just launch in command line: + +```bash +./Aquaria_Randomizer-*.AppImage --name YourName --server theServer:thePort +``` + +or, if the room has a password: + +```bash +./Aquaria_Randomizer-*.AppImage --name YourName --server theServer:thePort --password thePassword +``` + +Note that you should not have multiple Aquaria_Randomizer AppImage file in the same folder. If this situation occurs, +the preceding commands will launch the game multiple times. + +### Linux when using the tar file + +First, you should copy the original Aquaria folder game. The randomizer will possibly modify the game so that +the original game will stop working. Copying the folder will guarantee that the original game keeps on working. + +Untar the Aquaria randomizer release and copy all extracted files in the Aquaria game folder. The extracted files are: +- aquaria_randomizer +- override (directory) +- usersettings.xml +- cacert.pem + +If there is a conflict between files in the original game folder and the extracted files, you should overwrite +the original files with the ones from the extracted randomizer files. + +Then, you should use your system package manager to install `liblua5`, `libogg`, `libvorbis`, `libopenal` and `libsdl2`. +On Debian base system (like Ubuntu), you can use the following command: + +```bash +sudo apt install liblua5.1-0-dev libogg-dev libvorbis-dev libopenal-dev libsdl2-dev +``` + +Also, if there are certain `.so` files in the original Aquaria game folder (`libgcc_s.so.1`, `libopenal.so.1`, +`libSDL-1.2.so.0` and `libstdc++.so.6`), you should remove them from the Aquaria Randomizer game folder. Those are +old libraries that will not work on the recent build of the randomizer. + +To launch the randomizer, just launch in command line: + +```bash +./aquaria_randomizer --name YourName --server theServer:thePort +``` + +or, if the room has a password: + +```bash +./aquaria_randomizer --name YourName --server theServer:thePort --password thePassword +``` + +Note: If you get a permission denied error when using the command line, you can use this command to be +sure that your executable has executable permission: + +```bash +chmod +x aquaria_randomizer +``` diff --git a/worlds/aquaria/docs/setup_fr.md b/worlds/aquaria/docs/setup_fr.md new file mode 100644 index 000000000000..2c34f1e6a50f --- /dev/null +++ b/worlds/aquaria/docs/setup_fr.md @@ -0,0 +1,118 @@ +# Guide de configuration MultiWorld d'Aquaria + +## Logiciels nécessaires + +- Le jeu Aquaria original (trouvable sur la majorité des sites de ventes de jeux vidéo en ligne) +- Le client Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases) +- De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) + +## Procédures d'installation et d'exécution + +### Windows + +En premier lieu, vous devriez effectuer une nouvelle copie du jeu d'Aquaria original à chaque fois que vous effectuez une +nouvelle partie. La première raison de cette copie est que le randomizer modifie des fichiers qui rendront possiblement +le jeu original non fonctionnel. La seconde raison d'effectuer cette copie est que les sauvegardes sont créées +directement dans le répertoire du jeu. Donc, la copie permet d'éviter de perdre vos sauvegardes du jeu d'origine ou +encore de charger une sauvegarde d'une ancienne partie de multiworld (ce qui pourrait avoir comme conséquence de briser +la logique du multiworld). + +Désarchiver le randomizer d'Aquaria et copier tous les fichiers de l'archive dans le répertoire du jeu d'Aquaria. Le +fichier d'archive devrait contenir les fichiers suivants: +- aquaria_randomizer.exe +- OpenAL32.dll +- override (directory) +- SDL2.dll +- usersettings.xml +- wrap_oal.dll +- cacert.pem + +S'il y a des conflits entre les fichiers de l'archive zip et les fichiers du jeu original, vous devez utiliser +les fichiers contenus dans l'archive zip. + +Finalement, pour lancer le randomizer, vous devez utiliser la ligne de commande (vous pouvez ouvrir une interface de +ligne de commande, entrez l'adresse `cmd` dans la barre d'adresse de l'explorateur de fichier de Windows). Voici +la ligne de commande à utiliser pour lancer le randomizer: + +```bash +aquaria_randomizer.exe --name VotreNom --server leServeur:LePort +``` + +ou, si vous devez entrer un mot de passe: + +```bash +aquaria_randomizer.exe --name VotreNom --server leServeur:LePort --password leMotDePasse +``` + +### Linux avec le fichier AppImage + +Si vous utilisez le fichier AppImage, copiez le fichier dans le répertoire du jeu d'Aquaria. Ensuite, assurez-vous de +le mettre exécutable. Vous pouvez mettre le fichier exécutable avec la commande suivante: + +```bash +chmod +x Aquaria_Randomizer-*.AppImage +``` + +ou bien en utilisant l'explorateur graphique de votre système. + +Pour lancer le randomizer, utiliser la commande suivante: + +```bash +./Aquaria_Randomizer-*.AppImage --name VotreNom --server LeServeur:LePort +``` + +Si vous devez entrer un mot de passe: + +```bash +./Aquaria_Randomizer-*.AppImage --name VotreNom --server LeServeur:LePort --password LeMotDePasse +``` + +À noter que vous ne devez pas avoir plusieurs fichiers AppImage différents dans le même répertoire. Si cette situation +survient, le jeu sera lancé plusieurs fois. + +### Linux avec le fichier tar + +En premier lieu, assurez-vous de faire une copie du répertoire du jeu d'origine d'Aquaria. Les fichiers contenus +dans le randomizer auront comme impact de rendre le jeu d'origine non fonctionnel. Donc, effectuer la copie du jeu +avant de déposer le randomizer à l'intérieur permet de vous assurer de garder une version du jeu d'origine fonctionnel. + +Désarchiver le fichier tar et copier tous les fichiers qu'il contient dans le répertoire du jeu d'origine d'Aquaria. Les +fichiers extraient du fichier tar devraient être les suivants: +- aquaria_randomizer +- override (directory) +- usersettings.xml +- cacert.pem + +S'il y a des conflits entre les fichiers de l'archive tar et les fichiers du jeu original, vous devez utiliser +les fichiers contenus dans l'archive tar. + +Ensuite, vous devez installer manuellement les librairies dont dépend le jeu: liblua5, libogg, libvorbis, libopenal and +libsdl2. Vous pouvez utiliser le système de "package" de votre système pour les installer. Voici un exemple avec +Debian (et Ubuntu): + +```bash +sudo apt install liblua5.1-0-dev libogg-dev libvorbis-dev libopenal-dev libsdl2-dev +``` + +Notez également que s'il y a des fichiers ".so" dans le répertoire d'Aquaria (`libgcc_s.so.1`, `libopenal.so.1`, +`libSDL-1.2.so.0` and `libstdc++.so.6`), vous devriez les retirer. Il s'agit de vieille version des librairies qui +ne sont plus fonctionnelles dans les systèmes modernes et qui pourrait empêcher le randomizer de fonctionner. + +Pour lancer le randomizer, utiliser la commande suivante: + +```bash +./aquaria_randomizer --name VotreNom --server LeServeur:LePort +``` + +Si vous devez entrer un mot de passe: + +```bash +./aquaria_randomizer --name VotreNom --server LeServeur:LePort --password LeMotDePasse +``` + +Note: Si vous avez une erreur de permission lors de l'exécution du randomizer, vous pouvez utiliser cette commande +pour vous assurer que votre fichier est exécutable: + +```bash +chmod +x aquaria_randomizer +``` diff --git a/worlds/aquaria/test/__init__.py b/worlds/aquaria/test/__init__.py new file mode 100644 index 000000000000..029db691b66b --- /dev/null +++ b/worlds/aquaria/test/__init__.py @@ -0,0 +1,218 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Base class for the Aquaria randomizer unit tests +""" + + +from test.bases import WorldTestBase + +# Every location accessible after the home water. +after_home_water_locations = [ + "Sun Crystal", + "Home Water, Transturtle", + "Open Water top left area, bulb under the rock in the right path", + "Open Water top left area, bulb under the rock in the left path", + "Open Water top left area, bulb to the right of the save crystal", + "Open Water top right area, bulb in the small path before Mithalas", + "Open Water top right area, bulb in the path from the left entrance", + "Open Water top right area, bulb in the clearing close to the bottom exit", + "Open Water top right area, bulb in the big clearing close to the save crystal", + "Open Water top right area, bulb in the big clearing to the top exit", + "Open Water top right area, first urn in the Mithalas exit", + "Open Water top right area, second urn in the Mithalas exit", + "Open Water top right area, third urn in the Mithalas exit", + "Open Water top right area, bulb in the turtle room", + "Open Water top right area, Transturtle", + "Open Water bottom left area, bulb behind the chomper fish", + "Open Water bottom left area, bulb inside the lowest fish pass", + "Open Water skeleton path, bulb close to the right exit", + "Open Water skeleton path, bulb behind the chomper fish", + "Open Water skeleton path, King Skull", + "Arnassi Ruins, bulb in the right part", + "Arnassi Ruins, bulb in the left part", + "Arnassi Ruins, bulb in the center part", + "Arnassi Ruins, Song Plant Spore", + "Arnassi Ruins, Arnassi Armor", + "Arnassi Ruins, Arnassi Statue", + "Arnassi Ruins, Transturtle", + "Arnassi Ruins, Crab Armor", + "Simon Says area, Transturtle", + "Mithalas City, first bulb in the left city part", + "Mithalas City, second bulb in the left city part", + "Mithalas City, bulb in the right part", + "Mithalas City, bulb at the top of the city", + "Mithalas City, first bulb in a broken home", + "Mithalas City, second bulb in a broken home", + "Mithalas City, bulb in the bottom left part", + "Mithalas City, first bulb in one of the homes", + "Mithalas City, second bulb in one of the homes", + "Mithalas City, first urn in one of the homes", + "Mithalas City, second urn in one of the homes", + "Mithalas City, first urn in the city reserve", + "Mithalas City, second urn in the city reserve", + "Mithalas City, third urn in the city reserve", + "Mithalas City, first bulb at the end of the top path", + "Mithalas City, second bulb at the end of the top path", + "Mithalas City, bulb in the top path", + "Mithalas City, Mithalas Pot", + "Mithalas City, urn in the Castle flower tube entrance", + "Mithalas City, Doll", + "Mithalas City, urn inside a home fish pass", + "Mithalas City Castle, bulb in the flesh hole", + "Mithalas City Castle, Blue Banner", + "Mithalas City Castle, urn in the bedroom", + "Mithalas City Castle, first urn of the single lamp path", + "Mithalas City Castle, second urn of the single lamp path", + "Mithalas City Castle, urn in the bottom room", + "Mithalas City Castle, first urn on the entrance path", + "Mithalas City Castle, second urn on the entrance path", + "Mithalas City Castle, beating the Priests", + "Mithalas City Castle, Trident Head", + "Mithalas Cathedral, first urn in the top right room", + "Mithalas Cathedral, second urn in the top right room", + "Mithalas Cathedral, third urn in the top right room", + "Mithalas Cathedral, urn in the flesh room with fleas", + "Mithalas Cathedral, first urn in the bottom right path", + "Mithalas Cathedral, second urn in the bottom right path", + "Mithalas Cathedral, urn behind the flesh vein", + "Mithalas Cathedral, urn in the top left eyes boss room", + "Mithalas Cathedral, first urn in the path behind the flesh vein", + "Mithalas Cathedral, second urn in the path behind the flesh vein", + "Mithalas Cathedral, third urn in the path behind the flesh vein", + "Mithalas Cathedral, fourth urn in the top right room", + "Mithalas Cathedral, Mithalan Dress", + "Mithalas Cathedral, urn below the left entrance", + "Cathedral Underground, bulb in the center part", + "Cathedral Underground, first bulb in the top left part", + "Cathedral Underground, second bulb in the top left part", + "Cathedral Underground, third bulb in the top left part", + "Cathedral Underground, bulb close to the save crystal", + "Cathedral Underground, bulb in the bottom right path", + "Mithalas boss area, beating Mithalan God", + "Kelp Forest top left area, bulb in the bottom left clearing", + "Kelp Forest top left area, bulb in the path down from the top left clearing", + "Kelp Forest top left area, bulb in the top left clearing", + "Kelp Forest top left area, Jelly Egg", + "Kelp Forest top left area, bulb close to the Verse Egg", + "Kelp Forest top left area, Verse Egg", + "Kelp Forest top right area, bulb under the rock in the right path", + "Kelp Forest top right area, bulb at the left of the center clearing", + "Kelp Forest top right area, bulb in the left path's big room", + "Kelp Forest top right area, bulb in the left path's small room", + "Kelp Forest top right area, bulb at the top of the center clearing", + "Kelp Forest top right area, Black Pearl", + "Kelp Forest top right area, bulb in the top fish pass", + "Kelp Forest bottom left area, bulb close to the spirit crystals", + "Kelp Forest bottom left area, Walker Baby", + "Kelp Forest bottom left area, Transturtle", + "Kelp Forest bottom right area, Odd Container", + "Kelp Forest boss area, beating Drunian God", + "Kelp Forest boss room, bulb at the bottom of the area", + "Kelp Forest bottom left area, Fish Cave puzzle", + "Kelp Forest sprite cave, bulb inside the fish pass", + "Kelp Forest sprite cave, bulb in the second room", + "Kelp Forest sprite cave, Seed Bag", + "Mermog cave, bulb in the left part of the cave", + "Mermog cave, Piranha Egg", + "The Veil top left area, In Li's cave", + "The Veil top left area, bulb under the rock in the top right path", + "The Veil top left area, bulb hidden behind the blocking rock", + "The Veil top left area, Transturtle", + "The Veil top left area, bulb inside the fish pass", + "Turtle cave, Turtle Egg", + "Turtle cave, bulb in Bubble Cliff", + "Turtle cave, Urchin Costume", + "The Veil top right area, bulb in the middle of the wall jump cliff", + "The Veil top right area, Golden Starfish", + "The Veil top right area, bulb at the top of the waterfall", + "The Veil top right area, Transturtle", + "The Veil bottom area, bulb in the left path", + "The Veil bottom area, bulb in the spirit path", + "The Veil bottom area, Verse Egg", + "The Veil bottom area, Stone Head", + "Octopus Cave, Dumbo Egg", + "Octopus Cave, bulb in the path below the Octopus Cave path", + "Bubble Cave, bulb in the left cave wall", + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + "Bubble Cave, Verse Egg", + "Sun Temple, bulb in the top left part", + "Sun Temple, bulb in the top right part", + "Sun Temple, bulb at the top of the high dark room", + "Sun Temple, Golden Gear", + "Sun Temple, first bulb of the temple", + "Sun Temple, bulb on the left part", + "Sun Temple, bulb in the hidden room of the right part", + "Sun Temple, Sun Key", + "Sun Worm path, first path bulb", + "Sun Worm path, second path bulb", + "Sun Worm path, first cliff bulb", + "Sun Worm path, second cliff bulb", + "Sun Temple boss area, beating Sun God", + "Abyss left area, bulb in hidden path room", + "Abyss left area, bulb in the right part", + "Abyss left area, Glowing Seed", + "Abyss left area, Glowing Plant", + "Abyss left area, bulb in the bottom fish pass", + "Abyss right area, bulb behind the rock in the whale room", + "Abyss right area, bulb in the middle path", + "Abyss right area, bulb behind the rock in the middle path", + "Abyss right area, bulb in the left green room", + "Abyss right area, Transturtle", + "Ice Cave, bulb in the room to the right", + "Ice Cave, first bulb in the top exit room", + "Ice Cave, second bulb in the top exit room", + "Ice Cave, third bulb in the top exit room", + "Ice Cave, bulb in the left room", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "The Whale, Verse Egg", + "Sunken City right area, crate close to the save crystal", + "Sunken City right area, crate in the left bottom room", + "Sunken City left area, crate in the little pipe room", + "Sunken City left area, crate close to the save crystal", + "Sunken City left area, crate before the bedroom", + "Sunken City left area, Girl Costume", + "Sunken City, bulb on top of the boss area", + "The Body center area, breaking Li's cage", + "The Body center area, bulb on the main path blocking tube", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", + "The Body bottom area, bulb in the Jelly Zap room", + "The Body bottom area, bulb in the nautilus room", + "The Body bottom area, Mutant Costume", + "Final Boss area, first bulb in the turtle room", + "Final Boss area, second bulb in the turtle room", + "Final Boss area, third bulb in the turtle room", + "Final Boss area, Transturtle", + "Final Boss area, bulb in the boss third form room", + "Simon Says area, beating Simon Says", + "Beating Fallen God", + "Beating Mithalan God", + "Beating Drunian God", + "Beating Sun God", + "Beating the Golem", + "Beating Nautilus Prime", + "Beating Blaster Peg Prime", + "Beating Mergog", + "Beating Mithalan priests", + "Beating Octopus Prime", + "Beating Crabbius Maximus", + "Beating Mantis Shrimp Prime", + "Beating King Jellyfish God Prime", + "First secret", + "Second secret", + "Third secret", + "Sunken City cleared", + "Objective complete", +] + +class AquariaTestBase(WorldTestBase): + """Base class for Aquaria unit tests""" + game = "Aquaria" diff --git a/worlds/aquaria/test/test_beast_form_access.py b/worlds/aquaria/test/test_beast_form_access.py new file mode 100644 index 000000000000..0efc3e7388fe --- /dev/null +++ b/worlds/aquaria/test/test_beast_form_access.py @@ -0,0 +1,48 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the beast form +""" + +from . import AquariaTestBase + + +class BeastFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the beast form""" + + def test_beast_form_location(self) -> None: + """Test locations that require beast form""" + locations = [ + "Mithalas City Castle, beating the Priests", + "Arnassi Ruins, Crab Armor", + "Arnassi Ruins, Song Plant Spore", + "Mithalas City, first bulb at the end of the top path", + "Mithalas City, second bulb at the end of the top path", + "Mithalas City, bulb in the top path", + "Mithalas City, Mithalas Pot", + "Mithalas City, urn in the Castle flower tube entrance", + "Mermog cave, Piranha Egg", + "Mithalas Cathedral, Mithalan Dress", + "Turtle cave, bulb in Bubble Cliff", + "Turtle cave, Urchin Costume", + "Sun Worm path, first cliff bulb", + "Sun Worm path, second cliff bulb", + "The Veil top right area, bulb at the top of the waterfall", + "Bubble Cave, bulb in the left cave wall", + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + "Bubble Cave, Verse Egg", + "Sunken City, bulb on top of the boss area", + "Octopus Cave, Dumbo Egg", + "Beating the Golem", + "Beating Mergog", + "Beating Crabbius Maximus", + "Beating Octopus Prime", + "Beating Mantis Shrimp Prime", + "King Jellyfish Cave, Jellyfish Costume", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "Beating King Jellyfish God Prime", + "Beating Mithalan priests", + "Sunken City cleared" + ] + items = [["Beast form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_bind_song_access.py b/worlds/aquaria/test/test_bind_song_access.py new file mode 100644 index 000000000000..05f96edb9192 --- /dev/null +++ b/worlds/aquaria/test/test_bind_song_access.py @@ -0,0 +1,36 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the bind song (without the location + under rock needing bind song option) +""" + +from . import AquariaTestBase, after_home_water_locations + + +class BindSongAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the bind song""" + options = { + "bind_song_needed_to_get_under_rock_bulb": False, + } + + def test_bind_song_location(self) -> None: + """Test locations that require Bind song""" + locations = [ + "Verse Cave right area, Big Seed", + "Home Water, bulb in the path below Nautilus Prime", + "Home Water, bulb in the bottom left room", + "Home Water, Nautilus Egg", + "Song Cave, Verse Egg", + "Energy Temple first area, beating the Energy Statue", + "Energy Temple first area, bulb in the bottom room blocked by a rock", + "Energy Temple first area, Energy Idol", + "Energy Temple second area, bulb under the rock", + "Energy Temple bottom entrance, Krotite Armor", + "Energy Temple third area, bulb in the bottom path", + "Energy Temple boss area, Fallen God Tooth", + "Energy Temple blaster room, Blaster Egg", + *after_home_water_locations + ] + items = [["Bind song"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_bind_song_option_access.py b/worlds/aquaria/test/test_bind_song_option_access.py new file mode 100644 index 000000000000..e391eef101bf --- /dev/null +++ b/worlds/aquaria/test/test_bind_song_option_access.py @@ -0,0 +1,42 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the bind song (with the location + under rock needing bind song option) +""" + +from . import AquariaTestBase +from .test_bind_song_access import after_home_water_locations + + +class BindSongOptionAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the bind song""" + options = { + "bind_song_needed_to_get_under_rock_bulb": True, + } + + def test_bind_song_location(self) -> None: + """Test locations that require Bind song with the bind song needed option activated""" + locations = [ + "Verse Cave right area, Big Seed", + "Verse Cave left area, bulb under the rock at the end of the path", + "Home Water, bulb under the rock in the left path from the Verse Cave", + "Song Cave, bulb under the rock close to the song door", + "Song Cave, bulb under the rock in the path to the singing statues", + "Naija's Home, bulb under the rock at the right of the main path", + "Home Water, bulb in the path below Nautilus Prime", + "Home Water, bulb in the bottom left room", + "Home Water, Nautilus Egg", + "Song Cave, Verse Egg", + "Energy Temple first area, beating the Energy Statue", + "Energy Temple first area, bulb in the bottom room blocked by a rock", + "Energy Temple first area, Energy Idol", + "Energy Temple second area, bulb under the rock", + "Energy Temple bottom entrance, Krotite Armor", + "Energy Temple third area, bulb in the bottom path", + "Energy Temple boss area, Fallen God Tooth", + "Energy Temple blaster room, Blaster Egg", + *after_home_water_locations + ] + items = [["Bind song"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_confined_home_water.py b/worlds/aquaria/test/test_confined_home_water.py new file mode 100644 index 000000000000..89c51ac5c775 --- /dev/null +++ b/worlds/aquaria/test/test_confined_home_water.py @@ -0,0 +1,20 @@ +""" +Author: Louis M +Date: Fri, 03 May 2024 14:07:35 +0000 +Description: Unit test used to test accessibility of region with the home water confine via option +""" + +from . import AquariaTestBase + + +class ConfinedHomeWaterAccessTest(AquariaTestBase): + """Unit test used to test accessibility of region with the unconfine home water option disabled""" + options = { + "unconfine_home_water": 0, + "early_energy_form": False + } + + def test_confine_home_water_location(self) -> None: + """Test region accessible with confined home water""" + self.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area") + self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room") diff --git a/worlds/aquaria/test/test_dual_song_access.py b/worlds/aquaria/test/test_dual_song_access.py new file mode 100644 index 000000000000..bb9b2e739604 --- /dev/null +++ b/worlds/aquaria/test/test_dual_song_access.py @@ -0,0 +1,26 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the dual song +""" + +from . import AquariaTestBase + + +class LiAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the dual song""" + options = { + "turtle_randomizer": 1, + } + + def test_li_song_location(self) -> None: + """Test locations that require the dual song""" + locations = [ + "The Body bottom area, bulb in the Jelly Zap room", + "The Body bottom area, bulb in the nautilus room", + "The Body bottom area, Mutant Costume", + "Final Boss area, bulb in the boss third form room", + "Objective complete" + ] + items = [["Dual form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_energy_form_access.py b/worlds/aquaria/test/test_energy_form_access.py new file mode 100644 index 000000000000..82d8e89a0066 --- /dev/null +++ b/worlds/aquaria/test/test_energy_form_access.py @@ -0,0 +1,72 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the bind song (without the early + energy form option) +""" + +from . import AquariaTestBase + + +class EnergyFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the energy form""" + options = { + "early_energy_form": False, + } + + def test_energy_form_location(self) -> None: + """Test locations that require Energy form""" + locations = [ + "Home Water, Nautilus Egg", + "Naija's Home, bulb after the energy door", + "Energy Temple first area, bulb in the bottom room blocked by a rock", + "Energy Temple second area, bulb under the rock", + "Energy Temple bottom entrance, Krotite Armor", + "Energy Temple third area, bulb in the bottom path", + "Energy Temple boss area, Fallen God Tooth", + "Energy Temple blaster room, Blaster Egg", + "Mithalas City Castle, beating the Priests", + "Mithalas Cathedral, first urn in the top right room", + "Mithalas Cathedral, second urn in the top right room", + "Mithalas Cathedral, third urn in the top right room", + "Mithalas Cathedral, urn in the flesh room with fleas", + "Mithalas Cathedral, first urn in the bottom right path", + "Mithalas Cathedral, second urn in the bottom right path", + "Mithalas Cathedral, urn behind the flesh vein", + "Mithalas Cathedral, urn in the top left eyes boss room", + "Mithalas Cathedral, first urn in the path behind the flesh vein", + "Mithalas Cathedral, second urn in the path behind the flesh vein", + "Mithalas Cathedral, third urn in the path behind the flesh vein", + "Mithalas Cathedral, fourth urn in the top right room", + "Mithalas Cathedral, Mithalan Dress", + "Mithalas Cathedral, urn below the left entrance", + "Mithalas boss area, beating Mithalan God", + "Kelp Forest top left area, bulb close to the Verse Egg", + "Kelp Forest top left area, Verse Egg", + "Kelp Forest boss area, beating Drunian God", + "Mermog cave, Piranha Egg", + "Octopus Cave, Dumbo Egg", + "Sun Temple boss area, beating Sun God", + "Arnassi Ruins, Crab Armor", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "Sunken City, bulb on top of the boss area", + "Final Boss area, bulb in the boss third form room", + "Beating Fallen God", + "Beating Mithalan God", + "Beating Drunian God", + "Beating Sun God", + "Beating the Golem", + "Beating Nautilus Prime", + "Beating Blaster Peg Prime", + "Beating Mergog", + "Beating Mithalan priests", + "Beating Octopus Prime", + "Beating Crabbius Maximus", + "Beating King Jellyfish God Prime", + "First secret", + "Sunken City cleared", + "Objective complete", + ] + items = [["Energy form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_fish_form_access.py b/worlds/aquaria/test/test_fish_form_access.py new file mode 100644 index 000000000000..c98a53e92438 --- /dev/null +++ b/worlds/aquaria/test/test_fish_form_access.py @@ -0,0 +1,37 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the fish form +""" + +from . import AquariaTestBase + + +class FishFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the fish form""" + options = { + "turtle_randomizer": 1, + } + + def test_fish_form_location(self) -> None: + """Test locations that require fish form""" + locations = [ + "The Veil top left area, bulb inside the fish pass", + "Mithalas City, Doll", + "Mithalas City, urn inside a home fish pass", + "Kelp Forest top right area, bulb in the top fish pass", + "The Veil bottom area, Verse Egg", + "Open Water bottom left area, bulb inside the lowest fish pass", + "Kelp Forest top left area, bulb close to the Verse Egg", + "Kelp Forest top left area, Verse Egg", + "Mermog cave, bulb in the left part of the cave", + "Mermog cave, Piranha Egg", + "Beating Mergog", + "Octopus Cave, Dumbo Egg", + "Octopus Cave, bulb in the path below the Octopus Cave path", + "Beating Octopus Prime", + "Abyss left area, bulb in the bottom fish pass", + "Arnassi Ruins, Arnassi Armor" + ] + items = [["Fish form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_li_song_access.py b/worlds/aquaria/test/test_li_song_access.py new file mode 100644 index 000000000000..f615fb10c640 --- /dev/null +++ b/worlds/aquaria/test/test_li_song_access.py @@ -0,0 +1,45 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without Li +""" + +from . import AquariaTestBase + + +class LiAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without Li""" + options = { + "turtle_randomizer": 1, + } + + def test_li_song_location(self) -> None: + """Test locations that require Li""" + locations = [ + "Sunken City right area, crate close to the save crystal", + "Sunken City right area, crate in the left bottom room", + "Sunken City left area, crate in the little pipe room", + "Sunken City left area, crate close to the save crystal", + "Sunken City left area, crate before the bedroom", + "Sunken City left area, Girl Costume", + "Sunken City, bulb on top of the boss area", + "The Body center area, breaking Li's cage", + "The Body center area, bulb on the main path blocking tube", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", + "The Body bottom area, bulb in the Jelly Zap room", + "The Body bottom area, bulb in the nautilus room", + "The Body bottom area, Mutant Costume", + "Final Boss area, bulb in the boss third form room", + "Beating the Golem", + "Sunken City cleared", + "Objective complete" + ] + items = [["Li and Li song", "Body tongue cleared"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_light_access.py b/worlds/aquaria/test/test_light_access.py new file mode 100644 index 000000000000..b5d7cf99fea2 --- /dev/null +++ b/worlds/aquaria/test/test_light_access.py @@ -0,0 +1,71 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without a light (Dumbo pet or sun form) +""" + +from . import AquariaTestBase + + +class LightAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without light""" + options = { + "turtle_randomizer": 1, + "light_needed_to_get_to_dark_places": True, + } + + def test_light_location(self) -> None: + """Test locations that require light""" + locations = [ + # Since the `assertAccessDependency` sweep for events even if I tell it not to, those location cannot be + # tested. + # "Third secret", + # "Sun Temple, bulb in the top left part", + # "Sun Temple, bulb in the top right part", + # "Sun Temple, bulb at the top of the high dark room", + # "Sun Temple, Golden Gear", + # "Sun Worm path, first path bulb", + # "Sun Worm path, second path bulb", + # "Sun Worm path, first cliff bulb", + "Octopus Cave, Dumbo Egg", + "Kelp Forest bottom right area, Odd Container", + "Kelp Forest top right area, Black Pearl", + "Abyss left area, bulb in hidden path room", + "Abyss left area, bulb in the right part", + "Abyss left area, Glowing Seed", + "Abyss left area, Glowing Plant", + "Abyss left area, bulb in the bottom fish pass", + "Abyss right area, bulb behind the rock in the whale room", + "Abyss right area, bulb in the middle path", + "Abyss right area, bulb behind the rock in the middle path", + "Abyss right area, bulb in the left green room", + "Abyss right area, Transturtle", + "Ice Cave, bulb in the room to the right", + "Ice Cave, first bulb in the top exit room", + "Ice Cave, second bulb in the top exit room", + "Ice Cave, third bulb in the top exit room", + "Ice Cave, bulb in the left room", + "Bubble Cave, bulb in the left cave wall", + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + "Bubble Cave, Verse Egg", + "Beating Mantis Shrimp Prime", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "Beating King Jellyfish God Prime", + "The Whale, Verse Egg", + "First secret", + "Sunken City right area, crate close to the save crystal", + "Sunken City right area, crate in the left bottom room", + "Sunken City left area, crate in the little pipe room", + "Sunken City left area, crate close to the save crystal", + "Sunken City left area, crate before the bedroom", + "Sunken City left area, Girl Costume", + "Sunken City, bulb on top of the boss area", + "Sunken City cleared", + "Beating the Golem", + "Beating Octopus Prime", + "Final Boss area, bulb in the boss third form room", + "Objective complete", + ] + items = [["Sun form", "Baby Dumbo", "Has sun crystal"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_nature_form_access.py b/worlds/aquaria/test/test_nature_form_access.py new file mode 100644 index 000000000000..1d3b8f4150eb --- /dev/null +++ b/worlds/aquaria/test/test_nature_form_access.py @@ -0,0 +1,57 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the nature form +""" + +from . import AquariaTestBase + + +class NatureFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the nature form""" + options = { + "turtle_randomizer": 1, + } + + def test_nature_form_location(self) -> None: + """Test locations that require nature form""" + locations = [ + "Song Cave, Anemone Seed", + "Energy Temple blaster room, Blaster Egg", + "Beating Blaster Peg Prime", + "Kelp Forest top left area, Verse Egg", + "Kelp Forest top left area, bulb close to the Verse Egg", + "Mithalas City Castle, beating the Priests", + "Kelp Forest sprite cave, bulb in the second room", + "Kelp Forest sprite cave, Seed Bag", + "Beating Mithalan priests", + "Abyss left area, bulb in the bottom fish pass", + "Bubble Cave, Verse Egg", + "Beating Mantis Shrimp Prime", + "Sunken City right area, crate close to the save crystal", + "Sunken City right area, crate in the left bottom room", + "Sunken City left area, crate in the little pipe room", + "Sunken City left area, crate close to the save crystal", + "Sunken City left area, crate before the bedroom", + "Sunken City left area, Girl Costume", + "Sunken City, bulb on top of the boss area", + "Beating the Golem", + "Sunken City cleared", + "The Body center area, breaking Li's cage", + "The Body center area, bulb on the main path blocking tube", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", + "The Body bottom area, bulb in the Jelly Zap room", + "The Body bottom area, bulb in the nautilus room", + "The Body bottom area, Mutant Costume", + "Final Boss area, bulb in the boss third form room", + "Objective complete" + ] + items = [["Nature form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_no_progression_hard_hidden_locations.py b/worlds/aquaria/test/test_no_progression_hard_hidden_locations.py new file mode 100644 index 000000000000..f015b26de10b --- /dev/null +++ b/worlds/aquaria/test/test_no_progression_hard_hidden_locations.py @@ -0,0 +1,60 @@ +""" +Author: Louis M +Date: Fri, 03 May 2024 14:07:35 +0000 +Description: Unit test used to test that no progression items can be put in hard or hidden locations when option enabled +""" + +from . import AquariaTestBase +from BaseClasses import ItemClassification + + +class UNoProgressionHardHiddenTest(AquariaTestBase): + """Unit test used to test that no progression items can be put in hard or hidden locations when option enabled""" + options = { + "no_progression_hard_or_hidden_locations": True + } + + unfillable_locations = [ + "Energy Temple boss area, Fallen God Tooth", + "Mithalas boss area, beating Mithalan God", + "Kelp Forest boss area, beating Drunian God", + "Sun Temple boss area, beating Sun God", + "Sunken City, bulb on top of the boss area", + "Home Water, Nautilus Egg", + "Energy Temple blaster room, Blaster Egg", + "Mithalas City Castle, beating the Priests", + "Mermog cave, Piranha Egg", + "Octopus Cave, Dumbo Egg", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "Final Boss area, bulb in the boss third form room", + "Sun Worm path, first cliff bulb", + "Sun Worm path, second cliff bulb", + "The Veil top right area, bulb at the top of the waterfall", + "Bubble Cave, bulb in the left cave wall", + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + "Bubble Cave, Verse Egg", + "Kelp Forest bottom left area, bulb close to the spirit crystals", + "Kelp Forest bottom left area, Walker Baby", + "Sun Temple, Sun Key", + "The Body bottom area, Mutant Costume", + "Sun Temple, bulb in the hidden room of the right part", + "Arnassi Ruins, Arnassi Armor", + ] + + def test_unconfine_home_water_both_location_fillable(self) -> None: + """ + Unit test used to test that no progression items can be put in hard or hidden locations when option enabled + """ + for location in self.unfillable_locations: + for item_name in self.world.item_names: + item = self.get_item_by_name(item_name) + if item.classification == ItemClassification.progression: + self.assertFalse( + self.world.get_location(location).can_fill(self.multiworld.state, item, False), + "The location \"" + location + "\" can be filled with \"" + item_name + "\"") + else: + self.assertTrue( + self.world.get_location(location).can_fill(self.multiworld.state, item, False), + "The location \"" + location + "\" cannot be filled with \"" + item_name + "\"") + diff --git a/worlds/aquaria/test/test_progression_hard_hidden_locations.py b/worlds/aquaria/test/test_progression_hard_hidden_locations.py new file mode 100644 index 000000000000..a1493c5d0f39 --- /dev/null +++ b/worlds/aquaria/test/test_progression_hard_hidden_locations.py @@ -0,0 +1,52 @@ +""" +Author: Louis M +Date: Fri, 03 May 2024 14:07:35 +0000 +Description: Unit test used to test that progression items can be put in hard or hidden locations when option disabled +""" + +from . import AquariaTestBase + + +class UNoProgressionHardHiddenTest(AquariaTestBase): + """Unit test used to test that no progression items can be put in hard or hidden locations when option disabled""" + options = { + "no_progression_hard_or_hidden_locations": False + } + + unfillable_locations = [ + "Energy Temple boss area, Fallen God Tooth", + "Mithalas boss area, beating Mithalan God", + "Kelp Forest boss area, beating Drunian God", + "Sun Temple boss area, beating Sun God", + "Sunken City, bulb on top of the boss area", + "Home Water, Nautilus Egg", + "Energy Temple blaster room, Blaster Egg", + "Mithalas City Castle, beating the Priests", + "Mermog cave, Piranha Egg", + "Octopus Cave, Dumbo Egg", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "Final Boss area, bulb in the boss third form room", + "Sun Worm path, first cliff bulb", + "Sun Worm path, second cliff bulb", + "The Veil top right area, bulb at the top of the waterfall", + "Bubble Cave, bulb in the left cave wall", + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + "Bubble Cave, Verse Egg", + "Kelp Forest bottom left area, bulb close to the spirit crystals", + "Kelp Forest bottom left area, Walker Baby", + "Sun Temple, Sun Key", + "The Body bottom area, Mutant Costume", + "Sun Temple, bulb in the hidden room of the right part", + "Arnassi Ruins, Arnassi Armor", + ] + + def test_unconfine_home_water_both_location_fillable(self) -> None: + """Unit test used to test that progression items can be put in hard or hidden locations when option disabled""" + for location in self.unfillable_locations: + for item_name in self.world.item_names: + item = self.get_item_by_name(item_name) + self.assertTrue( + self.world.get_location(location).can_fill(self.multiworld.state, item, False), + "The location \"" + location + "\" cannot be filled with \"" + item_name + "\"") + diff --git a/worlds/aquaria/test/test_spirit_form_access.py b/worlds/aquaria/test/test_spirit_form_access.py new file mode 100644 index 000000000000..3bcbd7d72e02 --- /dev/null +++ b/worlds/aquaria/test/test_spirit_form_access.py @@ -0,0 +1,36 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the spirit form +""" + +from . import AquariaTestBase + + +class SpiritFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the spirit form""" + + def test_spirit_form_location(self) -> None: + """Test locations that require spirit form""" + locations = [ + "The Veil bottom area, bulb in the spirit path", + "Mithalas City Castle, Trident Head", + "Open Water skeleton path, King Skull", + "Kelp Forest bottom left area, Walker Baby", + "Abyss right area, bulb behind the rock in the whale room", + "The Whale, Verse Egg", + "Ice Cave, bulb in the room to the right", + "Ice Cave, first bulb in the top exit room", + "Ice Cave, second bulb in the top exit room", + "Ice Cave, third bulb in the top exit room", + "Ice Cave, bulb in the left room", + "Bubble Cave, bulb in the left cave wall", + "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", + "Bubble Cave, Verse Egg", + "Sunken City left area, Girl Costume", + "Beating Mantis Shrimp Prime", + "First secret", + "Arnassi Ruins, Arnassi Armor", + ] + items = [["Spirit form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_sun_form_access.py b/worlds/aquaria/test/test_sun_form_access.py new file mode 100644 index 000000000000..394d5e4b27ae --- /dev/null +++ b/worlds/aquaria/test/test_sun_form_access.py @@ -0,0 +1,28 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the sun form +""" + +from . import AquariaTestBase + + +class SunFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the sun form""" + + def test_sun_form_location(self) -> None: + """Test locations that require sun form""" + locations = [ + "First secret", + "The Whale, Verse Egg", + "Abyss right area, bulb behind the rock in the whale room", + "Octopus Cave, Dumbo Egg", + "Beating Octopus Prime", + "Sunken City, bulb on top of the boss area", + "Beating the Golem", + "Sunken City cleared", + "Final Boss area, bulb in the boss third form room", + "Objective complete" + ] + items = [["Sun form"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_unconfine_home_water_via_both.py b/worlds/aquaria/test/test_unconfine_home_water_via_both.py new file mode 100644 index 000000000000..5b8689bc53a2 --- /dev/null +++ b/worlds/aquaria/test/test_unconfine_home_water_via_both.py @@ -0,0 +1,21 @@ +""" +Author: Louis M +Date: Fri, 03 May 2024 14:07:35 +0000 +Description: Unit test used to test accessibility of region with the unconfined home water option via transportation + turtle and energy door +""" + +from . import AquariaTestBase + + +class UnconfineHomeWaterBothAccessTest(AquariaTestBase): + """Unit test used to test accessibility of region with the unconfine home water option enabled""" + options = { + "unconfine_home_water": 3, + "early_energy_form": False + } + + def test_unconfine_home_water_both_location(self) -> None: + """Test locations accessible with unconfined home water via energy door and transportation turtle""" + self.assertTrue(self.can_reach_region("Open Water top left area"), "Cannot reach Open Water top left area") + self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room") diff --git a/worlds/aquaria/test/test_unconfine_home_water_via_energy_door.py b/worlds/aquaria/test/test_unconfine_home_water_via_energy_door.py new file mode 100644 index 000000000000..37a5c98610b5 --- /dev/null +++ b/worlds/aquaria/test/test_unconfine_home_water_via_energy_door.py @@ -0,0 +1,20 @@ +""" +Author: Louis M +Date: Fri, 03 May 2024 14:07:35 +0000 +Description: Unit test used to test accessibility of region with the unconfined home water option via the energy door +""" + +from . import AquariaTestBase + + +class UnconfineHomeWaterEnergyDoorAccessTest(AquariaTestBase): + """Unit test used to test accessibility of region with the unconfine home water option enabled""" + options = { + "unconfine_home_water": 1, + "early_energy_form": False + } + + def test_unconfine_home_water_energy_door_location(self) -> None: + """Test locations accessible with unconfined home water via energy door""" + self.assertTrue(self.can_reach_region("Open Water top left area"), "Cannot reach Open Water top left area") + self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room") diff --git a/worlds/aquaria/test/test_unconfine_home_water_via_transturtle.py b/worlds/aquaria/test/test_unconfine_home_water_via_transturtle.py new file mode 100644 index 000000000000..da4c83c2bc7f --- /dev/null +++ b/worlds/aquaria/test/test_unconfine_home_water_via_transturtle.py @@ -0,0 +1,20 @@ +""" +Author: Louis M +Date: Fri, 03 May 2024 14:07:35 +0000 +Description: Unit test used to test accessibility of region with the unconfined home water option via transturtle +""" + +from . import AquariaTestBase + + +class UnconfineHomeWaterTransturtleAccessTest(AquariaTestBase): + """Unit test used to test accessibility of region with the unconfine home water option enabled""" + options = { + "unconfine_home_water": 2, + "early_energy_form": False + } + + def test_unconfine_home_water_transturtle_location(self) -> None: + """Test locations accessible with unconfined home water via transportation turtle""" + self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room") + self.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area") diff --git a/worlds/archipidle/Items.py b/worlds/archipidle/Items.py index 2b5e6e9a81d0..94665631b711 100644 --- a/worlds/archipidle/Items.py +++ b/worlds/archipidle/Items.py @@ -1,4 +1,7 @@ item_table = ( + 'An Old GeoCities Profile', + 'Very Funny Joke', + 'Motivational Video', 'Staples Easy Button', 'One Million Dollars', 'Replica Master Sword', @@ -13,7 +16,7 @@ '2012 Magic the Gathering Core Set Starter Box', 'Poke\'mon Booster Pack', 'USB Speakers', - 'Plastic Spork', + 'Eco-Friendly Spork', 'Cheeseburger', 'Brand New Car', 'Hunting Knife', @@ -22,7 +25,7 @@ 'One-Up Mushroom', 'Nokia N-GAGE', '2-Liter of Sprite', - 'Free trial of the critically acclaimed MMORPG Final Fantasy XIV, including the entirety of A Realm Reborn and the award winning Heavensward expansion up to level 60 with no restrictions on playtime!', + 'Free trial of the critically acclaimed MMORPG Final Fantasy XIV, including the entirety of A Realm Reborn and the award winning Heavensward and Stormblood expansions up to level 70 with no restrictions on playtime!', 'Can of Compressed Air', 'Striped Kitten', 'USB Power Adapter', diff --git a/worlds/archipidle/Rules.py b/worlds/archipidle/Rules.py index 3bf4bad475e1..2cc6220c6927 100644 --- a/worlds/archipidle/Rules.py +++ b/worlds/archipidle/Rules.py @@ -1,6 +1,5 @@ from BaseClasses import MultiWorld -from ..AutoWorld import LogicMixin -from ..generic.Rules import set_rule +from worlds.AutoWorld import LogicMixin class ArchipIDLELogic(LogicMixin): @@ -10,29 +9,20 @@ def _archipidle_location_is_accessible(self, player_id, items_required): def set_rules(world: MultiWorld, player: int): for i in range(16, 31): - set_rule( - world.get_location(f"IDLE item number {i}", player), - lambda state: state._archipidle_location_is_accessible(player, 4) - ) + world.get_location(f"IDLE item number {i}", player).access_rule = lambda \ + state: state._archipidle_location_is_accessible(player, 4) for i in range(31, 51): - set_rule( - world.get_location(f"IDLE item number {i}", player), - lambda state: state._archipidle_location_is_accessible(player, 10) - ) + world.get_location(f"IDLE item number {i}", player).access_rule = lambda \ + state: state._archipidle_location_is_accessible(player, 10) for i in range(51, 101): - set_rule( - world.get_location(f"IDLE item number {i}", player), - lambda state: state._archipidle_location_is_accessible(player, 20) - ) + world.get_location(f"IDLE item number {i}", player).access_rule = lambda \ + state: state._archipidle_location_is_accessible(player, 20) for i in range(101, 201): - set_rule( - world.get_location(f"IDLE item number {i}", player), - lambda state: state._archipidle_location_is_accessible(player, 40) - ) + world.get_location(f"IDLE item number {i}", player).access_rule = lambda \ + state: state._archipidle_location_is_accessible(player, 40) world.completion_condition[player] =\ - lambda state:\ - state.can_reach(world.get_location("IDLE item number 200", player), "Location", player) + lambda state: state.can_reach(world.get_location("IDLE item number 200", player), "Location", player) diff --git a/worlds/archipidle/__init__.py b/worlds/archipidle/__init__.py index 2d182f31dc20..f4345444efb9 100644 --- a/worlds/archipidle/__init__.py +++ b/worlds/archipidle/__init__.py @@ -1,8 +1,8 @@ from BaseClasses import Item, MultiWorld, Region, Location, Entrance, Tutorial, ItemClassification +from worlds.AutoWorld import World, WebWorld +from datetime import datetime from .Items import item_table from .Rules import set_rules -from ..AutoWorld import World, WebWorld -from datetime import datetime class ArchipIDLEWebWorld(WebWorld): @@ -29,11 +29,10 @@ class ArchipIDLEWebWorld(WebWorld): class ArchipIDLEWorld(World): """ - An idle game which sends a check every thirty seconds, up to two hundred checks. + An idle game which sends a check every thirty to sixty seconds, up to two hundred checks. """ game = "ArchipIDLE" topology_present = False - data_version = 5 hidden = (datetime.now().month != 4) # ArchipIDLE is only visible during April web = ArchipIDLEWebWorld() @@ -56,18 +55,40 @@ def create_item(self, name: str) -> Item: return Item(name, ItemClassification.progression, self.item_name_to_id[name], self.player) def create_items(self): - item_table_copy = list(item_table) - self.multiworld.random.shuffle(item_table_copy) + item_pool = [ + ArchipIDLEItem( + item_table[0], + ItemClassification.progression, + self.item_name_to_id[item_table[0]], + self.player + ) + ] - item_pool = [] - for i in range(200): - item = ArchipIDLEItem( + for i in range(40): + item_pool.append(ArchipIDLEItem( + item_table[1], + ItemClassification.progression, + self.item_name_to_id[item_table[1]], + self.player + )) + + for i in range(40): + item_pool.append(ArchipIDLEItem( + item_table[2], + ItemClassification.filler, + self.item_name_to_id[item_table[2]], + self.player + )) + + item_table_copy = list(item_table[3:]) + self.random.shuffle(item_table_copy) + for i in range(119): + item_pool.append(ArchipIDLEItem( item_table_copy[i], - ItemClassification.progression if i < 40 else ItemClassification.filler, + ItemClassification.progression if i < 9 else ItemClassification.filler, self.item_name_to_id[item_table_copy[i]], self.player - ) - item_pool.append(item) + )) self.multiworld.itempool += item_pool diff --git a/worlds/archipidle/docs/en_ArchipIDLE.md b/worlds/archipidle/docs/en_ArchipIDLE.md index 3d57e3a0551a..c3b396c64901 100644 --- a/worlds/archipidle/docs/en_ArchipIDLE.md +++ b/worlds/archipidle/docs/en_ArchipIDLE.md @@ -6,8 +6,8 @@ ArchipIDLE was originally the 2022 Archipelago April Fools' Day joke. It is an i on regular intervals. Updated annually with more items, gimmicks, and features, the game is visible only during the month of April. -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. diff --git a/worlds/archipidle/docs/guide_en.md b/worlds/archipidle/docs/guide_en.md index e1a6532992b5..c450ec421dfc 100644 --- a/worlds/archipidle/docs/guide_en.md +++ b/worlds/archipidle/docs/guide_en.md @@ -1,12 +1,12 @@ # ArchipIdle Setup Guide ## Joining a MultiWorld Game -1. Generate a `.yaml` file from the [ArchipIDLE Player Settings Page](/games/ArchipIDLE/player-settings) +1. Generate a `.yaml` file from the [ArchipIDLE Player Options Page](/games/ArchipIDLE/player-options) 2. Open the ArchipIDLE Client in your web browser by either: - Navigate to the [ArchipIDLE Client](http://idle.multiworld.link) - Download the client and run it locally from the [ArchipIDLE GitHub Releases Page](https://github.com/ArchipelagoMW/archipidle/releases) 3. Enter the server address in the `Server Address` field and press enter 4. Enter your slot name when prompted. This should be the same as the `name` you entered on the - setting page above, or the `name` field in your yaml file. + options page above, or the `name` field in your yaml file. 5. Click the "Begin!" button. diff --git a/worlds/archipidle/docs/guide_fr.md b/worlds/archipidle/docs/guide_fr.md index c3842ed7db6e..dc0c8af3218c 100644 --- a/worlds/archipidle/docs/guide_fr.md +++ b/worlds/archipidle/docs/guide_fr.md @@ -1,11 +1,10 @@ # Guide de configuration d'ArchipIdle ## Rejoindre une partie MultiWorld -1. Générez un fichier `.yaml` à partir de la [page des paramètres du lecteur ArchipIDLE](/games/ArchipIDLE/player-settings) +1. Générez un fichier `.yaml` à partir de la [page des paramètres du lecteur ArchipIDLE](/games/ArchipIDLE/player-options) 2. Ouvrez le client ArchipIDLE dans votre navigateur Web en : - - Accédez au [Client ArchipIDLE](http://idle.multiworld.link) - - Téléchargez le client et exécutez-le localement à partir du - [Page des versions d'ArchipIDLE GitHub](https://github.com/ArchipelagoMW/archipidle/releases) + - Accédez au [Client ArchipIDLE](http://idle.multiworld.link) + - Téléchargez le client et exécutez-le localement à partir du [Page des versions d'ArchipIDLE GitHub](https://github.com/ArchipelagoMW/archipidle/releases) 3. Entrez l'adresse du serveur dans le champ `Server Address` et appuyez sur Entrée 4. Entrez votre nom d'emplacement lorsque vous y êtes invité. Il doit être le même que le `name` que vous avez saisi sur le page de configuration ci-dessus, ou le champ `name` dans votre fichier yaml. diff --git a/worlds/bk_sudoku/__init__.py b/worlds/bk_sudoku/__init__.py deleted file mode 100644 index 195339c38070..000000000000 --- a/worlds/bk_sudoku/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Dict - -from BaseClasses import Tutorial -from ..AutoWorld import WebWorld, World - - -class Bk_SudokuWebWorld(WebWorld): - options_page = "games/Sudoku/info/en" - theme = 'partyTime' - - setup_en = Tutorial( - tutorial_name='Setup Guide', - description='A guide to playing BK Sudoku', - language='English', - file_name='setup_en.md', - link='setup/en', - authors=['Jarno'] - ) - setup_de = Tutorial( - tutorial_name='Setup Anleitung', - description='Eine Anleitung um BK-Sudoku zu spielen', - language='Deutsch', - file_name='setup_de.md', - link='setup/de', - authors=['Held_der_Zeit'] - ) - - tutorials = [setup_en, setup_de] - - -class Bk_SudokuWorld(World): - """ - Play a little Sudoku while you're in BK mode to maybe get some useful hints - """ - game = "Sudoku" - web = Bk_SudokuWebWorld() - data_version = 1 - - item_name_to_id: Dict[str, int] = {} - location_name_to_id: Dict[str, int] = {} - - @classmethod - def stage_assert_generate(cls, multiworld): - raise Exception("BK Sudoku cannot be used for generating worlds, the client can instead connect to any other world") diff --git a/worlds/bk_sudoku/docs/de_Sudoku.md b/worlds/bk_sudoku/docs/de_Sudoku.md deleted file mode 100644 index abb50c5498d1..000000000000 --- a/worlds/bk_sudoku/docs/de_Sudoku.md +++ /dev/null @@ -1,21 +0,0 @@ -# BK-Sudoku - -## Was ist das für ein Spiel? - -BK-Sudoku ist kein typisches Archipelago-Spiel; stattdessen ist es ein gewöhnlicher Sudoku-Client der sich zu jeder -beliebigen Multiworld verbinden kann. Einmal verbunden kannst du ein 9x9 Sudoku spielen um einen zufälligen Hinweis -für dein Spiel zu erhalten. Es ist zwar langsam, aber es gibt dir etwas zu tun, solltest du mal nicht in der Lage sein -weitere „Checks†zu erreichen. -(Wer mag kann auch einfach so Sudoku spielen. Man muss nicht mit einer Multiworld verbunden sein, um ein Sudoku zu -spielen/generieren.) - -## Wie werden Hinweise freigeschalten? - -Nach dem Lösen eines Sudokus wird für den verbundenen Slot ein zufällig ausgewählter Hinweis freigegeben, für einen -Gegenstand der noch nicht gefunden wurde. - -## Wo ist die Seite für die Einstellungen? - -Es gibt keine Seite für die Einstellungen. Dieses Spiel kann nicht in deinen YAML-Dateien benutzt werden. Stattdessen -kann sich der Client mit einem beliebigen Slot einer Multiworld verbinden. In dem Client selbst kann aber der -Schwierigkeitsgrad des Sudoku ausgewählt werden. diff --git a/worlds/bk_sudoku/docs/en_Sudoku.md b/worlds/bk_sudoku/docs/en_Sudoku.md deleted file mode 100644 index d69514752460..000000000000 --- a/worlds/bk_sudoku/docs/en_Sudoku.md +++ /dev/null @@ -1,13 +0,0 @@ -# Bk Sudoku - -## What is this game? - -BK Sudoku is not a typical Archipelago game; instead, it is a generic Sudoku client that can connect to any existing multiworld. When connected, you can play Sudoku to unlock random hints for your game. While slow, it will give you something to do when you can't reach the checks in your game. - -## What hints are unlocked? - -After completing a Sudoku puzzle, the game will unlock 1 random hint for an unchecked location in the slot you are connected to. - -## Where is the settings page? - -There is no settings page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld. diff --git a/worlds/bk_sudoku/docs/setup_de.md b/worlds/bk_sudoku/docs/setup_de.md deleted file mode 100644 index 71a8e5f6245d..000000000000 --- a/worlds/bk_sudoku/docs/setup_de.md +++ /dev/null @@ -1,27 +0,0 @@ -# BK-Sudoku Setup Anleitung - -## Benötigte Software -- [Bk-Sudoku](https://github.com/Jarno458/sudoku) -- Windows 8 oder höher - -## Generelles Konzept - -Dies ist ein Client, der sich mit jedem beliebigen Slot einer Multiworld verbinden kann. Er lässt dich ein (9x9) Sudoku -spielen, um zufällige Hinweise für den verbundenen Slot freizuschalten. - -Aufgrund des Fakts, dass der Sudoku-Client sich zu jedem beliebigen Slot verbinden kann, ist es daher nicht notwendig -eine YAML für dieses Spiel zu generieren, da es keinen neuen Slot zur Multiworld-Session hinzufügt. - -## Installationsprozess - -Gehe zu der aktuellsten (latest) Veröffentlichung der [BK-Sudoku Releases](https://github.com/Jarno458/sudoku/releases). -Downloade und extrahiere/entpacke die `Bk_Sudoku.zip`-Datei. - -## Verbinden mit einer Multiworld - -1. Starte `Bk_Sudoku.exe` -2. Trage den Namen des Slots ein, mit dem du dich verbinden möchtest -3. Trage die Server-URL und den Port ein -4. Drücke auf Verbinden (connect) -5. Wähle deinen Schwierigkeitsgrad -6. Versuche das Sudoku zu Lösen diff --git a/worlds/bk_sudoku/docs/setup_en.md b/worlds/bk_sudoku/docs/setup_en.md deleted file mode 100644 index eda17e701bb8..000000000000 --- a/worlds/bk_sudoku/docs/setup_en.md +++ /dev/null @@ -1,24 +0,0 @@ -# BK Sudoku Setup Guide - -## Required Software -- [Bk Sudoku](https://github.com/Jarno458/sudoku) -- Windows 8 or higher - -## General Concept - -This is a client that can connect to any multiworld slot, and lets you play Sudoku to unlock random hints for that slot's locations. - -Due to the fact that the Sudoku client may connect to any slot, it is not necessary to generate a YAML for this game as it does not generate any new slots in the multiworld session. - -## Installation Procedures - -Go to the latest release on [BK Sudoku Releases](https://github.com/Jarno458/sudoku/releases). Download and extract the `Bk_Sudoku.zip` file. - -## Joining a MultiWorld Game - -1. Run Bk_Sudoku.exe -2. Enter the name of the slot you wish to connect to -3. Enter the server url & port number -4. Press connect -5. Choose difficulty -6. Try to solve the Sudoku diff --git a/worlds/blasphemous/__init__.py b/worlds/blasphemous/__init__.py index 9abcd81b20e1..a46fb55b9541 100644 --- a/worlds/blasphemous/__init__.py +++ b/worlds/blasphemous/__init__.py @@ -32,7 +32,6 @@ class BlasphemousWorld(World): game: str = "Blasphemous" web = BlasphemousWeb() - data_version = 2 item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)} location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)} diff --git a/worlds/blasphemous/docs/en_Blasphemous.md b/worlds/blasphemous/docs/en_Blasphemous.md index 1ff7f5a9035c..a99eea6fa4b8 100644 --- a/worlds/blasphemous/docs/en_Blasphemous.md +++ b/worlds/blasphemous/docs/en_Blasphemous.md @@ -1,8 +1,8 @@ # Blasphemous -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/bomb_rush_cyberfunk/Items.py b/worlds/bomb_rush_cyberfunk/Items.py new file mode 100644 index 000000000000..b8aa877205e3 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Items.py @@ -0,0 +1,553 @@ +from typing import TypedDict, List, Dict, Set +from enum import Enum + + +class BRCType(Enum): + Music = 0 + GraffitiM = 1 + GraffitiL = 2 + GraffitiXL = 3 + Skateboard = 4 + InlineSkates = 5 + BMX = 6 + Character = 7 + Outfit = 8 + REP = 9 + Camera = 10 + + +class ItemDict(TypedDict, total=False): + name: str + count: int + type: BRCType + + +base_id = 2308000 + + +item_table: List[ItemDict] = [ + # Music + {'name': "Music (GET ENUF)", + 'type': BRCType.Music}, + {'name': "Music (Chuckin Up)", + 'type': BRCType.Music}, + {'name': "Music (Spectres)", + 'type': BRCType.Music}, + {'name': "Music (You Can Say Hi)", + 'type': BRCType.Music}, + {'name': "Music (JACK DA FUNK)", + 'type': BRCType.Music}, + {'name': "Music (Feel The Funk (Computer Love))", + 'type': BRCType.Music}, + {'name': "Music (Big City Life)", + 'type': BRCType.Music}, + {'name': "Music (I Wanna Kno)", + 'type': BRCType.Music}, + {'name': "Music (Plume)", + 'type': BRCType.Music}, + {'name': "Music (Two Days Off)", + 'type': BRCType.Music}, + {'name': "Music (Scraped On The Way Out)", + 'type': BRCType.Music}, + {'name': "Music (Last Hoorah)", + 'type': BRCType.Music}, + {'name': "Music (State of Mind)", + 'type': BRCType.Music}, + {'name': "Music (AGUA)", + 'type': BRCType.Music}, + {'name': "Music (Condensed milk)", + 'type': BRCType.Music}, + {'name': "Music (Light Switch)", + 'type': BRCType.Music}, + {'name': "Music (Hair Dun Nails Dun)", + 'type': BRCType.Music}, + {'name': "Music (Precious Thing)", + 'type': BRCType.Music}, + {'name': "Music (Next To Me)", + 'type': BRCType.Music}, + {'name': "Music (Refuse)", + 'type': BRCType.Music}, + {'name': "Music (Iridium)", + 'type': BRCType.Music}, + {'name': "Music (Funk Express)", + 'type': BRCType.Music}, + {'name': "Music (In The Pocket)", + 'type': BRCType.Music}, + {'name': "Music (Bounce Upon A Time)", + 'type': BRCType.Music}, + {'name': "Music (hwbouths)", + 'type': BRCType.Music}, + {'name': "Music (Morning Glow)", + 'type': BRCType.Music}, + {'name': "Music (Chromebies)", + 'type': BRCType.Music}, + {'name': "Music (watchyaback!)", + 'type': BRCType.Music}, + {'name': "Music (Anime Break)", + 'type': BRCType.Music}, + {'name': "Music (DA PEOPLE)", + 'type': BRCType.Music}, + {'name': "Music (Trinitron)", + 'type': BRCType.Music}, + {'name': "Music (Operator)", + 'type': BRCType.Music}, + {'name': "Music (Sunshine Popping Mixtape)", + 'type': BRCType.Music}, + {'name': "Music (House Cats Mixtape)", + 'type': BRCType.Music}, + {'name': "Music (Breaking Machine Mixtape)", + 'type': BRCType.Music}, + {'name': "Music (Beastmode Hip Hop Mixtape)", + 'type': BRCType.Music}, + + # Graffiti + {'name': "Graffiti (M - OVERWHELMME)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - QUICK BING)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - BLOCKY)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - Flow)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Pora)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Teddy 4)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - BOMB BEATS)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - SPRAYTANICPANIC!)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - SHOGUN)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - EVIL DARUMA)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - TeleBinge)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - All Screws Loose)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - 0m33)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Vom'B)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Street classic)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Thick Candy)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - colorBOMB)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Zona Leste)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Stacked Symbols)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - Constellation Circle)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - B-boy Love)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - Devil 68)", + 'type': BRCType.GraffitiM}, + {'name': "Graffiti (M - pico pow)", + 'type': BRCType.GraffitiM}, + #{'name': "Graffiti (M - 8 MINUTES OF LEAN MEAN)", + # 'type': BRCType.GraffitiM}, + {'name': "Graffiti (L - WHOLE SIXER)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - INFINITY)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - Dynamo)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - VoodooBoy)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Fang It Up!)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - FREAKS)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Graffo Le Fou)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Lauder)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - SpawningSeason)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Moai Marathon)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Tius)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - KANI-BOZU)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - NOISY NINJA)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - Dinner On The Court)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Campaign Trail)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - skate or di3)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Jd Vila Formosa)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Messenger Mural)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - Solstice Script)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - RECORD.HEAD)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - Boom)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - wild rush)", + 'type': BRCType.GraffitiL}, + {'name': "Graffiti (L - buttercup)", + 'type': BRCType.GraffitiL}, + #{'name': "Graffiti (L - DIGITAL BLOCKBUSTER)", + # 'type': BRCType.GraffitiL}, + {'name': "Graffiti (XL - Gold Rush)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - WILD STRUXXA)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - VIBRATIONS)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - Bevel)", + # 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - SECOND SIGHT)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Bomb Croc)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - FATE)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Web Spitter)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - MOTORCYCLE GANG)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - CYBER TENGU)", + # 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - Don't Screw Around)", + # 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Deep Dive)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - MegaHood)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Gamex UPA ABL)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - BiGSHiNYBoMB)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Bomb Burner)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - Astrological Augury)", + # 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Pirate's Life 4 Me)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Bombing by FireMan)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - end 2 end)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - Raver Funk)", + 'type': BRCType.GraffitiXL}, + {'name': "Graffiti (XL - headphones on Helmet on)", + 'type': BRCType.GraffitiXL}, + #{'name': "Graffiti (XL - HIGH TECH WS)", + # 'type': BRCType.GraffitiXL}, + + # Skateboards + {'name': "Skateboard (Devon)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Terrence)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Maceo)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Lazer Accuracy)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Death Boogie)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Sylk)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Taiga)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Just Swell)", + 'type': BRCType.Skateboard}, + {'name': "Skateboard (Mantra)", + 'type': BRCType.Skateboard}, + + # Inline Skates + {'name': "Inline Skates (Glaciers)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Sweet Royale)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Strawberry Missiles)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Ice Cold Killers)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Red Industry)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Mech Adversary)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Orange Blasters)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (ck)", + 'type': BRCType.InlineSkates}, + {'name': "Inline Skates (Sharpshooters)", + 'type': BRCType.InlineSkates}, + + # BMX + {'name': "BMX (Mr. Taupe)", + 'type': BRCType.BMX}, + {'name': "BMX (Gum)", + 'type': BRCType.BMX}, + {'name': "BMX (Steel Wheeler)", + 'type': BRCType.BMX}, + {'name': "BMX (oyo)", + 'type': BRCType.BMX}, + {'name': "BMX (Rigid No.6)", + 'type': BRCType.BMX}, + {'name': "BMX (Ceremony)", + 'type': BRCType.BMX}, + {'name': "BMX (XXX)", + 'type': BRCType.BMX}, + {'name': "BMX (Terrazza)", + 'type': BRCType.BMX}, + {'name': "BMX (Dedication)", + 'type': BRCType.BMX}, + + # Outfits + {'name': "Outfit (Red - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Red - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Tryce - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Tryce - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Bel - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Bel - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Vinyl - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Vinyl - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Solace - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Solace - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Felix - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Felix - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rave - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rave - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Mesh - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Mesh - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Shine - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Shine - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rise - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Rise - Winter)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Coil - Autumn)", + 'type': BRCType.Outfit}, + {'name': "Outfit (Coil - Winter)", + 'type': BRCType.Outfit}, + + # Characters + {'name': "Tryce", + 'type': BRCType.Character}, + {'name': "Bel", + 'type': BRCType.Character}, + {'name': "Vinyl", + 'type': BRCType.Character}, + {'name': "Solace", + 'type': BRCType.Character}, + {'name': "Rave", + 'type': BRCType.Character}, + {'name': "Mesh", + 'type': BRCType.Character}, + {'name': "Shine", + 'type': BRCType.Character}, + {'name': "Rise", + 'type': BRCType.Character}, + {'name': "Coil", + 'type': BRCType.Character}, + {'name': "Frank", + 'type': BRCType.Character}, + {'name': "Rietveld", + 'type': BRCType.Character}, + {'name': "DJ Cyber", + 'type': BRCType.Character}, + {'name': "Eclipse", + 'type': BRCType.Character}, + {'name': "DOT.EXE", + 'type': BRCType.Character}, + {'name': "Devil Theory", + 'type': BRCType.Character}, + {'name': "Flesh Prince", + 'type': BRCType.Character}, + {'name': "Futurism", + 'type': BRCType.Character}, + {'name': "Oldhead", + 'type': BRCType.Character}, + + # REP + {'name': "8 REP", + 'type': BRCType.REP}, + {'name': "16 REP", + 'type': BRCType.REP}, + {'name': "24 REP", + 'type': BRCType.REP}, + {'name': "32 REP", + 'type': BRCType.REP}, + {'name': "48 REP", + 'type': BRCType.REP}, + + # App + {'name': "Camera App", + 'type': BRCType.Camera} +] + + +group_table: Dict[str, Set[str]] = { + "graffitim": {"Graffiti (M - OVERWHELMME)", + "Graffiti (M - QUICK BING)", + "Graffiti (M - BLOCKY)", + "Graffiti (M - Pora)", + "Graffiti (M - Teddy 4)", + "Graffiti (M - BOMB BEATS)", + "Graffiti (M - SPRAYTANICPANIC!)", + "Graffiti (M - SHOGUN)", + "Graffiti (M - TeleBinge)", + "Graffiti (M - 0m33)", + "Graffiti (M - Vom'B)", + "Graffiti (M - Street classic)", + "Graffiti (M - Thick Candy)", + "Graffiti (M - colorBOMB)", + "Graffiti (M - Zona Leste)", + "Graffiti (M - Stacked Symbols)", + "Graffiti (M - B-boy Love)", + "Graffiti (M - Devil 68)", + "Graffiti (M - pico pow)"}, + "graffitil": {"Graffiti (L - WHOLE SIXER)", + "Graffiti (L - INFINITY)", + "Graffiti (L - VoodooBoy)", + "Graffiti (L - Fang It Up!)", + "Graffiti (L - FREAKS)", + "Graffiti (L - Graffo Le Fou)", + "Graffiti (L - Lauder)", + "Graffiti (L - SpawningSeason)", + "Graffiti (L - Moai Marathon)", + "Graffiti (L - Tius)", + "Graffiti (L - NOISY NINJA)", + "Graffiti (L - Campaign Trail)", + "Graffiti (L - skate or di3)", + "Graffiti (L - Jd Vila Formosa)", + "Graffiti (L - Messenger Mural)", + "Graffiti (L - RECORD.HEAD)", + "Graffiti (L - Boom)", + "Graffiti (L - wild rush)", + "Graffiti (L - buttercup)"}, + "graffitixl": {"Graffiti (XL - Gold Rush)", + "Graffiti (XL - WILD STRUXXA)", + "Graffiti (XL - VIBRATIONS)", + "Graffiti (XL - SECOND SIGHT)", + "Graffiti (XL - Bomb Croc)", + "Graffiti (XL - FATE)", + "Graffiti (XL - Web Spitter)", + "Graffiti (XL - MOTORCYCLE GANG)", + "Graffiti (XL - Deep Dive)", + "Graffiti (XL - MegaHood)", + "Graffiti (XL - Gamex UPA ABL)", + "Graffiti (XL - BiGSHiNYBoMB)", + "Graffiti (XL - Bomb Burner)", + "Graffiti (XL - Pirate's Life 4 Me)", + "Graffiti (XL - Bombing by FireMan)", + "Graffiti (XL - end 2 end)", + "Graffiti (XL - Raver Funk)", + "Graffiti (XL - headphones on Helmet on)"}, + "skateboard": {"Skateboard (Devon)", + "Skateboard (Terrence)", + "Skateboard (Maceo)", + "Skateboard (Lazer Accuracy)", + "Skateboard (Death Boogie)", + "Skateboard (Sylk)", + "Skateboard (Taiga)", + "Skateboard (Just Swell)", + "Skateboard (Mantra)"}, + "inline skates": {"Inline Skates (Glaciers)", + "Inline Skates (Sweet Royale)", + "Inline Skates (Strawberry Missiles)", + "Inline Skates (Ice Cold Killers)", + "Inline Skates (Red Industry)", + "Inline Skates (Mech Adversary)", + "Inline Skates (Orange Blasters)", + "Inline Skates (ck)", + "Inline Skates (Sharpshooters)"}, + "skates": {"Inline Skates (Glaciers)", + "Inline Skates (Sweet Royale)", + "Inline Skates (Strawberry Missiles)", + "Inline Skates (Ice Cold Killers)", + "Inline Skates (Red Industry)", + "Inline Skates (Mech Adversary)", + "Inline Skates (Orange Blasters)", + "Inline Skates (ck)", + "Inline Skates (Sharpshooters)"}, + "inline": {"Inline Skates (Glaciers)", + "Inline Skates (Sweet Royale)", + "Inline Skates (Strawberry Missiles)", + "Inline Skates (Ice Cold Killers)", + "Inline Skates (Red Industry)", + "Inline Skates (Mech Adversary)", + "Inline Skates (Orange Blasters)", + "Inline Skates (ck)", + "Inline Skates (Sharpshooters)"}, + "bmx": {"BMX (Mr. Taupe)", + "BMX (Gum)", + "BMX (Steel Wheeler)", + "BMX (oyo)", + "BMX (Rigid No.6)", + "BMX (Ceremony)", + "BMX (XXX)", + "BMX (Terrazza)", + "BMX (Dedication)"}, + "bike": {"BMX (Mr. Taupe)", + "BMX (Gum)", + "BMX (Steel Wheeler)", + "BMX (oyo)", + "BMX (Rigid No.6)", + "BMX (Ceremony)", + "BMX (XXX)", + "BMX (Terrazza)", + "BMX (Dedication)"}, + "bicycle": {"BMX (Mr. Taupe)", + "BMX (Gum)", + "BMX (Steel Wheeler)", + "BMX (oyo)", + "BMX (Rigid No.6)", + "BMX (Ceremony)", + "BMX (XXX)", + "BMX (Terrazza)", + "BMX (Dedication)"}, + "characters": {"Tryce", + "Bel", + "Vinyl", + "Solace", + "Rave", + "Mesh", + "Shine", + "Rise", + "Coil", + "Frank", + "Rietveld", + "DJ Cyber", + "Eclipse", + "DOT.EXE", + "Devil Theory", + "Flesh Prince", + "Futurism", + "Oldhead"}, + "girl": {"Bel", + "Vinyl", + "Rave", + "Shine", + "Rise", + "Futurism"} +} \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/Locations.py b/worlds/bomb_rush_cyberfunk/Locations.py new file mode 100644 index 000000000000..7ea959019067 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Locations.py @@ -0,0 +1,785 @@ +from typing import TypedDict, List +from .Regions import Stages + + +class LocationDict(TypedDict): + name: str + stage: Stages + game_id: str + + +class EventDict(TypedDict): + name: str + stage: str + item: str + + +location_table: List[LocationDict] = [ + {'name': "Hideout: Half pipe CD", + 'stage': Stages.H, + 'game_id': "MusicTrack_CondensedMilk"}, + {'name': "Hideout: Garage tower CD", + 'stage': Stages.H, + 'game_id': "MusicTrack_MorningGlow"}, + {'name': "Hideout: Rooftop CD", + 'stage': Stages.H, + 'game_id': "MusicTrack_LightSwitch"}, + {'name': "Hideout: Under staircase graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_M1"}, + {'name': "Hideout: Secret area graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_L1"}, + {'name': "Hideout: Rear studio graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_XL1"}, + {'name': "Hideout: Corner ledge graffiti", + 'stage': Stages.H, + 'game_id': "UnlockGraffiti_grafTex_M2"}, + {'name': "Hideout: Upper platform skateboard", + 'stage': Stages.H, + 'game_id': "SkateboardDeck3"}, + {'name': "Hideout: BMX garage skateboard", + 'stage': Stages.H, + 'game_id': "SkateboardDeck2"}, + {'name': "Hideout: Unlock phone app", + 'stage': Stages.H, + 'game_id': "camera"}, + {'name': "Hideout: Vinyl joins the crew", + 'stage': Stages.H, + 'game_id': "girl1"}, + {'name': "Hideout: Solace joins the crew", + 'stage': Stages.H, + 'game_id': "dummy"}, + + {'name': "Versum Hill: Main street Robo Post graffiti", + 'stage': Stages.VH1, + 'game_id': "UnlockGraffiti_grafTex_L4"}, + {'name': "Versum Hill: Behind glass graffiti", + 'stage': Stages.VH1, + 'game_id': "UnlockGraffiti_grafTex_L3"}, + {'name': "Versum Hill: Office room graffiti", + 'stage': Stages.VH1, + 'game_id': "UnlockGraffiti_grafTex_M4"}, + {'name': "Versum Hill: Under bridge graffiti", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_XL4"}, + {'name': "Versum Hill: Train rail ledge skateboard", + 'stage': Stages.VH2, + 'game_id': "SkateboardDeck6"}, + {'name': "Versum Hill: Train station CD", + 'stage': Stages.VH2, + 'game_id': "MusicTrack_PreciousThing"}, + {'name': "Versum Hill: Billboard platform outfit", + 'stage': Stages.VH2, + 'game_id': "MetalheadOutfit3"}, + {'name': "Versum Hill: Hilltop Robo Post CD", + 'stage': Stages.VH2, + 'game_id': "MusicTrack_BounceUponATime"}, + {'name': "Versum Hill: Hill secret skateboard", + 'stage': Stages.VH2, + 'game_id': "SkateboardDeck7"}, + {'name': "Versum Hill: Rooftop CD", + 'stage': Stages.VH2, + 'game_id': "MusicTrack_NextToMe"}, + {'name': "Versum Hill: Wallrunning challenge reward", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_M3"}, + {'name': "Versum Hill: Manual challenge reward", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_L2"}, + {'name': "Versum Hill: Corner challenge reward", + 'stage': Stages.VH2, + 'game_id': "UnlockGraffiti_grafTex_M13"}, + {'name': "Versum Hill: Side street alley outfit", + 'stage': Stages.VH3, + 'game_id': "MetalheadOutfit4"}, + {'name': "Versum Hill: Side street secret skateboard", + 'stage': Stages.VH3, + 'game_id': "SkateboardDeck9"}, + {'name': "Versum Hill: Basketball court alley skateboard", + 'stage': Stages.VH4, + 'game_id': "SkateboardDeck5"}, + {'name': "Versum Hill: Basketball court Robo Post CD", + 'stage': Stages.VH4, + 'game_id': "MusicTrack_Operator"}, + {'name': "Versum Hill: Underground mall billboard graffiti", + 'stage': Stages.VHO, + 'game_id': "UnlockGraffiti_grafTex_XL3"}, + {'name': "Versum Hill: Underground mall vending machine skateboard", + 'stage': Stages.VHO, + 'game_id': "SkateboardDeck8"}, + {'name': "Versum Hill: BMX gate outfit", + 'stage': Stages.VH1, + 'game_id': "AngelOutfit3"}, + {'name': "Versum Hill: Glass floor skates", + 'stage': Stages.VH2, + 'game_id': "InlineSkates4"}, + {'name': "Versum Hill: Basketball court shortcut CD", + 'stage': Stages.VH4, + 'game_id': "MusicTrack_GetEnuf"}, + {'name': "Versum Hill: Rave joins the crew", + 'stage': Stages.VHO, + 'game_id': "angel"}, + {'name': "Versum Hill: Frank joins the crew", + 'stage': Stages.VH2, + 'game_id': "frank"}, + {'name': "Versum Hill: Rietveld joins the crew", + 'stage': Stages.VH4, + 'game_id': "jetpackBossPlayer"}, + {'name': "Versum Hill: Big Polo", + 'stage': Stages.VH1, + 'game_id': "PoloBuilding/Mascot_Polo_sit_big"}, + {'name': "Versum Hill: Trash Polo", + 'stage': Stages.VH1, + 'game_id': "TrashCluster (1)/Mascot_Polo_street"}, + {'name': "Versum Hill: Fruit stand Polo", + 'stage': Stages.VHO, + 'game_id': "SecretRoom/Mascot_Polo_street"}, + + {'name': "Millennium Square: Center ramp graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_L6"}, + {'name': "Millennium Square: Rooftop staircase graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_M8"}, + {'name': "Millennium Square: Toilet graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_XL6"}, + {'name': "Millennium Square: Trash graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_M5"}, + {'name': "Millennium Square: Center tower graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_M6"}, + {'name': "Millennium Square: Rooftop billboard graffiti", + 'stage': Stages.MS, + 'game_id': "UnlockGraffiti_grafTex_XL7"}, + {'name': "Millennium Square: Center Robo Post CD", + 'stage': Stages.MS, + 'game_id': "MusicTrack_FeelTheFunk"}, + {'name': "Millennium Square: Parking garage Robo Post CD", + 'stage': Stages.MS, + 'game_id': "MusicTrack_Plume"}, + {'name': "Millennium Square: Mall ledge outfit", + 'stage': Stages.MS, + 'game_id': "BlockGuyOutfit3"}, + {'name': "Millennium Square: Alley rooftop outfit", + 'stage': Stages.MS, + 'game_id': "BlockGuyOutfit4"}, + {'name': "Millennium Square: Alley staircase skateboard", + 'stage': Stages.MS, + 'game_id': "SkateboardDeck4"}, + {'name': "Millennium Square: Secret painting skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates2"}, + {'name': "Millennium Square: Vending machine skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates3"}, + {'name': "Millennium Square: Walkway roof skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates5"}, + {'name': "Millennium Square: Alley ledge skates", + 'stage': Stages.MS, + 'game_id': "InlineSkates6"}, + {'name': "Millennium Square: DJ Cyber joins the crew", + 'stage': Stages.MS, + 'game_id': "dj"}, + {'name': "Millennium Square: Half pipe Polo", + 'stage': Stages.MS, + 'game_id': "propsSecretArea/Mascot_Polo_street"}, + + {'name': "Brink Terminal: Upside grind challenge reward", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_M10"}, + {'name': "Brink Terminal: Manual challenge reward", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_L8"}, + {'name': "Brink Terminal: Score challenge reward", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_M12"}, + {'name': "Brink Terminal: Under square ledge graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_L9"}, + {'name': "Brink Terminal: Bus graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_XL9"}, + {'name': "Brink Terminal: Under square Robo Post graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_M9"}, + {'name': "Brink Terminal: BMX gate graffiti", + 'stage': Stages.BT1, + 'game_id': "UnlockGraffiti_grafTex_L7"}, + {'name': "Brink Terminal: Square tower CD", + 'stage': Stages.BT1, + 'game_id': "MusicTrack_Chapter1Mixtape"}, + {'name': "Brink Terminal: Trash CD", + 'stage': Stages.BT1, + 'game_id': "MusicTrack_HairDunNailsDun"}, + {'name': "Brink Terminal: Shop roof outfit", + 'stage': Stages.BT1, + 'game_id': "AngelOutfit4"}, + {'name': "Brink Terminal: Underground glass skates", + 'stage': Stages.BTO1, + 'game_id': "InlineSkates8"}, + {'name': "Brink Terminal: Glass roof skates", + 'stage': Stages.BT1, + 'game_id': "InlineSkates10"}, + {'name': "Brink Terminal: Mesh's skateboard", + 'stage': Stages.BTO2, + 'game_id': "SkateboardDeck10"}, # double check this one + {'name': "Brink Terminal: Underground ramp skates", + 'stage': Stages.BTO1, + 'game_id': "InlineSkates7"}, + {'name': "Brink Terminal: Rooftop halfpipe graffiti", + 'stage': Stages.BT3, + 'game_id': "UnlockGraffiti_grafTex_M11"}, + {'name': "Brink Terminal: Wire grind CD", + 'stage': Stages.BT2, + 'game_id': "MusicTrack_Watchyaback"}, + {'name': "Brink Terminal: Rooftop glass CD", + 'stage': Stages.BT3, + 'game_id': "MusicTrack_Refuse"}, + {'name': "Brink Terminal: Tower core outfit", + 'stage': Stages.BT3, + 'game_id': "SpacegirlOutfit4"}, + {'name': "Brink Terminal: High rooftop outfit", + 'stage': Stages.BT3, + 'game_id': "WideKidOutfit3"}, + {'name': "Brink Terminal: Ocean platform CD", + 'stage': Stages.BTO2, + 'game_id': "MusicTrack_ScrapedOnTheWayOut"}, + {'name': "Brink Terminal: End of dock CD", + 'stage': Stages.BTO2, + 'game_id': "MusicTrack_Hwbouths"}, + {'name': "Brink Terminal: Dock Robo Post outfit", + 'stage': Stages.BTO2, + 'game_id': "WideKidOutfit4"}, + {'name': "Brink Terminal: Control room skates", + 'stage': Stages.BTO2, + 'game_id': "InlineSkates9"}, + {'name': "Brink Terminal: Mesh joins the crew", + 'stage': Stages.BTO2, + 'game_id': "wideKid"}, + {'name': "Brink Terminal: Eclipse joins the crew", + 'stage': Stages.BT1, + 'game_id': "medusa"}, + {'name': "Brink Terminal: Behind glass Polo", + 'stage': Stages.BT1, + 'game_id': "KingFood (Bear)/Mascot_Polo_street"}, + + {'name': "Millennium Mall: Warehouse pallet graffiti", + 'stage': Stages.MM1, + 'game_id': "UnlockGraffiti_grafTex_L5"}, + {'name': "Millennium Mall: Wall alcove graffiti", + 'stage': Stages.MM1, + 'game_id': "UnlockGraffiti_grafTex_XL10"}, + {'name': "Millennium Mall: Maintenance shaft CD", + 'stage': Stages.MM1, + 'game_id': "MusicTrack_MissingBreak"}, + {'name': "Millennium Mall: Glass cylinder CD", + 'stage': Stages.MM1, + 'game_id': "MusicTrack_DAPEOPLE"}, + {'name': "Millennium Mall: Lower Robo Post outfit", + 'stage': Stages.MM1, + 'game_id': "SpacegirlOutfit3"}, + {'name': "Millennium Mall: Atrium vending machine graffiti", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_M15"}, + {'name': "Millennium Mall: Trick challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_XL8"}, + {'name': "Millennium Mall: Slide challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_L10"}, + {'name': "Millennium Mall: Fish challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_L12"}, + {'name': "Millennium Mall: Score challenge reward", + 'stage': Stages.MM2, + 'game_id': "UnlockGraffiti_grafTex_XL11"}, + {'name': "Millennium Mall: Atrium top floor Robo Post CD", + 'stage': Stages.MM2, + 'game_id': "MusicTrack_TwoDaysOff"}, + {'name': "Millennium Mall: Atrium top floor floating CD", + 'stage': Stages.MM2, + 'game_id': "MusicTrack_Spectres"}, + {'name': "Millennium Mall: Atrium top floor BMX", + 'stage': Stages.MM2, + 'game_id': "BMXBike2"}, + {'name': "Millennium Mall: Theater entrance BMX", + 'stage': Stages.MM2, + 'game_id': "BMXBike3"}, + {'name': "Millennium Mall: Atrium BMX gate BMX", + 'stage': Stages.MM2, + 'game_id': "BMXBike5"}, + {'name': "Millennium Mall: Upside down rail outfit", + 'stage': Stages.MM2, + 'game_id': "BunGirlOutfit3"}, + {'name': "Millennium Mall: Theater stage corner graffiti", + 'stage': Stages.MM3, + 'game_id': "UnlockGraffiti_grafTex_L15"}, + {'name': "Millennium Mall: Theater hanging billboards graffiti", + 'stage': Stages.MM3, + 'game_id': "UnlockGraffiti_grafTex_XL15"}, + {'name': "Millennium Mall: Theater garage graffiti", + 'stage': Stages.MM3, + 'game_id': "UnlockGraffiti_grafTex_M16"}, + {'name': "Millennium Mall: Theater maintenance CD", + 'stage': Stages.MM3, + 'game_id': "MusicTrack_WannaKno"}, + {'name': "Millennium Mall: Race track Robo Post CD", + 'stage': Stages.MMO2, + 'game_id': "MusicTrack_StateOfMind"}, + {'name': "Millennium Mall: Hanging lights CD", + 'stage': Stages.MMO1, + 'game_id': "MusicTrack_Chapter2Mixtape"}, + {'name': "Millennium Mall: Shine joins the crew", + 'stage': Stages.MM3, + 'game_id': "bunGirl"}, + {'name': "Millennium Mall: DOT.EXE joins the crew", + 'stage': Stages.MM2, + 'game_id': "eightBall"}, + + {'name': "Pyramid Island: Lower rooftop graffiti", + 'stage': Stages.PI1, + 'game_id': "UnlockGraffiti_grafTex_L18"}, + {'name': "Pyramid Island: Polo graffiti", + 'stage': Stages.PI1, + 'game_id': "UnlockGraffiti_grafTex_L16"}, + {'name': "Pyramid Island: Above entrance graffiti", + 'stage': Stages.PI1, + 'game_id': "UnlockGraffiti_grafTex_XL16"}, + {'name': "Pyramid Island: BMX gate BMX", + 'stage': Stages.PI1, + 'game_id': "BMXBike6"}, + {'name': "Pyramid Island: Quarter pipe rooftop graffiti", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_M17"}, + {'name': "Pyramid Island: Supply port Robo Post CD", + 'stage': Stages.PI2, + 'game_id': "MusicTrack_Trinitron"}, + {'name': "Pyramid Island: Above gate ledge CD", + 'stage': Stages.PI2, + 'game_id': "MusicTrack_Agua"}, + {'name': "Pyramid Island: Smoke hole BMX", + 'stage': Stages.PI2, + 'game_id': "BMXBike8"}, + {'name': "Pyramid Island: Above gate rail outfit", + 'stage': Stages.PI2, + 'game_id': "VinylOutfit3"}, + {'name': "Pyramid Island: Rail loop outfit", + 'stage': Stages.PI2, + 'game_id': "BunGirlOutfit4"}, + {'name': "Pyramid Island: Score challenge reward", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_XL2"}, + {'name': "Pyramid Island: Score challenge 2 reward", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_L13"}, + {'name': "Pyramid Island: Quarter pipe challenge reward", + 'stage': Stages.PI2, + 'game_id': "UnlockGraffiti_grafTex_XL12"}, + {'name': "Pyramid Island: Wind turbines CD", + 'stage': Stages.PI3, + 'game_id': "MusicTrack_YouCanSayHi"}, + {'name': "Pyramid Island: Shortcut glass CD", + 'stage': Stages.PI3, + 'game_id': "MusicTrack_Chromebies"}, + {'name': "Pyramid Island: Turret jump CD", + 'stage': Stages.PI3, + 'game_id': "MusicTrack_ChuckinUp"}, + {'name': "Pyramid Island: Helipad BMX", + 'stage': Stages.PI3, + 'game_id': "BMXBike7"}, + {'name': "Pyramid Island: Pipe outfit", + 'stage': Stages.PI3, + 'game_id': "PufferGirlOutfit3"}, + {'name': "Pyramid Island: Trash outfit", + 'stage': Stages.PI3, + 'game_id': "PufferGirlOutfit4"}, + {'name': "Pyramid Island: Pyramid top CD", + 'stage': Stages.PI4, + 'game_id': "MusicTrack_BigCityLife"}, + {'name': "Pyramid Island: Pyramid top Robo Post CD", + 'stage': Stages.PI4, + 'game_id': "MusicTrack_Chapter3Mixtape"}, + {'name': "Pyramid Island: Maze outfit", + 'stage': Stages.PIO, + 'game_id': "VinylOutfit4"}, + {'name': "Pyramid Island: Rise joins the crew", + 'stage': Stages.PI4, + 'game_id': "pufferGirl"}, + {'name': "Pyramid Island: Devil Theory joins the crew", + 'stage': Stages.PI3, + 'game_id': "boarder"}, + {'name': "Pyramid Island: Polo pile 1", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave"}, + {'name': "Pyramid Island: Polo pile 2", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (1)"}, + {'name': "Pyramid Island: Polo pile 3", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (2)"}, + {'name': "Pyramid Island: Polo pile 4", + 'stage': Stages.PI1, + 'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (3)"}, + {'name': "Pyramid Island: Maze glass Polo", + 'stage': Stages.PIO, + 'game_id': "Start/Mascot_Polo_sit_big (1)"}, + {'name': "Pyramid Island: Maze classroom Polo", + 'stage': Stages.PIO, + 'game_id': "PeteRoom/Mascot_Polo_sit_big_wave (1)"}, + {'name': "Pyramid Island: Maze vent Polo", + 'stage': Stages.PIO, + 'game_id': "CheckerRoom/Mascot_Polo_street"}, + {'name': "Pyramid Island: Big maze Polo", + 'stage': Stages.PIO, + 'game_id': "YellowPoloRoom/Mascot_Polo_sit_big"}, + {'name': "Pyramid Island: Maze desk Polo", + 'stage': Stages.PIO, + 'game_id': "PoloRoom/Mascot_Polo_sit_big"}, + {'name': "Pyramid Island: Maze forklift Polo", + 'stage': Stages.PIO, + 'game_id': "ForkliftRoom/Mascot_Polo_sit_big_wave"}, + + {'name': "Mataan: Robo Post graffiti", + 'stage': Stages.MA1, + 'game_id': "UnlockGraffiti_grafTex_XL17"}, + {'name': "Mataan: Secret ledge BMX", + 'stage': Stages.MA1, + 'game_id': "BMXBike9"}, + {'name': "Mataan: Highway rooftop BMX", + 'stage': Stages.MA1, + 'game_id': "BMXBike10"}, + {'name': "Mataan: Trash CD", + 'stage': Stages.MA2, + 'game_id': "MusicTrack_JackDaFunk"}, + {'name': "Mataan: Half pipe CD", + 'stage': Stages.MA2, + 'game_id': "MusicTrack_FunkExpress"}, + {'name': "Mataan: Across bull horns graffiti", + 'stage': Stages.MA2, + 'game_id': "UnlockGraffiti_grafTex_L17"}, + {'name': "Mataan: Small rooftop graffiti", + 'stage': Stages.MA2, + 'game_id': "UnlockGraffiti_grafTex_M18"}, + {'name': "Mataan: Trash graffiti", + 'stage': Stages.MA2, + 'game_id': "UnlockGraffiti_grafTex_XL5"}, + {'name': "Mataan: Deep city Robo Post CD", + 'stage': Stages.MA3, + 'game_id': "MusicTrack_LastHoorah"}, + {'name': "Mataan: Deep city tower CD", + 'stage': Stages.MA3, + 'game_id': "MusicTrack_Chapter4Mixtape"}, + {'name': "Mataan: Race challenge reward", + 'stage': Stages.MA3, + 'game_id': "UnlockGraffiti_grafTex_M14"}, + {'name': "Mataan: Wallrunning challenge reward", + 'stage': Stages.MA3, + 'game_id': "UnlockGraffiti_grafTex_L14"}, + {'name': "Mataan: Score challenge reward", + 'stage': Stages.MA3, + 'game_id': "UnlockGraffiti_grafTex_XL13"}, + {'name': "Mataan: Deep city vent jump BMX", + 'stage': Stages.MA3, + 'game_id': "BMXBike4"}, + {'name': "Mataan: Deep city side wires outfit", + 'stage': Stages.MA3, + 'game_id': "DummyOutfit3"}, + {'name': "Mataan: Deep city center island outfit", + 'stage': Stages.MA3, + 'game_id': "DummyOutfit4"}, + {'name': "Mataan: Red light rail graffiti", + 'stage': Stages.MAO, + 'game_id': "UnlockGraffiti_grafTex_XL18"}, + {'name': "Mataan: Red light side alley outfit", + 'stage': Stages.MAO, + 'game_id': "RingDudeOutfit3"}, + {'name': "Mataan: Statue hand outfit", + 'stage': Stages.MA4, + 'game_id': "RingDudeOutfit4"}, + {'name': "Mataan: Crane CD", + 'stage': Stages.MA5, + 'game_id': "MusicTrack_InThePocket"}, + {'name': "Mataan: Elephant tower glass outfit", + 'stage': Stages.MA5, + 'game_id': "LegendFaceOutfit3"}, + {'name': "Mataan: Helipad outfit", + 'stage': Stages.MA5, + 'game_id': "LegendFaceOutfit4"}, + {'name': "Mataan: Vending machine CD", + 'stage': Stages.MA5, + 'game_id': "MusicTrack_Iridium"}, + {'name': "Mataan: Coil joins the crew", + 'stage': Stages.MA5, + 'game_id': "ringdude"}, + {'name': "Mataan: Flesh Prince joins the crew", + 'stage': Stages.MA5, + 'game_id': "prince"}, + {'name': "Mataan: Futurism joins the crew", + 'stage': Stages.MA5, + 'game_id': "futureGirl"}, + {'name': "Mataan: Trash Polo", + 'stage': Stages.MA2, + 'game_id': "PropsMallArea/Mascot_Polo_street"}, + {'name': "Mataan: Shopping Polo", + 'stage': Stages.MA5, + 'game_id': "propsMarket/Mascot_Polo_street"}, + + {'name': "Tagged 5 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf5"}, + {'name': "Tagged 10 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf10"}, + {'name': "Tagged 15 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf15"}, + {'name': "Tagged 20 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf20"}, + {'name': "Tagged 25 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf25"}, + {'name': "Tagged 30 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf30"}, + {'name': "Tagged 35 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf35"}, + {'name': "Tagged 40 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf40"}, + {'name': "Tagged 45 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf45"}, + {'name': "Tagged 50 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf50"}, + {'name': "Tagged 55 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf55"}, + {'name': "Tagged 60 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf60"}, + {'name': "Tagged 65 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf65"}, + {'name': "Tagged 70 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf70"}, + {'name': "Tagged 75 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf75"}, + {'name': "Tagged 80 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf80"}, + {'name': "Tagged 85 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf85"}, + {'name': "Tagged 90 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf90"}, + {'name': "Tagged 95 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf95"}, + {'name': "Tagged 100 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf100"}, + {'name': "Tagged 105 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf105"}, + {'name': "Tagged 110 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf110"}, + {'name': "Tagged 115 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf115"}, + {'name': "Tagged 120 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf120"}, + {'name': "Tagged 125 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf125"}, + {'name': "Tagged 130 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf130"}, + {'name': "Tagged 135 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf135"}, + {'name': "Tagged 140 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf140"}, + {'name': "Tagged 145 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf145"}, + {'name': "Tagged 150 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf150"}, + {'name': "Tagged 155 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf155"}, + {'name': "Tagged 160 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf160"}, + {'name': "Tagged 165 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf165"}, + {'name': "Tagged 170 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf170"}, + {'name': "Tagged 175 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf175"}, + {'name': "Tagged 180 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf180"}, + {'name': "Tagged 185 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf185"}, + {'name': "Tagged 190 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf190"}, + {'name': "Tagged 195 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf195"}, + {'name': "Tagged 200 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf200"}, + {'name': "Tagged 205 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf205"}, + {'name': "Tagged 210 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf210"}, + {'name': "Tagged 215 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf215"}, + {'name': "Tagged 220 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf220"}, + {'name': "Tagged 225 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf225"}, + {'name': "Tagged 230 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf230"}, + {'name': "Tagged 235 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf235"}, + {'name': "Tagged 240 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf240"}, + {'name': "Tagged 245 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf245"}, + {'name': "Tagged 250 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf250"}, + {'name': "Tagged 255 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf255"}, + {'name': "Tagged 260 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf260"}, + {'name': "Tagged 265 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf265"}, + {'name': "Tagged 270 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf270"}, + {'name': "Tagged 275 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf275"}, + {'name': "Tagged 280 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf280"}, + {'name': "Tagged 285 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf285"}, + {'name': "Tagged 290 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf290"}, + {'name': "Tagged 295 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf295"}, + {'name': "Tagged 300 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf300"}, + {'name': "Tagged 305 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf305"}, + {'name': "Tagged 310 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf310"}, + {'name': "Tagged 315 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf315"}, + {'name': "Tagged 320 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf320"}, + {'name': "Tagged 325 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf325"}, + {'name': "Tagged 330 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf330"}, + {'name': "Tagged 335 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf335"}, + {'name': "Tagged 340 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf340"}, + {'name': "Tagged 345 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf345"}, + {'name': "Tagged 350 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf350"}, + {'name': "Tagged 355 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf355"}, + {'name': "Tagged 360 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf360"}, + {'name': "Tagged 365 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf365"}, + {'name': "Tagged 370 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf370"}, + {'name': "Tagged 375 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf375"}, + {'name': "Tagged 380 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf380"}, + {'name': "Tagged 385 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf385"}, + {'name': "Tagged 389 Graffiti Spots", + 'stage': Stages.Misc, + 'game_id': "graf389"}, +] + + +event_table: List[EventDict] = [ + {'name': "Versum Hill: Complete Chapter 1", + 'stage': Stages.VH4, + 'item': "Chapter Completed"}, + {'name': "Brink Terminal: Complete Chapter 2", + 'stage': Stages.BT3, + 'item': "Chapter Completed"}, + {'name': "Millennium Mall: Complete Chapter 3", + 'stage': Stages.MM3, + 'item': "Chapter Completed"}, + {'name': "Pyramid Island: Complete Chapter 4", + 'stage': Stages.PI3, + 'item': "Chapter Completed"}, + {'name': "Defeat Faux", + 'stage': Stages.MA5, + 'item': "Victory"}, +] \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/Options.py b/worlds/bomb_rush_cyberfunk/Options.py new file mode 100644 index 000000000000..80831d064526 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Options.py @@ -0,0 +1,200 @@ +from dataclasses import dataclass +from Options import Choice, Toggle, DefaultOnToggle, Range, DeathLink, PerGameCommonOptions +import typing + +if typing.TYPE_CHECKING: + from random import Random +else: + Random = typing.Any + + +class Logic(Choice): + """ + Choose the logic used by the randomizer. + """ + display_name = "Logic" + option_glitchless = 0 + option_glitched = 1 + default = 0 + + +class SkipIntro(DefaultOnToggle): + """ + Skips escaping the police station. + + Graffiti spots tagged during the intro will not unlock items. + """ + display_name = "Skip Intro" + + +class SkipDreams(Toggle): + """ + Skips the dream sequences at the end of each chapter. + + This can be changed later in the options menu inside the Archipelago phone app. + """ + display_name = "Skip Dreams" + + +class SkipHands(Toggle): + """ + Skips spraying the lion statue hands after the dream in Chapter 5. + """ + display_name = "Skip Statue Hands" + + +class TotalRep(Range): + """ + Change the total amount of REP in your world. + + At least 960 REP is needed to finish the game. + + Will be rounded to the nearest number divisible by 8. + """ + display_name = "Total REP" + range_start = 1000 + range_end = 2000 + default = 1400 + + def round_to_nearest_step(self): + rem: int = self.value % 8 + if rem >= 5: + self.value = self.value - rem + 8 + else: + self.value = self.value - rem + + def get_rep_item_counts(self, random_source: Random, location_count: int) -> typing.List[int]: + def increment_item(item: int) -> int: + if item >= 32: + item = 48 + else: + item += 8 + return item + + items = [8]*location_count + while sum(items) < self.value: + index = random_source.randint(0, location_count-1) + while items[index] >= 48: + index = random_source.randint(0, location_count-1) + items[index] = increment_item(items[index]) + + while sum(items) > self.value: + index = random_source.randint(0, location_count-1) + while not (items[index] == 16 or items[index] == 24 or items[index] == 32): + index = random_source.randint(0, location_count-1) + items[index] -= 8 + + return [items.count(8), items.count(16), items.count(24), items.count(32), items.count(48)] + + +class EndingREP(Toggle): + """ + Changes the final boss to require 1000 REP instead of 960 REP to start. + """ + display_name = "Extra REP Required" + + +class StartStyle(Choice): + """ + Choose which movestyle to start with. + """ + display_name = "Starting Movestyle" + option_skateboard = 2 + option_inline_skates = 3 + option_bmx = 1 + default = 2 + + +class LimitedGraffiti(Toggle): + """ + Each graffiti design can only be used a limited number of times before being removed from your inventory. + + In some cases, such as completing a dream, using graffiti to defeat enemies, or spraying over your own graffiti, uses will not be counted. + + If enabled, doing graffiti is disabled during crew battles, to prevent softlocking. + """ + display_name = "Limited Graffiti" + + +class SGraffiti(Choice): + """ + Choose if small graffiti should be separate, meaning that you will need to switch characters every time you run out, or combined, meaning that unlocking new characters will add 5 uses that any character can use. + + Has no effect if Limited Graffiti is disabled. + """ + display_name = "Small Graffiti Uses" + option_separate = 0 + option_combined = 1 + default = 0 + + +class JunkPhotos(Toggle): + """ + Skip taking pictures of Polo for items. + """ + display_name = "Skip Polo Photos" + + +class DontSavePhotos(Toggle): + """ + Photos taken with the Camera app will not be saved. + + This can be changed later in the options menu inside the Archipelago phone app. + """ + display_name = "Don't Save Photos" + + +class ScoreDifficulty(Choice): + """ + Alters the score required to win score challenges and crew battles. + + This can be changed later in the options menu inside the Archipelago phone app. + """ + display_name = "Score Difficulty" + option_normal = 0 + option_medium = 1 + option_hard = 2 + option_very_hard = 3 + option_extreme = 4 + default = 0 + + +class DamageMultiplier(Range): + """ + Multiplies all damage received. + + At 3x, most damage will OHKO the player, including falling into pits. + At 6x, all damage will OHKO the player. + + This can be changed later in the options menu inside the Archipelago phone app. + """ + display_name = "Damage Multiplier" + range_start = 1 + range_end = 6 + default = 1 + + +class BRCDeathLink(DeathLink): + """ + When you die, everyone dies. The reverse is also true. + + This can be changed later in the options menu inside the Archipelago phone app. + """ + + +@dataclass +class BombRushCyberfunkOptions(PerGameCommonOptions): + logic: Logic + skip_intro: SkipIntro + skip_dreams: SkipDreams + skip_statue_hands: SkipHands + total_rep: TotalRep + extra_rep_required: EndingREP + starting_movestyle: StartStyle + limited_graffiti: LimitedGraffiti + small_graffiti_uses: SGraffiti + skip_polo_photos: JunkPhotos + dont_save_photos: DontSavePhotos + score_difficulty: ScoreDifficulty + damage_multiplier: DamageMultiplier + death_link: BRCDeathLink diff --git a/worlds/bomb_rush_cyberfunk/Regions.py b/worlds/bomb_rush_cyberfunk/Regions.py new file mode 100644 index 000000000000..206ae4ea5d6b --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Regions.py @@ -0,0 +1,103 @@ +from typing import Dict + + +class Stages: + Misc = "Misc" + H = "Hideout" + VH1 = "Versum Hill" + VH2 = "Versum Hill - After Roadblock" + VHO = "Versum Hill - Underground Mall" + VH3 = "Versum Hill - Side Street" + VH4 = "Versum Hill - Basketball Court" + MS = "Millennium Square" + BT1 = "Brink Terminal" + BTO1 = "Brink Terminal - Underground" + BTO2 = "Brink Terminal - Dock" + BT2 = "Brink Terminal - Planet Plaza" + BT3 = "Brink Terminal - Tower" + MM1 = "Millennium Mall" + MMO1 = "Millennium Mall - Hanging Lights" + MM2 = "Millennium Mall - Atrium" + MMO2 = "Millennium Mall - Race Track" + MM3 = "Millennium Mall - Theater" + PI1 = "Pyramid Island - Base" + PI2 = "Pyramid Island - After Gate" + PIO = "Pyramid Island - Maze" + PI3 = "Pyramid Island - Upper Areas" + PI4 = "Pyramid Island - Top" + MA1 = "Mataan - Streets" + MA2 = "Mataan - After Smoke Wall" + MA3 = "Mataan - Deep City" + MAO = "Mataan - Red Light District" + MA4 = "Mataan - Lion Statue" + MA5 = "Mataan - Skyscrapers" + + +region_exits: Dict[str, str] = { + Stages.Misc: [Stages.H], + Stages.H: [Stages.Misc, + Stages.VH1, + Stages.MS, + Stages.MA1], + Stages.VH1: [Stages.H, + Stages.VH2], + Stages.VH2: [Stages.H, + Stages.VH1, + Stages.MS, + Stages.VHO, + Stages.VH3, + Stages.VH4], + Stages.VHO: [Stages.VH2], + Stages.VH3: [Stages.VH2], + Stages.VH4: [Stages.VH2, + Stages.VH1], + Stages.MS: [Stages.VH2, + Stages.BT1, + Stages.MM1, + Stages.PI1, + Stages.MA1], + Stages.BT1: [Stages.MS, + Stages.BTO1, + Stages.BTO2, + Stages.BT2], + Stages.BTO1: [Stages.BT1], + Stages.BTO2: [Stages.BT1], + Stages.BT2: [Stages.BT1, + Stages.BT3], + Stages.BT3: [Stages.BT1, + Stages.BT2], + Stages.MM1: [Stages.MS, + Stages.MMO1, + Stages.MM2], + Stages.MMO1: [Stages.MM1], + Stages.MM2: [Stages.MM1, + Stages.MMO2, + Stages.MM3], + Stages.MMO2: [Stages.MM2], + Stages.MM3: [Stages.MM2, + Stages.MM1], + Stages.PI1: [Stages.MS, + Stages.PI2], + Stages.PI2: [Stages.PI1, + Stages.PIO, + Stages.PI3], + Stages.PIO: [Stages.PI2], + Stages.PI3: [Stages.PI1, + Stages.PI2, + Stages.PI4], + Stages.PI4: [Stages.PI1, + Stages.PI2, + Stages.PI3], + Stages.MA1: [Stages.H, + Stages.MS, + Stages.MA2], + Stages.MA2: [Stages.MA1, + Stages.MA3], + Stages.MA3: [Stages.MA2, + Stages.MAO, + Stages.MA4], + Stages.MAO: [Stages.MA3], + Stages.MA4: [Stages.MA3, + Stages.MA5], + Stages.MA5: [Stages.MA1] +} diff --git a/worlds/bomb_rush_cyberfunk/Rules.py b/worlds/bomb_rush_cyberfunk/Rules.py new file mode 100644 index 000000000000..8f283ee613b7 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/Rules.py @@ -0,0 +1,1041 @@ +from worlds.generic.Rules import set_rule, add_rule +from BaseClasses import CollectionState +from typing import Dict +from .Regions import Stages + + +def graffitiM(state: CollectionState, player: int, limit: bool, spots: int) -> bool: + return state.count_group_unique("graffitim", player) * 7 >= spots if limit \ + else state.has_group("graffitim", player) + + +def graffitiL(state: CollectionState, player: int, limit: bool, spots: int) -> bool: + return state.count_group_unique("graffitil", player) * 6 >= spots if limit \ + else state.has_group("graffitil", player) + + +def graffitiXL(state: CollectionState, player: int, limit: bool, spots: int) -> bool: + return state.count_group_unique("graffitixl", player) * 4 >= spots if limit \ + else state.has_group("graffitixl", player) + + +def skateboard(state: CollectionState, player: int, movestyle: int) -> bool: + return True if movestyle == 2 else state.has_group("skateboard", player) + + +def inline_skates(state: CollectionState, player: int, movestyle: int) -> bool: + return True if movestyle == 3 else state.has_group("skates", player) + + +def bmx(state: CollectionState, player: int, movestyle: int) -> bool: + return True if movestyle == 1 else state.has_group("bmx", player) + + +def camera(state: CollectionState, player: int) -> bool: + return state.has("Camera App", player) + + +def is_girl(state: CollectionState, player: int) -> bool: + return state.has_group("girl", player) + + +def current_chapter(state: CollectionState, player: int, chapter: int) -> bool: + return state.has("Chapter Completed", player, chapter-1) + + +def versum_hill_entrance(state: CollectionState, player: int) -> bool: + return rep(state, player, 20) + + +def versum_hill_ch1_roadblock(state: CollectionState, player: int, limit: bool) -> bool: + return graffitiL(state, player, limit, 10) + + +def versum_hill_challenge1(state: CollectionState, player: int) -> bool: + return rep(state, player, 50) + + +def versum_hill_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 58) + + +def versum_hill_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 65) + + +def versum_hill_all_challenges(state: CollectionState, player: int) -> bool: + return versum_hill_challenge3(state, player) + + +def versum_hill_basketball_court(state: CollectionState, player: int) -> bool: + return rep(state, player, 90) + + +def versum_hill_oldhead(state: CollectionState, player: int) -> bool: + return rep(state, player, 120) + + +def versum_hill_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 90) + and graffitiM(state, player, limit, 98) + ) + else: + return ( + rep(state, player, 90) + and graffitiM(state, player, limit, 27) + ) + + +def versum_hill_rietveld(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + current_chapter(state, player, 2) + and graffitiM(state, player, limit, 114) + ) + else: + return ( + current_chapter(state, player, 2) + and graffitiM(state, player, limit, 67) + ) + + +def versum_hill_rave(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + if current_chapter(state, player, 4): + return ( + graffitiL(state, player, limit, 90) + and graffitiXL(state, player, limit, 51) + ) + elif current_chapter(state, player, 3): + return ( + graffitiL(state, player, limit, 89) + and graffitiXL(state, player, limit, 51) + ) + else: + return ( + graffitiL(state, player, limit, 85) + and graffitiXL(state, player, limit, 48) + ) + else: + return ( + graffitiL(state, player, limit, 26) + and graffitiXL(state, player, limit, 10) + ) + + +def millennium_square_entrance(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 2) + + +def brink_terminal_entrance(state: CollectionState, player: int) -> bool: + return ( + is_girl(state, player) + and rep(state, player, 180) + and current_chapter(state, player, 2) + ) + + +def brink_terminal_challenge1(state: CollectionState, player: int) -> bool: + return rep(state, player, 188) + + +def brink_terminal_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 200) + + +def brink_terminal_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 220) + + +def brink_terminal_all_challenges(state: CollectionState, player: int) -> bool: + return brink_terminal_challenge3(state, player) + + +def brink_terminal_plaza(state: CollectionState, player: int) -> bool: + return brink_terminal_all_challenges(state, player) + + +def brink_terminal_tower(state: CollectionState, player: int) -> bool: + return rep(state, player, 280) + + +def brink_terminal_oldhead_underground(state: CollectionState, player: int) -> bool: + return rep(state, player, 250) + + +def brink_terminal_oldhead_dock(state: CollectionState, player: int) -> bool: + return rep(state, player, 320) + + +def brink_terminal_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 280) + and graffitiL(state, player, limit, 103) + ) + else: + return ( + rep(state, player, 280) + and graffitiL(state, player, limit, 62) + ) + + +def brink_terminal_mesh(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + graffitiM(state, player, limit, 114) + and graffitiXL(state, player, limit, 45) + ) + else: + return ( + graffitiM(state, player, limit, 67) + and graffitiXL(state, player, limit, 45) + ) + + +def millennium_mall_entrance(state: CollectionState, player: int) -> bool: + return ( + rep(state, player, 380) + and current_chapter(state, player, 3) + ) + + +def millennium_mall_oldhead_ceiling(state: CollectionState, player: int, limit: bool) -> bool: + return ( + rep(state, player, 580) + or millennium_mall_theater(state, player, limit) + ) + + +def millennium_mall_switch(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + graffitiM(state, player, limit, 114) + and current_chapter(state, player, 3) + ) + else: + return ( + graffitiM(state, player, limit, 72) + and current_chapter(state, player, 3) + ) + + +def millennium_mall_big(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return millennium_mall_switch(state, player, limit, glitched) + + +def millennium_mall_oldhead_race(state: CollectionState, player: int) -> bool: + return rep(state, player, 530) + + +def millennium_mall_challenge1(state: CollectionState, player: int) -> bool: + return rep(state, player, 434) + + +def millennium_mall_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 442) + + +def millennium_mall_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 450) + + +def millennium_mall_challenge4(state: CollectionState, player: int) -> bool: + return rep(state, player, 458) + + +def millennium_mall_all_challenges(state: CollectionState, player: int) -> bool: + return millennium_mall_challenge4(state, player) + + +def millennium_mall_theater(state: CollectionState, player: int, limit: bool) -> bool: + return ( + rep(state, player, 491) + and graffitiM(state, player, limit, 78) + ) + + +def millennium_mall_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 491) + and graffitiM(state, player, limit, 114) + and graffitiL(state, player, limit, 107) + ) + else: + return ( + rep(state, player, 491) + and graffitiM(state, player, limit, 78) + and graffitiL(state, player, limit, 80) + ) + + +def pyramid_island_entrance(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 4) + + +def pyramid_island_gate(state: CollectionState, player: int) -> bool: + return rep(state, player, 620) + + +def pyramid_island_oldhead(state: CollectionState, player: int) -> bool: + return rep(state, player, 780) + + +def pyramid_island_challenge1(state: CollectionState, player: int) -> bool: + return ( + rep(state, player, 630) + and current_chapter(state, player, 4) + ) + + +def pyramid_island_race(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + pyramid_island_challenge1(state, player) + and graffitiL(state, player, limit, 108) + ) + else: + return ( + pyramid_island_challenge1(state, player) + and graffitiL(state, player, limit, 93) + ) + + +def pyramid_island_challenge2(state: CollectionState, player: int) -> bool: + return rep(state, player, 650) + + +def pyramid_island_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 660) + + +def pyramid_island_all_challenges(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + graffitiM(state, player, limit, 114) + and rep(state, player, 660) + ) + else: + return ( + graffitiM(state, player, limit, 88) + and rep(state, player, 660) + ) + + +def pyramid_island_upper_half(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return pyramid_island_all_challenges(state, player, limit, glitched) + + +def pyramid_island_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 730) + and graffitiL(state, player, limit, 108) + ) + else: + return ( + rep(state, player, 730) + and graffitiL(state, player, limit, 97) + ) + + +def pyramid_island_top(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 5) + + +def mataan_entrance(state: CollectionState, player: int) -> bool: + return current_chapter(state, player, 2) + + +def mataan_smoke_wall(state: CollectionState, player: int) -> bool: + return ( + current_chapter(state, player, 5) + and rep(state, player, 850) + ) + + +def mataan_challenge1(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + current_chapter(state, player, 5) + and rep(state, player, 864) + and graffitiL(state, player, limit, 108) + ) + else: + return ( + current_chapter(state, player, 5) + and rep(state, player, 864) + and graffitiL(state, player, limit, 98) + ) + + +def mataan_deep_city(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return mataan_challenge1(state, player, limit, glitched) + + +def mataan_oldhead(state: CollectionState, player: int) -> bool: + return rep(state, player, 935) + + +def mataan_challenge2(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + rep(state, player, 880) + and graffitiXL(state, player, limit, 59) + ) + else: + return ( + rep(state, player, 880) + and graffitiXL(state, player, limit, 57) + ) + + +def mataan_challenge3(state: CollectionState, player: int) -> bool: + return rep(state, player, 920) + + +def mataan_all_challenges(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return ( + mataan_challenge2(state, player, limit, glitched) + and mataan_challenge3(state, player) + ) + + +def mataan_smoke_wall2(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return ( + mataan_all_challenges(state, player, limit, glitched) + and rep(state, player, 960) + ) + + +def mataan_crew_battle(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + if glitched: + return ( + mataan_smoke_wall2(state, player, limit, glitched) + and graffitiM(state, player, limit, 122) + and graffitiXL(state, player, limit, 59) + ) + else: + return ( + mataan_smoke_wall2(state, player, limit, glitched) + and graffitiM(state, player, limit, 117) + and graffitiXL(state, player, limit, 57) + ) + + +def mataan_deepest(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return mataan_crew_battle(state, player, limit, glitched) + + +def mataan_faux(state: CollectionState, player: int, limit: bool, glitched: bool) -> bool: + return ( + mataan_deepest(state, player, limit, glitched) + and graffitiM(state, player, limit, 122) + ) + + +def spots_s_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 10 + conditions: Dict[str, int] = { + "versum_hill_entrance": 1, + "versum_hill_ch1_roadblock": 11, + "chapter2": 12, + "versum_hill_oldhead": 1, + "brink_terminal_entrance": 9, + "brink_terminal_plaza": 3, + "brink_terminal_tower": 0, + "chapter3": 6, + "brink_terminal_oldhead_dock": 1, + "millennium_mall_entrance": 3, + "millennium_mall_switch": 4, + "millennium_mall_theater": 3, + "chapter4": 2, + "pyramid_island_gate": 5, + "pyramid_island_upper_half": 8, + "pyramid_island_oldhead": 2, + "mataan_smoke_wall": 3, + "mataan_deep_city": 5, + "mataan_oldhead": 3, + "mataan_deepest": 2 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = 5 + (state.count_group_unique("characters", player) * 5) + if total <= sprayable: + return total + else: + return sprayable + else: + return total + + +def spots_s_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 75 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 13, + "chapter3": 6 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = 5 + (state.count_group_unique("characters", player) * 5) + if total <= sprayable: + return total + else: + return sprayable + else: + return total + + +def spots_m_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 4 + conditions: Dict[str, int] = { + "versum_hill_entrance": 3, + "versum_hill_ch1_roadblock": 13, + "versum_hill_all_challenges": 3, + "chapter2": 16, + "versum_hill_oldhead": 4, + "brink_terminal_entrance": 13, + "brink_terminal_plaza": 4, + "brink_terminal_tower": 0, + "chapter3": 3, + "brink_terminal_oldhead_dock": 4, + "millennium_mall_entrance": 5, + "millennium_mall_big": 6, + "millennium_mall_theater": 4, + "chapter4": 2, + "millennium_mall_oldhead_ceiling": 1, + "pyramid_island_gate": 3, + "pyramid_island_upper_half": 8, + "chapter5": 2, + "pyramid_island_oldhead": 5, + "mataan_deep_city": 7, + "skateboard": 1, + "mataan_oldhead": 1, + "mataan_smoke_wall2": 1, + "mataan_deepest": 10 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + elif access_name != "skateboard": + break + + if limit: + sprayable: int = state.count_group_unique("graffitim", player) * 7 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitim", player): + return total + else: + return 0 + + +def spots_m_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 99 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 21, + "chapter3": 3 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_unique("graffitim", player) * 7 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitim", player): + return total + else: + return 0 + + +def spots_l_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 7 + conditions: Dict[str, int] = { + "inline_skates": 1, + "versum_hill_entrance": 2, + "versum_hill_ch1_roadblock": 13, + "versum_hill_all_challenges": 1, + "chapter2": 14, + "versum_hill_oldhead": 2, + "brink_terminal_entrance": 10, + "brink_terminal_plaza": 2, + "brink_terminal_oldhead_underground": 1, + "brink_terminal_tower": 1, + "chapter3": 4, + "brink_terminal_oldhead_dock": 4, + "millennium_mall_entrance": 3, + "millennium_mall_big": 8, + "millennium_mall_theater": 4, + "chapter4": 5, + "millennium_mall_oldhead_ceiling": 3, + "pyramid_island_gate": 4, + "pyramid_island_upper_half": 5, + "pyramid_island_crew_battle": 1, + "chapter5": 1, + "pyramid_island_oldhead": 2, + "mataan_smoke_wall": 1, + "mataan_deep_city": 2, + "skateboard": 1, + "mataan_oldhead": 2, + "mataan_deepest": 7 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + elif not (access_name == "inline_skates" or access_name == "skateboard"): + break + + if limit: + sprayable: int = state.count_group_unique("graffitil", player) * 6 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitil", player): + return total + else: + return 0 + + +def spots_l_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 88 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 18, + "chapter3": 4, + "chapter4": 1 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_unique("graffitil", player) * 6 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitil", player): + return total + else: + return 0 + + +def spots_xl_glitchless(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 3 + conditions: Dict[str, int] = { + "versum_hill_ch1_roadblock": 6, + "versum_hill_basketball_court": 1, + "chapter2": 9, + "brink_terminal_entrance": 3, + "brink_terminal_plaza": 1, + "brink_terminal_oldhead_underground": 1, + "brink_terminal_tower": 1, + "chapter3": 3, + "brink_terminal_oldhead_dock": 2, + "millennium_mall_entrance": 2, + "millennium_mall_big": 5, + "millennium_mall_theater": 5, + "chapter4": 3, + "millennium_mall_oldhead_ceiling": 1, + "pyramid_island_upper_half": 5, + "pyramid_island_oldhead": 3, + "mataan_smoke_wall": 2, + "mataan_deep_city": 2, + "mataan_oldhead": 2, + "mataan_deepest": 2 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_unique("graffitixl", player) * 4 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitixl", player): + return total + else: + return 0 + + +def spots_xl_glitched(state: CollectionState, player: int, limit: bool, access_cache: Dict[str, bool]) -> int: + total: int = 51 + conditions: Dict[str, int] = { + "brink_terminal_entrance": 7, + "chapter3": 3, + "chapter4": 1 + } + + for access_name, graffiti_count in conditions.items(): + if access_cache[access_name]: + total += graffiti_count + else: + break + + if limit: + sprayable: int = state.count_group_unique("graffitixl", player) * 4 + if total <= sprayable: + return total + else: + return sprayable + else: + if state.has_group("graffitixl", player): + return total + else: + return 0 + + +def build_access_cache(state: CollectionState, player: int, movestyle: int, limit: bool, glitched: bool) -> Dict[str, bool]: + funcs: Dict[str, tuple] = { + "versum_hill_entrance": (state, player), + "versum_hill_ch1_roadblock": (state, player, limit), + "versum_hill_oldhead": (state, player), + "versum_hill_all_challenges": (state, player), + "versum_hill_basketball_court": (state, player), + "brink_terminal_entrance": (state, player), + "brink_terminal_oldhead_underground": (state, player), + "brink_terminal_oldhead_dock": (state, player), + "brink_terminal_plaza": (state, player), + "brink_terminal_tower": (state, player), + "millennium_mall_entrance": (state, player), + "millennium_mall_switch": (state, player, limit, glitched), + "millennium_mall_oldhead_ceiling": (state, player, limit), + "millennium_mall_big": (state, player, limit, glitched), + "millennium_mall_theater": (state, player, limit), + "pyramid_island_gate": (state, player), + "pyramid_island_oldhead": (state, player), + "pyramid_island_upper_half": (state, player, limit, glitched), + "pyramid_island_crew_battle": (state, player, limit, glitched), + "mataan_smoke_wall": (state, player), + "mataan_deep_city": (state, player, limit, glitched), + "mataan_oldhead": (state, player), + "mataan_smoke_wall2": (state, player, limit, glitched), + "mataan_deepest": (state, player, limit, glitched) + } + + access_cache: Dict[str, bool] = { + "skateboard": skateboard(state, player, movestyle), + "inline_skates": inline_skates(state, player, movestyle), + "chapter2": current_chapter(state, player, 2), + "chapter3": current_chapter(state, player, 3), + "chapter4": current_chapter(state, player, 4), + "chapter5": current_chapter(state, player, 5) + } + + stop: bool = False + for fname, fvars in funcs.items(): + if stop: + access_cache[fname] = False + continue + func = globals()[fname] + access: bool = func(*fvars) + access_cache[fname] = access + if not access and "oldhead" not in fname: + stop = True + + return access_cache + + +def graffiti_spots(state: CollectionState, player: int, movestyle: int, limit: bool, glitched: bool, spots: int) -> bool: + access_cache = build_access_cache(state, player, movestyle, limit, glitched) + + total: int = 0 + + if glitched: + total = spots_s_glitched(state, player, limit, access_cache) \ + + spots_m_glitched(state, player, limit, access_cache) \ + + spots_l_glitched(state, player, limit, access_cache) \ + + spots_xl_glitched(state, player, limit, access_cache) + else: + total = spots_s_glitchless(state, player, limit, access_cache) \ + + spots_m_glitchless(state, player, limit, access_cache) \ + + spots_l_glitchless(state, player, limit, access_cache) \ + + spots_xl_glitchless(state, player, limit, access_cache) + + return total >= spots + + +def rep(state: CollectionState, player: int, required: int) -> bool: + return state.has("rep", player, required) + + +def rules(brcworld): + multiworld = brcworld.multiworld + player = brcworld.player + + movestyle = brcworld.options.starting_movestyle + limit = brcworld.options.limited_graffiti + glitched = brcworld.options.logic + extra = brcworld.options.extra_rep_required + photos = not brcworld.options.skip_polo_photos + + # entrances + for e in multiworld.get_region(Stages.BT1, player).entrances: + set_rule(e, lambda state: brink_terminal_entrance(state, player)) + + if not glitched: + # versum hill + for e in multiworld.get_region(Stages.VH1, player).entrances: + set_rule(e, lambda state: versum_hill_entrance(state, player)) + for e in multiworld.get_region(Stages.VH2, player).entrances: + set_rule(e, lambda state: versum_hill_ch1_roadblock(state, player, limit)) + for e in multiworld.get_region(Stages.VHO, player).entrances: + set_rule(e, lambda state: versum_hill_oldhead(state, player)) + for e in multiworld.get_region(Stages.VH3, player).entrances: + set_rule(e, lambda state: versum_hill_all_challenges(state, player)) + for e in multiworld.get_region(Stages.VH4, player).entrances: + set_rule(e, lambda state: versum_hill_basketball_court(state, player)) + + # millennium square + for e in multiworld.get_region(Stages.MS, player).entrances: + set_rule(e, lambda state: millennium_square_entrance(state, player)) + + # brink terminal + for e in multiworld.get_region(Stages.BTO1, player).entrances: + set_rule(e, lambda state: brink_terminal_oldhead_underground(state, player)) + for e in multiworld.get_region(Stages.BTO2, player).entrances: + set_rule(e, lambda state: brink_terminal_oldhead_dock(state, player)) + for e in multiworld.get_region(Stages.BT2, player).entrances: + set_rule(e, lambda state: brink_terminal_plaza(state, player)) + for e in multiworld.get_region(Stages.BT3, player).entrances: + set_rule(e, lambda state: brink_terminal_tower(state, player)) + + # millennium mall + for e in multiworld.get_region(Stages.MM1, player).entrances: + set_rule(e, lambda state: millennium_mall_entrance(state, player)) + for e in multiworld.get_region(Stages.MMO1, player).entrances: + set_rule(e, lambda state: millennium_mall_oldhead_ceiling(state, player, limit)) + for e in multiworld.get_region(Stages.MM2, player).entrances: + set_rule(e, lambda state: millennium_mall_big(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.MMO2, player).entrances: + set_rule(e, lambda state: millennium_mall_oldhead_race(state, player)) + for e in multiworld.get_region(Stages.MM3, player).entrances: + set_rule(e, lambda state: millennium_mall_theater(state, player, limit)) + + # pyramid island + for e in multiworld.get_region(Stages.PI1, player).entrances: + set_rule(e, lambda state: pyramid_island_entrance(state, player)) + for e in multiworld.get_region(Stages.PI2, player).entrances: + set_rule(e, lambda state: pyramid_island_gate(state, player)) + for e in multiworld.get_region(Stages.PIO, player).entrances: + set_rule(e, lambda state: pyramid_island_oldhead(state, player)) + for e in multiworld.get_region(Stages.PI3, player).entrances: + set_rule(e, lambda state: pyramid_island_upper_half(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.PI4, player).entrances: + set_rule(e, lambda state: pyramid_island_top(state, player)) + + # mataan + for e in multiworld.get_region(Stages.MA1, player).entrances: + set_rule(e, lambda state: mataan_entrance(state, player)) + for e in multiworld.get_region(Stages.MA2, player).entrances: + set_rule(e, lambda state: mataan_smoke_wall(state, player)) + for e in multiworld.get_region(Stages.MA3, player).entrances: + set_rule(e, lambda state: mataan_deep_city(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.MAO, player).entrances: + set_rule(e, lambda state: mataan_oldhead(state, player)) + for e in multiworld.get_region(Stages.MA4, player).entrances: + set_rule(e, lambda state: mataan_smoke_wall2(state, player, limit, glitched)) + for e in multiworld.get_region(Stages.MA5, player).entrances: + set_rule(e, lambda state: mataan_deepest(state, player, limit, glitched)) + + # locations + # hideout + set_rule(multiworld.get_location("Hideout: BMX garage skateboard", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Hideout: Unlock phone app", player), + lambda state: current_chapter(state, player, 2)) + set_rule(multiworld.get_location("Hideout: Vinyl joins the crew", player), + lambda state: current_chapter(state, player, 4)) + set_rule(multiworld.get_location("Hideout: Solace joins the crew", player), + lambda state: current_chapter(state, player, 5)) + + # versum hill + set_rule(multiworld.get_location("Versum Hill: Wallrunning challenge reward", player), + lambda state: versum_hill_challenge1(state, player)) + set_rule(multiworld.get_location("Versum Hill: Manual challenge reward", player), + lambda state: versum_hill_challenge2(state, player)) + set_rule(multiworld.get_location("Versum Hill: Corner challenge reward", player), + lambda state: versum_hill_challenge3(state, player)) + set_rule(multiworld.get_location("Versum Hill: BMX gate outfit", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Versum Hill: Glass floor skates", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Versum Hill: Basketball court shortcut CD", player), + lambda state: current_chapter(state, player, 2)) + set_rule(multiworld.get_location("Versum Hill: Rave joins the crew", player), + lambda state: versum_hill_rave(state, player, limit, glitched)) + set_rule(multiworld.get_location("Versum Hill: Frank joins the crew", player), + lambda state: current_chapter(state, player, 2)) + set_rule(multiworld.get_location("Versum Hill: Rietveld joins the crew", player), + lambda state: versum_hill_rietveld(state, player, limit, glitched)) + if photos: + set_rule(multiworld.get_location("Versum Hill: Big Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Versum Hill: Trash Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Versum Hill: Fruit stand Polo", player), + lambda state: camera(state, player)) + + # millennium square + if photos: + set_rule(multiworld.get_location("Millennium Square: Half pipe Polo", player), + lambda state: camera(state, player)) + + # brink terminal + set_rule(multiworld.get_location("Brink Terminal: Upside grind challenge reward", player), + lambda state: brink_terminal_challenge1(state, player)) + set_rule(multiworld.get_location("Brink Terminal: Manual challenge reward", player), + lambda state: brink_terminal_challenge2(state, player)) + set_rule(multiworld.get_location("Brink Terminal: Score challenge reward", player), + lambda state: brink_terminal_challenge3(state, player)) + set_rule(multiworld.get_location("Brink Terminal: BMX gate graffiti", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Brink Terminal: Mesh's skateboard", player), + lambda state: brink_terminal_mesh(state, player, limit, glitched)) + set_rule(multiworld.get_location("Brink Terminal: Rooftop glass CD", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Brink Terminal: Mesh joins the crew", player), + lambda state: brink_terminal_mesh(state, player, limit, glitched)) + set_rule(multiworld.get_location("Brink Terminal: Eclipse joins the crew", player), + lambda state: current_chapter(state, player, 3)) + if photos: + set_rule(multiworld.get_location("Brink Terminal: Behind glass Polo", player), + lambda state: camera(state, player)) + + # millennium mall + set_rule(multiworld.get_location("Millennium Mall: Glass cylinder CD", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Millennium Mall: Trick challenge reward", player), + lambda state: millennium_mall_challenge1(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Slide challenge reward", player), + lambda state: millennium_mall_challenge2(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Fish challenge reward", player), + lambda state: millennium_mall_challenge3(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Score challenge reward", player), + lambda state: millennium_mall_challenge4(state, player)) + set_rule(multiworld.get_location("Millennium Mall: Atrium BMX gate BMX", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Millennium Mall: Shine joins the crew", player), + lambda state: current_chapter(state, player, 4)) + set_rule(multiworld.get_location("Millennium Mall: DOT.EXE joins the crew", player), + lambda state: current_chapter(state, player, 4)) + + # pyramid island + set_rule(multiworld.get_location("Pyramid Island: BMX gate BMX", player), + lambda state: bmx(state, player, movestyle)) + set_rule(multiworld.get_location("Pyramid Island: Score challenge reward", player), + lambda state: pyramid_island_challenge1(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Score challenge 2 reward", player), + lambda state: pyramid_island_challenge2(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Quarter pipe challenge reward", player), + lambda state: pyramid_island_challenge3(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Shortcut glass CD", player), + lambda state: inline_skates(state, player, movestyle)) + set_rule(multiworld.get_location("Pyramid Island: Maze outfit", player), + lambda state: skateboard(state, player, movestyle)) + if not glitched: + add_rule(multiworld.get_location("Pyramid Island: Rise joins the crew", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Devil Theory joins the crew", player), + lambda state: current_chapter(state, player, 5)) + if photos: + set_rule(multiworld.get_location("Pyramid Island: Polo pile 1", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Polo pile 2", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Polo pile 3", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Polo pile 4", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze glass Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze classroom Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze vent Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Big maze Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze desk Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Pyramid Island: Maze forklift Polo", player), + lambda state: camera(state, player)) + + # mataan + set_rule(multiworld.get_location("Mataan: Race challenge reward", player), + lambda state: mataan_challenge1(state, player, limit, glitched)) + set_rule(multiworld.get_location("Mataan: Wallrunning challenge reward", player), + lambda state: mataan_challenge2(state, player, limit, glitched)) + set_rule(multiworld.get_location("Mataan: Score challenge reward", player), + lambda state: mataan_challenge3(state, player)) + set_rule(multiworld.get_location("Mataan: Coil joins the crew", player), + lambda state: mataan_deepest(state, player, limit, glitched)) + if photos: + set_rule(multiworld.get_location("Mataan: Trash Polo", player), + lambda state: camera(state, player)) + set_rule(multiworld.get_location("Mataan: Shopping Polo", player), + lambda state: camera(state, player)) + + # events + set_rule(multiworld.get_location("Versum Hill: Complete Chapter 1", player), + lambda state: versum_hill_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Brink Terminal: Complete Chapter 2", player), + lambda state: brink_terminal_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Millennium Mall: Complete Chapter 3", player), + lambda state: millennium_mall_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Pyramid Island: Complete Chapter 4", player), + lambda state: pyramid_island_crew_battle(state, player, limit, glitched)) + set_rule(multiworld.get_location("Defeat Faux", player), + lambda state: mataan_faux(state, player, limit, glitched)) + + if extra: + add_rule(multiworld.get_location("Defeat Faux", player), + lambda state: rep(state, player, 1000)) + + # graffiti spots + spots: int = 0 + while spots < 385: + spots += 5 + set_rule(multiworld.get_location(f"Tagged {spots} Graffiti Spots", player), + lambda state, spot_count=spots: graffiti_spots(state, player, movestyle, limit, glitched, spot_count)) + + set_rule(multiworld.get_location("Tagged 389 Graffiti Spots", player), + lambda state: graffiti_spots(state, player, movestyle, limit, glitched, 389)) diff --git a/worlds/bomb_rush_cyberfunk/__init__.py b/worlds/bomb_rush_cyberfunk/__init__.py new file mode 100644 index 000000000000..98926e335138 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/__init__.py @@ -0,0 +1,203 @@ +from typing import Any, Dict +from BaseClasses import MultiWorld, Region, Location, Item, Tutorial, ItemClassification, CollectionState +from worlds.AutoWorld import World, WebWorld +from .Items import base_id, item_table, group_table, BRCType +from .Locations import location_table, event_table +from .Regions import region_exits +from .Rules import rules +from .Options import BombRushCyberfunkOptions, StartStyle + + +class BombRushCyberfunkWeb(WebWorld): + theme = "ocean" + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to setting up Bomb Rush Cyberfunk randomizer and connecting to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["TRPG"] + )] + + +class BombRushCyberfunkWorld(World): + """Bomb Rush Cyberfunk is 1 second per second of advanced funkstyle. Battle rival crews and dispatch militarized + police to conquer the five boroughs of New Amsterdam. Become All City.""" + + game = "Bomb Rush Cyberfunk" + web = BombRushCyberfunkWeb() + + item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)} + item_name_to_type = {item["name"]: item["type"] for item in item_table} + location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)} + + item_name_groups = group_table + options_dataclass = BombRushCyberfunkOptions + options: BombRushCyberfunkOptions + + def __init__(self, multiworld: MultiWorld, player: int): + super(BombRushCyberfunkWorld, self).__init__(multiworld, player) + self.item_classification: Dict[BRCType, ItemClassification] = { + BRCType.Music: ItemClassification.filler, + BRCType.GraffitiM: ItemClassification.progression, + BRCType.GraffitiL: ItemClassification.progression, + BRCType.GraffitiXL: ItemClassification.progression, + BRCType.Outfit: ItemClassification.filler, + BRCType.Character: ItemClassification.progression, + BRCType.REP: ItemClassification.progression_skip_balancing, + BRCType.Camera: ItemClassification.progression + } + + def collect(self, state: "CollectionState", item: "Item") -> bool: + change = super().collect(state, item) + if change and "REP" in item.name: + rep: int = int(item.name[0:len(item.name)-4]) + state.prog_items[item.player]["rep"] += rep + return change + + def remove(self, state: "CollectionState", item: "Item") -> bool: + change = super().remove(state, item) + if change and "REP" in item.name: + rep: int = int(item.name[0:len(item.name)-4]) + state.prog_items[item.player]["rep"] -= rep + return change + + def set_rules(self): + rules(self) + + def get_item_classification(self, item_type: BRCType) -> ItemClassification: + classification = ItemClassification.filler + if item_type in self.item_classification.keys(): + classification = self.item_classification[item_type] + + return classification + + def create_item(self, name: str) -> "BombRushCyberfunkItem": + item_id: int = self.item_name_to_id[name] + item_type: BRCType = self.item_name_to_type[name] + classification = self.get_item_classification(item_type) + + return BombRushCyberfunkItem(name, classification, item_id, self.player) + + def create_event(self, event: str) -> "BombRushCyberfunkItem": + return BombRushCyberfunkItem(event, ItemClassification.progression_skip_balancing, None, self.player) + + def get_filler_item_name(self) -> str: + item = self.random.choice(item_table) + + while self.get_item_classification(item["type"]) == ItemClassification.progression: + item = self.random.choice(item_table) + + return item["name"] + + def generate_early(self): + if self.options.starting_movestyle == StartStyle.option_skateboard: + self.item_classification[BRCType.Skateboard] = ItemClassification.filler + else: + self.item_classification[BRCType.Skateboard] = ItemClassification.progression + + if self.options.starting_movestyle == StartStyle.option_inline_skates: + self.item_classification[BRCType.InlineSkates] = ItemClassification.filler + else: + self.item_classification[BRCType.InlineSkates] = ItemClassification.progression + + if self.options.starting_movestyle == StartStyle.option_bmx: + self.item_classification[BRCType.BMX] = ItemClassification.filler + else: + self.item_classification[BRCType.BMX] = ItemClassification.progression + + def create_items(self): + rep_locations: int = 87 + if self.options.skip_polo_photos: + rep_locations -= 17 + + self.options.total_rep.round_to_nearest_step() + rep_counts = self.options.total_rep.get_rep_item_counts(self.random, rep_locations) + #print(sum([8*rep_counts[0], 16*rep_counts[1], 24*rep_counts[2], 32*rep_counts[3], 48*rep_counts[4]]), \ + # rep_counts) + + pool = [] + + for item in item_table: + if "REP" in item["name"]: + count: int = 0 + + if item["name"] == "8 REP": + count = rep_counts[0] + elif item["name"] == "16 REP": + count = rep_counts[1] + elif item["name"] == "24 REP": + count = rep_counts[2] + elif item["name"] == "32 REP": + count = rep_counts[3] + elif item["name"] == "48 REP": + count = rep_counts[4] + + if count > 0: + for _ in range(count): + pool.append(self.create_item(item["name"])) + else: + pool.append(self.create_item(item["name"])) + + self.multiworld.itempool += pool + + def create_regions(self): + multiworld = self.multiworld + player = self.player + + menu = Region("Menu", player, multiworld) + multiworld.regions.append(menu) + + for n in region_exits: + multiworld.regions += [Region(n, player, multiworld)] + + menu.add_exits({"Hideout": "New Game"}) + + for n in region_exits: + self.get_region(n).add_exits(region_exits[n]) + + for index, loc in enumerate(location_table): + if self.options.skip_polo_photos and "Polo" in loc["game_id"]: + continue + stage: Region = self.get_region(loc["stage"]) + stage.add_locations({loc["name"]: base_id + index}) + + for e in event_table: + stage: Region = self.get_region(e["stage"]) + event = BombRushCyberfunkLocation(player, e["name"], None, stage) + event.show_in_spoiler = False + event.place_locked_item(self.create_event(e["item"])) + stage.locations += [event] + + multiworld.completion_condition[player] = lambda state: state.has("Victory", player) + + def fill_slot_data(self) -> Dict[str, Any]: + options = self.options + + slot_data: Dict[str, Any] = { + "locations": {loc["game_id"]: (base_id + index) for index, loc in enumerate(location_table)}, + "logic": options.logic.value, + "skip_intro": bool(options.skip_intro.value), + "skip_dreams": bool(options.skip_dreams.value), + "skip_statue_hands": bool(options.skip_statue_hands.value), + "total_rep": options.total_rep.value, + "extra_rep_required": bool(options.extra_rep_required.value), + "starting_movestyle": options.starting_movestyle.value, + "limited_graffiti": bool(options.limited_graffiti.value), + "small_graffiti_uses": options.small_graffiti_uses.value, + "skip_polo_photos": bool(options.skip_polo_photos.value), + "dont_save_photos": bool(options.dont_save_photos.value), + "score_difficulty": int(options.score_difficulty.value), + "damage_multiplier": options.damage_multiplier.value, + "death_link": bool(options.death_link.value) + } + + return slot_data + + +class BombRushCyberfunkItem(Item): + game: str = "Bomb Rush Cyberfunk" + + +class BombRushCyberfunkLocation(Location): + game: str = "Bomb Rush Cyberfunk" diff --git a/worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md b/worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md new file mode 100644 index 000000000000..c6866e489ffb --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md @@ -0,0 +1,29 @@ +# Bomb Rush Cyberfunk + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export +a config file. + +## What does randomization do in this game? + +The goal of Bomb Rush Cyberfunk randomizer is to defeat all rival crews in each borough of New Amsterdam. REP is no +longer earned from doing graffiti, and is instead earned by finding it randomly in the multiworld. + +Items can be found by picking up any type of collectible, unlocking characters, taking pictures of Polo, and for every +5 graffiti spots tagged. The types of items that can be found are Music, Graffiti (M), Graffiti (L), Graffiti (XL), +Skateboards, Inline Skates, BMX, Outfits, Characters, REP, and the Camera. + +Several changes have been made to the game for a better experience as a randomizer: + +- The prelude in the police station can be skipped. +- The map for each stage is always unlocked. +- The taxi is always unlocked, but you will still need to visit each stage's taxi stop before you can use them. +- No M, L, or XL graffiti is unlocked at the beginning. +- Optionally, graffiti can be depleted after a certain number of uses. +- All characters except Red are locked. +- One single REP count is used throughout the game, instead of having separate totals for each stage. REP requirements +are the same as the original game, but added together in order. At least 960 REP is needed to finish the game. + +The mod also adds two new apps to the phone, an "Encounter" app which lets you retry certain events early, and the +"Archipelago" app which lets you view chat messages and change some options while playing. \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/docs/setup_en.md b/worlds/bomb_rush_cyberfunk/docs/setup_en.md new file mode 100644 index 000000000000..14da25adb32b --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/docs/setup_en.md @@ -0,0 +1,41 @@ +# Bomb Rush Cyberfunk Multiworld Setup Guide + +## Quick Links + +- Bomb Rush Cyberfunk: [Steam](https://store.steampowered.com/app/1353230/Bomb_Rush_Cyberfunk/) +- Archipelago Mod: [Thunderstore](https://thunderstore.io/c/bomb-rush-cyberfunk/p/TRPG/BRC_Archipelago/), +[GitHub](https://github.com/TRPG0/BRC-Archipelago/releases) + +## Setup + +To install the Archipelago mod, you can use a mod manager like +[r2modman](https://thunderstore.io/c/bomb-rush-cyberfunk/p/ebkr/r2modman/), or install manually by following these steps: + +1. Download and install [BepInEx 5.4.22 x64](https://github.com/BepInEx/BepInEx/releases/tag/v5.4.22) in your Bomb Rush +Cyberfunk root folder. *Do not use any pre-release versions of BepInEx 6.* + +2. Start Bomb Rush Cyberfunk once so that BepInEx can create its required configuration files. + +3. Download the zip archive from the [releases](https://github.com/TRPG0/BRC-Archipelago/releases) page, and extract its +contents into `BepInEx\plugins`. + +After installing Archipelago, there are some additional mods that can also be installed for a better experience: + +- [MoreMap](https://thunderstore.io/c/bomb-rush-cyberfunk/p/TRPG/MoreMap/) by TRPG + - Adds pins to the map for every type of collectible. +- [FasterLoadTimes](https://thunderstore.io/c/bomb-rush-cyberfunk/p/cspotcode/FasterLoadTimes/) by cspotcode + - Load stages faster by skipping assets that are already loaded. +- [CutsceneSkip](https://thunderstore.io/c/bomb-rush-cyberfunk/p/Jay/CutsceneSkip/) by Jay + - Makes every cutscene skippable. +- [GimmeMyBoost](https://thunderstore.io/c/bomb-rush-cyberfunk/p/Yuri/GimmeMyBoost/) by Yuri + - Retains boost when loading into a new stage. +- [DisableAnnoyingCutscenes](https://thunderstore.io/c/bomb-rush-cyberfunk/p/viliger/DisableAnnoyingCutscenes/) by viliger + - Disables the police cutscenes when increasing your heat level. +- [FastTravel](https://thunderstore.io/c/bomb-rush-cyberfunk/p/tari/FastTravel/) by tari + - Adds an app to the phone to call for a taxi from anywhere. + +## Connecting + +To connect to an Archipelago server, click one of the Archipelago buttons next to the save files. If the save file is +blank or already has randomizer save data, it will open a menu where you can enter the server address and port, your +name, and a password if necessary. Then click the check mark to connect to the server. \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/__init__.py b/worlds/bomb_rush_cyberfunk/test/__init__.py new file mode 100644 index 000000000000..9cd6c3a504bf --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/__init__.py @@ -0,0 +1,5 @@ +from test.bases import WorldTestBase + + +class BombRushCyberfunkTestBase(WorldTestBase): + game = "Bomb Rush Cyberfunk" \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py b/worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py new file mode 100644 index 000000000000..af5402323038 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py @@ -0,0 +1,284 @@ +from . import BombRushCyberfunkTestBase +from ..Rules import build_access_cache, spots_s_glitchless, spots_s_glitched, spots_m_glitchless, spots_m_glitched, \ + spots_l_glitchless, spots_l_glitched, spots_xl_glitched, spots_xl_glitchless + + +class TestSpotsGlitchless(BombRushCyberfunkTestBase): + @property + def run_default_tests(self) -> bool: + return False + + def test_spots_glitchless(self) -> None: + player = self.player + + self.collect_by_name([ + "Graffiti (M - OVERWHELMME)", + "Graffiti (L - WHOLE SIXER)", + "Graffiti (XL - Gold Rush)" + ]) + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - hideout + self.assertEqual(10, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(4, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(7, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(3, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.collect_by_name("Inline Skates (Glaciers)") + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + self.assertEqual(8, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 20 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - VH1-2 + self.assertEqual(22, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(20, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(23, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(9, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 65 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - VH3 + self.assertEqual(23, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(24, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 90 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 1 - VH4 + self.assertEqual(10, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 1 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - MS + MA1 + self.assertEqual(34, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(39, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(38, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(19, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 120 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - VHO + self.assertEqual(35, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(43, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(40, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.collect_by_name("Bel") + self.multiworld.state.prog_items[player]["rep"] = 180 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BT1 + self.assertEqual(44, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(56, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(50, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(22, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 220 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BT2 + self.assertEqual(47, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(60, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(52, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(23, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 250 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BTO1 + self.assertEqual(53, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(24, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 280 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BT3 / chapter 3 - MS + self.assertEqual(58, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(28, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 320 + self.multiworld.state.prog_items[player]["Chapter Completed"] = 2 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 2 - BTO2 / chapter 3 - MS + self.assertEqual(54, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(67, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(62, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(30, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 380 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 3 - MM1-2 + self.assertEqual(61, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(78, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(73, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(37, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 491 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 3 - MM3 + self.assertEqual(64, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(82, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(77, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(42, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 3 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 4 - MS / BT / MMO1 / PI1 + self.assertEqual(66, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(85, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(85, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(46, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 620 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 4 - PI2 + self.assertEqual(71, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(88, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(89, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 660 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 4 - PI3 + self.assertEqual(79, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(96, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(94, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(51, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 730 + self.multiworld.state.prog_items[player]["Chapter Completed"] = 4 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - PI4 + self.assertEqual(98, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(96, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 780 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - PIO + self.assertEqual(81, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(103, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(98, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(54, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 850 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MA2 + self.assertEqual(84, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(99, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(56, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 864 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MA3 + self.assertEqual(89, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(111, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(102, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(58, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 935 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MAO + self.assertEqual(92, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(112, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(104, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(60, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["rep"] = 960 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, False) + + # chapter 5 - MA4-5 + self.assertEqual(94, spots_s_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(123, spots_m_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(111, spots_l_glitchless(self.multiworld.state, player, False, access_cache)) + self.assertEqual(62, spots_xl_glitchless(self.multiworld.state, player, False, access_cache)) + + +class TestSpotsGlitched(BombRushCyberfunkTestBase): + options = { + "logic": "glitched" + } + + @property + def run_default_tests(self) -> bool: + return False + + def test_spots_glitched(self) -> None: + player = self.player + + self.collect_by_name([ + "Graffiti (M - OVERWHELMME)", + "Graffiti (L - WHOLE SIXER)", + "Graffiti (XL - Gold Rush)" + ]) + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + self.assertEqual(75, spots_s_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(99, spots_m_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(88, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(51, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) + + + self.collect_by_name("Bel") + self.multiworld.state.prog_items[player]["Chapter Completed"] = 1 + self.multiworld.state.prog_items[player]["rep"] = 180 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + # brink terminal + self.assertEqual(88, spots_s_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(120, spots_m_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(106, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(58, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 2 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + # chapter 3 + self.assertEqual(94, spots_s_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(123, spots_m_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(110, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(61, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) + + + self.multiworld.state.prog_items[player]["Chapter Completed"] = 3 + access_cache = build_access_cache(self.multiworld.state, player, 2, False, True) + + # chapter 4 + self.assertEqual(111, spots_l_glitched(self.multiworld.state, player, False, access_cache)) + self.assertEqual(62, spots_xl_glitched(self.multiworld.state, player, False, access_cache)) \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/test_options.py b/worlds/bomb_rush_cyberfunk/test/test_options.py new file mode 100644 index 000000000000..7640700dc06f --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/test_options.py @@ -0,0 +1,29 @@ +from . import BombRushCyberfunkTestBase + + +class TestRegularGraffitiGlitchless(BombRushCyberfunkTestBase): + options = { + "logic": "glitchless", + "limited_graffiti": False + } + + +class TestLimitedGraffitiGlitchless(BombRushCyberfunkTestBase): + options = { + "logic": "glitchless", + "limited_graffiti": True + } + + +class TestRegularGraffitiGlitched(BombRushCyberfunkTestBase): + options = { + "logic": "glitched", + "limited_graffiti": False + } + + +class TestLimitedGraffitiGlitched(BombRushCyberfunkTestBase): + options = { + "logic": "glitched", + "limited_graffiti": True + } \ No newline at end of file diff --git a/worlds/bomb_rush_cyberfunk/test/test_rep_items.py b/worlds/bomb_rush_cyberfunk/test/test_rep_items.py new file mode 100644 index 000000000000..61272a3f0977 --- /dev/null +++ b/worlds/bomb_rush_cyberfunk/test/test_rep_items.py @@ -0,0 +1,45 @@ +from . import BombRushCyberfunkTestBase +from typing import List + + +rep_item_names: List[str] = [ + "8 REP", + "16 REP", + "24 REP", + "32 REP", + "48 REP" +] + + +class TestCollectAndRemoveREP(BombRushCyberfunkTestBase): + @property + def run_default_tests(self) -> bool: + return False + + def test_default_rep_total(self) -> None: + self.collect_by_name(rep_item_names) + self.assertEqual(1400, self.multiworld.state.prog_items[self.player]["rep"]) + + new_total = 1400 + + if self.count("8 REP") > 0: + new_total -= 8 + self.remove(self.get_item_by_name("8 REP")) + + if self.count("16 REP") > 0: + new_total -= 16 + self.remove(self.get_item_by_name("16 REP")) + + if self.count("24 REP") > 0: + new_total -= 24 + self.remove(self.get_item_by_name("24 REP")) + + if self.count("32 REP") > 0: + new_total -= 32 + self.remove(self.get_item_by_name("32 REP")) + + if self.count("48 REP") > 0: + new_total -= 48 + self.remove(self.get_item_by_name("48 REP")) + + self.assertEqual(new_total, self.multiworld.state.prog_items[self.player]["rep"]) \ No newline at end of file diff --git a/worlds/bumpstik/Options.py b/worlds/bumpstik/Options.py index 021f10af2016..a781178ad161 100644 --- a/worlds/bumpstik/Options.py +++ b/worlds/bumpstik/Options.py @@ -3,8 +3,10 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT +from dataclasses import dataclass + import typing -from Options import Option, Range +from Options import Option, Range, PerGameCommonOptions class TaskAdvances(Range): @@ -69,12 +71,12 @@ class KillerTrapWeight(Range): default = 0 -bumpstik_options: typing.Dict[str, type(Option)] = { - "task_advances": TaskAdvances, - "turners": Turners, - "paint_cans": PaintCans, - "trap_count": Traps, - "rainbow_trap_weight": RainbowTrapWeight, - "spinner_trap_weight": SpinnerTrapWeight, - "killer_trap_weight": KillerTrapWeight -} +@dataclass +class BumpstikOptions(PerGameCommonOptions): + task_advances: TaskAdvances + turners: Turners + paint_cans: PaintCans + trap_count: Traps + rainbow_trap_weight: RainbowTrapWeight + spinner_trap_weight: SpinnerTrapWeight + killer_trap_weight: KillerTrapWeight diff --git a/worlds/bumpstik/Regions.py b/worlds/bumpstik/Regions.py index 6cddde882a08..401b62b2d34b 100644 --- a/worlds/bumpstik/Regions.py +++ b/worlds/bumpstik/Regions.py @@ -11,7 +11,7 @@ def _generate_entrances(player: int, entrance_list: [str], parent: Region): return [Entrance(player, entrance, parent) for entrance in entrance_list] -def create_regions(world: MultiWorld, player: int): +def create_regions(multiworld: MultiWorld, player: int): region_map = { "Menu": level1_locs + ["Bonus Booster 1"] + [f"Treasure Bumper {i + 1}" for i in range(8)], "Level 1": level2_locs + ["Bonus Booster 2"] + [f"Treasure Bumper {i + 9}" for i in range(8)], @@ -34,7 +34,7 @@ def create_regions(world: MultiWorld, player: int): for x, region_name in enumerate(region_map): region_list = region_map[region_name] - region = Region(region_name, player, world) + region = Region(region_name, player, multiworld) for location_name in region_list: region.locations += [BumpStikLocation( player, location_name, location_table[location_name], region)] @@ -42,9 +42,9 @@ def create_regions(world: MultiWorld, player: int): region.exits += _generate_entrances(player, [f"To Level {x + 1}"], region) - world.regions += [region] + multiworld.regions += [region] for entrance in entrance_map: - connection = world.get_entrance(f"To {entrance}", player) + connection = multiworld.get_entrance(f"To {entrance}", player) connection.access_rule = entrance_map[entrance] - connection.connect(world.get_region(entrance, player)) + connection.connect(multiworld.get_region(entrance, player)) diff --git a/worlds/bumpstik/__init__.py b/worlds/bumpstik/__init__.py index 9fc9fc214e4b..fe261dc94d30 100644 --- a/worlds/bumpstik/__init__.py +++ b/worlds/bumpstik/__init__.py @@ -39,14 +39,13 @@ class BumpStikWorld(World): location_name_to_id = location_table item_name_groups = item_groups - data_version = 1 - required_client_version = (0, 3, 8) - option_definitions = bumpstik_options + options: BumpstikOptions + options_dataclass = BumpstikOptions - def __init__(self, world: MultiWorld, player: int): - super(BumpStikWorld, self).__init__(world, player) + def __init__(self, multiworld: MultiWorld, player: int): + super(BumpStikWorld, self).__init__(multiworld, player) self.task_advances = TaskAdvances.default self.turners = Turners.default self.paint_cans = PaintCans.default @@ -86,13 +85,13 @@ def get_filler_item_name(self) -> str: return "Nothing" def generate_early(self): - self.task_advances = self.multiworld.task_advances[self.player].value - self.turners = self.multiworld.turners[self.player].value - self.paint_cans = self.multiworld.paint_cans[self.player].value - self.traps = self.multiworld.trap_count[self.player].value - self.rainbow_trap_weight = self.multiworld.rainbow_trap_weight[self.player].value - self.spinner_trap_weight = self.multiworld.spinner_trap_weight[self.player].value - self.killer_trap_weight = self.multiworld.killer_trap_weight[self.player].value + self.task_advances = self.options.task_advances.value + self.turners = self.options.turners.value + self.paint_cans = self.options.paint_cans.value + self.traps = self.options.trap_count.value + self.rainbow_trap_weight = self.options.rainbow_trap_weight.value + self.spinner_trap_weight = self.options.spinner_trap_weight.value + self.killer_trap_weight = self.options.killer_trap_weight.value def create_regions(self): create_regions(self.multiworld, self.player) diff --git a/worlds/bumpstik/docs/en_Bumper Stickers.md b/worlds/bumpstik/docs/en_Bumper Stickers.md index 17a66d76122a..42599ec64121 100644 --- a/worlds/bumpstik/docs/en_Bumper Stickers.md +++ b/worlds/bumpstik/docs/en_Bumper Stickers.md @@ -1,7 +1,7 @@ # Bumper Stickers -## Where is the settings page? -The [player settings page for Bumper Stickers](../player-settings) contains all the options you need to configure and export a config file. +## Where is the options page? +The [player options page for Bumper Stickers](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? Playing this in Archipelago is a very different experience from Classic mode. You start with a very small board and a set of tasks. Completing those tasks will give you a larger board and more, harder tasks. In addition, special types of bumpers exist that must be cleared in order to progress. diff --git a/worlds/celeste64/CHANGELOG.md b/worlds/celeste64/CHANGELOG.md new file mode 100644 index 000000000000..5e562e17f443 --- /dev/null +++ b/worlds/celeste64/CHANGELOG.md @@ -0,0 +1,53 @@ +# Celeste 64 - Changelog + + +## v1.2 + +### Features: + +- New optional Location Checks + - Friendsanity + - Signsanity + - Carsanity +- Move Shuffle + - Basic movement abilities can be shuffled into the item pool + - Ground Dash + - Air Dash + - Skid Jump + - Climb +- Logic Difficulty + - Completely overhauled logic system + - Standard or Hard logic difficulty can be chosen +- Badeline Chasers + - Opt-in options which cause Badelines to start following you as you play, which will kill on contact + - These can be set to spawn based on either: + - The number of locations you've checked + - The number of Strawberry items you've received + - How fast they follow behind you can be specified + +### Quality of Life: + +- The maximum number of Strawberries in the item pool can be directly set + - The required amount of Strawberries is now set via percentage + - All items beyond the amount placed in the item pool will be `Raspberry` items, which have no effect +- Any unique items placed into the `start_inventory` will not be placed into the item pool + + +## v1.1 - First Stable Release + +### Features: + +- Goal is to collect a certain amount of Strawberries and visit Badeline on her island +- Locations included: + - Strawberries +- Items included: + - Strawberries + - Dash Refills + - Double Dash Refills + - Feathers + - Coins + - Cassettes + - Traffic Blocks + - Springs + - Breakable Blocks +- DeathLink is supported \ No newline at end of file diff --git a/worlds/celeste64/Items.py b/worlds/celeste64/Items.py index 94db0e8ef4d2..36c9f670c798 100644 --- a/worlds/celeste64/Items.py +++ b/worlds/celeste64/Items.py @@ -16,43 +16,29 @@ class Celeste64ItemData(NamedTuple): type: ItemClassification = ItemClassification.filler -item_data_table: Dict[str, Celeste64ItemData] = { - ItemName.strawberry: Celeste64ItemData( - code = celeste_64_base_id + 0, - type=ItemClassification.progression_skip_balancing, - ), - ItemName.dash_refill: Celeste64ItemData( - code = celeste_64_base_id + 1, - type=ItemClassification.progression, - ), - ItemName.double_dash_refill: Celeste64ItemData( - code = celeste_64_base_id + 2, - type=ItemClassification.progression, - ), - ItemName.feather: Celeste64ItemData( - code = celeste_64_base_id + 3, - type=ItemClassification.progression, - ), - ItemName.coin: Celeste64ItemData( - code = celeste_64_base_id + 4, - type=ItemClassification.progression, - ), - ItemName.cassette: Celeste64ItemData( - code = celeste_64_base_id + 5, - type=ItemClassification.progression, - ), - ItemName.traffic_block: Celeste64ItemData( - code = celeste_64_base_id + 6, - type=ItemClassification.progression, - ), - ItemName.spring: Celeste64ItemData( - code = celeste_64_base_id + 7, - type=ItemClassification.progression, - ), - ItemName.breakables: Celeste64ItemData( - code = celeste_64_base_id + 8, - type=ItemClassification.progression, - ) +collectable_item_data_table: Dict[str, Celeste64ItemData] = { + ItemName.strawberry: Celeste64ItemData(celeste_64_base_id + 0x0, ItemClassification.progression_skip_balancing), + ItemName.raspberry: Celeste64ItemData(celeste_64_base_id + 0x9, ItemClassification.filler), } +unlockable_item_data_table: Dict[str, Celeste64ItemData] = { + ItemName.dash_refill: Celeste64ItemData(celeste_64_base_id + 0x1, ItemClassification.progression), + ItemName.double_dash_refill: Celeste64ItemData(celeste_64_base_id + 0x2, ItemClassification.progression), + ItemName.feather: Celeste64ItemData(celeste_64_base_id + 0x3, ItemClassification.progression), + ItemName.coin: Celeste64ItemData(celeste_64_base_id + 0x4, ItemClassification.progression), + ItemName.cassette: Celeste64ItemData(celeste_64_base_id + 0x5, ItemClassification.progression), + ItemName.traffic_block: Celeste64ItemData(celeste_64_base_id + 0x6, ItemClassification.progression), + ItemName.spring: Celeste64ItemData(celeste_64_base_id + 0x7, ItemClassification.progression), + ItemName.breakables: Celeste64ItemData(celeste_64_base_id + 0x8, ItemClassification.progression), +} + +move_item_data_table: Dict[str, Celeste64ItemData] = { + ItemName.ground_dash: Celeste64ItemData(celeste_64_base_id + 0xA, ItemClassification.progression), + ItemName.air_dash: Celeste64ItemData(celeste_64_base_id + 0xB, ItemClassification.progression), + ItemName.skid_jump: Celeste64ItemData(celeste_64_base_id + 0xC, ItemClassification.progression), + ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression), +} + +item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table} + item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None} diff --git a/worlds/celeste64/Locations.py b/worlds/celeste64/Locations.py index 92ca425f8383..6341529da356 100644 --- a/worlds/celeste64/Locations.py +++ b/worlds/celeste64/Locations.py @@ -16,127 +16,67 @@ class Celeste64LocationData(NamedTuple): address: Optional[int] = None -location_data_table: Dict[str, Celeste64LocationData] = { - LocationName.strawberry_1 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 0, - ), - LocationName.strawberry_2 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 1, - ), - LocationName.strawberry_3 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 2, - ), - LocationName.strawberry_4 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 3, - ), - LocationName.strawberry_5 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 4, - ), - LocationName.strawberry_6 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 5, - ), - LocationName.strawberry_7 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 6, - ), - LocationName.strawberry_8 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 7, - ), - LocationName.strawberry_9 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 8, - ), - LocationName.strawberry_10 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 9, - ), - LocationName.strawberry_11 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 10, - ), - LocationName.strawberry_12 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 11, - ), - LocationName.strawberry_13 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 12, - ), - LocationName.strawberry_14 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 13, - ), - LocationName.strawberry_15 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 14, - ), - LocationName.strawberry_16 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 15, - ), - LocationName.strawberry_17 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 16, - ), - LocationName.strawberry_18 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 17, - ), - LocationName.strawberry_19 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 18, - ), - LocationName.strawberry_20 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 19, - ), - LocationName.strawberry_21 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 20, - ), - LocationName.strawberry_22 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 21, - ), - LocationName.strawberry_23 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 22, - ), - LocationName.strawberry_24 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 23, - ), - LocationName.strawberry_25 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 24, - ), - LocationName.strawberry_26 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 25, - ), - LocationName.strawberry_27 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 26, - ), - LocationName.strawberry_28 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 27, - ), - LocationName.strawberry_29 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 28, - ), - LocationName.strawberry_30 : Celeste64LocationData( - region = "Forsaken City", - address = celeste_64_base_id + 29, - ) +strawberry_location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00), + LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01), + LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02), + LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03), + LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04), + LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05), + LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06), + LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07), + LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08), + LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09), + LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A), + LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B), + LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C), + LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D), + LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E), + LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F), + LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10), + LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11), + LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12), + LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13), + LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14), + LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15), + LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16), + LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17), + LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18), + LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19), + LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A), + LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B), + LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C), + LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D), } +friend_location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00), + LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01), + LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02), + LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03), + LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04), + LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05), + LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06), + LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07), + LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08), +} + +sign_location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00), + LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01), + LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02), + LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03), + LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04), +} + +car_location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00), + LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01), +} + +location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table, + **friend_location_data_table, + **sign_location_data_table, + **car_location_data_table} + location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None} diff --git a/worlds/celeste64/Names/ItemName.py b/worlds/celeste64/Names/ItemName.py index 5e4daf8e4f2a..d4fb9496007f 100644 --- a/worlds/celeste64/Names/ItemName.py +++ b/worlds/celeste64/Names/ItemName.py @@ -1,4 +1,5 @@ strawberry = "Strawberry" +raspberry = "Raspberry" dash_refill = "Dash Refills" double_dash_refill = "Double Dash Refills" @@ -9,3 +10,8 @@ traffic_block = "Traffic Blocks" spring = "Springs" breakables = "Breakable Blocks" + +ground_dash = "Ground Dash" +air_dash = "Air Dash" +skid_jump = "Skid Jump" +climb = "Climb" diff --git a/worlds/celeste64/Names/LocationName.py b/worlds/celeste64/Names/LocationName.py index a9902f70f7ab..1b784f3875f1 100644 --- a/worlds/celeste64/Names/LocationName.py +++ b/worlds/celeste64/Names/LocationName.py @@ -29,3 +29,25 @@ strawberry_28 = "Feather Arches Cassette Strawberry" strawberry_29 = "North-East Tower Cassette Strawberry" strawberry_30 = "Badeline Cassette Strawberry" + +# Friend Locations +granny_1 = "Granny Conversation 1" +granny_2 = "Granny Conversation 2" +granny_3 = "Granny Conversation 3" +theo_1 = "Theo Conversation 1" +theo_2 = "Theo Conversation 2" +theo_3 = "Theo Conversation 3" +badeline_1 = "Badeline Conversation 1" +badeline_2 = "Badeline Conversation 2" +badeline_3 = "Badeline Conversation 3" + +# Sign Locations +sign_1 = "Camera Sign" +sign_2 = "Skid Jump Sign" +sign_3 = "Dash Jump Sign" +sign_4 = "Lonely Sign" +sign_5 = "Credits Sign" + +# Car Locations +car_1 = "Intro Car" +car_2 = "Secret Car" diff --git a/worlds/celeste64/Options.py b/worlds/celeste64/Options.py index f94fbb02931f..9a67e7d7d4f5 100644 --- a/worlds/celeste64/Options.py +++ b/worlds/celeste64/Options.py @@ -1,25 +1,148 @@ from dataclasses import dataclass -from Options import Range, DeathLink, PerGameCommonOptions +from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions -class StrawberriesRequired(Range): - """How many Strawberries you must receive to finish""" - display_name = "Strawberries Required" - range_start = 0 - range_end = 20 - default = 15 - class DeathLinkAmnesty(Range): - """How many deaths it takes to send a DeathLink""" + """ + How many deaths it takes to send a DeathLink + """ display_name = "Death Link Amnesty" range_start = 1 range_end = 30 default = 10 +class TotalStrawberries(Range): + """ + How many Strawberries exist + """ + display_name = "Total Strawberries" + range_start = 0 + range_end = 46 + default = 20 + +class StrawberriesRequiredPercentage(Range): + """ + Percentage of existing Strawberries you must receive to finish + """ + display_name = "Strawberries Required Percentage" + range_start = 0 + range_end = 100 + default = 80 + + +class LogicDifficulty(Choice): + """ + Whether the logic expects you to play the intended way, or to be able to use advanced tricks and skips + """ + display_name = "Logic Difficulty" + option_standard = 0 + option_hard = 1 + default = 0 + +class MoveShuffle(Toggle): + """ + Whether the following base movement abilities are shuffled into the item pool: + - Ground Dash + - Air Dash + - Skid Jump + - Climb + + NOTE: Having Move Shuffle and Standard Logic Difficulty will guarantee that one of the four Move items will be immediately accessible + + WARNING: Combining Move Shuffle and Hard Logic Difficulty can require very difficult tricks + """ + display_name = "Move Shuffle" + + +class Friendsanity(Toggle): + """ + Whether chatting with your friends grants location checks + """ + display_name = "Friendsanity" + +class Signsanity(Toggle): + """ + Whether reading signs grants location checks + """ + display_name = "Signsanity" + +class Carsanity(Toggle): + """ + Whether riding on cars grants location checks + """ + display_name = "Carsanity" + + +class BadelineChaserSource(Choice): + """ + What type of action causes more Badeline Chasers to start spawning + + Locations: The number of locations you've checked contributes to Badeline Chasers + + Strawberries: The number of Strawberry items you've received contributes to Badeline Chasers + """ + display_name = "Badeline Chaser Source" + option_locations = 0 + option_strawberries = 1 + default = 0 + +class BadelineChaserFrequency(Range): + """ + How many of the `Badeline Chaser Source` actions must occur to make each Badeline Chaser start spawning + + NOTE: Choosing `0` disables Badeline Chasers entirely + + WARNING: Turning on Badeline Chasers alongside Move Shuffle could result in extremely difficult situations + """ + display_name = "Badeline Chaser Frequency" + range_start = 0 + range_end = 10 + default = 0 + +class BadelineChaserSpeed(Range): + """ + How many seconds behind you each Badeline Chaser will be + """ + display_name = "Badeline Chaser Speed" + range_start = 2 + range_end = 10 + default = 3 + + +celeste_64_option_groups = [ + OptionGroup("Goal Options", [ + TotalStrawberries, + StrawberriesRequiredPercentage, + ]), + OptionGroup("Sanity Options", [ + Friendsanity, + Signsanity, + Carsanity, + ]), + OptionGroup("Badeline Chasers", [ + BadelineChaserSource, + BadelineChaserFrequency, + BadelineChaserSpeed, + ]), +] + @dataclass class Celeste64Options(PerGameCommonOptions): death_link: DeathLink death_link_amnesty: DeathLinkAmnesty - strawberries_required: StrawberriesRequired + + total_strawberries: TotalStrawberries + strawberries_required_percentage: StrawberriesRequiredPercentage + + logic_difficulty: LogicDifficulty + move_shuffle: MoveShuffle + + friendsanity: Friendsanity + signsanity: Signsanity + carsanity: Carsanity + + badeline_chaser_source: BadelineChaserSource + badeline_chaser_frequency: BadelineChaserFrequency + badeline_chaser_speed: BadelineChaserSpeed diff --git a/worlds/celeste64/Rules.py b/worlds/celeste64/Rules.py index 3baa231892ad..ebb47cca3093 100644 --- a/worlds/celeste64/Rules.py +++ b/worlds/celeste64/Rules.py @@ -1,3 +1,6 @@ +from typing import Dict, List + +from BaseClasses import CollectionState from worlds.generic.Rules import set_rule from . import Celeste64World @@ -5,100 +8,336 @@ def set_rules(world: Celeste64World): - set_rule(world.multiworld.get_location(LocationName.strawberry_4, world.player), - lambda state: state.has_all({ItemName.traffic_block, - ItemName.breakables}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_5, world.player), - lambda state: state.has(ItemName.breakables, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_6, world.player), - lambda state: state.has(ItemName.dash_refill, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_8, world.player), - lambda state: state.has(ItemName.traffic_block, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_9, world.player), - lambda state: state.has(ItemName.dash_refill, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_11, world.player), - lambda state: state.has(ItemName.dash_refill, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_12, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.double_dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_13, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.breakables}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_14, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.feather}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_15, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.feather}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_16, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.feather}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_17, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.double_dash_refill, - ItemName.feather, - ItemName.traffic_block}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_18, world.player), - lambda state: state.has(ItemName.double_dash_refill, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_19, world.player), - lambda state: state.has_all({ItemName.double_dash_refill, - ItemName.spring}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_20, world.player), - lambda state: state.has_all({ItemName.dash_refill, - ItemName.feather, - ItemName.breakables}, world.player)) - - set_rule(world.multiworld.get_location(LocationName.strawberry_21, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.traffic_block, - ItemName.breakables}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_22, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.dash_refill, - ItemName.breakables}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_23, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.dash_refill, - ItemName.coin}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_24, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.traffic_block, - ItemName.dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_25, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.dash_refill, - ItemName.double_dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_26, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_27, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.feather, - ItemName.coin, - ItemName.dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_28, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.feather, - ItemName.coin, - ItemName.dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_29, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.feather, - ItemName.coin, - ItemName.dash_refill}, world.player)) - set_rule(world.multiworld.get_location(LocationName.strawberry_30, world.player), - lambda state: state.has_all({ItemName.cassette, - ItemName.feather, - ItemName.traffic_block, - ItemName.spring, - ItemName.breakables, - ItemName.dash_refill, - ItemName.double_dash_refill}, world.player)) + if world.options.logic_difficulty == "standard": + if world.options.move_shuffle: + world.active_logic_mapping = location_standard_moves_logic + else: + world.active_logic_mapping = location_standard_logic + else: + if world.options.move_shuffle: + world.active_logic_mapping = location_hard_moves_logic + else: + world.active_logic_mapping = location_hard_logic + + for location in world.multiworld.get_locations(world.player): + set_rule(location, lambda state, location=location: location_rule(state, world, location.name)) + + if world.options.logic_difficulty == "standard": + if world.options.move_shuffle: + world.goal_logic_mapping = goal_standard_moves_logic + else: + world.goal_logic_mapping = goal_standard_logic + else: + if world.options.move_shuffle: + world.goal_logic_mapping = goal_hard_moves_logic + else: + world.goal_logic_mapping = goal_hard_logic # Completion condition. - world.multiworld.completion_condition[world.player] = lambda state: (state.has(ItemName.strawberry,world.player,world.options.strawberries_required.value) and - state.has_all({ItemName.feather, - ItemName.traffic_block, - ItemName.breakables, - ItemName.dash_refill, - ItemName.double_dash_refill}, world.player)) + world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world) + + +goal_standard_logic: List[List[str]] = [[ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.double_dash_refill]] +goal_hard_logic: List[List[str]] = [[]] +goal_standard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]] +goal_hard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], + [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], + [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]] + + +location_standard_logic: Dict[str, List[List[str]]] = { + LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables]], + LocationName.strawberry_6: [[ItemName.dash_refill], + [ItemName.traffic_block]], + LocationName.strawberry_7: [[ItemName.dash_refill], + [ItemName.traffic_block]], + LocationName.strawberry_8: [[ItemName.traffic_block]], + LocationName.strawberry_9: [[ItemName.dash_refill]], + LocationName.strawberry_11: [[ItemName.dash_refill], + [ItemName.traffic_block]], + LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill], + [ItemName.traffic_block, ItemName.double_dash_refill]], + LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables], + [ItemName.traffic_block, ItemName.breakables]], + LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather], + [ItemName.traffic_block, ItemName.feather]], + LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather], + [ItemName.traffic_block, ItemName.feather]], + LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather], + [ItemName.traffic_block, ItemName.feather]], + LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]], + LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill], + [ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]], + LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring], + [ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring]], + LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables], + [ItemName.traffic_block, ItemName.feather, ItemName.breakables]], + + LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]], + LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables]], + LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin], + [ItemName.cassette, ItemName.traffic_block, ItemName.coin]], + LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block]], + LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill], + [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]], + LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill], + [ItemName.cassette, ItemName.traffic_block]], + LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin], + [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]], + LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin], + [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]], + LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin]], + LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables]], + + LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables]], + LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables]], + LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables]], + LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], + LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], + LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], + + LocationName.sign_2: [[ItemName.breakables]], + LocationName.sign_3: [[ItemName.dash_refill], + [ItemName.traffic_block]], + LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill], + [ItemName.dash_refill, ItemName.feather], + [ItemName.traffic_block, ItemName.feather]], + LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], + + LocationName.car_2: [[ItemName.breakables]], +} + +location_hard_logic: Dict[str, List[List[str]]] = { + LocationName.strawberry_13: [[ItemName.breakables]], + LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]], + LocationName.strawberry_20: [[ItemName.breakables]], + + LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]], + LocationName.strawberry_22: [[ItemName.cassette]], + LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin]], + LocationName.strawberry_24: [[ItemName.cassette]], + LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill]], + LocationName.strawberry_26: [[ItemName.cassette]], + LocationName.strawberry_27: [[ItemName.cassette]], + LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather]], + LocationName.strawberry_29: [[ItemName.cassette]], + LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables]], + + LocationName.sign_2: [[ItemName.breakables]], + + LocationName.car_2: [[ItemName.breakables]], +} + +location_standard_moves_logic: Dict[str, List[List[str]]] = { + LocationName.strawberry_1: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + LocationName.strawberry_2: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + LocationName.strawberry_3: [[ItemName.air_dash], + [ItemName.skid_jump]], + LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_5: [[ItemName.air_dash]], + LocationName.strawberry_6: [[ItemName.dash_refill, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash], + [ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.skid_jump], + [ItemName.traffic_block, ItemName.climb]], + LocationName.strawberry_7: [[ItemName.dash_refill, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash], + [ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.skid_jump], + [ItemName.traffic_block, ItemName.climb]], + LocationName.strawberry_8: [[ItemName.traffic_block, ItemName.ground_dash], + [ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.skid_jump], + [ItemName.traffic_block, ItemName.climb]], + LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]], + LocationName.strawberry_10: [[ItemName.climb]], + LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.climb]], + LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash], + [ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]], + LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash], + [ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash], + [ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash], + [ItemName.traffic_block, ItemName.feather, ItemName.air_dash]], + LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.feather, ItemName.climb]], + LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash], + [ItemName.traffic_block, ItemName.feather]], + LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.ground_dash], + [ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.air_dash], + [ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.skid_jump], + [ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.climb]], + LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash], + [ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring, ItemName.air_dash]], + LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables, ItemName.air_dash], + [ItemName.traffic_block, ItemName.feather, ItemName.breakables, ItemName.air_dash]], + + LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.climb], + [ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]], + LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb], + [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb], + [ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash], + [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash]], + LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb], + [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]], + LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]], + + LocationName.granny_1: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + LocationName.granny_2: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + LocationName.granny_3: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], + LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], + LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], + + LocationName.sign_1: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash], + [ItemName.breakables, ItemName.air_dash]], + LocationName.sign_3: [[ItemName.dash_refill, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash], + [ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.skid_jump], + [ItemName.traffic_block, ItemName.climb]], + LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash], + [ItemName.dash_refill, ItemName.feather, ItemName.air_dash], + [ItemName.traffic_block, ItemName.feather, ItemName.ground_dash], + [ItemName.traffic_block, ItemName.feather, ItemName.air_dash], + [ItemName.traffic_block, ItemName.feather, ItemName.skid_jump], + [ItemName.traffic_block, ItemName.feather, ItemName.climb]], + LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], + + LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash], + [ItemName.breakables, ItemName.air_dash]], +} + +location_hard_moves_logic: Dict[str, List[List[str]]] = { + LocationName.strawberry_3: [[ItemName.air_dash], + [ItemName.skid_jump]], + LocationName.strawberry_5: [[ItemName.ground_dash], + [ItemName.air_dash]], + LocationName.strawberry_8: [[ItemName.traffic_block], + [ItemName.ground_dash, ItemName.air_dash]], + LocationName.strawberry_10: [[ItemName.air_dash], + [ItemName.climb]], + LocationName.strawberry_11: [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump]], + LocationName.strawberry_12: [[ItemName.feather], + [ItemName.ground_dash], + [ItemName.air_dash]], + LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash], + [ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash], + [ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_15: [[ItemName.feather], + [ItemName.ground_dash, ItemName.air_dash]], + LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]], + LocationName.strawberry_18: [[ItemName.air_dash, ItemName.climb], + [ItemName.double_dash_refill, ItemName.air_dash]], + LocationName.strawberry_19: [[ItemName.air_dash, ItemName.skid_jump], + [ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash], + [ItemName.spring, ItemName.ground_dash, ItemName.air_dash]], + LocationName.strawberry_20: [[ItemName.breakables, ItemName.air_dash]], + + LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_22: [[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash], + [ItemName.cassette, ItemName.dash_refill, ItemName.air_dash]], + LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash]], + LocationName.strawberry_24: [[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash], + [ItemName.cassette, ItemName.traffic_block, ItemName.air_dash]], + LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_26: [[ItemName.cassette, ItemName.ground_dash], + [ItemName.cassette, ItemName.air_dash]], + LocationName.strawberry_27: [[ItemName.cassette, ItemName.air_dash, ItemName.skid_jump], + [ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash], + [ItemName.cassette, ItemName.coin, ItemName.ground_dash], + [ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash]], + LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.air_dash], + [ItemName.cassette, ItemName.feather, ItemName.climb]], + LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump], + [ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]], + LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash, ItemName.air_dash, ItemName.climb, ItemName.skid_jump], + [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.feather, ItemName.air_dash, ItemName.climb, ItemName.skid_jump], + [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.ground_dash, ItemName.air_dash, ItemName.climb], + [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.feather, ItemName.air_dash, ItemName.climb]], + + LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], + [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], + [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], + LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], + [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], + [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], + LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], + [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], + [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], + + LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash], + [ItemName.breakables, ItemName.air_dash]], + LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], + [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], + [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], + [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], + [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], + + LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash], + [ItemName.breakables, ItemName.air_dash]], +} + + +def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool: + + if loc not in world.active_logic_mapping: + return True + + for possible_access in world.active_logic_mapping[loc]: + if state.has_all(possible_access, world.player): + return True + + return False + +def goal_rule(state: CollectionState, world: Celeste64World) -> bool: + if not state.has(ItemName.strawberry, world.player, world.strawberries_required): + return False + + for possible_access in world.goal_logic_mapping: + if state.has_all(possible_access, world.player): + return True + + return False diff --git a/worlds/celeste64/__init__.py b/worlds/celeste64/__init__.py index 0d3b5d015829..7786e381230a 100644 --- a/worlds/celeste64/__init__.py +++ b/worlds/celeste64/__init__.py @@ -1,11 +1,13 @@ -from typing import List +from copy import deepcopy +from typing import Dict, List -from BaseClasses import ItemClassification, Region, Tutorial +from BaseClasses import ItemClassification, Location, Region, Tutorial from worlds.AutoWorld import WebWorld, World -from .Items import Celeste64Item, item_data_table, item_table -from .Locations import Celeste64Location, location_data_table, location_table -from .Names import ItemName -from .Options import Celeste64Options +from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table +from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\ + sign_location_data_table, car_location_data_table, location_table +from .Names import ItemName, LocationName +from .Options import Celeste64Options, celeste_64_option_groups class Celeste64WebWorld(WebWorld): @@ -22,11 +24,14 @@ class Celeste64WebWorld(WebWorld): tutorials = [setup_en] + option_groups = celeste_64_option_groups + class Celeste64World(World): """Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer. Created in a week(ish) by the Celeste team to celebrate the game’s sixth anniversary ðŸ“✨""" + # Class Data game = "Celeste 64" web = Celeste64WebWorld() options_dataclass = Celeste64Options @@ -34,13 +39,18 @@ class Celeste64World(World): location_name_to_id = location_table item_name_to_id = item_table + # Instance Data + strawberries_required: int + active_logic_mapping: Dict[str, List[List[str]]] + goal_logic_mapping: Dict[str, List[List[str]]] + def create_item(self, name: str) -> Celeste64Item: # Only make required amount of strawberries be Progression - if getattr(self, "options", None) and name == ItemName.strawberry: + if getattr(self, "strawberries_required", None) and name == ItemName.strawberry: classification: ItemClassification = ItemClassification.filler self.prog_strawberries = getattr(self, "prog_strawberries", 0) - if self.prog_strawberries < self.options.strawberries_required.value: + if self.prog_strawberries < self.strawberries_required: classification = ItemClassification.progression_skip_balancing self.prog_strawberries += 1 @@ -51,9 +61,48 @@ def create_item(self, name: str) -> Celeste64Item: def create_items(self) -> None: item_pool: List[Celeste64Item] = [] - item_pool += [self.create_item(name) for name in item_data_table.keys()] + location_count: int = 30 + + if self.options.friendsanity: + location_count += 9 + + if self.options.signsanity: + location_count += 5 + + if self.options.carsanity: + location_count += 2 + + item_pool += [self.create_item(name) + for name in unlockable_item_data_table.keys() + if name not in self.options.start_inventory] + + if self.options.move_shuffle: + move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys())) + + if self.options.logic_difficulty == "standard": + # If the start_inventory already includes a move, don't worry about giving it one + if not [move for move in move_items_for_itempool if move in self.options.start_inventory]: + chosen_start_move = self.random.choice(move_items_for_itempool) + move_items_for_itempool.remove(chosen_start_move) - item_pool += [self.create_item(ItemName.strawberry) for _ in range(21)] + if self.options.carsanity: + intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player) + intro_car_loc.place_locked_item(self.create_item(chosen_start_move)) + location_count -= 1 + else: + self.multiworld.push_precollected(self.create_item(chosen_start_move)) + + item_pool += [self.create_item(name) + for name in move_items_for_itempool + if name not in self.options.start_inventory] + + real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool)) + self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100)) + + item_pool += [self.create_item(ItemName.strawberry) for _ in range(real_total_strawberries)] + + filler_item_count: int = location_count - len(item_pool) + item_pool += [self.create_item(ItemName.raspberry) for _ in range(filler_item_count)] self.multiworld.itempool += item_pool @@ -69,14 +118,33 @@ def create_regions(self) -> None: for region_name, region_data in region_data_table.items(): region = self.multiworld.get_region(region_name, self.player) region.add_locations({ - location_name: location_data.address for location_name, location_data in location_data_table.items() + location_name: location_data.address for location_name, location_data in strawberry_location_data_table.items() if location_data.region == region_name }, Celeste64Location) + + if self.options.friendsanity: + region.add_locations({ + location_name: location_data.address for location_name, location_data in friend_location_data_table.items() + if location_data.region == region_name + }, Celeste64Location) + + if self.options.signsanity: + region.add_locations({ + location_name: location_data.address for location_name, location_data in sign_location_data_table.items() + if location_data.region == region_name + }, Celeste64Location) + + if self.options.carsanity: + region.add_locations({ + location_name: location_data.address for location_name, location_data in car_location_data_table.items() + if location_data.region == region_name + }, Celeste64Location) + region.add_exits(region_data_table[region_name].connecting_regions) def get_filler_item_name(self) -> str: - return ItemName.strawberry + return ItemName.raspberry def set_rules(self) -> None: @@ -88,5 +156,12 @@ def fill_slot_data(self): return { "death_link": self.options.death_link.value, "death_link_amnesty": self.options.death_link_amnesty.value, - "strawberries_required": self.options.strawberries_required.value + "strawberries_required": self.strawberries_required, + "move_shuffle": self.options.move_shuffle.value, + "friendsanity": self.options.friendsanity.value, + "signsanity": self.options.signsanity.value, + "carsanity": self.options.carsanity.value, + "badeline_chaser_source": self.options.badeline_chaser_source.value, + "badeline_chaser_frequency": self.options.badeline_chaser_frequency.value, + "badeline_chaser_speed": self.options.badeline_chaser_speed.value, } diff --git a/worlds/celeste64/docs/guide_en.md b/worlds/celeste64/docs/guide_en.md index 116a3b13e91b..24fea92e3528 100644 --- a/worlds/celeste64/docs/guide_en.md +++ b/worlds/celeste64/docs/guide_en.md @@ -3,6 +3,11 @@ ## Required Software - Archipelago Build of Celeste 64 from: [Celeste 64 Archipelago Releases Page](https://github.com/PoryGoneDev/Celeste64/releases/) +## Optional Software +- Celeste 64 Tracker + - PopTracker from: [PopTracker Releases Page](https://github.com/black-sliver/PopTracker/releases/) + - Celeste 64 Archipelago PopTracker pack from: [Celeste 64 AP Tracker Releases Page](https://github.com/PoryGone/Celeste-64-AP-Tracker/releases/) + ## Installation Procedures (Windows) 1. Download the above release and extract it. diff --git a/worlds/checksfinder/Locations.py b/worlds/checksfinder/Locations.py index 8a2ae07b27b6..59a96c83ea8a 100644 --- a/worlds/checksfinder/Locations.py +++ b/worlds/checksfinder/Locations.py @@ -10,10 +10,6 @@ class AdvData(typing.NamedTuple): class ChecksFinderAdvancement(Location): game: str = "ChecksFinder" - def __init__(self, player: int, name: str, address: typing.Optional[int], parent): - super().__init__(player, name, address, parent) - self.event = not address - advancement_table = { "Tile 1": AdvData(81000, 'Board'), diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py index b70c65bb08f5..c8b9587f8500 100644 --- a/worlds/checksfinder/__init__.py +++ b/worlds/checksfinder/__init__.py @@ -33,8 +33,6 @@ class ChecksFinderWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {name: data.id for name, data in advancement_table.items()} - data_version = 4 - def _get_checksfinder_data(self): return { 'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32), diff --git a/worlds/checksfinder/docs/en_ChecksFinder.md b/worlds/checksfinder/docs/en_ChecksFinder.md index 96fb0529df64..c9569376c5f6 100644 --- a/worlds/checksfinder/docs/en_ChecksFinder.md +++ b/worlds/checksfinder/docs/en_ChecksFinder.md @@ -1,8 +1,8 @@ # ChecksFinder -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What is considered a location check in ChecksFinder? diff --git a/worlds/checksfinder/docs/setup_en.md b/worlds/checksfinder/docs/setup_en.md index 77eca6f71b34..673b34900af7 100644 --- a/worlds/checksfinder/docs/setup_en.md +++ b/worlds/checksfinder/docs/setup_en.md @@ -15,7 +15,7 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a YAML file? -You can customize your settings by visiting the [ChecksFinder Player Settings Page](/games/ChecksFinder/player-settings) +You can customize your options by visiting the [ChecksFinder Player Options Page](/games/ChecksFinder/player-options) ### Generating a ChecksFinder game diff --git a/worlds/clique/__init__.py b/worlds/clique/__init__.py index 30c0e47f818e..b5cc74d94ac0 100644 --- a/worlds/clique/__init__.py +++ b/worlds/clique/__init__.py @@ -37,7 +37,6 @@ class CliqueWorld(World): """The greatest game of all time.""" game = "Clique" - data_version = 3 web = CliqueWebWorld() option_definitions = clique_options location_name_to_id = location_table diff --git a/worlds/clique/docs/en_Clique.md b/worlds/clique/docs/en_Clique.md index 862454f5c613..e9cb164fecbf 100644 --- a/worlds/clique/docs/en_Clique.md +++ b/worlds/clique/docs/en_Clique.md @@ -10,7 +10,7 @@ wait for someone else in the multiworld to "activate" their button before they c Clique can be played on most modern HTML5-capable browsers. -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. diff --git a/worlds/cv64/__init__.py b/worlds/cv64/__init__.py new file mode 100644 index 000000000000..0d384acc8f3d --- /dev/null +++ b/worlds/cv64/__init__.py @@ -0,0 +1,317 @@ +import os +import typing +import settings +import base64 +import logging + +from BaseClasses import Item, Region, Tutorial, ItemClassification +from .items import CV64Item, filler_item_names, get_item_info, get_item_names_to_ids, get_item_counts +from .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id +from .entrances import verify_entrances, get_warp_entrances +from .options import CV64Options, cv64_option_groups, CharacterStages, DraculasCondition, SubWeaponShuffle +from .stages import get_locations_from_stage, get_normal_stage_exits, vanilla_stage_order, \ + shuffle_stages, generate_warps, get_region_names +from .regions import get_region_info +from .rules import CV64Rules +from .data import iname, rname, ename +from worlds.AutoWorld import WebWorld, World +from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \ + randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \ + get_countdown_numbers +from .rom import RomData, write_patch, get_base_rom_path, CV64ProcedurePatch, CV64_US_10_HASH +from .client import Castlevania64Client + + +class CV64Settings(settings.Group): + class RomFile(settings.UserFilePath): + """File name of the CV64 US 1.0 rom""" + copy_to = "Castlevania (USA).z64" + description = "CV64 (US 1.0) ROM File" + md5s = [CV64_US_10_HASH] + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class CV64Web(WebWorld): + theme = "stone" + + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Archipleago Castlevania 64 randomizer on your computer and connecting it to a " + "multiworld.", + "English", + "setup_en.md", + "setup/en", + ["Liquid Cat"] + )] + + option_groups = cv64_option_groups + + +class CV64World(World): + """ + Castlevania for the Nintendo 64 is the first 3D game in the Castlevania franchise. As either whip-wielding Belmont + descendant Reinhardt Schneider or powerful sorceress Carrie Fernandez, brave many terrifying traps and foes as you + make your way to Dracula's chamber and stop his rule of terror! + """ + game = "Castlevania 64" + item_name_groups = { + "Bomb": {iname.magical_nitro, iname.mandragora}, + "Ingredient": {iname.magical_nitro, iname.mandragora}, + } + location_name_groups = {stage: set(get_locations_from_stage(stage)) for stage in vanilla_stage_order} + options_dataclass = CV64Options + options: CV64Options + settings: typing.ClassVar[CV64Settings] + topology_present = True + + item_name_to_id = get_item_names_to_ids() + location_name_to_id = get_location_names_to_ids() + + active_stage_exits: typing.Dict[str, typing.Dict] + active_stage_list: typing.List[str] + active_warp_list: typing.List[str] + + # Default values to possibly be updated in generate_early + reinhardt_stages: bool = True + carrie_stages: bool = True + branching_stages: bool = False + starting_stage: str = rname.forest_of_silence + total_s1s: int = 7 + s1s_per_warp: int = 1 + total_s2s: int = 0 + required_s2s: int = 0 + drac_condition: int = 0 + + auth: bytearray + + web = CV64Web() + + def generate_early(self) -> None: + # Generate the player's unique authentication + self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16)) + + self.total_s1s = self.options.total_special1s.value + self.s1s_per_warp = self.options.special1s_per_warp.value + self.drac_condition = self.options.draculas_condition.value + + # If there are more S1s needed to unlock the whole warp menu than there are S1s in total, drop S1s per warp to + # something manageable. + if self.s1s_per_warp * 7 > self.total_s1s: + self.s1s_per_warp = self.total_s1s // 7 + logging.warning(f"[{self.multiworld.player_name[self.player]}] Too many required Special1s " + f"({self.options.special1s_per_warp.value * 7}) for Special1s Per Warp setting: " + f"{self.options.special1s_per_warp.value} with Total Special1s setting: " + f"{self.options.total_special1s.value}. Lowering Special1s Per Warp to: " + f"{self.s1s_per_warp}") + self.options.special1s_per_warp.value = self.s1s_per_warp + + # Set the total and required Special2s to 1 if the drac condition is the Crystal, to the specified YAML numbers + # if it's Specials, or to 0 if it's None or Bosses. The boss totals will be figured out later. + if self.drac_condition == DraculasCondition.option_crystal: + self.total_s2s = 1 + self.required_s2s = 1 + elif self.drac_condition == DraculasCondition.option_specials: + self.total_s2s = self.options.total_special2s.value + self.required_s2s = int(self.options.percent_special2s_required.value / 100 * self.total_s2s) + + # Enable/disable character stages and branching paths accordingly + if self.options.character_stages == CharacterStages.option_reinhardt_only: + self.carrie_stages = False + elif self.options.character_stages == CharacterStages.option_carrie_only: + self.reinhardt_stages = False + elif self.options.character_stages == CharacterStages.option_both: + self.branching_stages = True + + self.active_stage_exits = get_normal_stage_exits(self) + + stage_1_blacklist = [] + + # Prevent Clock Tower from being Stage 1 if more than 4 S1s are needed to warp out of it. + if self.s1s_per_warp > 4 and not self.options.multi_hit_breakables: + stage_1_blacklist.append(rname.clock_tower) + + # Shuffle the stages if the option is on. + if self.options.stage_shuffle: + self.active_stage_exits, self.starting_stage, self.active_stage_list = \ + shuffle_stages(self, stage_1_blacklist) + else: + self.active_stage_list = [stage for stage in vanilla_stage_order if stage in self.active_stage_exits] + + # Create a list of warps from the active stage list. They are in a random order by default and will never + # include the starting stage. + self.active_warp_list = generate_warps(self) + + def create_regions(self) -> None: + # Add the Menu Region. + created_regions = [Region("Menu", self.player, self.multiworld)] + + # Add every stage Region by checking to see if that stage is active. + created_regions.extend([Region(name, self.player, self.multiworld) + for name in get_region_names(self.active_stage_exits)]) + + # Add the Renon's shop Region if shopsanity is on. + if self.options.shopsanity: + created_regions.append(Region(rname.renon, self.player, self.multiworld)) + + # Add the Dracula's chamber (the end) Region. + created_regions.append(Region(rname.ck_drac_chamber, self.player, self.multiworld)) + + # Set up the Regions correctly. + self.multiworld.regions.extend(created_regions) + + # Add the warp Entrances to the Menu Region (the one always at the start of the Region list). + created_regions[0].add_exits(get_warp_entrances(self.active_warp_list)) + + for reg in created_regions: + + # Add the Entrances to all the Regions. + ent_names = get_region_info(reg.name, "entrances") + if ent_names is not None: + reg.add_exits(verify_entrances(self.options, ent_names, self.active_stage_exits)) + + # Add the Locations to all the Regions. + loc_names = get_region_info(reg.name, "locations") + if loc_names is None: + continue + verified_locs, events = verify_locations(self.options, loc_names) + reg.add_locations(verified_locs, CV64Location) + + # Place event Items on all of their associated Locations. + for event_loc, event_item in events.items(): + self.get_location(event_loc).place_locked_item(self.create_item(event_item, "progression")) + # If we're looking at a boss kill trophy, increment the total S2s and, if we're not already at the + # set number of required bosses, the total required number. This way, we can prevent gen failures + # should the player set more bosses required than there are total. + if event_item == iname.trophy: + self.total_s2s += 1 + if self.required_s2s < self.options.bosses_required.value: + self.required_s2s += 1 + + # If Dracula's Condition is Bosses and there are less calculated required S2s than the value specified by the + # player (meaning there weren't enough bosses to reach the player's setting), throw a warning and lower the + # option value. + if self.options.draculas_condition == DraculasCondition.option_bosses and self.required_s2s < \ + self.options.bosses_required.value: + logging.warning(f"[{self.multiworld.player_name[self.player]}] Not enough bosses for Bosses Required " + f"setting: {self.options.bosses_required.value}. Lowering to: {self.required_s2s}") + self.options.bosses_required.value = self.required_s2s + + def create_item(self, name: str, force_classification: typing.Optional[str] = None) -> Item: + if force_classification is not None: + classification = getattr(ItemClassification, force_classification) + else: + classification = getattr(ItemClassification, get_item_info(name, "default classification")) + + code = get_item_info(name, "code") + if code is not None: + code += base_id + + created_item = CV64Item(name, classification, code, self.player) + + return created_item + + def create_items(self) -> None: + item_counts = get_item_counts(self) + + # Set up the items correctly + self.multiworld.itempool += [self.create_item(item, classification) for classification in item_counts for item + in item_counts[classification] for _ in range(item_counts[classification][item])] + + def set_rules(self) -> None: + # Set all the Entrance rules properly. + CV64Rules(self).set_cv64_rules() + + def pre_fill(self) -> None: + # If we need more Special1s to warp out of Sphere 1 than there are locations available, then AP's fill + # algorithm may try placing the Special1s anyway despite placing the stage's single key always being an option. + # To get around this problem in the fill algorithm, the keys will be forced early in these situations to ensure + # the algorithm will pick them over the Special1s. + if self.starting_stage == rname.tower_of_science: + if self.s1s_per_warp > 3: + self.multiworld.local_early_items[self.player][iname.science_key2] = 1 + elif self.starting_stage == rname.clock_tower: + if (self.s1s_per_warp > 2 and not self.options.multi_hit_breakables) or \ + (self.s1s_per_warp > 8 and self.options.multi_hit_breakables): + self.multiworld.local_early_items[self.player][iname.clocktower_key1] = 1 + elif self.starting_stage == rname.castle_wall: + if self.s1s_per_warp > 5 and not self.options.hard_logic and \ + not self.options.multi_hit_breakables: + self.multiworld.local_early_items[self.player][iname.left_tower_key] = 1 + + def generate_output(self, output_directory: str) -> None: + active_locations = self.multiworld.get_locations(self.player) + + # Location data and shop names, descriptions, and colors + offset_data, shop_name_list, shop_colors_list, shop_desc_list = \ + get_location_data(self, active_locations) + # Shop prices + if self.options.shop_prices: + offset_data.update(randomize_shop_prices(self)) + # Map lighting + if self.options.map_lighting: + offset_data.update(randomize_lighting(self)) + # Sub-weapons + if self.options.sub_weapon_shuffle == SubWeaponShuffle.option_own_pool: + offset_data.update(shuffle_sub_weapons(self)) + elif self.options.sub_weapon_shuffle == SubWeaponShuffle.option_anywhere: + offset_data.update(rom_sub_weapon_flags) + # Empty breakables + if self.options.empty_breakables: + offset_data.update(rom_empty_breakables_flags) + # Music + if self.options.background_music: + offset_data.update(randomize_music(self)) + # Loading zones + offset_data.update(get_loading_zone_bytes(self.options, self.starting_stage, self.active_stage_exits)) + # Countdown + if self.options.countdown: + offset_data.update(get_countdown_numbers(self.options, active_locations)) + # Start Inventory + offset_data.update(get_start_inventory_data(self.player, self.options, + self.multiworld.precollected_items[self.player])) + + patch = CV64ProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player]) + write_patch(self, patch, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations) + + rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}" + f"{patch.patch_file_ending}") + + patch.write(rom_path) + + def get_filler_item_name(self) -> str: + return self.random.choice(filler_item_names) + + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): + # Attach each location's stage's position to its hint information if Stage Shuffle is on. + if not self.options.stage_shuffle: + return + + stage_pos_data = {} + for loc in list(self.multiworld.get_locations(self.player)): + stage = get_region_info(loc.parent_region.name, "stage") + if stage is not None and loc.address is not None: + num = str(self.active_stage_exits[stage]["position"]).zfill(2) + path = self.active_stage_exits[stage]["path"] + stage_pos_data[loc.address] = f"Stage {num}" + if path != " ": + stage_pos_data[loc.address] += path + hint_data[self.player] = stage_pos_data + + def modify_multidata(self, multidata: typing.Dict[str, typing.Any]): + # Put the player's unique authentication in connect_names. + multidata["connect_names"][base64.b64encode(self.auth).decode("ascii")] = \ + multidata["connect_names"][self.multiworld.player_name[self.player]] + + def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: + # Write the stage order to the spoiler log + spoiler_handle.write(f"\nCastlevania 64 stage & warp orders for {self.multiworld.player_name[self.player]}:\n") + for stage in self.active_stage_list: + num = str(self.active_stage_exits[stage]["position"]).zfill(2) + path = self.active_stage_exits[stage]["path"] + spoiler_handle.writelines(f"Stage {num}{path}:\t{stage}\n") + + # Write the warp order to the spoiler log + spoiler_handle.writelines(f"\nStart :\t{self.active_stage_list[0]}\n") + for i in range(1, len(self.active_warp_list)): + spoiler_handle.writelines(f"Warp {i}:\t{self.active_warp_list[i]}\n") diff --git a/worlds/cv64/aesthetics.py b/worlds/cv64/aesthetics.py new file mode 100644 index 000000000000..66709174d837 --- /dev/null +++ b/worlds/cv64/aesthetics.py @@ -0,0 +1,653 @@ +import logging + +from BaseClasses import ItemClassification, Location, Item +from .data import iname, rname +from .options import CV64Options, BackgroundMusic, Countdown, IceTrapAppearance, InvisibleItems, CharacterStages +from .stages import vanilla_stage_order, get_stage_info +from .locations import get_location_info, base_id +from .regions import get_region_info +from .items import get_item_info, item_info + +from typing import TYPE_CHECKING, Dict, List, Tuple, Union, Iterable + +if TYPE_CHECKING: + from . import CV64World + +rom_sub_weapon_offsets = { + 0x10C6EB: (b"\x10", rname.forest_of_silence), # Forest + 0x10C6F3: (b"\x0F", rname.forest_of_silence), + 0x10C6FB: (b"\x0E", rname.forest_of_silence), + 0x10C703: (b"\x0D", rname.forest_of_silence), + + 0x10C81F: (b"\x0F", rname.castle_wall), # Castle Wall + 0x10C827: (b"\x10", rname.castle_wall), + 0x10C82F: (b"\x0E", rname.castle_wall), + 0x7F9A0F: (b"\x0D", rname.castle_wall), + + 0x83A5D9: (b"\x0E", rname.villa), # Villa + 0x83A5E5: (b"\x0D", rname.villa), + 0x83A5F1: (b"\x0F", rname.villa), + 0xBFC903: (b"\x10", rname.villa), + 0x10C987: (b"\x10", rname.villa), + 0x10C98F: (b"\x0D", rname.villa), + 0x10C997: (b"\x0F", rname.villa), + 0x10CF73: (b"\x10", rname.villa), + + 0x10CA57: (b"\x0D", rname.tunnel), # Tunnel + 0x10CA5F: (b"\x0E", rname.tunnel), + 0x10CA67: (b"\x10", rname.tunnel), + 0x10CA6F: (b"\x0D", rname.tunnel), + 0x10CA77: (b"\x0F", rname.tunnel), + 0x10CA7F: (b"\x0E", rname.tunnel), + + 0x10CBC7: (b"\x0E", rname.castle_center), # Castle Center + 0x10CC0F: (b"\x0D", rname.castle_center), + 0x10CC5B: (b"\x0F", rname.castle_center), + + 0x10CD3F: (b"\x0E", rname.tower_of_execution), # Character towers + 0x10CD65: (b"\x0D", rname.tower_of_execution), + 0x10CE2B: (b"\x0E", rname.tower_of_science), + 0x10CE83: (b"\x10", rname.duel_tower), + + 0x10CF8B: (b"\x0F", rname.room_of_clocks), # Room of Clocks + 0x10CF93: (b"\x0D", rname.room_of_clocks), + + 0x99BC5A: (b"\x0D", rname.clock_tower), # Clock Tower + 0x10CECB: (b"\x10", rname.clock_tower), + 0x10CED3: (b"\x0F", rname.clock_tower), + 0x10CEDB: (b"\x0E", rname.clock_tower), + 0x10CEE3: (b"\x0D", rname.clock_tower), +} + +rom_sub_weapon_flags = { + 0x10C6EC: b"\x02\x00\xFF\x04", # Forest of Silence + 0x10C6FC: b"\x04\x00\xFF\x04", + 0x10C6F4: b"\x08\x00\xFF\x04", + 0x10C704: b"\x40\x00\xFF\x04", + + 0x10C831: b"\x08", # Castle Wall + 0x10C829: b"\x10", + 0x10C821: b"\x20", + 0xBFCA97: b"\x04", + + # Villa + 0xBFC926: b"\xFF\x04", + 0xBFC93A: b"\x80", + 0xBFC93F: b"\x01", + 0xBFC943: b"\x40", + 0xBFC947: b"\x80", + 0x10C989: b"\x10", + 0x10C991: b"\x20", + 0x10C999: b"\x40", + 0x10CF77: b"\x80", + + 0x10CA58: b"\x40\x00\xFF\x0E", # Tunnel + 0x10CA6B: b"\x80", + 0x10CA60: b"\x10\x00\xFF\x05", + 0x10CA70: b"\x20\x00\xFF\x05", + 0x10CA78: b"\x40\x00\xFF\x05", + 0x10CA80: b"\x80\x00\xFF\x05", + + 0x10CBCA: b"\x02", # Castle Center + 0x10CC10: b"\x80", + 0x10CC5C: b"\x40", + + 0x10CE86: b"\x01", # Duel Tower + 0x10CD43: b"\x02", # Tower of Execution + 0x10CE2E: b"\x20", # Tower of Science + + 0x10CF8E: b"\x04", # Room of Clocks + 0x10CF96: b"\x08", + + 0x10CECE: b"\x08", # Clock Tower + 0x10CED6: b"\x10", + 0x10CEE6: b"\x20", + 0x10CEDE: b"\x80", +} + +rom_empty_breakables_flags = { + 0x10C74D: b"\x40\xFF\x05", # Forest of Silence + 0x10C765: b"\x20\xFF\x0E", + 0x10C774: b"\x08\x00\xFF\x0E", + 0x10C755: b"\x80\xFF\x05", + 0x10C784: b"\x01\x00\xFF\x0E", + 0x10C73C: b"\x02\x00\xFF\x0E", + + 0x10C8D0: b"\x04\x00\xFF\x0E", # Villa foyer + + 0x10CF9F: b"\x08", # Room of Clocks flags + 0x10CFA7: b"\x01", + 0xBFCB6F: b"\x04", # Room of Clocks candle property IDs + 0xBFCB73: b"\x05", +} + +rom_axe_cross_lower_values = { + 0x6: [0x7C7F97, 0x07], # Forest + 0x8: [0x7C7FA6, 0xF9], + + 0x30: [0x83A60A, 0x71], # Villa hallway + 0x27: [0x83A617, 0x26], + 0x2C: [0x83A624, 0x6E], + + 0x16C: [0x850FE6, 0x07], # Villa maze + + 0x10A: [0x8C44D3, 0x08], # CC factory floor + 0x109: [0x8C44E1, 0x08], + + 0x74: [0x8DF77C, 0x07], # CC invention area + 0x60: [0x90FD37, 0x43], + 0x55: [0xBFCC2B, 0x43], + 0x65: [0x90FBA1, 0x51], + 0x64: [0x90FBAD, 0x50], + 0x61: [0x90FE56, 0x43] +} + +rom_looping_music_fade_ins = { + 0x10: None, + 0x11: None, + 0x12: None, + 0x13: None, + 0x14: None, + 0x15: None, + 0x16: 0x17, + 0x18: 0x19, + 0x1A: 0x1B, + 0x21: 0x75, + 0x27: None, + 0x2E: 0x23, + 0x39: None, + 0x45: 0x63, + 0x56: None, + 0x57: 0x58, + 0x59: None, + 0x5A: None, + 0x5B: 0x5C, + 0x5D: None, + 0x5E: None, + 0x5F: None, + 0x60: 0x61, + 0x62: None, + 0x64: None, + 0x65: None, + 0x66: None, + 0x68: None, + 0x69: None, + 0x6D: 0x78, + 0x6E: None, + 0x6F: None, + 0x73: None, + 0x74: None, + 0x77: None, + 0x79: None +} + +music_sfx_ids = [0x1C, 0x4B, 0x4C, 0x4D, 0x4E, 0x55, 0x6C, 0x76] + +renon_item_dialogue = { + 0x02: "More Sub-weapon uses!\n" + "Just what you need!", + 0x03: "Galamoth told me it's\n" + "a heart in other times.", + 0x04: "Who needs Warp Rooms\n" + "when you have these?", + 0x05: "I was told to safeguard\n" + "this, but I dunno why.", + 0x06: "Fresh off a Behemoth!\n" + "Those cows are weird.", + 0x07: "Preserved with special\n" + " wall-based methods.", + 0x08: "Don't tell Geneva\n" + "about this...", + 0x09: "If this existed in 1094,\n" + "that whip wouldn't...", + 0x0A: "For when some lizard\n" + "brain spits on your ego.", + 0x0C: "It'd be a shame if you\n" + "lost it immediately...", + 0x10C: "No consequences should\n" + "you perish with this!", + 0x0D: "Arthur was far better\n" + "with it than you!", + 0x0E: "Night Creatures handle\n" + "with care!", + 0x0F: "Some may call it a\n" + "\"Banshee Boomerang.\"", + 0x10: "No weapon triangle\n" + "advantages with this.", + 0x12: "It looks sus? Trust me," + "my wares are genuine.", + 0x15: "This non-volatile kind\n" + "is safe to handle.", + 0x16: "If you can soul-wield,\n" + "they have a good one!", + 0x17: "Calls the morning sun\n" + "to vanquish the night.", + 0x18: "1 on-demand horrible\n" + "night. Devils love it!", + 0x1A: "Want to study here?\n" + "It will cost you.", + 0x1B: "\"Let them eat cake!\"\n" + "Said no princess ever.", + 0x1C: "Why do I suspect this\n" + "was a toilet room?", + 0x1D: "When you see Coller,\n" + "tell him I said hi!", + 0x1E: "Atomic number is 29\n" + "and weight is 63.546.", + 0x1F: "One torture per pay!\n" + "Who will it be?", + 0x20: "Being here feels like\n" + "time is slowing down.", + 0x21: "Only one thing beind\n" + "this. Do you dare?", + 0x22: "The key 2 Science!\n" + "Both halves of it!", + 0x23: "This warehouse can\n" + "be yours for a fee.", + 0x24: "Long road ahead if you\n" + "don't have the others.", + 0x25: "Will you get the curse\n" + "of eternal burning?", + 0x26: "What's beyond time?\n" + "Find out your", + 0x27: "Want to take out a\n" + "loan? By all means!", + 0x28: "The bag is green,\n" + "so it must be lucky!", + 0x29: "(Does this fool realize?)\n" + "Oh, sorry.", + "prog": "They will absolutely\n" + "need it in time!", + "useful": "Now, this would be\n" + "useful to send...", + "common": "Every last little bit\n" + "helps, right?", + "trap": "I'll teach this fool\n" + " a lesson for a price!", + "dlc coin": "1 coin out of... wha!?\n" + "You imp, why I oughta!" +} + + +def randomize_lighting(world: "CV64World") -> Dict[int, bytes]: + """Generates randomized data for the map lighting table.""" + randomized_lighting = {} + for entry in range(67): + for sub_entry in range(19): + if sub_entry not in [3, 7, 11, 15] and entry != 4: + # The fourth entry in the lighting table affects the lighting on some item pickups; skip it + randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = bytes([world.random.randint(0, 255)]) + return randomized_lighting + + +def shuffle_sub_weapons(world: "CV64World") -> Dict[int, bytes]: + """Shuffles the sub-weapons amongst themselves.""" + sub_weapon_dict = {offset: rom_sub_weapon_offsets[offset][0] for offset in rom_sub_weapon_offsets if + rom_sub_weapon_offsets[offset][1] in world.active_stage_exits} + + # Remove the one 3HB sub-weapon in Tower of Execution if 3HBs are not shuffled. + if not world.options.multi_hit_breakables and 0x10CD65 in sub_weapon_dict: + del (sub_weapon_dict[0x10CD65]) + + sub_bytes = list(sub_weapon_dict.values()) + world.random.shuffle(sub_bytes) + return dict(zip(sub_weapon_dict, sub_bytes)) + + +def randomize_music(world: "CV64World") -> Dict[int, bytes]: + """Generates randomized or disabled data for all the music in the game.""" + music_array = bytearray(0x7A) + for number in music_sfx_ids: + music_array[number] = number + if world.options.background_music == BackgroundMusic.option_randomized: + looping_songs = [] + non_looping_songs = [] + fade_in_songs = {} + # Create shuffle-able lists of all the looping, non-looping, and fade-in track IDs + for i in range(0x10, len(music_array)): + if i not in rom_looping_music_fade_ins.keys() and i not in rom_looping_music_fade_ins.values() and \ + i != 0x72: # Credits song is blacklisted + non_looping_songs.append(i) + elif i in rom_looping_music_fade_ins.keys(): + looping_songs.append(i) + elif i in rom_looping_music_fade_ins.values(): + fade_in_songs[i] = i + # Shuffle the looping songs + rando_looping_songs = looping_songs.copy() + world.random.shuffle(rando_looping_songs) + looping_songs = dict(zip(looping_songs, rando_looping_songs)) + # Shuffle the non-looping songs + rando_non_looping_songs = non_looping_songs.copy() + world.random.shuffle(rando_non_looping_songs) + non_looping_songs = dict(zip(non_looping_songs, rando_non_looping_songs)) + non_looping_songs[0x72] = 0x72 + # Figure out the new fade-in songs if applicable + for vanilla_song in looping_songs: + if rom_looping_music_fade_ins[vanilla_song]: + if rom_looping_music_fade_ins[looping_songs[vanilla_song]]: + fade_in_songs[rom_looping_music_fade_ins[vanilla_song]] = rom_looping_music_fade_ins[ + looping_songs[vanilla_song]] + else: + fade_in_songs[rom_looping_music_fade_ins[vanilla_song]] = looping_songs[vanilla_song] + # Build the new music array + for i in range(0x10, len(music_array)): + if i in looping_songs.keys(): + music_array[i] = looping_songs[i] + elif i in non_looping_songs.keys(): + music_array[i] = non_looping_songs[i] + else: + music_array[i] = fade_in_songs[i] + del (music_array[0x00: 0x10]) + + return {0xBFCD30: bytes(music_array)} + + +def randomize_shop_prices(world: "CV64World") -> Dict[int, bytes]: + """Randomize the shop prices based on the minimum and maximum values chosen. + The minimum price will adjust if it's higher than the max.""" + min_price = world.options.minimum_gold_price.value + max_price = world.options.maximum_gold_price.value + + if min_price > max_price: + min_price = world.random.randint(0, max_price) + logging.warning(f"[{world.multiworld.player_name[world.player]}] The Minimum Gold Price " + f"({world.options.minimum_gold_price.value * 100}) is higher than the " + f"Maximum Gold Price ({max_price * 100}). Lowering the minimum to: {min_price * 100}") + world.options.minimum_gold_price.value = min_price + + shop_price_list = [world.random.randint(min_price * 100, max_price * 100) for _ in range(7)] + + # Convert the price list into a data dict. + price_dict = {} + for i in range(len(shop_price_list)): + price_dict[0x103D6C + (i * 12)] = int.to_bytes(shop_price_list[i], 4, "big") + + return price_dict + + +def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, bytes]: + """Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should + increase a number. + + First, check the location's info to see if it has a countdown number override. + If not, then figure it out based on the parent region's stage's position in the vanilla stage order. + If the parent region is not part of any stage (as is the case for Renon's shop), skip the location entirely.""" + countdown_list = [0 for _ in range(15)] + for loc in active_locations: + if loc.address is not None and (options.countdown == Countdown.option_all_locations or + (options.countdown == Countdown.option_majors + and loc.item.advancement)): + + countdown_number = get_location_info(loc.name, "countdown") + + if countdown_number is None: + stage = get_region_info(loc.parent_region.name, "stage") + if stage is not None: + countdown_number = vanilla_stage_order.index(stage) + + if countdown_number is not None: + countdown_list[countdown_number] += 1 + + return {0xBFD818: bytes(countdown_list)} + + +def get_location_data(world: "CV64World", active_locations: Iterable[Location]) \ + -> Tuple[Dict[int, bytes], List[str], List[bytearray], List[List[Union[int, str, None]]]]: + """Gets ALL the item data to go into the ROM. Item data consists of two bytes: the first dictates the appearance of + the item, the second determines what the item actually is when picked up. All items from other worlds will be AP + items that do nothing when picked up other than set their flag, and their appearance will depend on whether it's + another CV64 player's item and, if so, what item it is in their game. Ice Traps can assume the form of any item that + is progression, non-progression, or either depending on the player's settings. + + Appearance does not matter if it's one of the two NPC-given items (from either Vincent or Heinrich Meyer). For + Renon's shop items, a list containing the shop item names, descriptions, and colors will be returned alongside the + regular data.""" + + # Figure out the list of possible Ice Trap appearances to use based on the settings, first and foremost. + if world.options.ice_trap_appearance == IceTrapAppearance.option_major_only: + allowed_classifications = ["progression", "progression skip balancing"] + elif world.options.ice_trap_appearance == IceTrapAppearance.option_junk_only: + allowed_classifications = ["filler", "useful"] + else: + allowed_classifications = ["progression", "progression skip balancing", "filler", "useful"] + + trap_appearances = [] + for item in item_info: + if item_info[item]["default classification"] in allowed_classifications and item != "Ice Trap" and \ + get_item_info(item, "code") is not None: + trap_appearances.append(item) + + shop_name_list = [] + shop_desc_list = [] + shop_colors_list = [] + + location_bytes = {} + + for loc in active_locations: + # If the Location is an event, skip it. + if loc.address is None: + continue + + loc_type = get_location_info(loc.name, "type") + + # Figure out the item ID bytes to put in each Location here. Write the item itself if either it's the player's + # very own, or it belongs to an Item Link that the player is a part of. + if loc.item.player == world.player: + if loc_type not in ["npc", "shop"] and get_item_info(loc.item.name, "pickup actor id") is not None: + location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "pickup actor id") + else: + location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code") & 0xFF + else: + # Make the item the unused Wooden Stake - our multiworld item. + location_bytes[get_location_info(loc.name, "offset")] = 0x11 + + # Figure out the item's appearance. If it's a CV64 player's item, change the multiworld item's model to + # match what it is. Otherwise, change it to an Archipelago progress or not progress icon. The model "change" + # has to be applied to even local items because this is how the game knows to count it on the Countdown. + if loc.item.game == "Castlevania 64": + location_bytes[get_location_info(loc.name, "offset") - 1] = get_item_info(loc.item.name, "code") + elif loc.item.advancement: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x11 # Wooden Stakes are majors + else: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x12 # Roses are minors + + # If it's a PermaUp, change the item's model to a big PowerUp no matter what. + if loc.item.game == "Castlevania 64" and loc.item.code == 0x10C + base_id: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x0B + + # If it's an Ice Trap, change its model to one of the appearances we determined before. + # Unless it's an NPC item, in which case use the Ice Trap's regular ID so that it won't decrement the majors + # Countdown due to how I set up the NPC items to work. + if loc.item.game == "Castlevania 64" and loc.item.code == 0x12 + base_id: + if loc_type == "npc": + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x12 + else: + location_bytes[get_location_info(loc.name, "offset") - 1] = \ + get_item_info(world.random.choice(trap_appearances), "code") + # If we chose a PermaUp as our trap appearance, change it to its actual in-game ID of 0x0B. + if location_bytes[get_location_info(loc.name, "offset") - 1] == 0x10C: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x0B + + # Apply the invisibility variable depending on the "invisible items" setting. + if (world.options.invisible_items == InvisibleItems.option_vanilla and loc_type == "inv") or \ + (world.options.invisible_items == InvisibleItems.option_hide_all and loc_type not in ["npc", "shop"]): + location_bytes[get_location_info(loc.name, "offset") - 1] += 0x80 + elif world.options.invisible_items == InvisibleItems.option_chance and loc_type not in ["npc", "shop"]: + invisible = world.random.randint(0, 1) + if invisible: + location_bytes[get_location_info(loc.name, "offset") - 1] += 0x80 + + # If it's an Axe or Cross in a higher freestanding location, lower it into grab range. + # KCEK made these spawn 3.2 units higher for some reason. + if loc.address & 0xFFF in rom_axe_cross_lower_values and loc.item.code & 0xFF in [0x0F, 0x10]: + location_bytes[rom_axe_cross_lower_values[loc.address & 0xFFF][0]] = \ + rom_axe_cross_lower_values[loc.address & 0xFFF][1] + + # Figure out the list of shop names, descriptions, and text colors here. + if loc.parent_region.name != rname.renon: + continue + + shop_name = loc.item.name + if len(shop_name) > 18: + shop_name = shop_name[0:18] + shop_name_list.append(shop_name) + + if loc.item.player == world.player: + shop_desc_list.append([get_item_info(loc.item.name, "code"), None]) + elif loc.item.game == "Castlevania 64": + shop_desc_list.append([get_item_info(loc.item.name, "code"), + world.multiworld.get_player_name(loc.item.player)]) + else: + if loc.item.game == "DLCQuest" and loc.item.name in ["DLC Quest: Coin Bundle", + "Live Freemium or Die: Coin Bundle"]: + if getattr(world.multiworld.worlds[loc.item.player].options, "coinbundlequantity") == 1: + shop_desc_list.append(["dlc coin", world.multiworld.get_player_name(loc.item.player)]) + shop_colors_list.append(get_item_text_color(loc)) + continue + + if loc.item.advancement: + shop_desc_list.append(["prog", world.multiworld.get_player_name(loc.item.player)]) + elif loc.item.classification == ItemClassification.useful: + shop_desc_list.append(["useful", world.multiworld.get_player_name(loc.item.player)]) + elif loc.item.classification == ItemClassification.trap: + shop_desc_list.append(["trap", world.multiworld.get_player_name(loc.item.player)]) + else: + shop_desc_list.append(["common", world.multiworld.get_player_name(loc.item.player)]) + + shop_colors_list.append(get_item_text_color(loc)) + + return {offset: int.to_bytes(byte, 1, "big") for offset, byte in location_bytes.items()}, shop_name_list,\ + shop_colors_list, shop_desc_list + + +def get_loading_zone_bytes(options: CV64Options, starting_stage: str, + active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, bytes]: + """Figure out all the bytes for loading zones and map transitions based on which stages are where in the exit data. + The same data was used earlier in figuring out the logic. Map transitions consist of two major components: which map + to send the player to, and which spot within the map to spawn the player at.""" + + # Write the byte for the starting stage to send the player to after the intro narration. + loading_zone_bytes = {0xB73308: get_stage_info(starting_stage, "start map id")} + + for stage in active_stage_exits: + + # Start loading zones + # If the start zone is the start of the line, have it simply refresh the map. + if active_stage_exits[stage]["prev"] == "Menu": + loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = b"\xFF" + loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x00" + elif active_stage_exits[stage]["prev"]: + loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \ + get_stage_info(active_stage_exits[stage]["prev"], "end map id") + loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = \ + get_stage_info(active_stage_exits[stage]["prev"], "end spawn id") + + # Change CC's end-spawn ID to put you at Carrie's exit if appropriate + if active_stage_exits[stage]["prev"] == rname.castle_center: + if options.character_stages == CharacterStages.option_carrie_only or \ + active_stage_exits[rname.castle_center]["alt"] == stage: + loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x03" + + # End loading zones + if active_stage_exits[stage]["next"]: + loading_zone_bytes[get_stage_info(stage, "endzone map offset")] = \ + get_stage_info(active_stage_exits[stage]["next"], "start map id") + loading_zone_bytes[get_stage_info(stage, "endzone spawn offset")] = \ + get_stage_info(active_stage_exits[stage]["next"], "start spawn id") + + # Alternate end loading zones + if active_stage_exits[stage]["alt"]: + loading_zone_bytes[get_stage_info(stage, "altzone map offset")] = \ + get_stage_info(active_stage_exits[stage]["alt"], "start map id") + loading_zone_bytes[get_stage_info(stage, "altzone spawn offset")] = \ + get_stage_info(active_stage_exits[stage]["alt"], "start spawn id") + + return loading_zone_bytes + + +def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, bytes]: + """Calculate and return the starting inventory values. Not every Item goes into the menu inventory, so everything + has to be handled appropriately.""" + start_inventory_data = {} + + inventory_items_array = [0 for _ in range(35)] + total_money = 0 + total_jewels = 0 + total_powerups = 0 + total_ice_traps = 0 + + items_max = 10 + + # Raise the items max if Increase Item Limit is enabled. + if options.increase_item_limit: + items_max = 99 + + for item in precollected_items: + if item.player != player: + continue + + inventory_offset = get_item_info(item.name, "inventory offset") + sub_equip_id = get_item_info(item.name, "sub equip id") + # Starting inventory items + if inventory_offset is not None: + inventory_items_array[inventory_offset] += 1 + if inventory_items_array[inventory_offset] > items_max and "Special" not in item.name: + inventory_items_array[inventory_offset] = items_max + if item.name == iname.permaup: + if inventory_items_array[inventory_offset] > 2: + inventory_items_array[inventory_offset] = 2 + # Starting sub-weapon + elif sub_equip_id is not None: + start_inventory_data[0xBFD883] = bytes(sub_equip_id) + # Starting PowerUps + elif item.name == iname.powerup: + total_powerups += 1 + # Can't have more than 2 PowerUps. + if total_powerups > 2: + total_powerups = 2 + # Starting Gold + elif "GOLD" in item.name: + total_money += int(item.name[0:4]) + # Money cannot be higher than 99999. + if total_money > 99999: + total_money = 99999 + # Starting Jewels + elif "jewel" in item.name: + if "L" in item.name: + total_jewels += 10 + else: + total_jewels += 5 + # Jewels cannot be higher than 99. + if total_jewels > 99: + total_jewels = 99 + # Starting Ice Traps + else: + total_ice_traps += 1 + # Ice Traps cannot be higher than 255. + if total_ice_traps > 0xFF: + total_ice_traps = 0xFF + + # Convert the jewels into data. + start_inventory_data[0xBFD867] = bytes([total_jewels]) + + # Convert the Ice Traps into data. + start_inventory_data[0xBFD88B] = bytes([total_ice_traps]) + + # Convert the inventory items into data. + start_inventory_data[0xBFE518] = bytes(inventory_items_array) + + # Convert the starting money into data. + start_inventory_data[0xBFE514] = int.to_bytes(total_money, 4, "big") + + return start_inventory_data + + +def get_item_text_color(loc: Location) -> bytearray: + if loc.item.advancement: + return bytearray([0xA2, 0x0C]) + elif loc.item.classification == ItemClassification.useful: + return bytearray([0xA2, 0x0A]) + elif loc.item.classification == ItemClassification.trap: + return bytearray([0xA2, 0x0B]) + else: + return bytearray([0xA2, 0x02]) diff --git a/worlds/cv64/client.py b/worlds/cv64/client.py new file mode 100644 index 000000000000..2430cc5ffc67 --- /dev/null +++ b/worlds/cv64/client.py @@ -0,0 +1,207 @@ +from typing import TYPE_CHECKING, Set +from .locations import base_id +from .text import cv64_text_wrap, cv64_string_to_bytearray + +from NetUtils import ClientStatus +import worlds._bizhawk as bizhawk +import base64 +from worlds._bizhawk.client import BizHawkClient + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class Castlevania64Client(BizHawkClient): + game = "Castlevania 64" + system = "N64" + patch_suffix = ".apcv64" + self_induced_death = False + received_deathlinks = 0 + death_causes = [] + currently_shopping = False + local_checked_locations: Set[int] + + def __init__(self) -> None: + super().__init__() + self.local_checked_locations = set() + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + from CommonClient import logger + + try: + # Check ROM name/patch version + game_names = await bizhawk.read(ctx.bizhawk_ctx, [(0x20, 0x14, "ROM"), (0xBFBFD0, 12, "ROM")]) + if game_names[0].decode("ascii") != "CASTLEVANIA ": + return False + if game_names[1] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00': + logger.info("ERROR: You appear to be running an unpatched version of Castlevania 64. " + "You need to generate a patch file and use it to create a patched ROM.") + return False + if game_names[1].decode("ascii") != "ARCHIPELAGO1": + logger.info("ERROR: The patch file used to create this ROM is not compatible with " + "this client. Double check your client version against the version being " + "used by the generator.") + return False + except UnicodeDecodeError: + return False + except bizhawk.RequestFailedError: + return False # Should verify on the next pass + + ctx.game = self.game + ctx.items_handling = 0b001 + ctx.want_slot_data = False + ctx.watcher_timeout = 0.125 + return True + + async def set_auth(self, ctx: "BizHawkClientContext") -> None: + auth_raw = (await bizhawk.read(ctx.bizhawk_ctx, [(0xBFBFE0, 16, "ROM")]))[0] + ctx.auth = base64.b64encode(auth_raw).decode("utf-8") + + def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: + if cmd != "Bounced": + return + if "tags" not in args: + return + if "DeathLink" in args["tags"] and args["data"]["source"] != ctx.slot_info[ctx.slot].name: + self.received_deathlinks += 1 + if "cause" in args["data"]: + cause = args["data"]["cause"] + if len(cause) > 88: + cause = cause[0x00:0x89] + else: + cause = f"{args['data']['source']} killed you!" + self.death_causes.append(cause) + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + + try: + read_state = await bizhawk.read(ctx.bizhawk_ctx, [(0x342084, 4, "RDRAM"), + (0x389BDE, 6, "RDRAM"), + (0x389BE4, 224, "RDRAM"), + (0x389EFB, 1, "RDRAM"), + (0x389EEF, 1, "RDRAM"), + (0xBFBFDE, 2, "ROM")]) + + game_state = int.from_bytes(read_state[0], "big") + save_struct = read_state[2] + written_deathlinks = int.from_bytes(bytearray(read_state[1][4:6]), "big") + deathlink_induced_death = int.from_bytes(bytearray(read_state[1][0:1]), "big") + cutscene_value = int.from_bytes(read_state[3], "big") + current_menu = int.from_bytes(read_state[4], "big") + num_received_items = int.from_bytes(bytearray(save_struct[0xDA:0xDC]), "big") + rom_flags = int.from_bytes(read_state[5], "big") + + # Make sure we are in the Gameplay or Credits states before detecting sent locations and/or DeathLinks. + # If we are in any other state, such as the Game Over state, set self_induced_death to false, so we can once + # again send a DeathLink once we are back in the Gameplay state. + if game_state not in [0x00000002, 0x0000000B]: + self.self_induced_death = False + return + + # Enable DeathLink if the bit for it is set in our ROM flags. + if "DeathLink" not in ctx.tags and rom_flags & 0x0100: + await ctx.update_death_link(True) + + # Scout the Renon shop locations if the shopsanity flag is written in the ROM. + if rom_flags & 0x0001 and ctx.locations_info == {}: + await ctx.send_msgs([{ + "cmd": "LocationScouts", + "locations": [base_id + i for i in range(0x1C8, 0x1CF)], + "create_as_hint": 0 + }]) + + # Send a DeathLink if we died on our own independently of receiving another one. + if "DeathLink" in ctx.tags and save_struct[0xA4] & 0x80 and not self.self_induced_death and not \ + deathlink_induced_death: + self.self_induced_death = True + if save_struct[0xA4] & 0x08: + # Special death message for dying while having the Vamp status. + await ctx.send_death(f"{ctx.player_names[ctx.slot]} became a vampire and drank your blood!") + else: + await ctx.send_death(f"{ctx.player_names[ctx.slot]} perished. Dracula has won!") + + # Write any DeathLinks received along with the corresponding death cause starting with the oldest. + # To minimize Bizhawk Write jank, the DeathLink write will be prioritized over the item received one. + if self.received_deathlinks and not self.self_induced_death and not written_deathlinks: + death_text, num_lines = cv64_text_wrap(self.death_causes[0], 96) + await bizhawk.write(ctx.bizhawk_ctx, [(0x389BE3, [0x01], "RDRAM"), + (0x389BDF, [0x11], "RDRAM"), + (0x18BF98, bytearray([0xA2, 0x0B]) + + cv64_string_to_bytearray(death_text, False), "RDRAM"), + (0x18C097, [num_lines], "RDRAM")]) + self.received_deathlinks -= 1 + del self.death_causes[0] + else: + # If the game hasn't received all items yet, the received item struct doesn't contain an item, the + # current number of received items still matches what we read before, and there are no open text boxes, + # then fill it with the next item and write the "item from player" text in its buffer. The game will + # increment the number of received items on its own. + if num_received_items < len(ctx.items_received): + next_item = ctx.items_received[num_received_items] + if next_item.flags & 0b001: + text_color = bytearray([0xA2, 0x0C]) + elif next_item.flags & 0b010: + text_color = bytearray([0xA2, 0x0A]) + elif next_item.flags & 0b100: + text_color = bytearray([0xA2, 0x0B]) + else: + text_color = bytearray([0xA2, 0x02]) + received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_game(next_item.item)}\n" + f"from {ctx.player_names[next_item.player]}", 96) + await bizhawk.guarded_write(ctx.bizhawk_ctx, + [(0x389BE1, [next_item.item & 0xFF], "RDRAM"), + (0x18C0A8, text_color + cv64_string_to_bytearray(received_text, False), + "RDRAM"), + (0x18C1A7, [num_lines], "RDRAM")], + [(0x389BE1, [0x00], "RDRAM"), # Remote item reward buffer + (0x389CBE, save_struct[0xDA:0xDC], "RDRAM"), # Received items + (0x342891, [0x02], "RDRAM")]) # Textbox state + + flag_bytes = bytearray(save_struct[0x00:0x44]) + bytearray(save_struct[0x90:0x9F]) + locs_to_send = set() + + # Check for set location flags. + for byte_i, byte in enumerate(flag_bytes): + for i in range(8): + and_value = 0x80 >> i + if byte & and_value != 0: + flag_id = byte_i * 8 + i + + location_id = flag_id + base_id + if location_id in ctx.server_locations: + locs_to_send.add(location_id) + + # Send locations if there are any to send. + if locs_to_send != self.local_checked_locations: + self.local_checked_locations = locs_to_send + + if locs_to_send is not None: + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": list(locs_to_send) + }]) + + # Check the menu value to see if we are in Renon's shop, and set currently_shopping to True if we are. + if current_menu == 0xA: + self.currently_shopping = True + + # If we are currently shopping, and the current menu value is 0 (meaning we just left the shop), hint the + # un-bought shop locations that have progression. + if current_menu == 0 and self.currently_shopping: + await ctx.send_msgs([{ + "cmd": "LocationScouts", + "locations": [loc for loc, n_item in ctx.locations_info.items() if n_item.flags & 0b001], + "create_as_hint": 2 + }]) + self.currently_shopping = False + + # Send game clear if we're in either any ending cutscene or the credits state. + if not ctx.finished_game and (0x26 <= int(cutscene_value) <= 0x2E or game_state == 0x0000000B): + await ctx.send_msgs([{ + "cmd": "StatusUpdate", + "status": ClientStatus.CLIENT_GOAL + }]) + + except bizhawk.RequestFailedError: + # Exit handler and return to main loop to reconnect. + pass diff --git a/worlds/cv64/data/APLogo-LICENSE.txt b/worlds/cv64/data/APLogo-LICENSE.txt new file mode 100644 index 000000000000..69d1e3ecd137 --- /dev/null +++ b/worlds/cv64/data/APLogo-LICENSE.txt @@ -0,0 +1,3 @@ +The Archipelago Logo is © 2022 by Krista Corkos and Christopher Wilson is licensed under Attribution-NonCommercial 4.0 International. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/ + +Logo modified by Liquid Cat to fit artstyle and uses within the mod. diff --git a/worlds/cv64/data/ap_icons.bin b/worlds/cv64/data/ap_icons.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a4129ac409ceb3a7126a8f04fe793d27bb0d2bc GIT binary patch literal 4666 zcmeH}ze`(D6vt1}@M2rrS`jg+CS8O=|AA5{k#5DgW1&>U(p7XQ9i*#&fRlrW&<+lT z?sh12u*AWkbr8j1+d)Xv;7~9*J>PqBa$kNupP-$*2XgLv_kPa#-t%6P)1M872m5Gf zxGxyZ5oJdy$lVNxazT(B$`U2BKS{>{_+b9)o_Y93N$-tBh5$isF9AGn%sSJ%8UxBPnk$v{ z%z8%eF_*{7<6N;-+p|@j+@9RFJ@aGpV_S{y6Eo;yzL*DTW;@M;p3Y2XK$>X|@a?oH zkn#Gd8s?q!{8%(^G<-DAm9TbxujdE1dPj|UtLn8ctu3wj^Sk<2?A-9E9=OdFZ-`*$ zhPUcjedUOa7&vV^jX1ckF~GJyA4I`D9je>swwbMWvZ}(g)aP2YAI%)iY_8PjqB$P} z`Yk8C@^NXLq&^qsF^``eI&e}S18n^4b`CH$?B4l%>r+Z}EkOfB2SlU&BTv5X{Qm2{ zpF1Lfq!IZhP0a!=P>F6i5hc3e`kIr$kt=+Qxh8BtuNIEQr?0+!{-YtaDn4DPkmWfZ zkPWi_2kYriZ%Q;Maqdd%GtnpfM372KpcDlGB`FmojdMC==UyZ3#W|}v{8_Vrx$Il) zKihnC9Z@qKmwgNBaM`znzjc>=3px+^d32`#;QD=v-?vcW|1*Aar>55 z-gh4Ivd8LM!n~*Pmbl-IQ~Q=M-}zhQVz`fPeT&xJ*Uw#tU(n_p|M=bd4xb%Sgzq_e z_AR03*Ate7aL-}=OQUab5u$QyT$G`E-x94ntjgE3rPdMW4%2bx))$;l-!l8)fAuZ@ E08)QCxc~qF literal 0 HcmV?d00001 diff --git a/worlds/cv64/data/ename.py b/worlds/cv64/data/ename.py new file mode 100644 index 000000000000..26cd7151de89 --- /dev/null +++ b/worlds/cv64/data/ename.py @@ -0,0 +1,71 @@ +forest_dbridge_gate = "Descending bridge gate" +forest_werewolf_gate = "Werewolf gate" +forest_end = "Dracula's drawbridge" + +cw_portcullis_c = "Central portcullis" +cw_lt_skip = "Do Left Tower Skip" +cw_lt_door = "Left Tower door" +cw_end = "End portcullis" + +villa_dog_gates = "Front dog gates" +villa_snipe_dogs = "Orb snipe the dogs" +villa_to_storeroom = "To Storeroom door" +villa_to_archives = "To Archives door" +villa_renon = "Villa contract" +villa_to_maze = "To maze gate" +villa_from_storeroom = "From Storeroom door" +villa_from_maze = "From maze gate" +villa_servant_door = "Servants' door" +villa_copper_door = "Copper door" +villa_copper_skip = "Get Copper Skip" +villa_bridge_door = "From bridge door" +villa_end_r = "Villa Reinhardt (daytime) exit" +villa_end_c = "Villa Carrie (nighttime) exit" + +tunnel_start_renon = "Tunnel start contract" +tunnel_gondolas = "Gondola ride" +tunnel_end_renon = "Tunnel end contract" +tunnel_end = "End Tunnel door" + +uw_final_waterfall = "Final waterfall" +uw_waterfall_skip = "Do Waterfall Skip" +uw_renon = "Underground Waterway contract" +uw_end = "End Waterway door" + +cc_tc_door = "Torture Chamber door" +cc_renon = "Castle Center contract" +cc_lower_wall = "Lower sealed cracked wall" +cc_upper_wall = "Upper cracked wall" +cc_elevator = "Activate crystal and ride elevator" +cc_exit_r = "Castle Center Reinhardt (Medusa Head) exit" +cc_exit_c = "Castle Center Carrie (Ghost) exit" + +dt_start = "Duel Tower start passage" +dt_end = "Duel Tower end passage" + +toe_start = "Tower of Execution start passage" +toe_gate = "Execution gate" +toe_gate_skip = "Just jump past the gate from above, bro!" +toe_end = "Tower of Execution end staircase" + +tosci_start = "Tower of Science start passage" +tosci_key1_door = "Science Door 1" +tosci_to_key2_door = "To Science Door 2" +tosci_from_key2_door = "From Science Door 2" +tosci_key3_door = "Science Door 3" +tosci_end = "Tower of Science end passage" + +tosor_start = "Tower of Sorcery start passage" +tosor_end = "Tower of Sorcery end passage" + +roc_gate = "Defeat boss gate" + +ct_to_door1 = "To Clocktower Door 1" +ct_from_door1 = "From Clocktower Door 1" +ct_to_door2 = "To Clocktower Door 2" +ct_from_door2 = "From Clocktower Door 2" +ct_renon = "Clock Tower contract" +ct_door_3 = "Clocktower Door 3" + +ck_slope_jump = "Slope Jump to boss tower" +ck_drac_door = "Dracula's door" diff --git a/worlds/cv64/data/iname.py b/worlds/cv64/data/iname.py new file mode 100644 index 000000000000..9b9e225ca5c7 --- /dev/null +++ b/worlds/cv64/data/iname.py @@ -0,0 +1,49 @@ +# Items +white_jewel = "White jewel" +special_one = "Special1" +special_two = "Special2" +red_jewel_s = "Red jewel(S)" +red_jewel_l = "Red jewel(L)" +roast_chicken = "Roast chicken" +roast_beef = "Roast beef" +healing_kit = "Healing kit" +purifying = "Purifying" +cure_ampoule = "Cure ampoule" +pot_pourri = "pot-pourri" +powerup = "PowerUp" +permaup = "PermaUp" +holy_water = "Holy water" +cross = "Cross" +axe = "Axe" +knife = "Knife" +wooden_stake = "Wooden stake" +rose = "Rose" +ice_trap = "Ice Trap" +the_contract = "The contract" +engagement_ring = "engagement ring" +magical_nitro = "Magical Nitro" +mandragora = "Mandragora" +sun_card = "Sun card" +moon_card = "Moon card" +incandescent_gaze = "Incandescent gaze" +five_hundred_gold = "500 GOLD" +three_hundred_gold = "300 GOLD" +one_hundred_gold = "100 GOLD" +archives_key = "Archives Key" +left_tower_key = "Left Tower Key" +storeroom_key = "Storeroom Key" +garden_key = "Garden Key" +copper_key = "Copper Key" +chamber_key = "Chamber Key" +execution_key = "Execution Key" +science_key1 = "Science Key1" +science_key2 = "Science Key2" +science_key3 = "Science Key3" +clocktower_key1 = "Clocktower Key1" +clocktower_key2 = "Clocktower Key2" +clocktower_key3 = "Clocktower Key3" + +trophy = "Trophy" +crystal = "Crystal" + +victory = "The Count Downed" diff --git a/worlds/cv64/data/lname.py b/worlds/cv64/data/lname.py new file mode 100644 index 000000000000..09db86b38083 --- /dev/null +++ b/worlds/cv64/data/lname.py @@ -0,0 +1,479 @@ +# Forest of Silence main locations +forest_pillars_right = "Forest of Silence: Grab practice pillars - Right" +forest_pillars_top = "Forest of Silence: Grab practice pillars - Top" +forest_king_skeleton = "Forest of Silence: King Skeleton's bridge" +forest_lgaz_in = "Forest of Silence: Moon gazebo inside" +forest_lgaz_top = "Forest of Silence: Moon gazebo roof" +forest_hgaz_in = "Forest of Silence: Sun gazebo inside" +forest_hgaz_top = "Forest of Silence: Sun gazebo roof" +forest_weretiger_sw = "Forest of Silence: Were-tiger switch" +forest_weretiger_gate = "Forest of Silence: Dirge maiden gate" +forest_dirge_tomb_u = "Forest of Silence: Dirge maiden crypt - Upper" +forest_dirge_plaque = "Forest of Silence: Dirge maiden pedestal plaque" +forest_corpse_save = "Forest of Silence: Tri-corpse save junction" +forest_dbridge_wall = "Forest of Silence: Descending bridge wall side" +forest_dbridge_sw = "Forest of Silence: Descending bridge switch side" +forest_dbridge_gate_r = "Forest of Silence: Tri-corpse gate - Right" +forest_dbridge_tomb_uf = "Forest of Silence: Three-crypt plaza main path crypt - Upper-front" +forest_bface_tomb_lf = "Forest of Silence: Three-crypt plaza back-facing crypt - Lower-front" +forest_bface_tomb_u = "Forest of Silence: Three-crypt plaza back-facing crypt - Upper" +forest_ibridge = "Forest of Silence: Invisible bridge platform" +forest_werewolf_tomb_r = "Forest of Silence: Werewolf crypt - Right" +forest_werewolf_plaque = "Forest of Silence: Werewolf statue plaque" +forest_werewolf_tree = "Forest of Silence: Werewolf path near tree" +forest_final_sw = "Forest of Silence: Three-crypt plaza switch" + +# Forest of Silence empty breakables +forest_dirge_tomb_l = "Forest of Silence: Dirge maiden crypt - Lower" +forest_dbridge_tomb_l = "Forest of Silence: Three-crypt plaza main path crypt - Lower" +forest_dbridge_tomb_ur = "Forest of Silence: Three-crypt plaza main path crypt - Upper-rear" +forest_bface_tomb_lr = "Forest of Silence: Three-crypt plaza back-facing crypt - Lower-rear" +forest_werewolf_tomb_lf = "Forest of Silence: Werewolf crypt - Left-front" +forest_werewolf_tomb_lr = "Forest of Silence: Werewolf crypt - Left-rear" + +# Forest of Silence 3-hit breakables +forest_dirge_rock1 = "Forest of Silence: Dirge maiden rock - Item 1" +forest_dirge_rock2 = "Forest of Silence: Dirge maiden rock - Item 2" +forest_dirge_rock3 = "Forest of Silence: Dirge maiden rock - Item 3" +forest_dirge_rock4 = "Forest of Silence: Dirge maiden rock - Item 4" +forest_dirge_rock5 = "Forest of Silence: Dirge maiden rock - Item 5" +forest_bridge_rock1 = "Forest of Silence: Bat archway rock - Item 1" +forest_bridge_rock2 = "Forest of Silence: Bat archway rock - Item 2" +forest_bridge_rock3 = "Forest of Silence: Bat archway rock - Item 3" +forest_bridge_rock4 = "Forest of Silence: Bat archway rock - Item 4" + +# Forest of Silence sub-weapons +forest_pillars_left = "Forest of Silence: Grab practice pillars - Left" +forest_dirge_ped = "Forest of Silence: Dirge maiden pedestal" +forest_dbridge_gate_l = "Forest of Silence: Tri-corpse gate - Left" +forest_werewolf_island = "Forest of Silence: Werewolf path island switch platforms" + + +# Castle Wall main locations +cw_ground_middle = "Castle Wall: Ground gatehouse - Middle" +cw_rrampart = "Castle Wall: Central rampart near right tower" +cw_lrampart = "Castle Wall: Central rampart near left tower" +cw_dragon_sw = "Castle Wall: White Dragons switch door" +cw_drac_sw = "Castle Wall: Dracula cutscene switch door" +cw_shelf_visible = "Castle Wall: Sandbag shelf - Visible" +cw_shelf_sandbags = "Castle Wall: Sandbag shelf - Invisible" + +# Castle Wall towers main locations +cwr_bottom = "Castle Wall: Above bottom right tower door" +cwl_bottom = "Castle Wall: Above bottom left tower door" +cwl_bridge = "Castle Wall: Left tower child ledge" + +# Castle Wall 3-hit breakables +cw_save_slab1 = "Castle Wall: Central rampart savepoint slab - Item 1" +cw_save_slab2 = "Castle Wall: Central rampart savepoint slab - Item 2" +cw_save_slab3 = "Castle Wall: Central rampart savepoint slab - Item 3" +cw_save_slab4 = "Castle Wall: Central rampart savepoint slab - Item 4" +cw_save_slab5 = "Castle Wall: Central rampart savepoint slab - Item 5" +cw_drac_slab1 = "Castle Wall: Dracula cutscene switch slab - Item 1" +cw_drac_slab2 = "Castle Wall: Dracula cutscene switch slab - Item 2" +cw_drac_slab3 = "Castle Wall: Dracula cutscene switch slab - Item 3" +cw_drac_slab4 = "Castle Wall: Dracula cutscene switch slab - Item 4" +cw_drac_slab5 = "Castle Wall: Dracula cutscene switch slab - Item 5" + +# Castle Wall sub-weapons +cw_ground_left = "Castle Wall: Ground gatehouse - Left" +cw_ground_right = "Castle Wall: Ground gatehouse - Right" +cw_shelf_torch = "Castle Wall: Sandbag shelf floor torch" +cw_pillar = "Castle Wall: Central rampart broken pillar" + + +# Villa front yard main locations +villafy_outer_gate_l = "Villa: Outer front gate - Left" +villafy_outer_gate_r = "Villa: Outer front gate - Right" +villafy_inner_gate = "Villa: Inner front gate dog food" +villafy_dog_platform = "Villa: Outer front gate platform" +villafy_gate_marker = "Villa: Front yard cross grave near gates" +villafy_villa_marker = "Villa: Front yard cross grave near porch" +villafy_tombstone = "Villa: Front yard visitor's tombstone" +villafy_fountain_fl = "Villa: Midnight fountain - Front-left" +villafy_fountain_fr = "Villa: Midnight fountain - Front-right" +villafy_fountain_ml = "Villa: Midnight fountain - Middle-left" +villafy_fountain_mr = "Villa: Midnight fountain - Middle-right" +villafy_fountain_rl = "Villa: Midnight fountain - Rear-left" +villafy_fountain_rr = "Villa: Midnight fountain - Rear-right" + +# Villa foyer main locations +villafo_sofa = "Villa: Foyer sofa" +villafo_pot_r = "Villa: Foyer upper-right pot" +villafo_pot_l = "Villa: Foyer upper-left pot" +villafo_rear_r = "Villa: Foyer lower level - Rear-right" +villafo_rear_l = "Villa: Foyer lower level - Rear-left" +villafo_mid_l = "Villa: Foyer lower level - Middle-left" +villafo_front_r = "Villa: Foyer lower level - Front-right" +villafo_front_l = "Villa: Foyer lower level - Front-left" +villafo_serv_ent = "Villa: Servants' entrance" + +# Villa empty breakables +villafo_mid_r = "Villa: Foyer lower level - Middle-right" + +# Villa 3-hit breakables +villafo_chandelier1 = "Villa: Foyer chandelier - Item 1" +villafo_chandelier2 = "Villa: Foyer chandelier - Item 2" +villafo_chandelier3 = "Villa: Foyer chandelier - Item 3" +villafo_chandelier4 = "Villa: Foyer chandelier - Item 4" +villafo_chandelier5 = "Villa: Foyer chandelier - Item 5" + +# Villa living area main locations +villala_hallway_stairs = "Villa: Rose garden staircase bottom" +villala_bedroom_chairs = "Villa: Bedroom near chairs" +villala_bedroom_bed = "Villa: Bedroom near bed" +villala_vincent = "Villa: Vincent" +villala_slivingroom_table = "Villa: Mary's room table" +villala_storeroom_l = "Villa: Storeroom - Left" +villala_storeroom_r = "Villa: Storeroom - Right" +villala_storeroom_s = "Villa: Storeroom statue" +villala_diningroom_roses = "Villa: Dining room rose vase" +villala_archives_table = "Villa: Archives table" +villala_archives_rear = "Villa: Archives rear corner" +villala_llivingroom_lion = "Villa: Living room lion head" +villala_llivingroom_pot_r = "Villa: Living room - Right pot" +villala_llivingroom_pot_l = "Villa: Living room - Left pot" +villala_llivingroom_light = "Villa: Living room ceiling light" +villala_llivingroom_painting = "Villa: Living room clawed painting" +villala_exit_knight = "Villa: Maze garden exit knight" + +# Villa maze main locations +villam_malus_torch = "Villa: Front maze garden - Malus start torch" +villam_malus_bush = "Villa: Front maze garden - Malus's hiding bush" +villam_frankieturf_r = "Villa: Front maze garden - Frankie's right dead-end" +villam_frankieturf_l = "Villa: Front maze garden - Frankie's left dead-end" +villam_frankieturf_ru = "Villa: Front maze garden - Frankie's right dead-end urn" +villam_fgarden_f = "Villa: Rear maze garden - Iron Thorn Fenced area - Front" +villam_fgarden_mf = "Villa: Rear maze garden - Iron Thorn Fenced area - Mid-front" +villam_fgarden_mr = "Villa: Rear maze garden - Iron Thorn Fenced area - Mid-rear" +villam_fgarden_r = "Villa: Rear maze garden - Iron Thorn Fenced area - Rear" +villam_rplatform_de = "Villa: Rear maze garden - Viewing platform dead-end" +villam_exit_de = "Villa: Rear maze garden - Past-exit dead-end" +villam_serv_path = "Villa: Servants' path small alcove" +villam_crypt_ent = "Villa: Crypt entrance" +villam_crypt_upstream = "Villa: Crypt bridge upstream" + +# Villa crypt main locations +villac_ent_l = "Villa: Crypt - Left from entrance" +villac_ent_r = "Villa: Crypt - Right from entrance" +villac_wall_l = "Villa: Crypt - Left wall" +villac_wall_r = "Villa: Crypt - Right wall" +villac_coffin_r = "Villa: Crypt - Right of coffin" + +# Villa sub-weapons +villala_hallway_l = "Villa: Hallway near rose garden stairs - Left" +villala_hallway_r = "Villa: Hallway near rose garden stairs - Right" +villala_slivingroom_mirror = "Villa: Mary's room corner" +villala_archives_entrance = "Villa: Archives near entrance" +villam_fplatform = "Villa: Front maze garden - Viewing platform" +villam_rplatform = "Villa: Rear maze garden - Viewing platform" +villac_coffin_l = "Villa: Crypt - Left of coffin" + + +# Tunnel main locations +tunnel_landing = "Tunnel: Landing point" +tunnel_landing_rc = "Tunnel: Landing point rock crusher" +tunnel_stone_alcove_l = "Tunnel: Stepping stone alcove - Left" +tunnel_twin_arrows = "Tunnel: Twin arrow signs" +tunnel_lonesome_bucket = "Tunnel: Near lonesome bucket" +tunnel_lbucket_quag = "Tunnel: Lonesome bucket poison pit" +tunnel_lbucket_albert = "Tunnel: Lonesome bucket-Albert junction" +tunnel_albert_camp = "Tunnel: Albert's campsite" +tunnel_albert_quag = "Tunnel: Albert's poison pit" +tunnel_gondola_rc_sdoor_r = "Tunnel: Gondola rock crusher sun door - Right" +tunnel_gondola_rc_sdoor_m = "Tunnel: Gondola rock crusher sun door - Middle" +tunnel_gondola_rc = "Tunnel: Gondola rock crusher" +tunnel_rgondola_station = "Tunnel: Red gondola station" +tunnel_gondola_transfer = "Tunnel: Gondola transfer point" +tunnel_corpse_bucket_quag = "Tunnel: Corpse bucket poison pit" +tunnel_corpse_bucket_mdoor_r = "Tunnel: Corpse bucket moon door - Right" +tunnel_shovel_quag_start = "Tunnel: Shovel poison pit start" +tunnel_exit_quag_start = "Tunnel: Exit door poison pit start" +tunnel_shovel_quag_end = "Tunnel: Shovel poison pit end" +tunnel_exit_quag_end = "Tunnel: Exit door poison pit end" +tunnel_shovel = "Tunnel: Shovel" +tunnel_shovel_save = "Tunnel: Shovel zone save junction" +tunnel_shovel_mdoor_l = "Tunnel: Shovel zone moon door - Left" +tunnel_shovel_sdoor_l = "Tunnel: Shovel zone sun door - Left" +tunnel_shovel_sdoor_m = "Tunnel: Shovel zone sun door - Middle" + +# Tunnel 3-hit breakables +tunnel_arrows_rock1 = "Tunnel: Twin arrow signs rock - Item 1" +tunnel_arrows_rock2 = "Tunnel: Twin arrow signs rock - Item 2" +tunnel_arrows_rock3 = "Tunnel: Twin arrow signs rock - Item 3" +tunnel_arrows_rock4 = "Tunnel: Twin arrow signs rock - Item 4" +tunnel_arrows_rock5 = "Tunnel: Twin arrow signs rock - Item 5" +tunnel_bucket_quag_rock1 = "Tunnel: Lonesome bucket poison pit rock - Item 1" +tunnel_bucket_quag_rock2 = "Tunnel: Lonesome bucket poison pit rock - Item 2" +tunnel_bucket_quag_rock3 = "Tunnel: Lonesome bucket poison pit rock - Item 3" + +# Tunnel sub-weapons +tunnel_stone_alcove_r = "Tunnel: Stepping stone alcove - Right" +tunnel_lbucket_mdoor_l = "Tunnel: Lonesome bucket moon door" +tunnel_gondola_rc_sdoor_l = "Tunnel: Gondola rock crusher sun door - Left" +tunnel_corpse_bucket_mdoor_l = "Tunnel: Corpse bucket moon door - Left" +tunnel_shovel_mdoor_r = "Tunnel: Shovel zone moon door - Right" +tunnel_shovel_sdoor_r = "Tunnel: Shovel zone sun door - Right" + + +# Underground Waterway main locations +uw_near_ent = "Underground Waterway: Near entrance corridor" +uw_across_ent = "Underground Waterway: Across from entrance" +uw_poison_parkour = "Underground Waterway: Across poison parkour ledges" +uw_waterfall_alcove = "Underground Waterway: Waterfall alcove ledge" +uw_carrie1 = "Underground Waterway: Carrie crawlspace corridor - First left" +uw_carrie2 = "Underground Waterway: Carrie crawlspace corridor - Second left" +uw_bricks_save = "Underground Waterway: Brick platforms save corridor" +uw_above_skel_ledge = "Underground Waterway: Above skeleton crusher ledge" + +# Underground Waterway 3-hit breakables +uw_first_ledge1 = "Underground Waterway: First poison parkour ledge - Item 1" +uw_first_ledge2 = "Underground Waterway: First poison parkour ledge - Item 2" +uw_first_ledge3 = "Underground Waterway: First poison parkour ledge - Item 3" +uw_first_ledge4 = "Underground Waterway: First poison parkour ledge - Item 4" +uw_first_ledge5 = "Underground Waterway: First poison parkour ledge - Item 5" +uw_first_ledge6 = "Underground Waterway: First poison parkour ledge - Item 6" +uw_in_skel_ledge1 = "Underground Waterway: Inside skeleton crusher ledge - Item 1" +uw_in_skel_ledge2 = "Underground Waterway: Inside skeleton crusher ledge - Item 2" +uw_in_skel_ledge3 = "Underground Waterway: Inside skeleton crusher ledge - Item 3" + + +# Castle Center basement main locations +ccb_skel_hallway_ent = "Castle Center: Entrance hallway" +ccb_skel_hallway_jun = "Castle Center: Basement hallway junction" +ccb_skel_hallway_tc = "Castle Center: Torture chamber hallway" +ccb_behemoth_l_ff = "Castle Center: Behemoth arena - Left far-front torch" +ccb_behemoth_l_mf = "Castle Center: Behemoth arena - Left mid-front torch" +ccb_behemoth_l_mr = "Castle Center: Behemoth arena - Left mid-rear torch" +ccb_behemoth_l_fr = "Castle Center: Behemoth arena - Left far-rear torch" +ccb_behemoth_r_ff = "Castle Center: Behemoth arena - Right far-front torch" +ccb_behemoth_r_mf = "Castle Center: Behemoth arena - Right mid-front torch" +ccb_behemoth_r_mr = "Castle Center: Behemoth arena - Right mid-rear torch" +ccb_behemoth_r_fr = "Castle Center: Behemoth arena - Right far-rear torch" +ccb_mandrag_shelf_l = "Castle Center: Mandragora shelf - Left" +ccb_mandrag_shelf_r = "Castle Center: Mandragora shelf - Right" +ccb_torture_rack = "Castle Center: Torture chamber instrument rack" +ccb_torture_rafters = "Castle Center: Torture chamber rafters" + +# Castle Center elevator room main locations +ccelv_near_machine = "Castle Center: Near elevator room machine" +ccelv_atop_machine = "Castle Center: Atop elevator room machine" +ccelv_pipes = "Castle Center: Elevator pipe device" +ccelv_staircase = "Castle Center: Elevator room staircase" + +# Castle Center factory floor main locations +ccff_redcarpet_knight = "Castle Center: Red carpet hall knight" +ccff_gears_side = "Castle Center: Gear room side" +ccff_gears_mid = "Castle Center: Gear room center" +ccff_gears_corner = "Castle Center: Gear room corner" +ccff_lizard_knight = "Castle Center: Lizard locker knight" +ccff_lizard_pit = "Castle Center: Lizard locker room near pit" +ccff_lizard_corner = "Castle Center: Lizard locker room corner" + +# Castle Center lizard lab main locations +ccll_brokenstairs_floor = "Castle Center: Broken staircase floor" +ccll_brokenstairs_knight = "Castle Center: Broken staircase knight" +ccll_brokenstairs_save = "Castle Center: Above broken staircase savepoint" +ccll_glassknight_l = "Castle Center: Stained Glass Knight room - Left" +ccll_glassknight_r = "Castle Center: Stained Glass Knight room - Right" +ccll_butlers_door = "Castle Center: Butler bros. room near door" +ccll_butlers_side = "Castle Center: Butler bros. room inner" +ccll_cwhall_butlerflames_past = "Castle Center: Past butler room flamethrowers" +ccll_cwhall_flamethrower = "Castle Center: Inside cracked wall hallway flamethrower" +ccll_cwhall_cwflames = "Castle Center: Past upper cracked wall flamethrowers" +ccll_cwhall_wall = "Castle Center: Inside upper cracked wall" +ccll_heinrich = "Castle Center: Heinrich Meyer" + +# Castle Center library main locations +ccl_bookcase = "Castle Center: Library bookshelf" + +# Castle Center invention area main locations +ccia_nitro_crates = "Castle Center: Nitro room crates" +ccia_nitro_shelf_h = "Castle Center: Magical Nitro shelf - Heinrich side" +ccia_nitro_shelf_i = "Castle Center: Magical Nitro shelf - Invention side" +ccia_nitrohall_torch = "Castle Center: Past nitro room flamethrowers" +ccia_nitrohall_flamethrower = "Castle Center: Inside nitro hallway flamethrower" +ccia_inventions_crusher = "Castle Center: Invention room spike crusher door" +ccia_inventions_maids = "Castle Center: Invention room maid sisters door" +ccia_inventions_round = "Castle Center: Invention room round machine" +ccia_inventions_famicart = "Castle Center: Invention room giant Famicart" +ccia_inventions_zeppelin = "Castle Center: Invention room zeppelin" +ccia_maids_outer = "Castle Center: Maid sisters room outer table" +ccia_maids_inner = "Castle Center: Maid sisters room inner table" +ccia_maids_vase = "Castle Center: Maid sisters room vase" +ccia_stairs_knight = "Castle Center: Hell Knight landing corner knight" + +# Castle Center sub-weapons +ccb_skel_hallway_ba = "Castle Center: Behemoth arena hallway" +ccelv_switch = "Castle Center: Near elevator switch" +ccff_lizard_near_knight = "Castle Center: Near lizard locker knight" + +# Castle Center lizard lockers +ccff_lizard_locker_nfr = "Castle Center: Far-right near-side lizard locker" +ccff_lizard_locker_nmr = "Castle Center: Mid-right near-side lizard locker" +ccff_lizard_locker_nml = "Castle Center: Mid-left near-side lizard locker" +ccff_lizard_locker_nfl = "Castle Center: Far-left near-side lizard locker" +ccff_lizard_locker_fl = "Castle Center: Left far-side lizard locker" +ccff_lizard_locker_fr = "Castle Center: Right far-side lizard locker" + +# Castle Center 3-hit breakables +ccb_behemoth_crate1 = "Castle Center: Behemoth arena crate - Item 1" +ccb_behemoth_crate2 = "Castle Center: Behemoth arena crate - Item 2" +ccb_behemoth_crate3 = "Castle Center: Behemoth arena crate - Item 3" +ccb_behemoth_crate4 = "Castle Center: Behemoth arena crate - Item 4" +ccb_behemoth_crate5 = "Castle Center: Behemoth arena crate - Item 5" +ccelv_stand1 = "Castle Center: Elevator room unoccupied statue stand - Item 1" +ccelv_stand2 = "Castle Center: Elevator room unoccupied statue stand - Item 2" +ccelv_stand3 = "Castle Center: Elevator room unoccupied statue stand - Item 3" +ccff_lizard_slab1 = "Castle Center: Lizard locker room slab - Item 1" +ccff_lizard_slab2 = "Castle Center: Lizard locker room slab - Item 2" +ccff_lizard_slab3 = "Castle Center: Lizard locker room slab - Item 3" +ccff_lizard_slab4 = "Castle Center: Lizard locker room slab - Item 4" + + +# Duel Tower main locations +dt_stones_start = "Duel Tower: Stepping stone path start" +dt_werebull_arena = "Duel Tower: Above Were-bull arena" +dt_ibridge_l = "Duel Tower: Invisible bridge balcony - Left" +dt_ibridge_r = "Duel Tower: Invisible bridge balcony - Right" + +# Duel Tower sub-weapons +dt_stones_end = "Duel Tower: Stepping stone path end" + + +# Tower of Execution main locations +toe_midsavespikes_r = "Tower of Execution: Past mid-savepoint spikes - Right" +toe_midsavespikes_l = "Tower of Execution: Past mid-savepoint spikes - Left" +toe_elec_grate = "Tower of Execution: Electric grate ledge" +toe_ibridge = "Tower of Execution: Invisible bridge ledge" +toe_top = "Tower of Execution: Guillotine tower top level" +toe_keygate_l = "Tower of Execution: Key gate alcove - Left" + +# Tower of Execution 3-hit breakables +toe_ledge1 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 1" +toe_ledge2 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 2" +toe_ledge3 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 3" +toe_ledge4 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 4" +toe_ledge5 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 5" + +# Tower of Execution sub-weapons +toe_keygate_r = "Tower of Execution: Key gate alcove - Right" + + +# Tower of Science main locations +tosci_elevator = "Tower of Science: Elevator hallway" +tosci_plain_sr = "Tower of Science: Plain sight side room" +tosci_stairs_sr = "Tower of Science: Staircase side room" +tosci_three_door_hall = "Tower of Science: Pick-a-door hallway locked middle room" +tosci_ibridge_t = "Tower of Science: Invisible bridge platform torch" +tosci_conveyor_sr = "Tower of Science: Spiky conveyor side room" +tosci_exit = "Tower of Science: Exit hallway" +tosci_key3_r = "Tower of Science: Locked Key3 room - Right" +tosci_key3_l = "Tower of Science: Locked Key3 room - Left" + +# Tower of Science 3-hit breakables +tosci_ibridge_b1 = "Tower of Science: Invisible bridge platform crate - Item 1" +tosci_ibridge_b2 = "Tower of Science: Invisible bridge platform crate - Item 2" +tosci_ibridge_b3 = "Tower of Science: Invisible bridge platform crate - Item 3" +tosci_ibridge_b4 = "Tower of Science: Invisible bridge platform crate - Item 4" +tosci_ibridge_b5 = "Tower of Science: Invisible bridge platform crate - Item 5" +tosci_ibridge_b6 = "Tower of Science: Invisible bridge platform crate - Item 6" + +# Tower of Science sub-weapons +tosci_key3_m = "Tower of Science: Locked Key3 room - Middle" + + +# Tower of Sorcery main locations +tosor_stained_tower = "Tower of Sorcery: Stained glass tower" +tosor_savepoint = "Tower of Sorcery: Mid-savepoint platform" +tosor_trickshot = "Tower of Sorcery: Trick shot from mid-savepoint platform" +tosor_yellow_bubble = "Tower of Sorcery: Above yellow bubble" +tosor_blue_platforms = "Tower of Sorcery: Above tiny blue platforms start" +tosor_side_isle = "Tower of Sorcery: Lone red platform side island" +tosor_ibridge = "Tower of Sorcery: Invisible bridge platform" + +# Room of Clocks main locations +roc_ent_l = "Room of Clocks: Left from entrance hallway" +roc_cont_r = "Room of Clocks: Right of Contract" +roc_ent_r = "Room of Clocks: Right from entrance hallway" + +# Room of Clocks sub-weapons +roc_elev_l = "Room of Clocks: Left of elevator hallway" +roc_elev_r = "Room of Clocks: Right of elevator hallway" + +# Room of Clocks empty breakables +roc_cont_l = "Room of Clocks: Left of Contract" +roc_exit = "Room of Clocks: Left of exit" + +# Clock Tower main locations +ct_gearclimb_corner = "Clock Tower: Gear climb room corner" +ct_gearclimb_side = "Clock Tower: Gear climb room side" +ct_bp_chasm_fl = "Clock Tower: Bone Pillar chasm room - Front-left" +ct_bp_chasm_fr = "Clock Tower: Bone Pillar chasm room - Front-right" +ct_bp_chasm_k = "Clock Tower: Bone Pillar chasm room key alcove" +ct_finalroom_platform = "Clock Tower: Final room key ledge" + +# Clock Tower 3-hit breakables +ct_gearclimb_battery_slab1 = "Clock Tower: Gear climb room beneath battery slab - Item 1" +ct_gearclimb_battery_slab2 = "Clock Tower: Gear climb room beneath battery slab - Item 2" +ct_gearclimb_battery_slab3 = "Clock Tower: Gear climb room beneath battery slab - Item 3" +ct_gearclimb_door_slab1 = "Clock Tower: Gear climb room beneath door slab - Item 1" +ct_gearclimb_door_slab2 = "Clock Tower: Gear climb room beneath door slab - Item 2" +ct_gearclimb_door_slab3 = "Clock Tower: Gear climb room beneath door slab - Item 3" +ct_finalroom_door_slab1 = "Clock Tower: Final room entrance slab - Item 1" +ct_finalroom_door_slab2 = "Clock Tower: Final room entrance slab - Item 2" +ct_finalroom_renon_slab1 = "Clock Tower: Renon's final offers slab - Item 1" +ct_finalroom_renon_slab2 = "Clock Tower: Renon's final offers slab - Item 2" +ct_finalroom_renon_slab3 = "Clock Tower: Renon's final offers slab - Item 3" +ct_finalroom_renon_slab4 = "Clock Tower: Renon's final offers slab - Item 4" +ct_finalroom_renon_slab5 = "Clock Tower: Renon's final offers slab - Item 5" +ct_finalroom_renon_slab6 = "Clock Tower: Renon's final offers slab - Item 6" +ct_finalroom_renon_slab7 = "Clock Tower: Renon's final offers slab - Item 7" +ct_finalroom_renon_slab8 = "Clock Tower: Renon's final offers slab - Item 8" + +# Clock Tower sub-weapons +ct_bp_chasm_rl = "Clock Tower: Bone Pillar chasm room - Rear-left" +ct_finalroom_fr = "Clock Tower: Final room floor - front-right" +ct_finalroom_fl = "Clock Tower: Final room floor - front-left" +ct_finalroom_rr = "Clock Tower: Final room floor - rear-right" +ct_finalroom_rl = "Clock Tower: Final room floor - rear-left" + + +# Castle Keep main locations +ck_flame_l = "Castle Keep: Left Dracula door flame" +ck_flame_r = "Castle Keep: Right Dracula door flame" +ck_behind_drac = "Castle Keep: Behind Dracula's chamber" +ck_cube = "Castle Keep: Dracula's floating cube" + + +# Renon's shop locations +renon1 = "Renon's shop: Roast Chicken purchase" +renon2 = "Renon's shop: Roast Beef purchase" +renon3 = "Renon's shop: Healing Kit purchase" +renon4 = "Renon's shop: Purifying purchase" +renon5 = "Renon's shop: Cure Ampoule purchase" +renon6 = "Renon's shop: Sun Card purchase" +renon7 = "Renon's shop: Moon Card purchase" + + +# Events +forest_boss_one = "Forest of Silence: King Skeleton 1" +forest_boss_two = "Forest of Silence: Were-tiger" +forest_boss_three = "Forest of Silence: King Skeleton 2" +cw_boss = "Castle Wall: Bone Dragons" +villa_boss_one = "Villa: J. A. Oldrey" +villa_boss_two = "Villa: Undead Maiden" +uw_boss = "Underground Waterway: Lizard-man trio" +cc_boss_one = "Castle Center: Behemoth" +cc_boss_two = "Castle Center: Rosa/Camilla" +dt_boss_one = "Duel Tower: Were-jaguar" +dt_boss_two = "Duel Tower: Werewolf" +dt_boss_three = "Duel Tower: Were-bull" +dt_boss_four = "Duel Tower: Were-tiger" +roc_boss = "Room of Clocks: Death/Actrise" +ck_boss_one = "Castle Keep: Renon" +ck_boss_two = "Castle Keep: Vincent" + +cc_behind_the_seal = "Castle Center: Behind the seal" + +the_end = "Dracula" diff --git a/worlds/cv64/data/patches.py b/worlds/cv64/data/patches.py new file mode 100644 index 000000000000..938b615b3213 --- /dev/null +++ b/worlds/cv64/data/patches.py @@ -0,0 +1,2878 @@ +normal_door_hook = [ + 0x00862021, # ADDU A0, A0, A2 + 0x80849C60, # LB A0, 0x9C60 (A0) + 0x0C0FF174, # JAL 0x803FC5D0 + 0x308900FF # ANDI T1, A0, 0x00FF +] + +normal_door_code = [ + 0x00024080, # SLL T0, V0, 2 + 0x3C048039, # LUI A0, 0x8039 + 0x00882021, # ADDU A0, A0, T0 + 0x8C849BE4, # LW A0, 0x9BE4 (A0) + 0x8C6A0008, # LW T2, 0x0008 (V1) + 0x008A5824, # AND T3, A0, T2 + 0x11600003, # BEQZ T3, [forward 0x03] + 0x00000000, # NOP + 0x24020003, # ADDIU V0, R0, 0x0003 + 0x27FF006C, # ADDIU RA, RA, 0x006C + 0x03E00008 # JR RA +] + +ct_door_hook = [ + 0x0C0FF182, # JAL 0x803FC608 + 0x00000000, # NOP + 0x315900FF # ANDI T9, T2, 0x00FF +] + +ct_door_code = [ + 0x3C0A8039, # LUI T2, 0x8039 + 0x8D429BF8, # LW V0, 0x9BF8 (T2) + 0x01465021, # ADDU T2, T2, A2 + 0x814A9C60, # LB T2, 0x9C60 (T2) + 0x00495824, # AND T3, V0, T1 + 0x55600001, # BNEZL T3, [forward 0x01] + 0x27FF0010, # ADDIU RA, RA, 0x0010 + 0x03E00008 # JR RA +] + +stage_select_overwrite = [ + # Replacement for the "wipe world state" function when using the warp menu. Now it's the "Special1 jewel checker" + # to see how many destinations can be selected on it with the current count. + 0x8FA60018, # LW A2, 0x0018 (SP) + 0xA0606437, # SB R0, 0x6437 (V1) + 0x10000029, # B [forward 0x29] + 0x00000000, # NOP + 0x3C0A8039, # LUI T2, 0x8039 + 0x254A9C4B, # ADDIU T2, T2, 0x9C4B + 0x814B0000, # LB T3, 0x0000 (T2) + 0x240C000A, # ADDIU T4, R0, 0x000A + 0x016C001B, # DIVU T3, T4 + 0x00003012, # MFLO A2 + 0x24C60001, # ADDIU A2, A2, 0x0001 + 0x28CA0009, # SLTI T2, A2, 0x0009 + 0x51400001, # BEQZL T2, 0x8012AC7C + 0x24060008, # ADDIU A2, R0, 0x0008 + 0x3C0A800D, # LUI T2, 0x800D + 0x914A5E20, # LBU T2, 0x5E20 (T2) + 0x314A0040, # ANDI T2, T2, 0x0040 + 0x11400003, # BEQZ T2, [forward 0x03] + 0x240BFFFE, # ADDIU T3, R0, 0xFFFE + 0x3C0C8034, # LUI T4, 0x8034 + 0xAD8B2084, # SW T3, 0x2084 (T4) + 0x03200008, # JR T9 + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP +] + +custom_code_loader = [ + # On boot, when the company logos show up, this will trigger and load most of the custom ASM data in this module + # off from ROM offsets 0xBFC000-0xBFFFFF and into the 803FC000-803FFFFF range in RAM. + 0x3C080C10, # LUI T0, 0x0C10 + 0x2508F1C0, # ADDIU T0, T0, 0xF1C0 + 0x3C098000, # LUI T1, 0x8000 + 0xAD282438, # SW T0, 0x2438 (T1) + 0x3C088040, # LUI T0, 0x8040 + 0x9108C000, # ADDIU T0, 0xC000 (T0) + 0x15000007, # BNEZ T0, [forward 0x07] + 0x3C0400C0, # LUI A0, 0x00C0 + 0x2484C000, # ADDIU A0, A0, 0xC000 + 0x3C058040, # LUI A1, 0x8040 + 0x24A5C000, # ADDIU A1, A1, 0xC000 + 0x24064000, # ADDIU A2, R0, 0x4000 + 0x08005DFB, # J 0x800177EC + 0x00000000, # NOP + 0x03E00008 # JR RA +] + +remote_item_giver = [ + # The essential multiworld function. Every frame wherein the player is in control and not looking at a text box, + # this thing will check some bytes in RAM to see if an item or DeathLink has been received and trigger the right + # functions accordingly to either reward items or kill the player. + + # Primary checks + 0x3C088034, # LUI T0, 0x8034 + 0x9509244A, # LHU T1, 0x244A (T0) + 0x3C088039, # LUI T0, 0x8039 + 0x910A9EFB, # LBU T2, 0x9EFF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9EFF, # LBU T2, 0x9EFF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9CCF, # LBU T2, 0x9CCF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9EEF, # LBU T2, 0x9EEF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9CD3, # LBU T2, 0x9CD3 (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x3C088038, # LUI T0, 0x8038 + 0x910A7ADD, # LBU T2, 0x7ADD (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9BE0, # LBU T2, 0x9BE0 (T3) + 0x012A4821, # ADDU T1, T1, T2 + 0x11200006, # BEQZ T1, [forward 0x06] + 0x00000000, # NOP + 0x11400002, # BEQZ T2, [forward 0x02] + 0x254AFFFF, # ADDIU T2, T2, 0xFFFF + 0xA16A9BE0, # SB T2, 0x9BE0 (T3) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Item-specific checks + 0x3C088034, # LUI T0, 0x8034 + 0x91082891, # LBU T0, 0x2891 (T0) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x15090012, # BNE T0, T1, [forward 0x12] + 0x00000000, # NOP + 0x256B9BDF, # ADDIU T3, T3, 0x9BDF + 0x91640000, # LBU A0, 0x0000 (T3) + 0x14800003, # BNEZ A0, [forward 0x03] + 0x00000000, # NOP + 0x10000005, # B [forward 0x05] + 0x256B0002, # ADDIU T3, T3, 0x0002 + 0x2409000F, # ADDIU T1, R0, 0x000F + 0xA1690001, # SB T1, 0x0001 (T3) + 0x080FF8DD, # J 0x803FE374 + 0xA1600000, # SB R0, 0x0000 (T3) + 0x91640000, # LBU A0, 0x0000 (T3) + 0x14800002, # BNEZ A0, [forward 0x02] + 0x00000000, # NOP + 0x10000003, # B [forward 0x03] + 0x2409000F, # ADDIU T1, R0, 0x000F + 0x080FF864, # J 0x803FE190 + 0xA169FFFF, # SB T1, 0xFFFF (T3) + # DeathLink-specific checks + 0x3C0B8039, # LUI T3, 0x8039 + 0x256B9BE1, # ADDIU T3, T3, 0x9BE1 + 0x91640002, # LBU A0, 0x0002 (T3) + 0x14800002, # BNEZ A0, [forward 0x02] + 0x916900A7, # LBU T1, 0x00A7 (T3) + 0x080FF9C0, # J 0x803FE700 + 0x312A0080, # ANDI T2, T1, 0x0080 + 0x11400002, # BEQZ T2, [forward 0x02] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x35290080, # ORI T1, T1, 0x0080 + 0xA16900A7, # SB T1, 0x00A7 (T3) + 0x2484FFFF, # ADDIU A0, A0, 0xFFFF + 0x24080001, # ADDIU T0, R0, 0x0001 + 0x03E00008, # JR RA + 0xA168FFFD, # SB T0, 0xFFFD (T3) +] + +deathlink_nitro_edition = [ + # Alternative to the end of the above DeathLink-specific checks that kills the player with the Nitro explosion + # instead of the normal death. + 0x91690043, # LBU T1, 0x0043 (T3) + 0x080FF9C0, # J 0x803FE700 + 0x3C088034, # LUI T0, 0x8034 + 0x91082BFE, # LBU T0, 0x2BFE (T0) + 0x11000002, # BEQZ T0, [forward 0x02] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x35290080, # ORI T1, T1, 0x0080 + 0xA1690043, # SB T1, 0x0043 (T3) + 0x2484FFFF, # ADDIU A0, A0, 0xFFFF + 0x24080001, # ADDIU T0, R0, 0x0001 + 0x03E00008, # JR RA + 0xA168FFFD, # SB T0, 0xFFFD (T3) +] + +launch_fall_killer = [ + # Custom code to force the instant fall death if at a high enough falling speed after getting killed by something + # that launches you (whether it be the Nitro explosion or a Big Toss hit). The game doesn't normally run the check + # that would trigger the fall death after you get killed by some other means, which could result in a softlock + # when a killing blow launches you into an abyss. + 0x3C0C8035, # LUI T4, 0x8035 + 0x918807E2, # LBU T0, 0x07E2 (T4) + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x11090002, # BEQ T0, T1, [forward 0x02] + 0x2409000C, # ADDIU T1, R0, 0x000C + 0x15090006, # BNE T0, T1, [forward 0x06] + 0x3C098035, # LUI T1, 0x8035 + 0x91290810, # LBU T1, 0x0810 (T1) + 0x240A00C1, # ADDIU T2, R0, 0x00C1 + 0x152A0002, # BNE T1, T2, [forward 0x02] + 0x240B0001, # ADDIU T3, R0, 0x0001 + 0xA18B07E2, # SB T3, 0x07E2 (T4) + 0x03E00008 # JR RA +] + +deathlink_counter_decrementer = [ + # Decrements the DeathLink counter if it's above zero upon loading a previous state. Checking this number will be + # how the client will tell if a player's cause of death was something in-game or a DeathLink (and send a DeathLink + # to the server if it was the former). Also resets the remote item values to 00 so the player's received items don't + # get mucked up in-game. + 0x3C088039, # LUI T0, 0x8039 + 0x91099BE3, # LBU T1, 0x9BE3 (T0) + 0x11200002, # BEQZ T1, 0x803FC154 + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099BE3, # SB T1, 0x9BE3 + 0x240900FF, # ADDIU T1, R0, 0x00FF + 0xA1099BE0, # SB T1, 0x9BE0 (T0) + 0xA1009BDF, # SB R0, 0x9BDF (T0) + 0xA1009BE1, # SB R0, 0x9BE1 (T0) + 0x91099BDE, # LBU T1, 0x9BDE (T0) + 0x55200001, # BNEZL T1, [forward 0x01] + 0x24090000, # ADDIU T1, R0, 0x0000 + 0xA1099BDE, # SB T1, 0x9BDE (T0) + 0x91099C24, # LBU T1, 0x9C24 (T0) + 0x312A0080, # ANDI T2, T1, 0x0080 + 0x55400001, # BNEZL T2, [forward 0x01] + 0x3129007F, # ANDI T1, T1, 0x007F + 0x03E00008, # JR RA + 0xA1099C24 # SB T1, 0x9C24 (T0) +] + +death_flag_unsetter = [ + # Un-sets the Death status bitflag when overwriting the "Restart this stage" state and sets health to full if it's + # empty. This is to ensure DeathLinked players won't get trapped in a perpetual death loop for eternity should they + # receive one right before transitioning to a different stage. + 0x3C048039, # LUI A0, 0x8039 + 0x90889C88, # LBU T0, 0x9C88 (A0) + 0x31090080, # ANDI T1, T0, 0x0080 + 0x01094023, # SUBU T0, T0, T1 + 0x908A9C3F, # LBU T2, 0x9C3F (A0) + 0x24090064, # ADDIU T1, R0, 0x0064 + 0x51400001, # BEQZL T2, [forward 0x01] + 0xA0899C3F, # SB T1, 0x9C3F (A0) + 0x08006DAE, # J 0x8001B6B8 + 0xA0889C88 # SB T0, 0x9C88 (A0) +] + +warp_menu_opener = [ + # Enables opening the Stage Select menu by pausing while holding Z + R when not in a boss fight, the castle + # crumbling sequence following Fake Dracula, or Renon's arena (in the few seconds after his health bar vanishes). + 0x3C08800D, # LUI T0, 0x800D + 0x85095E20, # LH T1, 0x5E20 (T0) + 0x24083010, # ADDIU T0, R0, 0x3010 + 0x15090011, # BNE T0, T1, [forward 0x11] + 0x3C088035, # LUI T0, 0x8035 + 0x9108F7D8, # LBU T0, 0xF7D8 (T0) + 0x24090020, # ADDIU T1, R0, 0x0020 + 0x1109000D, # BEQ T0, T1, [forward 0x0D] + 0x3C088039, # LUI T0, 0x8039 + 0x91099BFA, # LBU T1, 0x9BFA (T0) + 0x31290001, # ANDI T1, T1, 0x0001 + 0x15200009, # BNEZ T1, [forward 0x09] + 0x8D099EE0, # LW T1, 0x9EE0 + 0x3C0A001B, # LUI T2, 0x001B + 0x254A0003, # ADDIU T2, T2, 0x0003 + 0x112A0005, # BEQ T1, T2, [forward 0x05] + 0x3C098034, # LUI T1, 0x8034 + 0xA1009BE1, # SB R0, 0x9BE1 (T0) + 0x2408FFFC, # ADDIU T0, R0, 0xFFFC + 0x0804DA70, # J 0x80136960 + 0xAD282084, # SW T0, 0x2084 (T1) + 0x0804DA70, # J 0x80136960 + 0xA44E6436 # SH T6, 0x6436 (V0) +] + +give_subweapon_stopper = [ + # Extension to "give subweapon" function to not change the player's weapon if the received item is a Stake or Rose. + # Can also increment the Ice Trap counter if getting a Rose or jump to prev_subweapon_dropper if applicable. + 0x24090011, # ADDIU T1, R0, 0x0011 + 0x11240009, # BEQ T1, A0, [forward 0x09] + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x11240003, # BEQ T1, A0, [forward 0x03] + 0x9465618A, # LHU A1, 0x618A (V1) + 0xA46D618A, # SH T5, 0x618A (V1) + 0x0804F0BF, # J 0x8013C2FC + 0x3C098039, # LUI T1, 0x8039 + 0x912A9BE2, # LBU T2, 0x9BE2 (T1) + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0xA12A9BE2, # SB T2, 0x9BE2 (T1) + 0x0804F0BF, # J 0x8013C2FC +] + +give_powerup_stopper = [ + # Extension to "give PowerUp" function to not increase the player's PowerUp count beyond 2 + 0x240D0002, # ADDIU T5, R0, 0x0002 + 0x556D0001, # BNEL T3, T5, [forward 0x01] + 0xA46C6234, # SH T4, 0x6234 (V1) + 0x0804F0BF # J 0x8013C2FC +] + +npc_item_hack = [ + # Hack to make NPC items show item textboxes when received (and decrease the Countdown if applicable). + 0x3C098039, # LUI T1, 0x8039 + 0x001F5602, # SRL T2, RA, 24 + 0x240B0080, # ADDIU T3, R0, 0x0080 + 0x114B001F, # BEQ T2, T3, [forward 0x1F] + 0x240A001A, # ADDIU T2, R0, 0x001A + 0x27BD0020, # ADDIU SP, SP, 0x20 + 0x15440004, # BNE T2, A0, [forward 0x04] + 0x240B0029, # ADDIU T3, R0, 0x0029 + 0x34199464, # ORI T9, R0, 0x9464 + 0x10000004, # B [forward 0x04] + 0x240C0002, # ADDIU T4, R0, 0x0002 + 0x3419DA64, # ORI T9, R0, 0xDA64 + 0x240B0002, # ADDIU T3, R0, 0x0002 + 0x240C000E, # ADDIU T4, R0, 0x000E + 0x012C7021, # ADDU T6, T1, T4 + 0x316C00FF, # ANDI T4, T3, 0x00FF + 0x000B5A02, # SRL T3, T3, 8 + 0x91CA9CA4, # LBU T2, 0x9CA4 (T6) + 0x3C0D8040, # LUI T5, 0x8040 + 0x256FFFFF, # ADDIU T7, T3, 0xFFFF + 0x01AF6821, # ADDU T5, T5, T7 + 0x91B8D71C, # LBU T8, 0xD71C (T5) + 0x29EF0019, # SLTI T7, T7, 0x0019 + 0x51E00001, # BEQZL T7, [forward 0x01] + 0x91B8D71F, # LBU T8, 0xD71F (T5) + 0x13000002, # BEQZ T8, [forward 0x02] + 0x254AFFFF, # ADDIU T2, T2, 0xFFFF + 0xA1CA9CA4, # SB T2, 0x9CA4 (T6) + 0xA12C9BDF, # SB T4, 0x9BDF (T1) + 0x3C0400BB, # LUI A0, 0x00BB + 0x00992025, # OR A0, A0, T9 + 0x3C058019, # LUI A1, 0x8019 + 0x24A5BF98, # ADDIU A1, A1, 0xBF98 + 0x08005DFB, # J 0x800177EC + 0x24060100, # ADDIU A2, R0, 0x0100 + 0x0804EFFD, # J 0x8013BFF4 + 0xAFBF0014 # SW RA, 0x0014 (SP) +] + +overlay_modifiers = [ + # Whenever a compressed overlay gets decompressed and mapped in the 0F or 0E domains, this thing will check the + # number ID in the T0 register to tell which one it is and overwrite some instructions in it on-the-fly accordingly + # to said number before it runs. Confirmed to NOT be a foolproof solution on console and Simple64; the instructions + # may not be properly overwritten on the first execution of the overlay. + + # Prevent being able to throw Nitro into the Hazardous Waste Disposals + 0x3C0A2402, # LUI T2, 0x2402 + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x24090023, # ADDIU T1, R0, 0x0023 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x00000000, # NOP + 0x03200008, # JR T9 + 0xAF2A01D4, # SW T2, 0x01D4 (T9) + # Make it so nothing can be taken from the Nitro or Mandragora shelves through the textboxes + 0x24090022, # ADDIU T1, R0, 0x0022 + 0x11090002, # BEQ T0, T1, [forward 0x02] + 0x24090021, # ADDIU T1, R0, 0x0021 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x254AFFFF, # ADDIU T2, T2, 0xFFFF + 0x03200008, # JR T9 + 0xAF2A0194, # SW T2, 0x0194 (T9) + # Fix to allow placing both bomb components at a cracked wall at once while having multiple copies of each, and + # prevent placing them at the downstairs crack altogether until the seal is removed. Also enables placing both in + # one interaction. + 0x24090024, # ADDIU T1, R0, 0x0024 + 0x15090012, # BNE T0, T1, [forward 0x12] + 0x240A0040, # ADDIU T2, R0, 0x0040 + 0x240BC338, # ADDIU T3, R0, 0xC338 + 0x240CC3D4, # ADDIU T4, R0, 0xC3D4 + 0x240DC38C, # ADDIU T5, R0, 0xC38C + 0xA32A030F, # SB T2, 0x030F (T9) + 0xA72B0312, # SH T3, 0x0312 (T9) + 0xA32A033F, # SB T2, 0x033F (T9) + 0xA72B0342, # SH T3, 0x0342 (T9) + 0xA32A03E3, # SB T2, 0x03E3 (T9) + 0xA72C03E6, # SH T4, 0x03E6 (T9) + 0xA32A039F, # SB T2, 0x039F (T9) + 0xA72D03A2, # SH T5, 0x03A2 (T9) + 0xA32A03CB, # SB T2, 0x03CB (T9) + 0xA72D03CE, # SH T5, 0x03CE (T9) + 0xA32A05CF, # SB T2, 0x05CF (T9) + 0x240EE074, # ADDIU T6, R0, 0xE074 + 0xA72E05D2, # SH T6, 0x05D2 (T9) + 0x03200008, # JR T9 + # Disable the costume and Hard Mode flag checks so that pressing Up on the Player Select screen will always allow + # the characters' alternate costumes to be used as well as Hard Mode being selectable without creating save data. + 0x2409012E, # ADDIU T1, R0, 0x012E + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x3C0A3C0B, # LUI T2, 0x3C0B + 0x254A8000, # ADDIU T2, T2, 0x8000 + 0x240B240E, # ADDIU T3, R0, 0x240E + 0x240C240F, # ADDIU T4, R0, 0x240F + 0x240D0024, # ADDIU T5, R0, 0x0024 + 0xAF2A0C78, # SW T2, 0x0C78 (T9) + 0xA72B0CA0, # SH T3, 0x0CA0 (T9) + 0xA72C0CDC, # SH T4, 0x0CDC (T9) + 0xA32D0168, # SB T5, 0x0024 (T9) + 0x03200008, # JR T9 + # Overwrite instructions in the Forest end cutscene script to store a spawn position ID instead of a cutscene ID. + 0x2409002E, # ADDIU T1, R0, 0x002E + 0x15090005, # BNE T0, T1, [forward 0x05] + 0x3C0AA058, # LUI T2, 0xA058 + 0x254A642B, # ADDIU T2, T2, 0x642B + 0xAF2A0D88, # SW T2, 0x0D88 (T9) + 0xAF200D98, # SW R0, 0x0D98 (T9) + 0x03200008, # JR T9 + # Disable the rapid flashing effect in the CC planetarium cutscene to ensure it won't trigger seizures. + 0x2409003E, # ADDIU T1, R0, 0x003E + 0x1509000C, # BNE T0, T1, [forward 0x0C] + 0x00000000, # NOP + 0xAF200C5C, # SW R0, 0x0C5C + 0xAF200CD0, # SW R0, 0x0CD0 + 0xAF200C64, # SW R0, 0x0C64 + 0xAF200C74, # SW R0, 0x0C74 + 0xAF200C80, # SW R0, 0x0C80 + 0xAF200C88, # SW R0, 0x0C88 + 0xAF200C90, # SW R0, 0x0C90 + 0xAF200C9C, # SW R0, 0x0C9C + 0xAF200CB4, # SW R0, 0x0CB4 + 0xAF200CC8, # SW R0, 0x0CC8 + 0x03200008, # JR T9 + 0x24090134, # ADDIU T1, R0, 0x0134 + 0x15090005, # BNE T0, T1, [forward 0x05] + 0x340B8040, # ORI T3, R0, 0x8040 + 0x340CDD20, # ORI T4, R0, 0xDD20 + 0xA72B1D1E, # SH T3, 0x1D1E (T9) + 0xA72C1D22, # SH T4, 0x1D22 (T9) + 0x03200008, # JR T9 + # Make the Ice Trap model check branch properly + 0x24090125, # ADDIU T1, R0, 0x0125 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x3C0B3C19, # LUI T3, 0x3C19 + 0x356B803F, # ORI T3, T3, 0x803F + 0xAF2B04D0, # SW T3, 0x04D0 (T9) + 0x03200008 # JR T9 +] + +double_component_checker = [ + # When checking to see if a bomb component can be placed at a cracked wall, this will run if the code lands at the + # "no need to set 2" outcome to see if the other can be set. + + # Mandragora checker + 0x10400007, # BEQZ V0, [forward 0x07] + 0x3C0A8039, # LUI T2, 0x8039 + 0x31098000, # ANDI T1, T0, 0x8000 + 0x15200008, # BNEZ T1, [forward 0x08] + 0x91499C5D, # LBU T1, 0x9C5D (T2) + 0x11200006, # BEQZ T1, 0x80183938 + 0x00000000, # NOP + 0x10000007, # B [forward 0x07] + 0x31E90100, # ANDI T1, T7, 0x0100 + 0x15200002, # BNEZ T1, [forward 0x02] + 0x91499C5D, # LBU T1, 0x9C5D (T2) + 0x15200003, # BNEZ T1, [forward 0x03] + 0x3C198000, # LUI T9, 0x8000 + 0x27391590, # ADDIU T9, T9, 0x1590 + 0x03200008, # JR T9 + 0x24090001, # ADDIU T1, R0, 0x0001 + 0xA4E9004C, # SH T1, 0x004C (A3) + 0x3C190E00, # LUI T9, 0x0E00 + 0x273903E0, # ADDIU T9, T9, 0x03E0 + 0x03200008, # JR T9 + 0x00000000, # NOP + # Nitro checker + 0x10400007, # BEQZ V0, [forward 0x07] + 0x3C0A8039, # LUI T2, 0x8039 + 0x31694000, # ANDI T1, T3, 0x4000 + 0x15200008, # BNEZ T1, [forward 0x08] + 0x91499C5C, # LBU T1, 0x9C5C + 0x11200006, # BEQZ T1, [forward 0x06] + 0x00000000, # NOP + 0x1000FFF4, # B [backward 0x0B] + 0x914F9C18, # LBU T7, 0x9C18 (T2) + 0x31E90002, # ANDI T1, T7, 0x0002 + 0x1520FFEC, # BNEZ T1, [backward 0x13] + 0x91499C5C, # LBU T1, 0x9C5C (T2) + 0x1520FFEF, # BNEZ T1, [backward 0x15] + 0x00000000, # NOP + 0x1000FFE8, # B [backward 0x17] + 0x00000000, # NOP +] + +downstairs_seal_checker = [ + # This will run specifically for the downstairs crack to see if the seal has been removed before then deciding to + # let the player set the bomb components or not. An anti-dick measure, since there is a limited number of each + # component per world. + 0x14400004, # BNEZ V0, [forward 0x04] + 0x3C0A8039, # LUI T2, 0x8039 + 0x914A9C18, # LBU T2, 0x9C18 (T2) + 0x314A0001, # ANDI T2, T2, 0x0001 + 0x11400003, # BEQZ T2, [forward 0x03] + 0x3C198000, # LUI T9, 0x8000 + 0x27391448, # ADDIU T9, T9, 0x1448 + 0x03200008, # JR T9 + 0x3C190E00, # LUI T9, 0x0E00 + 0x273902B4, # ADDIU T9, T9, 0x02B4 + 0x03200008, # JR T9 + 0x00000000, # NOP +] + +map_data_modifiers = [ + # Overwrites the map data table on-the-fly after it loads and before the game reads it to load objects. Good for + # changing anything that is part of a compression chain in the ROM data, including some freestanding item IDs. + # Also jumps to the function that overwrites the "Restart this stage" data if entering through the back of a level. + + 0x08006DAA, # J 0x8001B6A8 + 0x00000000, # NOP + # Demo checker (if we're in a title demo, don't do any of this) + 0x3C028034, # LUI V0, 0x8034 + 0x9449244A, # LHU T1, 0x244A (V0) + 0x11200002, # BEQZ T1, [forward 0x02] + # Zero checker (if there are zeroes in the word at 0x8034244A, where the entity list address is stored, don't do + # any of this either) + 0x8C422B00, # LW V0, 0x2B00 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + 0x14400002, # BNEZ V0, [forward 0x02] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x3C088039, # LUI T0, 0x8039 + 0x91199EE3, # LBU T9, 0x9EE3 (T0) + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + # Forest of Silence (replaces 1 invisible chicken) + 0x15000006, # BNEZ T0, [forward 0x06] + 0x340A0001, # ORI T2, R0, 0x0001 <- Werewolf plaque + 0xA44A01C8, # SH T2, 0x01C8 (V0) + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x1139FFED, # BEQ T1, T9, [backward 0x12] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Villa front yard (replaces 1 moneybag and 2 beefs) + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x15090008, # BNE T0, T1, [forward 0x08] + 0x340A0001, # ORI T2, R0, 0x0001 <- Fountain FL + 0x340B0001, # ORI T3, R0, 0x0001 <- Fountain RL + 0x340C001F, # ORI T4, R0, 0x0001 <- Dog food gate + 0xA44A0058, # SH T2, 0x0058 (V0) + 0xA44B0038, # SH T3, 0x0038 (V0) + 0xA44C0068, # SH T4, 0x0068 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Villa living area (Replaces 1 chicken, 1 knife, and 3 invisible Purifyings and assigns flags to the sub-weapons) + 0x24090005, # ADDIU T1, R0, 0x0005 + 0x15090025, # BNE T0, T1, [forward 0x25] + 0x340B0010, # ORI T3, R0, 0x0001 <- Hallway axe + 0xA44B00B8, # SH T3, 0x00B8 (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Storeroom R + 0x340B0010, # ORI T3, R0, 0x0001 <- Hallway knife + 0x340C0001, # ORI T4, R0, 0x0001 <- Living Room painting + 0x340D0001, # ORI T5, R0, 0x0001 <- Dining Room vase + 0x340E0001, # ORI T6, R0, 0x0001 <- Archives table + 0xA44A0078, # SH T2, 0x0078 (V0) + 0xA44B00C8, # SH T3, 0x00C8 (V0) + 0xA44C0108, # SH T4, 0x0108 (V0) + 0xA44D0128, # SH T5, 0x0128 (V0) + 0xA44E0138, # SH T6, 0x0138 (V0) + 0x340A0000, # ORI T2, R0, 0x0000 <- Sub-weapons left flag half + 0xA44A009C, # SH T2, 0x009C (V0) + 0xA44A00AC, # SH T2, 0x00AC (V0) + 0xA44A00BC, # SH T2, 0x00BC (V0) + 0xA44A00CC, # SH T2, 0x00CC (V0) + 0x340A0000, # ORI T2, R0, 0x0000 <- Sub-weapons right flag halves + 0x240B0000, # ADDIU T3, R0, 0x0000 + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x240D0000, # ADDIU T5, R0, 0x0000 + 0xA44A00CA, # SH T2, 0x00CA (V0) + 0xA44B00BA, # SH T3, 0x00BA (V0) + 0xA44C009A, # SH T4, 0x009A (V0) + 0xA44D00AA, # SH T5, 0x00AA (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Near bed + 0x340B0010, # ORI T3, R0, 0x0001 <- Storeroom L + 0x340C0001, # ORI T4, R0, 0x0001 <- Storeroom statue + 0x340D0001, # ORI T5, R0, 0x0001 <- Exit knight + 0x340E0001, # ORI T6, R0, 0x0001 <- Sitting room table + 0xA44A0048, # SH T2, 0x0078 (V0) + 0xA44B0088, # SH T3, 0x00C8 (V0) + 0xA44C00D8, # SH T4, 0x0108 (V0) + 0xA44D00F8, # SH T5, 0x0128 (V0) + 0xA44E0118, # SH T6, 0x0138 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Tunnel (replaces 1 invisible Cure Ampoule) + 0x24090007, # ADDIU T1, R0, 0x0007 + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x340A0001, # ORI T2, R0, 0x0001 <- Twin arrow signs + 0xA44A0268, # SH T2, 0x0268 (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Bucket + 0xA44A0258, # SH T2, 0x0258 (V0) + 0x240B0005, # ADDIU T3, R0, 0x0005 + 0xA04B0150, # SB T3, 0x0150 (V0) + 0x24090011, # ADDIU T1, R0, 0x0011 + 0x1139FFB0, # BEQ T1, T9, [backward 0x50] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Center factory floor (replaces 1 moneybag, 1 jewel, and gives every lizard man coffin item a unique flag) + 0x2409000B, # ADDIU T1, R0, 0x000B + 0x15090016, # BNE T0, T1, [forward 0x16] + 0x340A001A, # ORI T2, R0, 0x001A <- Lizard coffin nearside mid-right + 0x340B0003, # ORI T3, R0, 0x0003 <- Lizard coffin nearside mid-left + 0xA44A00C8, # SH T2, 0x00C8 (V0) + 0xA44B00D8, # SH T3, 0x00D8 (V0) + 0x240A1000, # ADDIU T2, R0, 0x1000 + 0x240B2000, # ADDIU T3, R0, 0x2000 + 0x240C0400, # ADDIU T4, R0, 0x0400 + 0x240D0800, # ADDIU T5, R0, 0x0800 + 0x240E0200, # ADDIU T6, R0, 0x0200 + 0x240F0100, # ADDIU T7, R0, 0x0100 + 0xA44A009A, # SH T2, 0x009A (V0) + 0xA44B00AA, # SH T3, 0x00AA (V0) + 0xA44C00CA, # SH T4, 0x00CA (V0) + 0xA44D00BA, # SH T5, 0x00BA (V0) + 0xA44E00DA, # SH T6, 0x00DA (V0) + 0xA44F00EA, # SH T7, 0x00EA (V0) + 0x340A0017, # ORI T2, R0, 0x0017 <- Lizard coffin nearside mid-right + 0x340B000C, # ORI T3, R0, 0x000C <- Lizard coffin nearside mid-left + 0xA44A00A8, # SH T2, 0x00C8 (V0) + 0xA44B00E8, # SH T3, 0x00D8 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Duel Tower (replaces a flame on top of a rotating lion pillar with a White Jewel on the invisible bridge ledge) + 0x24090013, # ADDIU T1, R0, 0x0013 + 0x1509000F, # BNE T0, T1, [forward 0x0F] + 0x3C0A00B9, # LUI T2, 0x00BB + 0x254A012B, # ADDIU T2, T2, 0x012B + 0x3C0BFE2A, # LUI T3, 0xFE2A + 0x256B0027, # ADDIU T3, T3, 0x0027 + 0x3C0C0001, # LUI T4, 0x0001 + 0x3C0D0022, # LUI T5, 0x0022 + 0x25AD0100, # ADDIU T5, T5, 0x0100 + 0xAC4A0A80, # SW T2, 0x0AE0 (V0) + 0xAC4B0A84, # SW T3, 0x0AE4 (V0) + 0xAC4C0A88, # SW T4, 0x0AE8 (V0) + 0xAC4D0A8C, # SW T5, 0x0AEC (V0) + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x1139FF87, # BEQ T1, T9, [backward 0x77] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Keep outside (replaces 1 invisible Healing Kit and gives both invisible Healing Kits pickup flags) + 0x24090014, # ADDIU T1, R0, 0x0014 + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x340A0001, # ORI T2, R0, 0x0001 <- Right flame + 0xA44A0058, # SH T2, 0x0058 (V0) + 0x240A0001, # ADDIU T2, R0, 0x0001 + 0x240B0002, # ADDIU T3, R0, 0x0002 + 0xA44A004A, # SH T2, 0x004A (V0) + 0xA44B005A, # SH T3, 0x005A (V0) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x1139FF7B, # BEQ T0, T1, [backward 0x74] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Wall main area (sets a flag for the freestanding Holy Water if applicable and the "beginning of stage" + # state if entered from the rear) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x15090006, # BNE T0, T1, [forward 0x06] + 0x24090000, # ADDIU T1, R0, 0x0000 + 0xA049009B, # SB T1, 0x009B (V0) + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x1139FF73, # BEQ T1, T9, [backward 0x8D] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Villa vampire crypt (sets the "beginning of stage" state if entered from the rear, as well as the "can warp here" + # flag if arriving for the first time) + 0x2409001A, # ADDIU T1, R0, 0x001A + 0x15090008, # BNE T0, T1, [forward 0x08] + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C1C, # LBU T3, 0x9C1C (T2) + 0x356B0001, # ORI T3, T3, 0x0001 + 0xA14B9C1C, # SB T3, 0x9C1C (T2) + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x1139FF69, # BEQ T1, T9, [backward 0x98] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Underground Waterway (sets the "beginning of stage" state if entered from the rear) + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x15090004, # BNE T0, T1, [forward 0x04] + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x1139FF63, # BEQ T1, T9, [backward 0x9F] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Center elevator top (sets the "beginning of stage" state if entered from either rear, as well as the "can + # warp here" flag if arriving for the first time) + 0x2409000F, # ADDIU T1, R0, 0x000F + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C1C, # LBU T3, 0x9C1C (T2) + 0x356B0002, # ORI T3, T3, 0x0002 + 0xA14B9C1C, # SB T3, 0x9C1C (T2) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x1139FF59, # BEQ T1, T9, [backward 0xAA] + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x1139FF57, # BEQ T1, T9, [backward 0xAC] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Tower of Execution (sets the "beginning of stage" state if entered from the rear) + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x15090004, # BNE T0, T1, [forward 0x10] + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x1139FF51, # BEQ T1, T9, [backward 0xAF] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Tower of Sorcery (sets the "beginning of stage" state if entered from the rear) + 0x24090011, # ADDIU T1, R0, 0x0011 + 0x15090004, # BNE T0, T1, [forward 0x04] + 0x24090013, # ADDIU T1, R0, 0x0013 + 0x1139FF4B, # BEQ T1, T9, [backward 0xBA] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Tower of Science (sets the "beginning of stage" state if entered from the rear) + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x15090004, # BNE T0, T1, [forward 0x04] + 0x24090004, # ADDIU T1, R0, 0x0004 + 0x1139FF45, # BEQ T1, T9, [backward 0xC1] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Room of Clocks (changes 2 candle settings if applicable and sets the "begging of stage" state if spawning at end) + 0x2409001B, # ADDIU T1, R0, 0x001B + 0x15090008, # BNE T0, T1, [forward 0x08] + 0x24090006, # ADDIU T1, R0, 0x0006 + 0x240A0006, # ADDIU T2, R0, 0x0006 + 0xA0490059, # SB T1, 0x0059 (V0) + 0xA04A0069, # SB T2, 0x0069 (V0) + 0x24090014, # ADDIU T1, R0, 0x0014 + 0x1139FF3B, # BEQ T1, T9, [backward 0xCC] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Center basement (changes 2 non-pickup-able Mandragoras into 2 real items and moves the torture shelf item + # forward slightly if it's turned visible) + 0x24090009, # ADDIU T1, R0, 0x0009 + 0x15090011, # BNE T0, T1, [forward 0x11] + 0x3409FFFC, # ORI T1, R0, 0xFFFC + 0xA44907C0, # SH T1, 0x07C0 (V0) + 0xA44907D0, # SH T1, 0x07D0 (V0) + 0x240A0027, # ADDIU T2, R0, 0x0027 + 0xA44A07C6, # SH T2, 0x07C6 (V0) + 0xA44A07D6, # SH T2, 0x07D6 (V0) + 0x340B0001, # ORI T3, R0, 0x0001 <- Right Mandragora + 0x340C0001, # ORI T4, R0, 0x0001 <- Left Mandragora + 0xA44B07C8, # SH T3, 0x07C8 (V0) + 0xA44C07D8, # SH T4, 0x07D8 (V0) + 0x240D00F5, # ADDIU T5, R0, 0x00F5 + 0xA04D06D1, # SB T5, 0x06D1 (V0) + 0x24090040, # ADDIU T1, R0, 0x0040 + 0x240A0080, # ADDIU T2, R0, 0x0080 + 0xA04907CA, # SB T1, 0x07CA (V0) + 0xA04A07DA, # SB T2, 0x07DA (V0) + 0x03E00008, # JR RA + # Castle Center nitro area (changes 2 non-pickup-able Nitros into 2 real items) + 0x2409000E, # ADDIU T1, R0, 0x000E + 0x15090015, # BNE T0, T1, [forward 0x15] + 0x240900C0, # ADDIU T1, R0, 0x00C0 + 0x240A00CE, # ADDIU T2, R0, 0x00CE + 0xA0490471, # SB T1, 0x0471 (V0) + 0xA04A04A1, # SB T2, 0x04A1 (V0) + 0x24090027, # ADDIU T1, R0, 0x0027 + 0xA4490476, # SH T1, 0x0476 (V0) + 0xA44904A6, # SH T1, 0x04A6 (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Invention-side shelf + 0x340B0001, # ORI T3, R0, 0x0001 <- Heinrich-side shelf + 0xA44A0478, # SH T2, 0x0478 (V0) + 0xA44B04A8, # SH T3, 0x04A8 (V0) + 0x24090080, # ADDIU T1, R0, 0x0080 + 0xA049047A, # SB T1, 0x047A (V0) + 0xA440047C, # SH R0, 0x047C (V0) + 0x240A0400, # ADDIU T2, R0, 0x0400 + 0x340BFF05, # ORI T3, R0, 0xFF05 + 0xA44A04AA, # SH T2, 0x04AA (V0) + 0xA44B04AC, # SH T3, 0x04AC (V0) + 0x24090046, # ADDIU T1, R0, 0x0046 + 0xA04904A3, # SB T1, 0x04A3 (V0) + 0x03E00008, # JR RA + # Fan meeting room (sets "beginning of stage" flag) + 0x24090019, # ADDIU T1, R0, 0x0019 + 0x1109FF0D, # BEQ T1, T9, [backward 0xFB] + 0x00000000, # NOP + 0x03E00008, # JR RA +] + +renon_cutscene_checker = [ + # Prevents Renon's departure/pre-fight cutscene from playing if the player is either in the escape sequence or both + # did not spend the required 30K to fight him and lacks the required Special2s to fight Dracula. + 0x15810002, # BNE T4, AT, [forward 0x02] + 0x00000000, # NOP + 0x08049EB3, # J 0x80127ACC + 0x24090016, # ADDIU T1, R0, 0x0016 + 0x11C90002, # BEQ T6, T1, [forward 0x02] + 0x00000000, # NOP + 0x08049ECA, # J 0x80127B28 + 0x24190000, # ADDIU T9, R0, 0x0000 + 0x8C696208, # LW T1, 0x6208 (V1) + 0x292A7531, # SLTI T2, T1, 0x7531 + 0x51400001, # BEQZL T2, [forward 0x01] + 0x24190001, # ADDIU T9, R0, 0x0001 + 0x3C0B8013, # LUI T3, 0x8013 + 0x916BAC9F, # LBU T3, 0xAC9F (T3) + 0x906C6194, # LBU T4, 0x6194 (V1) + 0x018B502A, # SLT T2, T4, T3 + 0x51400001, # BEQZL T2, [forward 0x01] + 0x24190001, # ADDIU T9, R0, 0x0001 + 0x90696142, # LBU T1, 0x6142 (V1) + 0x31290002, # ANDI T1, T1, 0x0002 + 0x55200001, # BNEZL T1, [forward 0x01] + 0x24190000, # ADDIU T9, R0, 0x0000 + 0x17200003, # BNEZ T9, [forward 0x03] + 0x00000000, # NOP + 0x08049ECC, # J 0x80127B30 + 0x00000000, # NOP + 0x08049ECA # J 0x80127B28 +] + +renon_cutscene_checker_jr = [ + # Like renon_cutscene_checker, but without the checks for the Special2 and spent money counters. Inserted instead if + # the player chooses to guarantee or disable the Renon fight on their YAML. + 0x15810002, # BNE T4, AT, [forward 0x02] + 0x00000000, # NOP + 0x08049EB3, # J 0x80127ACC + 0x24090016, # ADDIU T1, R0, 0x0016 + 0x11C90002, # BEQ T6, T1, [forward 0x02] + 0x00000000, # NOP + 0x08049ECA, # J 0x80127B28 + 0x24190001, # ADDIU T9, R0, 0x0001 + 0x90696142, # LBU T1, 0x6142 (V1) + 0x31290002, # ANDI T1, T1, 0x0002 + 0x55200001, # BNEZL T1, [forward 0x01] + 0x24190000, # ADDIU T9, R0, 0x0000 + 0x17200003, # BNEZ T9, [forward 0x03] + 0x00000000, # NOP + 0x08049ECC, # J 0x80127B30 + 0x00000000, # NOP + 0x08049ECA # J 0x80127B28 +] + +ck_door_music_player = [ + # Plays Castle Keep's song if you spawn in front of Dracula's door (teleporting via the warp menu) and haven't + # started the escape sequence yet. + 0x17010002, # BNE T8, AT, [forward 0x02] + 0x00000000, # NOP + 0x08063DF9, # J 0x8018F7E4 + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x3C088039, # LUI T0, 0x8039 + 0x91089BFA, # LBU T0, 0x9BFA (T0) + 0x31080002, # ANDI T0, T0, 0x0002 + 0x51090001, # BEQL T0, T1, [forward 0x01] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x24080003, # ADDIU T0, R0, 0x0003 + 0x51180001, # BEQL T0, T8, [forward 0x01] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x240B0002, # ADDIU T3, R0, 0x0002 + 0x114B0002, # BEQ T2, T3, [forward 0x02] + 0x00000000, # NOP + 0x08063DFD, # J 0x8018F7F4 + 0x00000000, # NOP + 0x08063DF9 # J 0x8018F7E4 +] + +dracula_door_text_redirector = [ + # Switches the standard pointer to the map text with one to a custom message for Dracula's chamber door if the + # current scene is Castle Keep exterior (Scene 0x14). + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x24090014, # ADDIU T1, R0, 0x0014 + 0x15090006, # BNE T0, T1, [forward 0x06] + 0x3C088014, # LUI T0, 0x8014 + 0x2508B9F4, # ADDIU T0, T0, 0xB9F4 + 0x151F0003, # BNE T0, RA, [forward 0x03] + 0x00000000, # NOP + 0x3C028040, # LUI V0, 0x8040 + 0x2442CC48, # ADDIU V0, V0, 0xCC48 + 0x03E00008 # JR RA +] + +coffin_time_checker = [ + # When entering the Villa coffin, this will check to see whether it's day or night and send you to either the Tunnel + # or Underground Waterway level slot accordingly regardless of which character you are + 0x28490006, # SLTI T1, V0, 0x0006 + 0x15200005, # BNEZ T1, [forward 0x05] + 0x28490012, # SLTI T1, V0, 0x0012 + 0x11200003, # BEQZ T1, [forward 0x03] + 0x00000000, # NOP + 0x08055AEB, # J 0x80156BAC + 0x00000000, # NOP + 0x08055AED # J 0x80156BB4 +] + +werebull_flag_unsetter = [ + # This will un-set Were-bull's defeat flag in Duel Tower after beating him so that the check above his arena can + # still be acquired later, if it hasn't been acquired already. This is the only check in the entire game that can be + # permanently missed even with the ability to return to levels. + 0x3C0E0400, # LUI T6, 0x0400 + 0x15CF0006, # BNE T6, T7, [forward 0x06] + 0x00187402, # SRL T6, T8, 16 + 0x31CE2000, # ANDI T6, T6, 0x2000 + 0x15C00003, # BNEZ T6, [forward 0x03] + 0x3C0E0020, # LUI T6, 0x0020 + 0x014E5025, # OR T2, T2, T6 + 0xAC4A613C, # SW T2, 0x613C (V0) + 0x03200008 # JR T9 +] + +werebull_flag_unsetter_special2_electric_boogaloo = [ + # Like werebull_flag_unsetter, but with the added feature of awarding a Special2 after determining the player isn't + # trying to beat Were-bull twice! This will be inserted over the former if the goal is set to boss hunt. + 0x3C0E0400, # LUI T6, 0x0400 + 0x15CF0008, # BNE T6, T7, [forward 0x06] + 0x00187402, # SRL T6, T8, 16 + 0x31CE2000, # ANDI T6, T6, 0x2000 + 0x15C00005, # BNEZ T6, [forward 0x05] + 0x3C0E0020, # LUI T6, 0x0020 + 0x014EC024, # AND T8, T2, T6 + 0x014E5025, # OR T2, T2, T6 + 0xAC4A613C, # SW T2, 0x613C (V0) + 0x17000003, # BNEZ T8, [forward 0x03] + 0x3C188039, # LUI T8, 0x8039 + 0x240E0005, # ADDIU T6, R0, 0x0005 + 0xA30E9BDF, # SB T6, 0x9BDF (T8) + 0x03200008 # JR T9 +] + +werebull_flag_pickup_setter = [ + # Checks to see if an item being picked up is the one on top of Were-bull's arena. If it is, then it'll check to see + # if our makeshift "Were-bull defeated once" flag and, if it is, set Were-bull's arena flag proper, so it'll + # permanently stay down. + 0x3C088038, # LUI T0, 0x8038 + 0x25083AC8, # ADDIU T0, T0, 0x3AC8 + 0x15020007, # BNE T0, V0, [forward 0x07] + 0x3C082000, # LUI T0, 0x2000 + 0x15040005, # BNE T0, A0, [forward 0x05] + 0x9449612C, # LHU T1, 0x612C (V0) + 0x31290020, # ANDI T1, T1, 0x0020 + 0x11200002, # BEQZ T1, [forward 0x02] + 0x3C0A0400, # LUI T2, 0x0400 + 0x014D6825, # OR T5, T2, T5 + 0xAC4D612C, # SW T5, 0x612C (V0) + 0x03E00008 # JR RA +] + +boss_special2_giver = [ + # Enables the rewarding of Special2s upon the vanishing of a boss's health bar when defeating it. + + # Also sets a flag in the case of the Castle Wall White Dragons' health bar going away. Their defeat flag in vanilla + # is tied to hitting the lever after killing them, so this alternate flag is used to track them for the "All Bosses" + # goal in the event someone kills them and then warps out opting to not be a Konami pachinko champ. + 0x3C118035, # LUI S1, 0x8035 + 0x962DF834, # LHU T5, 0xF834 (S1) + 0x240E3F73, # ADDIU T6, R0, 0x3F73 + 0x15AE0012, # BNE T5, T6, [forward 0x12] + 0x3C118039, # LUI S1, 0x8039 + 0x922D9EE1, # LBU T5, 0x9EE1 (S1) + 0x240E0013, # ADDIU T6, R0, 0x0013 + 0x11AE000E, # BEQ T5, T6, [forward 0x0E] + 0x922F9BFA, # LBU T7, 0x9BFA (S1) + 0x31EF0001, # ANDI T7, T7, 0x0001 + 0x15E0000B, # BNEZ T7, [forward 0x0B] + 0x240E0002, # ADDIU T6, R0, 0x0002 + 0x15AE0006, # BNE T5, T6, [forward 0x06] + 0x00000000, # NOP + 0x862F9BF4, # LH T7, 0x9BF4 (S1) + 0x31ED0080, # ANDI T5, T7, 0x0080 + 0x15A00005, # BNEZ T5, [forward 0x05] + 0x35EF0080, # ORI T7, T7, 0x0080 + 0xA62F9BF4, # SH T7, 0x9BF4 (S1) + 0x240D0005, # ADDIU T5, R0, 0x0005 + 0xA22D9BDF, # SB T5, 0x9BDF (S1) + 0xA22D9BE0, # SB T5, 0x9BE0 (S1) + 0x03E00008 # JR RA +] + +boss_goal_checker = [ + # Checks each boss flag to see if every boss with a health meter has been defeated and puts 0x0004 in V0 to + # disallow opening Dracula's door if not all have been. + 0x3C0A8039, # LUI T2, 0x8039 + 0x954B9BF4, # LHU T3, 0x9BF4 (T2) + 0x316D0BA0, # ANDI T5, T3, 0x0BA0 + 0x914B9BFB, # LBU T3, 0x9BFB (T2) + 0x000B6182, # SRL T4, T3, 6 + 0x11800010, # BEQZ T4, [forward 0x10] + 0x240C00C0, # ADDIU T4, R0, 0x00C0 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9BFD, # LBU T3, 0x9BFD (T2) + 0x316C0020, # ANDI T4, T3, 0x0020 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9BFE, # LBU T3, 0x9BFE (T2) + 0x316C0010, # ANDI T4, T3, 0x0010 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9C18, # LBU T3, 0x9C18 (T2) + 0x316C0010, # ANDI T4, T3, 0x0010 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9C1B, # LBU T3, 0x9C1B (T2) + 0x000B6102, # SRL T4, T3, 4 + 0x11800005, # BEQZ T4, [forward 0x05] + 0x240C0050, # ADDIU T4, R0, 0x0050 + 0x01AC6821, # ADDU T5, T5, T4 + 0x240E0CF0, # ADDIU T6, R0, 0x0CF0 + 0x55CD0001, # BNEL T6, T5, [forward 0x01] + 0x24020004, # ADDIU V0, R0, 0x0004 + 0x03E00008 # JR RA +] + +special_goal_checker = [ + # Checks the Special2 counter to see if the specified threshold has been reached and puts 0x0001 in V0 to disallow + # opening Dracula's door if it hasn't been. + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C4C, # LBU T3, 0x9C4C (T2) + 0x296A001E, # SLTI T2, T3, 0x001E + 0x55400001, # BNEZL T2, 0x8012AC8C + 0x24020001, # ADDIU V0, R0, 0x0001 + 0x03E00008 # JR RA +] + +warp_menu_rewrite = [ + # Rewrite to the warp menu code to ensure each option can have its own scene ID, spawn ID, and fade color. + # Start Warp + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x1000001F, # B [forward 0x1F] + 0x3C0F8000, # LUI T7, 0x8000 + # Warp 1 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x1000001B, # B [forward 0x1B] + 0x3C0F8040, # LUI T7, 0x8040 + # Warp 2 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x10000017, # B [forward 0x17] + 0x3C0F8080, # LUI T7, 0x8080 + # Warp 3 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x10000013, # B [forward 0x13] + 0x3C0F0080, # LUI T7, 0x0080 + # Warp 4 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x3C0F0080, # LUI T7, 0x0080 + 0x1000000E, # B [forward 0x0E] + 0x25EF8000, # ADDIU T7, T7, 0x8000 + # Warp 5 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x1000000A, # B [forward 0x0A] + 0x340F8000, # ORI T7, R0, 0x8000 + # Warp 6 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x3C0F8000, # LUI T7, 0x8000 + 0x10000005, # B [forward 0x05] + 0x35EF8000, # ORI T7, T7, 0x8000 + # Warp 7 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x3C0F8040, # LUI T7, 0x8040 + 0x35EF8000, # ORI T7, T7, 0x8000 + # Warp Crypt + 0x3C18800D, # LUI T8, 0x800D + 0x97185E20, # LHU T8, 0x5E20 (T8) + 0x24192000, # ADDIU T9, R0, 0x2000 + 0x17190009, # BNE T8, T9, [forward 0x09] + 0x3C088039, # LUI T0, 0x8039 + 0x91089C1C, # LBU T0, 0x9C1C (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x1100000F, # BEQZ T0, [forward 0x0F] + 0x00000000, # NOP + 0x3C0E001A, # LUI T6, 0x001A + 0x25CE0003, # ADDIU T6, T6, 0x0003 + 0x1000000B, # B [forward 0x0B] + 0x240F0000, # ADDIU T7, R0, 0x0000 + # Warp Elevator + 0x24190010, # ADDIU T9, R0, 0x0010 + 0x17190008, # BNE T8, T9, [forward 0x08] + 0x91089C1C, # LBU T0, 0x9C1C (T0) + 0x31080002, # ANDI T0, T0, 0x0002 + 0x11000005, # BEQZ T0, [forward 0x05] + 0x00000000, # NOP + 0x3C0E000F, # LUI T6, 0x000F + 0x25CE0001, # ADDIU T6, T6, 0x0001 + 0x3C0F8080, # LUI T7, 0x8080 + 0x35EF8000, # ORI T7, T7, 0x8000 + # All + 0xAC6E6428, # SW T6, 0x6428 (V1) + 0xAC6F642C, # SW T7, 0x642C (V1) + 0x2402001E, # ADDIU V0, R0, 0x001E + 0xA4626430, # SH V0, 0x6430 (V1) + 0xA4626432, # SH V0, 0x6432 (V1) +] + +warp_pointer_table = [ + # Changed pointer table addresses to go with the warp menu rewrite + 0x8012AD74, + 0x8012AD84, + 0x8012AD94, + 0x8012ADA4, + 0x8012ADB4, + 0x8012ADC8, + 0x8012ADD8, + 0x8012ADEC, +] + +spawn_coordinates_extension = [ + # Checks if the 0x10 bit is set in the spawn ID and references the below list of custom spawn coordinates if it is. + 0x316A0010, # ANDI T2, T3, 0x0010 + 0x11400003, # BEQZ T2, [forward 0x03] + 0x8CD90008, # LW T9, 0x0008 (A2) + 0x3C198040, # LUI T9, 0x8040 + 0x2739C2CC, # ADDIU T9, T9, 0xC2CC + 0x08054A83, # J 0x80152A0C + 0x00000000, # NOP + 0x00000000, # NOP + + # Castle Wall end: 10 + # player camera focus point + # x = 0xFFFF 0xFFFF 0xFFFF + # y = 0x0003 0x0012 0x000D + # z = 0xFFF3 0xEDFF 0xFFF3 + # r = 0xC000 + 0x0000FFFF, + 0x0003FFF3, + 0xC000FFFF, + 0x0012FFED, + 0xFFFF000D, + 0xFFF30000, + + # Tunnel end: 11 + # player camera focus point + # x = 0x0088 0x0087 0x0088 + # y = 0x01D6 0x01F1 0x01E5 + # z = 0xF803 0xF7D2 0xF803 + # r = 0xC000 + 0x008801D6, + 0xF803C000, + 0x008701F1, + 0xF7D20088, + 0x01E5F803, + + # Tower of Execution end: 12 + # player camera focus point + # x = 0x00AC 0x00EC 0x00AC + # y = 0x0154 0x0183 0x0160 + # z = 0xFE8F 0xFE8F 0xFE8F + # r = 0x8000 + 0x000000AC, + 0x0154FE8F, + 0x800000EC, + 0x0183FE8F, + 0x00AC0160, + 0xFE8F0000, + + # Tower of Sorcery end: 13 + # player camera focus point + # x = 0xFEB0 0xFE60 0xFEB0 + # y = 0x0348 0x036D 0x0358 + # z = 0xFEFB 0xFEFB 0xFEFB + # r = 0x0000 + 0xFEB00348, + 0xFEFB0000, + 0xFE60036D, + 0xFEFBFEB0, + 0x0358FEFB, + + # Room of Clocks end: 14 + # player camera focus point + # x = 0x01B1 0x01BE 0x01B1 + # y = 0x0006 0x001B 0x0015 + # z = 0xFFCD 0xFFCD 0xFFCD + # r = 0x8000 + 0x000001B1, + 0x0006FFCD, + 0x800001BE, + 0x001BFFCD, + 0x01B10015, + 0xFFCD0000, + + # Duel Tower savepoint: 15 + # player camera focus point + # x = 0x00B9 0x00B9 0x00B9 + # y = 0x012B 0x0150 0x0138 + # z = 0xFE20 0xFE92 0xFE20 + # r = 0xC000 + 0x00B9012B, + 0xFE20C000, + 0x00B90150, + 0xFE9200B9, + 0x0138FE20 +] + +waterway_end_coordinates = [ + # Underground Waterway end: 01 + # player camera focus point + # x = 0x0397 0x03A1 0x0397 + # y = 0xFFC4 0xFFDC 0xFFD3 + # z = 0xFDB9 0xFDB8 0xFDB9 + # r = 0x8000 + 0x00000397, + 0xFFC4FDB9, + 0x800003A1, + 0xFFDCFDB8, + 0x0397FFD3, + 0xFDB90000 +] + +continue_cursor_start_checker = [ + # This is used to improve the Game Over screen's "Continue" menu by starting the cursor on whichever checkpoint + # is most recent instead of always on "Previously saved". If a menu has a cursor start value of 0xFF in its text + # data, this will read the byte at 0x80389BC0 to determine which option to start the cursor on. + 0x8208001C, # LB T0, 0x001C(S0) + 0x05010003, # BGEZ T0, [forward 0x03] + 0x3C098039, # LUI T1, 0x8039 + 0x81289BC0, # LB T0, 0x9BC0 (T1) + 0xA208001C, # SB T0, 0x001C (S0) + 0x03E00008 # JR RA +] + +savepoint_cursor_updater = [ + # Sets the value at 0x80389BC0 to 0x00 after saving to let the Game Over screen's "Continue" menu know to start the + # cursor on "Previously saved" as well as updates the entrance variable for B warping. It then jumps to + # deathlink_counter_decrementer in the event we're loading a save from the Game Over screen. + 0x3C088039, # LUI T0, 0x8039 + 0x91099C95, # LBU T1, 0x9C95 (T0) + 0x000948C0, # SLL T1, T1, 3 + 0x3C0A8018, # LUI T2, 0x8018 + 0x01495021, # ADDU T2, T2, T1 + 0x914B17CF, # LBU T3, 0x17CF (T2) + 0xA10B9EE3, # SB T3, 0x9EE3 (T0) + 0xA1009BC0, # SB R0, 0x9BC0 (T0) + 0x080FF8F0 # J 0x803FE3C0 +] + +stage_start_cursor_updater = [ + # Sets the value at 0x80389BC0 to 0x01 after entering a stage to let the Game Over screen's "Continue" menu know to + # start the cursor on "Restart this stage". + 0x3C088039, # LUI T0, 0x8039 + 0x24090001, # ADDIU T1, R0, 0x0001 + 0xA1099BC0, # SB T1, 0x9BC0 (T0) + 0x03E00008 # JR RA +] + +elevator_flag_checker = [ + # Prevents the top elevator in Castle Center from activating if the bottom elevator switch is not turned on. + 0x3C088039, # LUI T0, 0x8039 + 0x91089C07, # LBU T0, 0x9C07 (T0) + 0x31080002, # ANDI T0, T0, 0x0002 + 0x15000002, # BNEZ T0, [forward 0x02] + 0x848E004C, # LH T6, 0x004C (A0) + 0x240E0000, # ADDIU T6, R0, 0x0000 + 0x03E00008 # JR RA +] + +crystal_special2_giver = [ + # Gives a Special2 upon activating the big crystal in CC basement. + 0x3C098039, # LUI T1, 0x8039 + 0x24190005, # ADDIU T9, R0, 0x0005 + 0xA1399BDF, # SB T9, 0x9BDF (T1) + 0x03E00008, # JR RA + 0x3C198000 # LUI T9, 0x8000 +] + +boss_save_stopper = [ + # Prevents usage of a White Jewel if in a boss fight. Important for the lizard-man trio in Waterway as escaping + # their fight by saving/reloading can render a Special2 permanently missable. + 0x24080001, # ADDIU T0, R0, 0x0001 + 0x15030005, # BNE T0, V1, [forward 0x05] + 0x3C088035, # LUI T0, 0x8035 + 0x9108F7D8, # LBU T0, 0xF7D8 (T0) + 0x24090020, # ADDIU T1, R0, 0x0020 + 0x51090001, # BEQL T0, T1, [forward 0x01] + 0x24020000, # ADDIU V0, R0, 0x0000 + 0x03E00008 # JR RA +] + +music_modifier = [ + # Uses the ID of a song about to be played to pull a switcheroo by grabbing a new ID from a custom table to play + # instead. A hacky way to circumvent song IDs in the compressed overlays' "play song" function calls, but it works! + 0xAFBF001C, # SW RA, 0x001C (SP) + 0x0C004A6B, # JAL 0x800129AC + 0x44800000, # MTC1 R0, F0 + 0x10400003, # BEQZ V0, [forward 0x03] + 0x3C088040, # LUI T0, 0x8040 + 0x01044821, # ADDU T1, T0, A0 + 0x9124CD20, # LBU A0, 0xCD20 (T1) + 0x08004E64 # J 0x80013990 +] + +music_comparer_modifier = [ + # The same as music_modifier, but for the function that compares the "song to play" ID with the one that's currently + # playing. This will ensure the randomized music doesn't reset when going through a loading zone in Villa or CC. + 0x3C088040, # LUI T0, 0x8040 + 0x01044821, # ADDU T1, T0, A0 + 0x9124CD20, # LBU A0, 0xCD20 (T1) + 0x08004A60, # J 0x80012980 +] + +item_customizer = [ + # Allows changing an item's appearance settings and visibility independent of what it actually is as well as setting + # its bitflag literally anywhere in the save file by changing things in the item actor's data as it's being created + # for the below three functions to then utilize. + 0x03205825, # OR T3, T9, R0 + 0x000B5A02, # SRL T3, T3, 8 + 0x316C0080, # ANDI T4, T3, 0x0080 + 0xA0CC0041, # SB T4, 0x0041 (A2) + 0x016C5823, # SUBU T3, T3, T4 + 0xA0CB0040, # SB T3, 0x0040 (A2) + 0x333900FF, # ANDI T9, T9, 0x00FF + 0xA4D90038, # SH T9, 0x0038 (A2) + 0x8CCD0058, # LW T5, 0x0058 (A2) + 0x31ACFF00, # ANDI T4, T5, 0xFF00 + 0x340EFF00, # ORI T6, R0, 0xFF00 + 0x158E000A, # BNE T4, T6, [forward 0x0A] + 0x31AC00FF, # ANDI T4, T5, 0x00FF + 0x240E0002, # ADDIU T6, R0, 0x0002 + 0x018E001B, # DIVU T4, T6 + 0x00006010, # MFHI T4 + 0x000D5C02, # SRL T3, T5, 16 + 0x51800001, # BEQZL T4, [forward 0x01] + 0x000B5C00, # SLL T3, T3, 16 + 0x00006012, # MFLO T4 + 0xA0CC0055, # SB T4, 0x0055 (A2) + 0xACCB0058, # SW T3, 0x0058 (A2) + 0x080494E5, # J 0x80125394 + 0x032A0019 # MULTU T9, T2 +] + +item_appearance_switcher = [ + # Determines an item's model appearance by checking to see if a different item appearance ID was written in a + # specific spot in the actor's data; if one wasn't, then the appearance value will be grabbed from the item's entry + # in the item property table like normal instead. + 0x92080040, # LBU T0, 0x0040 (S0) + 0x55000001, # BNEZL T0, T1, [forward 0x01] + 0x01002025, # OR A0, T0, R0 + 0x03E00008, # JR RA + 0xAFA70024 # SW A3, 0x0024 (SP) +] + +item_model_visibility_switcher = [ + # If 80 is written one byte ahead of the appearance switch value in the item's actor data, parse 0C00 to the + # function that checks if an item should be invisible or not. Otherwise, grab that setting from the item property + # table like normal. + 0x920B0041, # LBU T3, 0x0041 (S0) + 0x316E0080, # ANDI T6, T3, 0x0080 + 0x11C00003, # BEQZ T6, [forward 0x03] + 0x240D0C00, # ADDIU T5, R0, 0x0C00 + 0x03E00008, # JR RA + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x958D0004 # LHU T5, 0x0004 (T4) +] + +item_shine_visibility_switcher = [ + # Same as the above, but for item shines instead of the model. + 0x920B0041, # LBU T3, 0x0041 (S0) + 0x31690080, # ANDI T1, T3, 0x0080 + 0x11200003, # BEQZ T1, [forward 0x03] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x240C0C00, # ADDIU T4, R0, 0x0C00 + 0x03E00008, # JR RA + 0x958CA908 # LHU T4, 0xA908 (T4) +] + +three_hit_item_flags_setter = [ + # As the function to create items from the 3HB item lists iterates through said item lists, this will pass unique + # flag values to each item when calling the "create item instance" function by right-shifting said flag by a number + # of bits depending on which item in the list it is. Unlike the vanilla game which always puts flags of 0x00000000 + # on each of these. + 0x8DC80008, # LW T0, 0x0008 (T6) + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x00084C02, # SRL T1, T0, 16 + 0x3108FFFF, # ANDI T0, T0, 0xFFFF + 0x00094842, # SRL T1, T1, 1 + 0x15200003, # BNEZ T1, [forward 0x03] + 0x00000000, # NOP + 0x34098000, # ORI T1, R0, 0x8000 + 0x25080001, # ADDIU T0, T0, 0x0001 + 0x0154582A, # SLT T3, T2, S4 + 0x1560FFF9, # BNEZ T3, [backward 0x07] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x00094C00, # SLL T1, T1, 16 + 0x01094025, # OR T0, T0, T1 + 0x0805971E, # J 0x80165C78 + 0xAFA80010 # SW T0, 0x0010 (SP) +] + +chandelier_item_flags_setter = [ + # Same as the above, but for the unique function made specifically and ONLY for the Villa foyer chandelier's item + # list. KCEK, why the heck did you have to do this!? + 0x8F280014, # LW T0, 0x0014 (T9) + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x00084C02, # SRL T1, T0, 16 + 0x3108FFFF, # ANDI T0, T0, 0xFFFF + 0x00094842, # SRL T1, T1, 1 + 0x15200003, # BNEZ T1, [forward 0x03] + 0x00000000, # NOP + 0x34098000, # ORI T1, R0, 0x8000 + 0x25080001, # ADDIU T0, T0, 0x0001 + 0x0155582A, # SLT T3, T2, S5 + 0x1560FFF9, # BNEZ T3, [backward 0x07] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x00094C00, # SLL T1, T1, 16 + 0x01094025, # OR T0, T0, T1 + 0x0805971E, # J 0x80165C78 + 0xAFA80010 # SW T0, 0x0010 (SP) +] + +prev_subweapon_spawn_checker = [ + # When picking up a sub-weapon this will check to see if it's different from the one the player already had (if they + # did have one) and jump to prev_subweapon_dropper, which will spawn a subweapon actor of what they had before + # directly behind them. + 0x322F3031, # Previous sub-weapon bytes + 0x10A00009, # BEQZ A1, [forward 0x09] + 0x00000000, # NOP + 0x10AD0007, # BEQ A1, T5, [forward 0x07] + 0x3C088040, # LUI T0, 0x8040 + 0x01054021, # ADDU T0, T0, A1 + 0x0C0FF418, # JAL 0x803FD060 + 0x9104CFC3, # LBU A0, 0xCFC3 (T0) + 0x2484FF9C, # ADDIU A0, A0, 0xFF9C + 0x3C088039, # LUI T0, 0x8039 + 0xAD049BD4, # SW A0, 0x9BD4 (T0) + 0x0804F0BF, # J 0x8013C2FC + 0x24020001 # ADDIU V0, R0, 0x0001 +] + +prev_subweapon_fall_checker = [ + # Checks to see if a pointer to a previous sub-weapon drop actor spawned by prev_subweapon_dropper is in 80389BD4 + # and calls the function in prev_subweapon_dropper to lower the weapon closer to the ground on the next frame if a + # pointer exists and its actor ID is 0x0027. Once it hits the ground or despawns, the connection to the actor will + # be severed by 0-ing out the pointer. + 0x3C088039, # LUI T0, 0x8039 + 0x8D049BD4, # LW A0, 0x9BD4 (T0) + 0x10800008, # BEQZ A0, [forward 0x08] + 0x00000000, # NOP + 0x84890000, # LH T1, 0x0000 (A0) + 0x240A0027, # ADDIU T2, R0, 0x0027 + 0x152A0004, # BNE T1, T2, [forward 0x04] + 0x00000000, # NOP + 0x0C0FF452, # JAL 0x803FD148 + 0x00000000, # NOP + 0x50400001, # BEQZL V0, [forward 0x01] + 0xAD009BD4, # SW R0, 0x9BD4 (T0) + 0x080FF40F # J 0x803FD03C +] + +prev_subweapon_dropper = [ + # Spawns a pickup actor of the sub-weapon the player had before picking up a new one behind them at their current + # position like in other CVs. This will enable them to pick it back up again if they still want it. + # Courtesy of Moisés; see derp.c in the src folder for the C source code. + 0x27BDFFC8, + 0xAFBF001C, + 0xAFA40038, + 0xAFB00018, + 0x0C0006B4, + 0x2404016C, + 0x00402025, + 0x0C000660, + 0x24050027, + 0x1040002B, + 0x00408025, + 0x3C048035, + 0x848409DE, + 0x00042023, + 0x0C0230D4, + 0x3084FFFF, + 0x44822000, + 0x3C018040, + 0xC428D370, + 0x468021A0, + 0x3C048035, + 0x848409DE, + 0x00042023, + 0x46083282, + 0x3084FFFF, + 0x0C01FFAC, + 0xE7AA0024, + 0x44828000, + 0x3C018040, + 0xC424D374, + 0x468084A0, + 0x27A40024, + 0x00802825, + 0x3C064100, + 0x46049182, + 0x0C004562, + 0xE7A6002C, + 0x3C058035, + 0x24A509D0, + 0x26040064, + 0x0C004530, + 0x27A60024, + 0x3C018035, + 0xC42809D4, + 0x3C0140A0, + 0x44815000, + 0x00000000, + 0x460A4400, + 0xE6100068, + 0xC6120068, + 0xE6120034, + 0x8FAE0038, + 0xA60E0038, + 0x8FBF001C, + 0x8FB00018, + 0x27BD0038, + 0x03E00008, + 0x00000000, + 0x3C068040, + 0x24C6D368, + 0x90CE0000, + 0x27BDFFE8, + 0xAFBF0014, + 0x15C00027, + 0x00802825, + 0x240400DB, + 0x0C0006B4, + 0xAFA50018, + 0x44802000, + 0x3C038040, + 0x2463D364, + 0x3C068040, + 0x24C6D368, + 0x8FA50018, + 0x1040000A, + 0xE4640000, + 0x8C4F0024, + 0x3C013F80, + 0x44814000, + 0xC5E60044, + 0xC4700000, + 0x3C018040, + 0x46083280, + 0x460A8480, + 0xE432D364, + 0x94A20038, + 0x2401000F, + 0x24180001, + 0x10410006, + 0x24010010, + 0x10410004, + 0x2401002F, + 0x10410002, + 0x24010030, + 0x14410005, + 0x3C014040, + 0x44813000, + 0xC4640000, + 0x46062200, + 0xE4680000, + 0xA0D80000, + 0x10000023, + 0x24020001, + 0x3C038040, + 0x2463D364, + 0xC4600000, + 0xC4A20068, + 0x3C038039, + 0x24639BD0, + 0x4600103E, + 0x00001025, + 0x45000006, + 0x00000000, + 0x44808000, + 0xE4A00068, + 0xA0C00000, + 0x10000014, + 0xE4700000, + 0x3C038039, + 0x24639BD0, + 0x3C018019, + 0xC42AC870, + 0xC4600000, + 0x460A003C, + 0x00000000, + 0x45000006, + 0x3C018019, + 0xC432C878, + 0x46120100, + 0xE4640000, + 0xC4600000, + 0xC4A20068, + 0x46001181, + 0x24020001, + 0xE4A60068, + 0xC4A80068, + 0xE4A80034, + 0x8FBF0014, + 0x27BD0018, + 0x03E00008, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x0000001B, + 0x060048E0, + 0x40000000, + 0x06AEFFD3, + 0x06004B30, + 0x40000000, + 0x00000000, + 0x06004CB8, + 0x0000031A, + 0x002C0000, + 0x060059B8, + 0x40000248, + 0xFFB50186, + 0x06005B68, + 0xC00001DF, + 0x00000000, + 0x06005C88, + 0x80000149, + 0x00000000, + 0x06005DC0, + 0xC0000248, + 0xFFB5FE7B, + 0x06005F70, + 0xC00001E0, + 0x00000000, + 0x06006090, + 0x8000014A, + 0x00000000, + 0x06007D28, + 0x4000010E, + 0xFFF100A5, + 0x06007F60, + 0xC0000275, + 0x00000000, + 0x06008208, + 0x800002B2, + 0x00000000, + 0x060083B0, + 0xC000010D, + 0xFFF2FF5C, + 0x060085E8, + 0xC0000275, + 0x00000000, + 0x06008890, + 0x800002B2, + 0x00000000, + 0x3D4CCCCD, + 0x3FC00000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0xB8000100, + 0xB8000100, +] + +subweapon_surface_checker = [ + # During the process of remotely giving an item received via multiworld, this will check to see if the item being + # received is a subweapon and, if it is, wait until the player is not above an abyss or instant kill surface before + # giving it. This is to ensure dropped previous subweapons won't land somewhere inaccessible. + 0x2408000D, # ADDIU T0, R0, 0x000D + 0x11040006, # BEQ T0, A0, [forward 0x06] + 0x2409000E, # ADDIU T1, R0, 0x000E + 0x11240004, # BEQ T1, A0, [forward 0x04] + 0x2408000F, # ADDIU T0, R0, 0x000F + 0x11040002, # BEQ T0, A0, [forward 0x02] + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x1524000B, # BNE T1, A0, [forward 0x0B] + 0x3C0A800D, # LUI T2, 0x800D + 0x8D4A7B5C, # LW T2, 0x7B5C (T2) + 0x1140000E, # BEQZ T2, [forward 0x0E] + 0x00000000, # NOP + 0x914A0001, # LBU T2, 0x0001 (T2) + 0x240800A2, # ADDIU T0, R0, 0x00A2 + 0x110A000A, # BEQ T0, T2, [forward 0x0A] + 0x24090092, # ADDIU T1, R0, 0x0092 + 0x112A0008, # BEQ T1, T2, [forward 0x08] + 0x24080080, # ADDIU T0, R0, 0x0080 + 0x110A0006, # BEQ T0, T2, [forward 0x06] + 0x956C00DD, # LHU T4, 0x00DD (T3) + 0xA1600000, # SB R0, 0x0000 (T3) + 0x258C0001, # ADDIU T4, T4, 0x0001 + 0x080FF8D0, # J 0x803FE340 + 0xA56C00DD, # SH T4, 0x00DD (T3) + 0x00000000, # NOP + 0x03E00008 # JR RA +] + +countdown_number_displayer = [ + # Displays a number below the HUD clock of however many items are left to find in whichever stage the player is in. + # Which number in the save file to display depends on which map the player is currently on. It can track either + # items marked progression only or all locations in the stage. + # Courtesy of Moisés; see print_text_ovl.c in the src folder for the C source code. + 0x27BDFFD8, + 0xAFBF0024, + 0x00002025, + 0x0C000360, + 0x2405000C, + 0x3C038040, + 0x3C198034, + 0x2463D6D0, + 0x37392814, + 0x240E0002, + 0x3C0F0860, + 0x24180014, + 0xAC620000, + 0xAFB80018, + 0xAFAF0014, + 0xAFAE0010, + 0xAFB9001C, + 0x00002025, + 0x00402825, + 0x2406001E, + 0x0C0FF55D, + 0x24070028, + 0x8FBF0024, + 0x3C018040, + 0xAC22D6D4, + 0x03E00008, + 0x27BD0028, + 0x27BDFFE0, + 0xAFA40020, + 0x93AE0023, + 0x3C058039, + 0xAFBF001C, + 0x3C048040, + 0x3C068040, + 0x240F0014, + 0x00AE2821, + 0x90A59CA4, + 0xAFAF0010, + 0x8CC6D6D0, + 0x8C84D6D4, + 0x0C0FF58A, + 0x24070002, + 0x8FBF001C, + 0x27BD0020, + 0x03E00008, + 0x00000000, + 0x00000000, + 0x00000000, + 0x90820000, + 0x00001825, + 0x50400008, + 0xA4A00000, + 0xA4A20000, + 0x90820001, + 0x24840001, + 0x24A50002, + 0x1440FFFB, + 0x24630001, + 0xA4A00000, + 0x03E00008, + 0x00601025, + 0x27BDFFD8, + 0xAFBF0024, + 0xAFB0001C, + 0xAFA40028, + 0xAFA5002C, + 0xAFB10020, + 0xAFA60030, + 0xAFA70034, + 0x00008025, + 0x24050064, + 0x0C000360, + 0x00002025, + 0x8FA40040, + 0x00408825, + 0x3C05800A, + 0x10800004, + 0x8FA6003C, + 0x0C04B2E2, + 0x8CA5B450, + 0x00408025, + 0x5200001A, + 0x8FBF0024, + 0x12200017, + 0x8FAE0028, + 0x11C00015, + 0x02002025, + 0x97A5002E, + 0x97A60032, + 0x0C04B33F, + 0x24070001, + 0x02002025, + 0x83A50037, + 0x87A6003A, + 0x00003825, + 0x0C04B345, + 0xAFA00010, + 0x8FA40028, + 0x0C0FF51C, + 0x02202825, + 0x0C006CF0, + 0x02202025, + 0x02002025, + 0x02202825, + 0x00003025, + 0x0C04B34E, + 0x00003825, + 0x8FBF0024, + 0x02001025, + 0x8FB0001C, + 0x8FB10020, + 0x03E00008, + 0x27BD0028, + 0x27BDFFD8, + 0x8FAE0044, + 0xAFB00020, + 0xAFBF0024, + 0xAFA40028, + 0xAFA5002C, + 0xAFA60030, + 0xAFA70034, + 0x11C00007, + 0x00008025, + 0x3C05800A, + 0x8CA5B450, + 0x01C02025, + 0x0C04B2E2, + 0x8FA6003C, + 0x00408025, + 0x12000017, + 0x8FAF002C, + 0x11E00015, + 0x02002025, + 0x97A50032, + 0x97A60036, + 0x0C04B33F, + 0x24070001, + 0x02002025, + 0x24050001, + 0x24060064, + 0x00003825, + 0x0C04B345, + 0xAFA00010, + 0x8FA40028, + 0x8FA5002C, + 0x93A6003B, + 0x0C04B5BD, + 0x8FA70040, + 0x02002025, + 0x8FA5002C, + 0x00003025, + 0x0C04B34E, + 0x00003825, + 0x8FBF0024, + 0x02001025, + 0x8FB00020, + 0x03E00008, + 0x27BD0028, + 0x27BDFFE8, + 0xAFBF0014, + 0xAFA40018, + 0xAFA5001C, + 0xAFA60020, + 0x10C0000B, + 0xAFA70024, + 0x00A02025, + 0x00C02825, + 0x93A60027, + 0x0C04B5BD, + 0x8FA70028, + 0x8FA20018, + 0x3C010100, + 0x8C4F0000, + 0x01E1C025, + 0xAC580000, + 0x8FBF0014, + 0x27BD0018, + 0x03E00008, + 0x00000000, + 0xAFA50004, + 0x1080000E, + 0x30A500FF, + 0x24010001, + 0x54A10008, + 0x8C980000, + 0x8C8E0000, + 0x3C017FFF, + 0x3421FFFF, + 0x01C17824, + 0x03E00008, + 0xAC8F0000, + 0x8C980000, + 0x3C018000, + 0x0301C825, + 0xAC990000, + 0x03E00008, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +] + +countdown_number_manager = [ + # Tables and code for managing things about the Countdown number at the appropriate times. + 0x00010102, # Map ID offset table start + 0x02020D03, + 0x04050505, + 0x0E0E0E05, + 0x07090806, + 0x0C0C000B, + 0x0C050D0A, + 0x00000000, # Table end + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000001, # Major identifiers table start + 0x01000000, + 0x00000000, + 0x00000000, + 0x01000000, + 0x01010000, + 0x00010101, + 0x01010101, + 0x01010101, + 0x01010000, + 0x00000000, # Table end + # Decrements the counter upon picking up an item if the counter should be decremented. + 0x90E80039, # LBU T0, 0x0039 (A3) + 0x240B0011, # ADDIU T3, R0, 0x0011 + 0x110B0002, # BEQ T0, T3, [forward 0x02] + 0x90EA0040, # LBU T2, 0x0040 (A3) + 0x2548FFFF, # ADDIU T0, T2, 0xFFFF + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDIU T1, T1, T0 + 0x9129D71C, # LBU T1, 0xD71C (T1) + 0x11200009, # BEQZ T1, [forward 0x09] + 0x3C088039, # LUI T0, 0x8039 + 0x91099EE1, # LBU T1, 0x9EE1 (T0) + 0x3C0A8040, # LUI T2, 0x8040 + 0x01495021, # ADDU T2, T2, T1 + 0x914AD6DC, # LBU T2, 0xD6DC (T2) + 0x010A4021, # ADDU T0, T0, T2 + 0x91099CA4, # LBU T1, 0x9CA4 (T0) + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099CA4, # SB T1, 0x9CA4 (T0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Moves the number to/from its pause menu position when pausing/un-pausing. + 0x3C088040, # LUI T0, 0x8040 + 0x8D08D6D4, # LW T0, 0xD6D4 + 0x11000009, # BEQZ T0, [forward 0x09] + 0x92090000, # LBU T1, 0x0000 (S0) + 0x14200004, # BNEZ AT, [forward 0x04] + 0x3C0A0033, # LUI T2, 0x0033 + 0x254A001F, # ADDIU T2, T2, 0x001F + 0x03E00008, # JR RA + 0xAD0A0014, # SW T2, 0x0014 (T0) + 0x3C0A00D4, # LUI T2, 0x00D4 + 0x254A003C, # ADDIU T2, T2, 0x003C + 0xAD0A0014, # SW T2, 0x0014 (T0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Hides the number when going into a cutscene or the Options menu. + 0x3C048040, # LUI A0, 0x8040 + 0x8C84D6D4, # LW A0, 0xD6D4 (A0) + 0x0C0FF59F, # JAL 0x803FD67C + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804DFE0, # J 0x80137FB0 + 0x3C048000, # LUI A0, 0x8000 + 0x00000000, # NOP + # Un-hides the number when leaving a cutscene or the Options menu. + 0x3C048040, # LUI A0, 0x8040 + 0x8C84D6D4, # LW A0, 0xD6D4 (A0) + 0x0C0FF59F, # JAL 0x803FD67C + 0x24050001, # ADDIU A1, R0, 0x0000 + 0x0804DFFA, # J 0x8013 + 0x3C047FFF, # LUI A0, 0x7FFFF + 0x00000000, # NOP + # Kills the last map's pointer to the Countdown stuff. + 0x3C088040, # LUI T0, 0x8040 + 0xFD00D6D0, # SD R0, 0xD6D0 (T0) + 0x03E00008 # JR RA +] + +new_game_extras = [ + # Upon starting a new game, this will write anything extra to the save file data that the run should have at the + # start. The initial Countdown numbers begin here. + 0x24080000, # ADDIU T0, R0, 0x0000 + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x11090008, # BEQ T0, T1, [forward 0x08] + 0x3C0A8040, # LUI T2, 0x8040 + 0x01485021, # ADDU T2, T2, T0 + 0x8D4AD818, # LW T2, 0xD818 (T2) + 0x3C0B8039, # LUI T3, 0x8039 + 0x01685821, # ADDU T3, T3, T0 + 0xAD6A9CA4, # SW T2, 0x9CA4 (T3) + 0x1000FFF8, # B [backward 0x08] + 0x25080004, # ADDIU T0, T0, 0x0004 + # start_inventory begins here + 0x3C088039, # LUI T0, 0x8039 + 0x91099C27, # LBU T1, 0x9C27 (T0) + 0x31290010, # ANDI T1, T1, 0x0010 + 0x15200005, # BNEZ T1, [forward 0x05] + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting jewels + 0xA1099C49, # SB T1, 0x9C49 + 0x3C0A8040, # LUI T2, 0x8040 + 0x8D4BE514, # LW T3, 0xE514 (T2) <- Starting money + 0xAD0B9C44, # SW T3, 0x9C44 (T0) + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting PowerUps + 0xA1099CED, # SB T1, 0x9CED (T0) + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting sub-weapon + 0xA1099C43, # SB T1, 0x9C43 (T0) + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting Ice Traps + 0xA1099BE2, # SB T1, 0x9BE2 (T0) + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x240D0022, # ADDIU T5, R0, 0x0022 + 0x11AC0007, # BEQ T5, T4, [forward 0x07] + 0x3C0A8040, # LUI T2, 0x8040 + 0x014C5021, # ADDU T2, T2, T4 + 0x814AE518, # LB T2, 0xE518 <- Starting inventory items + 0x25080001, # ADDIU T0, T0, 0x0001 + 0xA10A9C4A, # SB T2, 0x9C4A (T0) + 0x1000FFF9, # B [backward 0x07] + 0x258C0001, # ADDIU T4, T4, 0x0001 + 0x03E00008 # JR RA +] + +shopsanity_stuff = [ + # Everything related to shopsanity. + # Flag table (in bytes) start + 0x80402010, + 0x08000000, + 0x00000000, + 0x00000000, + 0x00040200, + # Replacement item table (in halfwords) start + 0x00030003, + 0x00030003, + 0x00030000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000003, + 0x00030000, + # Switches the vanilla item being bought with the randomized one, if its flag is un-set, and sets its flag. + 0x3C088040, # LUI T0, 0x8040 + 0x01044021, # ADDU T0, T0, A0 + 0x9109D8CA, # LBU T1, 0xD8CA (T0) + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9C1D, # LBU T2, 0x9C1D (T3) + 0x01496024, # AND T4, T2, T1 + 0x15800005, # BNEZ T4, [forward 0x05] + 0x01495025, # OR T2, T2, T1 + 0xA16A9C1D, # SB T2, 0x9C1D (T3) + 0x01044021, # ADDU T0, T0, A0 + 0x9504D8D8, # LHU A0, 0xD8D8 (T0) + 0x308400FF, # ANDI A0, A0, 0x00FF + 0x0804EFFB, # J 0x8013BFEC + 0x00000000, # NOP + # Switches the vanilla item model on the buy menu with the randomized item if the randomized item isn't purchased. + 0x3C088040, # LUI T0, 0x8040 + 0x01044021, # ADDU T0, T0, A0 + 0x9109D8CA, # LBU T1, 0xD8CA (T0) + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9C1D, # LBU T2, 0x9C1D (T3) + 0x01495024, # AND T2, T2, T1 + 0x15400005, # BNEZ T2, [forward 0x05] + 0x01044021, # ADDU T0, T0, A0 + 0x9504D8D8, # LHU A0, 0xD8D8 (T0) + 0x00046202, # SRL T4, A0, 8 + 0x55800001, # BNEZL T4, [forward 0x01] + 0x01802021, # ADDU A0, T4, R0 + 0x0804F180, # J 0x8013C600 + 0x00000000, # NOP + # Replacement item names table start. + 0x00010203, + 0x04000000, + 0x00000000, + 0x00000000, + 0x00050600, + 0x00000000, + # Switches the vanilla item name in the shop menu with the randomized item if the randomized item isn't purchased. + 0x3C088040, # LUI T0, 0x8040 + 0x01064021, # ADDU T0, T0, A2 + 0x9109D8CA, # LBU T1, 0xD8CA (T0) + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9C1D, # LBU T2, 0x9C1D (T3) + 0x01495024, # AND T2, T2, T1 + 0x15400004, # BNEZ T2, [forward 0x04] + 0x00000000, # NOP + 0x9105D976, # LBU A1, 0xD976 (T0) + 0x3C048001, # LUI A0, 8001 + 0x3484A100, # ORI A0, A0, 0xA100 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + # Displays "Not purchased." if the selected randomized item is nor purchased, or the current holding amount of that + # slot's vanilla item if it is. + 0x3C0C8040, # LUI T4, 0x8040 + 0x018B6021, # ADDU T4, T4, T3 + 0x918DD8CA, # LBU T5, 0xD8CA (T4) + 0x3C0E8039, # LUI T6, 0x8039 + 0x91D89C1D, # LBU T8, 0x9C1D (T6) + 0x030DC024, # AND T8, T8, T5 + 0x13000003, # BEQZ T8, [forward 0x03] + 0x00000000, # NOP + 0x0804E819, # J 0x8013A064 + 0x00000000, # NOP + 0x0804E852, # J 0x8013A148 + 0x820F0061, # LB T7, 0x0061 (S0) + 0x00000000, # NOP + # Displays a custom item description if the selected randomized item is not purchased. + 0x3C088040, # LUI T0, 0x8040 + 0x01054021, # ADDU T0, T0, A1 + 0x9109D8D0, # LBU T1, 0xD8D0 (T0) + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C1D, # LBU T3, 0x9C1D (T2) + 0x01695824, # AND T3, T3, T1 + 0x15600003, # BNEZ T3, [forward 0x03] + 0x00000000, # NOP + 0x3C048002, # LUI A0, 0x8002 + 0x24849C00, # ADDIU A0, A0, 0x9C00 + 0x0804B39F # J 0x8012CE7C +] + +special_sound_notifs = [ + # Plays a distinct sound whenever you get enough Special1s to unlock a new location or enough Special2s to unlock + # Dracula's door. + 0x3C088013, # LUI A0, 0x8013 + 0x9108AC9F, # LBU T0, 0xAC57 (T0) + 0x3C098039, # LUI T1, 0x8039 + 0x91299C4C, # LBU T1, 0x9C4B (T1) + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x00000000, # NOP + 0x0C004FAB, # JAL 0x80013EAC + 0x24040162, # ADDIU A0, R0, 0x0162 + 0x0804F0BF, # J 0x8013C2FC + 0x00000000, # NOP + 0x3C088013, # LUI T0, 0x8013 + 0x9108AC57, # LBU T0, 0xAC57 (T0) + 0x3C098039, # LUI T1, 0x8039 + 0x91299C4B, # LBU T1, 0x9C4B (T1) + 0x0128001B, # DIVU T1, T0 + 0x00005010, # MFHI + 0x15400006, # BNEZ T2, [forward 0x06] + 0x00005812, # MFLO T3 + 0x296C0008, # SLTI T4, T3, 0x0008 + 0x11800003, # BEQZ T4, [forward 0x03] + 0x00000000, # NOP + 0x0C004FAB, # JAL 0x80013EAC + 0x2404019E, # ADDIU A0, R0, 0x019E + 0x0804F0BF # J 0x8013C2FC +] + +map_text_redirector = [ + # Checks for Map Texts 06 or 08 if in the Forest or Castle Wall Main maps respectively and redirects the text + # pointer to a blank string, skipping all the yes/no prompt text for pulling levers. + 0x0002FFFF, # Dummy text string + 0x3C0B8039, # LUI T3, 0x8039 + 0x91689EE1, # LBU T0, 0x9EE1 (T3) + 0x1100000F, # BEQZ T0, [forward 0x0F] + 0x24090006, # ADDIU T1, R0, 0x0006 + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x110A000C, # BEQ T0, T2, [forward 0x0C] + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x240A0009, # ADDIU T2, R0, 0x0009 + 0x110A0009, # BEQ T0, T2, [forward 0x09] + 0x24090004, # ADDIU T1, R0, 0x0004 + 0x240A000A, # ADDIU T2, R0, 0x000A + 0x110A0006, # BEQ T0, T2, [forward 0x06] + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x110A0003, # BEQ T0, T2, [forward 0x03] + 0x2409000C, # ADDIU T1, R0, 0x000C + 0x10000008, # B 0x803FDB34 + 0x00000000, # NOP + 0x15250006, # BNE T1, A1, [forward 0x06] + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484DACC, # ORI A0, A0, 0xDACC + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + # Redirects to a custom message if you try placing the bomb ingredients at the bottom CC crack before deactivating + # the seal. + 0x24090009, # ADDIU T1, R0, 0x0009 + 0x15090009, # BNE T0, T1, [forward 0x09] + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x15450007, # BNE T2, A1, [forward 0x07] + 0x916A9C18, # LBU T2, 0x9C18 (T3) + 0x314A0001, # ANDI T2, T2, 0x0001 + 0x15400004, # BNEZ T2, [forward 0x04] + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484DBAC, # ORI A0, A0, 0xDBAC + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + # Checks for Map Texts 02 or 00 if in the Villa hallway or CC lizard lab maps respectively and redirects the text + # pointer to a blank string, skipping all the NPC dialogue mandatory for checks. + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x240A0005, # ADDIU T2, R0, 0x0005 + 0x110A0006, # BEQ T0, T2, [forward 0x06] + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x110A0003, # BEQ T0, T2, [forward 0x03] + 0x24090000, # ADDIU T1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + 0x15250004, # BNE T1, A1, [forward 0x04] + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484DACC, # ORI A0, A0, 0xDACC + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F # J 0x8012CE7C +] + +special_descriptions_redirector = [ + # Redirects the menu description when looking at the Special1 and 2 items to different, custom strings that tell + # how many are needed per warp and to fight Dracula respectively, and how many there are of both in the whole seed. + 0x240A0003, # ADDIU T2, R0, 0x0003 + 0x10AA0005, # BEQ A1, T2, [forward 0x05] + 0x240A0004, # ADDIU T2, R0, 0x0004 + 0x10AA0003, # BEQ A1, T2, [forward 0x03] + 0x00000000, # NOP + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484E53C, # ORI A0, A0, 0xE53C + 0x24A5FFFD, # ADDIU A1, A1, 0xFFFD + 0x0804B39F # J 0x8012CE7C +] + +forest_cw_villa_intro_cs_player = [ + # Plays the Forest, Castle Wall, or Villa intro cutscene after transitioning to a different map if the map being + # transitioned to is the start of their levels respectively. Gets around the fact that they have to be set on the + # previous loading zone for them to play normally. + 0x3C088039, # LUI T0, 0x8039 + 0x8D099EE0, # LW T1, 0x9EE0 (T0) + 0x1120000B, # BEQZ T1 T1, [forward 0x0B] + 0x240B0000, # ADDIU T3, R0, 0x0000 + 0x3C0A0002, # LUI T2, 0x0002 + 0x112A0008, # BEQ T1, T2, [forward 0x08] + 0x240B0007, # ADDIU T3, R0, 0x0007 + 0x254A0007, # ADDIU T2, T2, 0x0007 + 0x112A0005, # BEQ T1, T2, [forward 0x05] + 0x3C0A0003, # LUI T2, 0x0003 + 0x112A0003, # BEQ T1, T2, [forward 0x03] + 0x240B0003, # ADDIU T3, R0, 0x0003 + 0x08005FAA, # J 0x80017EA8 + 0x00000000, # NOP + 0x010B6021, # ADDU T4, T0, T3 + 0x918D9C08, # LBU T5, 0x9C08 (T4) + 0x31AF0001, # ANDI T7, T5, 0x0001 + 0x15E00009, # BNEZ T7, [forward 0x09] + 0x240E0009, # ADDIU T6, R0, 0x0009 + 0x3C180003, # LUI T8, 0x0003 + 0x57090001, # BNEL T8, T1, [forward 0x01] + 0x240E0004, # ADDIU T6, R0, 0x0004 + 0x15200003, # BNEZ T1, [forward 0x03] + 0x240F0001, # ADDIU T7, R0, 0x0001 + 0xA18F9C08, # SB T7, 0x9C08 (T4) + 0x240E003C, # ADDIU T6, R0, 0x003C + 0xA10E9EFF, # SB T6, 0x9EFF (T0) + 0x08005FAA # J 0x80017EA8 +] + +map_id_refresher = [ + # After transitioning to a different map, if this detects the map ID being transitioned to as FF, it will write back + # the past map ID so that the map will reset. Useful for thngs like getting around a bug wherein the camera fixes in + # place if you enter a loading zone that doesn't actually change the map, which can happen in a seed that gives you + # any character tower stage at the very start. + 0x240800FF, # ADDIU T0, R0, 0x00FF + 0x110E0003, # BEQ T0, T6, [forward 0x03] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0xA44E61D8, # SH T6, 0x61D8 + 0x904961D9, # LBU T1, 0x61D9 + 0xA0496429, # SB T1, 0x6429 + 0x03E00008 # JR RA +] + +character_changer = [ + # Changes the character being controlled if the player is holding L while loading into a map by swapping the + # character ID. + 0x3C08800D, # LUI T0, 0x800D + 0x910B5E21, # LBU T3, 0x5E21 (T0) + 0x31680020, # ANDI T0, T3, 0x0020 + 0x3C0A8039, # LUI T2, 0x8039 + 0x1100000B, # BEQZ T0, [forward 0x0B] + 0x91499C3D, # LBU T1, 0x9C3D (T2) + 0x11200005, # BEQZ T1, [forward 0x05] + 0x24080000, # ADDIU T0, R0, 0x0000 + 0xA1489C3D, # SB T0, 0x9C3D (T2) + 0x25080001, # ADDIU T0, T0, 0x0001 + 0xA1489BC2, # SB T0, 0x9BC2 (T2) + 0x10000004, # B [forward 0x04] + 0x24080001, # ADDIU T0, R0, 0x0001 + 0xA1489C3D, # SB T0, 0x9C3D (T2) + 0x25080001, # ADDIU T0, T0, 0x0001 + 0xA1489BC2, # SB T0, 0x9BC2 (T2) + # Changes the alternate costume variables if the player is holding C-up. + 0x31680008, # ANDI T0, T3, 0x0008 + 0x11000009, # BEQZ T0, [forward 0x09] + 0x91499C24, # LBU T1, 0x9C24 (T2) + 0x312B0040, # ANDI T3, T1, 0x0040 + 0x2528FFC0, # ADDIU T0, T1, 0xFFC0 + 0x15600003, # BNEZ T3, [forward 0x03] + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x25280040, # ADDIU T0, T1, 0x0040 + 0x240C0001, # ADDIU T4, R0, 0x0001 + 0xA1489C24, # SB T0, 0x9C24 (T2) + 0xA14C9CEE, # SB T4, 0x9CEE (T2) + 0x080062AA, # J 0x80018AA8 + 0x00000000, # NOP + # Plays the attack sound of the character being changed into to indicate the change was successful. + 0x3C088039, # LUI T0, 0x8039 + 0x91099BC2, # LBU T1, 0x9BC2 (T0) + 0xA1009BC2, # SB R0, 0x9BC2 (T0) + 0xA1009BC1, # SB R0, 0x9BC1 (T0) + 0x11200006, # BEQZ T1, [forward 0x06] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0x240402F6, # ADDIU A0, R0, 0x02F6 + 0x55200001, # BNEZL T1, [forward 0x01] + 0x240402F8, # ADDIU A0, R0, 0x02F8 + 0x08004FAB, # J 0x80013EAC + 0x00000000, # NOP + 0x03E00008 # JR RA +] + +panther_dash = [ + # Changes various movement parameters when holding C-right so the player will move way faster. + # Increases movement speed and speeds up the running animation. + 0x3C08800D, # LUI T0, 0x800D + 0x91085E21, # LBU T0, 0x5E21 (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x24093FEA, # ADDIU T1, R0, 0x3FEA + 0x11000004, # BEQZ T0, [forward 0x04] + 0x240B0010, # ADDIU T3, R0, 0x0010 + 0x3C073F20, # LUI A3, 0x3F20 + 0x240940AA, # ADDIU T1, R0, 0x40AA + 0x240B000A, # ADDIU T3, R0, 0x000A + 0x3C0C8035, # LUI T4, 0x8035 + 0xA18B07AE, # SB T3, 0x07AE (T4) + 0xA18B07C2, # SB T3, 0x07C2 (T4) + 0x3C0A8034, # LUI T2, 0x8034 + 0x03200008, # JR T9 + 0xA5492BD8, # SH T1, 0x2BD8 (T2) + 0x00000000, # NOP + # Increases the turning speed so that handling is better. + 0x3C08800D, # LUI T0, 0x800D + 0x91085E21, # LBU T0, 0x5E21 (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x11000002, # BEQZ T0, [forward 0x02] + 0x240A00D9, # ADDIU T2, R0, 0x00D9 + 0x240A00F0, # ADDIU T2, R0, 0x00F0 + 0x3C0B8039, # LUI T3, 0x8039 + 0x916B9C3D, # LBU T3, 0x9C3D (T3) + 0x11600003, # BEQZ T3, [forward 0x03] + 0xD428DD58, # LDC1 F8, 0xDD58 (AT) + 0x03E00008, # JR RA + 0xA02ADD59, # SB T2, 0xDD59 (AT) + 0xD428D798, # LDC1 F8, 0xD798 (AT) + 0x03E00008, # JR RA + 0xA02AD799, # SB T2, 0xD799 (AT) + 0x00000000, # NOP + # Increases crouch-walking x and z speed. + 0x3C08800D, # LUI T0, 0x800D + 0x91085E21, # LBU T0, 0x5E21 (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x11000002, # BEQZ T0, [forward 0x02] + 0x240A00C5, # ADDIU T2, R0, 0x00C5 + 0x240A00F8, # ADDIU T2, R0, 0x00F8 + 0x3C0B8039, # LUI T3, 0x8039 + 0x916B9C3D, # LBU T3, 0x9C3D (T3) + 0x15600005, # BNEZ T3, [forward 0x05] + 0x00000000, # NOP + 0xA02AD801, # SB T2, 0xD801 (AT) + 0xA02AD809, # SB T2, 0xD809 (AT) + 0x03E00008, # JR RA + 0xD430D800, # LDC1 F16, 0xD800 (AT) + 0xA02ADDC1, # SB T2, 0xDDC1 (AT) + 0xA02ADDC9, # SB T2, 0xDDC9 (AT) + 0x03E00008, # JR RA + 0xD430DDC0 # LDC1 F16, 0xDDC0 (AT) +] + +panther_jump_preventer = [ + # Optional hack to prevent jumping while moving at the increased panther dash speed as a way to prevent logic + # sequence breaks that would otherwise be impossible without it. Such sequence breaks are never considered in logic + # either way. + + # Decreases a "can running jump" value by 1 per frame unless it's at 0, or while in the sliding state. When the + # player lets go of C-right, their running speed should have returned to a normal amount by the time it hits 0. + 0x9208007F, # LBU T0, 0x007F (S0) + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x11090005, # BEQ T0, T1, [forward 0x05] + 0x3C088039, # LUI T0, 0x8039 + 0x91099BC1, # LBU T1, 0x9BC1 (T0) + 0x11200002, # BEQZ T1, [forward 0x02] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099BC1, # SB T1, 0x9BC1 (T0) + 0x080FF413, # J 0x803FD04C + 0x00000000, # NOP + # Increases the "can running jump" value by 2 per frame while panther dashing unless it's at 8 or higher, at which + # point the player should be at the max panther dash speed. + 0x00074402, # SRL T0, A3, 16 + 0x29083F7F, # SLTI T0, T0, 0x3F7F + 0x11000006, # BEQZ T0, [forward 0x06] + 0x3C098039, # LUI T1, 0x8039 + 0x912A9BC1, # LBU T2, 0x9BC1 (T1) + 0x254A0002, # ADDIU T2, T2, 0x0002 + 0x294B0008, # SLTI T3, T2, 0x0008 + 0x55600001, # BNEZL T3, [forward 0x01] + 0xA12A9BC1, # SB T2, 0x9BC1 (T1) + 0x03200008, # JR T9 + 0x00000000, # NOP + # Makes running jumps only work while the "can running jump" value is at 0. Otherwise, their state won't change. + 0x3C010001, # LUI AT, 0x0001 + 0x3C088039, # LUI T0, 0x8039 + 0x91089BC1, # LBU T0, 0x9BC1 (T0) + 0x55000001, # BNEZL T0, [forward 0x01] + 0x3C010000, # LUI AT, 0x0000 + 0x03E00008 # JR RA +] + +gondola_skipper = [ + # Upon stepping on one of the gondolas in Tunnel to activate it, this will instantly teleport you to the other end + # of the gondola course depending on which one activated, skipping the entire 3-minute wait to get there. + 0x3C088039, # LUI T0, 0x8039 + 0x240900FF, # ADDIU T1, R0, 0x00FF + 0xA1099EE1, # SB T1, 0x9EE1 (T0) + 0x31EA0020, # ANDI T2, T7, 0x0020 + 0x3C0C3080, # LUI T4, 0x3080 + 0x358C9700, # ORI T4, T4, 0x9700 + 0x154B0003, # BNE T2, T3, [forward 0x03] + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x3C0C7A00, # LUI T4, 0x7A00 + 0xA1099EE3, # SB T1, 0x9EE3 (T0) + 0xAD0C9EE4, # SW T4, 0x9EE4 (T0) + 0x3C0D0010, # LUI T5, 0x0010 + 0x25AD0010, # ADDIU T5, T5, 0x0010 + 0xAD0D9EE8, # SW T5, 0x9EE8 (T0) + 0x08063E68 # J 0x8018F9A0 +] + +mandragora_with_nitro_setter = [ + # When setting a Nitro, if Mandragora is in the inventory too and the wall's "Mandragora set" flag is not set, this + # will automatically subtract a Mandragora from the inventory and set its flag so the wall can be blown up in just + # one interaction instead of two. + 0x3C088039, # LUI T0, 0x8039 + 0x81099EE1, # LB T1, 0x9EE1 (T0) + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x112A000E, # BEQ T1, T2, [forward 0x0E] + 0x81099C18, # LB T1, 0x9C18 (T0) + 0x31290002, # ANDI T1, T1, 0x0002 + 0x11200009, # BEQZ T1, [forward 0x09] + 0x91099C5D, # LBU T1, 0x9C5D (T0) + 0x11200007, # BEQZ T1, [forward 0x07] + 0x910B9C1A, # LBU T3, 0x9C1A (T0) + 0x316A0001, # ANDI T2, T3, 0x0001 + 0x15400004, # BNEZ T2, [forward 0x04] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099C5D, # SB T1, 0x9C5D (T0) + 0x356B0001, # ORI T3, T3, 0x0001 + 0xA10B9C1A, # SB T3, 0x9C1A (T0) + 0x08000512, # J 0x80001448 + 0x00000000, # NOP + 0x810B9BF2, # LB T3, 0x9BF2 (T0) + 0x31690040, # ANDI T1, T3, 0x0040 + 0x11200008, # BEQZ T1, [forward 0x08] + 0x91099C5D, # LBU T1, 0x9C5D (T0) + 0x11200006, # BEQZ T1, [forward 0x06] + 0x316A0080, # ANDI T2, T3, 0x0080 + 0x15400004, # BNEZ T2, 0x803FE0E8 + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099C5D, # SB T1, 0x9C5D (T0) + 0x356B0080, # ORI T3, T3, 0x0080 + 0xA10B9BF2, # SB T3, 0x9BF2 (T0) + 0x08000512 # J 0x80001448 +] + +ambience_silencer = [ + # Silences all map-specific ambience when loading into a different map, so we don't have to live with, say, Tower of + # Science/Clock Tower machinery noises everywhere until either resetting, dying, or going into a map that is + # normally set up to disable said noises. + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x11090003, # BEQ T0, T1, [forward 0x03] + 0x00000000, # NOP + 0x0C004FAB, # JAL 0x80013EAC + 0x3404818C, # ORI A0, R0, 0x818C + 0x0C004FAB, # JAL 0x80013EAC + 0x34048134, # ORI A0, R0, 0x8134 + 0x0C004FAB, # JAL 0x80013EAC + 0x34048135, # ORI A0, R0, 0x8135 + 0x0C004FAB, # JAL 0x80013EAC + 0x34048136, # ORI A0, R0, 0x8136 + 0x08054987, # J 0x8015261C + 0x00000000, # NOP + # Plays the fan ambience when loading into the fan meeting room if this detects the active character's cutscene flag + # here already being set. + 0x3C088039, # LUI T0, 0x8039 + 0x91099EE1, # LBU T1, 0x9EE1 (T0) + 0x240A0019, # ADDIU T2, R0, 0x0019 + 0x152A000A, # BNE T1, T2, [forward 0x0A] + 0x910B9BFE, # LBU T3, 0x9BFE (T0) + 0x910C9C3D, # LBU T4, 0x9C3D (T0) + 0x240D0001, # ADDIU T5, R0, 0x0001 + 0x55800001, # BNEZL T4, [forward 0x01] + 0x240D0002, # ADDIU T5, R0, 0x0002 + 0x016D7024, # AND T6, T3, T5 + 0x11C00003, # BEQZ T6, [forward 0x03] + 0x00000000, # NOP + 0x0C0052B4, # JAL 0x80014AD0 + 0x34040169, # ORI A0, R0, 0x0169 + 0x0805581C # J 0x80156070 +] + +coffin_cutscene_skipper = [ + # Kills the normally-unskippable "Found a hidden path" cutscene at the end of Villa if this detects, in the current + # module in the modules array, the cutscene's module number of 0x205C and the "skip" value 0f 0x01 normally set by + # all cutscenes upon pressing Start. + 0x10A0000B, # BEQZ A1, [forward 0x0B] + 0x00000000, # NOP + 0x94A80000, # LHU T0, 0x0000 (A1) + 0x2409205C, # ADDIU T1, R0, 0x205C + 0x15090007, # BNE T0, T1, [forward 0x07] + 0x90AA0070, # LBU T2, 0x0070 (A1) + 0x11400005, # BEQZ T2, [forward 0x05] + 0x90AB0009, # LBU T3, 0x0009 (A1) + 0x240C0003, # ADDIU T4, R0, 0x0003 + 0x156C0002, # BNE T3, T4, [forward 0x02] + 0x240B0004, # ADDIU T3, R0, 0x0004 + 0xA0AB0009, # SB T3, 0x0009 (A1) + 0x03E00008 # JR RA +] + +multiworld_item_name_loader = [ + # When picking up an item from another world, this will load from ROM the custom message for that item explaining + # in the item textbox what the item is and who it's for. The flag index it calculates determines from what part of + # the ROM to load the item name from. If the item being picked up is a white jewel or a contract, it will always + # load from a part of the ROM that has nothing in it to ensure their set "flag" values don't yield unintended names. + 0x3C088040, # LUI T0, 0x8040 + 0xAD03E238, # SW V1, 0xE238 (T0) + 0x92080039, # LBU T0, 0x0039 (S0) + 0x11000003, # BEQZ T0, [forward 0x03] + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x24080000, # ADDIU T0, R0, 0x0000 + 0x10000010, # B [forward 0x10] + 0x24080000, # ADDIU T0, R0, 0x0000 + 0x920C0055, # LBU T4, 0x0055 (S0) + 0x8E090058, # LW T1, 0x0058 (S0) + 0x1120000C, # BEQZ T1, [forward 0x0C] + 0x298A0011, # SLTI T2, T4, 0x0011 + 0x51400001, # BEQZL T2, [forward 0x01] + 0x258CFFED, # ADDIU T4, T4, 0xFFED + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x00094840, # SLL T1, T1, 1 + 0x5520FFFE, # BNEZL T1, [backward 0x02] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x240B0020, # ADDIU T3, R0, 0x0020 + 0x018B0019, # MULTU T4, T3 + 0x00004812, # MFLO T1 + 0x012A4021, # ADDU T0, T1, T2 + 0x00084200, # SLL T0, T0, 8 + 0x3C0400BB, # LUI A0, 0x00BB + 0x24847164, # ADDIU A0, A0, 0x7164 + 0x00882020, # ADD A0, A0, T0 + 0x3C058018, # LUI A1, 0x8018 + 0x34A5BF98, # ORI A1, A1, 0xBF98 + 0x0C005DFB, # JAL 0x800177EC + 0x24060100, # ADDIU A2, R0, 0x0100 + 0x3C088040, # LUI T0, 0x8040 + 0x8D03E238, # LW V1, 0xE238 (T0) + 0x3C1F8012, # LUI RA, 0x8012 + 0x27FF5BA4, # ADDIU RA, RA, 0x5BA4 + 0x0804EF54, # J 0x8013BD50 + 0x94640002, # LHU A0, 0x0002 (V1) + # Changes the Y screen position of the textbox depending on how many line breaks there are. + 0x3C088019, # LUI T0, 0x8019 + 0x9108C097, # LBU T0, 0xC097 (T0) + 0x11000005, # BEQZ T0, [forward 0x05] + 0x2508FFFF, # ADDIU T0, T0, 0xFFFF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x00000000, # NOP + 0x1000FFFC, # B [backward 0x04] + 0x24C6FFF1, # ADDIU A2, A2, 0xFFF1 + 0x0804B33F, # J 0x8012CCFC + # Changes the length and number of lines on the textbox if there's a multiworld message in the buffer. + 0x3C088019, # LUI T0, 0x8019 + 0x9108C097, # LBU T0, 0xC097 (T0) + 0x11000003, # BEQZ T0, [forward 0x03] + 0x00000000, # NOP + 0x00082821, # ADDU A1, R0, T0 + 0x240600B6, # ADDIU A2, R0, 0x00B6 + 0x0804B345, # J 0x8012CD14 + 0x00000000, # NOP + # Redirects the text to the multiworld message buffer if a message exists in it. + 0x3C088019, # LUI T0, 0x8019 + 0x9108C097, # LBU T0, 0xC097 (T0) + 0x11000004, # BEQZ T0, [forward 0x04] + 0x00000000, # NOP + 0x3C048018, # LUI A0, 0x8018 + 0x3484BF98, # ORI A0, A0, 0xBF98 + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + # Copy the "item from player" text when being given an item through the multiworld via the game's copy function. + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x3C088040, # LUI T0, 0x8040 + 0xAD1FE33C, # SW RA, 0xE33C (T0) + 0xA104E338, # SB A0, 0xE338 (T0) + 0x3C048019, # LUI A0, 0x8019 + 0x2484C0A8, # ADDIU A0, A0, 0xC0A8 + 0x3C058019, # LUI A1, 0x8019 + 0x24A5BF98, # ADDIU A1, A1, 0xBF98 + 0x0C000234, # JAL 0x800008D0 + 0x24060100, # ADDIU A2, R0, 0x0100 + 0x3C088040, # LUI T0, 0x8040 + 0x8D1FE33C, # LW RA, 0xE33C (T0) + 0x0804EDCE, # J 0x8013B738 + 0x9104E338, # LBU A0, 0xE338 (T0) + 0x00000000, # NOP + # Neuters the multiworld item text buffer if giving a non-multiworld item through the in-game remote item rewarder + # byte before then jumping to item_prepareTextbox. + 0x24080011, # ADDIU T0, R0, 0x0011 + 0x10880004, # BEQ A0, T0, [forward 0x04] + 0x24080012, # ADDIU T0, R0, 0x0012 + 0x10880002, # BEQ A0, T0, [forward 0x02] + 0x3C088019, # LUI T0, 0x8019 + 0xA100C097, # SB R0, 0xC097 (T0) + 0x0804EDCE # J 0x8013B738 +] + +ice_trap_initializer = [ + # During a map load, creates the module that allows the ice block model to appear while in the frozen state if not + # on the intro narration map (map 0x16). + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x24090016, # ADDIU T1, R0, 0x0016 + 0x11090004, # BEQ T0, T1, [forward 0x04] + 0x3C048034, # LUI A0, 0x8034 + 0x24842ACC, # ADDIU A0, A0, 0x2ACC + 0x08000660, # J 0x80001980 + 0x24052125, # ADDIU A1, R0, 0x2125 + 0x03E00008 # JR RA +] + +the_deep_freezer = [ + # Writes 000C0000 into the player state to freeze the player on the spot if Ice Traps have been received, writes the + # Ice Trap code into the pointer value (0x20B8, which is also Camilla's boss code),and decrements the Ice Traps + # remaining counter. All after verifying the player is in a "safe" state to be frozen in. + 0x3C0B8039, # LUI T3, 0x8039 + 0x91699BE2, # LBU T3, 0x9BE2 (T0) + 0x11200015, # BEQZ T1, [forward 0x15] + 0x3C088034, # LUI T0, 0x8034 + 0x910827A9, # LBU T0, 0x27A9 (T0) + 0x240A0005, # ADDIU T2, R0, 0x0005 + 0x110A0011, # BEQ T0, T2, [forward 0x11] + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x110A000F, # BEQ T0, T2, [forward 0x0F] + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x110A000D, # BEQ T0, T2, [forward 0x0D] + 0x240A0008, # ADDIU T2, R0, 0x0008 + 0x110A000B, # BEQ T0, T2, [forward 0x0B] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1699BE2, # SB T1, 0x9BE2 (T3) + 0x3C088034, # LUI T0, 0x8034 + 0x3C09000C, # LUI T1, 0x000C + 0xAD0927A8, # SW T1, 0x27A8 (T0) + 0x240C20B8, # ADDIU T4, R0, 0x20B8 + 0xA56C9E6E, # SH T4, 0x9E6E (T3) + 0x8D0927C8, # LW T1, 0x27C8 (T0) + 0x912A0048, # LBU T2, 0x0068 (T1) + 0x314A007F, # ANDI T2, T2, 0x007F + 0xA12A0048, # SB T2, 0x0068 (T1) + 0x03E00008 # JR RA +] + +freeze_verifier = [ + # Verifies for the ice chunk module that a freeze should spawn the ice model. The player must be in the frozen state + # (0x000C) and 0x20B8 must be in either the freeze pointer value or the current boss ID (Camilla's); otherwise, we + # weill assume that the freeze happened due to a vampire grab or Actrise shard tornado and not spawn the ice chunk. + 0x8C4E000C, # LW T6, 0x000C (V0) + 0x00803025, # OR A2, A0, R0 + 0x8DC30008, # LW V1, 0x0008 (T6) + 0x3C088039, # LUI T0, 0x8039 + 0x240920B8, # ADDIU T1, R0, 0x20B8 + 0x950A9E72, # LHU T2, 0x9E72 (T0) + 0x3C0C8034, # LUI T4, 0x8034 + 0x918C27A9, # LBU T4, 0x27A9 (T4) + 0x240D000C, # ADDIU T5, R0, 0x000C + 0x158D0004, # BNE T4, T5, [forward 0x04] + 0x3C0B0F00, # LUI T3, 0x0F00 + 0x112A0005, # BEQ T1, T2, [forward 0x05] + 0x950A9E78, # LHU T2, 0x9E78 (T0) + 0x112A0003, # BEQ T1, T2, [forward 0x03] + 0x357996A0, # ORI T9, T3, 0x96A0 + 0x03200008, # JR T9 + 0x00000000, # NOP + 0x35799640, # ORI T9, T3, 0x9640 + 0x03200008, # JR T9 +] + +countdown_extra_safety_check = [ + # Checks to see if the multiworld message is a red flashing trap before then truly deciding to decrement the + # Countdown number. This was a VERY last minute thing I caught, since Ice Traps for other CV64 players can take the + # appearance of majors with no other way of the game knowing. + 0x3C0B8019, # LUI T3, 0x8019 + 0x956BBF98, # LHU T3, 0xBF98 (T3) + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x358CA20B, # ORI T4, T4, 0xA20B + 0x556C0001, # BNEL T3, T4, [forward 0x01] + 0xA1099CA4, # SB T1, 0x9CA4 (T0) + 0x03E00008 # JR RA +] + +countdown_demo_hider = [ + # Hides the Countdown number if we are not in the Gameplay state (state 2), which would happen if we were in the + # Demo state (state 9). This is to ensure the demo maps' number is not peep-able before starting a run proper, for + # the sake of preventing a marginal unfair advantage. Otherwise, updates the number once per frame. + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x0C0FF507, # JAL 0x803FD41C + 0x9124D6DC, # LBU A0, 0xD6DC (T1) + 0x3C088034, # LUI T0, 0x8034 + 0x91092087, # LBU T0, 0x2087 (T0) + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x112A0003, # BEQ T1, T2, [forward 0x03] + 0x3C048040, # LUI A0, 0x8040 + 0x8C84D6D4, # LW A0, 0xD6D4 (A0) + 0x0C0FF59F, # JAL 0x803FD67C + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x080FF411, # J 0x803FD044 +] + +item_drop_spin_corrector = [ + # Corrects how far AP-placed items drop and how fast they spin based on what appearance they take. + + # Pickup actor ID table for the item appearance IDs to reference. + 0x01020304, + 0x05060708, + 0x090A0B0C, + 0x100D0E0F, + 0x11121314, + 0x15161718, + 0x191D1E1F, + 0x20212223, + 0x24252627, + 0x28291A1B, + 0x1C000000, + 0x00000000, + # Makes AP-placed items in 1-hit breakables drop to their correct, dev-intended height depending on what appearance + # we gave it. Primarily intended for the Axe and the Cross to ensure they don't land half buried in the ground. + 0x000C4202, # SRL T0, T4, 8 + 0x318C00FF, # ANDI T4, T4, 0x00FF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x912CE7DB, # LBU T4, 0xE7D8 + 0x03E00008, # JR RA + 0xAC600000, # SW R0, 0x0000 (V1) + 0x00000000, # NOP + # Makes items with changed appearances spin at their correct speed. Unless it's a local Ice Trap, wherein it will + # instead spin at the speed it isn't supposed to. + 0x920B0040, # LBU T3, 0x0040 (S0) + 0x1160000D, # BEQZ T3, [forward 0x0D] + 0x3C0C8040, # LUI T4, 0x8040 + 0x016C6021, # ADDU T4, T3, T4 + 0x918CE7DB, # LBU T4, 0xE7DB (T4) + 0x258CFFFF, # ADDIU T4, T4, 0xFFFF + 0x240D0011, # ADDIU T5, R0, 0x0011 + 0x154D0006, # BNE T2, T5, [forward 0x06] + 0x29AE0006, # SLTI T6, T5, 0x0006 + 0x240A0001, # ADDIU T2, R0, 0x0001 + 0x55C00001, # BNEZL T6, [forward 0x01] + 0x240A0007, # ADDIU T2, R0, 0x0007 + 0x10000002, # B [forward 0x02] + 0x00000000, # NOP + 0x258A0000, # ADDIU T2, T4, 0x0000 + 0x08049648, # J 0x80125920 + 0x3C028017, # LUI V0, 0x8017 + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + # Makes AP-placed items in 3-hit breakables drop to their correct, dev-intended height depending on what appearance + # we gave it. + 0x00184202, # SRL T0, T8, 8 + 0x331800FF, # ANDI T8, T8, 0x00FF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x9138E7DB, # LBU T8, 0xE7D8 + 0x03E00008, # JR RA + 0xAC60FFD8, # SW R0, 0xFFD8 (V1) + 0x00000000, + # Makes AP-placed items in the Villa chandelier drop to their correct, dev-intended height depending on what + # appearance we gave it. (why must this singular breakable be such a problem child with its own code? :/) + 0x000D4202, # SRL T0, T5, 8 + 0x31AD00FF, # ANDI T5, T5, 0x00FF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x912DE7DB, # LBU T5, 0xE7D8 + 0x03E00008, # JR RA + 0xAC60FFD8, # SW R0, 0xFFD8 (V1) +] + +big_tosser = [ + # Makes every hit the player takes that does not immobilize them send them flying backwards with the power of + # Behemoth's charge. + 0x3C0A8038, # LUI T2, 0x8038 + 0x914A7D7E, # LBU T2, 0x7D7E (T2) + 0x314A0020, # ANDI T2, T2, 0x0020 + 0x1540000D, # BEQZ T2, [forward 0x0D] + 0x3C0A800E, # LUI T2, 0x800E + 0x954B8290, # LHU T3, 0x8290 (T2) + 0x356B2000, # ORI T3, T3, 0x2000 + 0xA54B8290, # SH T3, 0x8290 (T2) + 0x3C0C8035, # LUI T4, 0x8035 + 0x958C09DE, # LHU T4, 0x09DE (T4) + 0x258C8000, # ADDIU T4, T4, 0x8000 + 0x3C0D8039, # LUI T5, 0x8039 + 0xA5AC9CF0, # SH T4, 0x9CF0 (T5) + 0x3C0C4160, # LUI T4, 0x4160 + 0xADAC9CF4, # SW T4, 0x9CF4 (T5) + 0x3C0C4040, # LUI T4, 0x4040 + 0xADAC9CF8, # SW T4, 0x9CF8 (T5) + 0x03E00008, # JR RA + 0x8C680048, # LW T0, 0x0048 (V1) + 0x00000000, + 0x00000000, + # Allows pressing A while getting launched to cancel all XZ momentum. Useful for saving oneself from getting + # launched into an instant death trap. + 0x3C088038, # LUI T0, 0x8038 + 0x91087D80, # LBU T0, 0x7D80 (T0) + 0x31090080, # ANDI T1, T0, 0x0080 + 0x11200009, # BEQZ T1, [forward 0x09] + 0x3C088035, # LUI T0, 0x8035 + 0x8D0A079C, # LW T2, 0x079C (T0) + 0x3C0B000C, # LUI T3, 0x000C + 0x256B4000, # ADDIU T3, T3, 0x4000 + 0x014B5024, # AND T2, T2, T3 + 0x154B0003, # BNE T2, T3, [forward 0x03] + 0x00000000, # NOP + 0xAD00080C, # SW R0, 0x080C (T0) + 0xAD000814, # SW R0, 0x0814 (T0) + 0x03200008 # JR T9 +] + +dog_bite_ice_trap_fix = [ + # Sets the freeze timer to 0 when a maze garden dog bites the player to ensure the ice chunk model will break if the + # player gets bitten while frozen via Ice Trap. + 0x3C088039, # LUI T0, 0x8039 + 0xA5009E76, # SH R0, 0x9E76 (T0) + 0x3C090F00, # LUI T1, 0x0F00 + 0x25291CB8, # ADDIU T1, T1, 0x1CB8 + 0x01200008 # JR T1 +] diff --git a/worlds/cv64/data/rname.py b/worlds/cv64/data/rname.py new file mode 100644 index 000000000000..851ee618af05 --- /dev/null +++ b/worlds/cv64/data/rname.py @@ -0,0 +1,63 @@ +forest_of_silence = "Forest of Silence" +forest_start = "Forest of Silence: first half" +forest_mid = "Forest of Silence: second half" +forest_end = "Forest of Silence: end area" + +castle_wall = "Castle Wall" +cw_start = "Castle Wall: main area" +cw_exit = "Castle Wall: exit room" +cw_ltower = "Castle Wall: left tower" + +villa = "Villa" +villa_start = "Villa: dog gates" +villa_main = "Villa: main interior" +villa_storeroom = "Villa: storeroom" +villa_archives = "Villa: archives" +villa_maze = "Villa: maze" +villa_servants = "Villa: servants entrance" +villa_crypt = "Villa: crypt" + +tunnel = "Tunnel" +tunnel_start = "Tunnel: first half" +tunnel_end = "Tunnel: second half" + +underground_waterway = "Underground Waterway" +uw_main = "Underground Waterway: main area" +uw_end = "Underground Waterway: end" + +castle_center = "Castle Center" +cc_main = "Castle Center: main area" +cc_crystal = "Castle Center: big crystal" +cc_torture_chamber = "Castle Center: torture chamber" +cc_library = "Castle Center: library" +cc_elev_top = "Castle Center: elevator top" + +duel_tower = "Duel Tower" +dt_main = "Duel Tower" + +tower_of_sorcery = "Tower of Sorcery" +tosor_main = "Tower of Sorcery" + +tower_of_execution = "Tower of Execution" +toe_main = "Tower of Execution: main area" +toe_ledge = "Tower of Execution: gated ledge" + +tower_of_science = "Tower of Science" +tosci_start = "Tower of Science: turret lab" +tosci_three_doors = "Tower of Science: locked key1 room" +tosci_conveyors = "Tower of Science: spiky conveyors" +tosci_key3 = "Tower of Science: locked key3 room" + +room_of_clocks = "Room of Clocks" +roc_main = "Room of Clocks" + +clock_tower = "Clock Tower" +ct_start = "Clock Tower: start" +ct_middle = "Clock Tower: middle" +ct_end = "Clock Tower: end" + +castle_keep = "Castle Keep" +ck_main = "Castle Keep: exterior" +ck_drac_chamber = "Castle Keep: Dracula's chamber" + +renon = "Renon's shop" diff --git a/worlds/cv64/docs/en_Castlevania 64.md b/worlds/cv64/docs/en_Castlevania 64.md new file mode 100644 index 000000000000..55f7eb03012f --- /dev/null +++ b/worlds/cv64/docs/en_Castlevania 64.md @@ -0,0 +1,148 @@ +# Castlevania 64 + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? + +All items that you would normally pick up throughout the game, be it from candles, breakables, or sitting out, have been +moved around. This includes the key items that the player would normally need to find to progress in some stages, which can +now be found outside their own stages, so returning to previously-visited stages will very likely be necessary (see: [How do +I jump to a different stage?](#how-do-i-jump-to-a-different-stage?)). The positions of the stages can optionally be randomized +too, so you may start out in Duel Tower and get Forest of Silence as your penultimate stage before Castle Keep, amongst +many other possibilities. + +## How do I jump to a different stage? + +Instant travel to an earlier or later stage is made possible through the Warp Menu, a major addition to the game that can +be pulled up while not in a boss fight by pressing START while holding Z and R. By finding Special1 jewels (the item that +unlocks Hard Mode in vanilla Castlevania 64), more destinations become available to be selected on this menu. The destinations +on the list are randomized per seed and the first one, which requires no Special1s to select, will always be your starting +area. + +NOTE: Regardless of which option on the menu you are currently highlighting, you can hold Z or R while making your selection +to return to Villa's crypt or Castle Center's top elevator room respectively, provided you've already been to that place at +least once. This can make checking out both character stages at the start of a route divergence far less of a hassle. + +## Can I do everything as one character? + +Yes! The Villa end-of-level coffin has had its behavior modified so that which character stage slot it sends you to +depends on the time of day, and likewise both bridges at the top of Castle Center's elevator are intact so both exits are +reachable regardless of who you are. With these changes in game behavior, every stage can be accessed by any character +in a singular run. + +NOTE: By holding L while loading into a map (this can even be done while cancelling out of the Warp Menu), you can swap to +the other character you are not playing as, and/or hold C-Up to swap to and from the characters' alternate costumes. Unless +you have Carrie Logic enabled, and you are not playing as her, switching should never be necessary. + +## What is the goal of Castlevania 64 when randomized? + +Make it to Castle Keep, enter Dracula's chamber, and defeat him to trigger an ending and complete your goal. Whether you +get your character's good or bad ending does **not** matter; the goal will send regardless. Options exist to force a specific +ending for those who prefer a specific one. + +Dracula's chamber's entrance door is initially locked until whichever of the following objectives that was specified on your +YAML under `draculas_condition` is completed: +- `crystal`: Activate the big crystal in the basement of Castle Center. Doing this entails finding two Magical Nitros and +two Mandragoras to blow up both cracked walls (see: [How does the Nitro transport work in this?](#how-does-the-nitro-transport-work-in-this?)). +Behemoth and Rosa/Camilla do **NOT** have to be defeated. +- `bosses`: Kill bosses with visible health meters to earn Trophies. The number of Trophies required can be specified under +`bosses_required`. +- `special2s`: Find enough Special2 jewels (the item that normally unlocks alternate costumes) that are shuffled in the +regular item pool. The total amount and percent needed can be specified under `total_special2s` and `percent_special2s_required` respectively. + +If `none` was specified, then there is no objective. Dracula's chamber door is unlocked from the start, and you merely have to reach it. + +## What items and locations get shuffled? + +Inventory items, jewels, moneybags, and PowerUps are all placed in the item pool by default. Randomizing Sub-weapons is optional, +and they can be shuffled in their own separate pool or in the main item pool. An optional hack can be enabled to make your +old sub-weapon drop behind you when you receive a different one, so you can pick it up again if you still want it. Location +checks by default include freestanding items, items from one-hit breakables, and the very few items given through NPC text. Additional +locations that can be toggled are: +- Objects that break in three hits. +- Sub-weapon locations if they have been shuffled anywhere. +- Seven items sold by the shopkeeper Renon. +- The two items beyond the crawlspace in Waterway that normally require Carrie, if Carrie Logic is on. +- The six items inside the Lizard-man generators in Castle Center that open randomly to spawn Lizard-men. These are particularly annoying! + +## How does the Nitro transport work in this? + +Two Magical Nitros and two Mandragoras are placed into the item pool for blowing up the cracked walls in Castle Center +and two randomized items are placed on both of their shelves. The randomized Magical Nitro will **NOT** kill you upon landing +or taking damage, so don't panic when you receive one! Hazardous Waste Dispoal bins are disabled and the basement crack with +a seal will not let you set anything at it until said seal is removed so none of the limited ingredients can be wasted. + +In short, Nitro is still in, explode-y business is not! Unless you turn on explosive DeathLink, that is... + +## Which items can be in another player's world? + +Any of the items which can be shuffled may also be placed into another player's world. The exception is if sub-weapons +are shuffled in their own pool, in which case they will only appear in your world in sub-weapon spots. + +## What does another world's item look like in Castlevania 64? + +An item belonging to another world will show up as that item if it's from another Castlevania 64 world, or one of two +Archipelago logo icons if it's from a different game entirely. If the icon is big and has an orange arrow in the top-right +corner, it is a progression item for that world; definitely get these! Otherwise, if it's small and with no arrow, it is +either filler, useful, or a trap. + +When you pick up someone else's item, you will not receive anything and the item textbox will show up to announce what you +found and who it was for. The color of the text will tell you its classification: +- Light brown-ish: Filler +- White/Yellow: Useful +- Yellow/Green: Progression +- Yellow/Red: Trap + +## When the player receives an item, what happens? + +A textbox containing the name of the item and the player who sent it will appear, and they will get it. +Just like the textbox that appears when sending an item, the color of the text will tell you its classification. + +NOTE: You can press B to close the item textbox instantly and get through your item queue quicker. + +## What tricks and glitches should I know for Hard Logic? + +The following tricks always have a chance to be required: +- Left Tower Skip in Castle Wall +- Copper Door Skip in Villa (both characters have their own methods for this) +- Waterfall Skip if you travel backwards into Underground Waterway +- Slope Jump to Room of Clocks from Castle Keep +- Jump to the gated ledge from the level above in Tower of Execution + +Enabling Carrie Logic will also expect the following: + +- Orb-sniping dogs through the front gates in Villa + +Library Skip is **NOT** logically expected by any options. The basement arena crack will always logically expect two Nitros +and two Mandragoras even with Hard Logic on due to the possibility of wasting a pair on the upper wall, after managing +to skip past it. And plus, the RNG manip may not even be possible after picking up all the items in the Nitro room. + +## What are the item name groups? +The groups you can use for Castlevania 64 are `bomb` and `ingredient`, both of which will hint randomly for either a +Magical Nitro or Mandragora. + +## What are the location name groups? +In Castlevania 64, every location that is specific to a stage is part of a location group under that stage's name. +So if you want to exclude all of, say, Duel Tower, you can do so by just excluding "Duel Tower" as a whole. + +## I'm stuck and/or I can't find this hinted location...is there a map tracker? +At the moment, no map tracker exists. [Here](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds/cv64/docs/obscure_checks.md) +is a list of many checks that someone could very likely miss, with instructions on how to find them. See if the check you +are missing is on there and if it isn't, or you still can't find it, reach out in the [Archipelago Discord server](https://discord.gg/archipelago) +to inquire about having the list updated if you think it should be. + +If you are new to this randomizer, it is strongly recommended to play with the Countdown option enabled to at least give you a general +idea of where you should be looking if you get completely stuck. It can track the total number of unchecked locations in the +area you are currently in, or the total remaining majors. + +## Why does the game stop working when I sit on the title screen for too long? +This is an issue that existed with Castlevania 64 on mupen64plus way back in 2017, and BizHawk never updated their +mupen64plus core since it was fixed way back then. This is a Castlevania 64 in general problem that happens even with the +vanilla ROM, so there's not much that can done about it besides opening an issue to them (which [has been done](https://github.com/TASEmulators/BizHawk/issues/3670)) +and hoping they update their mupen64plus core one day... + +## How the f*** do I set Nitro/Mandragora? +(>) diff --git a/worlds/cv64/docs/obscure_checks.md b/worlds/cv64/docs/obscure_checks.md new file mode 100644 index 000000000000..6f0e0cdbb34e --- /dev/null +++ b/worlds/cv64/docs/obscure_checks.md @@ -0,0 +1,429 @@ +# Obscure locations in the AP Castlevania 64 randomizer + + + +## Forest of Silence + +#### Invisible bridge platform +A square platform floating off to the side of the broken bridge between the three-crypt and Werewolf areas. There's an +invisible bridge connecting it with the middle piece on the broken bridge that you can cross over to reach it. This is +where you normally get the Special1 in vanilla that unlocks Hard Mode. + +### Invisible Items +#### Dirge maiden pedestal plaque +This plaque in question can be found on the statue pedestal with a torch on it in the area right after the first switch gate, +near the cliff where the breakable rock can be found. The plaque reads "A maiden sings a dirge" if you check it after you +pick up the item there, hence the name of all the locations in this area. + +#### Werewolf statue plaque +The plaque on the statue pedestal in the area inhabited by the Werewolf. Reading this plaque after picking up the item +says it's "the lady who blesses and restores." + +### 3-Hit Breakables +#### Dirge maiden pedestal rock +This rock can be found near the cliff behind the empty above-mentioned dirge maiden pedestal. Normally has a ton of money +in vanilla, contains 5 checks in rando. + +#### Bat archway rock +After the broken bridge containing the invisible pathway to the Special1 in vanilla, this rock is off to the side in front +of the gate frame with a swarm of bats that come at you, before the Werewolf's territory. Contains 4 checks. If you are new +to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guaranteed spot to find a PowerUp at. + + + +## Castle Wall +#### Above bottom right/left tower door +These checks are located on top of the bottom doorways inside the both the Left and Right Tower. You have to drop from above +to reach them. In the case of the left tower, it's probably easiest to wait on the green platform directly above it until it flips. + +#### Left tower child ledge +When you reach the bridge of four rotating green platforms, look towards the pillar in the center of the room (hold C-up to +enter first person view), and you'll see this. There's an invisible bridge between the rotating platforms and the tiny ledge +that you can use to get to and from it. In Legacy of Darkness, it is on this ledge where one of Henry's children is found. + +### Invisible Items +#### Sandbag shelf - Left +If you thought the PowerUp on this shelf in vanilla CV64 was the only thing here, then you'd be wrong! Hidden inside the +sandbags near the item is another item you can pick up before subsequent checks on this spot yield "only sand and gravel". +Legacy took this item out entirely, interestingly enough. + +### 3-Hit Breakables +#### Upper rampart savepoint slab +After killing the two White Dragons and flipping their switch, drop down onto this platform from the top, and you'll find +it near the White Jewel. Contains 5 checks that are all normally Red Jewels in vanilla, making it an excellent place to +fuel up at if you're not doing Left Tower Skip. Just be careful of the infinitely spawning skeletons! + +#### Dracula switch slab +Located behind the door that you come out of at the top of the left tower where you encounter Totally Real Dracula in a +cutscene. Contains 5 checks that are all normally money; take note of all these money spots if you're plaing vanilla and +plan on trying to trigger the Renon fight. + + + +## Villa +#### Outer front gate platform +From the start of the level, turn right, and you'll see a platform with a torch above a torch on the ground. This upper torch +is reachable via an invisible platform that you can grab and pull yourself up onto. The PAL version and onwards removed +this secret entirely, interestingly enough. + +#### Front yard cross grave near gates/porch +In the Villa's front yard area are two cross-shaped grave markers that are actually 1-hit breakables just like torches. +They contain a check each. + +#### Midnight fountain +At exactly midnight (0:00 on the pause screen), a pillar in the fountain's base will rise to give you access to the six +checks on the upper parts of the fountain. If you're playing with Disable Time Requirements enabled, this pillar will be +raised regardless of the current time. + +#### Vincent +Vincent has a check that he will give to you by speaking to him after triggering the Rosa cutscene at 3 AM in the rose +garden. With Disable Time Requirements enabled, the Rosa cutscene will trigger at any time. + +#### Living room ceiling light +In the rectangular living room with ghosts and flying skulls that come at you, there are two yellow lights on the ceiling +and one red light between them. The red light can be broken for a check; just jump directly below it and use your c-left +attack to hit it. + +#### Front maze garden - Frankie's right dead-end urn +When you first enter the maze, before going to trigger the Malus cutscene, go forward, right at the one-way door, then right +at the T-junction, and you'll reach a dead-end where the Gardner is just going about his business. The urn on the left +at this dead-end can be broken for a check; it's the ONLY urn in the entire maze that can be broken like this. + +#### Crypt bridge upstream +After unlocking the Copper Door, follow the stream all the way past the bridge to end up at this torch. +I see many people miss this one. + +### Invisible Items +#### Front yard visitor's tombstone +The tombstone closest to the Villa building itself, in the top-right corner if approaching from the gates. If you are +familiar with the puzzle here in Cornell's quest in Legacy, it's the tombstone prepared for "anybody else who drops by +to visit". + +#### Foyer sofa +The first sofa in the foyer, on the upper floor to the right. + +#### Mary's room table +The table closer to the mirror on the right in the small room adjacent to the bedroom, where Mary would normally be found +in Cornell's story in Legacy. + +#### Dining room rose vase +The vase of roses in the dining room that a rose falls out of in the cutscene here to warn Reinhardt/Carrie of the vampire +villager. + +#### Living room clawed painting +The painting with claw marks on it above the fireplace in the middle of the living room. + +#### Living room lion head +The lion head on the left wall of the living room (if you entered from one of the doors to the main hallway). + +#### Maze garden exit knight +The suit of armor in the stairs room before the Maze Garden, where Renon normally introduces himself. + +#### Storeroom statue +The weird statue in the back of the Storeroom. If you check it again after taking its item, the game questions why would +someone make something like it. + +#### Archives table +The table in the middle of the Archives. In Legacy, this is where Oldrey's diary normally sits if you are playing Cornell. + +#### Malus's hiding bush +The bush that Reinhardt/Carrie find Malus hiding in at the start of the Maze Garden chase sequence. + +### 3-Hit Breakables +#### Foyer chandelier +The big chandelier above the foyer can be broken for 5 assorted items, all of which become checks in rando with the multi +hits setting on. This is the only 3-hit breakable in the entire stage.
    + +Here's a fun fact about the chandelier: for some reason, KCEK made this thing a completely separate object from every other +3-hit breakable in the game, complete with its own distinct code that I had to modify entirely separately as I was making +this rando to make the 3-hit breakable setting feasible! What fun! + + + +## Tunnel +#### Stepping stone alcove +After the first save following the initial Spider Women encounter, take the first right you see, and you'll arrive back at +the poison river where there's a second set of stepping stones similar to the one you just jumped across earlier. Jump on +these to find the secret alcove containing one or two checks depending on whether sub-weapons are randomized anywhere or not. + +### Sun/Moon Doors + +In total, there are six of these throughout the entire stage. One of them you are required to open in order to leave the stage, +while the other five lead to optional side rooms containing items. These are all very skippable in the vanilla game, but in a +rando context, it is obviously *very* important you learn where all of them are for when the day comes that a Hookshot +lands behind one! If it helps, two of them are before the gondolas, while all the rest are after them. + +#### Lonesome bucket moon door +After you ride up on the second elevator, turn left at the first split path you see, and you will find, as I called it, the +"Lonesome bucket". Keep moving forward past this, and you will arrive at the moon door. The only thing of value, beside a shop +point, is a sub-weapon location. So if you don't have sub-weapons shuffled anywhere, you can very much skip this one. + +#### Gondola rock crusher sun door +Once you get past the first poison pit that you are literally required to platform over, go forward at the next junction +instead of left (going left is progress and will take you to the rock crusher right before the gondolas). This door notably +hides two Roast Beefs normally, making it probably the most worthwhile one to visit in vanilla. + +#### Corpse bucket moon door +After the poison pit immediately following the gondola ride, you will arrive at a bucket surrounded by corpses (hence the name). +Go left here, and you will arrive at this door. + +#### Shovel zone moon door +On the straight path to the end-of-level sun door are two separate poison pits on the right that you can platform over. +Both of these lead to and from the same optional area, the "shovel zone" as I call it due to the random shovel you can find +here. Follow the path near the shovel that leads away from both poison pits, and you'll arrive at a junction with a save jewel. +Go straight on at this junction to arrive at this moon door. This particular one is more notable in Legacy of Darkness as it +contains one of the locations of Henry's children. + +#### Shovel zone sun door +Same as the above moon door, but go left at the save jewel junction instead of straight. + +### Invisible Items +#### Twin arrow signs +From the save point after the stepping stones following the initial Spider Women encounter, travel forward until you reach a +T-junction with two arrow signs at it. The right-pointing sign here contains an item on its post. + +#### Near lonesome bucket +After riding the first upwards elevator following turning left at the twin arrow signs, you'll arrive at the lonesome bucket +area, with said bucket being found if you turn left at the first opportunity after said elevator. The item here is not +found *in* the bucket, but rather on a completely unmarked spot some meters from it. This had to have been a mistake, +seeing as Legacy moved it to actually be in the bucket. + +#### Shovel +Can be found by taking either platforming course on the right side of the straightaway to the end after the gondolas. +This entire zone is noteable for the fact that there's no reason for Reinhardt to come here in either game; it's only ever +required for Henry to rescue one of his children. + +### 3-Hit Breakables +#### Twin arrow signs rock +Turn right at the twin arrow signs junction, and you'll find this rock at the dead-end by the river. It contains a bunch of +healing and status items that translate into 5 rando checks. + +#### Lonesome bucket poison pit rock +Near the lonesome bucket is the start of a platforming course over poison water that connects near Albert's campsite...which +you could reach anyway just by traveling forward at the prior junction instead of left. So what's the point of this poison +pit, then? Look out into the middle of it, and you'll see this rock on a tiny island out in the middle of it. If you choose +to take the hard way here, your reward will be three meat checks. + + + +## Underground Waterway +#### Carrie Crawlspace +This is located shortly after the corridor following the ledges that let you reach the first waterfall's source alcove. +Notably, only Carrie is able to crouch and go through this, making these the only checks in the *entire* game that are +hard impossible without being a specific character. So if you have Carrie Logic on and your character is Reinhardt, you'll +have to hold L while loading into a map to change to Carrie just for this one secret. If Carrie Logic is off, then these +locations will not be added and you can just skip them entirely. + +### 3-Hit Breakables +#### First poison parkour ledge +Near the start of the level is a series of ledges you can climb onto and platform across to reach a corner with a lantern +that you can normally get a Cure Ampoule from. The first of these ledges can be broken for an assortment of 6 things. + +#### Inside skeleton crusher ledge +To the left of the hallway entrance leading to the third switch is a long shimmy-able ledge that you can grab onto and shimmy +for a whole eternity (I implemented a setting JUST to make shimmying this ledge faster!) to get to a couple stand on-able ledges, +one of which has a lantern above it containing a check. This ledge can be broken for 3 chickens. I'd highly suggest bringing +Holy Water for this because otherwise you're forced to break it from the other, lower ledge that's here. And this ledge +will drop endless crawling skeletons on you as long as you're on it. + + + +## Castle Center +#### Atop elevator room machine +In the elevator room, right from the entrance coming in from the vampire triplets' room, is a machine that you can press +C-Right on to get dialog reading "An enormous machine." There's a torch on top of this machine that you can reach by +climbing onto the slanted part of the walls in the room. + +#### Heinrich Meyer +The friendly lizard-man who normally gives you the Chamber Key in vanilla has a check for you just like Vincent. +Yes, he has a name! And you'd best not forget it! + +#### Torture chamber rafters +A check can be found in the rafters in the room with the Mandragora shelf. Get onto and jump off the giant scythe or the +torture instrument shelf to make it up there. It's less annoying to do without Mandragora since having it will cause ghosts to +infinitely spawn in here. + +### Invisible Items +#### Red carpet hall knight +The suit of armor in the red carpet hallway after the bottom elevator room, directly next to the door leading into the +Lizard Locker Room. + +#### Lizard locker knight +The suit of armor in the Lizard Locker Room itself, directly across from the door connecting to the red carpet hallway. + +#### Broken staircase knight +The suit of armor in the broken staircase room following the Lizard Locker Room. + +#### Inside cracked wall hallway flamethrower +In the upper cracked wall hallway, it is in the lower flamethrower that is part of the pair between the Butler Bros. Room +and the main part of the hallway. + +#### Nitro room crates +The wall of crates in the Nitro room on Heinrich Meyer's side. This is notable for being one of the very rare Healing Kits +that you can get for free in vanilla. + +#### Hell Knight landing corner knight +The inactive suit of armor in the corner of the room before the Maid Sisters' Room, which also contains an active knight. + +#### Maid sisters room vase +The lone vase in the vampire Maid Sisters' Room, directly across from the door leading to the Hell Knight Landing. +Yes, you are actually supposed to *check* this with C-right to get its item; not break it like you did to the pots in +the Villa earlier! + +#### Invention room giant Famicart +The giant square-shaped thing in one corner of the invention room that looks vaguely like a massive video game cartridge. +A Famicom cartridge, perhaps? + +#### Invention room round machine +The brown circular machine in the invention room, close to the middle of the wall on the side of the Spike Crusher Room. + +#### Inside nitro hallway flamethrower +The lower flamethrower in the hallway between the Nitro room from the Spike Crusher Room, near the two doors to said rooms. + +#### Torture chamber instrument rack +The shelf full of torture instruments in the torture chamber, to the right of the Mandragora shelf. + +### 3-Hit Breakables +#### Behemoth arena crate +This large crate can be found in the back-right corner of Behemoth's arena and is pretty hard to miss. Break it to get 5 +moneybags-turned-checks. + +#### Elevator room unoccupied statue stand +In the bottom elevator room is a statue on a stand that will cry literal Bloody Tears if you get near it. On the opposite +side of the room from this, near the enormous machine, is a stand much like the one the aforementioned statue is on only +this one is completely vacant. This stand can be broken for 3 roast beefs-turned checks. + +#### Lizard locker room slab +In the Lizard Locker Room, on top of the second locker from the side of the room with the door to the red carpet hallway, +is a metallic box-like slab thingy that can be broken normally for 4 status items. This 3HB is notable for being one of two +funny ones in the game that does NOT set a flag when you break it in vanilla, meaning you can keep breaking it over and +over again for infinite Purifyings and Cure Ampoules! + +### The Lizard Lockers +If you turned on the Lizard Locker Items setting, then hoo boy, you are in for a FUN =) time! Inside each one of the six Lizard +Lockers is a check, and the way you get these checks is by camping near each of the lockers as you defeat said Lizards, +praying that one will emerge from it and cause it to open to give you a chance to grab it. It is *completely* luck-based, +you have a 1-in-6 (around 16%) chance per Lizard-man that emerges, and you have to repeat this process six times for each +check inside each locker. You can open and cancel the warp menu to make things easier, but other than that, enjoy playing +with the Lizards! + + + +## Duel Tower +#### Invisible bridge balcony +Located between Werewolf and Were-bull's arenas. Use an invisible bridge to reach it; it starts at the highest platform +on the same wall as it that appears after defeating Werewolf. The balcony contains two checks and a save point that I +added specifically for this rando to make the level less frustrating. + +#### Above Were-bull arena +The only check that can be permanently missed in the vanilla game depending on the order you do things, not counting any +points of no return. Between Werewolf and Were-bull's arenas is an alternate path that you can take downward and around +to avoid Were-bull completely and get on top of his still-raised arena, so you can reach this. In the rando, I set it up so +that his arena will go back to being raised if you leave the area and come back, and if you get the item later his arena flag +will be set then. If you're playing with Dracula's bosses condition, then you can only get one Special2 off of him the first +time you beat him and then none more after that. + + + +## Tower of Execution +#### Invisible bridge ledge +There are two ways to reach this one; use the invisible bridge that starts at the walkway above the entrance, or jump to +it from the Execution Key gate alcove. Collecting this Special2 in vanilla unlocks Reinhardt's alternate costume. + +#### Guillotine tower top level +This iron maiden is strategically placed in such a way that you will very likely miss it if you aren't looking carefully for +it, so I am including it here. When you make it to the top floor of the level, as you approach the tower for the final time, +look on the opposite side of it from where you approach it, and you will find this. The laggiest check in the whole game! +I'd dare someone to find some check in some other Archipelago game that lags harder than this. + +### 3-Hit Breakables +#### Pre-mid-savepoint platforms ledge +Here's a really weird one that even I never found about until well after I finished the 3HB setting and moved on to deving +other things, and I'd honestly be shocked if ANYONE knew about outside the context of this rando! This square-shaped +platform can be found right before the second set of expanding and retracting wall platforms, leading up to the mid-save +point, after going towards the central tower structure for the second time on the second floor. Breaking it will drop an +assortment of 5 items, one of which is notable for being the ONE sub-weapon that drops from a 3HB. This meant I had to really +change how things work to account for sub-weapons being in 3HBs! + + + +## Tower of Science +#### Invisible bridge platform +Following the hallway with a save jewel beyond the Science Key2 door, look to your right, and you'll see this. Mind the +gap separating the invisible bridge from the solid ground of the bottom part of this section! + +### 3-Hit Breakables +#### Invisible bridge platform crate +Near the candle on the above-mentioned invisible bridge platform is a small metallic crate. Break it for a total of 6 +checks, which in vanilla are 2 chickens, moneybags, and jewels. + + + +## Tower of Sorcery +#### Trick shot from mid-savepoint platform +From the platform with the save jewel, look back towards the vanishing red platforms that you had to conquer to get up +there, and you'll see a breakable diamond floating high above a solid platform before it. An ultra-precise orb shot from +Carrie can hit it to reveal the check, so you can then go down and get it. If you are playing as Reinhardt, you'll have +to use a sub-weapon. Any sub-weapon that's not Holy Water will work. Sub-weapons and the necessary jewels can both be +farmed off the local Icemen if it really comes down to it. + +#### Above yellow bubble +Directly above the yellow bubble that you break to raise the middle yellow large platform. Jump off the final red platform +in the series of red platforms right after the save point when said platform bobs all the way up, and you'll be able to +hit this diamond with good timing. + +#### Above tiny blue platforms start +Above the large platform after the yellow ones from whence you can reach the first of the series of tiny blue platforms. +This diamond is low enough that you can reach it by simply jumping straight up to it. + +#### Invisible bridge platform +Located at the very end of the stage, off to the side. Use the invisible bridge to reach it. The Special2 that unlocks +Carrie's costume can be normally found here (or Special3, depending on if you are playing the PAL version or not). + + + +## Clock Tower +All the normal items here and in Room of Clocks are self-explanatory, but the 3HBs in Clock Tower are a whoooooooole 'nother story. So buckle up... + +### 3-Hit Breakables +#### Gear climb room battery underside slab +In the first room, on the side you initially climb up, go up until you find a battery-like object that you can stand on +as a platform. From the platform right after this, you can hit this 3HB that can be found on the underside of the structure +in the corner, above the structure before the first set of gears that you initially start the climb on. 3 chickens/checks +can be gotten out of this one. + +#### Gear climb room door underside slab +Now THIS one can be very annoying to get, doubly so if you're playing as Reinhardt, triply so if you're playing Hard Mode +on top of that because you will also have to deal with invincible, bone-throwing red skeletons here! This slab can be +found on the underside of the platform in the first room with the door leading out into the second area and drops 3 +beefs/checks when you break it. Carrie is small enough to crouch under the platform and shoot the thing a few times +without *too* much hassle, but the only way I know of for Reinhardt to get it is to hang him off the side of the gear +and pull him up once he gets moved under the platform. Getting him to then face the breakable and whip it without falling +off due to the gear's rotation is next-to-impossible, I find, so the safest method to breaking it as him is to just rush +it with his sword, go back up, repeat 2 more times. Pray the aftermentioned Hard Mode skellies don't decide to cause *too* +much trouble during all of this! + +#### Final room entrance slab +Simply next to the entrance when you come into the third and final room from the intended way. Drops 2 moneybags-turned-checks, +which funnily normally have their item IDs shared with the other 3HB in this room's items, so I had to separate those for +the rando. + +#### Renon's final offers slab +At the top of the final room, on a platform near the Renon contract that would normally be the very last in the game. +This 3HB drops a whopping 8 items, more than any other 3HB in the entire game, and 6 of those are all moneybags. They +*really* shower you in gold in preparation for the finale, huh? + + + +## Castle Keep +#### Behind Dracula's chamber/Dracula's floating cube +This game continues the CV tradition of having a hidden secret around Dracula's chamber that you can get helpful things +from before the final battle begins. Jump onto the torch ledges and use the thin walkway to reach the backside of Dracula's +chamber where these can both be found. The floating cube, in question, can be reached with an invisible bridge. The other +candle here is noteworthy for being the sole jewel candle in vanilla that doesn't set a flag, meaning you can keep going +back down and up the stairs to farm infinite sub-weapon ammo for Dracula like in old school Castlevania! + +### Invisible Items +#### Left/Right Dracula door flame +Inside the torches on either side of the door to Dracula's chamber. Similar to the above-mentioned jewel torch, these do +not set flags. So you can get infinite healing kits for free by constantly going down and back up! \ No newline at end of file diff --git a/worlds/cv64/docs/setup_en.md b/worlds/cv64/docs/setup_en.md new file mode 100644 index 000000000000..707618a1eba5 --- /dev/null +++ b/worlds/cv64/docs/setup_en.md @@ -0,0 +1,63 @@ +# Castlevania 64 Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- A Castlevania 64 ROM of the US 1.0 version specifically. The Archipelago community cannot provide this. +- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later + +### Configuring BizHawk + +Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings: + +- If you're using BizHawk 2.7 or 2.8, go to `Config > Customize`. On the Advanced tab, switch the Lua Core from +`NLua+KopiLua` to `Lua+LuaInterface`, then restart EmuHawk. (If you're using BizHawk 2.9, you can skip this step.) +- Under `Config > Customize`, check the "Run in background" option to prevent disconnecting from the client while you're +tabbed out of EmuHawk. +- Open a `.z64` file in EmuHawk and go to `Config > Controllers…` to configure your inputs. If you can't click +`Controllers…`, load any `.z64` ROM first. +- Consider clearing keybinds in `Config > Hotkeys…` if you don't intend to use them. Select the keybind and press Esc to +clear it. +- All non-Japanese versions of the N64 Castlevanias require a Controller Pak to save game data. To enable this, while +you still have the `.z64` ROM loaded, go to `N64 > Controller Settings...`, click the dropdown by `Controller 1`, and +click `Memory Card`. You must then restart EmuHawk for it to take effect. +- After enabling the `Memory Card` setting, next time you boot up your Castlevania 64 ROM, you will see the +No "CASTLEVANIA" Note Found screen. Pick `Create "CASTLEVANIA" Note Now > Yes` to create save data and enable saving at +the White Jewels. + + +## Generating and Patching a Game + +1. Create your options file (YAML). You can make one on the +[Castlevania 64 options page](../../../games/Castlevania%2064/player-options). +2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game). +This will generate an output file for you. Your patch file will have the `.apcv64` file extension. +3. Open `ArchipelagoLauncher.exe` +4. Select "Open Patch" on the left side and select your patch file. +5. If this is your first time patching, you will be prompted to locate your vanilla ROM. +6. A patched `.z64` file will be created in the same place as the patch file. +7. On your first time opening a patch with BizHawk Client, you will also be asked to locate `EmuHawk.exe` in your +BizHawk install. + +If you're playing a single-player seed, and you don't care about hints, you can stop here, close the client, and load +the patched ROM in any emulator or EverDrive of your choice. However, for multiworlds and other Archipelago features, +continue below using BizHawk as your emulator. + +## Connecting to a Server + +By default, opening a patch file will do steps 1-5 below for you automatically. Even so, keep them in your memory just +in case you have to close and reopen a window mid-game for some reason. + +1. Castlevania 64 uses Archipelago's BizHawk Client. If the client isn't still open from when you patched your game, +you can re-open it from the launcher. +2. Ensure EmuHawk is running the patched ROM. +3. In EmuHawk, go to `Tools > Lua Console`. This window must stay open while playing. +4. In the Lua Console window, go to `Script > Open Script…`. +5. Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`. +6. The emulator may freeze every few seconds until it manages to connect to the client. This is expected. The BizHawk +Client window should indicate that it connected and recognized Castlevania 64. +7. To connect the client to the server, enter your room's address and port (e.g. `archipelago.gg:38281`) into the +top text field of the client and click Connect. + +You should now be able to receive and send items. You'll need to do these steps every time you want to reconnect. It is +perfectly safe to make progress offline; everything will re-sync when you reconnect. diff --git a/worlds/cv64/entrances.py b/worlds/cv64/entrances.py new file mode 100644 index 000000000000..74537f92441b --- /dev/null +++ b/worlds/cv64/entrances.py @@ -0,0 +1,149 @@ +from .data import ename, iname, rname +from .stages import get_stage_info +from .options import CV64Options + +from typing import Dict, List, Tuple, Union + +# # # KEY # # # +# "connection" = The name of the Region the Entrance connects into. If it's a Tuple[str, str], we take the stage in +# active_stage_exits given in the second string and then the stage given in that stage's slot given in +# the first string, and take the start or end Region of that stage. +# "rule" = What rule should be applied to the Entrance during set_rules, as defined in self.rules in the CV64Rules class +# definition in rules.py. +# "add conds" = A list of player options conditions that must be satisfied for the Entrance to be added. Can be of +# varying length depending on how many conditions need to be satisfied. In the add_conds dict's tuples, +# the first element is the name of the option, the second is the option value to check for, and the third +# is a boolean for whether we are evaluating for the option value or not. +entrance_info = { + # Forest of Silence + ename.forest_dbridge_gate: {"connection": rname.forest_mid}, + ename.forest_werewolf_gate: {"connection": rname.forest_end}, + ename.forest_end: {"connection": ("next", rname.forest_of_silence)}, + # Castle Wall + ename.cw_portcullis_c: {"connection": rname.cw_exit}, + ename.cw_lt_skip: {"connection": ("next", rname.castle_wall), "add conds": ["hard"]}, + ename.cw_lt_door: {"connection": rname.cw_ltower, "rule": iname.left_tower_key}, + ename.cw_end: {"connection": ("next", rname.castle_wall)}, + # Villa + ename.villa_dog_gates: {"connection": rname.villa_main}, + ename.villa_snipe_dogs: {"connection": rname.villa_start, "add conds": ["carrie", "hard"]}, + ename.villa_to_storeroom: {"connection": rname.villa_storeroom, "rule": iname.storeroom_key}, + ename.villa_to_archives: {"connection": rname.villa_archives, "rule": iname.archives_key}, + ename.villa_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.villa_to_maze: {"connection": rname.villa_maze, "rule": iname.garden_key}, + ename.villa_from_storeroom: {"connection": rname.villa_main, "rule": iname.storeroom_key}, + ename.villa_from_maze: {"connection": rname.villa_servants, "rule": iname.garden_key}, + ename.villa_servant_door: {"connection": rname.villa_main}, + ename.villa_copper_door: {"connection": rname.villa_crypt, "rule": iname.copper_key, + "add conds": ["not hard"]}, + ename.villa_copper_skip: {"connection": rname.villa_crypt, "add conds": ["hard"]}, + ename.villa_bridge_door: {"connection": rname.villa_maze}, + ename.villa_end_r: {"connection": ("next", rname.villa)}, + ename.villa_end_c: {"connection": ("alt", rname.villa)}, + # Tunnel + ename.tunnel_start_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.tunnel_gondolas: {"connection": rname.tunnel_end}, + ename.tunnel_end_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.tunnel_end: {"connection": ("next", rname.tunnel)}, + # Underground Waterway + ename.uw_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.uw_final_waterfall: {"connection": rname.uw_end}, + ename.uw_waterfall_skip: {"connection": rname.uw_main, "add conds": ["hard"]}, + ename.uw_end: {"connection": ("next", rname.underground_waterway)}, + # Castle Center + ename.cc_tc_door: {"connection": rname.cc_torture_chamber, "rule": iname.chamber_key}, + ename.cc_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.cc_lower_wall: {"connection": rname.cc_crystal, "rule": "Bomb 2"}, + ename.cc_upper_wall: {"connection": rname.cc_library, "rule": "Bomb 1"}, + ename.cc_elevator: {"connection": rname.cc_elev_top}, + ename.cc_exit_r: {"connection": ("next", rname.castle_center)}, + ename.cc_exit_c: {"connection": ("alt", rname.castle_center)}, + # Duel Tower + ename.dt_start: {"connection": ("prev", rname.duel_tower)}, + ename.dt_end: {"connection": ("next", rname.duel_tower)}, + # Tower of Execution + ename.toe_start: {"connection": ("prev", rname.tower_of_execution)}, + ename.toe_gate: {"connection": rname.toe_ledge, "rule": iname.execution_key, + "add conds": ["not hard"]}, + ename.toe_gate_skip: {"connection": rname.toe_ledge, "add conds": ["hard"]}, + ename.toe_end: {"connection": ("next", rname.tower_of_execution)}, + # Tower of Science + ename.tosci_start: {"connection": ("prev", rname.tower_of_science)}, + ename.tosci_key1_door: {"connection": rname.tosci_three_doors, "rule": iname.science_key1}, + ename.tosci_to_key2_door: {"connection": rname.tosci_conveyors, "rule": iname.science_key2}, + ename.tosci_from_key2_door: {"connection": rname.tosci_start, "rule": iname.science_key2}, + ename.tosci_key3_door: {"connection": rname.tosci_key3, "rule": iname.science_key3}, + ename.tosci_end: {"connection": ("next", rname.tower_of_science)}, + # Tower of Sorcery + ename.tosor_start: {"connection": ("prev", rname.tower_of_sorcery)}, + ename.tosor_end: {"connection": ("next", rname.tower_of_sorcery)}, + # Room of Clocks + ename.roc_gate: {"connection": ("next", rname.room_of_clocks)}, + # Clock Tower + ename.ct_to_door1: {"connection": rname.ct_middle, "rule": iname.clocktower_key1}, + ename.ct_from_door1: {"connection": rname.ct_start, "rule": iname.clocktower_key1}, + ename.ct_to_door2: {"connection": rname.ct_end, "rule": iname.clocktower_key2}, + ename.ct_from_door2: {"connection": rname.ct_middle, "rule": iname.clocktower_key2}, + ename.ct_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.ct_door_3: {"connection": ("next", rname.clock_tower), "rule": iname.clocktower_key3}, + # Castle Keep + ename.ck_slope_jump: {"connection": rname.roc_main, "add conds": ["hard"]}, + ename.ck_drac_door: {"connection": rname.ck_drac_chamber, "rule": "Dracula"} +} + +add_conds = {"carrie": ("carrie_logic", True, True), + "hard": ("hard_logic", True, True), + "not hard": ("hard_logic", False, True), + "shopsanity": ("shopsanity", True, True)} + +stage_connection_types = {"prev": "end region", + "next": "start region", + "alt": "start region"} + + +def get_entrance_info(entrance: str, info: str) -> Union[str, Tuple[str, str], List[str], None]: + return entrance_info[entrance].get(info, None) + + +def get_warp_entrances(active_warp_list: List[str]) -> Dict[str, str]: + # Create the starting stage Entrance. + warp_entrances = {get_stage_info(active_warp_list[0], "start region"): "Start stage"} + + # Create the warp Entrances. + for i in range(1, len(active_warp_list)): + mid_stage_region = get_stage_info(active_warp_list[i], "mid region") + warp_entrances.update({mid_stage_region: f"Warp {i}"}) + + return warp_entrances + + +def verify_entrances(options: CV64Options, entrances: List[str], + active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[str, str]: + verified_entrances = {} + + for ent_name in entrances: + ent_add_conds = get_entrance_info(ent_name, "add conds") + + # Check any options that might be associated with the Entrance before adding it. + add_it = True + if ent_add_conds is not None: + for cond in ent_add_conds: + if not ((getattr(options, add_conds[cond][0]).value == add_conds[cond][1]) == add_conds[cond][2]): + add_it = False + + if not add_it: + continue + + # Add the Entrance to the verified Entrances if the above check passes. + connection = get_entrance_info(ent_name, "connection") + + # If the Entrance is a connection to a different stage, get the corresponding other stage Region. + if isinstance(connection, tuple): + connecting_stage = active_stage_exits[connection[1]][connection[0]] + # Stages that lead backwards at the beginning of the line will appear leading to "Menu". + if connecting_stage in ["Menu", None]: + continue + connection = get_stage_info(connecting_stage, stage_connection_types[connection[0]]) + verified_entrances.update({connection: ent_name}) + + return verified_entrances diff --git a/worlds/cv64/items.py b/worlds/cv64/items.py new file mode 100644 index 000000000000..d40f5d53cb41 --- /dev/null +++ b/worlds/cv64/items.py @@ -0,0 +1,214 @@ +from BaseClasses import Item +from .data import iname +from .locations import base_id, get_location_info +from .options import DraculasCondition, SpareKeys + +from typing import TYPE_CHECKING, Dict, Union + +if TYPE_CHECKING: + from . import CV64World + +import math + + +class CV64Item(Item): + game: str = "Castlevania 64" + + +# # # KEY # # # +# "code" = The unique part of the Item's AP code attribute, as well as the value to call the in-game "prepare item +# textbox" function with to give the Item in-game. Add this + base_id to get the actual AP code. +# "default classification" = The AP Item Classification that gets assigned to instances of that Item in create_item +# by default, unless I deliberately override it (as is the case for some Special1s). +# "inventory offset" = What offset from the start of the in-game inventory array (beginning at 0x80389C4B) stores the +# current count for that Item. Used for start inventory purposes. +# "pickup actor id" = The ID for the Item's in-game Item pickup actor. If it's not in the Item's data dict, it's the +# same as the Item's code. This is what gets written in the ROM to replace non-NPC/shop items. +# "sub equip id" = For sub-weapons specifically, this is the number to put in the game's "current sub-weapon" value to +# indicate the player currently having that weapon. Used for start inventory purposes. +item_info = { + # White jewel + iname.red_jewel_s: {"code": 0x02, "default classification": "filler"}, + iname.red_jewel_l: {"code": 0x03, "default classification": "filler"}, + iname.special_one: {"code": 0x04, "default classification": "progression_skip_balancing", + "inventory offset": 0}, + iname.special_two: {"code": 0x05, "default classification": "progression_skip_balancing", + "inventory offset": 1}, + iname.roast_chicken: {"code": 0x06, "default classification": "filler", "inventory offset": 2}, + iname.roast_beef: {"code": 0x07, "default classification": "filler", "inventory offset": 3}, + iname.healing_kit: {"code": 0x08, "default classification": "useful", "inventory offset": 4}, + iname.purifying: {"code": 0x09, "default classification": "filler", "inventory offset": 5}, + iname.cure_ampoule: {"code": 0x0A, "default classification": "filler", "inventory offset": 6}, + # pot-pourri + iname.powerup: {"code": 0x0C, "default classification": "filler"}, + iname.permaup: {"code": 0x10C, "default classification": "useful", "pickup actor id": 0x0C, + "inventory offset": 8}, + iname.knife: {"code": 0x0D, "default classification": "filler", "pickup actor id": 0x10, + "sub equip id": 1}, + iname.holy_water: {"code": 0x0E, "default classification": "filler", "pickup actor id": 0x0D, + "sub equip id": 2}, + iname.cross: {"code": 0x0F, "default classification": "filler", "pickup actor id": 0x0E, + "sub equip id": 3}, + iname.axe: {"code": 0x10, "default classification": "filler", "pickup actor id": 0x0F, + "sub equip id": 4}, + # Wooden stake (AP item) + iname.ice_trap: {"code": 0x12, "default classification": "trap"}, + # The contract + # engagement ring + iname.magical_nitro: {"code": 0x15, "default classification": "progression", "inventory offset": 17}, + iname.mandragora: {"code": 0x16, "default classification": "progression", "inventory offset": 18}, + iname.sun_card: {"code": 0x17, "default classification": "filler", "inventory offset": 19}, + iname.moon_card: {"code": 0x18, "default classification": "filler", "inventory offset": 20}, + # Incandescent gaze + iname.archives_key: {"code": 0x1A, "default classification": "progression", "pickup actor id": 0x1D, + "inventory offset": 22}, + iname.left_tower_key: {"code": 0x1B, "default classification": "progression", "pickup actor id": 0x1E, + "inventory offset": 23}, + iname.storeroom_key: {"code": 0x1C, "default classification": "progression", "pickup actor id": 0x1F, + "inventory offset": 24}, + iname.garden_key: {"code": 0x1D, "default classification": "progression", "pickup actor id": 0x20, + "inventory offset": 25}, + iname.copper_key: {"code": 0x1E, "default classification": "progression", "pickup actor id": 0x21, + "inventory offset": 26}, + iname.chamber_key: {"code": 0x1F, "default classification": "progression", "pickup actor id": 0x22, + "inventory offset": 27}, + iname.execution_key: {"code": 0x20, "default classification": "progression", "pickup actor id": 0x23, + "inventory offset": 28}, + iname.science_key1: {"code": 0x21, "default classification": "progression", "pickup actor id": 0x24, + "inventory offset": 29}, + iname.science_key2: {"code": 0x22, "default classification": "progression", "pickup actor id": 0x25, + "inventory offset": 30}, + iname.science_key3: {"code": 0x23, "default classification": "progression", "pickup actor id": 0x26, + "inventory offset": 31}, + iname.clocktower_key1: {"code": 0x24, "default classification": "progression", "pickup actor id": 0x27, + "inventory offset": 32}, + iname.clocktower_key2: {"code": 0x25, "default classification": "progression", "pickup actor id": 0x28, + "inventory offset": 33}, + iname.clocktower_key3: {"code": 0x26, "default classification": "progression", "pickup actor id": 0x29, + "inventory offset": 34}, + iname.five_hundred_gold: {"code": 0x27, "default classification": "filler", "pickup actor id": 0x1A}, + iname.three_hundred_gold: {"code": 0x28, "default classification": "filler", "pickup actor id": 0x1B}, + iname.one_hundred_gold: {"code": 0x29, "default classification": "filler", "pickup actor id": 0x1C}, + iname.crystal: {"default classification": "progression"}, + iname.trophy: {"default classification": "progression"}, + iname.victory: {"default classification": "progression"} +} + +filler_item_names = [iname.red_jewel_s, iname.red_jewel_l, iname.five_hundred_gold, iname.three_hundred_gold, + iname.one_hundred_gold] + + +def get_item_info(item: str, info: str) -> Union[str, int, None]: + return item_info[item].get(info, None) + + +def get_item_names_to_ids() -> Dict[str, int]: + return {name: get_item_info(name, "code")+base_id for name in item_info if get_item_info(name, "code") is not None} + + +def get_item_counts(world: "CV64World") -> Dict[str, Dict[str, int]]: + + active_locations = world.multiworld.get_unfilled_locations(world.player) + + item_counts = { + "progression": {}, + "progression_skip_balancing": {}, + "useful": {}, + "filler": {}, + "trap": {} + } + total_items = 0 + extras_count = 0 + + # Get from each location its vanilla item and add it to the default item counts. + for loc in active_locations: + if loc.address is None: + continue + + if world.options.hard_item_pool and get_location_info(loc.name, "hard item") is not None: + item_to_add = get_location_info(loc.name, "hard item") + else: + item_to_add = get_location_info(loc.name, "normal item") + + classification = get_item_info(item_to_add, "default classification") + + if item_to_add not in item_counts[classification]: + item_counts[classification][item_to_add] = 1 + else: + item_counts[classification][item_to_add] += 1 + total_items += 1 + + # Replace all but 2 PowerUps with junk if Permanent PowerUps is on and mark those two PowerUps as Useful. + if world.options.permanent_powerups: + for i in range(item_counts["filler"][iname.powerup] - 2): + item_counts["filler"][world.get_filler_item_name()] += 1 + del(item_counts["filler"][iname.powerup]) + item_counts["useful"][iname.permaup] = 2 + + # Add the total Special1s. + item_counts["progression_skip_balancing"][iname.special_one] = world.options.total_special1s.value + extras_count += world.options.total_special1s.value + + # Add the total Special2s if Dracula's Condition is Special2s. + if world.options.draculas_condition == DraculasCondition.option_specials: + item_counts["progression_skip_balancing"][iname.special_two] = world.options.total_special2s.value + extras_count += world.options.total_special2s.value + + # Determine the extra key counts if applicable. Doing this before moving Special1s will ensure only the keys and + # bomb components are affected by this. + for key in item_counts["progression"]: + spare_keys = 0 + if world.options.spare_keys == SpareKeys.option_on: + spare_keys = item_counts["progression"][key] + elif world.options.spare_keys == SpareKeys.option_chance: + if item_counts["progression"][key] > 0: + for i in range(item_counts["progression"][key]): + spare_keys += world.random.randint(0, 1) + item_counts["progression"][key] += spare_keys + extras_count += spare_keys + + # Move the total number of Special1s needed to warp everywhere to normal progression balancing if S1s per warp is + # 3 or lower. + if world.s1s_per_warp <= 3: + item_counts["progression_skip_balancing"][iname.special_one] -= world.s1s_per_warp * 7 + item_counts["progression"][iname.special_one] = world.s1s_per_warp * 7 + + # Determine the total amounts of replaceable filler and non-filler junk. + total_filler_junk = 0 + total_non_filler_junk = 0 + for junk in item_counts["filler"]: + if junk in filler_item_names: + total_filler_junk += item_counts["filler"][junk] + else: + total_non_filler_junk += item_counts["filler"][junk] + + # Subtract from the filler counts total number of "extra" items we've added. get_filler_item_name() filler will be + # subtracted from first until we run out of that, at which point we'll start subtracting from the rest. At this + # moment, non-filler item name filler cannot run out no matter the settings, so I haven't bothered adding handling + # for when it does yet. + available_filler_junk = filler_item_names.copy() + for i in range(extras_count): + if total_filler_junk > 0: + total_filler_junk -= 1 + item_to_subtract = world.random.choice(available_filler_junk) + else: + total_non_filler_junk -= 1 + item_to_subtract = world.random.choice(list(item_counts["filler"].keys())) + + item_counts["filler"][item_to_subtract] -= 1 + if item_counts["filler"][item_to_subtract] == 0: + del(item_counts["filler"][item_to_subtract]) + if item_to_subtract in available_filler_junk: + available_filler_junk.remove(item_to_subtract) + + # Determine the Ice Trap count by taking a certain % of the total filler remaining at this point. + item_counts["trap"][iname.ice_trap] = math.floor((total_filler_junk + total_non_filler_junk) * + (world.options.ice_trap_percentage.value / 100.0)) + for i in range(item_counts["trap"][iname.ice_trap]): + # Subtract the remaining filler after determining the ice trap count. + item_to_subtract = world.random.choice(list(item_counts["filler"].keys())) + item_counts["filler"][item_to_subtract] -= 1 + if item_counts["filler"][item_to_subtract] == 0: + del (item_counts["filler"][item_to_subtract]) + + return item_counts diff --git a/worlds/cv64/locations.py b/worlds/cv64/locations.py new file mode 100644 index 000000000000..264f2f7c0b9c --- /dev/null +++ b/worlds/cv64/locations.py @@ -0,0 +1,699 @@ +from BaseClasses import Location +from .data import lname, iname +from .options import CV64Options, SubWeaponShuffle, DraculasCondition, RenonFightCondition, VincentFightCondition + +from typing import Dict, Optional, Union, List, Tuple + +base_id = 0xC64000 + + +class CV64Location(Location): + game: str = "Castlevania 64" + + +# # # KEY # # # +# "code" = The unique part of the Location's AP code attribute, as well as the in-game bitflag index starting from +# 0x80389BE4 that indicates the Location has been checked. Add this + base_id to get the actual AP code. +# "offset" = The offset in the ROM to overwrite to change the Item on that Location. +# "normal item" = The Item normally there in vanilla on most difficulties in most versions of the game. Used to +# determine the World's Item counts by checking what Locations are active. +# "hard item" = The Item normally there in Hard Mode in the PAL version of CV64 specifically. Used instead of the +# normal Item when the hard Item pool is enabled if it's in the Location's data dict. +# "add conds" = A list of player options conditions that must be satisfied for the Location to be added. Can be of +# varying length depending on how many conditions need to be satisfied. In the add_conds dict's tuples, +# the first element is the name of the option, the second is the option value to check for, and the third +# is a boolean for whether we are evaluating for the option value or not. +# "event" = What event Item to place on that Location, for Locations that are events specifically. +# "countdown" = What Countdown number in the array of Countdown numbers that Location contributes to. For the most part, +# this is figured out by taking that Location's corresponding stage's postion in the vanilla stage order, +# but there are some exceptions made for Locations in parts of Villa and Castle Center that split off into +# their own numbers. +# "type" = Anything special about this Location in-game, whether it be NPC-given, invisible, etc. +location_info = { + # Forest of Silence + lname.forest_pillars_right: {"code": 0x1C, "offset": 0x10C67B, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.forest_pillars_left: {"code": 0x46, "offset": 0x10C6EB, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.forest_pillars_top: {"code": 0x13, "offset": 0x10C71B, "normal item": iname.roast_beef, + "hard item": iname.red_jewel_l}, + lname.forest_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.forest_king_skeleton: {"code": 0xC, "offset": 0x10C6BB, "normal item": iname.five_hundred_gold}, + lname.forest_lgaz_in: {"code": 0x1A, "offset": 0x10C68B, "normal item": iname.moon_card}, + lname.forest_lgaz_top: {"code": 0x19, "offset": 0x10C693, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.forest_hgaz_in: {"code": 0xB, "offset": 0x10C6C3, "normal item": iname.sun_card}, + lname.forest_hgaz_top: {"code": 0x3, "offset": 0x10C6E3, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.forest_weretiger_sw: {"code": 0xA, "offset": 0x10C6CB, "normal item": iname.five_hundred_gold}, + lname.forest_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + lname.forest_weretiger_gate: {"code": 0x7, "offset": 0x10C683, "normal item": iname.powerup}, + lname.forest_dirge_tomb_l: {"code": 0x59, "offset": 0x10C74B, "normal item": iname.one_hundred_gold, + "add conds": ["empty"]}, + lname.forest_dirge_tomb_u: {"code": 0x8, "offset": 0x10C743, "normal item": iname.one_hundred_gold}, + lname.forest_dirge_plaque: {"code": 0x6, "offset": 0x7C7F9D, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "type": "inv"}, + lname.forest_dirge_ped: {"code": 0x45, "offset": 0x10C6FB, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.forest_dirge_rock1: {"code": 0x221, "offset": 0x10C791, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock2: {"code": 0x222, "offset": 0x10C793, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock3: {"code": 0x223, "offset": 0x10C795, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock4: {"code": 0x224, "offset": 0x10C797, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock5: {"code": 0x225, "offset": 0x10C799, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_corpse_save: {"code": 0xF, "offset": 0x10C6A3, "normal item": iname.red_jewel_s}, + lname.forest_dbridge_wall: {"code": 0x18, "offset": 0x10C69B, "normal item": iname.red_jewel_s}, + lname.forest_dbridge_sw: {"code": 0x9, "offset": 0x10C6D3, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.forest_dbridge_gate_l: {"code": 0x44, "offset": 0x10C6F3, "normal item": iname.axe, "add conds": ["sub"]}, + lname.forest_dbridge_gate_r: {"code": 0xE, "offset": 0x10C6AB, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.forest_dbridge_tomb_l: {"code": 0xEA, "offset": 0x10C763, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_dbridge_tomb_ur: {"code": 0xE4, "offset": 0x10C773, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_dbridge_tomb_uf: {"code": 0x1B, "offset": 0x10C76B, "normal item": iname.red_jewel_s}, + lname.forest_bface_tomb_lf: {"code": 0x10, "offset": 0x10C75B, "normal item": iname.roast_chicken}, + lname.forest_bface_tomb_lr: {"code": 0x58, "offset": 0x10C753, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_bface_tomb_u: {"code": 0x1E, "offset": 0x10C77B, "normal item": iname.one_hundred_gold}, + lname.forest_ibridge: {"code": 0x2, "offset": 0x10C713, "normal item": iname.one_hundred_gold}, + lname.forest_bridge_rock1: {"code": 0x227, "offset": 0x10C79D, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.forest_bridge_rock2: {"code": 0x228, "offset": 0x10C79F, "normal item": iname.five_hundred_gold, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.forest_bridge_rock3: {"code": 0x229, "offset": 0x10C7A1, "normal item": iname.powerup, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.forest_bridge_rock4: {"code": 0x22A, "offset": 0x10C7A3, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.forest_werewolf_tomb_lf: {"code": 0xE7, "offset": 0x10C783, "normal item": iname.one_hundred_gold, + "add conds": ["empty"]}, + lname.forest_werewolf_tomb_lr: {"code": 0xE6, "offset": 0x10C73B, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_werewolf_tomb_r: {"code": 0x4, "offset": 0x10C733, "normal item": iname.sun_card}, + lname.forest_werewolf_plaque: {"code": 0x1, "offset": 0xBFC8AF, "normal item": iname.roast_chicken, + "type": "inv"}, + lname.forest_werewolf_tree: {"code": 0xD, "offset": 0x10C6B3, "normal item": iname.red_jewel_s}, + lname.forest_werewolf_island: {"code": 0x41, "offset": 0x10C703, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.forest_final_sw: {"code": 0x12, "offset": 0x10C72B, "normal item": iname.roast_beef}, + lname.forest_boss_three: {"event": iname.trophy, "add conds": ["boss"]}, + + # Castle Wall + lname.cwr_bottom: {"code": 0x1DD, "offset": 0x10C7E7, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold}, + lname.cw_dragon_sw: {"code": 0x153, "offset": 0x10C817, "normal item": iname.roast_chicken}, + lname.cw_boss: {"event": iname.trophy, "add conds": ["boss"]}, + lname.cw_save_slab1: {"code": 0x22C, "offset": 0x10C84D, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.cw_save_slab2: {"code": 0x22D, "offset": 0x10C84F, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_save_slab3: {"code": 0x22E, "offset": 0x10C851, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_save_slab4: {"code": 0x22F, "offset": 0x10C853, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_save_slab5: {"code": 0x230, "offset": 0x10C855, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_rrampart: {"code": 0x156, "offset": 0x10C7FF, "normal item": iname.five_hundred_gold}, + lname.cw_lrampart: {"code": 0x155, "offset": 0x10C807, "normal item": iname.moon_card, + "hard item": iname.one_hundred_gold}, + lname.cw_pillar: {"code": 0x14D, "offset": 0x7F9A0F, "normal item": iname.holy_water, "add conds": ["sub"]}, + lname.cw_shelf_visible: {"code": 0x158, "offset": 0x7F99A9, "normal item": iname.powerup}, + lname.cw_shelf_sandbags: {"code": 0x14E, "offset": 0x7F9A3E, "normal item": iname.five_hundred_gold, "type": "inv"}, + lname.cw_shelf_torch: {"code": 0x14C, "offset": 0x10C82F, "normal item": iname.cross, "add conds": ["sub"]}, + lname.cw_ground_left: {"code": 0x14B, "offset": 0x10C827, "normal item": iname.knife, "add conds": ["sub"]}, + lname.cw_ground_middle: {"code": 0x159, "offset": 0x10C7F7, "normal item": iname.left_tower_key}, + lname.cw_ground_right: {"code": 0x14A, "offset": 0x10C81F, "normal item": iname.axe, "add conds": ["sub"]}, + lname.cwl_bottom: {"code": 0x1DE, "offset": 0x10C7DF, "normal item": iname.moon_card}, + lname.cwl_bridge: {"code": 0x1DC, "offset": 0x10C7EF, "normal item": iname.roast_beef}, + lname.cw_drac_sw: {"code": 0x154, "offset": 0x10C80F, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold}, + lname.cw_drac_slab1: {"code": 0x232, "offset": 0x10C859, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab2: {"code": 0x233, "offset": 0x10C85B, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab3: {"code": 0x234, "offset": 0x10C85D, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab4: {"code": 0x235, "offset": 0x10C85F, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab5: {"code": 0x236, "offset": 0x10C861, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + # Villa + lname.villafy_outer_gate_l: {"code": 0x133, "offset": 0x10C87F, "normal item": iname.red_jewel_l}, + lname.villafy_outer_gate_r: {"code": 0x132, "offset": 0x10C887, "normal item": iname.red_jewel_l}, + lname.villafy_dog_platform: {"code": 0x134, "offset": 0x10C89F, "normal item": iname.red_jewel_l}, + lname.villafy_inner_gate: {"code": 0x138, "offset": 0xBFC8D7, "normal item": iname.roast_beef}, + lname.villafy_gate_marker: {"code": 0x131, "offset": 0x10C8A7, "normal item": iname.powerup, + "hard item": iname.one_hundred_gold}, + lname.villafy_villa_marker: {"code": 0x13E, "offset": 0x10C897, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.villafy_tombstone: {"code": 0x12F, "offset": 0x8099CC, "normal item": iname.moon_card, + "type": "inv"}, + lname.villafy_fountain_fl: {"code": 0x139, "offset": 0xBFC8CF, "normal item": iname.five_hundred_gold}, + lname.villafy_fountain_fr: {"code": 0x130, "offset": 0x80997D, "normal item": iname.purifying}, + lname.villafy_fountain_ml: {"code": 0x13A, "offset": 0x809956, "normal item": iname.sun_card}, + lname.villafy_fountain_mr: {"code": 0x13D, "offset": 0x80992D, "normal item": iname.moon_card}, + lname.villafy_fountain_rl: {"code": 0x13B, "offset": 0xBFC8D3, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold}, + lname.villafy_fountain_rr: {"code": 0x13C, "offset": 0x80993C, "normal item": iname.five_hundred_gold}, + lname.villafo_front_r: {"code": 0x3D, "offset": 0x10C8E7, "normal item": iname.red_jewel_l, + "hard item": iname.five_hundred_gold}, + lname.villafo_front_l: {"code": 0x3B, "offset": 0x10C8DF, "normal item": iname.red_jewel_s}, + lname.villafo_mid_l: {"code": 0x3C, "offset": 0x10C8D7, "normal item": iname.red_jewel_s}, + lname.villafo_mid_r: {"code": 0xE5, "offset": 0x10C8CF, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.villafo_rear_r: {"code": 0x38, "offset": 0x10C8C7, "normal item": iname.red_jewel_s}, + lname.villafo_rear_l: {"code": 0x39, "offset": 0x10C8BF, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.villafo_pot_r: {"code": 0x2E, "offset": 0x10C8AF, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.villafo_pot_l: {"code": 0x2F, "offset": 0x10C8B7, "normal item": iname.red_jewel_s}, + lname.villafo_sofa: {"code": 0x2D, "offset": 0x81F07C, "normal item": iname.purifying, + "type": "inv"}, + lname.villafo_chandelier1: {"code": 0x27D, "offset": 0x10C8F5, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.villafo_chandelier2: {"code": 0x27E, "offset": 0x10C8F7, "normal item": iname.purifying, + "add conds": ["3hb"]}, + lname.villafo_chandelier3: {"code": 0x27F, "offset": 0x10C8F9, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.villafo_chandelier4: {"code": 0x280, "offset": 0x10C8FB, "normal item": iname.cure_ampoule, + "add conds": ["3hb"]}, + lname.villafo_chandelier5: {"code": 0x281, "offset": 0x10C8FD, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.villala_hallway_stairs: {"code": 0x34, "offset": 0x10C927, "normal item": iname.red_jewel_l}, + lname.villala_hallway_l: {"code": 0x40, "offset": 0xBFC903, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.villala_hallway_r: {"code": 0x4F, "offset": 0xBFC8F7, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.villala_bedroom_chairs: {"code": 0x33, "offset": 0x83A588, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold}, + lname.villala_bedroom_bed: {"code": 0x32, "offset": 0xBFC95B, "normal item": iname.red_jewel_l, + "hard item": iname.three_hundred_gold}, + lname.villala_vincent: {"code": 0x23, "offset": 0xBFE42F, "normal item": iname.archives_key, + "type": "npc"}, + lname.villala_slivingroom_table: {"code": 0x2B, "offset": 0xBFC96B, "normal item": iname.five_hundred_gold, + "type": "inv"}, + lname.villala_slivingroom_mirror: {"code": 0x49, "offset": 0x83A5D9, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.villala_diningroom_roses: {"code": 0x2A, "offset": 0xBFC90B, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold, "type": "inv"}, + lname.villala_llivingroom_pot_r: {"code": 0x26, "offset": 0x10C90F, "normal item": iname.storeroom_key}, + lname.villala_llivingroom_pot_l: {"code": 0x25, "offset": 0x10C917, "normal item": iname.roast_chicken}, + lname.villala_llivingroom_painting: {"code": 0x2C, "offset": 0xBFC907, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "type": "inv"}, + lname.villala_llivingroom_light: {"code": 0x28, "offset": 0x10C91F, "normal item": iname.purifying}, + lname.villala_llivingroom_lion: {"code": 0x30, "offset": 0x83A610, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold, "type": "inv"}, + lname.villala_exit_knight: {"code": 0x27, "offset": 0xBFC967, "normal item": iname.purifying, + "type": "inv"}, + lname.villala_storeroom_l: {"code": 0x36, "offset": 0xBFC95F, "normal item": iname.roast_beef}, + lname.villala_storeroom_r: {"code": 0x37, "offset": 0xBFC8FF, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.villala_storeroom_s: {"code": 0x31, "offset": 0xBFC963, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "type": "inv"}, + lname.villala_archives_entrance: {"code": 0x48, "offset": 0x83A5E5, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.villala_archives_table: {"code": 0x29, "offset": 0xBFC90F, "normal item": iname.purifying, + "type": "inv"}, + lname.villala_archives_rear: {"code": 0x24, "offset": 0x83A5B1, "normal item": iname.garden_key}, + lname.villam_malus_torch: {"code": 0x173, "offset": 0x10C967, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_malus_bush: {"code": 0x16C, "offset": 0x850FEC, "normal item": iname.roast_chicken, + "type": "inv", "countdown": 13}, + lname.villam_fplatform: {"code": 0x16B, "offset": 0x10C987, "normal item": iname.knife, + "add conds": ["sub"], "countdown": 13}, + lname.villam_frankieturf_l: {"code": 0x177, "offset": 0x10C947, "normal item": iname.three_hundred_gold, + "countdown": 13}, + lname.villam_frankieturf_r: {"code": 0x16A, "offset": 0x10C98F, "normal item": iname.holy_water, + "add conds": ["sub"], "countdown": 13}, + lname.villam_frankieturf_ru: {"code": 0x16E, "offset": 0x10C9A7, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_fgarden_f: {"code": 0x172, "offset": 0x10C96F, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_fgarden_mf: {"code": 0x171, "offset": 0x10C977, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_fgarden_mr: {"code": 0x174, "offset": 0x10C95F, "normal item": iname.roast_chicken, + "countdown": 13}, + lname.villam_fgarden_r: {"code": 0x170, "offset": 0x10C97F, "normal item": iname.red_jewel_l, + "countdown": 13}, + lname.villam_rplatform: {"code": 0x169, "offset": 0x10C997, "normal item": iname.axe, + "add conds": ["sub"], "countdown": 13}, + lname.villam_rplatform_de: {"code": 0x176, "offset": 0x10C94F, "normal item": iname.five_hundred_gold, + "countdown": 13}, + lname.villam_exit_de: {"code": 0x175, "offset": 0x10C957, "normal item": iname.three_hundred_gold, + "countdown": 13}, + lname.villam_serv_path: {"code": 0x17A, "offset": 0x10C92F, "normal item": iname.copper_key, + "countdown": 13}, + lname.villafo_serv_ent: {"code": 0x3E, "offset": 0x10C8EF, "normal item": iname.roast_chicken}, + lname.villam_crypt_ent: {"code": 0x178, "offset": 0x10C93F, "normal item": iname.purifying, + "countdown": 13}, + lname.villam_crypt_upstream: {"code": 0x179, "offset": 0x10C937, "normal item": iname.roast_beef, + "countdown": 13}, + lname.villac_ent_l: {"code": 0xC9, "offset": 0x10CF4B, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villac_ent_r: {"code": 0xC0, "offset": 0x10CF63, "normal item": iname.five_hundred_gold, + "countdown": 13}, + lname.villac_wall_l: {"code": 0xC2, "offset": 0x10CF6B, "normal item": iname.roast_chicken, + "countdown": 13}, + lname.villac_wall_r: {"code": 0xC1, "offset": 0x10CF5B, "normal item": iname.red_jewel_l, + "countdown": 13}, + lname.villac_coffin_l: {"code": 0xD8, "offset": 0x10CF73, "normal item": iname.knife, + "add conds": ["sub"], "countdown": 13}, + lname.villac_coffin_r: {"code": 0xC8, "offset": 0x10CF53, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villa_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.villa_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + # Tunnel + lname.tunnel_landing: {"code": 0x197, "offset": 0x10C9AF, "normal item": iname.red_jewel_l, + "hard item": iname.one_hundred_gold}, + lname.tunnel_landing_rc: {"code": 0x196, "offset": 0x10C9B7, "normal item": iname.red_jewel_s, + "hard item": iname.one_hundred_gold}, + lname.tunnel_stone_alcove_r: {"code": 0xE1, "offset": 0x10CA57, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.tunnel_stone_alcove_l: {"code": 0x187, "offset": 0x10CA9F, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken}, + lname.tunnel_twin_arrows: {"code": 0x195, "offset": 0xBFC993, "normal item": iname.cure_ampoule, + "type": "inv"}, + lname.tunnel_arrows_rock1: {"code": 0x238, "offset": 0x10CABD, "normal item": iname.purifying, + "add conds": ["3hb"]}, + lname.tunnel_arrows_rock2: {"code": 0x239, "offset": 0x10CABF, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tunnel_arrows_rock3: {"code": 0x23A, "offset": 0x10CAC1, "normal item": iname.cure_ampoule, + "add conds": ["3hb"]}, + lname.tunnel_arrows_rock4: {"code": 0x23B, "offset": 0x10CAC3, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tunnel_arrows_rock5: {"code": 0x23C, "offset": 0x10CAC5, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tunnel_lonesome_bucket: {"code": 0x189, "offset": 0xBFC99B, "normal item": iname.cure_ampoule, + "type": "inv"}, + lname.tunnel_lbucket_mdoor_l: {"code": 0x198, "offset": 0x10CA67, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.tunnel_lbucket_quag: {"code": 0x191, "offset": 0x10C9DF, "normal item": iname.red_jewel_l}, + lname.tunnel_bucket_quag_rock1: {"code": 0x23E, "offset": 0x10CAC9, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.tunnel_bucket_quag_rock2: {"code": 0x23F, "offset": 0x10CACB, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.tunnel_bucket_quag_rock3: {"code": 0x240, "offset": 0x10CACD, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.tunnel_lbucket_albert: {"code": 0x190, "offset": 0x10C9E7, "normal item": iname.red_jewel_s}, + lname.tunnel_albert_camp: {"code": 0x192, "offset": 0x10C9D7, "normal item": iname.red_jewel_s}, + lname.tunnel_albert_quag: {"code": 0x193, "offset": 0x10C9CF, "normal item": iname.red_jewel_l}, + lname.tunnel_gondola_rc_sdoor_l: {"code": 0x53, "offset": 0x10CA5F, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.tunnel_gondola_rc_sdoor_m: {"code": 0x19E, "offset": 0x10CAA7, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.tunnel_gondola_rc_sdoor_r: {"code": 0x188, "offset": 0x10CA27, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.tunnel_gondola_rc: {"code": 0x19C, "offset": 0x10CAB7, "normal item": iname.powerup}, + lname.tunnel_rgondola_station: {"code": 0x194, "offset": 0x10C9C7, "normal item": iname.red_jewel_s}, + lname.tunnel_gondola_transfer: {"code": 0x186, "offset": 0x10CA2F, "normal item": iname.five_hundred_gold}, + lname.tunnel_corpse_bucket_quag: {"code": 0x18E, "offset": 0x10C9F7, "normal item": iname.red_jewel_s}, + lname.tunnel_corpse_bucket_mdoor_l: {"code": 0x52, "offset": 0x10CA6F, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.tunnel_corpse_bucket_mdoor_r: {"code": 0x185, "offset": 0x10CA37, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold}, + lname.tunnel_shovel_quag_start: {"code": 0x18D, "offset": 0x10C9FF, "normal item": iname.red_jewel_l}, + lname.tunnel_exit_quag_start: {"code": 0x18C, "offset": 0x10CA07, "normal item": iname.red_jewel_l}, + lname.tunnel_shovel_quag_end: {"code": 0x18B, "offset": 0x10CA0F, "normal item": iname.red_jewel_l}, + lname.tunnel_exit_quag_end: {"code": 0x184, "offset": 0x10CA3F, "normal item": iname.five_hundred_gold}, + lname.tunnel_shovel: {"code": 0x18F, "offset": 0x86D8FC, "normal item": iname.roast_beef, + "type": "inv"}, + lname.tunnel_shovel_save: {"code": 0x18A, "offset": 0x10CA17, "normal item": iname.red_jewel_l}, + lname.tunnel_shovel_mdoor_l: {"code": 0x183, "offset": 0x10CA47, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold}, + lname.tunnel_shovel_mdoor_r: {"code": 0x51, "offset": 0x10CA77, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.tunnel_shovel_sdoor_l: {"code": 0x182, "offset": 0x10CA4F, "normal item": iname.moon_card}, + lname.tunnel_shovel_sdoor_m: {"code": 0x19D, "offset": 0x10CAAF, "normal item": iname.roast_chicken}, + lname.tunnel_shovel_sdoor_r: {"code": 0x50, "offset": 0x10CA7F, "normal item": iname.cross, + "add conds": ["sub"]}, + # Underground Waterway + lname.uw_near_ent: {"code": 0x4C, "offset": 0x10CB03, "normal item": iname.three_hundred_gold}, + lname.uw_across_ent: {"code": 0x4E, "offset": 0x10CAF3, "normal item": iname.five_hundred_gold}, + lname.uw_first_ledge1: {"code": 0x242, "offset": 0x10CB39, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.uw_first_ledge2: {"code": 0x243, "offset": 0x10CB3B, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.uw_first_ledge3: {"code": 0x244, "offset": 0x10CB3D, "normal item": iname.purifying, + "hard item": iname.five_hundred_gold, "add conds": ["3hb"]}, + lname.uw_first_ledge4: {"code": 0x245, "offset": 0x10CB3F, "normal item": iname.cure_ampoule, + "hard item": iname.five_hundred_gold, "add conds": ["3hb"]}, + lname.uw_first_ledge5: {"code": 0x246, "offset": 0x10CB41, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.uw_first_ledge6: {"code": 0x247, "offset": 0x10CB43, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.uw_poison_parkour: {"code": 0x4D, "offset": 0x10CAFB, "normal item": iname.cure_ampoule}, + lname.uw_boss: {"event": iname.trophy, "add conds": ["boss"]}, + lname.uw_waterfall_alcove: {"code": 0x57, "offset": 0x10CB23, "normal item": iname.five_hundred_gold}, + lname.uw_carrie1: {"code": 0x4B, "offset": 0x10CB0B, "normal item": iname.moon_card, + "hard item": iname.five_hundred_gold, "add conds": ["carrie"]}, + lname.uw_carrie2: {"code": 0x4A, "offset": 0x10CB13, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold, "add conds": ["carrie"]}, + lname.uw_bricks_save: {"code": 0x5A, "offset": 0x10CB33, "normal item": iname.powerup, + "hard item": iname.one_hundred_gold}, + lname.uw_above_skel_ledge: {"code": 0x56, "offset": 0x10CB2B, "normal item": iname.roast_chicken}, + lname.uw_in_skel_ledge1: {"code": 0x249, "offset": 0x10CB45, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.uw_in_skel_ledge2: {"code": 0x24A, "offset": 0x10CB47, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.uw_in_skel_ledge3: {"code": 0x24B, "offset": 0x10CB49, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + # Castle Center + lname.ccb_skel_hallway_ent: {"code": 0x1AF, "offset": 0x10CB67, "normal item": iname.red_jewel_s}, + lname.ccb_skel_hallway_jun: {"code": 0x1A8, "offset": 0x10CBD7, "normal item": iname.powerup}, + lname.ccb_skel_hallway_tc: {"code": 0x1AE, "offset": 0x10CB6F, "normal item": iname.red_jewel_l}, + lname.ccb_skel_hallway_ba: {"code": 0x1B6, "offset": 0x10CBC7, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.ccb_behemoth_l_ff: {"code": 0x1AD, "offset": 0x10CB77, "normal item": iname.red_jewel_s}, + lname.ccb_behemoth_l_mf: {"code": 0x1B3, "offset": 0x10CBA7, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_l_mr: {"code": 0x1AC, "offset": 0x10CB7F, "normal item": iname.red_jewel_l}, + lname.ccb_behemoth_l_fr: {"code": 0x1B2, "offset": 0x10CBAF, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_r_ff: {"code": 0x1B1, "offset": 0x10CBB7, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_r_mf: {"code": 0x1AB, "offset": 0x10CB87, "normal item": iname.red_jewel_s}, + lname.ccb_behemoth_r_mr: {"code": 0x1B0, "offset": 0x10CBBF, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_r_fr: {"code": 0x1AA, "offset": 0x10CB8F, "normal item": iname.red_jewel_l}, + lname.ccb_behemoth_crate1: {"code": 0x24D, "offset": 0x10CBDD, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate2: {"code": 0x24E, "offset": 0x10CBDF, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate3: {"code": 0x24F, "offset": 0x10CBE1, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate4: {"code": 0x250, "offset": 0x10CBE3, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate5: {"code": 0x251, "offset": 0x10CBE5, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccelv_near_machine: {"code": 0x11A, "offset": 0x10CBF7, "normal item": iname.red_jewel_s}, + lname.ccelv_atop_machine: {"code": 0x118, "offset": 0x10CC17, "normal item": iname.powerup, + "hard item": iname.three_hundred_gold}, + lname.ccelv_stand1: {"code": 0x253, "offset": 0x10CC1D, "normal item": iname.roast_beef, + "add conds": ["3hb"]}, + lname.ccelv_stand2: {"code": 0x254, "offset": 0x10CC1F, "normal item": iname.roast_beef, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.ccelv_stand3: {"code": 0x255, "offset": 0x10CC21, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ccelv_pipes: {"code": 0x11B, "offset": 0x10CC07, "normal item": iname.one_hundred_gold}, + lname.ccelv_switch: {"code": 0x100, "offset": 0x10CC0F, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.ccelv_staircase: {"code": 0x119, "offset": 0x10CBFF, "normal item": iname.red_jewel_l, + "hard item": iname.five_hundred_gold}, + lname.ccff_redcarpet_knight: {"code": 0x10A, "offset": 0x8C44D9, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "type": "inv"}, + lname.ccff_gears_side: {"code": 0x10F, "offset": 0x10CC33, "normal item": iname.red_jewel_s}, + lname.ccff_gears_mid: {"code": 0x10E, "offset": 0x10CC3B, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold}, + lname.ccff_gears_corner: {"code": 0x10D, "offset": 0x10CC43, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold}, + lname.ccff_lizard_knight: {"code": 0x109, "offset": 0x8C44E7, "normal item": iname.roast_chicken, + "hard item": iname.three_hundred_gold, "type": "inv"}, + lname.ccff_lizard_near_knight: {"code": 0x101, "offset": 0x10CC5B, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.ccff_lizard_pit: {"code": 0x10C, "offset": 0x10CC4B, "normal item": iname.sun_card, + "hard item": iname.five_hundred_gold}, + lname.ccff_lizard_corner: {"code": 0x10B, "offset": 0x10CC53, "normal item": iname.moon_card, + "hard item": iname.five_hundred_gold}, + lname.ccff_lizard_locker_nfr: {"code": 0x104, "offset": 0x8C450A, "normal item": iname.red_jewel_l, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_nmr: {"code": 0x105, "offset": 0xBFC9C3, "normal item": iname.five_hundred_gold, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_nml: {"code": 0x106, "offset": 0xBFC9C7, "normal item": iname.red_jewel_l, + "hard item": iname.cure_ampoule, "add conds": ["liz"]}, + lname.ccff_lizard_locker_nfl: {"code": 0x107, "offset": 0xBFCA07, "normal item": iname.powerup, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_fl: {"code": 0x102, "offset": 0xBFCA03, "normal item": iname.five_hundred_gold, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_fr: {"code": 0x103, "offset": 0x8C44F5, "normal item": iname.sun_card, + "hard item": iname.three_hundred_gold, "add conds": ["liz"]}, + lname.ccff_lizard_slab1: {"code": 0x257, "offset": 0x10CC61, "normal item": iname.purifying, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.ccff_lizard_slab2: {"code": 0x258, "offset": 0x10CC63, "normal item": iname.purifying, + "hard item": iname.powerup, "add conds": ["3hb"]}, + lname.ccff_lizard_slab3: {"code": 0x259, "offset": 0x10CC65, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ccff_lizard_slab4: {"code": 0x25A, "offset": 0x10CC67, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ccb_mandrag_shelf_l: {"code": 0x1A0, "offset": 0xBFCBB3, "normal item": iname.mandragora}, + lname.ccb_mandrag_shelf_r: {"code": 0x1A1, "offset": 0xBFCBAF, "normal item": iname.mandragora}, + lname.ccb_torture_rack: {"code": 0x1A9, "offset": 0x8985E5, "normal item": iname.purifying, + "type": "inv"}, + lname.ccb_torture_rafters: {"code": 0x1A2, "offset": 0x8985D6, "normal item": iname.roast_beef}, + lname.cc_behind_the_seal: {"event": iname.crystal, "add conds": ["crystal"]}, + lname.cc_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.cc_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + lname.ccll_brokenstairs_floor: {"code": 0x7B, "offset": 0x10CC8F, "normal item": iname.red_jewel_l, + "countdown": 14}, + lname.ccll_brokenstairs_knight: {"code": 0x74, "offset": 0x8DF782, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "type": "inv", "countdown": 14}, + lname.ccll_brokenstairs_save: {"code": 0x7C, "offset": 0x10CC87, "normal item": iname.red_jewel_l, + "countdown": 14}, + lname.ccll_glassknight_l: {"code": 0x7A, "offset": 0x10CC97, "normal item": iname.red_jewel_s, + "hard item": iname.five_hundred_gold, "countdown": 14}, + lname.ccll_glassknight_r: {"code": 0x7E, "offset": 0x10CC77, "normal item": iname.red_jewel_s, + "hard item": iname.five_hundred_gold, "countdown": 14}, + lname.ccll_butlers_door: {"code": 0x7D, "offset": 0x10CC7F, "normal item": iname.red_jewel_s, + "countdown": 14}, + lname.ccll_butlers_side: {"code": 0x79, "offset": 0x10CC9F, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccll_cwhall_butlerflames_past: {"code": 0x78, "offset": 0x10CCA7, "normal item": iname.cure_ampoule, + "hard item": iname.red_jewel_l, "countdown": 14}, + lname.ccll_cwhall_flamethrower: {"code": 0x73, "offset": 0x8DF580, "normal item": iname.five_hundred_gold, + "type": "inv", "countdown": 14}, + lname.ccll_cwhall_cwflames: {"code": 0x77, "offset": 0x10CCAF, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_l, "countdown": 14}, + lname.ccll_heinrich: {"code": 0x69, "offset": 0xBFE443, "normal item": iname.chamber_key, + "type": "npc", "countdown": 14}, + lname.ccia_nitro_crates: {"code": 0x66, "offset": 0x90FCE9, "normal item": iname.healing_kit, + "hard item": iname.one_hundred_gold, "type": "inv", "countdown": 14}, + lname.ccia_nitro_shelf_h: {"code": 0x55, "offset": 0xBFCC03, "normal item": iname.magical_nitro, + "countdown": 14}, + lname.ccia_stairs_knight: {"code": 0x61, "offset": 0x90FE5C, "normal item": iname.five_hundred_gold, + "type": "inv", "countdown": 14}, + lname.ccia_maids_vase: {"code": 0x63, "offset": 0x90FF1D, "normal item": iname.red_jewel_l, + "type": "inv", "countdown": 14}, + lname.ccia_maids_outer: {"code": 0x6B, "offset": 0x10CCFF, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold, "countdown": 14}, + lname.ccia_maids_inner: {"code": 0x6A, "offset": 0x10CD07, "normal item": iname.cure_ampoule, + "hard item": iname.three_hundred_gold, "countdown": 14}, + lname.ccia_inventions_maids: {"code": 0x6C, "offset": 0x10CCE7, "normal item": iname.moon_card, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccia_inventions_crusher: {"code": 0x6E, "offset": 0x10CCDF, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccia_inventions_famicart: {"code": 0x64, "offset": 0x90FBB3, "normal item": iname.five_hundred_gold, + "type": "inv", "countdown": 14}, + lname.ccia_inventions_zeppelin: {"code": 0x6D, "offset": 0x90FBC0, "normal item": iname.roast_beef, + "countdown": 14}, + lname.ccia_inventions_round: {"code": 0x65, "offset": 0x90FBA7, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold, "type": "inv", "countdown": 14}, + lname.ccia_nitrohall_flamethrower: {"code": 0x62, "offset": 0x90FCDA, "normal item": iname.red_jewel_l, + "type": "inv", "countdown": 14}, + lname.ccia_nitrohall_torch: {"code": 0x6F, "offset": 0x10CCD7, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_s, "countdown": 14}, + lname.ccia_nitro_shelf_i: {"code": 0x60, "offset": 0xBFCBFF, "normal item": iname.magical_nitro, + "countdown": 14}, + lname.ccll_cwhall_wall: {"code": 0x76, "offset": 0x10CCB7, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccl_bookcase: {"code": 0x166, "offset": 0x8F1197, "normal item": iname.sun_card, + "countdown": 14}, + # Duel Tower + lname.dt_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.dt_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + lname.dt_ibridge_l: {"code": 0x81, "offset": 0x10CE8B, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold}, + lname.dt_ibridge_r: {"code": 0x80, "offset": 0x10CE93, "normal item": iname.powerup}, + lname.dt_stones_start: {"code": 0x83, "offset": 0x10CE73, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.dt_stones_end: {"code": 0x97, "offset": 0x10CE83, "normal item": iname.knife, "add conds": ["sub"]}, + lname.dt_werebull_arena: {"code": 0x82, "offset": 0x10CE7B, "normal item": iname.roast_beef}, + lname.dt_boss_three: {"event": iname.trophy, "add conds": ["boss"]}, + lname.dt_boss_four: {"event": iname.trophy, "add conds": ["boss"]}, + # Tower of Execution + lname.toe_ledge1: {"code": 0x25C, "offset": 0x10CD5D, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.toe_ledge2: {"code": 0x25D, "offset": 0x10CD5F, "normal item": iname.purifying, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.toe_ledge3: {"code": 0x25E, "offset": 0x10CD61, "normal item": iname.five_hundred_gold, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.toe_ledge4: {"code": 0x25F, "offset": 0x10CD63, "normal item": iname.cure_ampoule, + "hard item": iname.five_hundred_gold, "add conds": ["3hb"]}, + lname.toe_ledge5: {"code": 0x260, "offset": 0x10CD65, "normal item": iname.holy_water, + "add conds": ["3hb", "sub"]}, + lname.toe_midsavespikes_r: {"code": 0x9C, "offset": 0x10CD1F, "normal item": iname.five_hundred_gold}, + lname.toe_midsavespikes_l: {"code": 0x9B, "offset": 0x10CD27, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.toe_elec_grate: {"code": 0x99, "offset": 0x10CD17, "normal item": iname.execution_key}, + lname.toe_ibridge: {"code": 0x98, "offset": 0x10CD47, "normal item": iname.one_hundred_gold}, + lname.toe_top: {"code": 0x9D, "offset": 0x10CD4F, "normal item": iname.red_jewel_l}, + lname.toe_keygate_l: {"code": 0x9A, "offset": 0x10CD37, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.toe_keygate_r: {"code": 0x9E, "offset": 0x10CD3F, "normal item": iname.cross, "add conds": ["sub"]}, + # Tower of Science + lname.tosci_elevator: {"code": 0x1FC, "offset": 0x10CE0B, "normal item": iname.three_hundred_gold}, + lname.tosci_plain_sr: {"code": 0x1FF, "offset": 0x10CDF3, "normal item": iname.science_key1}, + lname.tosci_stairs_sr: {"code": 0x1FB, "offset": 0x10CE13, "normal item": iname.three_hundred_gold}, + lname.tosci_three_door_hall: {"code": 0x1FE, "offset": 0x10CDFB, "normal item": iname.science_key2}, + lname.tosci_ibridge_t: {"code": 0x1F3, "offset": 0x10CE3B, "normal item": iname.roast_beef, + "hard item": iname.red_jewel_l}, + lname.tosci_ibridge_b1: {"code": 0x262, "offset": 0x10CE59, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b2: {"code": 0x263, "offset": 0x10CE5B, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b3: {"code": 0x264, "offset": 0x10CE5D, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b4: {"code": 0x265, "offset": 0x10CE5F, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b5: {"code": 0x266, "offset": 0x10CE61, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b6: {"code": 0x267, "offset": 0x10CE63, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tosci_conveyor_sr: {"code": 0x1F7, "offset": 0x10CE33, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.tosci_exit: {"code": 0x1FD, "offset": 0x10CE03, "normal item": iname.science_key3}, + lname.tosci_key3_r: {"code": 0x1FA, "offset": 0x10CE1B, "normal item": iname.five_hundred_gold}, + lname.tosci_key3_m: {"code": 0x1F2, "offset": 0x10CE2B, "normal item": iname.cross, "add conds": ["sub"]}, + lname.tosci_key3_l: {"code": 0x1F9, "offset": 0x10CE23, "normal item": iname.five_hundred_gold}, + # Tower of Sorcery + lname.tosor_stained_tower: {"code": 0x96, "offset": 0x10CDB3, "normal item": iname.red_jewel_l}, + lname.tosor_savepoint: {"code": 0x95, "offset": 0x10CDBB, "normal item": iname.red_jewel_l}, + lname.tosor_trickshot: {"code": 0x92, "offset": 0x10CDD3, "normal item": iname.roast_beef}, + lname.tosor_yellow_bubble: {"code": 0x91, "offset": 0x10CDDB, "normal item": iname.five_hundred_gold}, + lname.tosor_blue_platforms: {"code": 0x94, "offset": 0x10CDC3, "normal item": iname.red_jewel_s}, + lname.tosor_side_isle: {"code": 0x93, "offset": 0x10CDCB, "normal item": iname.red_jewel_s}, + lname.tosor_ibridge: {"code": 0x90, "offset": 0x10CDE3, "normal item": iname.three_hundred_gold}, + # Room of Clocks + lname.roc_ent_l: {"code": 0xC6, "offset": 0x10CF7B, "normal item": iname.roast_beef, + "hard item": iname.red_jewel_l}, + lname.roc_ent_r: {"code": 0xC3, "offset": 0x10CFBB, "normal item": iname.powerup, + "hard item": iname.five_hundred_gold}, + lname.roc_elev_r: {"code": 0xD4, "offset": 0x10CF93, "normal item": iname.holy_water, "add conds": ["sub"]}, + lname.roc_elev_l: {"code": 0xD5, "offset": 0x10CF8B, "normal item": iname.axe, "add conds": ["sub"]}, + lname.roc_cont_r: {"code": 0xC5, "offset": 0x10CFB3, "normal item": iname.powerup, + "hard item": iname.one_hundred_gold}, + lname.roc_cont_l: {"code": 0xDF, "offset": 0x10CFA3, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.roc_exit: {"code": 0xDC, "offset": 0x10CF9B, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.roc_boss: {"event": iname.trophy, "add conds": ["boss"]}, + # Clock Tower + lname.ct_gearclimb_battery_slab1: {"code": 0x269, "offset": 0x10CEF9, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.ct_gearclimb_battery_slab2: {"code": 0x26A, "offset": 0x10CEFB, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.ct_gearclimb_battery_slab3: {"code": 0x26B, "offset": 0x10CEFD, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.ct_gearclimb_corner: {"code": 0xA7, "offset": 0x10CEB3, "normal item": iname.red_jewel_s}, + lname.ct_gearclimb_side: {"code": 0xAD, "offset": 0x10CEC3, "normal item": iname.clocktower_key1}, + lname.ct_gearclimb_door_slab1: {"code": 0x26D, "offset": 0x10CF01, "normal item": iname.roast_beef, + "add conds": ["3hb"]}, + lname.ct_gearclimb_door_slab2: {"code": 0x26E, "offset": 0x10CF03, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ct_gearclimb_door_slab3: {"code": 0x26F, "offset": 0x10CF05, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ct_bp_chasm_fl: {"code": 0xA5, "offset": 0x99BC4D, "normal item": iname.five_hundred_gold}, + lname.ct_bp_chasm_fr: {"code": 0xA6, "offset": 0x99BC3E, "normal item": iname.red_jewel_l}, + lname.ct_bp_chasm_rl: {"code": 0xA4, "offset": 0x99BC5A, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.ct_bp_chasm_k: {"code": 0xAC, "offset": 0x99BC30, "normal item": iname.clocktower_key2}, + lname.ct_finalroom_door_slab1: {"code": 0x271, "offset": 0x10CEF5, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_door_slab2: {"code": 0x272, "offset": 0x10CEF7, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_fl: {"code": 0xB3, "offset": 0x10CED3, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.ct_finalroom_fr: {"code": 0xB4, "offset": 0x10CECB, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.ct_finalroom_rl: {"code": 0xB2, "offset": 0x10CEE3, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.ct_finalroom_rr: {"code": 0xB0, "offset": 0x10CEDB, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.ct_finalroom_platform: {"code": 0xAB, "offset": 0x10CEBB, "normal item": iname.clocktower_key3}, + lname.ct_finalroom_renon_slab1: {"code": 0x274, "offset": 0x10CF09, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab2: {"code": 0x275, "offset": 0x10CF0B, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab3: {"code": 0x276, "offset": 0x10CF0D, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab4: {"code": 0x277, "offset": 0x10CF0F, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab5: {"code": 0x278, "offset": 0x10CF11, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab6: {"code": 0x279, "offset": 0x10CF13, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab7: {"code": 0x27A, "offset": 0x10CF15, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab8: {"code": 0x27B, "offset": 0x10CF17, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + # Castle Keep + lname.ck_boss_one: {"event": iname.trophy, "add conds": ["boss", "renon"]}, + lname.ck_boss_two: {"event": iname.trophy, "add conds": ["boss", "vincent"]}, + lname.ck_flame_l: {"code": 0xAF, "offset": 0x9778C8, "normal item": iname.healing_kit, "type": "inv"}, + lname.ck_flame_r: {"code": 0xAE, "offset": 0xBFCA67, "normal item": iname.healing_kit, "type": "inv"}, + lname.ck_behind_drac: {"code": 0xBF, "offset": 0x10CE9B, "normal item": iname.red_jewel_l}, + lname.ck_cube: {"code": 0xB5, "offset": 0x10CEA3, "normal item": iname.healing_kit}, + lname.renon1: {"code": 0x1C8, "offset": 0xBFD8E5, "normal item": iname.roast_chicken, "type": "shop"}, + lname.renon2: {"code": 0x1C9, "offset": 0xBFD8E7, "normal item": iname.roast_beef, "type": "shop"}, + lname.renon3: {"code": 0x1CA, "offset": 0xBFD8E9, "normal item": iname.healing_kit, "type": "shop"}, + lname.renon4: {"code": 0x1CB, "offset": 0xBFD8EB, "normal item": iname.purifying, "type": "shop"}, + lname.renon5: {"code": 0x1CC, "offset": 0xBFD8ED, "normal item": iname.cure_ampoule, "type": "shop"}, + lname.renon6: {"code": 0x1CD, "offset": 0xBFD907, "normal item": iname.sun_card, "type": "shop"}, + lname.renon7: {"code": 0x1CE, "offset": 0xBFD909, "normal item": iname.moon_card, "type": "shop"}, + lname.the_end: {"event": iname.victory}, +} + + +add_conds = {"carrie": ("carrie_logic", True, True), + "liz": ("lizard_locker_items", True, True), + "sub": ("sub_weapon_shuffle", SubWeaponShuffle.option_anywhere, True), + "3hb": ("multi_hit_breakables", True, True), + "empty": ("empty_breakables", True, True), + "shop": ("shopsanity", True, True), + "crystal": ("draculas_condition", DraculasCondition.option_crystal, True), + "boss": ("draculas_condition", DraculasCondition.option_bosses, True), + "renon": ("renon_fight_condition", RenonFightCondition.option_never, False), + "vincent": ("vincent_fight_condition", VincentFightCondition.option_never, False)} + + +def get_location_info(location: str, info: str) -> Union[int, str, List[str], None]: + return location_info[location].get(info, None) + + +def get_location_names_to_ids() -> Dict[str, int]: + return {name: get_location_info(name, "code")+base_id for name in location_info if get_location_info(name, "code") + is not None} + + +def verify_locations(options: CV64Options, locations: List[str]) -> Tuple[Dict[str, Optional[int]], Dict[str, str]]: + + verified_locations = {} + events = {} + + for loc in locations: + loc_add_conds = get_location_info(loc, "add conds") + loc_code = get_location_info(loc, "code") + + # Check any options that might be associated with the Location before adding it. + add_it = True + if isinstance(loc_add_conds, list): + for cond in loc_add_conds: + if not ((getattr(options, add_conds[cond][0]).value == add_conds[cond][1]) == add_conds[cond][2]): + add_it = False + + if not add_it: + continue + + # Add the location to the verified Locations if the above check passes. + # If we are looking at an event Location, add its associated event Item to the events' dict. + # Otherwise, add the base_id to the Location's code. + if loc_code is None: + events[loc] = get_location_info(loc, "event") + else: + loc_code += base_id + verified_locations.update({loc: loc_code}) + + return verified_locations, events diff --git a/worlds/cv64/lzkn64.py b/worlds/cv64/lzkn64.py new file mode 100644 index 000000000000..9a94cebbb4eb --- /dev/null +++ b/worlds/cv64/lzkn64.py @@ -0,0 +1,266 @@ +# ************************************************************** +# * LZKN64 Compression and Decompression Utility * +# * Original repo at https://github.com/Fluvian/lzkn64, * +# * converted from C to Python with permission from Fluvian. * +# ************************************************************** + +TYPE_COMPRESS = 1 +TYPE_DECOMPRESS = 2 + +MODE_NONE = 0x7F +MODE_WINDOW_COPY = 0x00 +MODE_RAW_COPY = 0x80 +MODE_RLE_WRITE_A = 0xC0 +MODE_RLE_WRITE_B = 0xE0 +MODE_RLE_WRITE_C = 0xFF + +WINDOW_SIZE = 0x3FF +COPY_SIZE = 0x21 +RLE_SIZE = 0x101 + + +# Compresses the data in the buffer specified in the arguments. +def compress_buffer(file_buffer: bytearray) -> bytearray: + # Size of the buffer to compress + buffer_size = len(file_buffer) - 1 + + # Position of the current read location in the buffer. + buffer_position = 0 + + # Position of the current write location in the written buffer. + write_position = 4 + + # Allocate write_buffer with size of 0xFFFFFF (24-bit). + write_buffer = bytearray(0xFFFFFF) + + # Position in the input buffer of the last time one of the copy modes was used. + buffer_last_copy_position = 0 + + while buffer_position < buffer_size: + # Calculate maximum length we are able to copy without going out of bounds. + if COPY_SIZE < (buffer_size - 1) - buffer_position: + sliding_window_maximum_length = COPY_SIZE + else: + sliding_window_maximum_length = (buffer_size - 1) - buffer_position + + # Calculate how far we are able to look back without going behind the start of the uncompressed buffer. + if buffer_position - WINDOW_SIZE > 0: + sliding_window_maximum_offset = buffer_position - WINDOW_SIZE + else: + sliding_window_maximum_offset = 0 + + # Calculate maximum length the forwarding looking window is able to search. + if RLE_SIZE < (buffer_size - 1) - buffer_position: + forward_window_maximum_length = RLE_SIZE + else: + forward_window_maximum_length = (buffer_size - 1) - buffer_position + + sliding_window_match_position = -1 + sliding_window_match_size = 0 + + forward_window_match_value = 0 + forward_window_match_size = 0 + + # The current mode the compression algorithm prefers. (0x7F == None) + current_mode = MODE_NONE + + # The current submode the compression algorithm prefers. + current_submode = MODE_NONE + + # How many bytes will have to be copied in the raw copy command. + raw_copy_size = buffer_position - buffer_last_copy_position + + # How many bytes we still have to copy in RLE matches with more than 0x21 bytes. + rle_bytes_left = 0 + + """Go backwards in the buffer, is there a matching value? + If yes, search forward and check for more matching values in a loop. + If no, go further back and repeat.""" + for search_position in range(buffer_position - 1, sliding_window_maximum_offset - 1, -1): + matching_sequence_size = 0 + + while file_buffer[search_position + matching_sequence_size] == file_buffer[buffer_position + + matching_sequence_size]: + matching_sequence_size += 1 + + if matching_sequence_size >= sliding_window_maximum_length: + break + + # Once we find a match or a match that is bigger than the match before it, we save its position and length. + if matching_sequence_size > sliding_window_match_size: + sliding_window_match_position = search_position + sliding_window_match_size = matching_sequence_size + + """Look one step forward in the buffer, is there a matching value? + If yes, search further and check for a repeating value in a loop. + If no, continue to the rest of the function.""" + matching_sequence_value = file_buffer[buffer_position] + matching_sequence_size = 0 + + while file_buffer[buffer_position + matching_sequence_size] == matching_sequence_value: + matching_sequence_size += 1 + + if matching_sequence_size >= forward_window_maximum_length: + break + + # If we find a sequence of matching values, save them. + if matching_sequence_size >= 1: + forward_window_match_value = matching_sequence_value + forward_window_match_size = matching_sequence_size + + # Try to pick which mode works best with the current values. + if sliding_window_match_size >= 3: + current_mode = MODE_WINDOW_COPY + elif forward_window_match_size >= 3: + current_mode = MODE_RLE_WRITE_A + + if forward_window_match_value != 0x00 and forward_window_match_size <= COPY_SIZE: + current_submode = MODE_RLE_WRITE_A + elif forward_window_match_value != 0x00 and forward_window_match_size > COPY_SIZE: + current_submode = MODE_RLE_WRITE_A + rle_bytes_left = forward_window_match_size + elif forward_window_match_value == 0x00 and forward_window_match_size <= COPY_SIZE: + current_submode = MODE_RLE_WRITE_B + elif forward_window_match_value == 0x00 and forward_window_match_size > COPY_SIZE: + current_submode = MODE_RLE_WRITE_C + elif forward_window_match_size >= 2 and forward_window_match_value == 0x00: + current_mode = MODE_RLE_WRITE_A + current_submode = MODE_RLE_WRITE_B + + """Write a raw copy command when these following conditions are met: + The current mode is set and there are raw bytes available to be copied. + The raw byte length exceeds the maximum length that can be stored. + Raw bytes need to be written due to the proximity to the end of the buffer.""" + if (current_mode != MODE_NONE and raw_copy_size >= 1) or raw_copy_size >= 0x1F or \ + (buffer_position + 1) == buffer_size: + if buffer_position + 1 == buffer_size: + raw_copy_size = buffer_size - buffer_last_copy_position + + write_buffer[write_position] = MODE_RAW_COPY | raw_copy_size & 0x1F + write_position += 1 + + for written_bytes in range(raw_copy_size): + write_buffer[write_position] = file_buffer[buffer_last_copy_position] + write_position += 1 + buffer_last_copy_position += 1 + + if current_mode == MODE_WINDOW_COPY: + write_buffer[write_position] = MODE_WINDOW_COPY | ((sliding_window_match_size - 2) & 0x1F) << 2 | \ + (((buffer_position - sliding_window_match_position) & 0x300) >> 8) + write_position += 1 + write_buffer[write_position] = (buffer_position - sliding_window_match_position) & 0xFF + write_position += 1 + + buffer_position += sliding_window_match_size + buffer_last_copy_position = buffer_position + elif current_mode == MODE_RLE_WRITE_A: + if current_submode == MODE_RLE_WRITE_A: + if rle_bytes_left > 0: + while rle_bytes_left > 0: + # Dump raw bytes if we have less than two bytes left, not doing so would cause an underflow + # error. + if rle_bytes_left < 2: + write_buffer[write_position] = MODE_RAW_COPY | rle_bytes_left & 0x1F + write_position += 1 + + for writtenBytes in range(rle_bytes_left): + write_buffer[write_position] = forward_window_match_value & 0xFF + write_position += 1 + + rle_bytes_left = 0 + break + + if rle_bytes_left < COPY_SIZE: + write_buffer[write_position] = MODE_RLE_WRITE_A | (rle_bytes_left - 2) & 0x1F + write_position += 1 + else: + write_buffer[write_position] = MODE_RLE_WRITE_A | (COPY_SIZE - 2) & 0x1F + write_position += 1 + write_buffer[write_position] = forward_window_match_value & 0xFF + write_position += 1 + rle_bytes_left -= COPY_SIZE + else: + write_buffer[write_position] = MODE_RLE_WRITE_A | (forward_window_match_size - 2) & 0x1F + write_position += 1 + write_buffer[write_position] = forward_window_match_value & 0xFF + write_position += 1 + + elif current_submode == MODE_RLE_WRITE_B: + write_buffer[write_position] = MODE_RLE_WRITE_B | (forward_window_match_size - 2) & 0x1F + write_position += 1 + elif current_submode == MODE_RLE_WRITE_C: + write_buffer[write_position] = MODE_RLE_WRITE_C + write_position += 1 + write_buffer[write_position] = (forward_window_match_size - 2) & 0xFF + write_position += 1 + + buffer_position += forward_window_match_size + buffer_last_copy_position = buffer_position + else: + buffer_position += 1 + + # Write the compressed size. + write_buffer[1] = 0x00 + write_buffer[1] = write_position >> 16 & 0xFF + write_buffer[2] = write_position >> 8 & 0xFF + write_buffer[3] = write_position & 0xFF + + # Return the compressed write buffer. + return write_buffer[0:write_position] + + +# Decompresses the data in the buffer specified in the arguments. +def decompress_buffer(file_buffer: bytearray) -> bytearray: + # Position of the current read location in the buffer. + buffer_position = 4 + + # Position of the current write location in the written buffer. + write_position = 0 + + # Get compressed size. + compressed_size = (file_buffer[1] << 16) + (file_buffer[2] << 8) + file_buffer[3] - 1 + + # Allocate writeBuffer with size of 0xFFFFFF (24-bit). + write_buffer = bytearray(0xFFFFFF) + + while buffer_position < compressed_size: + mode_command = file_buffer[buffer_position] + buffer_position += 1 + + if MODE_WINDOW_COPY <= mode_command < MODE_RAW_COPY: + copy_length = (mode_command >> 2) + 2 + copy_offset = file_buffer[buffer_position] + (mode_command << 8) & 0x3FF + buffer_position += 1 + + for current_length in range(copy_length, 0, -1): + write_buffer[write_position] = write_buffer[write_position - copy_offset] + write_position += 1 + elif MODE_RAW_COPY <= mode_command < MODE_RLE_WRITE_A: + copy_length = mode_command & 0x1F + + for current_length in range(copy_length, 0, -1): + write_buffer[write_position] = file_buffer[buffer_position] + write_position += 1 + buffer_position += 1 + elif MODE_RLE_WRITE_A <= mode_command <= MODE_RLE_WRITE_C: + write_length = 0 + write_value = 0x00 + + if MODE_RLE_WRITE_A <= mode_command < MODE_RLE_WRITE_B: + write_length = (mode_command & 0x1F) + 2 + write_value = file_buffer[buffer_position] + buffer_position += 1 + elif MODE_RLE_WRITE_B <= mode_command < MODE_RLE_WRITE_C: + write_length = (mode_command & 0x1F) + 2 + elif mode_command == MODE_RLE_WRITE_C: + write_length = file_buffer[buffer_position] + 2 + buffer_position += 1 + + for current_length in range(write_length, 0, -1): + write_buffer[write_position] = write_value + write_position += 1 + + # Return the current position of the write buffer, essentially giving us the size of the write buffer. + while write_position % 16 != 0: + write_position += 1 + return write_buffer[0:write_position] diff --git a/worlds/cv64/options.py b/worlds/cv64/options.py new file mode 100644 index 000000000000..07e86347bda6 --- /dev/null +++ b/worlds/cv64/options.py @@ -0,0 +1,591 @@ +from dataclasses import dataclass +from Options import (OptionGroup, Choice, DefaultOnToggle, ItemsAccessibility, PerGameCommonOptions, Range, Toggle, + StartInventoryPool) + + +class CharacterStages(Choice): + """ + Whether to include Reinhardt-only stages, Carrie-only stages, or both with or without branching paths at the end of Villa and Castle Center. + """ + display_name = "Character Stages" + option_both = 0 + option_branchless_both = 1 + option_reinhardt_only = 2 + option_carrie_only = 3 + default = 0 + + +class StageShuffle(Toggle): + """ + Shuffles which stages appear in which stage slots. + Villa and Castle Center will never appear in any character stage slots if Character Stages is set to Both; they can only be somewhere on the main path. + Castle Keep will always be at the end of the line. + """ + display_name = "Stage Shuffle" + + +class StartingStage(Choice): + """ + Which stage to start at if Stage Shuffle is turned on. + """ + display_name = "Starting Stage" + option_forest_of_silence = 0 + option_castle_wall = 1 + option_villa = 2 + option_tunnel = 3 + option_underground_waterway = 4 + option_castle_center = 5 + option_duel_tower = 6 + option_tower_of_execution = 7 + option_tower_of_science = 8 + option_tower_of_sorcery = 9 + option_room_of_clocks = 10 + option_clock_tower = 11 + default = "random" + + +class WarpOrder(Choice): + """ + Arranges the warps in the warp menu in whichever stage order chosen, thereby changing the order they are unlocked in. + """ + display_name = "Warp Order" + option_seed_stage_order = 0 + option_vanilla_stage_order = 1 + option_randomized_order = 2 + default = 0 + + +class SubWeaponShuffle(Choice): + """ + Shuffles all sub-weapons in the game within each other in their own pool or in the main item pool. + """ + display_name = "Sub-weapon Shuffle" + option_off = 0 + option_own_pool = 1 + option_anywhere = 2 + default = 0 + + +class SpareKeys(Choice): + """ + Puts an additional copy of every non-Special key item in the pool for every key item that there is. + Chance gives each key item a 50% chance of having a duplicate instead of guaranteeing one for all of them. + """ + display_name = "Spare Keys" + option_off = 0 + option_on = 1 + option_chance = 2 + default = 0 + + +class HardItemPool(Toggle): + """ + Replaces some items in the item pool with less valuable ones, to make the item pool sort of resemble Hard Mode in the PAL version. + """ + display_name = "Hard Item Pool" + + +class Special1sPerWarp(Range): + """ + Sets how many Special1 jewels are needed per warp menu option unlock. + This will decrease until the number x 7 is less than or equal to the Total Specail1s if it isn't already. + """ + range_start = 1 + range_end = 10 + default = 1 + display_name = "Special1s Per Warp" + + +class TotalSpecial1s(Range): + """ + Sets how many Speical1 jewels are in the pool in total. + """ + range_start = 7 + range_end = 70 + default = 7 + display_name = "Total Special1s" + + +class DraculasCondition(Choice): + """ + Sets the requirement for unlocking and opening the door to Dracula's chamber. + None: No requirement. Door is unlocked from the start. + Crystal: Activate the big crystal in Castle Center's basement. Neither boss afterwards has to be defeated. + Bosses: Kill a specified number of bosses with health bars and claim their Trophies. + Specials: Find a specified number of Special2 jewels shuffled in the main item pool. + """ + display_name = "Dracula's Condition" + option_none = 0 + option_crystal = 1 + option_bosses = 2 + option_specials = 3 + default = 1 + + +class PercentSpecial2sRequired(Range): + """ + Percentage of Special2s required to enter Dracula's chamber when Dracula's Condition is Special2s. + """ + range_start = 1 + range_end = 100 + default = 80 + display_name = "Percent Special2s Required" + + +class TotalSpecial2s(Range): + """ + How many Speical2 jewels are in the pool in total when Dracula's Condition is Special2s. + """ + range_start = 1 + range_end = 70 + default = 25 + display_name = "Total Special2s" + + +class BossesRequired(Range): + """ + How many bosses need to be defeated to enter Dracula's chamber when Dracula's Condition is set to Bosses. + This will automatically adjust if there are fewer available bosses than the chosen number. + """ + range_start = 1 + range_end = 16 + default = 12 + display_name = "Bosses Required" + + +class CarrieLogic(Toggle): + """ + Adds the 2 checks inside Underground Waterway's crawlspace to the pool. + If you (and everyone else if racing the same seed) are planning to only ever play Reinhardt, don't enable this. + Can be combined with Hard Logic to include Carrie-only tricks. + """ + display_name = "Carrie Logic" + + +class HardLogic(Toggle): + """ + Properly considers sequence break tricks in logic (i.e. maze skip). Can be combined with Carrie Logic to include Carrie-only tricks. + See the Game Page for a full list of tricks and glitches that may be logically required. + """ + display_name = "Hard Logic" + + +class MultiHitBreakables(Toggle): + """ + Adds the items that drop from the objects that break in three hits to the pool. + There are 18 of these throughout the game, adding up to 79 or 80 checks (depending on sub-weapons being shuffled anywhere or not) in total with all stages. + The game will be modified to remember exactly which of their items you've picked up instead of simply whether they were broken or not. + """ + display_name = "Multi-hit Breakables" + + +class EmptyBreakables(Toggle): + """ + Adds 9 check locations in the form of breakables that normally have nothing (all empty Forest coffins, etc.) and some additional Red Jewels and/or moneybags into the item pool to compensate. + """ + display_name = "Empty Breakables" + + +class LizardLockerItems(Toggle): + """ + Adds the 6 items inside Castle Center 2F's Lizard-man generators to the pool. + Picking up all of these can be a very tedious luck-based process, so they are off by default. + """ + display_name = "Lizard Locker Items" + + +class Shopsanity(Toggle): + """ + Adds 7 one-time purchases from Renon's shop into the location pool. + After buying an item from a slot, it will revert to whatever it is in the vanilla game. + """ + display_name = "Shopsanity" + + +class ShopPrices(Choice): + """ + Randomizes the amount of gold each item costs in Renon's shop. + Use the Minimum and Maximum Gold Price options to control how much or how little an item can cost. + """ + display_name = "Shop Prices" + option_vanilla = 0 + option_randomized = 1 + default = 0 + + +class MinimumGoldPrice(Range): + """ + The lowest amount of gold an item can cost in Renon's shop, divided by 100. + """ + display_name = "Minimum Gold Price" + range_start = 1 + range_end = 50 + default = 2 + + +class MaximumGoldPrice(Range): + """ + The highest amount of gold an item can cost in Renon's shop, divided by 100. + """ + display_name = "Maximum Gold Price" + range_start = 1 + range_end = 50 + default = 30 + + +class PostBehemothBoss(Choice): + """ + Sets which boss is fought in the vampire triplets' room in Castle Center by which characters after defeating Behemoth. + """ + display_name = "Post-Behemoth Boss" + option_vanilla = 0 + option_inverted = 1 + option_always_rosa = 2 + option_always_camilla = 3 + default = 0 + + +class RoomOfClocksBoss(Choice): + """ + Sets which boss is fought at Room of Clocks by which characters. + """ + display_name = "Room of Clocks Boss" + option_vanilla = 0 + option_inverted = 1 + option_always_death = 2 + option_always_actrise = 3 + default = 0 + + +class RenonFightCondition(Choice): + """ + Sets the condition on which the Renon fight will trigger. + """ + display_name = "Renon Fight Condition" + option_never = 0 + option_spend_30k = 1 + option_always = 2 + default = 1 + + +class VincentFightCondition(Choice): + """ + Sets the condition on which the vampire Vincent fight will trigger. + """ + display_name = "Vincent Fight Condition" + option_never = 0 + option_wait_16_days = 1 + option_always = 2 + default = 1 + + +class BadEndingCondition(Choice): + """ + Sets the condition on which the currently-controlled character's Bad Ending will trigger. + """ + display_name = "Bad Ending Condition" + option_never = 0 + option_kill_vincent = 1 + option_always = 2 + default = 1 + + +class IncreaseItemLimit(DefaultOnToggle): + """ + Increases the holding limit of usable items from 10 to 99 of each item. + """ + display_name = "Increase Item Limit" + + +class NerfHealingItems(Toggle): + """ + Decreases the amount of health healed by Roast Chickens to 25%, Roast Beefs to 50%, and Healing Kits to 80%. + """ + display_name = "Nerf Healing Items" + + +class LoadingZoneHeals(DefaultOnToggle): + """ + Whether end-of-level loading zones restore health and cure status aliments or not. + Recommended off for those looking for more of a survival horror experience! + """ + display_name = "Loading Zone Heals" + + +class InvisibleItems(Choice): + """ + Sets which items are visible in their locations and which are invisible until picked up. + 'Chance' gives each item a 50/50 chance of being visible or invisible. + """ + display_name = "Invisible Items" + option_vanilla = 0 + option_reveal_all = 1 + option_hide_all = 2 + option_chance = 3 + default = 0 + + +class DropPreviousSubWeapon(Toggle): + """ + When receiving a sub-weapon, the one you had before will drop behind you, so it can be taken back if desired. + """ + display_name = "Drop Previous Sub-weapon" + + +class PermanentPowerUps(Toggle): + """ + Replaces PowerUps with PermaUps, which upgrade your B weapon level permanently and will stay even after dying and/or continuing. + To compensate, only two will be in the pool overall, and they will not drop from any enemy or projectile. + """ + display_name = "Permanent PowerUps" + + +class IceTrapPercentage(Range): + """ + Replaces a percentage of junk items with Ice Traps. + These will be visibly disguised as other items, and receiving one will freeze you as if you were hit by Camilla's ice cloud attack. + """ + display_name = "Ice Trap Percentage" + range_start = 0 + range_end = 100 + default = 0 + + +class IceTrapAppearance(Choice): + """ + What items Ice Traps can possibly be disguised as. + """ + display_name = "Ice Trap Appearance" + option_major_only = 0 + option_junk_only = 1 + option_anything = 2 + default = 0 + + +class DisableTimeRestrictions(Toggle): + """ + Disables the restriction on every event and door that requires the current time to be within a specific range, so they can be triggered at any time. + This includes all sun/moon doors and, in the Villa, the meeting with Rosa and the fountain pillar. + The Villa coffin is not affected by this. + """ + display_name = "Disable Time Requirements" + + +class SkipGondolas(Toggle): + """ + Makes jumping on and activating a gondola in Tunnel instantly teleport you to the other station, thereby skipping the entire three-minute ride. + The item normally at the gondola transfer point is moved to instead be near the red gondola at its station. + """ + display_name = "Skip Gondolas" + + +class SkipWaterwayBlocks(Toggle): + """ + Opens the door to the third switch in Underground Waterway from the start so that the jumping across floating brick platforms won't have to be done. + Shopping at the Contract on the other side of them may still be logically required if Shopsanity is on. + """ + display_name = "Skip Waterway Blocks" + + +class Countdown(Choice): + """ + Displays, near the HUD clock and below the health bar, the number of unobtained progression-marked items or the total check locations remaining in the stage you are currently in. + """ + display_name = "Countdown" + option_none = 0 + option_majors = 1 + option_all_locations = 2 + default = 0 + + +class BigToss(Toggle): + """ + Makes every non-immobilizing damage source launch you as if you got hit by Behemoth's charge. + Press A while tossed to cancel the launch momentum and avoid being thrown off ledges. + Hold Z to have all incoming damage be treated as it normally would. + Any tricks that might be possible with it are not in logic. + """ + display_name = "Big Toss" + + +class PantherDash(Choice): + """ + Hold C-right at any time to sprint way faster. + Any tricks that are possible with it are not in logic and any boss fights with boss health meters, if started, are expected to be finished before leaving their arenas if Dracula's Condition is bosses. + Jumpless will prevent jumping while moving at the increased speed to make logic harder to break with it. + """ + display_name = "Panther Dash" + option_off = 0 + option_on = 1 + option_jumpless = 2 + default = 0 + + +class IncreaseShimmySpeed(Toggle): + """ + Increases the speed at which characters shimmy left and right while hanging on ledges. + """ + display_name = "Increase Shimmy Speed" + + +class FallGuard(Toggle): + """ + Removes fall damage from landing too hard. Note that falling for too long will still result in instant death. + """ + display_name = "Fall Guard" + + +class BackgroundMusic(Choice): + """ + Randomizes or disables the music heard throughout the game. + Randomized music is split into two pools: songs that loop and songs that don't. + The "lead-in" versions of some songs will be paired accordingly. + """ + display_name = "Background Music" + option_normal = 0 + option_disabled = 1 + option_randomized = 2 + default = 0 + + +class MapLighting(Choice): + """ + Randomizes the lighting color RGB values on every map during every time of day to be literally anything. + The colors and/or shading of the following things are affected: fog, maps, player, enemies, and some objects. + """ + display_name = "Map Lighting" + option_normal = 0 + option_randomized = 1 + default = 0 + + +class CinematicExperience(Toggle): + """ + Enables an unused film reel effect on every cutscene in the game. Purely cosmetic. + """ + display_name = "Cinematic Experience" + + +class WindowColorR(Range): + """ + The red value for the background color of the text windows during gameplay. + """ + display_name = "Window Color R" + range_start = 0 + range_end = 15 + default = 1 + + +class WindowColorG(Range): + """ + The green value for the background color of the text windows during gameplay. + """ + display_name = "Window Color G" + range_start = 0 + range_end = 15 + default = 5 + + +class WindowColorB(Range): + """ + The blue value for the background color of the text windows during gameplay. + """ + display_name = "Window Color B" + range_start = 0 + range_end = 15 + default = 15 + + +class WindowColorA(Range): + """ + The alpha value for the background color of the text windows during gameplay. + """ + display_name = "Window Color A" + range_start = 0 + range_end = 15 + default = 8 + + +class DeathLink(Choice): + """ + When you die, everyone dies. Of course the reverse is true too. + Explosive: Makes received DeathLinks kill you via the Magical Nitro explosion instead of the normal death animation. + """ + display_name = "DeathLink" + option_off = 0 + alias_no = 0 + alias_true = 1 + alias_yes = 1 + option_on = 1 + option_explosive = 2 + + +@dataclass +class CV64Options(PerGameCommonOptions): + accessibility: ItemsAccessibility + start_inventory_from_pool: StartInventoryPool + character_stages: CharacterStages + stage_shuffle: StageShuffle + starting_stage: StartingStage + warp_order: WarpOrder + sub_weapon_shuffle: SubWeaponShuffle + spare_keys: SpareKeys + hard_item_pool: HardItemPool + special1s_per_warp: Special1sPerWarp + total_special1s: TotalSpecial1s + draculas_condition: DraculasCondition + percent_special2s_required: PercentSpecial2sRequired + total_special2s: TotalSpecial2s + bosses_required: BossesRequired + carrie_logic: CarrieLogic + hard_logic: HardLogic + multi_hit_breakables: MultiHitBreakables + empty_breakables: EmptyBreakables + lizard_locker_items: LizardLockerItems + shopsanity: Shopsanity + shop_prices: ShopPrices + minimum_gold_price: MinimumGoldPrice + maximum_gold_price: MaximumGoldPrice + post_behemoth_boss: PostBehemothBoss + room_of_clocks_boss: RoomOfClocksBoss + renon_fight_condition: RenonFightCondition + vincent_fight_condition: VincentFightCondition + bad_ending_condition: BadEndingCondition + increase_item_limit: IncreaseItemLimit + nerf_healing_items: NerfHealingItems + loading_zone_heals: LoadingZoneHeals + invisible_items: InvisibleItems + drop_previous_sub_weapon: DropPreviousSubWeapon + permanent_powerups: PermanentPowerUps + ice_trap_percentage: IceTrapPercentage + ice_trap_appearance: IceTrapAppearance + disable_time_restrictions: DisableTimeRestrictions + skip_gondolas: SkipGondolas + skip_waterway_blocks: SkipWaterwayBlocks + countdown: Countdown + big_toss: BigToss + panther_dash: PantherDash + increase_shimmy_speed: IncreaseShimmySpeed + window_color_r: WindowColorR + window_color_g: WindowColorG + window_color_b: WindowColorB + window_color_a: WindowColorA + background_music: BackgroundMusic + map_lighting: MapLighting + fall_guard: FallGuard + cinematic_experience: CinematicExperience + death_link: DeathLink + + +cv64_option_groups = [ + OptionGroup("gameplay tweaks", [ + HardItemPool, ShopPrices, MinimumGoldPrice, MaximumGoldPrice, PostBehemothBoss, RoomOfClocksBoss, + RenonFightCondition, VincentFightCondition, BadEndingCondition, IncreaseItemLimit, NerfHealingItems, + LoadingZoneHeals, InvisibleItems, DropPreviousSubWeapon, PermanentPowerUps, IceTrapPercentage, + IceTrapAppearance, DisableTimeRestrictions, SkipGondolas, SkipWaterwayBlocks, Countdown, BigToss, PantherDash, + IncreaseShimmySpeed, FallGuard, DeathLink + ]), + OptionGroup("cosmetics", [ + WindowColorR, WindowColorG, WindowColorB, WindowColorA, BackgroundMusic, MapLighting, CinematicExperience + ]) +] diff --git a/worlds/cv64/regions.py b/worlds/cv64/regions.py new file mode 100644 index 000000000000..2194828a19ae --- /dev/null +++ b/worlds/cv64/regions.py @@ -0,0 +1,517 @@ +from .data import lname, rname, ename +from typing import List, Union + + +# # # KEY # # # +# "stage" = What stage the Region is a part of. The Region and its corresponding Locations and Entrances will only be +# put in if its stage is active. +# "locations" = The Locations to add to that Region when putting in said Region (provided their add conditions pass). +# "entrances" = The Entrances to add to that Region when putting in said Region (provided their add conditions pass). +region_info = { + "Menu": {}, + + rname.forest_start: {"stage": rname.forest_of_silence, + "locations": [lname.forest_pillars_right, + lname.forest_pillars_left, + lname.forest_pillars_top, + lname.forest_king_skeleton, + lname.forest_boss_one, + lname.forest_lgaz_in, + lname.forest_lgaz_top, + lname.forest_hgaz_in, + lname.forest_hgaz_top, + lname.forest_weretiger_sw, + lname.forest_boss_two, + lname.forest_weretiger_gate, + lname.forest_dirge_tomb_l, + lname.forest_dirge_tomb_u, + lname.forest_dirge_plaque, + lname.forest_dirge_ped, + lname.forest_dirge_rock1, + lname.forest_dirge_rock2, + lname.forest_dirge_rock3, + lname.forest_dirge_rock4, + lname.forest_dirge_rock5, + lname.forest_corpse_save, + lname.forest_dbridge_wall, + lname.forest_dbridge_sw], + "entrances": [ename.forest_dbridge_gate]}, + + rname.forest_mid: {"stage": rname.forest_of_silence, + "locations": [lname.forest_dbridge_gate_l, + lname.forest_dbridge_gate_r, + lname.forest_dbridge_tomb_l, + lname.forest_dbridge_tomb_ur, + lname.forest_dbridge_tomb_uf, + lname.forest_bface_tomb_lf, + lname.forest_bface_tomb_lr, + lname.forest_bface_tomb_u, + lname.forest_ibridge, + lname.forest_bridge_rock1, + lname.forest_bridge_rock2, + lname.forest_bridge_rock3, + lname.forest_bridge_rock4, + lname.forest_werewolf_tomb_lf, + lname.forest_werewolf_tomb_lr, + lname.forest_werewolf_tomb_r, + lname.forest_werewolf_plaque, + lname.forest_werewolf_tree, + lname.forest_werewolf_island, + lname.forest_final_sw], + "entrances": [ename.forest_werewolf_gate]}, + + rname.forest_end: {"stage": rname.forest_of_silence, + "locations": [lname.forest_boss_three], + "entrances": [ename.forest_end]}, + + rname.cw_start: {"stage": rname.castle_wall, + "locations": [lname.cwr_bottom, + lname.cw_dragon_sw, + lname.cw_boss, + lname.cw_save_slab1, + lname.cw_save_slab2, + lname.cw_save_slab3, + lname.cw_save_slab4, + lname.cw_save_slab5, + lname.cw_rrampart, + lname.cw_lrampart, + lname.cw_pillar, + lname.cw_shelf_visible, + lname.cw_shelf_sandbags, + lname.cw_shelf_torch], + "entrances": [ename.cw_portcullis_c, + ename.cw_lt_skip, + ename.cw_lt_door]}, + + rname.cw_exit: {"stage": rname.castle_wall, + "locations": [lname.cw_ground_left, + lname.cw_ground_middle, + lname.cw_ground_right]}, + + rname.cw_ltower: {"stage": rname.castle_wall, + "locations": [lname.cwl_bottom, + lname.cwl_bridge, + lname.cw_drac_sw, + lname.cw_drac_slab1, + lname.cw_drac_slab2, + lname.cw_drac_slab3, + lname.cw_drac_slab4, + lname.cw_drac_slab5], + "entrances": [ename.cw_end]}, + + rname.villa_start: {"stage": rname.villa, + "locations": [lname.villafy_outer_gate_l, + lname.villafy_outer_gate_r, + lname.villafy_dog_platform, + lname.villafy_inner_gate], + "entrances": [ename.villa_dog_gates]}, + + rname.villa_main: {"stage": rname.villa, + "locations": [lname.villafy_gate_marker, + lname.villafy_villa_marker, + lname.villafy_tombstone, + lname.villafy_fountain_fl, + lname.villafy_fountain_fr, + lname.villafy_fountain_ml, + lname.villafy_fountain_mr, + lname.villafy_fountain_rl, + lname.villafy_fountain_rr, + lname.villafo_front_r, + lname.villafo_front_l, + lname.villafo_mid_l, + lname.villafo_mid_r, + lname.villafo_rear_r, + lname.villafo_rear_l, + lname.villafo_pot_r, + lname.villafo_pot_l, + lname.villafo_sofa, + lname.villafo_chandelier1, + lname.villafo_chandelier2, + lname.villafo_chandelier3, + lname.villafo_chandelier4, + lname.villafo_chandelier5, + lname.villala_hallway_stairs, + lname.villala_hallway_l, + lname.villala_hallway_r, + lname.villala_bedroom_chairs, + lname.villala_bedroom_bed, + lname.villala_vincent, + lname.villala_slivingroom_table, + lname.villala_slivingroom_mirror, + lname.villala_diningroom_roses, + lname.villala_llivingroom_pot_r, + lname.villala_llivingroom_pot_l, + lname.villala_llivingroom_painting, + lname.villala_llivingroom_light, + lname.villala_llivingroom_lion, + lname.villala_exit_knight], + "entrances": [ename.villa_snipe_dogs, + ename.villa_renon, + ename.villa_to_storeroom, + ename.villa_to_archives, + ename.villa_to_maze]}, + + rname.villa_storeroom: {"stage": rname.villa, + "locations": [lname.villala_storeroom_l, + lname.villala_storeroom_r, + lname.villala_storeroom_s], + "entrances": [ename.villa_from_storeroom]}, + + rname.villa_archives: {"stage": rname.villa, + "locations": [lname.villala_archives_entrance, + lname.villala_archives_table, + lname.villala_archives_rear]}, + + rname.villa_maze: {"stage": rname.villa, + "locations": [lname.villam_malus_torch, + lname.villam_malus_bush, + lname.villam_fplatform, + lname.villam_frankieturf_l, + lname.villam_frankieturf_r, + lname.villam_frankieturf_ru, + lname.villam_fgarden_f, + lname.villam_fgarden_mf, + lname.villam_fgarden_mr, + lname.villam_fgarden_r, + lname.villam_rplatform, + lname.villam_rplatform_de, + lname.villam_exit_de, + lname.villam_serv_path], + "entrances": [ename.villa_from_maze, + ename.villa_copper_door, + ename.villa_copper_skip]}, + + rname.villa_servants: {"stage": rname.villa, + "locations": [lname.villafo_serv_ent], + "entrances": [ename.villa_servant_door]}, + + rname.villa_crypt: {"stage": rname.villa, + "locations": [lname.villam_crypt_ent, + lname.villam_crypt_upstream, + lname.villac_ent_l, + lname.villac_ent_r, + lname.villac_wall_l, + lname.villac_wall_r, + lname.villac_coffin_l, + lname.villac_coffin_r, + lname.villa_boss_one, + lname.villa_boss_two], + "entrances": [ename.villa_bridge_door, + ename.villa_end_r, + ename.villa_end_c]}, + + rname.tunnel_start: {"stage": rname.tunnel, + "locations": [lname.tunnel_landing, + lname.tunnel_landing_rc, + lname.tunnel_stone_alcove_r, + lname.tunnel_stone_alcove_l, + lname.tunnel_twin_arrows, + lname.tunnel_arrows_rock1, + lname.tunnel_arrows_rock2, + lname.tunnel_arrows_rock3, + lname.tunnel_arrows_rock4, + lname.tunnel_arrows_rock5, + lname.tunnel_lonesome_bucket, + lname.tunnel_lbucket_mdoor_l, + lname.tunnel_lbucket_quag, + lname.tunnel_bucket_quag_rock1, + lname.tunnel_bucket_quag_rock2, + lname.tunnel_bucket_quag_rock3, + lname.tunnel_lbucket_albert, + lname.tunnel_albert_camp, + lname.tunnel_albert_quag, + lname.tunnel_gondola_rc_sdoor_l, + lname.tunnel_gondola_rc_sdoor_m, + lname.tunnel_gondola_rc_sdoor_r, + lname.tunnel_gondola_rc, + lname.tunnel_rgondola_station, + lname.tunnel_gondola_transfer], + "entrances": [ename.tunnel_start_renon, + ename.tunnel_gondolas]}, + + rname.tunnel_end: {"stage": rname.tunnel, + "locations": [lname.tunnel_corpse_bucket_quag, + lname.tunnel_corpse_bucket_mdoor_l, + lname.tunnel_corpse_bucket_mdoor_r, + lname.tunnel_shovel_quag_start, + lname.tunnel_exit_quag_start, + lname.tunnel_shovel_quag_end, + lname.tunnel_exit_quag_end, + lname.tunnel_shovel, + lname.tunnel_shovel_save, + lname.tunnel_shovel_mdoor_l, + lname.tunnel_shovel_mdoor_r, + lname.tunnel_shovel_sdoor_l, + lname.tunnel_shovel_sdoor_m, + lname.tunnel_shovel_sdoor_r], + "entrances": [ename.tunnel_end_renon, + ename.tunnel_end]}, + + rname.uw_main: {"stage": rname.underground_waterway, + "locations": [lname.uw_near_ent, + lname.uw_across_ent, + lname.uw_first_ledge1, + lname.uw_first_ledge2, + lname.uw_first_ledge3, + lname.uw_first_ledge4, + lname.uw_first_ledge5, + lname.uw_first_ledge6, + lname.uw_poison_parkour, + lname.uw_boss, + lname.uw_waterfall_alcove, + lname.uw_carrie1, + lname.uw_carrie2, + lname.uw_bricks_save, + lname.uw_above_skel_ledge, + lname.uw_in_skel_ledge1, + lname.uw_in_skel_ledge2, + lname.uw_in_skel_ledge3], + "entrances": [ename.uw_final_waterfall, + ename.uw_renon]}, + + rname.uw_end: {"stage": rname.underground_waterway, + "entrances": [ename.uw_waterfall_skip, + ename.uw_end]}, + + rname.cc_main: {"stage": rname.castle_center, + "locations": [lname.ccb_skel_hallway_ent, + lname.ccb_skel_hallway_jun, + lname.ccb_skel_hallway_tc, + lname.ccb_skel_hallway_ba, + lname.ccb_behemoth_l_ff, + lname.ccb_behemoth_l_mf, + lname.ccb_behemoth_l_mr, + lname.ccb_behemoth_l_fr, + lname.ccb_behemoth_r_ff, + lname.ccb_behemoth_r_mf, + lname.ccb_behemoth_r_mr, + lname.ccb_behemoth_r_fr, + lname.ccb_behemoth_crate1, + lname.ccb_behemoth_crate2, + lname.ccb_behemoth_crate3, + lname.ccb_behemoth_crate4, + lname.ccb_behemoth_crate5, + lname.ccelv_near_machine, + lname.ccelv_atop_machine, + lname.ccelv_stand1, + lname.ccelv_stand2, + lname.ccelv_stand3, + lname.ccelv_pipes, + lname.ccelv_switch, + lname.ccelv_staircase, + lname.ccff_redcarpet_knight, + lname.ccff_gears_side, + lname.ccff_gears_mid, + lname.ccff_gears_corner, + lname.ccff_lizard_knight, + lname.ccff_lizard_near_knight, + lname.ccff_lizard_pit, + lname.ccff_lizard_corner, + lname.ccff_lizard_locker_nfr, + lname.ccff_lizard_locker_nmr, + lname.ccff_lizard_locker_nml, + lname.ccff_lizard_locker_nfl, + lname.ccff_lizard_locker_fl, + lname.ccff_lizard_locker_fr, + lname.ccff_lizard_slab1, + lname.ccff_lizard_slab2, + lname.ccff_lizard_slab3, + lname.ccff_lizard_slab4, + lname.ccll_brokenstairs_floor, + lname.ccll_brokenstairs_knight, + lname.ccll_brokenstairs_save, + lname.ccll_glassknight_l, + lname.ccll_glassknight_r, + lname.ccll_butlers_door, + lname.ccll_butlers_side, + lname.ccll_cwhall_butlerflames_past, + lname.ccll_cwhall_flamethrower, + lname.ccll_cwhall_cwflames, + lname.ccll_heinrich, + lname.ccia_nitro_crates, + lname.ccia_nitro_shelf_h, + lname.ccia_stairs_knight, + lname.ccia_maids_vase, + lname.ccia_maids_outer, + lname.ccia_maids_inner, + lname.ccia_inventions_maids, + lname.ccia_inventions_crusher, + lname.ccia_inventions_famicart, + lname.ccia_inventions_zeppelin, + lname.ccia_inventions_round, + lname.ccia_nitrohall_flamethrower, + lname.ccia_nitrohall_torch, + lname.ccia_nitro_shelf_i], + "entrances": [ename.cc_tc_door, + ename.cc_lower_wall, + ename.cc_renon, + ename.cc_upper_wall]}, + + rname.cc_torture_chamber: {"stage": rname.castle_center, + "locations": [lname.ccb_mandrag_shelf_l, + lname.ccb_mandrag_shelf_r, + lname.ccb_torture_rack, + lname.ccb_torture_rafters]}, + + rname.cc_library: {"stage": rname.castle_center, + "locations": [lname.ccll_cwhall_wall, + lname.ccl_bookcase]}, + + rname.cc_crystal: {"stage": rname.castle_center, + "locations": [lname.cc_behind_the_seal, + lname.cc_boss_one, + lname.cc_boss_two], + "entrances": [ename.cc_elevator]}, + + rname.cc_elev_top: {"stage": rname.castle_center, + "entrances": [ename.cc_exit_r, + ename.cc_exit_c]}, + + rname.dt_main: {"stage": rname.duel_tower, + "locations": [lname.dt_boss_one, + lname.dt_boss_two, + lname.dt_ibridge_l, + lname.dt_ibridge_r, + lname.dt_stones_start, + lname.dt_stones_end, + lname.dt_werebull_arena, + lname.dt_boss_three, + lname.dt_boss_four], + "entrances": [ename.dt_start, + ename.dt_end]}, + + rname.toe_main: {"stage": rname.tower_of_execution, + "locations": [lname.toe_ledge1, + lname.toe_ledge2, + lname.toe_ledge3, + lname.toe_ledge4, + lname.toe_ledge5, + lname.toe_midsavespikes_r, + lname.toe_midsavespikes_l, + lname.toe_elec_grate, + lname.toe_ibridge, + lname.toe_top], + "entrances": [ename.toe_start, + ename.toe_gate, + ename.toe_gate_skip, + ename.toe_end]}, + + rname.toe_ledge: {"stage": rname.tower_of_execution, + "locations": [lname.toe_keygate_l, + lname.toe_keygate_r]}, + + rname.tosci_start: {"stage": rname.tower_of_science, + "locations": [lname.tosci_elevator, + lname.tosci_plain_sr, + lname.tosci_stairs_sr], + "entrances": [ename.tosci_start, + ename.tosci_key1_door, + ename.tosci_to_key2_door]}, + + rname.tosci_three_doors: {"stage": rname.tower_of_science, + "locations": [lname.tosci_three_door_hall]}, + + rname.tosci_conveyors: {"stage": rname.tower_of_science, + "locations": [lname.tosci_ibridge_t, + lname.tosci_ibridge_b1, + lname.tosci_ibridge_b2, + lname.tosci_ibridge_b3, + lname.tosci_ibridge_b4, + lname.tosci_ibridge_b5, + lname.tosci_ibridge_b6, + lname.tosci_conveyor_sr, + lname.tosci_exit], + "entrances": [ename.tosci_from_key2_door, + ename.tosci_key3_door, + ename.tosci_end]}, + + rname.tosci_key3: {"stage": rname.tower_of_science, + "locations": [lname.tosci_key3_r, + lname.tosci_key3_m, + lname.tosci_key3_l]}, + + rname.tosor_main: {"stage": rname.tower_of_sorcery, + "locations": [lname.tosor_stained_tower, + lname.tosor_savepoint, + lname.tosor_trickshot, + lname.tosor_yellow_bubble, + lname.tosor_blue_platforms, + lname.tosor_side_isle, + lname.tosor_ibridge], + "entrances": [ename.tosor_start, + ename.tosor_end]}, + + rname.roc_main: {"stage": rname.room_of_clocks, + "locations": [lname.roc_ent_l, + lname.roc_ent_r, + lname.roc_elev_r, + lname.roc_elev_l, + lname.roc_cont_r, + lname.roc_cont_l, + lname.roc_exit, + lname.roc_boss], + "entrances": [ename.roc_gate]}, + + rname.ct_start: {"stage": rname.clock_tower, + "locations": [lname.ct_gearclimb_battery_slab1, + lname.ct_gearclimb_battery_slab2, + lname.ct_gearclimb_battery_slab3, + lname.ct_gearclimb_side, + lname.ct_gearclimb_corner, + lname.ct_gearclimb_door_slab1, + lname.ct_gearclimb_door_slab2, + lname.ct_gearclimb_door_slab3], + "entrances": [ename.ct_to_door1]}, + + rname.ct_middle: {"stage": rname.clock_tower, + "locations": [lname.ct_bp_chasm_fl, + lname.ct_bp_chasm_fr, + lname.ct_bp_chasm_rl, + lname.ct_bp_chasm_k], + "entrances": [ename.ct_from_door1, + ename.ct_to_door2]}, + + rname.ct_end: {"stage": rname.clock_tower, + "locations": [lname.ct_finalroom_door_slab1, + lname.ct_finalroom_door_slab2, + lname.ct_finalroom_fl, + lname.ct_finalroom_fr, + lname.ct_finalroom_rl, + lname.ct_finalroom_rr, + lname.ct_finalroom_platform, + lname.ct_finalroom_renon_slab1, + lname.ct_finalroom_renon_slab2, + lname.ct_finalroom_renon_slab3, + lname.ct_finalroom_renon_slab4, + lname.ct_finalroom_renon_slab5, + lname.ct_finalroom_renon_slab6, + lname.ct_finalroom_renon_slab7, + lname.ct_finalroom_renon_slab8], + "entrances": [ename.ct_from_door2, + ename.ct_renon, + ename.ct_door_3]}, + + rname.ck_main: {"stage": rname.castle_keep, + "locations": [lname.ck_boss_one, + lname.ck_boss_two, + lname.ck_flame_l, + lname.ck_flame_r, + lname.ck_behind_drac, + lname.ck_cube], + "entrances": [ename.ck_slope_jump, + ename.ck_drac_door]}, + + rname.renon: {"locations": [lname.renon1, + lname.renon2, + lname.renon3, + lname.renon4, + lname.renon5, + lname.renon6, + lname.renon7]}, + + rname.ck_drac_chamber: {"locations": [lname.the_end]} +} + + +def get_region_info(region: str, info: str) -> Union[str, List[str], None]: + return region_info[region].get(info, None) diff --git a/worlds/cv64/rom.py b/worlds/cv64/rom.py new file mode 100644 index 000000000000..ab4371b0ac12 --- /dev/null +++ b/worlds/cv64/rom.py @@ -0,0 +1,1026 @@ +import json +import Utils + +from BaseClasses import Location +from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension +from typing import List, Dict, Union, Iterable, Collection, Optional, TYPE_CHECKING + +import hashlib +import os +import pkgutil + +from . import lzkn64 +from .data import patches +from .stages import get_stage_info +from .text import cv64_string_to_bytearray, cv64_text_truncate, cv64_text_wrap +from .aesthetics import renon_item_dialogue, get_item_text_color +from .locations import get_location_info +from .options import CharacterStages, VincentFightCondition, RenonFightCondition, PostBehemothBoss, RoomOfClocksBoss, \ + BadEndingCondition, DeathLink, DraculasCondition, InvisibleItems, Countdown, PantherDash +from settings import get_settings + +if TYPE_CHECKING: + from . import CV64World + +CV64_US_10_HASH = "1cc5cf3b4d29d8c3ade957648b529dc1" + +warp_map_offsets = [0xADF67, 0xADF77, 0xADF87, 0xADF97, 0xADFA7, 0xADFBB, 0xADFCB, 0xADFDF] + + +class RomData: + orig_buffer: None + buffer: bytearray + + def __init__(self, file: bytes, name: Optional[str] = None) -> None: + self.file = bytearray(file) + self.name = name + + def read_bit(self, address: int, bit_number: int) -> bool: + bitflag = (1 << bit_number) + return (self.buffer[address] & bitflag) != 0 + + def read_byte(self, address: int) -> int: + return self.file[address] + + def read_bytes(self, start_address: int, length: int) -> bytearray: + return self.file[start_address:start_address + length] + + def write_byte(self, address: int, value: int) -> None: + self.file[address] = value + + def write_bytes(self, start_address: int, values: Collection[int]) -> None: + self.file[start_address:start_address + len(values)] = values + + def write_int16(self, address: int, value: int) -> None: + value = value & 0xFFFF + self.write_bytes(address, [(value >> 8) & 0xFF, value & 0xFF]) + + def write_int16s(self, start_address: int, values: List[int]) -> None: + for i, value in enumerate(values): + self.write_int16(start_address + (i * 2), value) + + def write_int24(self, address: int, value: int) -> None: + value = value & 0xFFFFFF + self.write_bytes(address, [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]) + + def write_int24s(self, start_address: int, values: List[int]) -> None: + for i, value in enumerate(values): + self.write_int24(start_address + (i * 3), value) + + def write_int32(self, address, value: int) -> None: + value = value & 0xFFFFFFFF + self.write_bytes(address, [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]) + + def write_int32s(self, start_address: int, values: list) -> None: + for i, value in enumerate(values): + self.write_int32(start_address + (i * 4), value) + + def get_bytes(self) -> bytes: + return bytes(self.file) + + +class CV64PatchExtensions(APPatchExtension): + game = "Castlevania 64" + + @staticmethod + def apply_patches(caller: APProcedurePatch, rom: bytes, options_file: str) -> bytes: + rom_data = RomData(rom) + options = json.loads(caller.get_file(options_file).decode("utf-8")) + + # NOP out the CRC BNEs + rom_data.write_int32(0x66C, 0x00000000) + rom_data.write_int32(0x678, 0x00000000) + + # Always offer Hard Mode on file creation + rom_data.write_int32(0xC8810, 0x240A0100) # ADDIU T2, R0, 0x0100 + + # Disable the Easy Mode cutoff point at Castle Center's elevator. + rom_data.write_int32(0xD9E18, 0x240D0000) # ADDIU T5, R0, 0x0000 + + # Disable the Forest, Castle Wall, and Villa intro cutscenes and make it possible to change the starting level + rom_data.write_byte(0xB73308, 0x00) + rom_data.write_byte(0xB7331A, 0x40) + rom_data.write_byte(0xB7332B, 0x4C) + rom_data.write_byte(0xB6302B, 0x00) + rom_data.write_byte(0x109F8F, 0x00) + + # Prevent Forest end cutscene flag from setting so it can be triggered infinitely. + rom_data.write_byte(0xEEA51, 0x01) + + # Hack to make the Forest, CW and Villa intro cutscenes play at the start of their levels no matter what map + # came before them. + rom_data.write_int32(0x97244, 0x803FDD60) + rom_data.write_int32s(0xBFDD60, patches.forest_cw_villa_intro_cs_player) + + # Make changing the map ID to 0xFF reset the map. Helpful to work around a bug wherein the camera gets stuck + # when entering a loading zone that doesn't change the map. + rom_data.write_int32s(0x197B0, [0x0C0FF7E6, # JAL 0x803FDF98 + 0x24840008]) # ADDIU A0, A0, 0x0008 + rom_data.write_int32s(0xBFDF98, patches.map_id_refresher) + + # Enable swapping characters when loading into a map by holding L. + rom_data.write_int32(0x97294, 0x803FDFC4) + rom_data.write_int32(0x19710, 0x080FF80E) # J 0x803FE038 + rom_data.write_int32s(0xBFDFC4, patches.character_changer) + + # Villa coffin time-of-day hack + rom_data.write_byte(0xD9D83, 0x74) + rom_data.write_int32(0xD9D84, 0x080FF14D) # J 0x803FC534 + rom_data.write_int32s(0xBFC534, patches.coffin_time_checker) + + # Fix both Castle Center elevator bridges for both characters unless enabling only one character's stages. + # At which point one bridge will be always broken and one always repaired instead. + if options["character_stages"] == CharacterStages.option_reinhardt_only: + rom_data.write_int32(0x6CEAA0, 0x240B0000) # ADDIU T3, R0, 0x0000 + elif options["character_stages"] == CharacterStages.option_carrie_only: + rom_data.write_int32(0x6CEAA0, 0x240B0001) # ADDIU T3, R0, 0x0001 + else: + rom_data.write_int32(0x6CEAA0, 0x240B0001) # ADDIU T3, R0, 0x0001 + rom_data.write_int32(0x6CEAA4, 0x240D0001) # ADDIU T5, R0, 0x0001 + + # Were-bull arena flag hack + rom_data.write_int32(0x6E38F0, 0x0C0FF157) # JAL 0x803FC55C + rom_data.write_int32s(0xBFC55C, patches.werebull_flag_unsetter) + rom_data.write_int32(0xA949C, 0x0C0FF380) # JAL 0x803FCE00 + rom_data.write_int32s(0xBFCE00, patches.werebull_flag_pickup_setter) + + # Enable being able to carry multiple Special jewels, Nitros, and Mandragoras simultaneously + rom_data.write_int32(0xBF1F4, 0x3C038039) # LUI V1, 0x8039 + # Special1 + rom_data.write_int32(0xBF210, 0x80659C4B) # LB A1, 0x9C4B (V1) + rom_data.write_int32(0xBF214, 0x24A50001) # ADDIU A1, A1, 0x0001 + rom_data.write_int32(0xBF21C, 0xA0659C4B) # SB A1, 0x9C4B (V1) + # Special2 + rom_data.write_int32(0xBF230, 0x80659C4C) # LB A1, 0x9C4C (V1) + rom_data.write_int32(0xBF234, 0x24A50001) # ADDIU A1, A1, 0x0001 + rom_data.write_int32(0xbf23C, 0xA0659C4C) # SB A1, 0x9C4C (V1) + # Magical Nitro + rom_data.write_int32(0xBF360, 0x10000004) # B 0x8013C184 + rom_data.write_int32(0xBF378, 0x25E50001) # ADDIU A1, T7, 0x0001 + rom_data.write_int32(0xBF37C, 0x10000003) # B 0x8013C19C + # Mandragora + rom_data.write_int32(0xBF3A8, 0x10000004) # B 0x8013C1CC + rom_data.write_int32(0xBF3C0, 0x25050001) # ADDIU A1, T0, 0x0001 + rom_data.write_int32(0xBF3C4, 0x10000003) # B 0x8013C1E4 + + # Give PowerUps their Legacy of Darkness behavior when attempting to pick up more than two + rom_data.write_int16(0xA9624, 0x1000) + rom_data.write_int32(0xA9730, 0x24090000) # ADDIU T1, R0, 0x0000 + rom_data.write_int32(0xBF2FC, 0x080FF16D) # J 0x803FC5B4 + rom_data.write_int32(0xBF300, 0x00000000) # NOP + rom_data.write_int32s(0xBFC5B4, patches.give_powerup_stopper) + + # Rename the Wooden Stake and Rose to "You are a FOOL!" + rom_data.write_bytes(0xEFE34, + bytearray([0xFF, 0xFF, 0xA2, 0x0B]) + cv64_string_to_bytearray("You are a FOOL!", + append_end=False)) + # Capitalize the "k" in "Archives key" to be consistent with...literally every other key name! + rom_data.write_byte(0xEFF21, 0x2D) + + # Skip the "There is a white jewel" text so checking one saves the game instantly. + rom_data.write_int32s(0xEFC72, [0x00020002 for _ in range(37)]) + rom_data.write_int32(0xA8FC0, 0x24020001) # ADDIU V0, R0, 0x0001 + # Skip the yes/no prompts when activating things. + rom_data.write_int32s(0xBFDACC, patches.map_text_redirector) + rom_data.write_int32(0xA9084, 0x24020001) # ADDIU V0, R0, 0x0001 + rom_data.write_int32(0xBEBE8, 0x0C0FF6B4) # JAL 0x803FDAD0 + # Skip Vincent and Heinrich's mandatory-for-a-check dialogue + rom_data.write_int32(0xBED9C, 0x0C0FF6DA) # JAL 0x803FDB68 + # Skip the long yes/no prompt in the CC planetarium to set the pieces. + rom_data.write_int32(0xB5C5DF, 0x24030001) # ADDIU V1, R0, 0x0001 + # Skip the yes/no prompt to activate the CC elevator. + rom_data.write_int32(0xB5E3FB, 0x24020001) # ADDIU V0, R0, 0x0001 + # Skip the yes/no prompts to set Nitro/Mandragora at both walls. + rom_data.write_int32(0xB5DF3E, 0x24030001) # ADDIU V1, R0, 0x0001 + + # Custom message if you try checking the downstairs CC crack before removing the seal. + rom_data.write_bytes(0xBFDBAC, cv64_string_to_bytearray("The Furious Nerd Curse\n" + "prevents you from setting\n" + "anything until the seal\n" + "is removed!", True)) + + rom_data.write_int32s(0xBFDD20, patches.special_descriptions_redirector) + + # Change the Stage Select menu options + rom_data.write_int32s(0xADF64, patches.warp_menu_rewrite) + rom_data.write_int32s(0x10E0C8, patches.warp_pointer_table) + + # Play the "teleportation" sound effect when teleporting + rom_data.write_int32s(0xAE088, [0x08004FAB, # J 0x80013EAC + 0x2404019E]) # ADDIU A0, R0, 0x019E + + # Lizard-man save proofing + rom_data.write_int32(0xA99AC, 0x080FF0B8) # J 0x803FC2E0 + rom_data.write_int32s(0xBFC2E0, patches.boss_save_stopper) + + # Disable or guarantee vampire Vincent's fight + if options["vincent_fight_condition"] == VincentFightCondition.option_never: + rom_data.write_int32(0xAACC0, 0x24010001) # ADDIU AT, R0, 0x0001 + rom_data.write_int32(0xAACE0, 0x24180000) # ADDIU T8, R0, 0x0000 + elif options["vincent_fight_condition"] == VincentFightCondition.option_always: + rom_data.write_int32(0xAACE0, 0x24180010) # ADDIU T8, R0, 0x0010 + else: + rom_data.write_int32(0xAACE0, 0x24180000) # ADDIU T8, R0, 0x0000 + + # Disable or guarantee Renon's fight + rom_data.write_int32(0xAACB4, 0x080FF1A4) # J 0x803FC690 + if options["renon_fight_condition"] == RenonFightCondition.option_never: + rom_data.write_byte(0xB804F0, 0x00) + rom_data.write_byte(0xB80632, 0x00) + rom_data.write_byte(0xB807E3, 0x00) + rom_data.write_byte(0xB80988, 0xB8) + rom_data.write_byte(0xB816BD, 0xB8) + rom_data.write_byte(0xB817CF, 0x00) + rom_data.write_int32s(0xBFC690, patches.renon_cutscene_checker_jr) + elif options["renon_fight_condition"] == RenonFightCondition.option_always: + rom_data.write_byte(0xB804F0, 0x0C) + rom_data.write_byte(0xB80632, 0x0C) + rom_data.write_byte(0xB807E3, 0x0C) + rom_data.write_byte(0xB80988, 0xC4) + rom_data.write_byte(0xB816BD, 0xC4) + rom_data.write_byte(0xB817CF, 0x0C) + rom_data.write_int32s(0xBFC690, patches.renon_cutscene_checker_jr) + else: + rom_data.write_int32s(0xBFC690, patches.renon_cutscene_checker) + + # NOP the Easy Mode check when buying a thing from Renon, so his fight can be triggered even on this mode. + rom_data.write_int32(0xBD8B4, 0x00000000) + + # Disable or guarantee the Bad Ending + if options["bad_ending_condition"] == BadEndingCondition.option_never: + rom_data.write_int32(0xAEE5C6, 0x3C0A0000) # LUI T2, 0x0000 + elif options["bad_ending_condition"] == BadEndingCondition.option_always: + rom_data.write_int32(0xAEE5C6, 0x3C0A0040) # LUI T2, 0x0040 + + # Play Castle Keep's song if teleporting in front of Dracula's door outside the escape sequence + rom_data.write_int32(0x6E937C, 0x080FF12E) # J 0x803FC4B8 + rom_data.write_int32s(0xBFC4B8, patches.ck_door_music_player) + + # Increase item capacity to 100 if "Increase Item Limit" is turned on + if options["increase_item_limit"]: + rom_data.write_byte(0xBF30B, 0x63) # Most items + rom_data.write_byte(0xBF3F7, 0x63) # Sun/Moon cards + rom_data.write_byte(0xBF353, 0x64) # Keys (increase regardless) + + # Change the item healing values if "Nerf Healing" is turned on + if options["nerf_healing_items"]: + rom_data.write_byte(0xB56371, 0x50) # Healing kit (100 -> 80) + rom_data.write_byte(0xB56374, 0x32) # Roast beef ( 80 -> 50) + rom_data.write_byte(0xB56377, 0x19) # Roast chicken ( 50 -> 25) + + # Disable loading zone healing if turned off + if not options["loading_zone_heals"]: + rom_data.write_byte(0xD99A5, 0x00) # Skip all loading zone checks + rom_data.write_byte(0xA9DFFB, + 0x40) # Disable free heal from King Skeleton by reading the unused magic meter value + + # Disable spinning on the Special1 and 2 pickup models so colorblind people can more easily identify them + rom_data.write_byte(0xEE4F5, 0x00) # Special1 + rom_data.write_byte(0xEE505, 0x00) # Special2 + # Make the Special2 the same size as a Red jewel(L) to further distinguish them + rom_data.write_int32(0xEE4FC, 0x3FA66666) + + # Prevent the vanilla Magical Nitro transport's "can explode" flag from setting + rom_data.write_int32(0xB5D7AA, 0x00000000) # NOP + + # Ensure the vampire Nitro check will always pass, so they'll never not spawn and crash the Villa cutscenes + rom_data.write_byte(0xA6253D, 0x03) + + # Enable the Game Over's "Continue" menu starting the cursor on whichever checkpoint is most recent + rom_data.write_int32(0xB4DDC, 0x0C060D58) # JAL 0x80183560 + rom_data.write_int32s(0x106750, patches.continue_cursor_start_checker) + rom_data.write_int32(0x1C444, 0x080FF08A) # J 0x803FC228 + rom_data.write_int32(0x1C2A0, 0x080FF08A) # J 0x803FC228 + rom_data.write_int32s(0xBFC228, patches.savepoint_cursor_updater) + rom_data.write_int32(0x1C2D0, 0x080FF094) # J 0x803FC250 + rom_data.write_int32s(0xBFC250, patches.stage_start_cursor_updater) + rom_data.write_byte(0xB585C8, 0xFF) + + # Make the Special1 and 2 play sounds when you reach milestones with them. + rom_data.write_int32s(0xBFDA50, patches.special_sound_notifs) + rom_data.write_int32(0xBF240, 0x080FF694) # J 0x803FDA50 + rom_data.write_int32(0xBF220, 0x080FF69E) # J 0x803FDA78 + + # Add data for White Jewel #22 (the new Duel Tower savepoint) at the end of the White Jewel ID data list + rom_data.write_int16s(0x104AC8, [0x0000, 0x0006, + 0x0013, 0x0015]) + + # Take the contract in Waterway off of its 00400000 bitflag. + rom_data.write_byte(0x87E3DA, 0x00) + + # Spawn coordinates list extension + rom_data.write_int32(0xD5BF4, 0x080FF103) # J 0x803FC40C + rom_data.write_int32s(0xBFC40C, patches.spawn_coordinates_extension) + rom_data.write_int32s(0x108A5E, patches.waterway_end_coordinates) + + # Fix a vanilla issue wherein saving in a character-exclusive stage as the other character would incorrectly + # display the name of that character's equivalent stage on the save file instead of the one they're actually in. + rom_data.write_byte(0xC9FE3, 0xD4) + rom_data.write_byte(0xCA055, 0x08) + rom_data.write_byte(0xCA066, 0x40) + rom_data.write_int32(0xCA068, 0x860C17D0) # LH T4, 0x17D0 (S0) + rom_data.write_byte(0xCA06D, 0x08) + rom_data.write_byte(0x104A31, 0x01) + rom_data.write_byte(0x104A39, 0x01) + rom_data.write_byte(0x104A89, 0x01) + rom_data.write_byte(0x104A91, 0x01) + rom_data.write_byte(0x104A99, 0x01) + rom_data.write_byte(0x104AA1, 0x01) + + # CC top elevator switch check + rom_data.write_int32(0x6CF0A0, 0x0C0FF0B0) # JAL 0x803FC2C0 + rom_data.write_int32s(0xBFC2C0, patches.elevator_flag_checker) + + # Disable time restrictions + if options["disable_time_restrictions"]: + # Fountain + rom_data.write_int32(0x6C2340, 0x00000000) # NOP + rom_data.write_int32(0x6C257C, 0x10000023) # B [forward 0x23] + # Rosa + rom_data.write_byte(0xEEAAB, 0x00) + rom_data.write_byte(0xEEAAD, 0x18) + # Moon doors + rom_data.write_int32(0xDC3E0, 0x00000000) # NOP + rom_data.write_int32(0xDC3E8, 0x00000000) # NOP + # Sun doors + rom_data.write_int32(0xDC410, 0x00000000) # NOP + rom_data.write_int32(0xDC418, 0x00000000) # NOP + + # Custom data-loading code + rom_data.write_int32(0x6B5028, 0x08060D70) # J 0x801835D0 + rom_data.write_int32s(0x1067B0, patches.custom_code_loader) + + # Custom remote item rewarding and DeathLink receiving code + rom_data.write_int32(0x19B98, 0x080FF000) # J 0x803FC000 + rom_data.write_int32s(0xBFC000, patches.remote_item_giver) + rom_data.write_int32s(0xBFE190, patches.subweapon_surface_checker) + + # Make received DeathLinks blow you to smithereens instead of kill you normally. + if options["death_link"] == DeathLink.option_explosive: + rom_data.write_int32(0x27A70, 0x10000008) # B [forward 0x08] + rom_data.write_int32s(0xBFC0D0, patches.deathlink_nitro_edition) + + # Set the DeathLink ROM flag if it's on at all. + if options["death_link"] != DeathLink.option_off: + rom_data.write_byte(0xBFBFDE, 0x01) + + # DeathLink counter decrementer code + rom_data.write_int32(0x1C340, 0x080FF8F0) # J 0x803FE3C0 + rom_data.write_int32s(0xBFE3C0, patches.deathlink_counter_decrementer) + rom_data.write_int32(0x25B6C, 0x080FFA5E) # J 0x803FE978 + rom_data.write_int32s(0xBFE978, patches.launch_fall_killer) + + # Death flag un-setter on "Beginning of stage" state overwrite code + rom_data.write_int32(0x1C2B0, 0x080FF047) # J 0x803FC11C + rom_data.write_int32s(0xBFC11C, patches.death_flag_unsetter) + + # Warp menu-opening code + rom_data.write_int32(0xB9BA8, 0x080FF099) # J 0x803FC264 + rom_data.write_int32s(0xBFC264, patches.warp_menu_opener) + + # NPC item textbox hack + rom_data.write_int32(0xBF1DC, 0x080FF904) # J 0x803FE410 + rom_data.write_int32(0xBF1E0, 0x27BDFFE0) # ADDIU SP, SP, -0x20 + rom_data.write_int32s(0xBFE410, patches.npc_item_hack) + + # Sub-weapon check function hook + rom_data.write_int32(0xBF32C, 0x00000000) # NOP + rom_data.write_int32(0xBF330, 0x080FF05E) # J 0x803FC178 + rom_data.write_int32s(0xBFC178, patches.give_subweapon_stopper) + + # Warp menu Special1 restriction + rom_data.write_int32(0xADD68, 0x0C04AB12) # JAL 0x8012AC48 + rom_data.write_int32s(0xADE28, patches.stage_select_overwrite) + rom_data.write_byte(0xADE47, options["s1s_per_warp"]) + + # Dracula's door text pointer hijack + rom_data.write_int32(0xD69F0, 0x080FF141) # J 0x803FC504 + rom_data.write_int32s(0xBFC504, patches.dracula_door_text_redirector) + + # Dracula's chamber condition + rom_data.write_int32(0xE2FDC, 0x0804AB25) # J 0x8012AC78 + rom_data.write_int32s(0xADE84, patches.special_goal_checker) + rom_data.write_bytes(0xBFCC48, + [0xA0, 0x00, 0xFF, 0xFF, 0xA0, 0x01, 0xFF, 0xFF, 0xA0, 0x02, 0xFF, 0xFF, 0xA0, 0x03, 0xFF, + 0xFF, 0xA0, 0x04, 0xFF, 0xFF, 0xA0, 0x05, 0xFF, 0xFF, 0xA0, 0x06, 0xFF, 0xFF, 0xA0, 0x07, + 0xFF, 0xFF, 0xA0, 0x08, 0xFF, 0xFF, 0xA0, 0x09]) + if options["draculas_condition"] == DraculasCondition.option_crystal: + rom_data.write_int32(0x6C8A54, 0x0C0FF0C1) # JAL 0x803FC304 + rom_data.write_int32s(0xBFC304, patches.crystal_special2_giver) + rom_data.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n" + f"You'll need the power\n" + f"of the basement crystal\n" + f"to undo the seal.", True)) + special2_name = "Crystal " + special2_text = "The crystal is on!\n" \ + "Time to teach the old man\n" \ + "a lesson!" + elif options["draculas_condition"] == DraculasCondition.option_bosses: + rom_data.write_int32(0xBBD50, 0x080FF18C) # J 0x803FC630 + rom_data.write_int32s(0xBFC630, patches.boss_special2_giver) + rom_data.write_int32s(0xBFC55C, patches.werebull_flag_unsetter_special2_electric_boogaloo) + rom_data.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n" + f"You'll need to defeat\n" + f"{options['required_s2s']} powerful monsters\n" + f"to undo the seal.", True)) + special2_name = "Trophy " + special2_text = f"Proof you killed a powerful\n" \ + f"Night Creature. Earn {options['required_s2s']}/{options['total_s2s']}\n" \ + f"to battle Dracula." + elif options["draculas_condition"] == DraculasCondition.option_specials: + special2_name = "Special2" + rom_data.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n" + f"You'll need to find\n" + f"{options['required_s2s']} Special2 jewels\n" + f"to undo the seal.", True)) + special2_text = f"Need {options['required_s2s']}/{options['total_s2s']} to kill Dracula.\n" \ + f"Looking closely, you see...\n" \ + f"a piece of him within?" + else: + rom_data.write_byte(0xADE8F, 0x00) + special2_name = "Special2" + special2_text = "If you're reading this,\n" \ + "how did you get a Special2!?" + rom_data.write_byte(0xADE8F, options["required_s2s"]) + # Change the Special2 name depending on the setting. + rom_data.write_bytes(0xEFD4E, cv64_string_to_bytearray(special2_name)) + # Change the Special1 and 2 menu descriptions to tell you how many you need to unlock a warp and fight Dracula + # respectively. + special_text_bytes = cv64_string_to_bytearray(f"{options['s1s_per_warp']} per warp unlock.\n" + f"{options['total_special1s']} exist in total.\n" + f"Z + R + START to warp.") + cv64_string_to_bytearray( + special2_text) + rom_data.write_bytes(0xBFE53C, special_text_bytes) + + # On-the-fly overlay modifier + rom_data.write_int32s(0xBFC338, patches.double_component_checker) + rom_data.write_int32s(0xBFC3D4, patches.downstairs_seal_checker) + rom_data.write_int32s(0xBFE074, patches.mandragora_with_nitro_setter) + rom_data.write_int32s(0xBFC700, patches.overlay_modifiers) + + # On-the-fly actor data modifier hook + rom_data.write_int32(0xEAB04, 0x080FF21E) # J 0x803FC878 + rom_data.write_int32s(0xBFC870, patches.map_data_modifiers) + + # Fix to make flags apply to freestanding invisible items properly + rom_data.write_int32(0xA84F8, 0x90CC0039) # LBU T4, 0x0039 (A2) + + # Fix locked doors to check the key counters instead of their vanilla key locations' bitflags + # Pickup flag check modifications: + rom_data.write_int32(0x10B2D8, 0x00000002) # Left Tower Door + rom_data.write_int32(0x10B2F0, 0x00000003) # Storeroom Door + rom_data.write_int32(0x10B2FC, 0x00000001) # Archives Door + rom_data.write_int32(0x10B314, 0x00000004) # Maze Gate + rom_data.write_int32(0x10B350, 0x00000005) # Copper Door + rom_data.write_int32(0x10B3A4, 0x00000006) # Torture Chamber Door + rom_data.write_int32(0x10B3B0, 0x00000007) # ToE Gate + rom_data.write_int32(0x10B3BC, 0x00000008) # Science Door1 + rom_data.write_int32(0x10B3C8, 0x00000009) # Science Door2 + rom_data.write_int32(0x10B3D4, 0x0000000A) # Science Door3 + rom_data.write_int32(0x6F0094, 0x0000000B) # CT Door 1 + rom_data.write_int32(0x6F00A4, 0x0000000C) # CT Door 2 + rom_data.write_int32(0x6F00B4, 0x0000000D) # CT Door 3 + # Item counter decrement check modifications: + rom_data.write_int32(0xEDA84, 0x00000001) # Archives Door + rom_data.write_int32(0xEDA8C, 0x00000002) # Left Tower Door + rom_data.write_int32(0xEDA94, 0x00000003) # Storeroom Door + rom_data.write_int32(0xEDA9C, 0x00000004) # Maze Gate + rom_data.write_int32(0xEDAA4, 0x00000005) # Copper Door + rom_data.write_int32(0xEDAAC, 0x00000006) # Torture Chamber Door + rom_data.write_int32(0xEDAB4, 0x00000007) # ToE Gate + rom_data.write_int32(0xEDABC, 0x00000008) # Science Door1 + rom_data.write_int32(0xEDAC4, 0x00000009) # Science Door2 + rom_data.write_int32(0xEDACC, 0x0000000A) # Science Door3 + rom_data.write_int32(0xEDAD4, 0x0000000B) # CT Door 1 + rom_data.write_int32(0xEDADC, 0x0000000C) # CT Door 2 + rom_data.write_int32(0xEDAE4, 0x0000000D) # CT Door 3 + + # Fix ToE gate's "unlocked" flag in the locked door flags table + rom_data.write_int16(0x10B3B6, 0x0001) + + rom_data.write_int32(0x10AB2C, 0x8015FBD4) # Maze Gates' check code pointer adjustments + rom_data.write_int32(0x10AB40, 0x8015FBD4) + rom_data.write_int32s(0x10AB50, [0x0D0C0000, + 0x8015FBD4]) + rom_data.write_int32s(0x10AB64, [0x0D0C0000, + 0x8015FBD4]) + rom_data.write_int32s(0xE2E14, patches.normal_door_hook) + rom_data.write_int32s(0xBFC5D0, patches.normal_door_code) + rom_data.write_int32s(0x6EF298, patches.ct_door_hook) + rom_data.write_int32s(0xBFC608, patches.ct_door_code) + # Fix key counter not decrementing if 2 or above + rom_data.write_int32(0xAA0E0, 0x24020000) # ADDIU V0, R0, 0x0000 + + # Make the Easy-only candle drops in Room of Clocks appear on any difficulty + rom_data.write_byte(0x9B518F, 0x01) + + # Slightly move some once-invisible freestanding items to be more visible + if options["invisible_items"] == InvisibleItems.option_reveal_all: + rom_data.write_byte(0x7C7F95, 0xEF) # Forest dirge maiden statue + rom_data.write_byte(0x7C7FA8, 0xAB) # Forest werewolf statue + rom_data.write_byte(0x8099C4, 0x8C) # Villa courtyard tombstone + rom_data.write_byte(0x83A626, 0xC2) # Villa living room painting + # rom_data.write_byte(0x83A62F, 0x64) # Villa Mary's room table + rom_data.write_byte(0xBFCB97, 0xF5) # CC torture instrument rack + rom_data.write_byte(0x8C44D5, 0x22) # CC red carpet hallway knight + rom_data.write_byte(0x8DF57C, 0xF1) # CC cracked wall hallway flamethrower + rom_data.write_byte(0x90FCD6, 0xA5) # CC nitro hallway flamethrower + rom_data.write_byte(0x90FB9F, 0x9A) # CC invention room round machine + rom_data.write_byte(0x90FBAF, 0x03) # CC invention room giant famicart + rom_data.write_byte(0x90FE54, 0x97) # CC staircase knight (x) + rom_data.write_byte(0x90FE58, 0xFB) # CC staircase knight (z) + + # Change the bitflag on the item in upper coffin in Forest final switch gate tomb to one that's not used by + # something else. + rom_data.write_int32(0x10C77C, 0x00000002) + + # Make the torch directly behind Dracula's chamber that normally doesn't set a flag set bitflag 0x08 in + # 0x80389BFA. + rom_data.write_byte(0x10CE9F, 0x01) + + # Change the CC post-Behemoth boss depending on the option for Post-Behemoth Boss + if options["post_behemoth_boss"] == PostBehemothBoss.option_inverted: + rom_data.write_byte(0xEEDAD, 0x02) + rom_data.write_byte(0xEEDD9, 0x01) + elif options["post_behemoth_boss"] == PostBehemothBoss.option_always_rosa: + rom_data.write_byte(0xEEDAD, 0x00) + rom_data.write_byte(0xEEDD9, 0x03) + # Put both on the same flag so changing character won't trigger a rematch with the same boss. + rom_data.write_byte(0xEED8B, 0x40) + elif options["post_behemoth_boss"] == PostBehemothBoss.option_always_camilla: + rom_data.write_byte(0xEEDAD, 0x03) + rom_data.write_byte(0xEEDD9, 0x00) + rom_data.write_byte(0xEED8B, 0x40) + + # Change the RoC boss depending on the option for Room of Clocks Boss + if options["room_of_clocks_boss"] == RoomOfClocksBoss.option_inverted: + rom_data.write_byte(0x109FB3, 0x56) + rom_data.write_byte(0x109FBF, 0x44) + rom_data.write_byte(0xD9D44, 0x14) + rom_data.write_byte(0xD9D4C, 0x14) + elif options["room_of_clocks_boss"] == RoomOfClocksBoss.option_always_death: + rom_data.write_byte(0x109FBF, 0x44) + rom_data.write_byte(0xD9D45, 0x00) + # Put both on the same flag so changing character won't trigger a rematch with the same boss. + rom_data.write_byte(0x109FB7, 0x90) + rom_data.write_byte(0x109FC3, 0x90) + elif options["room_of_clocks_boss"] == RoomOfClocksBoss.option_always_actrise: + rom_data.write_byte(0x109FB3, 0x56) + rom_data.write_int32(0xD9D44, 0x00000000) + rom_data.write_byte(0xD9D4D, 0x00) + rom_data.write_byte(0x109FB7, 0x90) + rom_data.write_byte(0x109FC3, 0x90) + + # Un-nerf Actrise when playing as Reinhardt. + # This is likely a leftover TGS demo feature in which players could battle Actrise as Reinhardt. + rom_data.write_int32(0xB318B4, 0x240E0001) # ADDIU T6, R0, 0x0001 + + # Tunnel gondola skip + if options["skip_gondolas"]: + rom_data.write_int32(0x6C5F58, 0x080FF7D0) # J 0x803FDF40 + rom_data.write_int32s(0xBFDF40, patches.gondola_skipper) + # New gondola transfer point candle coordinates + rom_data.write_byte(0xBFC9A3, 0x04) + rom_data.write_bytes(0x86D824, [0x27, 0x01, 0x10, 0xF7, 0xA0]) + + # Waterway brick platforms skip + if options["skip_waterway_blocks"]: + rom_data.write_int32(0x6C7E2C, 0x00000000) # NOP + + # Ambience silencing fix + rom_data.write_int32(0xD9270, 0x080FF840) # J 0x803FE100 + rom_data.write_int32s(0xBFE100, patches.ambience_silencer) + # Fix for the door sliding sound playing infinitely if leaving the fan meeting room before the door closes + # entirely. Hooking this in the ambience silencer code does nothing for some reason. + rom_data.write_int32s(0xAE10C, [0x08004FAB, # J 0x80013EAC + 0x3404829B]) # ORI A0, R0, 0x829B + rom_data.write_int32s(0xD9E8C, [0x08004FAB, # J 0x80013EAC + 0x3404829B]) # ORI A0, R0, 0x829B + # Fan meeting room ambience fix + rom_data.write_int32(0x109964, 0x803FE13C) + + # Make the Villa coffin cutscene skippable + rom_data.write_int32(0xAA530, 0x080FF880) # J 0x803FE200 + rom_data.write_int32s(0xBFE200, patches.coffin_cutscene_skipper) + + # Increase shimmy speed + if options["increase_shimmy_speed"]: + rom_data.write_byte(0xA4241, 0x5A) + + # Disable landing fall damage + if options["fall_guard"]: + rom_data.write_byte(0x27B23, 0x00) + + # Enable the unused film reel effect on all cutscenes + if options["cinematic_experience"]: + rom_data.write_int32(0xAA33C, 0x240A0001) # ADDIU T2, R0, 0x0001 + rom_data.write_byte(0xAA34B, 0x0C) + rom_data.write_int32(0xAA4C4, 0x24090001) # ADDIU T1, R0, 0x0001 + + # Permanent PowerUp stuff + if options["permanent_powerups"]: + # Make receiving PowerUps increase the unused menu PowerUp counter instead of the one outside the save + # struct. + rom_data.write_int32(0xBF2EC, 0x806B619B) # LB T3, 0x619B (V1) + rom_data.write_int32(0xBFC5BC, 0xA06C619B) # SB T4, 0x619B (V1) + # Make Reinhardt's whip check the menu PowerUp counter + rom_data.write_int32(0x69FA08, 0x80CC619B) # LB T4, 0x619B (A2) + rom_data.write_int32(0x69FBFC, 0x80C3619B) # LB V1, 0x619B (A2) + rom_data.write_int32(0x69FFE0, 0x818C9C53) # LB T4, 0x9C53 (T4) + # Make Carrie's orb check the menu PowerUp counter + rom_data.write_int32(0x6AC86C, 0x8105619B) # LB A1, 0x619B (T0) + rom_data.write_int32(0x6AC950, 0x8105619B) # LB A1, 0x619B (T0) + rom_data.write_int32(0x6AC99C, 0x810E619B) # LB T6, 0x619B (T0) + rom_data.write_int32(0x5AFA0, 0x80639C53) # LB V1, 0x9C53 (V1) + rom_data.write_int32(0x5B0A0, 0x81089C53) # LB T0, 0x9C53 (T0) + rom_data.write_byte(0x391C7, 0x00) # Prevent PowerUps from dropping from regular enemies + rom_data.write_byte(0xEDEDF, 0x03) # Make any vanishing PowerUps that do show up L jewels instead + # Rename the PowerUp to "PermaUp" + rom_data.write_bytes(0xEFDEE, cv64_string_to_bytearray("PermaUp")) + # 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"]: + rom_data.write_byte(0x10C7A1, 0x03) + # Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other + # game PermaUps are distinguishable. + rom_data.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00]) + + # Write the associated code for the randomized (or disabled) music list. + if options["background_music"]: + rom_data.write_int32(0x14588, 0x08060D60) # J 0x80183580 + rom_data.write_int32(0x14590, 0x00000000) # NOP + rom_data.write_int32s(0x106770, patches.music_modifier) + rom_data.write_int32(0x15780, 0x0C0FF36E) # JAL 0x803FCDB8 + rom_data.write_int32s(0xBFCDB8, patches.music_comparer_modifier) + + # Enable storing item flags anywhere and changing the item model/visibility on any item instance. + rom_data.write_int32s(0xA857C, [0x080FF38F, # J 0x803FCE3C + 0x94D90038]) # LHU T9, 0x0038 (A2) + rom_data.write_int32s(0xBFCE3C, patches.item_customizer) + rom_data.write_int32s(0xA86A0, [0x0C0FF3AF, # JAL 0x803FCEBC + 0x95C40002]) # LHU A0, 0x0002 (T6) + rom_data.write_int32s(0xBFCEBC, patches.item_appearance_switcher) + rom_data.write_int32s(0xA8728, [0x0C0FF3B8, # JAL 0x803FCEE4 + 0x01396021]) # ADDU T4, T1, T9 + rom_data.write_int32s(0xBFCEE4, patches.item_model_visibility_switcher) + rom_data.write_int32s(0xA8A04, [0x0C0FF3C2, # JAL 0x803FCF08 + 0x018B6021]) # ADDU T4, T4, T3 + rom_data.write_int32s(0xBFCF08, patches.item_shine_visibility_switcher) + + # Make Axes and Crosses in AP Locations drop to their correct height, and make items with changed appearances + # spin their correct speed. + rom_data.write_int32s(0xE649C, [0x0C0FFA03, # JAL 0x803FE80C + 0x956C0002]) # LHU T4, 0x0002 (T3) + rom_data.write_int32s(0xA8B08, [0x080FFA0C, # J 0x803FE830 + 0x960A0038]) # LHU T2, 0x0038 (S0) + rom_data.write_int32s(0xE8584, [0x0C0FFA21, # JAL 0x803FE884 + 0x95D80000]) # LHU T8, 0x0000 (T6) + rom_data.write_int32s(0xE7AF0, [0x0C0FFA2A, # JAL 0x803FE8A8 + 0x958D0000]) # LHU T5, 0x0000 (T4) + rom_data.write_int32s(0xBFE7DC, patches.item_drop_spin_corrector) + + # Disable the 3HBs checking and setting flags when breaking them and enable their individual items checking and + # setting flags instead. + if options["multi_hit_breakables"]: + rom_data.write_int32(0xE87F8, 0x00000000) # NOP + rom_data.write_int16(0xE836C, 0x1000) + rom_data.write_int32(0xE8B40, 0x0C0FF3CD) # JAL 0x803FCF34 + rom_data.write_int32s(0xBFCF34, patches.three_hit_item_flags_setter) + # Villa foyer chandelier-specific functions (yeah, IDK why KCEK made different functions for this one) + rom_data.write_int32(0xE7D54, 0x00000000) # NOP + rom_data.write_int16(0xE7908, 0x1000) + rom_data.write_byte(0xE7A5C, 0x10) + rom_data.write_int32(0xE7F08, 0x0C0FF3DF) # JAL 0x803FCF7C + rom_data.write_int32s(0xBFCF7C, patches.chandelier_item_flags_setter) + + # New flag values to put in each 3HB vanilla flag's spot + rom_data.write_int32(0x10C7C8, 0x8000FF48) # FoS dirge maiden rock + rom_data.write_int32(0x10C7B0, 0x0200FF48) # FoS S1 bridge rock + rom_data.write_int32(0x10C86C, 0x0010FF48) # CW upper rampart save nub + rom_data.write_int32(0x10C878, 0x4000FF49) # CW Dracula switch slab + rom_data.write_int32(0x10CAD8, 0x0100FF49) # Tunnel twin arrows slab + rom_data.write_int32(0x10CAE4, 0x0004FF49) # Tunnel lonesome bucket pit rock + rom_data.write_int32(0x10CB54, 0x4000FF4A) # UW poison parkour ledge + rom_data.write_int32(0x10CB60, 0x0080FF4A) # UW skeleton crusher ledge + rom_data.write_int32(0x10CBF0, 0x0008FF4A) # CC Behemoth crate + rom_data.write_int32(0x10CC2C, 0x2000FF4B) # CC elevator pedestal + rom_data.write_int32(0x10CC70, 0x0200FF4B) # CC lizard locker slab + rom_data.write_int32(0x10CD88, 0x0010FF4B) # ToE pre-midsavepoint platforms ledge + rom_data.write_int32(0x10CE6C, 0x4000FF4C) # ToSci invisible bridge crate + rom_data.write_int32(0x10CF20, 0x0080FF4C) # CT inverted battery slab + rom_data.write_int32(0x10CF2C, 0x0008FF4C) # CT inverted 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(0x10C908, 0x0008FF4D) # Villa foyer chandelier + rom_data.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data + + # Once-per-frame gameplay checks + rom_data.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034 + rom_data.write_int32(0xBFD058, 0x0801AEB5) # J 0x8006BAD4 + + # Everything related to dropping the previous sub-weapon + if options["drop_previous_sub_weapon"]: + rom_data.write_int32(0xBFD034, 0x080FF3FF) # J 0x803FCFFC + rom_data.write_int32(0xBFC190, 0x080FF3F2) # J 0x803FCFC8 + rom_data.write_int32s(0xBFCFC4, patches.prev_subweapon_spawn_checker) + rom_data.write_int32s(0xBFCFFC, patches.prev_subweapon_fall_checker) + rom_data.write_int32s(0xBFD060, patches.prev_subweapon_dropper) + + # Everything related to the Countdown counter + if options["countdown"]: + rom_data.write_int32(0xBFD03C, 0x080FF9DC) # J 0x803FE770 + rom_data.write_int32(0xD5D48, 0x080FF4EC) # J 0x803FD3B0 + rom_data.write_int32s(0xBFD3B0, patches.countdown_number_displayer) + rom_data.write_int32s(0xBFD6DC, patches.countdown_number_manager) + rom_data.write_int32s(0xBFE770, patches.countdown_demo_hider) + rom_data.write_int32(0xBFCE2C, 0x080FF5D2) # J 0x803FD748 + rom_data.write_int32s(0xBB168, [0x080FF5F4, # J 0x803FD7D0 + 0x8E020028]) # LW V0, 0x0028 (S0) + rom_data.write_int32s(0xBB1D0, [0x080FF5FB, # J 0x803FD7EC + 0x8E020028]) # LW V0, 0x0028 (S0) + rom_data.write_int32(0xBC4A0, 0x080FF5E6) # J 0x803FD798 + rom_data.write_int32(0xBC4C4, 0x080FF5E6) # J 0x803FD798 + rom_data.write_int32(0x19844, 0x080FF602) # J 0x803FD808 + # If the option is set to "all locations", count it down no matter what the item is. + if options["countdown"] == Countdown.option_all_locations: + rom_data.write_int32s(0xBFD71C, [0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101, + 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101]) + else: + # If it's majors, then insert this last minute check I threw together for the weird edge case of a CV64 + # ice trap for another CV64 player taking the form of a major. + rom_data.write_int32s(0xBFD788, [0x080FF717, # J 0x803FDC5C + 0x2529FFFF]) # ADDIU T1, T1, 0xFFFF + rom_data.write_int32s(0xBFDC5C, patches.countdown_extra_safety_check) + rom_data.write_int32(0xA9ECC, + 0x00000000) # NOP the pointless overwrite of the item actor appearance custom value. + + # Ice Trap stuff + rom_data.write_int32(0x697C60, 0x080FF06B) # J 0x803FC18C + rom_data.write_int32(0x6A5160, 0x080FF06B) # J 0x803FC18C + rom_data.write_int32s(0xBFC1AC, patches.ice_trap_initializer) + rom_data.write_int32s(0xBFE700, patches.the_deep_freezer) + rom_data.write_int32s(0xB2F354, [0x3739E4C0, # ORI T9, T9, 0xE4C0 + 0x03200008, # JR T9 + 0x00000000]) # NOP + rom_data.write_int32s(0xBFE4C0, patches.freeze_verifier) + + # Fix for the ice chunk model staying when getting bitten by the maze garden dogs + rom_data.write_int32(0xA2DC48, 0x803FE9C0) + rom_data.write_int32s(0xBFE9C0, patches.dog_bite_ice_trap_fix) + + # Initial Countdown numbers + rom_data.write_int32(0xAD6A8, 0x080FF60A) # J 0x803FD828 + rom_data.write_int32s(0xBFD828, patches.new_game_extras) + + # Everything related to shopsanity + if options["shopsanity"]: + rom_data.write_byte(0xBFBFDF, 0x01) + rom_data.write_bytes(0x103868, cv64_string_to_bytearray("Not obtained. ")) + rom_data.write_int32s(0xBFD8D0, patches.shopsanity_stuff) + rom_data.write_int32(0xBD828, 0x0C0FF643) # JAL 0x803FD90C + rom_data.write_int32(0xBD5B8, 0x0C0FF651) # JAL 0x803FD944 + rom_data.write_int32(0xB0610, 0x0C0FF665) # JAL 0x803FD994 + rom_data.write_int32s(0xBD24C, [0x0C0FF677, # J 0x803FD9DC + 0x00000000]) # NOP + rom_data.write_int32(0xBD618, 0x0C0FF684) # JAL 0x803FDA10 + + # Panther Dash running + if options["panther_dash"]: + rom_data.write_int32(0x69C8C4, 0x0C0FF77E) # JAL 0x803FDDF8 + rom_data.write_int32(0x6AA228, 0x0C0FF77E) # JAL 0x803FDDF8 + rom_data.write_int32s(0x69C86C, [0x0C0FF78E, # JAL 0x803FDE38 + 0x3C01803E]) # LUI AT, 0x803E + rom_data.write_int32s(0x6AA1D0, [0x0C0FF78E, # JAL 0x803FDE38 + 0x3C01803E]) # LUI AT, 0x803E + rom_data.write_int32(0x69D37C, 0x0C0FF79E) # JAL 0x803FDE78 + rom_data.write_int32(0x6AACE0, 0x0C0FF79E) # JAL 0x803FDE78 + rom_data.write_int32s(0xBFDDF8, patches.panther_dash) + # Jump prevention + if options["panther_dash"] == PantherDash.option_jumpless: + rom_data.write_int32(0xBFDE2C, 0x080FF7BB) # J 0x803FDEEC + rom_data.write_int32(0xBFD044, 0x080FF7B1) # J 0x803FDEC4 + rom_data.write_int32s(0x69B630, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8CCD0000]) # LW T5, 0x0000 (A2) + rom_data.write_int32s(0x6A8EC0, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8CCC0000]) # LW T4, 0x0000 (A2) + # Fun fact: KCEK put separate code to handle coyote time jumping + rom_data.write_int32s(0x69910C, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8C4E0000]) # LW T6, 0x0000 (V0) + rom_data.write_int32s(0x6A6718, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8C4E0000]) # LW T6, 0x0000 (V0) + rom_data.write_int32s(0xBFDEC4, patches.panther_jump_preventer) + + # Everything related to Big Toss. + if options["big_toss"]: + rom_data.write_int32s(0x27E90, [0x0C0FFA38, # JAL 0x803FE8E0 + 0xAFB80074]) # SW T8, 0x0074 (SP) + rom_data.write_int32(0x26F54, 0x0C0FFA4D) # JAL 0x803FE934 + rom_data.write_int32s(0xBFE8E0, patches.big_tosser) + + # Write the specified window colors + rom_data.write_byte(0xAEC23, options["window_color_r"] << 4) + rom_data.write_byte(0xAEC33, options["window_color_g"] << 4) + rom_data.write_byte(0xAEC47, options["window_color_b"] << 4) + rom_data.write_byte(0xAEC43, options["window_color_a"] << 4) + + # Everything relating to loading the other game items text + rom_data.write_int32(0xA8D8C, 0x080FF88F) # J 0x803FE23C + rom_data.write_int32(0xBEA98, 0x0C0FF8B4) # JAL 0x803FE2D0 + rom_data.write_int32(0xBEAB0, 0x0C0FF8BD) # JAL 0x803FE2F8 + rom_data.write_int32(0xBEACC, 0x0C0FF8C5) # JAL 0x803FE314 + rom_data.write_int32s(0xBFE23C, patches.multiworld_item_name_loader) + rom_data.write_bytes(0x10F188, [0x00 for _ in range(264)]) + rom_data.write_bytes(0x10F298, [0x00 for _ in range(264)]) + + # When the game normally JALs to the item prepare textbox function after the player picks up an item, set the + # "no receiving" timer to ensure the item textbox doesn't freak out if you pick something up while there's a + # queue of unreceived items. + rom_data.write_int32(0xA8D94, 0x0C0FF9F0) # JAL 0x803FE7C0 + rom_data.write_int32s(0xBFE7C0, [0x3C088039, # LUI T0, 0x8039 + 0x24090020, # ADDIU T1, R0, 0x0020 + 0x0804EDCE, # J 0x8013B738 + 0xA1099BE0]) # SB T1, 0x9BE0 (T0) + + return rom_data.get_bytes() + + @staticmethod + def patch_ap_graphics(caller: APProcedurePatch, rom: bytes) -> bytes: + rom_data = RomData(rom) + + # Extract the item models file, decompress it, append the AP icons, compress it back, re-insert it. + items_file = lzkn64.decompress_buffer(rom_data.read_bytes(0x9C5310, 0x3D28)) + compressed_file = lzkn64.compress_buffer(items_file[0:0x69B6] + pkgutil.get_data(__name__, "data/ap_icons.bin")) + rom_data.write_bytes(0xBB2D88, compressed_file) + # Update the items' Nisitenma-Ichigo table entry to point to the new file's start and end addresses in the rom. + rom_data.write_int32s(0x95F04, [0x80BB2D88, 0x00BB2D88 + len(compressed_file)]) + # Update the items' decompressed file size tables with the new file's decompressed file size. + rom_data.write_int16(0x95706, 0x7BF0) + rom_data.write_int16(0x104CCE, 0x7BF0) + # Update the Wooden Stake and Roses' item appearance settings table to point to the Archipelago item graphics. + rom_data.write_int16(0xEE5BA, 0x7B38) + rom_data.write_int16(0xEE5CA, 0x7280) + # Change the items' sizes. The progression one will be larger than the non-progression one. + rom_data.write_int32(0xEE5BC, 0x3FF00000) + rom_data.write_int32(0xEE5CC, 0x3FA00000) + + return rom_data.get_bytes() + + +class CV64ProcedurePatch(APProcedurePatch, APTokenMixin): + hash = [CV64_US_10_HASH] + patch_file_ending: str = ".apcv64" + result_file_ending: str = ".z64" + + game = "Castlevania 64" + + procedure = [ + ("apply_patches", ["options.json"]), + ("apply_tokens", ["token_data.bin"]), + ("patch_ap_graphics", []) + ] + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +def write_patch(world: "CV64World", patch: CV64ProcedurePatch, offset_data: Dict[int, bytes], shop_name_list: List[str], + shop_desc_list: List[List[Union[int, str, None]]], shop_colors_list: List[bytearray], + active_locations: Iterable[Location]) -> None: + active_warp_list = world.active_warp_list + s1s_per_warp = world.s1s_per_warp + + # Write all the new item/loading zone/shop/lighting/music/etc. values. + for offset, data in offset_data.items(): + patch.write_token(APTokenTypes.WRITE, offset, data) + + # Write the new Stage Select menu destinations. + for i in range(len(active_warp_list)): + if i == 0: + patch.write_token(APTokenTypes.WRITE, + warp_map_offsets[i], get_stage_info(active_warp_list[i], "start map id")) + patch.write_token(APTokenTypes.WRITE, + warp_map_offsets[i] + 4, get_stage_info(active_warp_list[i], "start spawn id")) + else: + patch.write_token(APTokenTypes.WRITE, + warp_map_offsets[i], get_stage_info(active_warp_list[i], "mid map id")) + patch.write_token(APTokenTypes.WRITE, + warp_map_offsets[i] + 4, get_stage_info(active_warp_list[i], "mid spawn id")) + + # Change the Stage Select menu's text to reflect its new purpose. + patch.write_token(APTokenTypes.WRITE, 0xEFAD0, bytes( + cv64_string_to_bytearray(f"Where to...?\t{active_warp_list[0]}\t" + f"`{str(s1s_per_warp).zfill(2)} {active_warp_list[1]}\t" + f"`{str(s1s_per_warp * 2).zfill(2)} {active_warp_list[2]}\t" + f"`{str(s1s_per_warp * 3).zfill(2)} {active_warp_list[3]}\t" + f"`{str(s1s_per_warp * 4).zfill(2)} {active_warp_list[4]}\t" + f"`{str(s1s_per_warp * 5).zfill(2)} {active_warp_list[5]}\t" + f"`{str(s1s_per_warp * 6).zfill(2)} {active_warp_list[6]}\t" + f"`{str(s1s_per_warp * 7).zfill(2)} {active_warp_list[7]}"))) + + # Write the new File Select stage numbers. + for stage in world.active_stage_exits: + for offset in get_stage_info(stage, "save number offsets"): + patch.write_token(APTokenTypes.WRITE, offset, bytes([world.active_stage_exits[stage]["position"]])) + + # Write all the shop text. + if world.options.shopsanity: + patch.write_token(APTokenTypes.WRITE, 0x103868, bytes(cv64_string_to_bytearray("Not obtained. "))) + + shopsanity_name_text = bytearray(0) + shopsanity_desc_text = bytearray(0) + for i in range(len(shop_name_list)): + shopsanity_name_text += bytearray([0xA0, i]) + shop_colors_list[i] + \ + cv64_string_to_bytearray(cv64_text_truncate(shop_name_list[i], 74)) + + shopsanity_desc_text += bytearray([0xA0, i]) + if shop_desc_list[i][1] is not None: + shopsanity_desc_text += cv64_string_to_bytearray("For " + shop_desc_list[i][1] + ".\n", + append_end=False) + shopsanity_desc_text += cv64_string_to_bytearray(renon_item_dialogue[shop_desc_list[i][0]]) + patch.write_token(APTokenTypes.WRITE, 0x1AD00, bytes(shopsanity_name_text)) + patch.write_token(APTokenTypes.WRITE, 0x1A800, bytes(shopsanity_desc_text)) + + # Write the item/player names for other game items. + for loc in active_locations: + if loc.address is None or get_location_info(loc.name, "type") == "shop" or loc.item.player == world.player: + continue + if len(loc.item.name) > 67: + item_name = loc.item.name[0x00:0x68] + else: + item_name = loc.item.name + inject_address = 0xBB7164 + (256 * (loc.address & 0xFFF)) + wrapped_name, num_lines = cv64_text_wrap(item_name + "\nfor " + + world.multiworld.get_player_name(loc.item.player), 96) + patch.write_token(APTokenTypes.WRITE, inject_address, bytes(get_item_text_color(loc) + + cv64_string_to_bytearray(wrapped_name))) + patch.write_token(APTokenTypes.WRITE, inject_address + 255, bytes([num_lines])) + + # Write the secondary name the client will use to distinguish a vanilla ROM from an AP one. + patch.write_token(APTokenTypes.WRITE, 0xBFBFD0, "ARCHIPELAGO1".encode("utf-8")) + # Write the slot authentication + patch.write_token(APTokenTypes.WRITE, 0xBFBFE0, bytes(world.auth)) + + patch.write_file("token_data.bin", patch.get_token_binary()) + + # Write these slot options to a JSON. + options_dict = { + "character_stages": world.options.character_stages.value, + "vincent_fight_condition": world.options.vincent_fight_condition.value, + "renon_fight_condition": world.options.renon_fight_condition.value, + "bad_ending_condition": world.options.bad_ending_condition.value, + "increase_item_limit": world.options.increase_item_limit.value, + "nerf_healing_items": world.options.nerf_healing_items.value, + "loading_zone_heals": world.options.loading_zone_heals.value, + "disable_time_restrictions": world.options.disable_time_restrictions.value, + "death_link": world.options.death_link.value, + "draculas_condition": world.options.draculas_condition.value, + "invisible_items": world.options.invisible_items.value, + "post_behemoth_boss": world.options.post_behemoth_boss.value, + "room_of_clocks_boss": world.options.room_of_clocks_boss.value, + "skip_gondolas": world.options.skip_gondolas.value, + "skip_waterway_blocks": world.options.skip_waterway_blocks.value, + "s1s_per_warp": world.options.special1s_per_warp.value, + "required_s2s": world.required_s2s, + "total_s2s": world.total_s2s, + "total_special1s": world.options.total_special1s.value, + "increase_shimmy_speed": world.options.increase_shimmy_speed.value, + "fall_guard": world.options.fall_guard.value, + "cinematic_experience": world.options.cinematic_experience.value, + "permanent_powerups": world.options.permanent_powerups.value, + "background_music": world.options.background_music.value, + "multi_hit_breakables": world.options.multi_hit_breakables.value, + "drop_previous_sub_weapon": world.options.drop_previous_sub_weapon.value, + "countdown": world.options.countdown.value, + "shopsanity": world.options.shopsanity.value, + "panther_dash": world.options.panther_dash.value, + "big_toss": world.options.big_toss.value, + "window_color_r": world.options.window_color_r.value, + "window_color_g": world.options.window_color_g.value, + "window_color_b": world.options.window_color_b.value, + "window_color_a": world.options.window_color_a.value, + } + + patch.write_file("options.json", json.dumps(options_dict).encode('utf-8')) + + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(open(file_name, "rb").read()) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if CV64_US_10_HASH != basemd5.hexdigest(): + raise Exception("Supplied Base Rom does not match known MD5 for Castlevania 64 US 1.0." + "Get the correct game and version, then dump it.") + setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes) + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + if not file_name: + file_name = get_settings()["cv64_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/cv64/rules.py b/worlds/cv64/rules.py new file mode 100644 index 000000000000..9642073341e4 --- /dev/null +++ b/worlds/cv64/rules.py @@ -0,0 +1,103 @@ +from typing import Dict, TYPE_CHECKING + +from BaseClasses import CollectionState +from worlds.generic.Rules import allow_self_locking_items, CollectionRule +from .options import DraculasCondition +from .entrances import get_entrance_info +from .data import iname, rname + +if TYPE_CHECKING: + from . import CV64World + + +class CV64Rules: + player: int + world: "CV64World" + rules: Dict[str, CollectionRule] + s1s_per_warp: int + required_s2s: int + drac_condition: int + + def __init__(self, world: "CV64World") -> None: + self.player = world.player + self.world = world + self.s1s_per_warp = world.s1s_per_warp + self.required_s2s = world.required_s2s + self.drac_condition = world.drac_condition + + self.rules = { + iname.left_tower_key: lambda state: state.has(iname.left_tower_key, self.player), + iname.storeroom_key: lambda state: state.has(iname.storeroom_key, self.player), + iname.archives_key: lambda state: state.has(iname.archives_key, self.player), + iname.garden_key: lambda state: state.has(iname.garden_key, self.player), + iname.copper_key: lambda state: state.has(iname.copper_key, self.player), + iname.chamber_key: lambda state: state.has(iname.chamber_key, self.player), + "Bomb 1": lambda state: state.has_all({iname.magical_nitro, iname.mandragora}, self.player), + "Bomb 2": lambda state: state.has(iname.magical_nitro, self.player, 2) + and state.has(iname.mandragora, self.player, 2), + iname.execution_key: lambda state: state.has(iname.execution_key, self.player), + iname.science_key1: lambda state: state.has(iname.science_key1, self.player), + iname.science_key2: lambda state: state.has(iname.science_key2, self.player), + iname.science_key3: lambda state: state.has(iname.science_key3, self.player), + iname.clocktower_key1: lambda state: state.has(iname.clocktower_key1, self.player), + iname.clocktower_key2: lambda state: state.has(iname.clocktower_key2, self.player), + iname.clocktower_key3: lambda state: state.has(iname.clocktower_key3, self.player), + "Dracula": self.can_enter_dracs_chamber + } + + def can_enter_dracs_chamber(self, state: CollectionState) -> bool: + drac_object_name = None + if self.drac_condition == DraculasCondition.option_crystal: + drac_object_name = "Crystal" + elif self.drac_condition == DraculasCondition.option_bosses: + drac_object_name = "Trophy" + elif self.drac_condition == DraculasCondition.option_specials: + drac_object_name = "Special2" + + if drac_object_name is not None: + return state.has(drac_object_name, self.player, self.required_s2s) + return True + + def set_cv64_rules(self) -> None: + multiworld = self.world.multiworld + + for region in multiworld.get_regions(self.player): + # Set each entrance's rule if it should have one. + # Warp entrances have their own special handling. + for entrance in region.entrances: + if entrance.parent_region.name == "Menu": + if entrance.name.startswith("Warp "): + entrance.access_rule = lambda state, warp_num=int(entrance.name[5]): \ + state.has(iname.special_one, self.player, self.s1s_per_warp * warp_num) + else: + ent_rule = get_entrance_info(entrance.name, "rule") + if ent_rule in self.rules: + entrance.access_rule = self.rules[ent_rule] + + multiworld.completion_condition[self.player] = lambda state: state.has(iname.victory, self.player) + if self.world.options.accessibility: # not locations accessibility + self.set_self_locking_items() + + def set_self_locking_items(self) -> None: + multiworld = self.world.multiworld + + # Do the regions that we know for a fact always exist, and we always do no matter what. + allow_self_locking_items(multiworld.get_region(rname.villa_archives, self.player), iname.archives_key) + allow_self_locking_items(multiworld.get_region(rname.cc_torture_chamber, self.player), iname.chamber_key) + + # Add this region if the world doesn't have the Villa Storeroom warp entrance. + if "Villa" not in self.world.active_warp_list[1:]: + allow_self_locking_items(multiworld.get_region(rname.villa_storeroom, self.player), iname.storeroom_key) + + # Add this region if Hard Logic is on and Multi Hit Breakables are off. + if self.world.options.hard_logic and not self.world.options.multi_hit_breakables: + allow_self_locking_items(multiworld.get_region(rname.cw_ltower, self.player), iname.left_tower_key) + + # Add these regions if Tower of Science is in the world. + if "Tower of Science" in self.world.active_stage_exits: + allow_self_locking_items(multiworld.get_region(rname.tosci_three_doors, self.player), iname.science_key1) + allow_self_locking_items(multiworld.get_region(rname.tosci_key3, self.player), iname.science_key3) + + # Add this region if Tower of Execution is in the world and Hard Logic is not on. + if "Tower of Execution" in self.world.active_stage_exits and self.world.options.hard_logic: + allow_self_locking_items(multiworld.get_region(rname.toe_ledge, self.player), iname.execution_key) diff --git a/worlds/cv64/src/drop_sub_weapon.c b/worlds/cv64/src/drop_sub_weapon.c new file mode 100644 index 000000000000..d1a4b52269c6 --- /dev/null +++ b/worlds/cv64/src/drop_sub_weapon.c @@ -0,0 +1,69 @@ +// Written by Moisés +#include "include/game/module.h" +#include "include/game/math.h" +#include "cv64.h" + +extern vec3f player_pos; +extern vec3s player_angle; // player_angle.y = Player's facing angle (yaw) +extern f32 player_height_with_respect_of_floor; // Stored negative in-game + +#define SHT_MAX 32767.0f +#define SHT_MINV (1.0f / SHT_MAX) + +void spawn_item_behind_player(s32 item) { + interactuablesModule* pickable_item = NULL; + const f32 spawnDistance = 8.0f; + vec3f player_backwards_dir; + + pickable_item = (interactuablesModule*)module_createAndSetChild(moduleList_findFirstModuleByID(ACTOR_CREATOR), ACTOR_ITEM); + if (pickable_item != NULL) { + // Convert facing angle to a vec3f + // SHT_MINV needs to be negative here for the item to be spawned properly on the character's back + player_backwards_dir.x = coss(-player_angle.y) * -SHT_MINV; + player_backwards_dir.z = sins(-player_angle.y) * -SHT_MINV; + // Multiply facing vector with distance away from the player + vec3f_multiplyScalar(&player_backwards_dir, &player_backwards_dir, spawnDistance); + // Assign the position of the item relative to the player's current position. + vec3f_add(&pickable_item->position, &player_pos, &player_backwards_dir); + // The Y position of the item will be the same as the floor right under the player + // The player's height with respect of the flower under them is already stored negative in-game, + // so no need to substract + pickable_item->position.y = player_pos.y + 5.0f; + pickable_item->height = pickable_item->position.y; + + // Assign item ID + pickable_item->item_ID = item; + } +} + + +const f32 droppingAccel = 0.05f; +const f32 maxDroppingSpeed = 1.5f; +f32 droppingSpeed = 0.0f; +f32 droppingTargetYPos = 0.0f; +u8 dropItemCalcFuncCalled = FALSE; + +s32 drop_item_calc(interactuablesModule* pickable_item) { + if (dropItemCalcFuncCalled == FALSE) { + droppingTargetYPos = player_pos.y + player_height_with_respect_of_floor + 1.0f; + if (pickable_item->item_ID == CROSS || pickable_item->item_ID == AXE || + pickable_item->item_ID == CROSS__VANISH || pickable_item->item_ID == AXE__VANISH) { + droppingTargetYPos += 3.0f; + } + dropItemCalcFuncCalled = TRUE; + return TRUE; + } + if (pickable_item->position.y <= droppingTargetYPos) { + droppingSpeed = 0.0f; + dropItemCalcFuncCalled = FALSE; + return FALSE; + } + else { + if (droppingSpeed < maxDroppingSpeed) { + droppingSpeed += droppingAccel; + } + pickable_item->position.y -= droppingSpeed; + pickable_item->height = pickable_item->position.y; + return TRUE; + } +} \ No newline at end of file diff --git a/worlds/cv64/src/print.c b/worlds/cv64/src/print.c new file mode 100644 index 000000000000..7f77afb00f0f --- /dev/null +++ b/worlds/cv64/src/print.c @@ -0,0 +1,116 @@ +// Written by Moisés. +// NOTE: This is an earlier version to-be-replaced. +#include +#include + +// Helper function +// https://decomp.me/scratch/9H1Uy +u32 convertUTF8StringToUTF16(char* src, u16* buffer) { + u32 string_length = 0; + + // If the source string starts with a null char (0), we assume the string empty. + if (*src != 0) { + // Copy the char from the source string into the bufferination. + // Then advance to the next char until we find the null char (0). + do { + *buffer = *src; + src++; + buffer++; + string_length++; + } while (*src != 0); + } + // Make sure to add the null char at the end of the bufferination string, + // and then return the length of the string. + *buffer = 0; + return string_length; +} + +// Begin printing ASCII text stored in a char* +textbox* print_text(const char* message, const s16 X_pos, const s16 Y_pos, const u8 number_of_lines, const s16 textbox_width, const u32 txtbox_flags, const void* module) { + textbox* (*ptr_textbox_create)(void*, void*, u32) = textbox_create; + void (*ptr_textbox_setPos)(textbox*, u16, u16, s32) = textbox_setPos; + void (*ptr_textbox_setDimensions)(textbox*, s8, s16, u8, u8) = textbox_setDimensions; + void (*ptr_textbox_setMessagePtr)(textbox*, u16*, s32, s16) = textbox_setMessagePtr; + u16* (*ptr_convertUTF16ToCustomTextFormat)(u16*) = convertUTF16ToCustomTextFormat; + void* (*ptr_malloc)(s32, u32) = malloc; + + textbox* txtbox = NULL; + + // Allocate memory for the text buffer + u16* text_buffer = (u16*) ptr_malloc(0, 100); + + // Create the textbox data structure + if (module != NULL) { + txtbox = ptr_textbox_create(module, HUD_camera, txtbox_flags); + } + + if (txtbox != NULL && text_buffer != NULL && message != NULL) { + // Set text position and dimensions + ptr_textbox_setPos(txtbox, X_pos, Y_pos, 1); + ptr_textbox_setDimensions(txtbox, number_of_lines, textbox_width, 0, 0); + + // Convert the ASCII message to the CV64 custom format + convertUTF8StringToUTF16(message, text_buffer); + ptr_convertUTF16ToCustomTextFormat(text_buffer); + + // Set the text buffer pointer to the textbox data structure + ptr_textbox_setMessagePtr(txtbox, text_buffer, 0, 0); + } + // We return the textbox so that we can modify its properties once it begins printing + // (say to show, hide the text) + return txtbox; +} + +// Begin printing signed integer +textbox* print_number(const s32 number, u16* text_buffer, const s16 X_pos, const s16 Y_pos, const u8 number_of_digits, const u32 txtbox_flags, const u32 additional_text_flag, const void* module) { + textbox* (*ptr_textbox_create)(void*, void*, u32) = textbox_create; + void (*ptr_textbox_setPos)(textbox*, u16, u16, s32) = textbox_setPos; + void (*ptr_textbox_setDimensions)(textbox*, s8, s16, u8, u8) = textbox_setDimensions; + void (*ptr_textbox_setMessagePtr)(textbox*, u16*, s32, s16) = textbox_setMessagePtr; + void (*ptr_text_convertIntNumberToText)(u32, u16*, u8, u32) = text_convertIntNumberToText; + + textbox* txtbox = NULL; + + // Create the textbox data structure + if (module != NULL) { + txtbox = ptr_textbox_create(module, HUD_camera, txtbox_flags); + } + + if (txtbox != NULL && text_buffer != NULL) { + // Set text position and dimensions + ptr_textbox_setPos(txtbox, X_pos, Y_pos, 1); + ptr_textbox_setDimensions(txtbox, 1, 100, 0, 0); + + // Convert the number to the CV64 custom format + ptr_text_convertIntNumberToText(number, text_buffer, number_of_digits, additional_text_flag); + + // Set the text buffer pointer to the textbox data structure + ptr_textbox_setMessagePtr(txtbox, text_buffer, 0, 0); + } + // We return the textbox so that we can modify its properties once it begins printing + // (say to show, hide the text) + return txtbox; +} + +// Update the value of a number that began printing after calling "print_number()" +void update_printed_number(textbox* txtbox, const s32 number, u16* text_buffer, const u8 number_of_digits, const u32 additional_text_flag) { + void (*ptr_text_convertIntNumberToText)(u32, u16*, u8, u32) = text_convertIntNumberToText; + + if (text_buffer != NULL) { + ptr_text_convertIntNumberToText(number, text_buffer, number_of_digits, additional_text_flag); + txtbox->flags |= 0x1000000; // Needed to make sure the number updates properly + } +} + +void display_text(textbox* txtbox, const u8 display_textbox) { + if (txtbox != NULL) { + if (display_textbox == TRUE) { + // Show text + txtbox->flags &= ~HIDE_TEXTBOX; + } + else { + // Hide text + txtbox->flags |= HIDE_TEXTBOX; + } + } +} diff --git a/worlds/cv64/src/print_text_ovl.c b/worlds/cv64/src/print_text_ovl.c new file mode 100644 index 000000000000..7ca8e6f35e2d --- /dev/null +++ b/worlds/cv64/src/print_text_ovl.c @@ -0,0 +1,26 @@ +// Written by Moisés +#include "print.h" +#include +#include + +#define counter_X_pos 30 +#define counter_Y_pos 40 +#define counter_number_of_digits 2 +#define GOLD_JEWEL_FONT 0x14 + +extern u8 bytes[13]; + +u16* number_text_buffer = NULL; +textbox* txtbox = NULL; + +void begin_print() { + // Allocate memory for the number text + number_text_buffer = (u16*) malloc(0, 12); + + // Assuming that 0x80342814 = HUD Module + txtbox = print_number(0, number_text_buffer, counter_X_pos, counter_Y_pos, counter_number_of_digits, 0x08600000, GOLD_JEWEL_FONT, (void*) 0x80342814); +} + +void update_print(u8 i) { + update_printed_number(txtbox, (s32) bytes[i], number_text_buffer, counter_number_of_digits, GOLD_JEWEL_FONT); +} diff --git a/worlds/cv64/stages.py b/worlds/cv64/stages.py new file mode 100644 index 000000000000..d7059b3580f2 --- /dev/null +++ b/worlds/cv64/stages.py @@ -0,0 +1,490 @@ +import logging + +from .data import rname +from .regions import get_region_info +from .locations import get_location_info +from .options import WarpOrder + +from typing import TYPE_CHECKING, Dict, List, Tuple, Union + +if TYPE_CHECKING: + from . import CV64World + + +# # # KEY # # # +# "start region" = The Region that the start of the stage is in. Used for connecting the previous stage's end and +# alternate end (if it exists) Entrances to the start of the next one. +# "start map id" = The map ID that the start of the stage is in. +# "start spawn id" = The player spawn location ID for the start of the stage. This and "start map id" are both written +# to the previous stage's end loading zone to make it send the player to the next stage in the +# world's determined stage order. +# "mid region" = The Region that the stage's middle warp point is in. Used for connecting the warp Entrances after the +# starting stage to where they should be connecting to. +# "mid map id" = The map ID that the stage's middle warp point is in. +# "mid spawn id" = The player spawn location ID for the stage's middle warp point. This and "mid map id" are both +# written to the warp menu code to make it send the player to where it should be sending them. +# "end region" = The Region that the end of the stage is in. Used for connecting the next stage's beginning Entrance +# (if it exists) to the end of the previous one. +# "end map id" = The map ID that the end of the stage is in. +# "end spawn id" = The player spawn location ID for the end of the stage. This and "end map id" are both written to the +# next stage's beginning loading zone (if it exists) to make it send the player to the previous stage +# in the world's determined stage order. +# startzone map offset = The offset in the ROM to overwrite to change where the start of the stage leads. +# startzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the previous map the +# start of the stage puts the player at. +# endzone map offset = The offset in the ROM to overwrite to change where the end of the stage leads. +# endzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the next map the end of +# the stage puts the player at. +# altzone map offset = The offset in the ROM to overwrite to change where the alternate end of the stage leads +# (if it exists). +# altzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the next map the alternate +# end of the stage puts the player at. +# character = What character that stage is exclusively meant for normally. Used in determining what stages to leave out +# depending on what character stage setting was chosen in the player options. +# save number offsets = The offsets to overwrite to change what stage number is displayed on the save file when saving +# at the stage's White Jewels. +# regions = All Regions that make up the stage. If the stage is in the world's active stages, its Regions and their +# corresponding Locations and Entrances will all be created. +stage_info = { + "Forest of Silence": { + "start region": rname.forest_start, "start map id": b"\x00", "start spawn id": b"\x00", + "mid region": rname.forest_mid, "mid map id": b"\x00", "mid spawn id": b"\x04", + "end region": rname.forest_end, "end map id": b"\x00", "end spawn id": b"\x01", + "endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B, + "save number offsets": [0x1049C5, 0x1049CD, 0x1049D5], + "regions": [rname.forest_start, + rname.forest_mid, + rname.forest_end] + }, + + "Castle Wall": { + "start region": rname.cw_start, "start map id": b"\x02", "start spawn id": b"\x00", + "mid region": rname.cw_start, "mid map id": b"\x02", "mid spawn id": b"\x07", + "end region": rname.cw_exit, "end map id": b"\x02", "end spawn id": b"\x10", + "endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61, + "save number offsets": [0x1049DD, 0x1049E5, 0x1049ED], + "regions": [rname.cw_start, + rname.cw_exit, + rname.cw_ltower] + }, + + "Villa": { + "start region": rname.villa_start, "start map id": b"\x03", "start spawn id": b"\x00", + "mid region": rname.villa_storeroom, "mid map id": b"\x05", "mid spawn id": b"\x04", + "end region": rname.villa_crypt, "end map id": b"\x1A", "end spawn id": b"\x03", + "endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81, + "altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81, + "save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D], + "regions": [rname.villa_start, + rname.villa_main, + rname.villa_storeroom, + rname.villa_archives, + rname.villa_maze, + rname.villa_servants, + rname.villa_crypt] + }, + + "Tunnel": { + "start region": rname.tunnel_start, "start map id": b"\x07", "start spawn id": b"\x00", + "mid region": rname.tunnel_end, "mid map id": b"\x07", "mid spawn id": b"\x03", + "end region": rname.tunnel_end, "end map id": b"\x07", "end spawn id": b"\x11", + "endzone map offset": 0x109B4F, "endzone spawn offset": 0x109B51, "character": "Reinhardt", + "save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D], + "regions": [rname.tunnel_start, + rname.tunnel_end] + }, + + "Underground Waterway": { + "start region": rname.uw_main, "start map id": b"\x08", "start spawn id": b"\x00", + "mid region": rname.uw_main, "mid map id": b"\x08", "mid spawn id": b"\x03", + "end region": rname.uw_end, "end map id": b"\x08", "end spawn id": b"\x01", + "endzone map offset": 0x109B67, "endzone spawn offset": 0x109B69, "character": "Carrie", + "save number offsets": [0x104A35, 0x104A3D], + "regions": [rname.uw_main, + rname.uw_end] + }, + + "Castle Center": { + "start region": rname.cc_main, "start map id": b"\x19", "start spawn id": b"\x00", + "mid region": rname.cc_main, "mid map id": b"\x0E", "mid spawn id": b"\x03", + "end region": rname.cc_elev_top, "end map id": b"\x0F", "end spawn id": b"\x02", + "endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9, + "altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1, + "save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75], + "regions": [rname.cc_main, + rname.cc_torture_chamber, + rname.cc_library, + rname.cc_crystal, + rname.cc_elev_top] + }, + + "Duel Tower": { + "start region": rname.dt_main, "start map id": b"\x13", "start spawn id": b"\x00", + "startzone map offset": 0x109DA7, "startzone spawn offset": 0x109DA9, + "mid region": rname.dt_main, "mid map id": b"\x13", "mid spawn id": b"\x15", + "end region": rname.dt_main, "end map id": b"\x13", "end spawn id": b"\x01", + "endzone map offset": 0x109D8F, "endzone spawn offset": 0x109D91, "character": "Reinhardt", + "save number offsets": [0x104ACD], + "regions": [rname.dt_main] + }, + + "Tower of Execution": { + "start region": rname.toe_main, "start map id": b"\x10", "start spawn id": b"\x00", + "startzone map offset": 0x109D17, "startzone spawn offset": 0x109D19, + "mid region": rname.toe_main, "mid map id": b"\x10", "mid spawn id": b"\x02", + "end region": rname.toe_main, "end map id": b"\x10", "end spawn id": b"\x12", + "endzone map offset": 0x109CFF, "endzone spawn offset": 0x109D01, "character": "Reinhardt", + "save number offsets": [0x104A7D, 0x104A85], + "regions": [rname.toe_main, + rname.toe_ledge] + }, + + "Tower of Science": { + "start region": rname.tosci_start, "start map id": b"\x12", "start spawn id": b"\x00", + "startzone map offset": 0x109D77, "startzone spawn offset": 0x109D79, + "mid region": rname.tosci_conveyors, "mid map id": b"\x12", "mid spawn id": b"\x03", + "end region": rname.tosci_conveyors, "end map id": b"\x12", "end spawn id": b"\x04", + "endzone map offset": 0x109D5F, "endzone spawn offset": 0x109D61, "character": "Carrie", + "save number offsets": [0x104A95, 0x104A9D, 0x104AA5], + "regions": [rname.tosci_start, + rname.tosci_three_doors, + rname.tosci_conveyors, + rname.tosci_key3] + }, + + "Tower of Sorcery": { + "start region": rname.tosor_main, "start map id": b"\x11", "start spawn id": b"\x00", + "startzone map offset": 0x109D47, "startzone spawn offset": 0x109D49, + "mid region": rname.tosor_main, "mid map id": b"\x11", "mid spawn id": b"\x01", + "end region": rname.tosor_main, "end map id": b"\x11", "end spawn id": b"\x13", + "endzone map offset": 0x109D2F, "endzone spawn offset": 0x109D31, "character": "Carrie", + "save number offsets": [0x104A8D], + "regions": [rname.tosor_main] + }, + + "Room of Clocks": { + "start region": rname.roc_main, "start map id": b"\x1B", "start spawn id": b"\x00", + "mid region": rname.roc_main, "mid map id": b"\x1B", "mid spawn id": b"\x02", + "end region": rname.roc_main, "end map id": b"\x1B", "end spawn id": b"\x14", + "endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1, + "save number offsets": [0x104AC5], + "regions": [rname.roc_main] + }, + + "Clock Tower": { + "start region": rname.ct_start, "start map id": b"\x17", "start spawn id": b"\x00", + "mid region": rname.ct_middle, "mid map id": b"\x17", "mid spawn id": b"\x02", + "end region": rname.ct_end, "end map id": b"\x17", "end spawn id": b"\x03", + "endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39, + "save number offsets": [0x104AB5, 0x104ABD], + "regions": [rname.ct_start, + rname.ct_middle, + rname.ct_end] + }, + + "Castle Keep": { + "start region": rname.ck_main, "start map id": b"\x14", "start spawn id": b"\x02", + "mid region": rname.ck_main, "mid map id": b"\x14", "mid spawn id": b"\x03", + "end region": rname.ck_drac_chamber, + "save number offsets": [0x104AAD], + "regions": [rname.ck_main] + }, +} + +vanilla_stage_order = ("Forest of Silence", "Castle Wall", "Villa", "Tunnel", "Underground Waterway", "Castle Center", + "Duel Tower", "Tower of Execution", "Tower of Science", "Tower of Sorcery", "Room of Clocks", + "Clock Tower", "Castle Keep") + +# # # KEY # # # +# "prev" = The previous stage in the line. +# "next" = The next stage in the line. +# "alt" = The alternate next stage in the line (if one exists). +# "position" = The stage's number in the order of stages. +# "path" = Character indicating whether the stage is on the main path or an alternate path, similar to Rondo of Blood. +# Used in writing the randomized stage order to the spoiler. +vanilla_stage_exits = {rname.forest_of_silence: {"prev": None, "next": rname.castle_wall, + "alt": None, "position": 1, "path": " "}, + rname.castle_wall: {"prev": None, "next": rname.villa, + "alt": None, "position": 2, "path": " "}, + rname.villa: {"prev": None, "next": rname.tunnel, + "alt": rname.underground_waterway, "position": 3, "path": " "}, + rname.tunnel: {"prev": None, "next": rname.castle_center, + "alt": None, "position": 4, "path": " "}, + rname.underground_waterway: {"prev": None, "next": rname.castle_center, + "alt": None, "position": 4, "path": "'"}, + rname.castle_center: {"prev": None, "next": rname.duel_tower, + "alt": rname.tower_of_science, "position": 5, "path": " "}, + rname.duel_tower: {"prev": rname.castle_center, "next": rname.tower_of_execution, + "alt": None, "position": 6, "path": " "}, + rname.tower_of_execution: {"prev": rname.duel_tower, "next": rname.room_of_clocks, + "alt": None, "position": 7, "path": " "}, + rname.tower_of_science: {"prev": rname.castle_center, "next": rname.tower_of_sorcery, + "alt": None, "position": 6, "path": "'"}, + rname.tower_of_sorcery: {"prev": rname.tower_of_science, "next": rname.room_of_clocks, + "alt": None, "position": 7, "path": "'"}, + rname.room_of_clocks: {"prev": None, "next": rname.clock_tower, + "alt": None, "position": 8, "path": " "}, + rname.clock_tower: {"prev": None, "next": rname.castle_keep, + "alt": None, "position": 9, "path": " "}, + rname.castle_keep: {"prev": None, "next": None, + "alt": None, "position": 10, "path": " "}} + + +def get_stage_info(stage: str, info: str) -> Union[str, int, Union[List[int], List[str]], None]: + return stage_info[stage].get(info, None) + + +def get_locations_from_stage(stage: str) -> List[str]: + overall_locations = [] + for region in get_stage_info(stage, "regions"): + stage_locations = get_region_info(region, "locations") + if stage_locations is not None: + overall_locations += stage_locations + + final_locations = [] + for loc in overall_locations: + if get_location_info(loc, "code") is not None: + final_locations.append(loc) + return final_locations + + +def verify_character_stage(world: "CV64World", stage: str) -> bool: + # Verify a character stage is in the world if the given stage is a character stage. + stage_char = get_stage_info(stage, "character") + return stage_char is None or (world.reinhardt_stages and stage_char == "Reinhardt") or \ + (world.carrie_stages and stage_char == "Carrie") + + +def get_normal_stage_exits(world: "CV64World") -> Dict[str, dict]: + exits = {name: vanilla_stage_exits[name].copy() for name in vanilla_stage_exits} + non_branching_pos = 1 + + for stage in stage_info: + # Remove character stages that are not enabled. + if not verify_character_stage(world, stage): + del exits[stage] + continue + + # If branching pathways are not enabled, update the exit info to converge said stages on a single path. + if world.branching_stages: + continue + if world.carrie_stages and not world.reinhardt_stages and exits[stage]["alt"] is not None: + exits[stage]["next"] = exits[stage]["alt"] + elif world.carrie_stages and world.reinhardt_stages and stage != rname.castle_keep: + exits[stage]["next"] = vanilla_stage_order[vanilla_stage_order.index(stage) + 1] + exits[stage]["alt"] = None + exits[stage]["position"] = non_branching_pos + exits[stage]["path"] = " " + non_branching_pos += 1 + + return exits + + +def shuffle_stages(world: "CV64World", stage_1_blacklist: List[str]) \ + -> Tuple[Dict[str, Dict[str, Union[str, int, None]]], str, List[str]]: + """Woah, this is a lot! I should probably summarize what's happening in here, huh? + + So, in the vanilla game, all the stages are basically laid out on a linear "timeline" with some stages being + different depending on who you are playing as. The different character stages, in question, are the one following + Villa and the two following Castle Center. The ends of these two stages are considered the route divergences and, in + this rando, the game's behavior has been changed in such that both characters can access each other's exclusive + stages (thereby making the entire game playable in just one character run). With this in mind, when shuffling the + stages around, there is one particularly big rule that must be kept in mind to ensure things don't get too wacky. + That being: + + Villa and Castle Center cannot appear in branching path stage slots; they can only be on "main" path slots. + + So for this reason, generating a new stage layout is not as simple as just scrambling a list of stages around. It + must be done in such a way that whatever stages directly follow Villa or CC is not the other stage. The exception is + if branching stages are not a thing at all due to the player settings, in which case everything I said above does + not matter. Consider the following representation of a stage "timeline", wherein each "-" represents a main stage + and a "=" represents a pair of branching stages: + + -==---=--- + + In the above example, CC is the first "-" and Villa is the fourth. CC and Villa can only be "-"s whereas every other + stage can be literally anywhere, including on one of the "=" dashes. Villa will always be followed by one pair of + branching stages and CC will be followed by two pairs. + + This code starts by first generating a singular list of stages that fit the criteria of Castle Center not being in + the next two entries following Villa and Villa not being in the next four entries after Castle Center. Once that has + been figured out, it will then generate a dictionary of stages with the appropriate information regarding what + stages come before and after them to then be used for Entrance creation as well as what position in the list they + are in for the purposes of the spoiler log and extended hint information. + + I opted to use the Rondo of Blood "'" stage notation to represent Carrie stage slots specifically. If a main stage + with a backwards connection connects backwards into a pair of branching stages, it will be the non-"'" stage + (Reinhardt's) that it connects to. The Carrie stage slot cannot be accessed this way. + + If anyone has any ideas or suggestions on how to improve this, I'd love to hear them! Because it's only going to get + uglier come Legacy of Darkness and Cornell's funny side route later on. + """ + + starting_stage_value = world.options.starting_stage.value + + # Verify the starting stage is valid. If it isn't, pick a stage at random. + if vanilla_stage_order[starting_stage_value] not in stage_1_blacklist and \ + verify_character_stage(world, vanilla_stage_order[starting_stage_value]): + starting_stage = vanilla_stage_order[starting_stage_value] + else: + logging.warning(f"[{world.multiworld.player_name[world.player]}] {vanilla_stage_order[starting_stage_value]} " + f"cannot be the starting stage with the chosen settings. Picking a different stage instead...") + possible_stages = [] + for stage in vanilla_stage_order: + if stage in world.active_stage_exits and stage != rname.castle_keep: + possible_stages.append(stage) + starting_stage = world.random.choice(possible_stages) + world.options.starting_stage.value = vanilla_stage_order.index(starting_stage) + + remaining_stage_pool = [stage for stage in world.active_stage_exits] + remaining_stage_pool.remove(rname.castle_keep) + + total_stages = len(remaining_stage_pool) + + new_stage_order = [] + villa_cc_ids = [2, 3] + alt_villa_stage = [] + alt_cc_stages = [] + + # If there are branching stages, remove Villa and CC from the list and determine their placements first. + if world.branching_stages: + villa_cc_ids = world.random.sample(range(1, 5), 2) + remaining_stage_pool.remove(rname.villa) + remaining_stage_pool.remove(rname.castle_center) + + # Remove the starting stage from the remaining pool if it's in there at this point. + if starting_stage in remaining_stage_pool: + remaining_stage_pool.remove(starting_stage) + + # If Villa or CC is our starting stage, force its respective ID to be 0 and re-randomize the other. + if starting_stage == rname.villa: + villa_cc_ids[0] = 0 + villa_cc_ids[1] = world.random.randint(1, 5) + elif starting_stage == rname.castle_center: + villa_cc_ids[1] = 0 + villa_cc_ids[0] = world.random.randint(1, 5) + + for i in range(total_stages): + # If we're on Villa or CC's ID while in branching stage mode, put the respective stage in the slot. + if world.branching_stages and i == villa_cc_ids[0] and rname.villa not in new_stage_order: + new_stage_order.append(rname.villa) + villa_cc_ids[1] += 2 + elif world.branching_stages and i == villa_cc_ids[1] and rname.castle_center not in new_stage_order: + new_stage_order.append(rname.castle_center) + villa_cc_ids[0] += 4 + else: + # If neither of the above are true, if we're looking at Stage 1, append the starting stage. + # Otherwise, draw a random stage from the active list and delete it from there. + if i == 0: + new_stage_order.append(starting_stage) + else: + new_stage_order.append(world.random.choice(remaining_stage_pool)) + remaining_stage_pool.remove(new_stage_order[i]) + + # If we're looking at an alternate stage slot, put the stage in one of these lists to indicate it as such + if not world.branching_stages: + continue + if i - 2 >= 0: + if new_stage_order[i - 2] == rname.villa: + alt_villa_stage.append(new_stage_order[i]) + if i - 3 >= 0: + if new_stage_order[i - 3] == rname.castle_center: + alt_cc_stages.append(new_stage_order[i]) + if i - 4 >= 0: + if new_stage_order[i - 4] == rname.castle_center: + alt_cc_stages.append(new_stage_order[i]) + + new_stage_order.append(rname.castle_keep) + + # Update the dictionary of stage exits + current_stage_number = 1 + for i in range(len(new_stage_order)): + # Stage position number and alternate path indicator + world.active_stage_exits[new_stage_order[i]]["position"] = current_stage_number + if new_stage_order[i] in alt_villa_stage + alt_cc_stages: + world.active_stage_exits[new_stage_order[i]]["path"] = "'" + else: + world.active_stage_exits[new_stage_order[i]]["path"] = " " + + # Previous stage + if world.active_stage_exits[new_stage_order[i]]["prev"]: + if i - 1 < 0: + world.active_stage_exits[new_stage_order[i]]["prev"] = "Menu" + elif world.branching_stages: + if new_stage_order[i - 1] == alt_villa_stage[0] or new_stage_order[i] == alt_villa_stage[0]: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 2] + elif new_stage_order[i - 1] == alt_cc_stages[1] or new_stage_order[i] == alt_cc_stages[0]: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 3] + else: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 1] + else: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 1] + + # Next stage + if world.active_stage_exits[new_stage_order[i]]["next"]: + if world.branching_stages: + if new_stage_order[i + 1] == alt_villa_stage[0]: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 2] + current_stage_number -= 1 + elif new_stage_order[i + 1] == alt_cc_stages[0]: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 3] + current_stage_number -= 2 + else: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 1] + else: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 1] + + # Alternate next stage + if world.active_stage_exits[new_stage_order[i]]["alt"]: + if world.branching_stages: + if new_stage_order[i] == rname.villa: + world.active_stage_exits[new_stage_order[i]]["alt"] = alt_villa_stage[0] + else: + world.active_stage_exits[new_stage_order[i]]["alt"] = alt_cc_stages[0] + else: + world.active_stage_exits[new_stage_order[i]]["alt"] = None + + current_stage_number += 1 + + return world.active_stage_exits, starting_stage, new_stage_order + + +def generate_warps(world: "CV64World") -> List[str]: + # Create a list of warps from the active stage list. They are in a random order by default and will never + # include the starting stage. + possible_warps = [stage for stage in world.active_stage_list] + + # Remove the starting stage from the possible warps. + del (possible_warps[0]) + + active_warp_list = world.random.sample(possible_warps, 7) + + if world.options.warp_order == WarpOrder.option_seed_stage_order: + # Arrange the warps to be in the seed's stage order + new_list = world.active_stage_list.copy() + for warp in world.active_stage_list: + if warp not in active_warp_list: + new_list.remove(warp) + active_warp_list = new_list + elif world.options.warp_order == WarpOrder.option_vanilla_stage_order: + # Arrange the warps to be in the vanilla game's stage order + new_list = list(vanilla_stage_order) + for warp in vanilla_stage_order: + if warp not in active_warp_list: + new_list.remove(warp) + active_warp_list = new_list + + # Insert the starting stage at the start of the warp list + active_warp_list.insert(0, world.active_stage_list[0]) + + return active_warp_list + + +def get_region_names(active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> List[str]: + region_names = [] + for stage in active_stage_exits: + stage_regions = get_stage_info(stage, "regions") + for region in stage_regions: + region_names.append(region) + + return region_names diff --git a/worlds/cv64/test/__init__.py b/worlds/cv64/test/__init__.py new file mode 100644 index 000000000000..2d09e27cb316 --- /dev/null +++ b/worlds/cv64/test/__init__.py @@ -0,0 +1,6 @@ +from test.bases import WorldTestBase + + +class CV64TestBase(WorldTestBase): + game = "Castlevania 64" + player: int = 1 diff --git a/worlds/cv64/test/test_access.py b/worlds/cv64/test/test_access.py new file mode 100644 index 000000000000..79b1e14e11ea --- /dev/null +++ b/worlds/cv64/test/test_access.py @@ -0,0 +1,250 @@ +from . import CV64TestBase + + +class WarpTest(CV64TestBase): + options = { + "special1s_per_warp": 3, + "total_special1s": 21 + } + + def test_warps(self) -> None: + for i in range(1, 8): + self.assertFalse(self.can_reach_entrance(f"Warp {i}")) + self.collect([self.get_item_by_name("Special1")] * 2) + self.assertFalse(self.can_reach_entrance(f"Warp {i}")) + self.collect([self.get_item_by_name("Special1")] * 1) + self.assertTrue(self.can_reach_entrance(f"Warp {i}")) + + +class CastleWallTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 1 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance(f"Left Tower door")) + self.collect([self.get_item_by_name("Left Tower Key")] * 1) + self.assertTrue(self.can_reach_entrance(f"Left Tower door")) + + +class VillaTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 2 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("To Storeroom door")) + self.collect([self.get_item_by_name("Storeroom Key")] * 1) + self.assertTrue(self.can_reach_entrance("To Storeroom door")) + self.assertFalse(self.can_reach_entrance("To Archives door")) + self.collect([self.get_item_by_name("Archives Key")] * 1) + self.assertTrue(self.can_reach_entrance("To Archives door")) + self.assertFalse(self.can_reach_entrance("To maze gate")) + self.assertFalse(self.can_reach_entrance("Copper door")) + self.collect([self.get_item_by_name("Garden Key")] * 1) + self.assertTrue(self.can_reach_entrance("To maze gate")) + self.assertFalse(self.can_reach_entrance("Copper door")) + self.collect([self.get_item_by_name("Copper Key")] * 1) + self.assertTrue(self.can_reach_entrance("Copper door")) + + +class CastleCenterTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 5 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("Torture Chamber door")) + self.collect([self.get_item_by_name("Chamber Key")] * 1) + self.assertTrue(self.can_reach_entrance("Torture Chamber door")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.assertFalse(self.can_reach_entrance("Upper cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro")] * 1) + self.assertFalse(self.can_reach_entrance("Upper cracked wall")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Upper cracked wall")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro")] * 1) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Upper cracked wall")) + + +class ExecutionTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 7 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("Execution gate")) + self.collect([self.get_item_by_name("Execution Key")] * 1) + self.assertTrue(self.can_reach_entrance("Execution gate")) + + +class ScienceTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 8 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("Science Door 1")) + self.collect([self.get_item_by_name("Science Key1")] * 1) + self.assertTrue(self.can_reach_entrance("Science Door 1")) + self.assertFalse(self.can_reach_entrance("To Science Door 2")) + self.assertFalse(self.can_reach_entrance("Science Door 3")) + self.collect([self.get_item_by_name("Science Key2")] * 1) + self.assertTrue(self.can_reach_entrance("To Science Door 2")) + self.assertFalse(self.can_reach_entrance("Science Door 3")) + self.collect([self.get_item_by_name("Science Key3")] * 1) + self.assertTrue(self.can_reach_entrance("Science Door 3")) + + +class ClocktowerTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 11 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("To Clocktower Door 1")) + self.assertFalse(self.can_reach_entrance("To Clocktower Door 2")) + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Clocktower Key1")] * 1) + self.assertTrue(self.can_reach_entrance("To Clocktower Door 1")) + self.assertFalse(self.can_reach_entrance("To Clocktower Door 2")) + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Clocktower Key2")] * 1) + self.assertTrue(self.can_reach_entrance("To Clocktower Door 2")) + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Clocktower Key3")] * 1) + self.assertTrue(self.can_reach_entrance("Clocktower Door 3")) + + +class DraculaNoneTest(CV64TestBase): + options = { + "draculas_condition": 0, + "stage_shuffle": True, + "starting_stage": 5, + } + + def test_dracula_none_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Science Key1"), + self.get_item_by_name("Science Key2"), + self.get_item_by_name("Science Key3"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 1) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Special1")] * 7) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class DraculaSpecialTest(CV64TestBase): + options = { + "draculas_condition": 3 + } + + def test_dracula_special_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 2) + self.assertTrue(self.can_reach_entrance("Clocktower Door 3")) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Special2")] * 19) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Special2")] * 1) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class DraculaCrystalTest(CV64TestBase): + options = { + "draculas_condition": 1, + "stage_shuffle": True, + "starting_stage": 5, + "hard_logic": True + } + + def test_dracula_crystal_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Science Key1"), + self.get_item_by_name("Science Key2"), + self.get_item_by_name("Science Key3"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 1) + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Special1")] * 7) + self.assertTrue(self.can_reach_entrance("Slope Jump to boss tower")) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Lower sealed cracked wall")) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class DraculaBossTest(CV64TestBase): + options = { + "draculas_condition": 2, + "stage_shuffle": True, + "starting_stage": 5, + "hard_logic": True, + "bosses_required": 16 + } + + def test_dracula_boss_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Science Key1"), + self.get_item_by_name("Science Key2"), + self.get_item_by_name("Science Key3"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 1) + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Special1")] * 7) + self.assertTrue(self.can_reach_entrance("Slope Jump to boss tower")) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Lower sealed cracked wall")) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class LizardTest(CV64TestBase): + options = { + "stage_shuffle": True, + "draculas_condition": 2, + "starting_stage": 4 + } + + def test_lizard_man_trio(self) -> None: + self.assertTrue(self.can_reach_location("Underground Waterway: Lizard-man trio")) diff --git a/worlds/cv64/text.py b/worlds/cv64/text.py new file mode 100644 index 000000000000..3ba0b9153e9c --- /dev/null +++ b/worlds/cv64/text.py @@ -0,0 +1,98 @@ +from typing import Tuple + +cv64_char_dict = {"\n": (0x01, 0), " ": (0x02, 4), "!": (0x03, 2), '"': (0x04, 5), "#": (0x05, 6), "$": (0x06, 5), + "%": (0x07, 8), "&": (0x08, 7), "'": (0x09, 4), "(": (0x0A, 3), ")": (0x0B, 3), "*": (0x0C, 4), + "+": (0x0D, 5), ",": (0x0E, 3), "-": (0x0F, 4), ".": (0x10, 3), "/": (0x11, 6), "0": (0x12, 5), + "1": (0x13, 3), "2": (0x14, 5), "3": (0x15, 4), "4": (0x16, 5), "5": (0x17, 5), "6": (0x18, 5), + "7": (0x19, 5), "8": (0x1A, 5), "9": (0x1B, 5), ":": (0x1C, 3), ";": (0x1D, 3), "<": (0x1E, 3), + "=": (0x1F, 4), ">": (0x20, 3), "?": (0x21, 5), "@": (0x22, 8), "A": (0x23, 7), "B": (0x24, 6), + "C": (0x25, 5), "D": (0x26, 7), "E": (0x27, 5), "F": (0x28, 6), "G": (0x29, 6), "H": (0x2A, 7), + "I": (0x2B, 3), "J": (0x2C, 3), "K": (0x2D, 6), "L": (0x2E, 6), "M": (0x2F, 8), "N": (0x30, 7), + "O": (0x31, 6), "P": (0x32, 6), "Q": (0x33, 8), "R": (0x34, 6), "S": (0x35, 5), "T": (0x36, 6), + "U": (0x37, 6), "V": (0x38, 7), "W": (0x39, 8), "X": (0x3A, 6), "Y": (0x3B, 7), "Z": (0x3C, 6), + "[": (0x3D, 3), "\\": (0x3E, 6), "]": (0x3F, 3), "^": (0x40, 6), "_": (0x41, 5), "a": (0x43, 5), + "b": (0x44, 6), "c": (0x45, 4), "d": (0x46, 6), "e": (0x47, 5), "f": (0x48, 5), "g": (0x49, 5), + "h": (0x4A, 6), "i": (0x4B, 3), "j": (0x4C, 3), "k": (0x4D, 6), "l": (0x4E, 3), "m": (0x4F, 8), + "n": (0x50, 6), "o": (0x51, 5), "p": (0x52, 5), "q": (0x53, 5), "r": (0x54, 4), "s": (0x55, 4), + "t": (0x56, 4), "u": (0x57, 5), "v": (0x58, 6), "w": (0x59, 8), "x": (0x5A, 5), "y": (0x5B, 5), + "z": (0x5C, 4), "{": (0x5D, 4), "|": (0x5E, 2), "}": (0x5F, 3), "`": (0x61, 4), "「": (0x62, 3), + "ã€": (0x63, 3), "~": (0x65, 3), "″": (0x72, 3), "°": (0x73, 3), "∞": (0x74, 8)} +# [0] = CV64's in-game ID for that text character. +# [1] = How much space towards the in-game line length limit it contributes. + + +def cv64_string_to_bytearray(cv64text: str, a_advance: bool = False, append_end: bool = True) -> bytearray: + """Converts a string into a bytearray following CV64's string format.""" + text_bytes = bytearray(0) + for i, char in enumerate(cv64text): + if char == "\t": + text_bytes.extend([0xFF, 0xFF]) + else: + if char in cv64_char_dict: + text_bytes.extend([0x00, cv64_char_dict[char][0]]) + else: + text_bytes.extend([0x00, 0x21]) + + if a_advance: + text_bytes.extend([0xA3, 0x00]) + if append_end: + text_bytes.extend([0xFF, 0xFF]) + return text_bytes + + +def cv64_text_truncate(cv64text: str, textbox_len_limit: int) -> str: + """Truncates a string at a given in-game text line length.""" + line_len = 0 + + for i in range(len(cv64text)): + if cv64text[i] in cv64_char_dict: + line_len += cv64_char_dict[cv64text[i]][1] + else: + line_len += 5 + + if line_len > textbox_len_limit: + return cv64text[0x00:i] + + return cv64text + + +def cv64_text_wrap(cv64text: str, textbox_len_limit: int) -> Tuple[str, int]: + """Rebuilds a string with some of its spaces replaced with newlines to ensure the text wraps properly in an in-game + textbox of a given length.""" + words = cv64text.split(" ") + new_text = "" + line_len = 0 + num_lines = 1 + + for i in range(len(words)): + word_len = 0 + word_divider = " " + + if line_len != 0: + line_len += 4 + else: + word_divider = "" + + for char in words[i]: + if char in cv64_char_dict: + line_len += cv64_char_dict[char][1] + word_len += cv64_char_dict[char][1] + else: + line_len += 5 + word_len += 5 + + if word_len > textbox_len_limit or char in ["\n", "\t"]: + word_len = 0 + line_len = 0 + if num_lines < 4: + num_lines += 1 + + if line_len > textbox_len_limit: + word_divider = "\n" + line_len = word_len + if num_lines < 4: + num_lines += 1 + + new_text += word_divider + words[i] + + return new_text, num_lines diff --git a/worlds/dark_souls_3/Items.py b/worlds/dark_souls_3/Items.py index a13235b12aac..3dd5cb2d3c3f 100644 --- a/worlds/dark_souls_3/Items.py +++ b/worlds/dark_souls_3/Items.py @@ -1272,11 +1272,7 @@ def get_name_to_id() -> dict: ]] item_descriptions = { - "Cinders": """ - All four Cinders of a Lord. - - Once you have these four, you can fight Soul of Cinder and win the game. - """, + "Cinders": "All four Cinders of a Lord.\n\nOnce you have these four, you can fight Soul of Cinder and win the game.", } _all_items = _vanilla_items + _dlc_items diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index b4c231cdea1b..020010981160 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -35,6 +35,8 @@ class DarkSouls3Web(WebWorld): tutorials = [setup_en, setup_fr] + item_descriptions = item_descriptions + class DarkSouls3World(World): """ @@ -47,7 +49,6 @@ class DarkSouls3World(World): option_definitions = dark_souls_options topology_present: bool = True web = DarkSouls3Web() - data_version = 8 base_id = 100000 enabled_location_categories: Set[DS3LocationCategory] required_client_version = (0, 4, 2) @@ -61,8 +62,6 @@ class DarkSouls3World(World): "Cinders of a Lord - Lothric Prince" } } - item_descriptions = item_descriptions - def __init__(self, multiworld: MultiWorld, player: int): super().__init__(multiworld, player) diff --git a/worlds/dark_souls_3/docs/en_Dark Souls III.md b/worlds/dark_souls_3/docs/en_Dark Souls III.md index e844925df1ea..f31358bb9c2f 100644 --- a/worlds/dark_souls_3/docs/en_Dark Souls III.md +++ b/worlds/dark_souls_3/docs/en_Dark Souls III.md @@ -1,8 +1,8 @@ # Dark Souls III -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -13,7 +13,7 @@ location "Titanite Shard #5" is the fifth titanite shard you pick up, no matter happens when you randomize Estus Shards and Undead Bone Shards. It's also possible to randomize the upgrade level of weapons and shields as well as their infusions (if they can have -one). Additionally, there are settings that can make the randomized experience more convenient or more interesting, such as +one). Additionally, there are options that can make the randomized experience more convenient or more interesting, such as removing weapon requirements or auto-equipping whatever equipment you most recently received. The goal is to find the four "Cinders of a Lord" items randomized into the multiworld and defeat the Soul of Cinder. diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index 72c665af9507..61215dbc6043 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -25,31 +25,30 @@ To downpatch DS3 for use with Archipelago, use the following instructions from t 1. Launch Steam (in online mode). 2. Press the Windows Key + R. This will open the Run window. -3. Open the Steam console by typing the following string: steam://open/console , Steam should now open in Console Mode. -4. Insert the string of the depot you wish to download. For the AP supported v1.15, you will want to use: download_depot 374320 374321 4471176929659548333. -5. Steam will now download the depot. Note: There is no progress bar of the download in Steam, but it is still downloading in the background. -6. Turn off auto-updates in Steam by right-clicking Dark Souls III in your library > Properties > Updates > set "Automatic Updates" to "Only update this game when I launch it" (or change the value for AutoUpdateBehavior to 1 in "\Steam\steamapps\appmanifest_374320.acf"). -7. Back up your existing game folder in "\Steam\steamapps\common\DARK SOULS III". -8. Return back to Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like "\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX". Back up this game folder as well. -9. Delete your existing game folder in "\Steam\steamapps\common\DARK SOULS III", then replace it with your game folder in "\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX". -10. Back up and delete your save file "DS30000.sl2" in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type %appdata% and hit enter or: open File Explorer > View > Hidden Items and follow "C:\Users\your username\AppData\Roaming\DarkSoulsIII\numbers". -11. If you did all these steps correctly, you should be able to confirm your game version in the upper left corner after launching Dark Souls III. +3. Open the Steam console by typing the following string: `steam://open/console`. Steam should now open in Console Mode. +4. Insert the string of the depot you wish to download. For the AP-supported v1.15, you will want to use: `download_depot 374320 374321 4471176929659548333`. +5. Steam will now download the depot. Note: There is no progress bar for the download in Steam, but it is still downloading in the background. +6. Back up your existing game executable (`DarkSoulsIII.exe`) found in `\Steam\steamapps\common\DARK SOULS III\Game`. Easiest way to do this is to move it to another directory. If you have file extensions enabled, you can instead rename the executable to `DarkSoulsIII.exe.bak`. +7. Return to the Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like `\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX`. +8. Take the `DarkSoulsIII.exe` from that folder and place it in `\Steam\steamapps\common\DARK SOULS III\Game`. +9. Back up and delete your save file (`DS30000.sl2`) in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type `%appdata%` and hit enter. Alternatively: open File Explorer > View > Hidden Items and follow `C:\Users\\AppData\Roaming\DarkSoulsIII\`. +10. If you did all these steps correctly, you should be able to confirm your game version in the upper-left corner after launching Dark Souls III. ## Installing the Archipelago mod -Get the dinput8.dll from the [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases) and -add it at the root folder of your game (e.g. "SteamLibrary\steamapps\common\DARK SOULS III\Game") +Get the `dinput8.dll` from the [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases) and +add it at the root folder of your game (e.g. `SteamLibrary\steamapps\common\DARK SOULS III\Game`) ## Joining a MultiWorld Game -1. Run Steam in offline mode, both to avoid being banned and to prevent Steam from updating the game files -2. Launch Dark Souls III -3. Type in "/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME}" in the "Windows Command Prompt" that opened -4. Once connected, create a new game, choose a class and wait for the others before starting -5. You can quit and launch at anytime during a game +1. Run Steam in offline mode to avoid being banned. +2. Launch Dark Souls III. +3. Type in `/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME} password:{PASSWORD}` in the "Windows Command Prompt" that opened. For example: `/connect archipelago.gg:38281 "Example Name" password:"Example Password"`. The password parameter is only necessary if your game requires one. +4. Once connected, create a new game, choose a class and wait for the others before starting. +5. You can quit and launch at anytime during a game. ## Where do I get a config file? -The [Player Settings](/games/Dark%20Souls%20III/player-settings) page on the website allows you to -configure your personal settings and export them into a config file. +The [Player Options](/games/Dark%20Souls%20III/player-options) page on the website allows you to +configure your personal options and export them into a config file. diff --git a/worlds/dark_souls_3/docs/setup_fr.md b/worlds/dark_souls_3/docs/setup_fr.md index 769d331bb98d..ea4d8f818604 100644 --- a/worlds/dark_souls_3/docs/setup_fr.md +++ b/worlds/dark_souls_3/docs/setup_fr.md @@ -29,5 +29,5 @@ placez-le à la racine du jeu (ex: "SteamLibrary\steamapps\common\DARK SOULS III ## Où trouver le fichier de configuration ? -La [Page de configuration](/games/Dark%20Souls%20III/player-settings) sur le site vous permez de configurer vos +La [Page de configuration](/games/Dark%20Souls%20III/player-options) sur le site vous permez de configurer vos paramètres et de les exporter sous la forme d'un fichier. diff --git a/worlds/dkc3/CHANGELOG.md b/worlds/dkc3/CHANGELOG.md new file mode 100644 index 000000000000..dc853a5aa944 --- /dev/null +++ b/worlds/dkc3/CHANGELOG.md @@ -0,0 +1,47 @@ +# Donkey Kong Country 3 - Changelog + + +## v1.1 + +### Features: + +- KONGsanity option (Collect all KONG letters in each level for a check) +- Autosave option +- Difficulty option +- MERRY option +- Handle collected/co-op locations + +### Bug Fixes: + +- Fixed Mekanos softlock +- Prevent Brothers Bear giving extra Banana Birds +- Fixed Banana Bird Mother check sending prematurely +- Fix Logic bug with Krematoa level costs + + +## v1.0 + +### Features: + +- Goal + - Knautilus + - Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein to win + - Banana Bird Hunt + - Find the Banana Birds and rescue their mother to win +- Locations included: + - Level Flags + - Bonuses + - DK Coins + - Banana Bird Caves +- Items included: + - Progressive Boat Upgrade + - Three are placed into the item pool (Patch -> First Ski -> Second Ski) + - Bonus Coins + - DK Coins + - Krematoa Cogs + - Bear Coins + - 1-Up Balloons +- Level Shuffle is supported +- Music Shuffle is supported +- Kong Palette options are supported +- Starting life count can be set diff --git a/worlds/dkc3/Client.py b/worlds/dkc3/Client.py index efa199e1d0c9..ee2bd1dbdfb8 100644 --- a/worlds/dkc3/Client.py +++ b/worlds/dkc3/Client.py @@ -60,7 +60,7 @@ async def game_watcher(self, ctx): return new_checks = [] - from worlds.dkc3.Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map + from .Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map location_ram_data = await snes_read(ctx, WRAM_START + 0x5FE, 0x81) for loc_id, loc_data in location_rom_data.items(): if loc_id not in ctx.locations_checked: @@ -86,7 +86,7 @@ async def game_watcher(self, ctx): for new_check_id in new_checks: ctx.locations_checked.add(new_check_id) - location = ctx.location_names[new_check_id] + location = ctx.location_names.lookup_in_game(new_check_id) snes_logger.info( f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) @@ -99,9 +99,9 @@ async def game_watcher(self, ctx): item = ctx.items_received[recv_index] recv_index += 1 logging.info('Received %s from %s (%s) (%d/%d in list)' % ( - color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - ctx.location_names[item.location], recv_index, len(ctx.items_received))) + ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received))) snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index])) if item.item in item_rom_data: diff --git a/worlds/dkc3/Names/LocationName.py b/worlds/dkc3/Names/LocationName.py index f79a25f143de..dbd63623ab22 100644 --- a/worlds/dkc3/Names/LocationName.py +++ b/worlds/dkc3/Names/LocationName.py @@ -294,7 +294,7 @@ blue_region = "Blue's Beach Hut Region" blizzard_region = "Bizzard's Basecamp Region" -lake_orangatanga_region = "Lake_Orangatanga" +lake_orangatanga_region = "Lake Orangatanga" kremwood_forest_region = "Kremwood Forest" cotton_top_cove_region = "Cotton-Top Cove" mekanos_region = "Mekanos" diff --git a/worlds/dkc3/Options.py b/worlds/dkc3/Options.py index 06be30cf15ae..b114a503b982 100644 --- a/worlds/dkc3/Options.py +++ b/worlds/dkc3/Options.py @@ -1,13 +1,15 @@ from dataclasses import dataclass import typing -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, PerGameCommonOptions +from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions class Goal(Choice): """ Determines the goal of the seed + Knautilus: Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein + Banana Bird Hunt: Find a certain number of Banana Birds and rescue their mother """ display_name = "Goal" @@ -26,6 +28,7 @@ class IncludeTradeSequence(Toggle): class DKCoinsForGyrocopter(Range): """ How many DK Coins are needed to unlock the Gyrocopter + Note: Achieving this number before unlocking the Turbo Ski will cause the game to grant you a one-time upgrade to the next non-unlocked boat, until you return to Funky. Logic does not assume that you will use this. @@ -93,6 +96,7 @@ class LevelShuffle(Toggle): class Difficulty(Choice): """ Which Difficulty Level to use + NORML: The Normal Difficulty HARDR: Many DK Barrels are removed TUFST: Most DK Barrels and all Midway Barrels are removed @@ -159,19 +163,40 @@ class StartingLifeCount(Range): default = 5 +dkc3_option_groups = [ + OptionGroup("Goal Options", [ + Goal, + KrematoaBonusCoinCost, + PercentageOfExtraBonusCoins, + NumberOfBananaBirds, + PercentageOfBananaBirds, + ]), + OptionGroup("Aesthetics", [ + Autosave, + MERRY, + MusicShuffle, + KongPaletteSwap, + StartingLifeCount, + ]), +] + + @dataclass class DKC3Options(PerGameCommonOptions): #death_link: DeathLink # Disabled - goal: Goal #include_trade_sequence: IncludeTradeSequence # Disabled - dk_coins_for_gyrocopter: DKCoinsForGyrocopter + + goal: Goal krematoa_bonus_coin_cost: KrematoaBonusCoinCost percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins number_of_banana_birds: NumberOfBananaBirds percentage_of_banana_birds: PercentageOfBananaBirds + + dk_coins_for_gyrocopter: DKCoinsForGyrocopter kongsanity: KONGsanity level_shuffle: LevelShuffle difficulty: Difficulty + autosave: Autosave merry: MERRY music_shuffle: MusicShuffle diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py index efe8033d0fa5..0dc722a73868 100644 --- a/worlds/dkc3/Rom.py +++ b/worlds/dkc3/Rom.py @@ -434,7 +434,7 @@ 0x21, ] -class LocalRom(object): +class LocalRom: def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): self.name = name @@ -457,7 +457,7 @@ def read_bit(self, address: int, bit_number: int) -> bool: def read_byte(self, address: int) -> int: return self.buffer[address] - def read_bytes(self, startaddress: int, length: int) -> bytes: + def read_bytes(self, startaddress: int, length: int) -> bytearray: return self.buffer[startaddress:startaddress + length] def write_byte(self, address: int, value: int): diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index dfb42bd04ca8..de6fb4a44a03 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -4,20 +4,21 @@ import math import threading -import settings from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification from Options import PerGameCommonOptions +import Patch +import settings +from worlds.AutoWorld import WebWorld, World + +from .Client import DKC3SNIClient from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table -from .Locations import DKC3Location, all_locations, setup_locations -from .Options import DKC3Options -from .Regions import create_regions, connect_regions from .Levels import level_list -from .Rules import set_rules +from .Locations import DKC3Location, all_locations, setup_locations from .Names import ItemName, LocationName -from .Client import DKC3SNIClient -from worlds.AutoWorld import WebWorld, World +from .Options import DKC3Options, dkc3_option_groups +from .Regions import create_regions, connect_regions from .Rom import LocalRom, patch_rom, get_base_rom_path, DKC3DeltaPatch -import Patch +from .Rules import set_rules class DK3Settings(settings.Group): @@ -41,9 +42,11 @@ class DKC3Web(WebWorld): "setup/en", ["PoryGone"] ) - + tutorials = [setup_en] + option_groups = dkc3_option_groups + class DKC3World(World): """ @@ -58,7 +61,6 @@ class DKC3World(World): options: DKC3Options topology_present = False - data_version = 2 #hint_blacklist = {LocationName.rocket_rush_flag} item_name_to_id = {name: data.code for name, data in item_table.items()} @@ -201,7 +203,12 @@ def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, s er_hint_data = {} for world_index in range(len(world_names)): for level_index in range(5): - level_region = self.multiworld.get_region(self.active_level_list[world_index * 5 + level_index], self.player) + level_id: int = world_index * 5 + level_index + + if level_id >= len(self.active_level_list): + break + + level_region = self.multiworld.get_region(self.active_level_list[level_id], self.player) for location in level_region.locations: er_hint_data[location.address] = world_names[world_index] diff --git a/worlds/dkc3/docs/en_Donkey Kong Country 3.md b/worlds/dkc3/docs/en_Donkey Kong Country 3.md index 2041f0a41bd2..83ba25a9960b 100644 --- a/worlds/dkc3/docs/en_Donkey Kong Country 3.md +++ b/worlds/dkc3/docs/en_Donkey Kong Country 3.md @@ -1,8 +1,8 @@ # Donkey Kong Country 3 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/dkc3/docs/setup_en.md b/worlds/dkc3/docs/setup_en.md index 236d1cb8ad32..c832bcd702e9 100644 --- a/worlds/dkc3/docs/setup_en.md +++ b/worlds/dkc3/docs/setup_en.md @@ -45,8 +45,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Donkey Kong Country 3 Player Settings Page](/games/Donkey%20Kong%20Country%203/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Donkey Kong Country 3 Player Options Page](/games/Donkey%20Kong%20Country%203/player-options) ### Verifying your config file @@ -55,8 +55,8 @@ validator page: [YAML Validation page](/check) ## Generating a Single-Player Game -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [Donkey Kong Country 3 Player Settings Page](/games/Donkey%20Kong%20Country%203/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [Donkey Kong Country 3 Player Options Page](/games/Donkey%20Kong%20Country%203/player-options) 2. You will be presented with a "Seed Info" page. 3. Click the "Create New Room" link. 4. You will be presented with a server page, from which you can download your patch file. diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py index 2200729a3210..b8f2aad6ff94 100644 --- a/worlds/dlcquest/__init__.py +++ b/worlds/dlcquest/__init__.py @@ -8,11 +8,15 @@ from .Options import DLCQuestOptions from .Regions import create_regions from .Rules import set_rules +from .presets import dlcq_options_presets +from .option_groups import dlcq_option_groups client_version = 0 class DLCqwebworld(WebWorld): + options_presets = dlcq_options_presets + option_groups = dlcq_option_groups setup_en = Tutorial( "Multiworld Setup Guide", "A guide to setting up the Archipelago DLCQuest game on your computer.", @@ -43,8 +47,6 @@ class DLCqworld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = location_table - data_version = 1 - options_dataclass = DLCQuestOptions options: DLCQuestOptions @@ -61,7 +63,7 @@ def create_items(self): self.precollect_coinsanity() locations_count = len([location for location in self.multiworld.get_locations(self.player) - if not location.event]) + if not location.advancement]) items_to_exclude = [excluded_items for excluded_items in self.multiworld.precollected_items[self.player]] diff --git a/worlds/dlcquest/docs/en_DLCQuest.md b/worlds/dlcquest/docs/en_DLCQuest.md index eaccc8ff0a46..0ae8f2291e1f 100644 --- a/worlds/dlcquest/docs/en_DLCQuest.md +++ b/worlds/dlcquest/docs/en_DLCQuest.md @@ -1,8 +1,8 @@ # DLC Quest -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/dlcquest/docs/fr_DLCQuest.md b/worlds/dlcquest/docs/fr_DLCQuest.md index 95a8048dfe5e..25f2d728160e 100644 --- a/worlds/dlcquest/docs/fr_DLCQuest.md +++ b/worlds/dlcquest/docs/fr_DLCQuest.md @@ -2,7 +2,7 @@ ## Où se trouve la page des paramètres ? -La [page des paramètres du joueur pour ce jeu](../player-settings) contient tous les paramètres dont vous avez besoin pour configurer et exporter le fichier. +La [page des paramètres du joueur pour ce jeu](../player-options) contient tous les paramètres dont vous avez besoin pour configurer et exporter le fichier. ## Quel est l'effet de la randomisation sur ce jeu ? @@ -46,4 +46,4 @@ Il y a aussi de nouveaux objets pièges, utilisés comme substituts, basés sur Chaque fois qu'un objet est reçu en ligne, une notification apparaît à l'écran pour en informer le joueur. Certains objets sont accompagnés d'une animation ou d'une scène qui se déroule immédiatement après leur réception. -Les objets reçus hors ligne ne sont pas accompagnés d'une animation ou d'une scène, et sont simplement activés lors de la connexion. \ No newline at end of file +Les objets reçus hors ligne ne sont pas accompagnés d'une animation ou d'une scène, et sont simplement activés lors de la connexion. diff --git a/worlds/dlcquest/docs/setup_en.md b/worlds/dlcquest/docs/setup_en.md index 47e22e0f74ee..7c82b9d69fc0 100644 --- a/worlds/dlcquest/docs/setup_en.md +++ b/worlds/dlcquest/docs/setup_en.md @@ -19,7 +19,7 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a YAML file? -You can customize your settings by visiting the [DLC Quest Player Settings Page](/games/DLCQuest/player-settings) +You can customize your options by visiting the [DLC Quest Player Options Page](/games/DLCQuest/player-options) ## Joining a MultiWorld Game diff --git a/worlds/dlcquest/docs/setup_fr.md b/worlds/dlcquest/docs/setup_fr.md index 78c69eb5a729..e4b431215d47 100644 --- a/worlds/dlcquest/docs/setup_fr.md +++ b/worlds/dlcquest/docs/setup_fr.md @@ -18,7 +18,7 @@ Voir le guide d'Archipelago sur la mise en place d'un YAML de base : [Basic Mult ### Où puis-je obtenir un fichier YAML ? -Vous pouvez personnaliser vos paramètres en visitant la [page des paramètres du joueur DLC Quest] (/games/DLCQuest/player-settings). +Vous pouvez personnaliser vos paramètres en visitant la [page des paramètres du joueur DLC Quest](/games/DLCQuest/player-options). ## Rejoindre une partie multi-monde @@ -52,4 +52,4 @@ Vous pouvez personnaliser vos paramètres en visitant la [page des paramètres d Vous ne pouvez pas envoyer de commandes au serveur ou discuter avec les autres joueurs depuis DLC Quest, car le jeu ne dispose pas d'un moyen approprié pour saisir du texte. Vous pouvez suivre l'activité du serveur dans votre console BepInEx, car les messages de chat d'Archipelago y seront affichés. -Vous devrez utiliser [Archipelago Text Client] (https://github.com/ArchipelagoMW/Archipelago/releases) si vous voulez envoyer des commandes. \ No newline at end of file +Vous devrez utiliser [Archipelago Text Client] (https://github.com/ArchipelagoMW/Archipelago/releases) si vous voulez envoyer des commandes. diff --git a/worlds/dlcquest/option_groups.py b/worlds/dlcquest/option_groups.py new file mode 100644 index 000000000000..9510c061e18f --- /dev/null +++ b/worlds/dlcquest/option_groups.py @@ -0,0 +1,27 @@ +from typing import List + +from Options import ProgressionBalancing, Accessibility, OptionGroup +from .Options import (Campaign, ItemShuffle, TimeIsMoney, EndingChoice, PermanentCoins, DoubleJumpGlitch, CoinSanity, + CoinSanityRange, DeathLink) + +dlcq_option_groups: List[OptionGroup] = [ + OptionGroup("General", [ + Campaign, + ItemShuffle, + CoinSanity, + ]), + OptionGroup("Customization", [ + EndingChoice, + PermanentCoins, + CoinSanityRange, + ]), + OptionGroup("Tedious and Grind", [ + TimeIsMoney, + DoubleJumpGlitch, + ]), + OptionGroup("Advanced Options", [ + DeathLink, + ProgressionBalancing, + Accessibility, + ]), +] diff --git a/worlds/dlcquest/presets.py b/worlds/dlcquest/presets.py new file mode 100644 index 000000000000..ccfd79399521 --- /dev/null +++ b/worlds/dlcquest/presets.py @@ -0,0 +1,68 @@ +from typing import Any, Dict + +from .Options import DoubleJumpGlitch, CoinSanity, CoinSanityRange, PermanentCoins, TimeIsMoney, EndingChoice, Campaign, ItemShuffle + +all_random_settings = { + DoubleJumpGlitch.internal_name: "random", + CoinSanity.internal_name: "random", + CoinSanityRange.internal_name: "random", + PermanentCoins.internal_name: "random", + TimeIsMoney.internal_name: "random", + EndingChoice.internal_name: "random", + Campaign.internal_name: "random", + ItemShuffle.internal_name: "random", + "death_link": "random", +} + +main_campaign_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none, + CoinSanity.internal_name: CoinSanity.option_coin, + CoinSanityRange.internal_name: 30, + PermanentCoins.internal_name: PermanentCoins.option_false, + TimeIsMoney.internal_name: TimeIsMoney.option_required, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_basic, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + +lfod_campaign_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none, + CoinSanity.internal_name: CoinSanity.option_coin, + CoinSanityRange.internal_name: 30, + PermanentCoins.internal_name: PermanentCoins.option_false, + TimeIsMoney.internal_name: TimeIsMoney.option_required, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_live_freemium_or_die, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + +easy_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none, + CoinSanity.internal_name: CoinSanity.option_none, + CoinSanityRange.internal_name: 40, + PermanentCoins.internal_name: PermanentCoins.option_true, + TimeIsMoney.internal_name: TimeIsMoney.option_required, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_both, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + +hard_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_simple, + CoinSanity.internal_name: CoinSanity.option_coin, + CoinSanityRange.internal_name: 30, + PermanentCoins.internal_name: PermanentCoins.option_false, + TimeIsMoney.internal_name: TimeIsMoney.option_optional, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_both, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + + +dlcq_options_presets: Dict[str, Dict[str, Any]] = { + "All random": all_random_settings, + "Main campaign": main_campaign_settings, + "LFOD campaign": lfod_campaign_settings, + "Both easy": easy_settings, + "Both hard": hard_settings, +} diff --git a/worlds/dlcquest/test/checks/world_checks.py b/worlds/dlcquest/test/checks/world_checks.py index a97093d62036..cc2fa7f51ad2 100644 --- a/worlds/dlcquest/test/checks/world_checks.py +++ b/worlds/dlcquest/test/checks/world_checks.py @@ -10,7 +10,7 @@ def get_all_item_names(multiworld: MultiWorld) -> List[str]: def get_all_location_names(multiworld: MultiWorld) -> List[str]: - return [location.name for location in multiworld.get_locations() if not location.event] + return [location.name for location in multiworld.get_locations() if not location.advancement] def assert_victory_exists(tester: DLCQuestTestBase, multiworld: MultiWorld): @@ -38,5 +38,5 @@ def assert_can_win(tester: DLCQuestTestBase, multiworld: MultiWorld): def assert_same_number_items_locations(tester: DLCQuestTestBase, multiworld: MultiWorld): - non_event_locations = [location for location in multiworld.get_locations() if not location.event] + non_event_locations = [location for location in multiworld.get_locations() if not location.advancement] tester.assertEqual(len(multiworld.itempool), len(non_event_locations)) \ No newline at end of file diff --git a/worlds/doom_1993/Options.py b/worlds/doom_1993/Options.py index 59f7bcef49a2..f65952d3eb49 100644 --- a/worlds/doom_1993/Options.py +++ b/worlds/doom_1993/Options.py @@ -1,6 +1,5 @@ -import typing - -from Options import AssembleOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool +from Options import PerGameCommonOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool +from dataclasses import dataclass class Goal(Choice): @@ -140,21 +139,22 @@ class Episode4(Toggle): display_name = "Episode 4" -options: typing.Dict[str, AssembleOptions] = { - "start_inventory_from_pool": StartInventoryPool, - "goal": Goal, - "difficulty": Difficulty, - "random_monsters": RandomMonsters, - "random_pickups": RandomPickups, - "random_music": RandomMusic, - "flip_levels": FlipLevels, - "allow_death_logic": AllowDeathLogic, - "pro": Pro, - "start_with_computer_area_maps": StartWithComputerAreaMaps, - "death_link": DeathLink, - "reset_level_on_death": ResetLevelOnDeath, - "episode1": Episode1, - "episode2": Episode2, - "episode3": Episode3, - "episode4": Episode4 -} +@dataclass +class DOOM1993Options(PerGameCommonOptions): + start_inventory_from_pool: StartInventoryPool + goal: Goal + difficulty: Difficulty + random_monsters: RandomMonsters + random_pickups: RandomPickups + random_music: RandomMusic + flip_levels: FlipLevels + allow_death_logic: AllowDeathLogic + pro: Pro + start_with_computer_area_maps: StartWithComputerAreaMaps + death_link: DeathLink + reset_level_on_death: ResetLevelOnDeath + episode1: Episode1 + episode2: Episode2 + episode3: Episode3 + episode4: Episode4 + diff --git a/worlds/doom_1993/Rules.py b/worlds/doom_1993/Rules.py index d5abc367a149..4faeb4a27dbd 100644 --- a/worlds/doom_1993/Rules.py +++ b/worlds/doom_1993/Rules.py @@ -7,105 +7,105 @@ from . import DOOM1993World -def set_episode1_rules(player, world, pro): +def set_episode1_rules(player, multiworld, pro): # Hangar (E1M1) - set_rule(world.get_entrance("Hub -> Hangar (E1M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Hangar (E1M1) Main", player), lambda state: state.has("Hangar (E1M1)", player, 1)) # Nuclear Plant (E1M2) - set_rule(world.get_entrance("Hub -> Nuclear Plant (E1M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Nuclear Plant (E1M2) Main", player), lambda state: (state.has("Nuclear Plant (E1M2)", player, 1)) and (state.has("Shotgun", player, 1) or state.has("Chaingun", player, 1))) - set_rule(world.get_entrance("Nuclear Plant (E1M2) Main -> Nuclear Plant (E1M2) Red", player), lambda state: + set_rule(multiworld.get_entrance("Nuclear Plant (E1M2) Main -> Nuclear Plant (E1M2) Red", player), lambda state: state.has("Nuclear Plant (E1M2) - Red keycard", player, 1)) - set_rule(world.get_entrance("Nuclear Plant (E1M2) Red -> Nuclear Plant (E1M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Nuclear Plant (E1M2) Red -> Nuclear Plant (E1M2) Main", player), lambda state: state.has("Nuclear Plant (E1M2) - Red keycard", player, 1)) # Toxin Refinery (E1M3) - set_rule(world.get_entrance("Hub -> Toxin Refinery (E1M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Toxin Refinery (E1M3) Main", player), lambda state: (state.has("Toxin Refinery (E1M3)", player, 1)) and (state.has("Shotgun", player, 1) or state.has("Chaingun", player, 1))) - set_rule(world.get_entrance("Toxin Refinery (E1M3) Main -> Toxin Refinery (E1M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Main -> Toxin Refinery (E1M3) Blue", player), lambda state: state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Yellow", player), lambda state: state.has("Toxin Refinery (E1M3) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Main", player), lambda state: state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Toxin Refinery (E1M3) Yellow -> Toxin Refinery (E1M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Yellow -> Toxin Refinery (E1M3) Blue", player), lambda state: state.has("Toxin Refinery (E1M3) - Yellow keycard", player, 1)) # Command Control (E1M4) - set_rule(world.get_entrance("Hub -> Command Control (E1M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Command Control (E1M4) Main", player), lambda state: state.has("Command Control (E1M4)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) - set_rule(world.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Blue", player), lambda state: state.has("Command Control (E1M4) - Blue keycard", player, 1) or state.has("Command Control (E1M4) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Yellow", player), lambda state: state.has("Command Control (E1M4) - Blue keycard", player, 1) or state.has("Command Control (E1M4) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Command Control (E1M4) Blue -> Command Control (E1M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Command Control (E1M4) Blue -> Command Control (E1M4) Main", player), lambda state: state.has("Command Control (E1M4) - Yellow keycard", player, 1) or state.has("Command Control (E1M4) - Blue keycard", player, 1)) # Phobos Lab (E1M5) - set_rule(world.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state: state.has("Phobos Lab (E1M5)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) - set_rule(world.get_entrance("Phobos Lab (E1M5) Main -> Phobos Lab (E1M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Main -> Phobos Lab (E1M5) Yellow", player), lambda state: state.has("Phobos Lab (E1M5) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Phobos Lab (E1M5) Yellow -> Phobos Lab (E1M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Yellow -> Phobos Lab (E1M5) Blue", player), lambda state: state.has("Phobos Lab (E1M5) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Phobos Lab (E1M5) Blue -> Phobos Lab (E1M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Blue -> Phobos Lab (E1M5) Green", player), lambda state: state.has("Phobos Lab (E1M5) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Phobos Lab (E1M5) Green -> Phobos Lab (E1M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Green -> Phobos Lab (E1M5) Blue", player), lambda state: state.has("Phobos Lab (E1M5) - Blue keycard", player, 1)) # Central Processing (E1M6) - set_rule(world.get_entrance("Hub -> Central Processing (E1M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Central Processing (E1M6) Main", player), lambda state: state.has("Central Processing (E1M6)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Rocket launcher", player, 1)) - set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Yellow", player), lambda state: state.has("Central Processing (E1M6) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Red", player), lambda state: + set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Red", player), lambda state: state.has("Central Processing (E1M6) - Red keycard", player, 1)) - set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Blue", player), lambda state: state.has("Central Processing (E1M6) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Nukage", player), lambda state: + set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Nukage", player), lambda state: state.has("Central Processing (E1M6) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Central Processing (E1M6) Yellow -> Central Processing (E1M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Central Processing (E1M6) Yellow -> Central Processing (E1M6) Main", player), lambda state: state.has("Central Processing (E1M6) - Yellow keycard", player, 1)) # Computer Station (E1M7) - set_rule(world.get_entrance("Hub -> Computer Station (E1M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Computer Station (E1M7) Main", player), lambda state: state.has("Computer Station (E1M7)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Rocket launcher", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Red", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Red", player), lambda state: state.has("Computer Station (E1M7) - Red keycard", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Yellow", player), lambda state: state.has("Computer Station (E1M7) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Blue -> Computer Station (E1M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Blue -> Computer Station (E1M7) Yellow", player), lambda state: state.has("Computer Station (E1M7) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Red -> Computer Station (E1M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Red -> Computer Station (E1M7) Main", player), lambda state: state.has("Computer Station (E1M7) - Red keycard", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Blue", player), lambda state: state.has("Computer Station (E1M7) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Courtyard", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Courtyard", player), lambda state: state.has("Computer Station (E1M7) - Yellow keycard", player, 1) and state.has("Computer Station (E1M7) - Red keycard", player, 1)) - set_rule(world.get_entrance("Computer Station (E1M7) Courtyard -> Computer Station (E1M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Computer Station (E1M7) Courtyard -> Computer Station (E1M7) Yellow", player), lambda state: state.has("Computer Station (E1M7) - Yellow keycard", player, 1)) # Phobos Anomaly (E1M8) - set_rule(world.get_entrance("Hub -> Phobos Anomaly (E1M8) Start", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Phobos Anomaly (E1M8) Start", player), lambda state: (state.has("Phobos Anomaly (E1M8)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and @@ -114,255 +114,260 @@ def set_episode1_rules(player, world, pro): state.has("BFG9000", player, 1))) # Military Base (E1M9) - set_rule(world.get_entrance("Hub -> Military Base (E1M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Military Base (E1M9) Main", player), lambda state: state.has("Military Base (E1M9)", player, 1) and state.has("Chaingun", player, 1) and state.has("Shotgun", player, 1)) - set_rule(world.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Blue", player), lambda state: state.has("Military Base (E1M9) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Yellow", player), lambda state: state.has("Military Base (E1M9) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Red", player), lambda state: + set_rule(multiworld.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Red", player), lambda state: state.has("Military Base (E1M9) - Red keycard", player, 1)) - set_rule(world.get_entrance("Military Base (E1M9) Blue -> Military Base (E1M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Military Base (E1M9) Blue -> Military Base (E1M9) Main", player), lambda state: state.has("Military Base (E1M9) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Military Base (E1M9) Yellow -> Military Base (E1M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Military Base (E1M9) Yellow -> Military Base (E1M9) Main", player), lambda state: state.has("Military Base (E1M9) - Yellow keycard", player, 1)) -def set_episode2_rules(player, world, pro): +def set_episode2_rules(player, multiworld, pro): # Deimos Anomaly (E2M1) - set_rule(world.get_entrance("Hub -> Deimos Anomaly (E2M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Deimos Anomaly (E2M1) Main", player), lambda state: state.has("Deimos Anomaly (E2M1)", player, 1)) - set_rule(world.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Red", player), lambda state: + set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Red", player), lambda state: state.has("Deimos Anomaly (E2M1) - Red keycard", player, 1)) - set_rule(world.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Blue", player), lambda state: state.has("Deimos Anomaly (E2M1) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Deimos Anomaly (E2M1) Blue -> Deimos Anomaly (E2M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Blue -> Deimos Anomaly (E2M1) Main", player), lambda state: state.has("Deimos Anomaly (E2M1) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Deimos Anomaly (E2M1) Red -> Deimos Anomaly (E2M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Red -> Deimos Anomaly (E2M1) Main", player), lambda state: state.has("Deimos Anomaly (E2M1) - Red keycard", player, 1)) # Containment Area (E2M2) - set_rule(world.get_entrance("Hub -> Containment Area (E2M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Containment Area (E2M2) Main", player), lambda state: (state.has("Containment Area (E2M2)", player, 1) and state.has("Shotgun", player, 1)) and (state.has("Chaingun", player, 1) or state.has("Plasma gun", player, 1))) - set_rule(world.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Yellow", player), lambda state: state.has("Containment Area (E2M2) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Blue", player), lambda state: state.has("Containment Area (E2M2) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Red", player), lambda state: + set_rule(multiworld.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Red", player), lambda state: state.has("Containment Area (E2M2) - Red keycard", player, 1)) - set_rule(world.get_entrance("Containment Area (E2M2) Blue -> Containment Area (E2M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Containment Area (E2M2) Blue -> Containment Area (E2M2) Main", player), lambda state: state.has("Containment Area (E2M2) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Containment Area (E2M2) Red -> Containment Area (E2M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Containment Area (E2M2) Red -> Containment Area (E2M2) Main", player), lambda state: state.has("Containment Area (E2M2) - Red keycard", player, 1)) # Refinery (E2M3) - set_rule(world.get_entrance("Hub -> Refinery (E2M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Refinery (E2M3) Main", player), lambda state: (state.has("Refinery (E2M3)", player, 1) and state.has("Shotgun", player, 1)) and (state.has("Chaingun", player, 1) or state.has("Plasma gun", player, 1))) - set_rule(world.get_entrance("Refinery (E2M3) Main -> Refinery (E2M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Refinery (E2M3) Main -> Refinery (E2M3) Blue", player), lambda state: state.has("Refinery (E2M3) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Refinery (E2M3) Blue -> Refinery (E2M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Refinery (E2M3) Blue -> Refinery (E2M3) Main", player), lambda state: state.has("Refinery (E2M3) - Blue keycard", player, 1)) # Deimos Lab (E2M4) - set_rule(world.get_entrance("Hub -> Deimos Lab (E2M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Deimos Lab (E2M4) Main", player), lambda state: state.has("Deimos Lab (E2M4)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Plasma gun", player, 1)) - set_rule(world.get_entrance("Deimos Lab (E2M4) Main -> Deimos Lab (E2M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Deimos Lab (E2M4) Main -> Deimos Lab (E2M4) Blue", player), lambda state: state.has("Deimos Lab (E2M4) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Deimos Lab (E2M4) Blue -> Deimos Lab (E2M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Deimos Lab (E2M4) Blue -> Deimos Lab (E2M4) Yellow", player), lambda state: state.has("Deimos Lab (E2M4) - Yellow keycard", player, 1)) # Command Center (E2M5) - set_rule(world.get_entrance("Hub -> Command Center (E2M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Command Center (E2M5) Main", player), lambda state: state.has("Command Center (E2M5)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Plasma gun", player, 1)) # Halls of the Damned (E2M6) - set_rule(world.get_entrance("Hub -> Halls of the Damned (E2M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Halls of the Damned (E2M6) Main", player), lambda state: state.has("Halls of the Damned (E2M6)", player, 1) and state.has("Chaingun", player, 1) and state.has("Shotgun", player, 1) and state.has("Plasma gun", player, 1)) - set_rule(world.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Blue Yellow Red", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Blue Yellow Red", player), lambda state: state.has("Halls of the Damned (E2M6) - Blue skull key", player, 1) and state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1) and state.has("Halls of the Damned (E2M6) - Red skull key", player, 1)) - set_rule(world.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Yellow", player), lambda state: state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) One way Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) One way Yellow", player), lambda state: state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Halls of the Damned (E2M6) Blue Yellow Red -> Halls of the Damned (E2M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Blue Yellow Red -> Halls of the Damned (E2M6) Main", player), lambda state: state.has("Halls of the Damned (E2M6) - Blue skull key", player, 1) and state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1) and state.has("Halls of the Damned (E2M6) - Red skull key", player, 1)) - set_rule(world.get_entrance("Halls of the Damned (E2M6) One way Yellow -> Halls of the Damned (E2M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) One way Yellow -> Halls of the Damned (E2M6) Main", player), lambda state: state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1)) # Spawning Vats (E2M7) - set_rule(world.get_entrance("Hub -> Spawning Vats (E2M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Spawning Vats (E2M7) Main", player), lambda state: state.has("Spawning Vats (E2M7)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Plasma gun", player, 1) and state.has("Rocket launcher", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Blue", player), lambda state: state.has("Spawning Vats (E2M7) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Entrance Secret", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Entrance Secret", player), lambda state: state.has("Spawning Vats (E2M7) - Blue keycard", player, 1) and state.has("Spawning Vats (E2M7) - Red keycard", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red", player), lambda state: state.has("Spawning Vats (E2M7) - Red keycard", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Yellow", player), lambda state: state.has("Spawning Vats (E2M7) - Yellow keycard", player, 1)) if pro: - set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red Exit", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red Exit", player), lambda state: state.has("Rocket launcher", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Yellow -> Spawning Vats (E2M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Yellow -> Spawning Vats (E2M7) Main", player), lambda state: state.has("Spawning Vats (E2M7) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Red -> Spawning Vats (E2M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Red -> Spawning Vats (E2M7) Main", player), lambda state: state.has("Spawning Vats (E2M7) - Red keycard", player, 1)) - set_rule(world.get_entrance("Spawning Vats (E2M7) Entrance Secret -> Spawning Vats (E2M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Entrance Secret -> Spawning Vats (E2M7) Main", player), lambda state: state.has("Spawning Vats (E2M7) - Blue keycard", player, 1) and state.has("Spawning Vats (E2M7) - Red keycard", player, 1)) # Tower of Babel (E2M8) - set_rule(world.get_entrance("Hub -> Tower of Babel (E2M8) Main", player), lambda state: - state.has("Tower of Babel (E2M8)", player, 1)) + set_rule(multiworld.get_entrance("Hub -> Tower of Babel (E2M8) Main", player), lambda state: + (state.has("Tower of Babel (E2M8)", player, 1) and + state.has("Shotgun", player, 1) and + state.has("Chaingun", player, 1)) and + (state.has("Rocket launcher", player, 1) or + state.has("Plasma gun", player, 1) or + state.has("BFG9000", player, 1))) # Fortress of Mystery (E2M9) - set_rule(world.get_entrance("Hub -> Fortress of Mystery (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Fortress of Mystery (E2M9) Main", player), lambda state: (state.has("Fortress of Mystery (E2M9)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Blue", player), lambda state: state.has("Fortress of Mystery (E2M9) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Red", player), lambda state: + set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Red", player), lambda state: state.has("Fortress of Mystery (E2M9) - Red skull key", player, 1)) - set_rule(world.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Yellow", player), lambda state: state.has("Fortress of Mystery (E2M9) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Fortress of Mystery (E2M9) Blue -> Fortress of Mystery (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Blue -> Fortress of Mystery (E2M9) Main", player), lambda state: state.has("Fortress of Mystery (E2M9) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Fortress of Mystery (E2M9) Red -> Fortress of Mystery (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Red -> Fortress of Mystery (E2M9) Main", player), lambda state: state.has("Fortress of Mystery (E2M9) - Red skull key", player, 1)) - set_rule(world.get_entrance("Fortress of Mystery (E2M9) Yellow -> Fortress of Mystery (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Yellow -> Fortress of Mystery (E2M9) Main", player), lambda state: state.has("Fortress of Mystery (E2M9) - Yellow skull key", player, 1)) -def set_episode3_rules(player, world, pro): +def set_episode3_rules(player, multiworld, pro): # Hell Keep (E3M1) - set_rule(world.get_entrance("Hub -> Hell Keep (E3M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Hell Keep (E3M1) Main", player), lambda state: state.has("Hell Keep (E3M1)", player, 1)) - set_rule(world.get_entrance("Hell Keep (E3M1) Main -> Hell Keep (E3M1) Narrow", player), lambda state: + set_rule(multiworld.get_entrance("Hell Keep (E3M1) Main -> Hell Keep (E3M1) Narrow", player), lambda state: state.has("Chaingun", player, 1) or state.has("Shotgun", player, 1)) # Slough of Despair (E3M2) - set_rule(world.get_entrance("Hub -> Slough of Despair (E3M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Slough of Despair (E3M2) Main", player), lambda state: (state.has("Slough of Despair (E3M2)", player, 1)) and (state.has("Shotgun", player, 1) or state.has("Chaingun", player, 1))) - set_rule(world.get_entrance("Slough of Despair (E3M2) Main -> Slough of Despair (E3M2) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Slough of Despair (E3M2) Main -> Slough of Despair (E3M2) Blue", player), lambda state: state.has("Slough of Despair (E3M2) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Slough of Despair (E3M2) Blue -> Slough of Despair (E3M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Slough of Despair (E3M2) Blue -> Slough of Despair (E3M2) Main", player), lambda state: state.has("Slough of Despair (E3M2) - Blue skull key", player, 1)) # Pandemonium (E3M3) - set_rule(world.get_entrance("Hub -> Pandemonium (E3M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Pandemonium (E3M3) Main", player), lambda state: (state.has("Pandemonium (E3M3)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Pandemonium (E3M3) Main -> Pandemonium (E3M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Pandemonium (E3M3) Main -> Pandemonium (E3M3) Blue", player), lambda state: state.has("Pandemonium (E3M3) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Pandemonium (E3M3) Blue -> Pandemonium (E3M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Pandemonium (E3M3) Blue -> Pandemonium (E3M3) Main", player), lambda state: state.has("Pandemonium (E3M3) - Blue skull key", player, 1)) # House of Pain (E3M4) - set_rule(world.get_entrance("Hub -> House of Pain (E3M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> House of Pain (E3M4) Main", player), lambda state: (state.has("House of Pain (E3M4)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("House of Pain (E3M4) Main -> House of Pain (E3M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("House of Pain (E3M4) Main -> House of Pain (E3M4) Blue", player), lambda state: state.has("House of Pain (E3M4) - Blue skull key", player, 1)) - set_rule(world.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Main", player), lambda state: state.has("House of Pain (E3M4) - Blue skull key", player, 1)) - set_rule(world.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Yellow", player), lambda state: state.has("House of Pain (E3M4) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Red", player), lambda state: + set_rule(multiworld.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Red", player), lambda state: state.has("House of Pain (E3M4) - Red skull key", player, 1)) - set_rule(world.get_entrance("House of Pain (E3M4) Red -> House of Pain (E3M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("House of Pain (E3M4) Red -> House of Pain (E3M4) Blue", player), lambda state: state.has("House of Pain (E3M4) - Red skull key", player, 1)) - set_rule(world.get_entrance("House of Pain (E3M4) Yellow -> House of Pain (E3M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("House of Pain (E3M4) Yellow -> House of Pain (E3M4) Blue", player), lambda state: state.has("House of Pain (E3M4) - Yellow skull key", player, 1)) # Unholy Cathedral (E3M5) - set_rule(world.get_entrance("Hub -> Unholy Cathedral (E3M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Unholy Cathedral (E3M5) Main", player), lambda state: (state.has("Unholy Cathedral (E3M5)", player, 1) and state.has("Chaingun", player, 1) and state.has("Shotgun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Yellow", player), lambda state: state.has("Unholy Cathedral (E3M5) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Blue", player), lambda state: state.has("Unholy Cathedral (E3M5) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Unholy Cathedral (E3M5) Blue -> Unholy Cathedral (E3M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Blue -> Unholy Cathedral (E3M5) Main", player), lambda state: state.has("Unholy Cathedral (E3M5) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Unholy Cathedral (E3M5) Yellow -> Unholy Cathedral (E3M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Yellow -> Unholy Cathedral (E3M5) Main", player), lambda state: state.has("Unholy Cathedral (E3M5) - Yellow skull key", player, 1)) # Mt. Erebus (E3M6) - set_rule(world.get_entrance("Hub -> Mt. Erebus (E3M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Mt. Erebus (E3M6) Main", player), lambda state: state.has("Mt. Erebus (E3M6)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) - set_rule(world.get_entrance("Mt. Erebus (E3M6) Main -> Mt. Erebus (E3M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Mt. Erebus (E3M6) Main -> Mt. Erebus (E3M6) Blue", player), lambda state: state.has("Mt. Erebus (E3M6) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Mt. Erebus (E3M6) Blue -> Mt. Erebus (E3M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Mt. Erebus (E3M6) Blue -> Mt. Erebus (E3M6) Main", player), lambda state: state.has("Mt. Erebus (E3M6) - Blue skull key", player, 1)) # Limbo (E3M7) - set_rule(world.get_entrance("Hub -> Limbo (E3M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Limbo (E3M7) Main", player), lambda state: (state.has("Limbo (E3M7)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Red", player), lambda state: + set_rule(multiworld.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Red", player), lambda state: state.has("Limbo (E3M7) - Red skull key", player, 1)) - set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Blue", player), lambda state: state.has("Limbo (E3M7) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Pink", player), lambda state: + set_rule(multiworld.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Pink", player), lambda state: state.has("Limbo (E3M7) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Limbo (E3M7) Red -> Limbo (E3M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Limbo (E3M7) Red -> Limbo (E3M7) Yellow", player), lambda state: state.has("Limbo (E3M7) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Limbo (E3M7) Pink -> Limbo (E3M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("Limbo (E3M7) Pink -> Limbo (E3M7) Green", player), lambda state: state.has("Limbo (E3M7) - Red skull key", player, 1)) # Dis (E3M8) - set_rule(world.get_entrance("Hub -> Dis (E3M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Dis (E3M8) Main", player), lambda state: (state.has("Dis (E3M8)", player, 1) and state.has("Chaingun", player, 1) and state.has("Shotgun", player, 1)) and @@ -371,129 +376,129 @@ def set_episode3_rules(player, world, pro): state.has("Rocket launcher", player, 1))) # Warrens (E3M9) - set_rule(world.get_entrance("Hub -> Warrens (E3M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Warrens (E3M9) Main", player), lambda state: (state.has("Warrens (E3M9)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Plasma gun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue", player), lambda state: state.has("Warrens (E3M9) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue trigger", player), lambda state: + set_rule(multiworld.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue trigger", player), lambda state: state.has("Warrens (E3M9) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Main", player), lambda state: state.has("Warrens (E3M9) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Red", player), lambda state: + set_rule(multiworld.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Red", player), lambda state: state.has("Warrens (E3M9) - Red skull key", player, 1)) -def set_episode4_rules(player, world, pro): +def set_episode4_rules(player, multiworld, pro): # Hell Beneath (E4M1) - set_rule(world.get_entrance("Hub -> Hell Beneath (E4M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Hell Beneath (E4M1) Main", player), lambda state: state.has("Hell Beneath (E4M1)", player, 1)) - set_rule(world.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Red", player), lambda state: + set_rule(multiworld.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Red", player), lambda state: (state.has("Hell Beneath (E4M1) - Red skull key", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Blue", player), lambda state: state.has("Shotgun", player, 1) or state.has("Chaingun", player, 1) or state.has("Hell Beneath (E4M1) - Blue skull key", player, 1)) # Perfect Hatred (E4M2) - set_rule(world.get_entrance("Hub -> Perfect Hatred (E4M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Perfect Hatred (E4M2) Main", player), lambda state: (state.has("Perfect Hatred (E4M2)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Blue", player), lambda state: state.has("Perfect Hatred (E4M2) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Yellow", player), lambda state: state.has("Perfect Hatred (E4M2) - Yellow skull key", player, 1)) # Sever the Wicked (E4M3) - set_rule(world.get_entrance("Hub -> Sever the Wicked (E4M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Sever the Wicked (E4M3) Main", player), lambda state: (state.has("Sever the Wicked (E4M3)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Sever the Wicked (E4M3) Main -> Sever the Wicked (E4M3) Red", player), lambda state: + set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Main -> Sever the Wicked (E4M3) Red", player), lambda state: state.has("Sever the Wicked (E4M3) - Red skull key", player, 1)) - set_rule(world.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Blue", player), lambda state: state.has("Sever the Wicked (E4M3) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Main", player), lambda state: state.has("Sever the Wicked (E4M3) - Red skull key", player, 1)) - set_rule(world.get_entrance("Sever the Wicked (E4M3) Blue -> Sever the Wicked (E4M3) Red", player), lambda state: + set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Blue -> Sever the Wicked (E4M3) Red", player), lambda state: state.has("Sever the Wicked (E4M3) - Blue skull key", player, 1)) # Unruly Evil (E4M4) - set_rule(world.get_entrance("Hub -> Unruly Evil (E4M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Unruly Evil (E4M4) Main", player), lambda state: (state.has("Unruly Evil (E4M4)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Unruly Evil (E4M4) Main -> Unruly Evil (E4M4) Red", player), lambda state: + set_rule(multiworld.get_entrance("Unruly Evil (E4M4) Main -> Unruly Evil (E4M4) Red", player), lambda state: state.has("Unruly Evil (E4M4) - Red skull key", player, 1)) # They Will Repent (E4M5) - set_rule(world.get_entrance("Hub -> They Will Repent (E4M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> They Will Repent (E4M5) Main", player), lambda state: (state.has("They Will Repent (E4M5)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("They Will Repent (E4M5) Main -> They Will Repent (E4M5) Red", player), lambda state: + set_rule(multiworld.get_entrance("They Will Repent (E4M5) Main -> They Will Repent (E4M5) Red", player), lambda state: state.has("They Will Repent (E4M5) - Red skull key", player, 1)) - set_rule(world.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Main", player), lambda state: state.has("They Will Repent (E4M5) - Red skull key", player, 1)) - set_rule(world.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Yellow", player), lambda state: state.has("They Will Repent (E4M5) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Blue", player), lambda state: state.has("They Will Repent (E4M5) - Blue skull key", player, 1)) # Against Thee Wickedly (E4M6) - set_rule(world.get_entrance("Hub -> Against Thee Wickedly (E4M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Against Thee Wickedly (E4M6) Main", player), lambda state: (state.has("Against Thee Wickedly (E4M6)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Blue", player), lambda state: state.has("Against Thee Wickedly (E4M6) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Yellow", player), lambda state: state.has("Against Thee Wickedly (E4M6) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Red", player), lambda state: + set_rule(multiworld.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Red", player), lambda state: state.has("Against Thee Wickedly (E4M6) - Red skull key", player, 1)) # And Hell Followed (E4M7) - set_rule(world.get_entrance("Hub -> And Hell Followed (E4M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> And Hell Followed (E4M7) Main", player), lambda state: (state.has("And Hell Followed (E4M7)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Blue", player), lambda state: state.has("And Hell Followed (E4M7) - Blue skull key", player, 1)) - set_rule(world.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Red", player), lambda state: + set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Red", player), lambda state: state.has("And Hell Followed (E4M7) - Red skull key", player, 1)) - set_rule(world.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Yellow", player), lambda state: state.has("And Hell Followed (E4M7) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("And Hell Followed (E4M7) Red -> And Hell Followed (E4M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Red -> And Hell Followed (E4M7) Main", player), lambda state: state.has("And Hell Followed (E4M7) - Red skull key", player, 1)) # Unto the Cruel (E4M8) - set_rule(world.get_entrance("Hub -> Unto the Cruel (E4M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Unto the Cruel (E4M8) Main", player), lambda state: (state.has("Unto the Cruel (E4M8)", player, 1) and state.has("Chainsaw", player, 1) and state.has("Shotgun", player, 1) and @@ -501,37 +506,37 @@ def set_episode4_rules(player, world, pro): state.has("Rocket launcher", player, 1)) and (state.has("BFG9000", player, 1) or state.has("Plasma gun", player, 1))) - set_rule(world.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Red", player), lambda state: + set_rule(multiworld.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Red", player), lambda state: state.has("Unto the Cruel (E4M8) - Red skull key", player, 1)) - set_rule(world.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Yellow", player), lambda state: state.has("Unto the Cruel (E4M8) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Orange", player), lambda state: + set_rule(multiworld.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Orange", player), lambda state: state.has("Unto the Cruel (E4M8) - Yellow skull key", player, 1) and state.has("Unto the Cruel (E4M8) - Red skull key", player, 1)) # Fear (E4M9) - set_rule(world.get_entrance("Hub -> Fear (E4M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Fear (E4M9) Main", player), lambda state: state.has("Fear (E4M9)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and state.has("Rocket launcher", player, 1) and state.has("Plasma gun", player, 1) and state.has("BFG9000", player, 1)) - set_rule(world.get_entrance("Fear (E4M9) Main -> Fear (E4M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Fear (E4M9) Main -> Fear (E4M9) Yellow", player), lambda state: state.has("Fear (E4M9) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Fear (E4M9) Yellow -> Fear (E4M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Fear (E4M9) Yellow -> Fear (E4M9) Main", player), lambda state: state.has("Fear (E4M9) - Yellow skull key", player, 1)) def set_rules(doom_1993_world: "DOOM1993World", included_episodes, pro): player = doom_1993_world.player - world = doom_1993_world.multiworld + multiworld = doom_1993_world.multiworld if included_episodes[0]: - set_episode1_rules(player, world, pro) + set_episode1_rules(player, multiworld, pro) if included_episodes[1]: - set_episode2_rules(player, world, pro) + set_episode2_rules(player, multiworld, pro) if included_episodes[2]: - set_episode3_rules(player, world, pro) + set_episode3_rules(player, multiworld, pro) if included_episodes[3]: - set_episode4_rules(player, world, pro) + set_episode4_rules(player, multiworld, pro) diff --git a/worlds/doom_1993/__init__.py b/worlds/doom_1993/__init__.py index e420b34b4f00..b6138ae07103 100644 --- a/worlds/doom_1993/__init__.py +++ b/worlds/doom_1993/__init__.py @@ -2,9 +2,10 @@ import logging from typing import Any, Dict, List -from BaseClasses import Entrance, CollectionState, Item, ItemClassification, Location, MultiWorld, Region, Tutorial +from BaseClasses import Entrance, CollectionState, Item, Location, MultiWorld, Region, Tutorial from worlds.AutoWorld import WebWorld, World -from . import Items, Locations, Maps, Options, Regions, Rules +from . import Items, Locations, Maps, Regions, Rules +from .Options import DOOM1993Options logger = logging.getLogger("DOOM 1993") @@ -37,10 +38,10 @@ class DOOM1993World(World): Developed by id Software, and originally released in 1993, DOOM pioneered and popularized the first-person shooter, setting a standard for all FPS games. """ - option_definitions = Options.options + options_dataclass = DOOM1993Options + options: DOOM1993Options game = "DOOM 1993" web = DOOM1993Web() - data_version = 3 required_client_version = (0, 3, 9) item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()} @@ -78,26 +79,28 @@ class DOOM1993World(World): "Energy cell pack": 10 } - def __init__(self, world: MultiWorld, player: int): + def __init__(self, multiworld: MultiWorld, player: int): self.included_episodes = [1, 1, 1, 0] self.location_count = 0 - super().__init__(world, player) + super().__init__(multiworld, player) def get_episode_count(self): return functools.reduce(lambda count, episode: count + episode, self.included_episodes) def generate_early(self): # Cache which episodes are included - for i in range(4): - self.included_episodes[i] = getattr(self.multiworld, f"episode{i + 1}")[self.player].value + self.included_episodes[0] = self.options.episode1.value + self.included_episodes[1] = self.options.episode2.value + self.included_episodes[2] = self.options.episode3.value + self.included_episodes[3] = self.options.episode4.value # If no episodes selected, select Episode 1 if self.get_episode_count() == 0: self.included_episodes[0] = 1 def create_regions(self): - pro = getattr(self.multiworld, "pro")[self.player].value + pro = self.options.pro.value # Main regions menu_region = Region("Menu", self.player, self.multiworld) @@ -148,7 +151,7 @@ def create_regions(self): def completion_rule(self, state: CollectionState): goal_levels = Maps.map_names - if getattr(self.multiworld, "goal")[self.player].value: + if self.options.goal.value: goal_levels = self.boss_level_for_espidoes for map_name in goal_levels: @@ -167,8 +170,8 @@ def completion_rule(self, state: CollectionState): return True def set_rules(self): - pro = getattr(self.multiworld, "pro")[self.player].value - allow_death_logic = getattr(self.multiworld, "allow_death_logic")[self.player].value + pro = self.options.pro.value + allow_death_logic = self.options.allow_death_logic.value Rules.set_rules(self, self.included_episodes, pro) self.multiworld.completion_condition[self.player] = lambda state: self.completion_rule(state) @@ -177,7 +180,7 @@ def set_rules(self): # platform) Unless the user allows for it. if not allow_death_logic: for death_logic_location in Locations.death_logic_locations: - self.multiworld.exclude_locations[self.player].value.add(death_logic_location) + self.options.exclude_locations.value.add(death_logic_location) def create_item(self, name: str) -> DOOM1993Item: item_id: int = self.item_name_to_id[name] @@ -185,7 +188,7 @@ def create_item(self, name: str) -> DOOM1993Item: def create_items(self): itempool: List[DOOM1993Item] = [] - start_with_computer_area_maps: bool = getattr(self.multiworld, "start_with_computer_area_maps")[self.player].value + start_with_computer_area_maps: bool = self.options.start_with_computer_area_maps.value # Items for item_id, item in Items.item_table.items(): @@ -225,7 +228,7 @@ def create_items(self): self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i])) # Give Computer area maps if option selected - if getattr(self.multiworld, "start_with_computer_area_maps")[self.player].value: + if self.options.start_with_computer_area_maps.value: for item_id, item_dict in Items.item_table.items(): item_episode = item_dict["episode"] if item_episode > 0: @@ -269,7 +272,7 @@ def create_ratioed_items(self, item_name: str, itempool: List[DOOM1993Item]): itempool.append(self.create_item(item_name)) def fill_slot_data(self) -> Dict[str, Any]: - slot_data = {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions} + slot_data = self.options.as_dict("goal", "difficulty", "random_monsters", "random_pickups", "random_music", "flip_levels", "allow_death_logic", "pro", "start_with_computer_area_maps", "death_link", "reset_level_on_death", "episode1", "episode2", "episode3", "episode4") # E2M6 and E3M9 each have one way keydoor. You can enter, but required the keycard to get out. # We used to force place the keycard behind those doors. Limiting the randomness for those items. A change diff --git a/worlds/doom_1993/docs/en_DOOM 1993.md b/worlds/doom_1993/docs/en_DOOM 1993.md index 0419741bc308..ea7e3200307f 100644 --- a/worlds/doom_1993/docs/en_DOOM 1993.md +++ b/worlds/doom_1993/docs/en_DOOM 1993.md @@ -1,8 +1,8 @@ # DOOM 1993 -## Where is the settings page? +## Where is the options page? -The [player settings page](../player-settings) contains the options needed to configure your game session. +The [player options page](../player-options) contains the options needed to configure your game session. ## What does randomization do to this game? diff --git a/worlds/doom_1993/docs/setup_en.md b/worlds/doom_1993/docs/setup_en.md index 1e546d359c91..8906efac9cea 100644 --- a/worlds/doom_1993/docs/setup_en.md +++ b/worlds/doom_1993/docs/setup_en.md @@ -15,7 +15,7 @@ 1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it. 2. Copy `DOOM.WAD` from your game's installation directory into the newly extracted folder. You can find the folder in steam by finding the game in your library, - right-clicking it and choosing **Manage -> Browse Local Files**. + right-clicking it and choosing **Manage -> Browse Local Files**. The WAD file is in the `/base/` folder. ## Joining a MultiWorld Game diff --git a/worlds/doom_ii/Rules.py b/worlds/doom_ii/Rules.py index 89f3a10f9faf..139733c0eac8 100644 --- a/worlds/doom_ii/Rules.py +++ b/worlds/doom_ii/Rules.py @@ -7,57 +7,53 @@ from . import DOOM2World -def set_episode1_rules(player, world, pro): +def set_episode1_rules(player, multiworld, pro): # Entryway (MAP01) - set_rule(world.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state: state.has("Entryway (MAP01)", player, 1)) - set_rule(world.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state: state.has("Entryway (MAP01)", player, 1)) # Underhalls (MAP02) - set_rule(world.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state: state.has("Underhalls (MAP02)", player, 1)) - set_rule(world.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state: - state.has("Underhalls (MAP02)", player, 1)) - set_rule(world.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state: - state.has("Underhalls (MAP02)", player, 1)) - set_rule(world.get_entrance("Underhalls (MAP02) Main -> Underhalls (MAP02) Red", player), lambda state: + set_rule(multiworld.get_entrance("Underhalls (MAP02) Main -> Underhalls (MAP02) Red", player), lambda state: state.has("Underhalls (MAP02) - Red keycard", player, 1)) - set_rule(world.get_entrance("Underhalls (MAP02) Blue -> Underhalls (MAP02) Red", player), lambda state: + set_rule(multiworld.get_entrance("Underhalls (MAP02) Blue -> Underhalls (MAP02) Red", player), lambda state: state.has("Underhalls (MAP02) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Underhalls (MAP02) Red -> Underhalls (MAP02) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Underhalls (MAP02) Red -> Underhalls (MAP02) Blue", player), lambda state: state.has("Underhalls (MAP02) - Blue keycard", player, 1)) # The Gantlet (MAP03) - set_rule(world.get_entrance("Hub -> The Gantlet (MAP03) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Gantlet (MAP03) Main", player), lambda state: (state.has("The Gantlet (MAP03)", player, 1)) and (state.has("Shotgun", player, 1) or state.has("Chaingun", player, 1) or state.has("Super Shotgun", player, 1))) - set_rule(world.get_entrance("The Gantlet (MAP03) Main -> The Gantlet (MAP03) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Gantlet (MAP03) Main -> The Gantlet (MAP03) Blue", player), lambda state: state.has("The Gantlet (MAP03) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Gantlet (MAP03) Blue -> The Gantlet (MAP03) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Gantlet (MAP03) Blue -> The Gantlet (MAP03) Red", player), lambda state: state.has("The Gantlet (MAP03) - Red keycard", player, 1)) # The Focus (MAP04) - set_rule(world.get_entrance("Hub -> The Focus (MAP04) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Focus (MAP04) Main", player), lambda state: (state.has("The Focus (MAP04)", player, 1)) and (state.has("Shotgun", player, 1) or state.has("Chaingun", player, 1) or state.has("Super Shotgun", player, 1))) - set_rule(world.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Red", player), lambda state: state.has("The Focus (MAP04) - Red keycard", player, 1)) - set_rule(world.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Blue", player), lambda state: state.has("The Focus (MAP04) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Focus (MAP04) Yellow -> The Focus (MAP04) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Focus (MAP04) Yellow -> The Focus (MAP04) Red", player), lambda state: state.has("The Focus (MAP04) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Yellow", player), lambda state: state.has("The Focus (MAP04) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Main", player), lambda state: state.has("The Focus (MAP04) - Red keycard", player, 1)) # The Waste Tunnels (MAP05) - set_rule(world.get_entrance("Hub -> The Waste Tunnels (MAP05) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Waste Tunnels (MAP05) Main", player), lambda state: (state.has("The Waste Tunnels (MAP05)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -65,19 +61,19 @@ def set_episode1_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Red", player), lambda state: state.has("The Waste Tunnels (MAP05) - Red keycard", player, 1)) - set_rule(world.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Blue", player), lambda state: state.has("The Waste Tunnels (MAP05) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Yellow", player), lambda state: state.has("The Waste Tunnels (MAP05) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Main", player), lambda state: state.has("The Waste Tunnels (MAP05) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Waste Tunnels (MAP05) Yellow -> The Waste Tunnels (MAP05) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Yellow -> The Waste Tunnels (MAP05) Blue", player), lambda state: state.has("The Waste Tunnels (MAP05) - Yellow keycard", player, 1)) # The Crusher (MAP06) - set_rule(world.get_entrance("Hub -> The Crusher (MAP06) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Crusher (MAP06) Main", player), lambda state: (state.has("The Crusher (MAP06)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -85,21 +81,21 @@ def set_episode1_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Crusher (MAP06) Main -> The Crusher (MAP06) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Crusher (MAP06) Main -> The Crusher (MAP06) Blue", player), lambda state: state.has("The Crusher (MAP06) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Red", player), lambda state: state.has("The Crusher (MAP06) - Red keycard", player, 1)) - set_rule(world.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Main", player), lambda state: state.has("The Crusher (MAP06) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Crusher (MAP06) Yellow -> The Crusher (MAP06) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Crusher (MAP06) Yellow -> The Crusher (MAP06) Red", player), lambda state: state.has("The Crusher (MAP06) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Yellow", player), lambda state: state.has("The Crusher (MAP06) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Blue", player), lambda state: state.has("The Crusher (MAP06) - Red keycard", player, 1)) # Dead Simple (MAP07) - set_rule(world.get_entrance("Hub -> Dead Simple (MAP07) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Dead Simple (MAP07) Main", player), lambda state: (state.has("Dead Simple (MAP07)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -109,7 +105,7 @@ def set_episode1_rules(player, world, pro): state.has("BFG9000", player, 1))) # Tricks and Traps (MAP08) - set_rule(world.get_entrance("Hub -> Tricks and Traps (MAP08) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Tricks and Traps (MAP08) Main", player), lambda state: (state.has("Tricks and Traps (MAP08)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -117,13 +113,13 @@ def set_episode1_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Red", player), lambda state: + set_rule(multiworld.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Red", player), lambda state: state.has("Tricks and Traps (MAP08) - Red skull key", player, 1)) - set_rule(world.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Yellow", player), lambda state: state.has("Tricks and Traps (MAP08) - Yellow skull key", player, 1)) # The Pit (MAP09) - set_rule(world.get_entrance("Hub -> The Pit (MAP09) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Pit (MAP09) Main", player), lambda state: (state.has("The Pit (MAP09)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -131,15 +127,15 @@ def set_episode1_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Yellow", player), lambda state: state.has("The Pit (MAP09) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Blue", player), lambda state: state.has("The Pit (MAP09) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Pit (MAP09) Yellow -> The Pit (MAP09) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Pit (MAP09) Yellow -> The Pit (MAP09) Main", player), lambda state: state.has("The Pit (MAP09) - Yellow keycard", player, 1)) # Refueling Base (MAP10) - set_rule(world.get_entrance("Hub -> Refueling Base (MAP10) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Refueling Base (MAP10) Main", player), lambda state: (state.has("Refueling Base (MAP10)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -147,13 +143,13 @@ def set_episode1_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Refueling Base (MAP10) Main -> Refueling Base (MAP10) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Refueling Base (MAP10) Main -> Refueling Base (MAP10) Yellow", player), lambda state: state.has("Refueling Base (MAP10) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Refueling Base (MAP10) Yellow -> Refueling Base (MAP10) Yellow Blue", player), lambda state: + set_rule(multiworld.get_entrance("Refueling Base (MAP10) Yellow -> Refueling Base (MAP10) Yellow Blue", player), lambda state: state.has("Refueling Base (MAP10) - Blue keycard", player, 1)) # Circle of Death (MAP11) - set_rule(world.get_entrance("Hub -> Circle of Death (MAP11) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Circle of Death (MAP11) Main", player), lambda state: (state.has("Circle of Death (MAP11)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -161,15 +157,15 @@ def set_episode1_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Blue", player), lambda state: state.has("Circle of Death (MAP11) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Red", player), lambda state: + set_rule(multiworld.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Red", player), lambda state: state.has("Circle of Death (MAP11) - Red keycard", player, 1)) -def set_episode2_rules(player, world, pro): +def set_episode2_rules(player, multiworld, pro): # The Factory (MAP12) - set_rule(world.get_entrance("Hub -> The Factory (MAP12) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Factory (MAP12) Main", player), lambda state: (state.has("The Factory (MAP12)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -177,13 +173,13 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Yellow", player), lambda state: state.has("The Factory (MAP12) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Blue", player), lambda state: state.has("The Factory (MAP12) - Blue keycard", player, 1)) # Downtown (MAP13) - set_rule(world.get_entrance("Hub -> Downtown (MAP13) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Downtown (MAP13) Main", player), lambda state: (state.has("Downtown (MAP13)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -191,15 +187,15 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Yellow", player), lambda state: state.has("Downtown (MAP13) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Red", player), lambda state: + set_rule(multiworld.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Red", player), lambda state: state.has("Downtown (MAP13) - Red keycard", player, 1)) - set_rule(world.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Blue", player), lambda state: state.has("Downtown (MAP13) - Blue keycard", player, 1)) # The Inmost Dens (MAP14) - set_rule(world.get_entrance("Hub -> The Inmost Dens (MAP14) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Inmost Dens (MAP14) Main", player), lambda state: (state.has("The Inmost Dens (MAP14)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -207,17 +203,17 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Inmost Dens (MAP14) Main -> The Inmost Dens (MAP14) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Main -> The Inmost Dens (MAP14) Red", player), lambda state: state.has("The Inmost Dens (MAP14) - Red skull key", player, 1)) - set_rule(world.get_entrance("The Inmost Dens (MAP14) Blue -> The Inmost Dens (MAP14) Red East", player), lambda state: + set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Blue -> The Inmost Dens (MAP14) Red East", player), lambda state: state.has("The Inmost Dens (MAP14) - Blue skull key", player, 1)) - set_rule(world.get_entrance("The Inmost Dens (MAP14) Red -> The Inmost Dens (MAP14) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Red -> The Inmost Dens (MAP14) Main", player), lambda state: state.has("The Inmost Dens (MAP14) - Red skull key", player, 1)) - set_rule(world.get_entrance("The Inmost Dens (MAP14) Red East -> The Inmost Dens (MAP14) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Red East -> The Inmost Dens (MAP14) Blue", player), lambda state: state.has("The Inmost Dens (MAP14) - Blue skull key", player, 1)) # Industrial Zone (MAP15) - set_rule(world.get_entrance("Hub -> Industrial Zone (MAP15) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Industrial Zone (MAP15) Main", player), lambda state: (state.has("Industrial Zone (MAP15)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -225,17 +221,17 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow East", player), lambda state: + set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow East", player), lambda state: state.has("Industrial Zone (MAP15) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow West", player), lambda state: + set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow West", player), lambda state: state.has("Industrial Zone (MAP15) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("Industrial Zone (MAP15) Blue -> Industrial Zone (MAP15) Yellow East", player), lambda state: + set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Blue -> Industrial Zone (MAP15) Yellow East", player), lambda state: state.has("Industrial Zone (MAP15) - Blue keycard", player, 1)) - set_rule(world.get_entrance("Industrial Zone (MAP15) Yellow East -> Industrial Zone (MAP15) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Yellow East -> Industrial Zone (MAP15) Blue", player), lambda state: state.has("Industrial Zone (MAP15) - Blue keycard", player, 1)) # Suburbs (MAP16) - set_rule(world.get_entrance("Hub -> Suburbs (MAP16) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Suburbs (MAP16) Main", player), lambda state: (state.has("Suburbs (MAP16)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -243,13 +239,13 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Red", player), lambda state: + set_rule(multiworld.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Red", player), lambda state: state.has("Suburbs (MAP16) - Red skull key", player, 1)) - set_rule(world.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Blue", player), lambda state: state.has("Suburbs (MAP16) - Blue skull key", player, 1)) # Tenements (MAP17) - set_rule(world.get_entrance("Hub -> Tenements (MAP17) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Tenements (MAP17) Main", player), lambda state: (state.has("Tenements (MAP17)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -257,15 +253,15 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Tenements (MAP17) Main -> Tenements (MAP17) Red", player), lambda state: + set_rule(multiworld.get_entrance("Tenements (MAP17) Main -> Tenements (MAP17) Red", player), lambda state: state.has("Tenements (MAP17) - Red keycard", player, 1)) - set_rule(world.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Yellow", player), lambda state: state.has("Tenements (MAP17) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Blue", player), lambda state: state.has("Tenements (MAP17) - Blue keycard", player, 1)) # The Courtyard (MAP18) - set_rule(world.get_entrance("Hub -> The Courtyard (MAP18) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Courtyard (MAP18) Main", player), lambda state: (state.has("The Courtyard (MAP18)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -273,17 +269,17 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Yellow", player), lambda state: state.has("The Courtyard (MAP18) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Blue", player), lambda state: state.has("The Courtyard (MAP18) - Blue skull key", player, 1)) - set_rule(world.get_entrance("The Courtyard (MAP18) Blue -> The Courtyard (MAP18) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Courtyard (MAP18) Blue -> The Courtyard (MAP18) Main", player), lambda state: state.has("The Courtyard (MAP18) - Blue skull key", player, 1)) - set_rule(world.get_entrance("The Courtyard (MAP18) Yellow -> The Courtyard (MAP18) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Courtyard (MAP18) Yellow -> The Courtyard (MAP18) Main", player), lambda state: state.has("The Courtyard (MAP18) - Yellow skull key", player, 1)) # The Citadel (MAP19) - set_rule(world.get_entrance("Hub -> The Citadel (MAP19) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Citadel (MAP19) Main", player), lambda state: (state.has("The Citadel (MAP19)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -291,15 +287,15 @@ def set_episode2_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("The Citadel (MAP19) Main -> The Citadel (MAP19) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Citadel (MAP19) Main -> The Citadel (MAP19) Red", player), lambda state: (state.has("The Citadel (MAP19) - Red skull key", player, 1)) and (state.has("The Citadel (MAP19) - Blue skull key", player, 1) or state.has("The Citadel (MAP19) - Yellow skull key", player, 1))) - set_rule(world.get_entrance("The Citadel (MAP19) Red -> The Citadel (MAP19) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Citadel (MAP19) Red -> The Citadel (MAP19) Main", player), lambda state: (state.has("The Citadel (MAP19) - Red skull key", player, 1)) and (state.has("The Citadel (MAP19) - Yellow skull key", player, 1) or state.has("The Citadel (MAP19) - Blue skull key", player, 1))) # Gotcha! (MAP20) - set_rule(world.get_entrance("Hub -> Gotcha! (MAP20) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Gotcha! (MAP20) Main", player), lambda state: (state.has("Gotcha! (MAP20)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -309,9 +305,9 @@ def set_episode2_rules(player, world, pro): state.has("BFG9000", player, 1))) -def set_episode3_rules(player, world, pro): +def set_episode3_rules(player, multiworld, pro): # Nirvana (MAP21) - set_rule(world.get_entrance("Hub -> Nirvana (MAP21) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Nirvana (MAP21) Main", player), lambda state: (state.has("Nirvana (MAP21)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -319,19 +315,19 @@ def set_episode3_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Nirvana (MAP21) Main -> Nirvana (MAP21) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Nirvana (MAP21) Main -> Nirvana (MAP21) Yellow", player), lambda state: state.has("Nirvana (MAP21) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Main", player), lambda state: + set_rule(multiworld.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Main", player), lambda state: state.has("Nirvana (MAP21) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Magenta", player), lambda state: + set_rule(multiworld.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Magenta", player), lambda state: state.has("Nirvana (MAP21) - Red skull key", player, 1) and state.has("Nirvana (MAP21) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Nirvana (MAP21) Magenta -> Nirvana (MAP21) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Nirvana (MAP21) Magenta -> Nirvana (MAP21) Yellow", player), lambda state: state.has("Nirvana (MAP21) - Red skull key", player, 1) and state.has("Nirvana (MAP21) - Blue skull key", player, 1)) # The Catacombs (MAP22) - set_rule(world.get_entrance("Hub -> The Catacombs (MAP22) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Catacombs (MAP22) Main", player), lambda state: (state.has("The Catacombs (MAP22)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -339,15 +335,15 @@ def set_episode3_rules(player, world, pro): (state.has("BFG9000", player, 1) or state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1))) - set_rule(world.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Blue", player), lambda state: state.has("The Catacombs (MAP22) - Blue skull key", player, 1)) - set_rule(world.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Red", player), lambda state: state.has("The Catacombs (MAP22) - Red skull key", player, 1)) - set_rule(world.get_entrance("The Catacombs (MAP22) Red -> The Catacombs (MAP22) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Catacombs (MAP22) Red -> The Catacombs (MAP22) Main", player), lambda state: state.has("The Catacombs (MAP22) - Red skull key", player, 1)) # Barrels o Fun (MAP23) - set_rule(world.get_entrance("Hub -> Barrels o Fun (MAP23) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Barrels o Fun (MAP23) Main", player), lambda state: (state.has("Barrels o Fun (MAP23)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -355,13 +351,13 @@ def set_episode3_rules(player, world, pro): (state.has("Rocket launcher", player, 1) or state.has("Plasma gun", player, 1) or state.has("BFG9000", player, 1))) - set_rule(world.get_entrance("Barrels o Fun (MAP23) Main -> Barrels o Fun (MAP23) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Barrels o Fun (MAP23) Main -> Barrels o Fun (MAP23) Yellow", player), lambda state: state.has("Barrels o Fun (MAP23) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Barrels o Fun (MAP23) Yellow -> Barrels o Fun (MAP23) Main", player), lambda state: + set_rule(multiworld.get_entrance("Barrels o Fun (MAP23) Yellow -> Barrels o Fun (MAP23) Main", player), lambda state: state.has("Barrels o Fun (MAP23) - Yellow skull key", player, 1)) # The Chasm (MAP24) - set_rule(world.get_entrance("Hub -> The Chasm (MAP24) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Chasm (MAP24) Main", player), lambda state: state.has("The Chasm (MAP24)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -369,13 +365,13 @@ def set_episode3_rules(player, world, pro): state.has("Plasma gun", player, 1) and state.has("BFG9000", player, 1) and state.has("Super Shotgun", player, 1)) - set_rule(world.get_entrance("The Chasm (MAP24) Main -> The Chasm (MAP24) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (MAP24) Main -> The Chasm (MAP24) Red", player), lambda state: state.has("The Chasm (MAP24) - Red keycard", player, 1)) - set_rule(world.get_entrance("The Chasm (MAP24) Red -> The Chasm (MAP24) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (MAP24) Red -> The Chasm (MAP24) Main", player), lambda state: state.has("The Chasm (MAP24) - Red keycard", player, 1)) # Bloodfalls (MAP25) - set_rule(world.get_entrance("Hub -> Bloodfalls (MAP25) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Bloodfalls (MAP25) Main", player), lambda state: state.has("Bloodfalls (MAP25)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -383,13 +379,13 @@ def set_episode3_rules(player, world, pro): state.has("Plasma gun", player, 1) and state.has("BFG9000", player, 1) and state.has("Super Shotgun", player, 1)) - set_rule(world.get_entrance("Bloodfalls (MAP25) Main -> Bloodfalls (MAP25) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Bloodfalls (MAP25) Main -> Bloodfalls (MAP25) Blue", player), lambda state: state.has("Bloodfalls (MAP25) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Bloodfalls (MAP25) Blue -> Bloodfalls (MAP25) Main", player), lambda state: + set_rule(multiworld.get_entrance("Bloodfalls (MAP25) Blue -> Bloodfalls (MAP25) Main", player), lambda state: state.has("Bloodfalls (MAP25) - Blue skull key", player, 1)) # The Abandoned Mines (MAP26) - set_rule(world.get_entrance("Hub -> The Abandoned Mines (MAP26) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Abandoned Mines (MAP26) Main", player), lambda state: state.has("The Abandoned Mines (MAP26)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -397,19 +393,19 @@ def set_episode3_rules(player, world, pro): state.has("BFG9000", player, 1) and state.has("Plasma gun", player, 1) and state.has("Super Shotgun", player, 1)) - set_rule(world.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Yellow", player), lambda state: state.has("The Abandoned Mines (MAP26) - Yellow keycard", player, 1)) - set_rule(world.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Red", player), lambda state: state.has("The Abandoned Mines (MAP26) - Red keycard", player, 1)) - set_rule(world.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Blue", player), lambda state: state.has("The Abandoned Mines (MAP26) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Abandoned Mines (MAP26) Blue -> The Abandoned Mines (MAP26) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Blue -> The Abandoned Mines (MAP26) Main", player), lambda state: state.has("The Abandoned Mines (MAP26) - Blue keycard", player, 1)) - set_rule(world.get_entrance("The Abandoned Mines (MAP26) Yellow -> The Abandoned Mines (MAP26) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Yellow -> The Abandoned Mines (MAP26) Main", player), lambda state: state.has("The Abandoned Mines (MAP26) - Yellow keycard", player, 1)) # Monster Condo (MAP27) - set_rule(world.get_entrance("Hub -> Monster Condo (MAP27) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Monster Condo (MAP27) Main", player), lambda state: state.has("Monster Condo (MAP27)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -417,17 +413,17 @@ def set_episode3_rules(player, world, pro): state.has("Plasma gun", player, 1) and state.has("BFG9000", player, 1) and state.has("Super Shotgun", player, 1)) - set_rule(world.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Yellow", player), lambda state: state.has("Monster Condo (MAP27) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Red", player), lambda state: + set_rule(multiworld.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Red", player), lambda state: state.has("Monster Condo (MAP27) - Red skull key", player, 1)) - set_rule(world.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Blue", player), lambda state: state.has("Monster Condo (MAP27) - Blue skull key", player, 1)) - set_rule(world.get_entrance("Monster Condo (MAP27) Red -> Monster Condo (MAP27) Main", player), lambda state: + set_rule(multiworld.get_entrance("Monster Condo (MAP27) Red -> Monster Condo (MAP27) Main", player), lambda state: state.has("Monster Condo (MAP27) - Red skull key", player, 1)) # The Spirit World (MAP28) - set_rule(world.get_entrance("Hub -> The Spirit World (MAP28) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Spirit World (MAP28) Main", player), lambda state: state.has("The Spirit World (MAP28)", player, 1) and state.has("Shotgun", player, 1) and state.has("Rocket launcher", player, 1) and @@ -435,17 +431,17 @@ def set_episode3_rules(player, world, pro): state.has("Plasma gun", player, 1) and state.has("BFG9000", player, 1) and state.has("Super Shotgun", player, 1)) - set_rule(world.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Yellow", player), lambda state: state.has("The Spirit World (MAP28) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Red", player), lambda state: + set_rule(multiworld.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Red", player), lambda state: state.has("The Spirit World (MAP28) - Red skull key", player, 1)) - set_rule(world.get_entrance("The Spirit World (MAP28) Yellow -> The Spirit World (MAP28) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Spirit World (MAP28) Yellow -> The Spirit World (MAP28) Main", player), lambda state: state.has("The Spirit World (MAP28) - Yellow skull key", player, 1)) - set_rule(world.get_entrance("The Spirit World (MAP28) Red -> The Spirit World (MAP28) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Spirit World (MAP28) Red -> The Spirit World (MAP28) Main", player), lambda state: state.has("The Spirit World (MAP28) - Red skull key", player, 1)) # The Living End (MAP29) - set_rule(world.get_entrance("Hub -> The Living End (MAP29) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Living End (MAP29) Main", player), lambda state: state.has("The Living End (MAP29)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -455,7 +451,7 @@ def set_episode3_rules(player, world, pro): state.has("Super Shotgun", player, 1)) # Icon of Sin (MAP30) - set_rule(world.get_entrance("Hub -> Icon of Sin (MAP30) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Icon of Sin (MAP30) Main", player), lambda state: state.has("Icon of Sin (MAP30)", player, 1) and state.has("Rocket launcher", player, 1) and state.has("Shotgun", player, 1) and @@ -465,9 +461,9 @@ def set_episode3_rules(player, world, pro): state.has("Super Shotgun", player, 1)) -def set_episode4_rules(player, world, pro): +def set_episode4_rules(player, multiworld, pro): # Wolfenstein2 (MAP31) - set_rule(world.get_entrance("Hub -> Wolfenstein2 (MAP31) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Wolfenstein2 (MAP31) Main", player), lambda state: (state.has("Wolfenstein2 (MAP31)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -477,7 +473,7 @@ def set_episode4_rules(player, world, pro): state.has("BFG9000", player, 1))) # Grosse2 (MAP32) - set_rule(world.get_entrance("Hub -> Grosse2 (MAP32) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Grosse2 (MAP32) Main", player), lambda state: (state.has("Grosse2 (MAP32)", player, 1) and state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1) and @@ -489,13 +485,13 @@ def set_episode4_rules(player, world, pro): def set_rules(doom_ii_world: "DOOM2World", included_episodes, pro): player = doom_ii_world.player - world = doom_ii_world.multiworld + multiworld = doom_ii_world.multiworld if included_episodes[0]: - set_episode1_rules(player, world, pro) + set_episode1_rules(player, multiworld, pro) if included_episodes[1]: - set_episode2_rules(player, world, pro) + set_episode2_rules(player, multiworld, pro) if included_episodes[2]: - set_episode3_rules(player, world, pro) + set_episode3_rules(player, multiworld, pro) if included_episodes[3]: - set_episode4_rules(player, world, pro) + set_episode4_rules(player, multiworld, pro) diff --git a/worlds/doom_ii/__init__.py b/worlds/doom_ii/__init__.py index 22dee2ab743e..32c3cbd5a2c1 100644 --- a/worlds/doom_ii/__init__.py +++ b/worlds/doom_ii/__init__.py @@ -43,7 +43,6 @@ class DOOM2World(World): options: DOOM2Options game = "DOOM II" web = DOOM2Web() - data_version = 3 required_client_version = (0, 3, 9) item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()} @@ -61,24 +60,25 @@ class DOOM2World(World): # 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. items_ratio: Dict[str, float] = { - "Armor": 41, - "Mega Armor": 25, - "Berserk": 12, + "Armor": 39, + "Mega Armor": 23, + "Berserk": 11, "Invulnerability": 10, "Partial invisibility": 18, - "Supercharge": 28, + "Supercharge": 26, "Medikit": 15, "Box of bullets": 13, "Box of rockets": 13, "Box of shotgun shells": 13, - "Energy cell pack": 10 + "Energy cell pack": 10, + "Megasphere": 7 } - def __init__(self, world: MultiWorld, player: int): + def __init__(self, multiworld: MultiWorld, player: int): self.included_episodes = [1, 1, 1, 0] self.location_count = 0 - super().__init__(world, player) + super().__init__(multiworld, player) def get_episode_count(self): # Don't include 4th, those are secret levels they are additive @@ -172,7 +172,7 @@ def set_rules(self): # platform) Unless the user allows for it. if not allow_death_logic: for death_logic_location in Locations.death_logic_locations: - self.multiworld.exclude_locations[self.player].value.add(death_logic_location) + self.options.exclude_locations.value.add(death_logic_location) def create_item(self, name: str) -> DOOM2Item: item_id: int = self.item_name_to_id[name] @@ -234,6 +234,7 @@ def create_items(self): self.create_ratioed_items("Invulnerability", itempool) self.create_ratioed_items("Partial invisibility", itempool) self.create_ratioed_items("Supercharge", itempool) + self.create_ratioed_items("Megasphere", itempool) while len(itempool) < self.location_count: itempool.append(self.create_item(self.get_filler_item_name())) diff --git a/worlds/doom_ii/docs/en_DOOM II.md b/worlds/doom_ii/docs/en_DOOM II.md index d561745b76c2..d02f75cb6c8f 100644 --- a/worlds/doom_ii/docs/en_DOOM II.md +++ b/worlds/doom_ii/docs/en_DOOM II.md @@ -1,8 +1,8 @@ # DOOM II -## Where is the settings page? +## Where is the options page? -The [player settings page](../player-settings) contains the options needed to configure your game session. +The [player options page](../player-options) contains the options needed to configure your game session. ## What does randomization do to this game? diff --git a/worlds/doom_ii/docs/setup_en.md b/worlds/doom_ii/docs/setup_en.md index 321d440ea68b..87054ab30783 100644 --- a/worlds/doom_ii/docs/setup_en.md +++ b/worlds/doom_ii/docs/setup_en.md @@ -13,7 +13,7 @@ 1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it. 2. Copy DOOM2.WAD from your steam install into the extracted folder. You can find the folder in steam by finding the game in your library, - right clicking it and choosing *Manage→Browse Local Files*. + right clicking it and choosing *Manage→Browse Local Files*. The WAD file is in the `/base/` folder. ## Joining a MultiWorld Game diff --git a/worlds/factorio/Client.py b/worlds/factorio/Client.py index 050455bb076a..23dfa0633eb4 100644 --- a/worlds/factorio/Client.py +++ b/worlds/factorio/Client.py @@ -21,7 +21,7 @@ from CommonClient import ClientCommandProcessor, CommonContext, logger, server_loop, gui_enabled, get_base_parser from MultiServer import mark_raw from NetUtils import ClientStatus, NetworkItem, JSONtoTextParser, JSONMessagePart -from Utils import async_start +from Utils import async_start, get_file_safe_name def check_stdin() -> None: @@ -120,7 +120,7 @@ def on_print_json(self, args: dict): @property def savegame_name(self) -> str: - return f"AP_{self.seed_name}_{self.auth}_Save.zip" + return get_file_safe_name(f"AP_{self.seed_name}_{self.auth}")+"_Save.zip" def print_to_game(self, text): self.rcon_client.send_command(f"/ap-print [font=default-large-bold]Archipelago:[/font] " @@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext): if ctx.locations_checked != research_data: bridge_logger.debug( f"New researches done: " - f"{[ctx.location_names[rid] for rid in research_data - ctx.locations_checked]}") + f"{[ctx.location_names.lookup_in_game(rid) for rid in research_data - ctx.locations_checked]}") ctx.locations_checked = research_data await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}]) death_link_tick = data.get("death_link_tick", 0) @@ -360,7 +360,7 @@ async def factorio_server_watcher(ctx: FactorioContext): transfer_item: NetworkItem = ctx.items_received[ctx.send_index] item_id = transfer_item.item player_name = ctx.player_names[transfer_item.player] - item_name = ctx.item_names[item_id] + item_name = ctx.item_names.lookup_in_game(item_id) factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.") commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}" ctx.send_index += 1 @@ -521,7 +521,7 @@ def _handle_color(self, node: JSONMessagePart): rcon_password = args.rcon_password if args.rcon_password else ''.join( random.choice(string.ascii_letters) for x in range(32)) factorio_server_logger = logging.getLogger("FactorioServer") -options = Utils.get_options() +options = Utils.get_settings() executable = options["factorio_options"]["executable"] server_settings = args.server_settings if args.server_settings \ else options["factorio_options"].get("server_settings", None) diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 3b7475738489..1ea2f6e4c98c 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -95,7 +95,6 @@ class Factorio(World): item_name_groups = { "Progressive": set(progressive_tech_table.keys()), } - data_version = 8 required_client_version = (0, 4, 2) ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs() diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua index 8ce0b45a5f67..ace231e12b4b 100644 --- a/worlds/factorio/data/mod_template/control.lua +++ b/worlds/factorio/data/mod_template/control.lua @@ -660,11 +660,18 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi end local tech local force = game.forces["player"] + if call.parameter == nil then + game.print("ap-get-technology is only to be used by the Archipelago Factorio Client") + return + end chunks = split(call.parameter, "\t") local item_name = chunks[1] local index = chunks[2] local source = chunks[3] or "Archipelago" - if index == -1 then -- for coop sync and restoring from an older savegame + if index == nil then + game.print("ap-get-technology is only to be used by the Archipelago Factorio Client") + return + elseif index == -1 then -- for coop sync and restoring from an older savegame tech = force.technologies[item_name] if tech.researched ~= true then game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."}) diff --git a/worlds/factorio/docs/en_Factorio.md b/worlds/factorio/docs/en_Factorio.md index dbc33d05dfde..94d2a5505bda 100644 --- a/worlds/factorio/docs/en_Factorio.md +++ b/worlds/factorio/docs/en_Factorio.md @@ -1,8 +1,8 @@ # Factorio -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -36,7 +36,7 @@ inventory. ## What is EnergyLink? EnergyLink is an energy storage supported by certain games that is shared across all worlds in a multiworld. -In Factorio, if enabled in the player settings, EnergyLink Bridge buildings can be crafted and placed, which allow +In Factorio, if enabled in the player options, EnergyLink Bridge buildings can be crafted and placed, which allow depositing excess energy and supplementing energy deficits, much like Accumulators. Each placed EnergyLink Bridge provides 10 MW of throughput. The shared storage has unlimited capacity, but 25% of energy diff --git a/worlds/factorio/docs/setup_en.md b/worlds/factorio/docs/setup_en.md index b6d45459253a..0b4ccee0d7f2 100644 --- a/worlds/factorio/docs/setup_en.md +++ b/worlds/factorio/docs/setup_en.md @@ -25,8 +25,8 @@ options. ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Factorio player settings page: [Factorio Settings Page](/games/Factorio/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Factorio player options page: [Factorio Options Page](/games/Factorio/player-options) ### Verifying your config file @@ -133,7 +133,7 @@ This allows you to host your own Factorio game. For additional client features, issue the `/help` command in the Archipelago Client. Once connected to the AP server, you can also issue the `!help` command to learn about additional commands like `!hint`. For more information about the commands you can use, see the [Commands Guide](/tutorial/Archipelago/commands/en) and -[Other Settings](#other-settings). +[Other Options](#other-options). ## Allowing Other People to Join Your Game @@ -148,11 +148,11 @@ For more information about the commands you can use, see the [Commands Guide](/t By default, peaceful mode is disabled. There are two methods to enable peaceful mode: ### By config file -You can specify Factorio game settings such as peaceful mode and terrain and resource generation parameters in your -config .yaml file by including the `world_gen` setting. This setting is currently not supported by the web UI, so you'll +You can specify Factorio game options such as peaceful mode and terrain and resource generation parameters in your +config .yaml file by including the `world_gen` option. This option is currently not supported by the web UI, so you'll have to manually create or edit your config file with a text editor of your choice. The [template file](/static/generated/configs/Factorio.yaml) is a good starting point and contains the default value of -the `world_gen` setting. If you already have a config file you may also just copy that setting over from the template. +the `world_gen` option. If you already have a config file you may also just copy that option over from the template. To enable peaceful mode, simply replace `peaceful_mode: false` with `peaceful_mode: true`. Finally, use the [.yaml checker](/check) to ensure your file is valid. @@ -165,7 +165,7 @@ enable peaceful mode by entering the following commands into your Archipelago Fa ``` (If this warns you that these commands may disable achievements, you may need to repeat them for them to take effect.) -## Other Settings +## Other Options ### filter_item_sends diff --git a/worlds/factorio/requirements.txt b/worlds/factorio/requirements.txt index c45fb771da6a..8d684401663b 100644 --- a/worlds/factorio/requirements.txt +++ b/worlds/factorio/requirements.txt @@ -1 +1 @@ -factorio-rcon-py>=2.0.1 +factorio-rcon-py>=2.1.2 diff --git a/worlds/ff1/Options.py b/worlds/ff1/Options.py index 0993d103d575..d8d24a529f36 100644 --- a/worlds/ff1/Options.py +++ b/worlds/ff1/Options.py @@ -1,6 +1,6 @@ -from typing import Dict +from dataclasses import dataclass -from Options import OptionDict +from Options import OptionDict, PerGameCommonOptions class Locations(OptionDict): @@ -18,8 +18,8 @@ class Rules(OptionDict): display_name = "rules" -ff1_options: Dict[str, OptionDict] = { - "locations": Locations, - "items": Items, - "rules": Rules -} +@dataclass +class FF1Options(PerGameCommonOptions): + locations: Locations + items: Items + rules: Rules diff --git a/worlds/ff1/__init__.py b/worlds/ff1/__init__.py index 4ff361c07243..3a5047506850 100644 --- a/worlds/ff1/__init__.py +++ b/worlds/ff1/__init__.py @@ -5,7 +5,7 @@ from BaseClasses import Item, Location, MultiWorld, Tutorial, ItemClassification from .Items import ItemData, FF1Items, FF1_STARTER_ITEMS, FF1_PROGRESSION_LIST, FF1_BRIDGE from .Locations import EventId, FF1Locations, generate_rule, CHAOS_TERMINATED_EVENT -from .Options import ff1_options +from .Options import FF1Options from ..AutoWorld import World, WebWorld @@ -34,12 +34,12 @@ class FF1World(World): Part puzzle and part speed-run, it breathes new life into one of the most influential games ever made. """ - option_definitions = ff1_options + options: FF1Options + options_dataclass = FF1Options settings: typing.ClassVar[FF1Settings] settings_key = "ffr_options" game = "Final Fantasy" topology_present = False - data_version = 2 ff1_items = FF1Items() ff1_locations = FF1Locations() @@ -58,20 +58,20 @@ def __init__(self, world: MultiWorld, player: int): def stage_assert_generate(cls, multiworld: MultiWorld) -> None: # Fail generation if there are no items in the pool for player in multiworld.get_game_players(cls.game): - options = get_options(multiworld, 'items', player) - assert options,\ + items = multiworld.worlds[player].options.items.value + assert items, \ f"FFR settings submitted with no key items ({multiworld.get_player_name(player)}). Please ensure you " \ f"generated the settings using finalfantasyrandomizer.com AND enabled the AP flag" def create_regions(self): - locations = get_options(self.multiworld, 'locations', self.player) - rules = get_options(self.multiworld, 'rules', self.player) + locations = self.options.locations.value + rules = self.options.rules.value menu_region = self.ff1_locations.create_menu_region(self.player, locations, rules, self.multiworld) terminated_event = Location(self.player, CHAOS_TERMINATED_EVENT, EventId, menu_region) terminated_item = Item(CHAOS_TERMINATED_EVENT, ItemClassification.progression, EventId, self.player) terminated_event.place_locked_item(terminated_item) - items = get_options(self.multiworld, 'items', self.player) + items = self.options.items.value goal_rule = generate_rule([[name for name in items.keys() if name in FF1_PROGRESSION_LIST and name != "Shard"]], self.player) terminated_event.access_rule = goal_rule @@ -93,7 +93,7 @@ def set_rules(self): self.multiworld.completion_condition[self.player] = lambda state: state.has(CHAOS_TERMINATED_EVENT, self.player) def create_items(self): - items = get_options(self.multiworld, 'items', self.player) + items = self.options.items.value if FF1_BRIDGE in items.keys(): self._place_locked_item_in_sphere0(FF1_BRIDGE) if items: @@ -109,7 +109,7 @@ def create_items(self): def _place_locked_item_in_sphere0(self, progression_item: str): if progression_item: - rules = get_options(self.multiworld, 'rules', self.player) + rules = self.options.rules.value sphere_0_locations = [name for name, rules in rules.items() if rules and len(rules[0]) == 0 and name not in self.locked_locations] if sphere_0_locations: @@ -126,7 +126,3 @@ def fill_slot_data(self) -> Dict[str, object]: def get_filler_item_name(self) -> str: return self.multiworld.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"]) - - -def get_options(world: MultiWorld, name: str, player: int): - return getattr(world, name, None)[player].value diff --git a/worlds/ff1/docs/en_Final Fantasy.md b/worlds/ff1/docs/en_Final Fantasy.md index 59fa85d91613..889bb46e0c35 100644 --- a/worlds/ff1/docs/en_Final Fantasy.md +++ b/worlds/ff1/docs/en_Final Fantasy.md @@ -1,9 +1,9 @@ # Final Fantasy 1 (NES) -## Where is the settings page? +## Where is the options page? -Unlike most games on Archipelago.gg, Final Fantasy 1's settings are controlled entirely by the original randomzier. You -can find an exhaustive list of documented settings on the FFR +Unlike most games on Archipelago.gg, Final Fantasy 1's options are controlled entirely by the original randomzier. You +can find an exhaustive list of documented options on the FFR website: [FF1R Website](https://finalfantasyrandomizer.com/) ## What does randomization do to this game? diff --git a/worlds/ffmq/Client.py b/worlds/ffmq/Client.py index 7de486314c6c..93688a6116f6 100644 --- a/worlds/ffmq/Client.py +++ b/worlds/ffmq/Client.py @@ -71,7 +71,7 @@ async def game_watcher(self, ctx): received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1]) data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START) check_2 = await snes_read(ctx, 0xF53749, 1) - if check_1 in (b'\x00', b'\x55') or check_2 in (b'\x00', b'\x55'): + if check_1 != b'\x01' or check_2 != b'\x01': return def get_range(data_range): diff --git a/worlds/ffmq/Items.py b/worlds/ffmq/Items.py index d0898d7e81c8..f1c102d34ef8 100644 --- a/worlds/ffmq/Items.py +++ b/worlds/ffmq/Items.py @@ -222,10 +222,10 @@ def yaml_item(text): def create_items(self) -> None: items = [] - starting_weapon = self.multiworld.starting_weapon[self.player].current_key.title().replace("_", " ") + starting_weapon = self.options.starting_weapon.current_key.title().replace("_", " ") self.multiworld.push_precollected(self.create_item(starting_weapon)) self.multiworld.push_precollected(self.create_item("Steel Armor")) - if self.multiworld.sky_coin_mode[self.player] == "start_with": + if self.options.sky_coin_mode == "start_with": self.multiworld.push_precollected(self.create_item("Sky Coin")) precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]} @@ -233,28 +233,28 @@ def create_items(self) -> None: def add_item(item_name): if item_name in ["Steel Armor", "Sky Fragment"] or "Progressive" in item_name: return - if item_name.lower().replace(" ", "_") == self.multiworld.starting_weapon[self.player].current_key: + if item_name.lower().replace(" ", "_") == self.options.starting_weapon.current_key: return - if self.multiworld.progressive_gear[self.player]: + if self.options.progressive_gear: for item_group in prog_map: if item_name in self.item_name_groups[item_group]: item_name = prog_map[item_group] break if item_name == "Sky Coin": - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + if self.options.sky_coin_mode == "shattered_sky_coin": for _ in range(40): items.append(self.create_item("Sky Fragment")) return - elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": + elif self.options.sky_coin_mode == "save_the_crystals": items.append(self.create_filler()) return if item_name in precollected_item_names: items.append(self.create_filler()) return i = self.create_item(item_name) - if self.multiworld.logic[self.player] != "friendly" and item_name in ("Magic Mirror", "Mask"): + if self.options.logic != "friendly" and item_name in ("Magic Mirror", "Mask"): i.classification = ItemClassification.useful - if (self.multiworld.logic[self.player] == "expert" and self.multiworld.map_shuffle[self.player] == "none" and + if (self.options.logic == "expert" and self.options.map_shuffle == "none" and item_name == "Exit Book"): i.classification = ItemClassification.progression items.append(i) @@ -263,11 +263,11 @@ def add_item(item_name): for item in self.item_name_groups[item_group]: add_item(item) - if self.multiworld.brown_boxes[self.player] == "include": + if self.options.brown_boxes == "include": filler_items = [] for item, count in fillers.items(): filler_items += [self.create_item(item) for _ in range(count)] - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + if self.options.sky_coin_mode == "shattered_sky_coin": self.multiworld.random.shuffle(filler_items) filler_items = filler_items[39:] items += filler_items diff --git a/worlds/ffmq/Options.py b/worlds/ffmq/Options.py index 4b9f4a4a8819..41c397315f87 100644 --- a/worlds/ffmq/Options.py +++ b/worlds/ffmq/Options.py @@ -1,4 +1,5 @@ -from Options import Choice, FreeText, Toggle, Range +from Options import Choice, FreeText, Toggle, Range, PerGameCommonOptions +from dataclasses import dataclass class Logic(Choice): @@ -207,10 +208,10 @@ class CrestShuffle(Toggle): class MapShuffleSeed(FreeText): - """If this is a number, it will be used as a set seed number for Map, Crest, and Battlefield Reward shuffles. + """If this is a number, it will be used as a set seed number for Map, Crest, Battlefield Reward, and Companion shuffles. If this is "random" the seed will be chosen randomly. If it is any other text, it will be used as a seed group name. All players using the same seed group name will get the same shuffle results, as long as their Map Shuffle, - Crest Shuffle, and Shuffle Battlefield Rewards settings are the same.""" + Crest Shuffle, Shuffle Battlefield Rewards, Companion Shuffle, and Kaeli's Mom settings are the same.""" display_name = "Map Shuffle Seed" default = "random" @@ -321,36 +322,36 @@ class KaelisMomFightsMinotaur(Toggle): default = 0 -option_definitions = { - "logic": Logic, - "brown_boxes": BrownBoxes, - "sky_coin_mode": SkyCoinMode, - "shattered_sky_coin_quantity": ShatteredSkyCoinQuantity, - "starting_weapon": StartingWeapon, - "progressive_gear": ProgressiveGear, - "leveling_curve": LevelingCurve, - "starting_companion": StartingCompanion, - "available_companions": AvailableCompanions, - "companions_locations": CompanionsLocations, - "kaelis_mom_fight_minotaur": KaelisMomFightsMinotaur, - "companion_leveling_type": CompanionLevelingType, - "companion_spellbook_type": CompanionSpellbookType, - "enemies_density": EnemiesDensity, - "enemies_scaling_lower": EnemiesScalingLower, - "enemies_scaling_upper": EnemiesScalingUpper, - "bosses_scaling_lower": BossesScalingLower, - "bosses_scaling_upper": BossesScalingUpper, - "enemizer_attacks": EnemizerAttacks, - "enemizer_groups": EnemizerGroups, - "shuffle_res_weak_types": ShuffleResWeakType, - "shuffle_enemies_position": ShuffleEnemiesPositions, - "progressive_formations": ProgressiveFormations, - "doom_castle_mode": DoomCastle, - "doom_castle_shortcut": DoomCastleShortcut, - "tweak_frustrating_dungeons": TweakFrustratingDungeons, - "map_shuffle": MapShuffle, - "crest_shuffle": CrestShuffle, - "shuffle_battlefield_rewards": ShuffleBattlefieldRewards, - "map_shuffle_seed": MapShuffleSeed, - "battlefields_battles_quantities": BattlefieldsBattlesQuantities, -} +@dataclass +class FFMQOptions(PerGameCommonOptions): + logic: Logic + brown_boxes: BrownBoxes + sky_coin_mode: SkyCoinMode + shattered_sky_coin_quantity: ShatteredSkyCoinQuantity + starting_weapon: StartingWeapon + progressive_gear: ProgressiveGear + leveling_curve: LevelingCurve + starting_companion: StartingCompanion + available_companions: AvailableCompanions + companions_locations: CompanionsLocations + kaelis_mom_fight_minotaur: KaelisMomFightsMinotaur + companion_leveling_type: CompanionLevelingType + companion_spellbook_type: CompanionSpellbookType + enemies_density: EnemiesDensity + enemies_scaling_lower: EnemiesScalingLower + enemies_scaling_upper: EnemiesScalingUpper + bosses_scaling_lower: BossesScalingLower + bosses_scaling_upper: BossesScalingUpper + enemizer_attacks: EnemizerAttacks + enemizer_groups: EnemizerGroups + shuffle_res_weak_types: ShuffleResWeakType + shuffle_enemies_position: ShuffleEnemiesPositions + progressive_formations: ProgressiveFormations + doom_castle_mode: DoomCastle + doom_castle_shortcut: DoomCastleShortcut + tweak_frustrating_dungeons: TweakFrustratingDungeons + map_shuffle: MapShuffle + crest_shuffle: CrestShuffle + shuffle_battlefield_rewards: ShuffleBattlefieldRewards + map_shuffle_seed: MapShuffleSeed + battlefields_battles_quantities: BattlefieldsBattlesQuantities diff --git a/worlds/ffmq/Output.py b/worlds/ffmq/Output.py index 9daee9f643a9..1e436a90c5fd 100644 --- a/worlds/ffmq/Output.py +++ b/worlds/ffmq/Output.py @@ -1,13 +1,13 @@ import yaml import os import zipfile +import Utils from copy import deepcopy from .Regions import object_id_table -from Utils import __version__ -from worlds.Files import APContainer +from worlds.Files import APPatch import pkgutil -settings_template = yaml.load(pkgutil.get_data(__name__, "data/settings.yaml"), yaml.Loader) +settings_template = Utils.parse_yaml(pkgutil.get_data(__name__, "data/settings.yaml")) def generate_output(self, output_directory): @@ -21,7 +21,7 @@ def output_item_name(item): item_name = "".join(item_name.split(" ")) else: if item.advancement or item.useful or (item.trap and - self.multiworld.per_slot_randoms[self.player].randint(0, 1)): + self.random.randint(0, 1)): item_name = "APItem" else: item_name = "APItemFiller" @@ -46,60 +46,60 @@ def tf(option): options = deepcopy(settings_template) options["name"] = self.multiworld.player_name[self.player] option_writes = { - "enemies_density": cc(self.multiworld.enemies_density[self.player]), + "enemies_density": cc(self.options.enemies_density), "chests_shuffle": "Include", - "shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle", + "shuffle_boxes_content": self.options.brown_boxes == "shuffle", "npcs_shuffle": "Include", "battlefields_shuffle": "Include", - "logic_options": cc(self.multiworld.logic[self.player]), - "shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]), - "enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]), - "enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]), - "bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]), - "bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]), - "enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]), - "leveling_curve": cc(self.multiworld.leveling_curve[self.player]), - "battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if - self.multiworld.battlefields_battles_quantities[self.player].value < 5 else + "logic_options": cc(self.options.logic), + "shuffle_enemies_position": tf(self.options.shuffle_enemies_position), + "enemies_scaling_lower": cc(self.options.enemies_scaling_lower), + "enemies_scaling_upper": cc(self.options.enemies_scaling_upper), + "bosses_scaling_lower": cc(self.options.bosses_scaling_lower), + "bosses_scaling_upper": cc(self.options.bosses_scaling_upper), + "enemizer_attacks": cc(self.options.enemizer_attacks), + "leveling_curve": cc(self.options.leveling_curve), + "battles_quantity": cc(self.options.battlefields_battles_quantities) if + self.options.battlefields_battles_quantities.value < 5 else "RandomLow" if - self.multiworld.battlefields_battles_quantities[self.player].value == 5 else + self.options.battlefields_battles_quantities.value == 5 else "RandomHigh", - "shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]), + "shuffle_battlefield_rewards": tf(self.options.shuffle_battlefield_rewards), "random_starting_weapon": True, - "progressive_gear": tf(self.multiworld.progressive_gear[self.player]), - "tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]), - "doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]), - "doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]), - "sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]), - "sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]), + "progressive_gear": tf(self.options.progressive_gear), + "tweaked_dungeons": tf(self.options.tweak_frustrating_dungeons), + "doom_castle_mode": cc(self.options.doom_castle_mode), + "doom_castle_shortcut": tf(self.options.doom_castle_shortcut), + "sky_coin_mode": cc(self.options.sky_coin_mode), + "sky_coin_fragments_qty": cc(self.options.shattered_sky_coin_quantity), "enable_spoilers": False, - "progressive_formations": cc(self.multiworld.progressive_formations[self.player]), - "map_shuffling": cc(self.multiworld.map_shuffle[self.player]), - "crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]), - "enemizer_groups": cc(self.multiworld.enemizer_groups[self.player]), - "shuffle_res_weak_type": tf(self.multiworld.shuffle_res_weak_types[self.player]), - "companion_leveling_type": cc(self.multiworld.companion_leveling_type[self.player]), - "companion_spellbook_type": cc(self.multiworld.companion_spellbook_type[self.player]), - "starting_companion": cc(self.multiworld.starting_companion[self.player]), + "progressive_formations": cc(self.options.progressive_formations), + "map_shuffling": cc(self.options.map_shuffle), + "crest_shuffle": tf(self.options.crest_shuffle), + "enemizer_groups": cc(self.options.enemizer_groups), + "shuffle_res_weak_type": tf(self.options.shuffle_res_weak_types), + "companion_leveling_type": cc(self.options.companion_leveling_type), + "companion_spellbook_type": cc(self.options.companion_spellbook_type), + "starting_companion": cc(self.options.starting_companion), "available_companions": ["Zero", "One", "Two", - "Three", "Four"][self.multiworld.available_companions[self.player].value], - "companions_locations": cc(self.multiworld.companions_locations[self.player]), - "kaelis_mom_fight_minotaur": tf(self.multiworld.kaelis_mom_fight_minotaur[self.player]), + "Three", "Four"][self.options.available_companions.value], + "companions_locations": cc(self.options.companions_locations), + "kaelis_mom_fight_minotaur": tf(self.options.kaelis_mom_fight_minotaur), } for option, data in option_writes.items(): options["Final Fantasy Mystic Quest"][option][data] = 1 - rom_name = f'MQ{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21] + rom_name = f'MQ{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21] self.rom_name = bytearray(rom_name, 'utf8') self.rom_name_available_event.set() setup = {"version": "1.5", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed": - hex(self.multiworld.per_slot_randoms[self.player].randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} + hex(self.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]] - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + if self.options.sky_coin_mode == "shattered_sky_coin": starting_items.append("SkyCoin") file_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.apmq") @@ -116,10 +116,10 @@ def tf(option): APMQ.write_contents(zf) -class APMQFile(APContainer): +class APMQFile(APPatch): game = "Final Fantasy Mystic Quest" def get_manifest(self): manifest = super().get_manifest() manifest["patch_file_ending"] = ".apmq" - return manifest \ No newline at end of file + return manifest diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py index 8b83c88e72c9..c1d3d619ffaa 100644 --- a/worlds/ffmq/Regions.py +++ b/worlds/ffmq/Regions.py @@ -1,11 +1,9 @@ from BaseClasses import Region, MultiWorld, Entrance, Location, LocationProgressType, ItemClassification from worlds.generic.Rules import add_rule +from .data.rooms import rooms, entrances from .Items import item_groups, yaml_item -import pkgutil -import yaml -rooms = yaml.load(pkgutil.get_data(__name__, "data/rooms.yaml"), yaml.Loader) -entrance_names = {entrance["id"]: entrance["name"] for entrance in yaml.load(pkgutil.get_data(__name__, "data/entrances.yaml"), yaml.Loader)} +entrance_names = {entrance["id"]: entrance["name"] for entrance in entrances} object_id_table = {} object_type_table = {} @@ -69,7 +67,7 @@ def create_regions(self): location_table else None, object["type"], object["access"], self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for object in room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in ("BattlefieldGp", - "BattlefieldXp") and (object["type"] != "Box" or self.multiworld.brown_boxes[self.player] == "include") and + "BattlefieldXp") and (object["type"] != "Box" or self.options.brown_boxes == "include") and not (object["name"] == "Kaeli Companion" and not object["on_trigger"])], room["links"])) dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player) @@ -91,15 +89,13 @@ def create_regions(self): if "entrance" in link and link["entrance"] != -1: spoiler = False if link["entrance"] in crest_warps: - if self.multiworld.crest_shuffle[self.player]: + if self.options.crest_shuffle: spoiler = True - elif self.multiworld.map_shuffle[self.player] == "everything": + elif self.options.map_shuffle == "everything": spoiler = True - elif "Subregion" in region.name and self.multiworld.map_shuffle[self.player] not in ("dungeons", - "none"): + elif "Subregion" in region.name and self.options.map_shuffle not in ("dungeons", "none"): spoiler = True - elif "Subregion" not in region.name and self.multiworld.map_shuffle[self.player] not in ("none", - "overworld"): + elif "Subregion" not in region.name and self.options.map_shuffle not in ("none", "overworld"): spoiler = True if spoiler: @@ -111,6 +107,7 @@ def create_regions(self): connection.connect(connect_room) break + non_dead_end_crest_rooms = [ 'Libra Temple', 'Aquaria Gemini Room', "GrenadeMan's Mobius Room", 'Fireburg Gemini Room', 'Sealed Temple', 'Alive Forest', 'Kaidge Temple Upper Ledge', @@ -140,7 +137,7 @@ def hard_boss_logic(state): add_rule(self.multiworld.get_location("Gidrah", self.player), hard_boss_logic) add_rule(self.multiworld.get_location("Dullahan", self.player), hard_boss_logic) - if self.multiworld.map_shuffle[self.player]: + if self.options.map_shuffle: for boss in ("Freezer Crab", "Ice Golem", "Jinn", "Medusa", "Dualhead Hydra"): loc = self.multiworld.get_location(boss, self.player) checked_regions = {loc.parent_region} @@ -158,12 +155,12 @@ def check_foresta(region): return True check_foresta(loc.parent_region) - if self.multiworld.logic[self.player] == "friendly": + if self.options.logic == "friendly": process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player), ["MagicMirror"]) process_rules(self.multiworld.get_entrance("Overworld - Volcano", self.player), ["Mask"]) - if self.multiworld.map_shuffle[self.player] in ("none", "overworld"): + if self.options.map_shuffle in ("none", "overworld"): process_rules(self.multiworld.get_entrance("Overworld - Bone Dungeon", self.player), ["Bomb"]) process_rules(self.multiworld.get_entrance("Overworld - Wintry Cave", self.player), @@ -185,8 +182,8 @@ def check_foresta(region): process_rules(self.multiworld.get_entrance("Overworld - Mac Ship Doom", self.player), ["DragonClaw", "CaptainCap"]) - if self.multiworld.logic[self.player] == "expert": - if self.multiworld.map_shuffle[self.player] == "none" and not self.multiworld.crest_shuffle[self.player]: + if self.options.logic == "expert": + if self.options.map_shuffle == "none" and not self.options.crest_shuffle: inner_room = self.multiworld.get_region("Wintry Temple Inner Room", self.player) connection = Entrance(self.player, "Sealed Temple Exit Trick", inner_room) connection.connect(self.multiworld.get_region("Wintry Temple Outer Room", self.player)) @@ -198,14 +195,14 @@ def check_foresta(region): if entrance.connected_region.name in non_dead_end_crest_rooms: entrance.access_rule = lambda state: False - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": - logic_coins = [16, 24, 32, 32, 38][self.multiworld.shattered_sky_coin_quantity[self.player].value] + if self.options.sky_coin_mode == "shattered_sky_coin": + logic_coins = [16, 24, 32, 32, 38][self.options.shattered_sky_coin_quantity.value] self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ lambda state: state.has("Sky Fragment", self.player, logic_coins) - elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": + elif self.options.sky_coin_mode == "save_the_crystals": self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ lambda state: state.has_all(["Flamerus Rex", "Dualhead Hydra", "Ice Golem", "Pazuzu"], self.player) - elif self.multiworld.sky_coin_mode[self.player] in ("standard", "start_with"): + elif self.options.sky_coin_mode in ("standard", "start_with"): self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ lambda state: state.has("Sky Coin", self.player) @@ -213,26 +210,24 @@ def check_foresta(region): def stage_set_rules(multiworld): # If there's no enemies, there's no repeatable income sources no_enemies_players = [player for player in multiworld.get_game_players("Final Fantasy Mystic Quest") - if multiworld.enemies_density[player] == "none"] + if multiworld.worlds[player].options.enemies_density == "none"] if (len([item for item in multiworld.itempool if item.classification in (ItemClassification.filler, ItemClassification.trap)]) > len([player for player in no_enemies_players if - multiworld.accessibility[player] == "minimal"]) * 3): + multiworld.worlds[player].options.accessibility == "minimal"]) * 3): for player in no_enemies_players: for location in vendor_locations: - if multiworld.accessibility[player] == "locations": + if multiworld.worlds[player].options.accessibility == "full": multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED else: multiworld.get_location(location, player).access_rule = lambda state: False else: # There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing - # advancement items so that useful items can be placed + # advancement items so that useful items can be placed. for player in no_enemies_players: for location in vendor_locations: multiworld.get_location(location, player).item_rule = lambda item: not item.advancement - - class FFMQLocation(Location): game = "Final Fantasy Mystic Quest" diff --git a/worlds/ffmq/__init__.py b/worlds/ffmq/__init__.py index b995cc427c9b..3c58487265a6 100644 --- a/worlds/ffmq/__init__.py +++ b/worlds/ffmq/__init__.py @@ -10,7 +10,7 @@ non_dead_end_crest_warps from .Items import item_table, item_groups, create_items, FFMQItem, fillers from .Output import generate_output -from .Options import option_definitions +from .Options import FFMQOptions from .Client import FFMQClient @@ -25,14 +25,25 @@ class FFMQWebWorld(WebWorld): - tutorials = [Tutorial( + setup_en = Tutorial( "Multiworld Setup Guide", "A guide to playing Final Fantasy Mystic Quest with Archipelago.", "English", "setup_en.md", "setup/en", ["Alchav"] - )] + ) + + setup_fr = Tutorial( + setup_en.tutorial_name, + setup_en.description, + "Français", + "setup_fr.md", + "setup/fr", + ["Artea"] + ) + + tutorials = [setup_en, setup_fr] class FFMQWorld(World): @@ -45,7 +56,8 @@ class FFMQWorld(World): item_name_to_id = {name: data.id for name, data in item_table.items() if data.id is not None} location_name_to_id = location_table - option_definitions = option_definitions + options_dataclass = FFMQOptions + options: FFMQOptions topology_present = True @@ -56,8 +68,6 @@ class FFMQWorld(World): create_regions = create_regions set_rules = set_rules stage_set_rules = stage_set_rules - - data_version = 1 web = FFMQWebWorld() # settings: FFMQSettings @@ -69,20 +79,14 @@ def __init__(self, world, player: int): super().__init__(world, player) def generate_early(self): - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": - self.multiworld.brown_boxes[self.player].value = 1 - if self.multiworld.enemies_scaling_lower[self.player].value > \ - self.multiworld.enemies_scaling_upper[self.player].value: - (self.multiworld.enemies_scaling_lower[self.player].value, - self.multiworld.enemies_scaling_upper[self.player].value) =\ - (self.multiworld.enemies_scaling_upper[self.player].value, - self.multiworld.enemies_scaling_lower[self.player].value) - if self.multiworld.bosses_scaling_lower[self.player].value > \ - self.multiworld.bosses_scaling_upper[self.player].value: - (self.multiworld.bosses_scaling_lower[self.player].value, - self.multiworld.bosses_scaling_upper[self.player].value) =\ - (self.multiworld.bosses_scaling_upper[self.player].value, - self.multiworld.bosses_scaling_lower[self.player].value) + if self.options.sky_coin_mode == "shattered_sky_coin": + self.options.brown_boxes.value = 1 + if self.options.enemies_scaling_lower.value > self.options.enemies_scaling_upper.value: + self.options.enemies_scaling_lower.value, self.options.enemies_scaling_upper.value = \ + self.options.enemies_scaling_upper.value, self.options.enemies_scaling_lower.value + if self.options.bosses_scaling_lower.value > self.options.bosses_scaling_upper.value: + self.options.bosses_scaling_lower.value, self.options.bosses_scaling_upper.value = \ + self.options.bosses_scaling_upper.value, self.options.bosses_scaling_lower.value @classmethod def stage_generate_early(cls, multiworld): @@ -96,20 +100,20 @@ def stage_generate_early(cls, multiworld): rooms_data = {} for world in multiworld.get_game_worlds("Final Fantasy Mystic Quest"): - if (world.multiworld.map_shuffle[world.player] or world.multiworld.crest_shuffle[world.player] or - world.multiworld.crest_shuffle[world.player]): - if world.multiworld.map_shuffle_seed[world.player].value.isdigit(): - multiworld.random.seed(int(world.multiworld.map_shuffle_seed[world.player].value)) - elif world.multiworld.map_shuffle_seed[world.player].value != "random": - multiworld.random.seed(int(hash(world.multiworld.map_shuffle_seed[world.player].value)) - + int(world.multiworld.seed)) + if (world.options.map_shuffle or world.options.crest_shuffle or world.options.shuffle_battlefield_rewards + or world.options.companions_locations): + if world.options.map_shuffle_seed.value.isdigit(): + multiworld.random.seed(int(world.options.map_shuffle_seed.value)) + elif world.options.map_shuffle_seed.value != "random": + multiworld.random.seed(int(hash(world.options.map_shuffle_seed.value)) + + int(world.multiworld.seed)) seed = hex(multiworld.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper() - map_shuffle = multiworld.map_shuffle[world.player].value - crest_shuffle = multiworld.crest_shuffle[world.player].current_key - battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key - companion_shuffle = multiworld.companions_locations[world.player].value - kaeli_mom = multiworld.kaelis_mom_fight_minotaur[world.player].current_key + map_shuffle = world.options.map_shuffle.value + crest_shuffle = world.options.crest_shuffle.current_key + battlefield_shuffle = world.options.shuffle_battlefield_rewards.current_key + companion_shuffle = world.options.companions_locations.value + kaeli_mom = world.options.kaelis_mom_fight_minotaur.current_key query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}&cs={companion_shuffle}&km={kaeli_mom}" @@ -177,14 +181,14 @@ def get_filler_item_name(self): def extend_hint_information(self, hint_data): hint_data[self.player] = {} - if self.multiworld.map_shuffle[self.player]: + if self.options.map_shuffle: single_location_regions = ["Subregion Volcano Battlefield", "Subregion Mac's Ship", "Subregion Doom Castle"] for subregion in ["Subregion Foresta", "Subregion Aquaria", "Subregion Frozen Fields", "Subregion Fireburg", "Subregion Volcano Battlefield", "Subregion Windia", "Subregion Mac's Ship", "Subregion Doom Castle"]: region = self.multiworld.get_region(subregion, self.player) for location in region.locations: - if location.address and self.multiworld.map_shuffle[self.player] != "dungeons": + if location.address and self.options.map_shuffle != "dungeons": hint_data[self.player][location.address] = (subregion.split("Subregion ")[-1] + (" Region" if subregion not in single_location_regions else "")) @@ -204,16 +208,14 @@ def extend_hint_information(self, hint_data): for location in exit_check.connected_region.locations: if location.address: hint = [] - if self.multiworld.map_shuffle[self.player] != "dungeons": + if self.options.map_shuffle != "dungeons": hint.append((subregion.split("Subregion ")[-1] + (" Region" if subregion not in single_location_regions else ""))) - if self.multiworld.map_shuffle[self.player] != "overworld" and subregion not in \ - ("Subregion Mac's Ship", "Subregion Doom Castle"): + if self.options.map_shuffle != "overworld": hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu", "Pazuzu's")) - hint = " - ".join(hint) + hint = " - ".join(hint).replace(" - Mac Ship", "") if location.address in hint_data[self.player]: hint_data[self.player][location.address] += f"/{hint}" else: hint_data[self.player][location.address] = hint - diff --git a/worlds/ffmq/data/entrances.yaml b/worlds/ffmq/data/entrances.yaml deleted file mode 100644 index 1dfef2655c37..000000000000 --- a/worlds/ffmq/data/entrances.yaml +++ /dev/null @@ -1,2450 +0,0 @@ -- name: Doom Castle - Sand Floor - To Sky Door - Sand Floor - id: 0 - area: 7 - coordinates: [24, 19] - teleporter: [0, 0] -- name: Doom Castle - Sand Floor - Main Entrance - Sand Floor - id: 1 - area: 7 - coordinates: [19, 43] - teleporter: [1, 6] -- name: Doom Castle - Aero Room - Aero Room Entrance - id: 2 - area: 7 - coordinates: [27, 39] - teleporter: [1, 0] -- name: Focus Tower B1 - Main Loop - South Entrance - id: 3 - area: 8 - coordinates: [43, 60] - teleporter: [2, 6] -- name: Focus Tower B1 - Main Loop - To Focus Tower 1F - Main Hall - id: 4 - area: 8 - coordinates: [37, 41] - teleporter: [4, 0] -- name: Focus Tower B1 - Aero Corridor - To Focus Tower 1F - Sun Coin Room - id: 5 - area: 8 - coordinates: [59, 35] - teleporter: [5, 0] -- name: Focus Tower B1 - Aero Corridor - To Sand Floor - Aero Chest - id: 6 - area: 8 - coordinates: [57, 59] - teleporter: [8, 0] -- name: Focus Tower B1 - Inner Loop - To Focus Tower 1F - Sky Door - id: 7 - area: 8 - coordinates: [51, 49] - teleporter: [6, 0] -- name: Focus Tower B1 - Inner Loop - To Doom Castle Sand Floor - id: 8 - area: 8 - coordinates: [51, 45] - teleporter: [7, 0] -- name: Focus Tower 1F - Focus Tower West Entrance - id: 9 - area: 9 - coordinates: [25, 29] - teleporter: [3, 6] -- name: Focus Tower 1F - To Focus Tower 2F - From SandCoin - id: 10 - area: 9 - coordinates: [16, 4] - teleporter: [10, 0] -- name: Focus Tower 1F - To Focus Tower B1 - Main Hall - id: 11 - area: 9 - coordinates: [4, 23] - teleporter: [11, 0] -- name: Focus Tower 1F - To Focus Tower B1 - To Aero Chest - id: 12 - area: 9 - coordinates: [26, 17] - teleporter: [12, 0] -- name: Focus Tower 1F - Sky Door - id: 13 - area: 9 - coordinates: [16, 24] - teleporter: [13, 0] -- name: Focus Tower 1F - To Focus Tower 2F - From RiverCoin - id: 14 - area: 9 - coordinates: [16, 10] - teleporter: [14, 0] -- name: Focus Tower 1F - To Focus Tower B1 - From Sky Door - id: 15 - area: 9 - coordinates: [16, 29] - teleporter: [15, 0] -- name: Focus Tower 2F - Sand Coin Passage - North Entrance - id: 16 - area: 10 - coordinates: [49, 30] - teleporter: [4, 6] -- name: Focus Tower 2F - Sand Coin Passage - To Focus Tower 1F - To SandCoin - id: 17 - area: 10 - coordinates: [47, 33] - teleporter: [17, 0] -- name: Focus Tower 2F - River Coin Passage - To Focus Tower 1F - To RiverCoin - id: 18 - area: 10 - coordinates: [47, 41] - teleporter: [18, 0] -- name: Focus Tower 2F - River Coin Passage - To Focus Tower 3F - Lower Floor - id: 19 - area: 10 - coordinates: [38, 40] - teleporter: [20, 0] -- name: Focus Tower 2F - Venus Chest Room - To Focus Tower 3F - Upper Floor - id: 20 - area: 10 - coordinates: [56, 40] - teleporter: [19, 0] -- name: Focus Tower 2F - Venus Chest Room - Pillar Script - id: 21 - area: 10 - coordinates: [48, 53] - teleporter: [13, 8] -- name: Focus Tower 3F - Lower Floor - To Fireburg Entrance - id: 22 - area: 11 - coordinates: [11, 39] - teleporter: [6, 6] -- name: Focus Tower 3F - Lower Floor - To Focus Tower 2F - Jump on Pillar - id: 23 - area: 11 - coordinates: [6, 47] - teleporter: [24, 0] -- name: Focus Tower 3F - Upper Floor - To Aquaria Entrance - id: 24 - area: 11 - coordinates: [21, 38] - teleporter: [5, 6] -- name: Focus Tower 3F - Upper Floor - To Focus Tower 2F - Venus Chest Room - id: 25 - area: 11 - coordinates: [24, 47] - teleporter: [23, 0] -- name: Level Forest - Boulder Script - id: 26 - area: 14 - coordinates: [52, 15] - teleporter: [0, 8] -- name: Level Forest - Rotten Tree Script - id: 27 - area: 14 - coordinates: [47, 6] - teleporter: [2, 8] -- name: Level Forest - Exit Level Forest 1 - id: 28 - area: 14 - coordinates: [46, 25] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 2 - id: 29 - area: 14 - coordinates: [46, 26] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 3 - id: 30 - area: 14 - coordinates: [47, 25] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 4 - id: 31 - area: 14 - coordinates: [47, 26] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 5 - id: 32 - area: 14 - coordinates: [60, 14] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 6 - id: 33 - area: 14 - coordinates: [61, 14] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 7 - id: 34 - area: 14 - coordinates: [46, 4] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 8 - id: 35 - area: 14 - coordinates: [46, 3] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 9 - id: 36 - area: 14 - coordinates: [47, 4] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest A - id: 37 - area: 14 - coordinates: [47, 3] - teleporter: [25, 0] -- name: Foresta - Exit Foresta 1 - id: 38 - area: 15 - coordinates: [10, 25] - teleporter: [31, 0] -- name: Foresta - Exit Foresta 2 - id: 39 - area: 15 - coordinates: [10, 26] - teleporter: [31, 0] -- name: Foresta - Exit Foresta 3 - id: 40 - area: 15 - coordinates: [11, 25] - teleporter: [31, 0] -- name: Foresta - Exit Foresta 4 - id: 41 - area: 15 - coordinates: [11, 26] - teleporter: [31, 0] -- name: Foresta - Old Man House - Front Door - id: 42 - area: 15 - coordinates: [25, 17] - teleporter: [32, 4] -- name: Foresta - Old Man House - Back Door - id: 43 - area: 15 - coordinates: [25, 14] - teleporter: [33, 0] -- name: Foresta - Kaeli's House - id: 44 - area: 15 - coordinates: [7, 21] - teleporter: [0, 5] -- name: Foresta - Rest House - id: 45 - area: 15 - coordinates: [23, 23] - teleporter: [1, 5] -- name: Kaeli's House - Kaeli's House Entrance - id: 46 - area: 16 - coordinates: [11, 20] - teleporter: [86, 3] -- name: Foresta Houses - Old Man's House - Old Man Front Exit - id: 47 - area: 17 - coordinates: [35, 44] - teleporter: [34, 0] -- name: Foresta Houses - Old Man's House - Old Man Back Exit - id: 48 - area: 17 - coordinates: [35, 27] - teleporter: [35, 0] -- name: Foresta - Old Man House - Barrel Tile Script # New, use the focus tower column's script - id: 483 - area: 17 - coordinates: [0x23, 0x1E] - teleporter: [0x0D, 8] -- name: Foresta Houses - Rest House - Bed Script - id: 49 - area: 17 - coordinates: [30, 6] - teleporter: [1, 8] -- name: Foresta Houses - Rest House - Rest House Exit - id: 50 - area: 17 - coordinates: [35, 20] - teleporter: [87, 3] -- name: Foresta Houses - Libra House - Libra House Script - id: 51 - area: 17 - coordinates: [8, 49] - teleporter: [67, 8] -- name: Foresta Houses - Gemini House - Gemini House Script - id: 52 - area: 17 - coordinates: [26, 55] - teleporter: [68, 8] -- name: Foresta Houses - Mobius House - Mobius House Script - id: 53 - area: 17 - coordinates: [14, 33] - teleporter: [69, 8] -- name: Sand Temple - Sand Temple Entrance - id: 54 - area: 18 - coordinates: [56, 27] - teleporter: [36, 0] -- name: Bone Dungeon 1F - Bone Dungeon Entrance - id: 55 - area: 19 - coordinates: [13, 60] - teleporter: [37, 0] -- name: Bone Dungeon 1F - To Bone Dungeon B1 - id: 56 - area: 19 - coordinates: [13, 39] - teleporter: [2, 2] -- name: Bone Dungeon B1 - Waterway - Exit Waterway - id: 57 - area: 20 - coordinates: [27, 39] - teleporter: [3, 2] -- name: Bone Dungeon B1 - Waterway - Tristam's Script - id: 58 - area: 20 - coordinates: [27, 45] - teleporter: [3, 8] -- name: Bone Dungeon B1 - Waterway - To Bone Dungeon 1F - id: 59 - area: 20 - coordinates: [54, 61] - teleporter: [88, 3] -- name: Bone Dungeon B1 - Checker Room - Exit Checker Room - id: 60 - area: 20 - coordinates: [23, 40] - teleporter: [4, 2] -- name: Bone Dungeon B1 - Checker Room - To Waterway - id: 61 - area: 20 - coordinates: [39, 49] - teleporter: [89, 3] -- name: Bone Dungeon B1 - Hidden Room - To B2 - Exploding Skull Room - id: 62 - area: 20 - coordinates: [5, 33] - teleporter: [91, 3] -- name: Bonne Dungeon B2 - Exploding Skull Room - To Hidden Passage - id: 63 - area: 21 - coordinates: [19, 13] - teleporter: [5, 2] -- name: Bonne Dungeon B2 - Exploding Skull Room - To Two Skulls Room - id: 64 - area: 21 - coordinates: [29, 15] - teleporter: [6, 2] -- name: Bonne Dungeon B2 - Exploding Skull Room - To Checker Room - id: 65 - area: 21 - coordinates: [8, 25] - teleporter: [90, 3] -- name: Bonne Dungeon B2 - Box Room - To B2 - Two Skulls Room - id: 66 - area: 21 - coordinates: [59, 12] - teleporter: [93, 3] -- name: Bonne Dungeon B2 - Quake Room - To B2 - Two Skulls Room - id: 67 - area: 21 - coordinates: [59, 28] - teleporter: [94, 3] -- name: Bonne Dungeon B2 - Two Skulls Room - To Box Room - id: 68 - area: 21 - coordinates: [53, 7] - teleporter: [7, 2] -- name: Bonne Dungeon B2 - Two Skulls Room - To Quake Room - id: 69 - area: 21 - coordinates: [41, 3] - teleporter: [8, 2] -- name: Bonne Dungeon B2 - Two Skulls Room - To Boss Room - id: 70 - area: 21 - coordinates: [47, 57] - teleporter: [9, 2] -- name: Bonne Dungeon B2 - Two Skulls Room - To B2 - Exploding Skull Room - id: 71 - area: 21 - coordinates: [54, 23] - teleporter: [92, 3] -- name: Bone Dungeon B2 - Boss Room - Flamerus Rex Script - id: 72 - area: 22 - coordinates: [29, 19] - teleporter: [4, 8] -- name: Bone Dungeon B2 - Boss Room - Tristam Leave Script - id: 73 - area: 22 - coordinates: [29, 23] - teleporter: [75, 8] -- name: Bone Dungeon B2 - Boss Room - To B2 - Two Skulls Room - id: 74 - area: 22 - coordinates: [30, 27] - teleporter: [95, 3] -- name: Libra Temple - Entrance - id: 75 - area: 23 - coordinates: [10, 15] - teleporter: [13, 6] -- name: Libra Temple - Libra Tile Script - id: 76 - area: 23 - coordinates: [9, 8] - teleporter: [59, 8] -- name: Aquaria Winter - Winter Entrance 1 - id: 77 - area: 24 - coordinates: [25, 25] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Entrance 2 - id: 78 - area: 24 - coordinates: [25, 26] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Entrance 3 - id: 79 - area: 24 - coordinates: [26, 25] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Entrance 4 - id: 80 - area: 24 - coordinates: [26, 26] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Phoebe's House Entrance Script #Modified to not be a script - id: 81 - area: 24 - coordinates: [8, 19] - teleporter: [10, 5] # original value [5, 8] -- name: Aquaria Winter - Winter Vendor House Entrance - id: 82 - area: 24 - coordinates: [8, 5] - teleporter: [44, 4] -- name: Aquaria Winter - Winter INN Entrance - id: 83 - area: 24 - coordinates: [26, 17] - teleporter: [11, 5] -- name: Aquaria Summer - Summer Entrance 1 - id: 84 - area: 25 - coordinates: [57, 25] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Entrance 2 - id: 85 - area: 25 - coordinates: [57, 26] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Entrance 3 - id: 86 - area: 25 - coordinates: [58, 25] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Entrance 4 - id: 87 - area: 25 - coordinates: [58, 26] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Phoebe's House Entrance - id: 88 - area: 25 - coordinates: [40, 19] - teleporter: [10, 5] -- name: Aquaria Summer - Spencer's Place Entrance Top - id: 89 - area: 25 - coordinates: [40, 16] - teleporter: [42, 0] -- name: Aquaria Summer - Spencer's Place Entrance Side - id: 90 - area: 25 - coordinates: [41, 18] - teleporter: [43, 0] -- name: Aquaria Summer - Summer Vendor House Entrance - id: 91 - area: 25 - coordinates: [40, 5] - teleporter: [44, 4] -- name: Aquaria Summer - Summer INN Entrance - id: 92 - area: 25 - coordinates: [58, 17] - teleporter: [11, 5] -- name: Phoebe's House - Entrance # Change to a script, same as vendor house - id: 93 - area: 26 - coordinates: [29, 14] - teleporter: [5, 8] # Original Value [11,3] -- name: Aquaria Vendor House - Vendor House Entrance's Script - id: 94 - area: 27 - coordinates: [7, 10] - teleporter: [40, 8] -- name: Aquaria Vendor House - Vendor House Stairs - id: 95 - area: 27 - coordinates: [1, 4] - teleporter: [47, 0] -- name: Aquaria Gemini Room - Gemini Script - id: 96 - area: 27 - coordinates: [2, 40] - teleporter: [72, 8] -- name: Aquaria Gemini Room - Gemini Room Stairs - id: 97 - area: 27 - coordinates: [4, 39] - teleporter: [48, 0] -- name: Aquaria INN - Aquaria INN entrance # Change to a script, same as vendor house - id: 98 - area: 27 - coordinates: [51, 46] - teleporter: [75, 8] # Original value [48,3] -- name: Wintry Cave 1F - Main Entrance - id: 99 - area: 28 - coordinates: [50, 58] - teleporter: [49, 0] -- name: Wintry Cave 1F - To 3F Top - id: 100 - area: 28 - coordinates: [40, 25] - teleporter: [14, 2] -- name: Wintry Cave 1F - To 2F - id: 101 - area: 28 - coordinates: [10, 43] - teleporter: [15, 2] -- name: Wintry Cave 1F - Phoebe's Script - id: 102 - area: 28 - coordinates: [44, 37] - teleporter: [6, 8] -- name: Wintry Cave 2F - To 3F Bottom - id: 103 - area: 29 - coordinates: [58, 5] - teleporter: [50, 0] -- name: Wintry Cave 2F - To 1F - id: 104 - area: 29 - coordinates: [38, 18] - teleporter: [97, 3] -- name: Wintry Cave 3F Top - Exit from 3F Top - id: 105 - area: 30 - coordinates: [24, 6] - teleporter: [96, 3] -- name: Wintry Cave 3F Bottom - Exit to 2F - id: 106 - area: 31 - coordinates: [4, 29] - teleporter: [51, 0] -- name: Life Temple - Entrance - id: 107 - area: 32 - coordinates: [9, 60] - teleporter: [14, 6] -- name: Life Temple - Libra Tile Script - id: 108 - area: 32 - coordinates: [3, 55] - teleporter: [60, 8] -- name: Life Temple - Mysterious Man Script - id: 109 - area: 32 - coordinates: [9, 44] - teleporter: [78, 8] -- name: Fall Basin - Back Exit Script - id: 110 - area: 33 - coordinates: [17, 5] - teleporter: [9, 0] # Remove script [42, 8] for overworld teleport (but not main exit) -- name: Fall Basin - Main Exit - id: 111 - area: 33 - coordinates: [15, 26] - teleporter: [53, 0] -- name: Fall Basin - Phoebe's Script - id: 112 - area: 33 - coordinates: [17, 6] - teleporter: [9, 8] -- name: Ice Pyramid B1 Taunt Room - To Climbing Wall Room - id: 113 - area: 34 - coordinates: [43, 6] - teleporter: [55, 0] -- name: Ice Pyramid 1F Maze - Main Entrance 1 - id: 114 - area: 35 - coordinates: [18, 36] - teleporter: [56, 0] -- name: Ice Pyramid 1F Maze - Main Entrance 2 - id: 115 - area: 35 - coordinates: [19, 36] - teleporter: [56, 0] -- name: Ice Pyramid 1F Maze - West Stairs To 2F South Tiled Room - id: 116 - area: 35 - coordinates: [3, 27] - teleporter: [57, 0] -- name: Ice Pyramid 1F Maze - West Center Stairs to 2F West Room - id: 117 - area: 35 - coordinates: [11, 15] - teleporter: [58, 0] -- name: Ice Pyramid 1F Maze - East Center Stairs to 2F Center Room - id: 118 - area: 35 - coordinates: [25, 16] - teleporter: [59, 0] -- name: Ice Pyramid 1F Maze - Upper Stairs to 2F Small North Room - id: 119 - area: 35 - coordinates: [31, 1] - teleporter: [60, 0] -- name: Ice Pyramid 1F Maze - East Stairs to 2F North Corridor - id: 120 - area: 35 - coordinates: [34, 9] - teleporter: [61, 0] -- name: Ice Pyramid 1F Maze - Statue's Script - id: 121 - area: 35 - coordinates: [21, 32] - teleporter: [77, 8] -- name: Ice Pyramid 2F South Tiled Room - To 1F - id: 122 - area: 36 - coordinates: [4, 26] - teleporter: [62, 0] -- name: Ice Pyramid 2F South Tiled Room - To 3F Two Boxes Room - id: 123 - area: 36 - coordinates: [22, 17] - teleporter: [67, 0] -- name: Ice Pyramid 2F West Room - To 1F - id: 124 - area: 36 - coordinates: [9, 10] - teleporter: [63, 0] -- name: Ice Pyramid 2F Center Room - To 1F - id: 125 - area: 36 - coordinates: [22, 14] - teleporter: [64, 0] -- name: Ice Pyramid 2F Small North Room - To 1F - id: 126 - area: 36 - coordinates: [26, 4] - teleporter: [65, 0] -- name: Ice Pyramid 2F North Corridor - To 1F - id: 127 - area: 36 - coordinates: [32, 8] - teleporter: [66, 0] -- name: Ice Pyramid 2F North Corridor - To 3F Main Loop - id: 128 - area: 36 - coordinates: [12, 7] - teleporter: [68, 0] -- name: Ice Pyramid 3F Two Boxes Room - To 2F South Tiled Room - id: 129 - area: 37 - coordinates: [24, 54] - teleporter: [69, 0] -- name: Ice Pyramid 3F Main Loop - To 2F Corridor - id: 130 - area: 37 - coordinates: [16, 45] - teleporter: [70, 0] -- name: Ice Pyramid 3F Main Loop - To 4F - id: 131 - area: 37 - coordinates: [19, 43] - teleporter: [71, 0] -- name: Ice Pyramid 4F Treasure Room - To 3F Main Loop - id: 132 - area: 38 - coordinates: [52, 5] - teleporter: [72, 0] -- name: Ice Pyramid 4F Treasure Room - To 5F Leap of Faith Room - id: 133 - area: 38 - coordinates: [62, 19] - teleporter: [73, 0] -- name: Ice Pyramid 5F Leap of Faith Room - To 4F Treasure Room - id: 134 - area: 39 - coordinates: [54, 63] - teleporter: [74, 0] -- name: Ice Pyramid 5F Leap of Faith Room - Bombed Ice Plate - id: 135 - area: 39 - coordinates: [47, 54] - teleporter: [77, 8] -- name: Ice Pyramid 5F Stairs to Ice Golem - To Ice Golem Room - id: 136 - area: 39 - coordinates: [39, 43] - teleporter: [75, 0] -- name: Ice Pyramid 5F Stairs to Ice Golem - To Climbing Wall Room - id: 137 - area: 39 - coordinates: [39, 60] - teleporter: [76, 0] -- name: Ice Pyramid - Duplicate Ice Golem Room # not used? - id: 138 - area: 40 - coordinates: [44, 43] - teleporter: [77, 0] -- name: Ice Pyramid Climbing Wall Room - To Taunt Room - id: 139 - area: 41 - coordinates: [4, 59] - teleporter: [78, 0] -- name: Ice Pyramid Climbing Wall Room - To 5F Stairs - id: 140 - area: 41 - coordinates: [4, 45] - teleporter: [79, 0] -- name: Ice Pyramid Ice Golem Room - To 5F Stairs - id: 141 - area: 42 - coordinates: [44, 43] - teleporter: [80, 0] -- name: Ice Pyramid Ice Golem Room - Ice Golem Script - id: 142 - area: 42 - coordinates: [53, 32] - teleporter: [10, 8] -- name: Spencer Waterfall - To Spencer Cave - id: 143 - area: 43 - coordinates: [48, 57] - teleporter: [81, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 1 - id: 144 - area: 43 - coordinates: [40, 5] - teleporter: [82, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 2 - id: 145 - area: 43 - coordinates: [40, 6] - teleporter: [82, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 3 - id: 146 - area: 43 - coordinates: [41, 5] - teleporter: [82, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 4 - id: 147 - area: 43 - coordinates: [41, 6] - teleporter: [82, 0] -- name: Spencer Waterfall - Right Exit to Aquaria 1 - id: 148 - area: 43 - coordinates: [46, 8] - teleporter: [83, 0] -- name: Spencer Waterfall - Right Exit to Aquaria 2 - id: 149 - area: 43 - coordinates: [47, 8] - teleporter: [83, 0] -- name: Spencer Cave Normal Main - To Waterfall - id: 150 - area: 44 - coordinates: [14, 39] - teleporter: [85, 0] -- name: Spencer Cave Normal From Overworld - Exit to Overworld - id: 151 - area: 44 - coordinates: [15, 57] - teleporter: [7, 6] -- name: Spencer Cave Unplug - Exit to Overworld - id: 152 - area: 45 - coordinates: [40, 29] - teleporter: [7, 6] -- name: Spencer Cave Unplug - Libra Teleporter Start Script - id: 153 - area: 45 - coordinates: [28, 21] - teleporter: [33, 8] -- name: Spencer Cave Unplug - Libra Teleporter End Script - id: 154 - area: 45 - coordinates: [46, 4] - teleporter: [34, 8] -- name: Spencer Cave Unplug - Mobius Teleporter Chest Script - id: 155 - area: 45 - coordinates: [21, 9] - teleporter: [35, 8] -- name: Spencer Cave Unplug - Mobius Teleporter Start Script - id: 156 - area: 45 - coordinates: [29, 28] - teleporter: [36, 8] -- name: Wintry Temple Outer Room - Main Entrance - id: 157 - area: 46 - coordinates: [8, 31] - teleporter: [15, 6] -- name: Wintry Temple Inner Room - Gemini Tile to Sealed temple - id: 158 - area: 46 - coordinates: [9, 24] - teleporter: [62, 8] -- name: Fireburg - To Overworld - id: 159 - area: 47 - coordinates: [4, 13] - teleporter: [9, 6] -- name: Fireburg - To Overworld - id: 160 - area: 47 - coordinates: [5, 13] - teleporter: [9, 6] -- name: Fireburg - To Overworld - id: 161 - area: 47 - coordinates: [28, 15] - teleporter: [9, 6] -- name: Fireburg - To Overworld - id: 162 - area: 47 - coordinates: [27, 15] - teleporter: [9, 6] -- name: Fireburg - Vendor House - id: 163 - area: 47 - coordinates: [10, 24] - teleporter: [91, 0] -- name: Fireburg - Reuben House - id: 164 - area: 47 - coordinates: [14, 6] - teleporter: [98, 8] # Script for reuben, original value [16, 2] -- name: Fireburg - Hotel - id: 165 - area: 47 - coordinates: [20, 8] - teleporter: [96, 8] # It's a script now for tristam, original value [17, 2] -- name: Fireburg - GrenadeMan House Script - id: 166 - area: 47 - coordinates: [12, 18] - teleporter: [11, 8] -- name: Reuben House - Main Entrance - id: 167 - area: 48 - coordinates: [33, 46] - teleporter: [98, 3] -- name: GrenadeMan House - Entrance Script - id: 168 - area: 49 - coordinates: [55, 60] - teleporter: [9, 8] -- name: GrenadeMan House - To Mobius Crest Room - id: 169 - area: 49 - coordinates: [57, 52] - teleporter: [93, 0] -- name: GrenadeMan Mobius Room - Stairs to House - id: 170 - area: 49 - coordinates: [39, 26] - teleporter: [94, 0] -- name: GrenadeMan Mobius Room - Mobius Teleporter Script - id: 171 - area: 49 - coordinates: [39, 23] - teleporter: [54, 8] -- name: Fireburg Vendor House - Entrance Script # No use to be a script - id: 172 - area: 49 - coordinates: [7, 10] - teleporter: [95, 0] # Original value [39, 8] -- name: Fireburg Vendor House - Stairs to Gemini Room - id: 173 - area: 49 - coordinates: [1, 4] - teleporter: [96, 0] -- name: Fireburg Gemini Room - Stairs to Vendor House - id: 174 - area: 49 - coordinates: [4, 39] - teleporter: [97, 0] -- name: Fireburg Gemini Room - Gemini Teleporter Script - id: 175 - area: 49 - coordinates: [2, 40] - teleporter: [45, 8] -- name: Fireburg Hotel Lobby - Stairs to beds - id: 176 - area: 49 - coordinates: [4, 50] - teleporter: [213, 0] -- name: Fireburg Hotel Lobby - Entrance - id: 177 - area: 49 - coordinates: [17, 56] - teleporter: [99, 3] -- name: Fireburg Hotel Beds - Stairs to Hotel Lobby - id: 178 - area: 49 - coordinates: [45, 59] - teleporter: [214, 0] -- name: Mine Exterior - Main Entrance - id: 179 - area: 50 - coordinates: [5, 28] - teleporter: [98, 0] -- name: Mine Exterior - To Cliff - id: 180 - area: 50 - coordinates: [58, 29] - teleporter: [99, 0] -- name: Mine Exterior - To Parallel Room - id: 181 - area: 50 - coordinates: [8, 7] - teleporter: [20, 2] -- name: Mine Exterior - To Crescent Room - id: 182 - area: 50 - coordinates: [26, 15] - teleporter: [21, 2] -- name: Mine Exterior - To Climbing Room - id: 183 - area: 50 - coordinates: [21, 35] - teleporter: [22, 2] -- name: Mine Exterior - Jinn Fight Script - id: 184 - area: 50 - coordinates: [58, 31] - teleporter: [74, 8] -- name: Mine Parallel Room - To Mine Exterior - id: 185 - area: 51 - coordinates: [7, 60] - teleporter: [100, 3] -- name: Mine Crescent Room - To Mine Exterior - id: 186 - area: 51 - coordinates: [22, 61] - teleporter: [101, 3] -- name: Mine Climbing Room - To Mine Exterior - id: 187 - area: 51 - coordinates: [56, 21] - teleporter: [102, 3] -- name: Mine Cliff - Entrance - id: 188 - area: 52 - coordinates: [9, 5] - teleporter: [100, 0] -- name: Mine Cliff - Reuben Grenade Script - id: 189 - area: 52 - coordinates: [15, 7] - teleporter: [12, 8] -- name: Sealed Temple - To Overworld - id: 190 - area: 53 - coordinates: [58, 43] - teleporter: [16, 6] -- name: Sealed Temple - Gemini Tile Script - id: 191 - area: 53 - coordinates: [56, 38] - teleporter: [63, 8] -- name: Volcano Base - Main Entrance 1 - id: 192 - area: 54 - coordinates: [23, 25] - teleporter: [103, 0] -- name: Volcano Base - Main Entrance 2 - id: 193 - area: 54 - coordinates: [23, 26] - teleporter: [103, 0] -- name: Volcano Base - Main Entrance 3 - id: 194 - area: 54 - coordinates: [24, 25] - teleporter: [103, 0] -- name: Volcano Base - Main Entrance 4 - id: 195 - area: 54 - coordinates: [24, 26] - teleporter: [103, 0] -- name: Volcano Base - Left Stairs Script - id: 196 - area: 54 - coordinates: [20, 5] - teleporter: [31, 8] -- name: Volcano Base - Right Stairs Script - id: 197 - area: 54 - coordinates: [32, 5] - teleporter: [30, 8] -- name: Volcano Top Right - Top Exit - id: 198 - area: 55 - coordinates: [44, 8] - teleporter: [9, 0] # Original value [103, 0] changed to volcano escape so floor shuffling doesn't pick it up -- name: Volcano Top Left - To Right-Left Path Script - id: 199 - area: 55 - coordinates: [40, 24] - teleporter: [26, 8] -- name: Volcano Top Right - To Left-Right Path Script - id: 200 - area: 55 - coordinates: [52, 24] - teleporter: [79, 8] # Original Value [26, 8] -- name: Volcano Right Path - To Volcano Base Script - id: 201 - area: 56 - coordinates: [48, 42] - teleporter: [15, 8] # Original Value [27, 8] -- name: Volcano Left Path - To Volcano Cross Left-Right - id: 202 - area: 56 - coordinates: [40, 31] - teleporter: [25, 2] -- name: Volcano Left Path - To Volcano Cross Right-Left - id: 203 - area: 56 - coordinates: [52, 29] - teleporter: [26, 2] -- name: Volcano Left Path - To Volcano Base Script - id: 204 - area: 56 - coordinates: [36, 42] - teleporter: [27, 8] -- name: Volcano Cross Left-Right - To Volcano Left Path - id: 205 - area: 56 - coordinates: [10, 42] - teleporter: [103, 3] -- name: Volcano Cross Left-Right - To Volcano Top Right Script - id: 206 - area: 56 - coordinates: [16, 24] - teleporter: [29, 8] -- name: Volcano Cross Right-Left - To Volcano Top Left Script - id: 207 - area: 56 - coordinates: [8, 22] - teleporter: [28, 8] -- name: Volcano Cross Right-Left - To Volcano Left Path - id: 208 - area: 56 - coordinates: [16, 42] - teleporter: [104, 3] -- name: Lava Dome Inner Ring Main Loop - Main Entrance 1 - id: 209 - area: 57 - coordinates: [32, 5] - teleporter: [104, 0] -- name: Lava Dome Inner Ring Main Loop - Main Entrance 2 - id: 210 - area: 57 - coordinates: [33, 5] - teleporter: [104, 0] -- name: Lava Dome Inner Ring Main Loop - To Three Steps Room - id: 211 - area: 57 - coordinates: [14, 5] - teleporter: [105, 0] -- name: Lava Dome Inner Ring Main Loop - To Life Chest Room Lower - id: 212 - area: 57 - coordinates: [40, 17] - teleporter: [106, 0] -- name: Lava Dome Inner Ring Main Loop - To Big Jump Room Left - id: 213 - area: 57 - coordinates: [8, 11] - teleporter: [108, 0] -- name: Lava Dome Inner Ring Main Loop - To Split Corridor Room - id: 214 - area: 57 - coordinates: [11, 19] - teleporter: [111, 0] -- name: Lava Dome Inner Ring Center Ledge - To Life Chest Room Higher - id: 215 - area: 57 - coordinates: [32, 11] - teleporter: [107, 0] -- name: Lava Dome Inner Ring Plate Ledge - To Plate Corridor - id: 216 - area: 57 - coordinates: [12, 23] - teleporter: [109, 0] -- name: Lava Dome Inner Ring Plate Ledge - Plate Script - id: 217 - area: 57 - coordinates: [5, 23] - teleporter: [47, 8] -- name: Lava Dome Inner Ring Upper Ledges - To Pointless Room - id: 218 - area: 57 - coordinates: [0, 9] - teleporter: [110, 0] -- name: Lava Dome Inner Ring Upper Ledges - To Lower Moon Helm Room - id: 219 - area: 57 - coordinates: [0, 15] - teleporter: [112, 0] -- name: Lava Dome Inner Ring Upper Ledges - To Up-Down Corridor - id: 220 - area: 57 - coordinates: [54, 5] - teleporter: [113, 0] -- name: Lava Dome Inner Ring Big Door Ledge - To Jumping Maze II - id: 221 - area: 57 - coordinates: [54, 21] - teleporter: [114, 0] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 1 - id: 222 - area: 57 - coordinates: [62, 20] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 2 - id: 223 - area: 57 - coordinates: [63, 20] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 3 - id: 224 - area: 57 - coordinates: [62, 21] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 4 - id: 225 - area: 57 - coordinates: [63, 21] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Tiny Bottom Ledge - To Four Boxes Corridor - id: 226 - area: 57 - coordinates: [50, 25] - teleporter: [115, 0] -- name: Lava Dome Jump Maze II - Lower Right Entrance - id: 227 - area: 58 - coordinates: [55, 28] - teleporter: [116, 0] -- name: Lava Dome Jump Maze II - Upper Entrance - id: 228 - area: 58 - coordinates: [35, 3] - teleporter: [119, 0] -- name: Lava Dome Jump Maze II - Lower Left Entrance - id: 229 - area: 58 - coordinates: [34, 27] - teleporter: [120, 0] -- name: Lava Dome Up-Down Corridor - Upper Entrance - id: 230 - area: 58 - coordinates: [29, 8] - teleporter: [117, 0] -- name: Lava Dome Up-Down Corridor - Lower Entrance - id: 231 - area: 58 - coordinates: [28, 25] - teleporter: [118, 0] -- name: Lava Dome Jump Maze I - South Entrance - id: 232 - area: 59 - coordinates: [20, 27] - teleporter: [121, 0] -- name: Lava Dome Jump Maze I - North Entrance - id: 233 - area: 59 - coordinates: [7, 3] - teleporter: [122, 0] -- name: Lava Dome Pointless Room - Entrance - id: 234 - area: 60 - coordinates: [2, 7] - teleporter: [123, 0] -- name: Lava Dome Pointless Room - Visit Quest Script 1 - id: 490 - area: 60 - coordinates: [4, 4] - teleporter: [99, 8] -- name: Lava Dome Pointless Room - Visit Quest Script 2 - id: 491 - area: 60 - coordinates: [4, 5] - teleporter: [99, 8] -- name: Lava Dome Lower Moon Helm Room - Left Entrance - id: 235 - area: 60 - coordinates: [2, 19] - teleporter: [124, 0] -- name: Lava Dome Lower Moon Helm Room - Right Entrance - id: 236 - area: 60 - coordinates: [11, 21] - teleporter: [125, 0] -- name: Lava Dome Moon Helm Room - Entrance - id: 237 - area: 60 - coordinates: [15, 23] - teleporter: [126, 0] -- name: Lava Dome Three Jumps Room - To Main Loop - id: 238 - area: 61 - coordinates: [58, 15] - teleporter: [127, 0] -- name: Lava Dome Life Chest Room - Lower South Entrance - id: 239 - area: 61 - coordinates: [38, 27] - teleporter: [128, 0] -- name: Lava Dome Life Chest Room - Upper South Entrance - id: 240 - area: 61 - coordinates: [28, 23] - teleporter: [129, 0] -- name: Lava Dome Big Jump Room - Left Entrance - id: 241 - area: 62 - coordinates: [42, 51] - teleporter: [133, 0] -- name: Lava Dome Big Jump Room - North Entrance - id: 242 - area: 62 - coordinates: [30, 29] - teleporter: [131, 0] -- name: Lava Dome Big Jump Room - Lower Right Stairs - id: 243 - area: 62 - coordinates: [61, 59] - teleporter: [132, 0] -- name: Lava Dome Split Corridor - Upper Stairs - id: 244 - area: 62 - coordinates: [30, 43] - teleporter: [130, 0] -- name: Lava Dome Split Corridor - Lower Stairs - id: 245 - area: 62 - coordinates: [36, 61] - teleporter: [134, 0] -- name: Lava Dome Plate Corridor - Right Entrance - id: 246 - area: 63 - coordinates: [19, 29] - teleporter: [135, 0] -- name: Lava Dome Plate Corridor - Left Entrance - id: 247 - area: 63 - coordinates: [60, 21] - teleporter: [137, 0] -- name: Lava Dome Four Boxes Stairs - Upper Entrance - id: 248 - area: 63 - coordinates: [22, 3] - teleporter: [136, 0] -- name: Lava Dome Four Boxes Stairs - Lower Entrance - id: 249 - area: 63 - coordinates: [22, 17] - teleporter: [16, 0] -- name: Lava Dome Hydra Room - South Entrance - id: 250 - area: 64 - coordinates: [14, 59] - teleporter: [105, 3] -- name: Lava Dome Hydra Room - North Exit - id: 251 - area: 64 - coordinates: [25, 31] - teleporter: [138, 0] -- name: Lava Dome Hydra Room - Hydra Script - id: 252 - area: 64 - coordinates: [14, 36] - teleporter: [14, 8] -- name: Lava Dome Escape Corridor - South Entrance - id: 253 - area: 65 - coordinates: [22, 17] - teleporter: [139, 0] -- name: Lava Dome Escape Corridor - North Entrance - id: 254 - area: 65 - coordinates: [22, 3] - teleporter: [9, 0] -- name: Rope Bridge - West Entrance 1 - id: 255 - area: 66 - coordinates: [3, 10] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 2 - id: 256 - area: 66 - coordinates: [3, 11] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 3 - id: 257 - area: 66 - coordinates: [3, 12] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 4 - id: 258 - area: 66 - coordinates: [3, 13] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 5 - id: 259 - area: 66 - coordinates: [4, 10] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 6 - id: 260 - area: 66 - coordinates: [4, 11] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 7 - id: 261 - area: 66 - coordinates: [4, 12] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 8 - id: 262 - area: 66 - coordinates: [4, 13] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 1 - id: 263 - area: 66 - coordinates: [59, 10] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 2 - id: 264 - area: 66 - coordinates: [59, 11] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 3 - id: 265 - area: 66 - coordinates: [59, 12] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 4 - id: 266 - area: 66 - coordinates: [59, 13] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 5 - id: 267 - area: 66 - coordinates: [60, 10] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 6 - id: 268 - area: 66 - coordinates: [60, 11] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 7 - id: 269 - area: 66 - coordinates: [60, 12] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 8 - id: 270 - area: 66 - coordinates: [60, 13] - teleporter: [140, 0] -- name: Rope Bridge - Reuben Fall Script - id: 271 - area: 66 - coordinates: [13, 12] - teleporter: [15, 8] -- name: Alive Forest - West Entrance 1 - id: 272 - area: 67 - coordinates: [8, 13] - teleporter: [142, 0] -- name: Alive Forest - West Entrance 2 - id: 273 - area: 67 - coordinates: [9, 13] - teleporter: [142, 0] -- name: Alive Forest - Giant Tree Entrance - id: 274 - area: 67 - coordinates: [42, 42] - teleporter: [143, 0] -- name: Alive Forest - Libra Teleporter Script - id: 275 - area: 67 - coordinates: [8, 52] - teleporter: [64, 8] -- name: Alive Forest - Gemini Teleporter Script - id: 276 - area: 67 - coordinates: [57, 49] - teleporter: [65, 8] -- name: Alive Forest - Mobius Teleporter Script - id: 277 - area: 67 - coordinates: [24, 10] - teleporter: [66, 8] -- name: Giant Tree 1F - Entrance Script 1 - id: 278 - area: 68 - coordinates: [18, 31] - teleporter: [56, 1] # The script is restored if no map shuffling [49, 8] -- name: Giant Tree 1F - Entrance Script 2 - id: 279 - area: 68 - coordinates: [19, 31] - teleporter: [56, 1] # Same [49, 8] -- name: Giant Tree 1F - North Entrance To 2F - id: 280 - area: 68 - coordinates: [16, 1] - teleporter: [144, 0] -- name: Giant Tree 2F Main Lobby - North Entrance to 1F - id: 281 - area: 69 - coordinates: [44, 33] - teleporter: [145, 0] -- name: Giant Tree 2F Main Lobby - Central Entrance to 3F - id: 282 - area: 69 - coordinates: [42, 47] - teleporter: [146, 0] -- name: Giant Tree 2F Main Lobby - West Entrance to Mushroom Room - id: 283 - area: 69 - coordinates: [58, 49] - teleporter: [149, 0] -- name: Giant Tree 2F West Ledge - To 3F Northwest Ledge - id: 284 - area: 69 - coordinates: [34, 37] - teleporter: [147, 0] -- name: Giant Tree 2F Fall From Vine Script - id: 482 - area: 69 - coordinates: [0x2E, 0x33] - teleporter: [76, 8] -- name: Giant Tree Meteor Chest Room - To 2F Mushroom Room - id: 285 - area: 69 - coordinates: [58, 44] - teleporter: [148, 0] -- name: Giant Tree 2F Mushroom Room - Entrance - id: 286 - area: 70 - coordinates: [55, 18] - teleporter: [150, 0] -- name: Giant Tree 2F Mushroom Room - North Face to Meteor - id: 287 - area: 70 - coordinates: [56, 7] - teleporter: [151, 0] -- name: Giant Tree 3F Central Room - Central Entrance to 2F - id: 288 - area: 71 - coordinates: [46, 53] - teleporter: [152, 0] -- name: Giant Tree 3F Central Room - East Entrance to Worm Room - id: 289 - area: 71 - coordinates: [58, 39] - teleporter: [153, 0] -- name: Giant Tree 3F Lower Corridor - Entrance from Worm Room - id: 290 - area: 71 - coordinates: [45, 39] - teleporter: [154, 0] -- name: Giant Tree 3F West Platform - Lower Entrance - id: 291 - area: 71 - coordinates: [33, 43] - teleporter: [155, 0] -- name: Giant Tree 3F West Platform - Top Entrance - id: 292 - area: 71 - coordinates: [52, 25] - teleporter: [156, 0] -- name: Giant Tree Worm Room - East Entrance - id: 293 - area: 72 - coordinates: [20, 58] - teleporter: [157, 0] -- name: Giant Tree Worm Room - West Entrance - id: 294 - area: 72 - coordinates: [6, 56] - teleporter: [158, 0] -- name: Giant Tree 4F Lower Floor - Entrance - id: 295 - area: 73 - coordinates: [20, 7] - teleporter: [159, 0] -- name: Giant Tree 4F Lower Floor - Lower West Mouth - id: 296 - area: 73 - coordinates: [8, 23] - teleporter: [160, 0] -- name: Giant Tree 4F Lower Floor - Lower Central Mouth - id: 297 - area: 73 - coordinates: [14, 25] - teleporter: [161, 0] -- name: Giant Tree 4F Lower Floor - Lower East Mouth - id: 298 - area: 73 - coordinates: [20, 25] - teleporter: [162, 0] -- name: Giant Tree 4F Upper Floor - Upper West Mouth - id: 299 - area: 73 - coordinates: [8, 19] - teleporter: [163, 0] -- name: Giant Tree 4F Upper Floor - Upper Central Mouth - id: 300 - area: 73 - coordinates: [12, 17] - teleporter: [164, 0] -- name: Giant Tree 4F Slime Room - Exit - id: 301 - area: 74 - coordinates: [47, 10] - teleporter: [165, 0] -- name: Giant Tree 4F Slime Room - West Entrance - id: 302 - area: 74 - coordinates: [45, 24] - teleporter: [166, 0] -- name: Giant Tree 4F Slime Room - Central Entrance - id: 303 - area: 74 - coordinates: [50, 24] - teleporter: [167, 0] -- name: Giant Tree 4F Slime Room - East Entrance - id: 304 - area: 74 - coordinates: [57, 28] - teleporter: [168, 0] -- name: Giant Tree 5F - Entrance - id: 305 - area: 75 - coordinates: [14, 51] - teleporter: [169, 0] -- name: Giant Tree 5F - Giant Tree Face # Unused - id: 306 - area: 75 - coordinates: [14, 37] - teleporter: [170, 0] -- name: Kaidge Temple - Entrance - id: 307 - area: 77 - coordinates: [44, 63] - teleporter: [18, 6] -- name: Kaidge Temple - Mobius Teleporter Script - id: 308 - area: 77 - coordinates: [35, 57] - teleporter: [71, 8] -- name: Windhole Temple - Entrance - id: 309 - area: 78 - coordinates: [10, 29] - teleporter: [173, 0] -- name: Mount Gale - Entrance 1 - id: 310 - area: 79 - coordinates: [1, 45] - teleporter: [174, 0] -- name: Mount Gale - Entrance 2 - id: 311 - area: 79 - coordinates: [2, 45] - teleporter: [174, 0] -- name: Mount Gale - Visit Quest - id: 494 - area: 79 - coordinates: [44, 7] - teleporter: [101, 8] -- name: Windia - Main Entrance 1 - id: 312 - area: 80 - coordinates: [12, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 2 - id: 313 - area: 80 - coordinates: [13, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 3 - id: 314 - area: 80 - coordinates: [14, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 4 - id: 315 - area: 80 - coordinates: [15, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 5 - id: 316 - area: 80 - coordinates: [12, 41] - teleporter: [10, 6] -- name: Windia - Main Entrance 6 - id: 317 - area: 80 - coordinates: [13, 41] - teleporter: [10, 6] -- name: Windia - Main Entrance 7 - id: 318 - area: 80 - coordinates: [14, 41] - teleporter: [10, 6] -- name: Windia - Main Entrance 8 - id: 319 - area: 80 - coordinates: [15, 41] - teleporter: [10, 6] -- name: Windia - Otto's House - id: 320 - area: 80 - coordinates: [21, 39] - teleporter: [30, 5] -- name: Windia - INN's Script # Change to teleporter / Change back to script! - id: 321 - area: 80 - coordinates: [18, 34] - teleporter: [97, 8] # Original value [79, 8] > [31, 2] -- name: Windia - Vendor House - id: 322 - area: 80 - coordinates: [8, 36] - teleporter: [32, 5] -- name: Windia - Kid House - id: 323 - area: 80 - coordinates: [7, 23] - teleporter: [176, 4] -- name: Windia - Old People House - id: 324 - area: 80 - coordinates: [19, 21] - teleporter: [177, 4] -- name: Windia - Rainbow Bridge Script - id: 325 - area: 80 - coordinates: [21, 9] - teleporter: [10, 6] # Change to entrance, usually a script [41, 8] -- name: Otto's House - Attic Stairs - id: 326 - area: 81 - coordinates: [2, 19] - teleporter: [33, 2] -- name: Otto's House - Entrance - id: 327 - area: 81 - coordinates: [9, 30] - teleporter: [106, 3] -- name: Otto's Attic - Stairs - id: 328 - area: 81 - coordinates: [26, 23] - teleporter: [107, 3] -- name: Windia Kid House - Entrance Script # Change to teleporter - id: 329 - area: 82 - coordinates: [7, 10] - teleporter: [178, 0] # Original value [38, 8] -- name: Windia Kid House - Basement Stairs - id: 330 - area: 82 - coordinates: [1, 4] - teleporter: [180, 0] -- name: Windia Old People House - Entrance - id: 331 - area: 82 - coordinates: [55, 12] - teleporter: [179, 0] -- name: Windia Old People House - Basement Stairs - id: 332 - area: 82 - coordinates: [60, 5] - teleporter: [181, 0] -- name: Windia Kid House Basement - Stairs - id: 333 - area: 82 - coordinates: [43, 8] - teleporter: [182, 0] -- name: Windia Kid House Basement - Mobius Teleporter - id: 334 - area: 82 - coordinates: [41, 9] - teleporter: [44, 8] -- name: Windia Old People House Basement - Stairs - id: 335 - area: 82 - coordinates: [39, 26] - teleporter: [183, 0] -- name: Windia Old People House Basement - Mobius Teleporter Script - id: 336 - area: 82 - coordinates: [39, 23] - teleporter: [43, 8] -- name: Windia Inn Lobby - Stairs to Beds - id: 337 - area: 82 - coordinates: [45, 24] - teleporter: [102, 8] # Changed to script, original value [215, 0] -- name: Windia Inn Lobby - Exit - id: 338 - area: 82 - coordinates: [53, 30] - teleporter: [135, 3] -- name: Windia Inn Beds - Stairs to Lobby - id: 339 - area: 82 - coordinates: [33, 59] - teleporter: [216, 0] -- name: Windia Vendor House - Entrance - id: 340 - area: 82 - coordinates: [29, 14] - teleporter: [108, 3] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 1 - id: 341 - area: 83 - coordinates: [47, 29] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 2 - id: 342 - area: 83 - coordinates: [47, 30] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 3 - id: 343 - area: 83 - coordinates: [48, 29] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 4 - id: 344 - area: 83 - coordinates: [48, 30] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - East Entrance - id: 345 - area: 83 - coordinates: [55, 12] - teleporter: [185, 0] -- name: Pazuzu Tower 1F Main Lobby - South Stairs - id: 346 - area: 83 - coordinates: [51, 25] - teleporter: [186, 0] -- name: Pazuzu Tower 1F Main Lobby - Pazuzu Script 1 - id: 347 - area: 83 - coordinates: [47, 8] - teleporter: [16, 8] -- name: Pazuzu Tower 1F Main Lobby - Pazuzu Script 2 - id: 348 - area: 83 - coordinates: [48, 8] - teleporter: [16, 8] -- name: Pazuzu Tower 1F Boxes Room - West Stairs - id: 349 - area: 83 - coordinates: [38, 17] - teleporter: [187, 0] -- name: Pazuzu 2F - West Upper Stairs - id: 350 - area: 84 - coordinates: [7, 11] - teleporter: [188, 0] -- name: Pazuzu 2F - South Stairs - id: 351 - area: 84 - coordinates: [20, 24] - teleporter: [189, 0] -- name: Pazuzu 2F - West Lower Stairs - id: 352 - area: 84 - coordinates: [6, 17] - teleporter: [190, 0] -- name: Pazuzu 2F - Central Stairs - id: 353 - area: 84 - coordinates: [15, 15] - teleporter: [191, 0] -- name: Pazuzu 2F - Pazuzu 1 - id: 354 - area: 84 - coordinates: [15, 8] - teleporter: [17, 8] -- name: Pazuzu 2F - Pazuzu 2 - id: 355 - area: 84 - coordinates: [16, 8] - teleporter: [17, 8] -- name: Pazuzu 3F Main Room - North Stairs - id: 356 - area: 85 - coordinates: [23, 11] - teleporter: [192, 0] -- name: Pazuzu 3F Main Room - West Stairs - id: 357 - area: 85 - coordinates: [7, 15] - teleporter: [193, 0] -- name: Pazuzu 3F Main Room - Pazuzu Script 1 - id: 358 - area: 85 - coordinates: [15, 8] - teleporter: [18, 8] -- name: Pazuzu 3F Main Room - Pazuzu Script 2 - id: 359 - area: 85 - coordinates: [16, 8] - teleporter: [18, 8] -- name: Pazuzu 3F Central Island - Central Stairs - id: 360 - area: 85 - coordinates: [15, 14] - teleporter: [194, 0] -- name: Pazuzu 3F Central Island - South Stairs - id: 361 - area: 85 - coordinates: [17, 25] - teleporter: [195, 0] -- name: Pazuzu 4F - Northwest Stairs - id: 362 - area: 86 - coordinates: [39, 12] - teleporter: [196, 0] -- name: Pazuzu 4F - Southwest Stairs - id: 363 - area: 86 - coordinates: [39, 19] - teleporter: [197, 0] -- name: Pazuzu 4F - South Stairs - id: 364 - area: 86 - coordinates: [47, 24] - teleporter: [198, 0] -- name: Pazuzu 4F - Northeast Stairs - id: 365 - area: 86 - coordinates: [54, 9] - teleporter: [199, 0] -- name: Pazuzu 4F - Pazuzu Script 1 - id: 366 - area: 86 - coordinates: [47, 8] - teleporter: [19, 8] -- name: Pazuzu 4F - Pazuzu Script 2 - id: 367 - area: 86 - coordinates: [48, 8] - teleporter: [19, 8] -- name: Pazuzu 5F Pazuzu Loop - West Stairs - id: 368 - area: 87 - coordinates: [9, 49] - teleporter: [200, 0] -- name: Pazuzu 5F Pazuzu Loop - South Stairs - id: 369 - area: 87 - coordinates: [16, 55] - teleporter: [201, 0] -- name: Pazuzu 5F Upper Loop - Northeast Stairs - id: 370 - area: 87 - coordinates: [22, 40] - teleporter: [202, 0] -- name: Pazuzu 5F Upper Loop - Northwest Stairs - id: 371 - area: 87 - coordinates: [9, 40] - teleporter: [203, 0] -- name: Pazuzu 5F Upper Loop - Pazuzu Script 1 - id: 372 - area: 87 - coordinates: [15, 40] - teleporter: [20, 8] -- name: Pazuzu 5F Upper Loop - Pazuzu Script 2 - id: 373 - area: 87 - coordinates: [16, 40] - teleporter: [20, 8] -- name: Pazuzu 6F - West Stairs - id: 374 - area: 88 - coordinates: [41, 47] - teleporter: [204, 0] -- name: Pazuzu 6F - Northwest Stairs - id: 375 - area: 88 - coordinates: [41, 40] - teleporter: [205, 0] -- name: Pazuzu 6F - Northeast Stairs - id: 376 - area: 88 - coordinates: [54, 40] - teleporter: [206, 0] -- name: Pazuzu 6F - South Stairs - id: 377 - area: 88 - coordinates: [52, 56] - teleporter: [207, 0] -- name: Pazuzu 6F - Pazuzu Script 1 - id: 378 - area: 88 - coordinates: [47, 40] - teleporter: [21, 8] -- name: Pazuzu 6F - Pazuzu Script 2 - id: 379 - area: 88 - coordinates: [48, 40] - teleporter: [21, 8] -- name: Pazuzu 7F Main Room - Southwest Stairs - id: 380 - area: 89 - coordinates: [15, 54] - teleporter: [26, 0] -- name: Pazuzu 7F Main Room - Northeast Stairs - id: 381 - area: 89 - coordinates: [21, 40] - teleporter: [27, 0] -- name: Pazuzu 7F Main Room - Southeast Stairs - id: 382 - area: 89 - coordinates: [21, 56] - teleporter: [28, 0] -- name: Pazuzu 7F Main Room - Pazuzu Script 1 - id: 383 - area: 89 - coordinates: [15, 44] - teleporter: [22, 8] -- name: Pazuzu 7F Main Room - Pazuzu Script 2 - id: 384 - area: 89 - coordinates: [16, 44] - teleporter: [22, 8] -- name: Pazuzu 7F Main Room - Crystal Script # Added for floor shuffle - id: 480 - area: 89 - coordinates: [15, 40] - teleporter: [38, 8] -- name: Pazuzu 1F to 3F - South Stairs - id: 385 - area: 90 - coordinates: [43, 60] - teleporter: [29, 0] -- name: Pazuzu 1F to 3F - North Stairs - id: 386 - area: 90 - coordinates: [43, 36] - teleporter: [30, 0] -- name: Pazuzu 3F to 5F - South Stairs - id: 387 - area: 91 - coordinates: [43, 60] - teleporter: [40, 0] -- name: Pazuzu 3F to 5F - North Stairs - id: 388 - area: 91 - coordinates: [43, 36] - teleporter: [41, 0] -- name: Pazuzu 5F to 7F - South Stairs - id: 389 - area: 92 - coordinates: [43, 60] - teleporter: [38, 0] -- name: Pazuzu 5F to 7F - North Stairs - id: 390 - area: 92 - coordinates: [43, 36] - teleporter: [39, 0] -- name: Pazuzu 2F to 4F - South Stairs - id: 391 - area: 93 - coordinates: [43, 60] - teleporter: [21, 0] -- name: Pazuzu 2F to 4F - North Stairs - id: 392 - area: 93 - coordinates: [43, 36] - teleporter: [22, 0] -- name: Pazuzu 4F to 6F - South Stairs - id: 393 - area: 94 - coordinates: [43, 60] - teleporter: [2, 0] -- name: Pazuzu 4F to 6F - North Stairs - id: 394 - area: 94 - coordinates: [43, 36] - teleporter: [3, 0] -- name: Light Temple - Entrance - id: 395 - area: 95 - coordinates: [28, 57] - teleporter: [19, 6] -- name: Light Temple - Mobius Teleporter Script - id: 396 - area: 95 - coordinates: [29, 37] - teleporter: [70, 8] -- name: Light Temple - Visit Quest Script 1 - id: 492 - area: 95 - coordinates: [34, 39] - teleporter: [100, 8] -- name: Light Temple - Visit Quest Script 2 - id: 493 - area: 95 - coordinates: [35, 39] - teleporter: [100, 8] -- name: Ship Dock - Mobius Teleporter Script - id: 397 - area: 96 - coordinates: [15, 18] - teleporter: [61, 8] -- name: Ship Dock - From Overworld - id: 398 - area: 96 - coordinates: [15, 11] - teleporter: [73, 0] -- name: Ship Dock - Entrance - id: 399 - area: 96 - coordinates: [15, 23] - teleporter: [17, 6] -- name: Mac Ship Deck - East Entrance Script - id: 400 - area: 97 - coordinates: [26, 40] - teleporter: [37, 8] -- name: Mac Ship Deck - Central Stairs Script - id: 401 - area: 97 - coordinates: [16, 47] - teleporter: [50, 8] -- name: Mac Ship Deck - West Stairs Script - id: 402 - area: 97 - coordinates: [8, 34] - teleporter: [51, 8] -- name: Mac Ship Deck - East Stairs Script - id: 403 - area: 97 - coordinates: [24, 36] - teleporter: [52, 8] -- name: Mac Ship Deck - North Stairs Script - id: 404 - area: 97 - coordinates: [12, 9] - teleporter: [53, 8] -- name: Mac Ship B1 Outer Ring - South Stairs - id: 405 - area: 98 - coordinates: [16, 45] - teleporter: [208, 0] -- name: Mac Ship B1 Outer Ring - West Stairs - id: 406 - area: 98 - coordinates: [8, 35] - teleporter: [175, 0] -- name: Mac Ship B1 Outer Ring - East Stairs - id: 407 - area: 98 - coordinates: [25, 37] - teleporter: [172, 0] -- name: Mac Ship B1 Outer Ring - Northwest Stairs - id: 408 - area: 98 - coordinates: [10, 23] - teleporter: [88, 0] -- name: Mac Ship B1 Square Room - North Stairs - id: 409 - area: 98 - coordinates: [14, 9] - teleporter: [141, 0] -- name: Mac Ship B1 Square Room - South Stairs - id: 410 - area: 98 - coordinates: [16, 12] - teleporter: [87, 0] -- name: Mac Ship B1 Mac Room - Stairs # Unused? - id: 411 - area: 98 - coordinates: [16, 51] - teleporter: [101, 0] -- name: Mac Ship B1 Central Corridor - South Stairs - id: 412 - area: 98 - coordinates: [16, 38] - teleporter: [102, 0] -- name: Mac Ship B1 Central Corridor - North Stairs - id: 413 - area: 98 - coordinates: [16, 26] - teleporter: [86, 0] -- name: Mac Ship B2 South Corridor - South Stairs - id: 414 - area: 99 - coordinates: [48, 51] - teleporter: [57, 1] -- name: Mac Ship B2 South Corridor - North Stairs Script - id: 415 - area: 99 - coordinates: [48, 38] - teleporter: [55, 8] -- name: Mac Ship B2 North Corridor - South Stairs Script - id: 416 - area: 99 - coordinates: [48, 27] - teleporter: [56, 8] -- name: Mac Ship B2 North Corridor - North Stairs Script - id: 417 - area: 99 - coordinates: [48, 12] - teleporter: [57, 8] -- name: Mac Ship B2 Outer Ring - Northwest Stairs Script - id: 418 - area: 99 - coordinates: [55, 11] - teleporter: [58, 8] -- name: Mac Ship B1 Outer Ring Cleared - South Stairs - id: 419 - area: 100 - coordinates: [16, 45] - teleporter: [208, 0] -- name: Mac Ship B1 Outer Ring Cleared - West Stairs - id: 420 - area: 100 - coordinates: [8, 35] - teleporter: [175, 0] -- name: Mac Ship B1 Outer Ring Cleared - East Stairs - id: 421 - area: 100 - coordinates: [25, 37] - teleporter: [172, 0] -- name: Mac Ship B1 Square Room Cleared - North Stairs - id: 422 - area: 100 - coordinates: [14, 9] - teleporter: [141, 0] -- name: Mac Ship B1 Square Room Cleared - South Stairs - id: 423 - area: 100 - coordinates: [16, 12] - teleporter: [87, 0] -- name: Mac Ship B1 Mac Room Cleared - Main Stairs - id: 424 - area: 100 - coordinates: [16, 51] - teleporter: [101, 0] -- name: Mac Ship B1 Central Corridor Cleared - South Stairs - id: 425 - area: 100 - coordinates: [16, 38] - teleporter: [102, 0] -- name: Mac Ship B1 Central Corridor Cleared - North Stairs - id: 426 - area: 100 - coordinates: [16, 26] - teleporter: [86, 0] -- name: Mac Ship B1 Central Corridor Cleared - Northwest Stairs - id: 427 - area: 100 - coordinates: [23, 10] - teleporter: [88, 0] -- name: Doom Castle Corridor of Destiny - South Entrance - id: 428 - area: 101 - coordinates: [59, 29] - teleporter: [84, 0] -- name: Doom Castle Corridor of Destiny - Ice Floor Entrance - id: 429 - area: 101 - coordinates: [59, 21] - teleporter: [35, 2] -- name: Doom Castle Corridor of Destiny - Lava Floor Entrance - id: 430 - area: 101 - coordinates: [59, 13] - teleporter: [209, 0] -- name: Doom Castle Corridor of Destiny - Sky Floor Entrance - id: 431 - area: 101 - coordinates: [59, 5] - teleporter: [211, 0] -- name: Doom Castle Corridor of Destiny - Hero Room Entrance - id: 432 - area: 101 - coordinates: [59, 61] - teleporter: [13, 2] -- name: Doom Castle Ice Floor - Entrance - id: 433 - area: 102 - coordinates: [23, 42] - teleporter: [109, 3] -- name: Doom Castle Lava Floor - Entrance - id: 434 - area: 103 - coordinates: [23, 40] - teleporter: [210, 0] -- name: Doom Castle Sky Floor - Entrance - id: 435 - area: 104 - coordinates: [24, 41] - teleporter: [212, 0] -- name: Doom Castle Hero Room - Dark King Entrance 1 - id: 436 - area: 106 - coordinates: [15, 5] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Dark King Entrance 2 - id: 437 - area: 106 - coordinates: [16, 5] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Dark King Entrance 3 - id: 438 - area: 106 - coordinates: [15, 4] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Dark King Entrance 4 - id: 439 - area: 106 - coordinates: [16, 4] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Hero Statue Script - id: 440 - area: 106 - coordinates: [15, 17] - teleporter: [24, 8] -- name: Doom Castle Hero Room - Entrance - id: 441 - area: 106 - coordinates: [15, 24] - teleporter: [110, 3] -- name: Doom Castle Dark King Room - Entrance - id: 442 - area: 107 - coordinates: [14, 26] - teleporter: [52, 0] -- name: Doom Castle Dark King Room - Dark King Script - id: 443 - area: 107 - coordinates: [14, 15] - teleporter: [25, 8] -- name: Doom Castle Dark King Room - Unknown - id: 444 - area: 107 - coordinates: [47, 54] - teleporter: [77, 0] -- name: Overworld - Level Forest - id: 445 - area: 0 - type: "Overworld" - teleporter: [0x2E, 8] -- name: Overworld - Foresta - id: 446 - area: 0 - type: "Overworld" - teleporter: [0x02, 1] -- name: Overworld - Sand Temple - id: 447 - area: 0 - type: "Overworld" - teleporter: [0x03, 1] -- name: Overworld - Bone Dungeon - id: 448 - area: 0 - type: "Overworld" - teleporter: [0x04, 1] -- name: Overworld - Focus Tower Foresta - id: 449 - area: 0 - type: "Overworld" - teleporter: [0x05, 1] -- name: Overworld - Focus Tower Aquaria - id: 450 - area: 0 - type: "Overworld" - teleporter: [0x13, 1] -- name: Overworld - Libra Temple - id: 451 - area: 0 - type: "Overworld" - teleporter: [0x07, 1] -- name: Overworld - Aquaria - id: 452 - area: 0 - type: "Overworld" - teleporter: [0x08, 8] -- name: Overworld - Wintry Cave - id: 453 - area: 0 - type: "Overworld" - teleporter: [0x0A, 1] -- name: Overworld - Life Temple - id: 454 - area: 0 - type: "Overworld" - teleporter: [0x0B, 1] -- name: Overworld - Falls Basin - id: 455 - area: 0 - type: "Overworld" - teleporter: [0x0C, 1] -- name: Overworld - Ice Pyramid - id: 456 - area: 0 - type: "Overworld" - teleporter: [0x0D, 1] # Will be switched to a script -- name: Overworld - Spencer's Place - id: 457 - area: 0 - type: "Overworld" - teleporter: [0x30, 8] -- name: Overworld - Wintry Temple - id: 458 - area: 0 - type: "Overworld" - teleporter: [0x10, 1] -- name: Overworld - Focus Tower Frozen Strip - id: 459 - area: 0 - type: "Overworld" - teleporter: [0x11, 1] -- name: Overworld - Focus Tower Fireburg - id: 460 - area: 0 - type: "Overworld" - teleporter: [0x12, 1] -- name: Overworld - Fireburg - id: 461 - area: 0 - type: "Overworld" - teleporter: [0x14, 1] -- name: Overworld - Mine - id: 462 - area: 0 - type: "Overworld" - teleporter: [0x15, 1] -- name: Overworld - Sealed Temple - id: 463 - area: 0 - type: "Overworld" - teleporter: [0x16, 1] -- name: Overworld - Volcano - id: 464 - area: 0 - type: "Overworld" - teleporter: [0x17, 1] -- name: Overworld - Lava Dome - id: 465 - area: 0 - type: "Overworld" - teleporter: [0x18, 1] -- name: Overworld - Focus Tower Windia - id: 466 - area: 0 - type: "Overworld" - teleporter: [0x06, 1] -- name: Overworld - Rope Bridge - id: 467 - area: 0 - type: "Overworld" - teleporter: [0x19, 1] -- name: Overworld - Alive Forest - id: 468 - area: 0 - type: "Overworld" - teleporter: [0x1A, 1] -- name: Overworld - Giant Tree - id: 469 - area: 0 - type: "Overworld" - teleporter: [0x1B, 1] -- name: Overworld - Kaidge Temple - id: 470 - area: 0 - type: "Overworld" - teleporter: [0x1C, 1] -- name: Overworld - Windia - id: 471 - area: 0 - type: "Overworld" - teleporter: [0x1D, 1] -- name: Overworld - Windhole Temple - id: 472 - area: 0 - type: "Overworld" - teleporter: [0x1E, 1] -- name: Overworld - Mount Gale - id: 473 - area: 0 - type: "Overworld" - teleporter: [0x1F, 1] -- name: Overworld - Pazuzu Tower - id: 474 - area: 0 - type: "Overworld" - teleporter: [0x20, 1] -- name: Overworld - Ship Dock - id: 475 - area: 0 - type: "Overworld" - teleporter: [0x3E, 1] -- name: Overworld - Doom Castle - id: 476 - area: 0 - type: "Overworld" - teleporter: [0x21, 1] -- name: Overworld - Light Temple - id: 477 - area: 0 - type: "Overworld" - teleporter: [0x22, 1] -- name: Overworld - Mac Ship - id: 478 - area: 0 - type: "Overworld" - teleporter: [0x24, 1] -- name: Overworld - Mac Ship Doom - id: 479 - area: 0 - type: "Overworld" - teleporter: [0x24, 1] -- name: Dummy House - Bed Script - id: 480 - area: 17 - coordinates: [0x28, 0x38] - teleporter: [1, 8] -- name: Dummy House - Entrance - id: 481 - area: 17 - coordinates: [0x29, 0x3B] - teleporter: [0, 10] #None diff --git a/worlds/ffmq/data/rooms.py b/worlds/ffmq/data/rooms.py new file mode 100644 index 000000000000..38634f107679 --- /dev/null +++ b/worlds/ffmq/data/rooms.py @@ -0,0 +1,2 @@ +rooms = [{'name': 'Overworld', 'id': 0, 'type': 'Overworld', 'game_objects': [], 'links': [{'target_room': 220, 'access': []}]}, {'name': 'Subregion Foresta', 'id': 220, 'type': 'Subregion', 'region': 'Foresta', 'game_objects': [{'name': 'Foresta South Battlefield', 'object_id': 1, 'location': 'ForestaSouthBattlefield', 'location_slot': 'ForestaSouthBattlefield', 'type': 'BattlefieldXp', 'access': []}, {'name': 'Foresta West Battlefield', 'object_id': 2, 'location': 'ForestaWestBattlefield', 'location_slot': 'ForestaWestBattlefield', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Foresta East Battlefield', 'object_id': 3, 'location': 'ForestaEastBattlefield', 'location_slot': 'ForestaEastBattlefield', 'type': 'BattlefieldGp', 'access': []}], 'links': [{'target_room': 15, 'location': 'LevelForest', 'location_slot': 'LevelForest', 'entrance': 445, 'teleporter': [46, 8], 'access': []}, {'target_room': 16, 'location': 'Foresta', 'location_slot': 'Foresta', 'entrance': 446, 'teleporter': [2, 1], 'access': []}, {'target_room': 24, 'location': 'SandTemple', 'location_slot': 'SandTemple', 'entrance': 447, 'teleporter': [3, 1], 'access': []}, {'target_room': 25, 'location': 'BoneDungeon', 'location_slot': 'BoneDungeon', 'entrance': 448, 'teleporter': [4, 1], 'access': []}, {'target_room': 3, 'location': 'FocusTowerForesta', 'location_slot': 'FocusTowerForesta', 'entrance': 449, 'teleporter': [5, 1], 'access': []}, {'target_room': 221, 'access': ['SandCoin']}, {'target_room': 224, 'access': ['RiverCoin']}, {'target_room': 226, 'access': ['SunCoin']}]}, {'name': 'Subregion Aquaria', 'id': 221, 'type': 'Subregion', 'region': 'Aquaria', 'game_objects': [{'name': 'South of Libra Temple Battlefield', 'object_id': 4, 'location': 'AquariaBattlefield01', 'location_slot': 'AquariaBattlefield01', 'type': 'BattlefieldXp', 'access': []}, {'name': 'East of Libra Temple Battlefield', 'object_id': 5, 'location': 'AquariaBattlefield02', 'location_slot': 'AquariaBattlefield02', 'type': 'BattlefieldGp', 'access': []}, {'name': 'South of Aquaria Battlefield', 'object_id': 6, 'location': 'AquariaBattlefield03', 'location_slot': 'AquariaBattlefield03', 'type': 'BattlefieldItem', 'access': []}, {'name': 'South of Wintry Cave Battlefield', 'object_id': 7, 'location': 'WintryBattlefield01', 'location_slot': 'WintryBattlefield01', 'type': 'BattlefieldXp', 'access': []}, {'name': 'West of Wintry Cave Battlefield', 'object_id': 8, 'location': 'WintryBattlefield02', 'location_slot': 'WintryBattlefield02', 'type': 'BattlefieldGp', 'access': []}, {'name': 'Ice Pyramid Battlefield', 'object_id': 9, 'location': 'PyramidBattlefield01', 'location_slot': 'PyramidBattlefield01', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 10, 'location': 'FocusTowerAquaria', 'location_slot': 'FocusTowerAquaria', 'entrance': 450, 'teleporter': [19, 1], 'access': []}, {'target_room': 39, 'location': 'LibraTemple', 'location_slot': 'LibraTemple', 'entrance': 451, 'teleporter': [7, 1], 'access': []}, {'target_room': 40, 'location': 'Aquaria', 'location_slot': 'Aquaria', 'entrance': 452, 'teleporter': [8, 8], 'access': []}, {'target_room': 45, 'location': 'WintryCave', 'location_slot': 'WintryCave', 'entrance': 453, 'teleporter': [10, 1], 'access': []}, {'target_room': 52, 'location': 'FallsBasin', 'location_slot': 'FallsBasin', 'entrance': 455, 'teleporter': [12, 1], 'access': []}, {'target_room': 54, 'location': 'IcePyramid', 'location_slot': 'IcePyramid', 'entrance': 456, 'teleporter': [13, 1], 'access': []}, {'target_room': 220, 'access': ['SandCoin']}, {'target_room': 224, 'access': ['SandCoin', 'RiverCoin']}, {'target_room': 226, 'access': ['SandCoin', 'SunCoin']}, {'target_room': 223, 'access': ['SummerAquaria']}]}, {'name': 'Subregion Life Temple', 'id': 222, 'type': 'Subregion', 'region': 'LifeTemple', 'game_objects': [], 'links': [{'target_room': 51, 'location': 'LifeTemple', 'location_slot': 'LifeTemple', 'entrance': 454, 'teleporter': [11, 1], 'access': []}]}, {'name': 'Subregion Frozen Fields', 'id': 223, 'type': 'Subregion', 'region': 'AquariaFrozenField', 'game_objects': [{'name': 'North of Libra Temple Battlefield', 'object_id': 10, 'location': 'LibraBattlefield01', 'location_slot': 'LibraBattlefield01', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Aquaria Frozen Field Battlefield', 'object_id': 11, 'location': 'LibraBattlefield02', 'location_slot': 'LibraBattlefield02', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 74, 'location': 'WintryTemple', 'location_slot': 'WintryTemple', 'entrance': 458, 'teleporter': [16, 1], 'access': []}, {'target_room': 14, 'location': 'FocusTowerFrozen', 'location_slot': 'FocusTowerFrozen', 'entrance': 459, 'teleporter': [17, 1], 'access': []}, {'target_room': 221, 'access': []}, {'target_room': 225, 'access': ['SummerAquaria', 'DualheadHydra']}]}, {'name': 'Subregion Fireburg', 'id': 224, 'type': 'Subregion', 'region': 'Fireburg', 'game_objects': [{'name': 'Path to Fireburg Southern Battlefield', 'object_id': 12, 'location': 'FireburgBattlefield01', 'location_slot': 'FireburgBattlefield01', 'type': 'BattlefieldGp', 'access': []}, {'name': 'Path to Fireburg Central Battlefield', 'object_id': 13, 'location': 'FireburgBattlefield02', 'location_slot': 'FireburgBattlefield02', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Path to Fireburg Northern Battlefield', 'object_id': 14, 'location': 'FireburgBattlefield03', 'location_slot': 'FireburgBattlefield03', 'type': 'BattlefieldXp', 'access': []}, {'name': 'Sealed Temple Battlefield', 'object_id': 15, 'location': 'MineBattlefield01', 'location_slot': 'MineBattlefield01', 'type': 'BattlefieldGp', 'access': []}, {'name': 'Mine Battlefield', 'object_id': 16, 'location': 'MineBattlefield02', 'location_slot': 'MineBattlefield02', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Boulder Battlefield', 'object_id': 17, 'location': 'MineBattlefield03', 'location_slot': 'MineBattlefield03', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 13, 'location': 'FocusTowerFireburg', 'location_slot': 'FocusTowerFireburg', 'entrance': 460, 'teleporter': [18, 1], 'access': []}, {'target_room': 76, 'location': 'Fireburg', 'location_slot': 'Fireburg', 'entrance': 461, 'teleporter': [20, 1], 'access': []}, {'target_room': 84, 'location': 'Mine', 'location_slot': 'Mine', 'entrance': 462, 'teleporter': [21, 1], 'access': []}, {'target_room': 92, 'location': 'SealedTemple', 'location_slot': 'SealedTemple', 'entrance': 463, 'teleporter': [22, 1], 'access': []}, {'target_room': 93, 'location': 'Volcano', 'location_slot': 'Volcano', 'entrance': 464, 'teleporter': [23, 1], 'access': []}, {'target_room': 100, 'location': 'LavaDome', 'location_slot': 'LavaDome', 'entrance': 465, 'teleporter': [24, 1], 'access': []}, {'target_room': 220, 'access': ['RiverCoin']}, {'target_room': 221, 'access': ['SandCoin', 'RiverCoin']}, {'target_room': 226, 'access': ['RiverCoin', 'SunCoin']}, {'target_room': 225, 'access': ['DualheadHydra']}]}, {'name': 'Subregion Volcano Battlefield', 'id': 225, 'type': 'Subregion', 'region': 'VolcanoBattlefield', 'game_objects': [{'name': 'Volcano Battlefield', 'object_id': 18, 'location': 'VolcanoBattlefield01', 'location_slot': 'VolcanoBattlefield01', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 224, 'access': ['DualheadHydra']}, {'target_room': 223, 'access': ['SummerAquaria']}]}, {'name': 'Subregion Windia', 'id': 226, 'type': 'Subregion', 'region': 'Windia', 'game_objects': [{'name': 'Kaidge Temple Battlefield', 'object_id': 19, 'location': 'WindiaBattlefield01', 'location_slot': 'WindiaBattlefield01', 'type': 'BattlefieldXp', 'access': ['SandCoin', 'RiverCoin']}, {'name': 'South of Windia Battlefield', 'object_id': 20, 'location': 'WindiaBattlefield02', 'location_slot': 'WindiaBattlefield02', 'type': 'BattlefieldXp', 'access': ['SandCoin', 'RiverCoin']}], 'links': [{'target_room': 9, 'location': 'FocusTowerWindia', 'location_slot': 'FocusTowerWindia', 'entrance': 466, 'teleporter': [6, 1], 'access': []}, {'target_room': 123, 'location': 'RopeBridge', 'location_slot': 'RopeBridge', 'entrance': 467, 'teleporter': [25, 1], 'access': []}, {'target_room': 124, 'location': 'AliveForest', 'location_slot': 'AliveForest', 'entrance': 468, 'teleporter': [26, 1], 'access': []}, {'target_room': 125, 'location': 'GiantTree', 'location_slot': 'GiantTree', 'entrance': 469, 'teleporter': [27, 1], 'access': ['Barred']}, {'target_room': 152, 'location': 'KaidgeTemple', 'location_slot': 'KaidgeTemple', 'entrance': 470, 'teleporter': [28, 1], 'access': []}, {'target_room': 156, 'location': 'Windia', 'location_slot': 'Windia', 'entrance': 471, 'teleporter': [29, 1], 'access': []}, {'target_room': 154, 'location': 'WindholeTemple', 'location_slot': 'WindholeTemple', 'entrance': 472, 'teleporter': [30, 1], 'access': []}, {'target_room': 155, 'location': 'MountGale', 'location_slot': 'MountGale', 'entrance': 473, 'teleporter': [31, 1], 'access': []}, {'target_room': 166, 'location': 'PazuzusTower', 'location_slot': 'PazuzusTower', 'entrance': 474, 'teleporter': [32, 1], 'access': []}, {'target_room': 220, 'access': ['SunCoin']}, {'target_room': 221, 'access': ['SandCoin', 'SunCoin']}, {'target_room': 224, 'access': ['RiverCoin', 'SunCoin']}, {'target_room': 227, 'access': ['RainbowBridge']}]}, {'name': "Subregion Spencer's Cave", 'id': 227, 'type': 'Subregion', 'region': 'SpencerCave', 'game_objects': [], 'links': [{'target_room': 73, 'location': 'SpencersPlace', 'location_slot': 'SpencersPlace', 'entrance': 457, 'teleporter': [48, 8], 'access': []}, {'target_room': 226, 'access': ['RainbowBridge']}]}, {'name': 'Subregion Ship Dock', 'id': 228, 'type': 'Subregion', 'region': 'ShipDock', 'game_objects': [], 'links': [{'target_room': 186, 'location': 'ShipDock', 'location_slot': 'ShipDock', 'entrance': 475, 'teleporter': [62, 1], 'access': []}, {'target_room': 229, 'access': ['ShipLiberated', 'ShipDockAccess']}]}, {'name': "Subregion Mac's Ship", 'id': 229, 'type': 'Subregion', 'region': 'MacShip', 'game_objects': [], 'links': [{'target_room': 187, 'location': 'MacsShip', 'location_slot': 'MacsShip', 'entrance': 478, 'teleporter': [36, 1], 'access': []}, {'target_room': 228, 'access': ['ShipLiberated', 'ShipDockAccess']}, {'target_room': 231, 'access': ['ShipLoaned', 'ShipDockAccess', 'ShipSteeringWheel']}]}, {'name': 'Subregion Light Temple', 'id': 230, 'type': 'Subregion', 'region': 'LightTemple', 'game_objects': [], 'links': [{'target_room': 185, 'location': 'LightTemple', 'location_slot': 'LightTemple', 'entrance': 477, 'teleporter': [35, 1], 'access': []}]}, {'name': 'Subregion Doom Castle', 'id': 231, 'type': 'Subregion', 'region': 'DoomCastle', 'game_objects': [], 'links': [{'target_room': 1, 'location': 'DoomCastle', 'location_slot': 'DoomCastle', 'entrance': 476, 'teleporter': [33, 1], 'access': []}, {'target_room': 187, 'location': 'MacsShipDoom', 'location_slot': 'MacsShipDoom', 'entrance': 479, 'teleporter': [36, 1], 'access': ['Barred']}, {'target_room': 229, 'access': ['ShipLoaned', 'ShipDockAccess', 'ShipSteeringWheel']}]}, {'name': 'Doom Castle - Sand Floor', 'id': 1, 'game_objects': [{'name': 'Doom Castle B2 - Southeast Chest', 'object_id': 1, 'type': 'Chest', 'access': ['Bomb']}, {'name': 'Doom Castle B2 - Bone Ledge Box', 'object_id': 30, 'type': 'Box', 'access': []}, {'name': 'Doom Castle B2 - Hook Platform Box', 'object_id': 31, 'type': 'Box', 'access': ['DragonClaw']}], 'links': [{'target_room': 231, 'entrance': 1, 'teleporter': [1, 6], 'access': []}, {'target_room': 5, 'entrance': 0, 'teleporter': [0, 0], 'access': ['DragonClaw', 'MegaGrenade']}]}, {'name': 'Doom Castle - Aero Room', 'id': 2, 'game_objects': [{'name': 'Doom Castle B2 - Sun Door Chest', 'object_id': 0, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 4, 'entrance': 2, 'teleporter': [1, 0], 'access': []}]}, {'name': 'Focus Tower B1 - Main Loop', 'id': 3, 'game_objects': [], 'links': [{'target_room': 220, 'entrance': 3, 'teleporter': [2, 6], 'access': []}, {'target_room': 6, 'entrance': 4, 'teleporter': [4, 0], 'access': []}]}, {'name': 'Focus Tower B1 - Aero Corridor', 'id': 4, 'game_objects': [], 'links': [{'target_room': 9, 'entrance': 5, 'teleporter': [5, 0], 'access': []}, {'target_room': 2, 'entrance': 6, 'teleporter': [8, 0], 'access': []}]}, {'name': 'Focus Tower B1 - Inner Loop', 'id': 5, 'game_objects': [], 'links': [{'target_room': 1, 'entrance': 8, 'teleporter': [7, 0], 'access': []}, {'target_room': 201, 'entrance': 7, 'teleporter': [6, 0], 'access': []}]}, {'name': 'Focus Tower 1F Main Lobby', 'id': 6, 'game_objects': [{'name': 'Focus Tower 1F - Main Lobby Box', 'object_id': 33, 'type': 'Box', 'access': []}], 'links': [{'target_room': 3, 'entrance': 11, 'teleporter': [11, 0], 'access': []}, {'target_room': 7, 'access': ['SandCoin']}, {'target_room': 8, 'access': ['RiverCoin']}, {'target_room': 9, 'access': ['SunCoin']}]}, {'name': 'Focus Tower 1F SandCoin Room', 'id': 7, 'game_objects': [], 'links': [{'target_room': 6, 'access': ['SandCoin']}, {'target_room': 10, 'entrance': 10, 'teleporter': [10, 0], 'access': []}]}, {'name': 'Focus Tower 1F RiverCoin Room', 'id': 8, 'game_objects': [], 'links': [{'target_room': 6, 'access': ['RiverCoin']}, {'target_room': 11, 'entrance': 14, 'teleporter': [14, 0], 'access': []}]}, {'name': 'Focus Tower 1F SunCoin Room', 'id': 9, 'game_objects': [], 'links': [{'target_room': 6, 'access': ['SunCoin']}, {'target_room': 4, 'entrance': 12, 'teleporter': [12, 0], 'access': []}, {'target_room': 226, 'entrance': 9, 'teleporter': [3, 6], 'access': []}]}, {'name': 'Focus Tower 1F SkyCoin Room', 'id': 201, 'game_objects': [], 'links': [{'target_room': 195, 'entrance': 13, 'teleporter': [13, 0], 'access': ['SkyCoin', 'FlamerusRex', 'IceGolem', 'DualheadHydra', 'Pazuzu']}, {'target_room': 5, 'entrance': 15, 'teleporter': [15, 0], 'access': []}]}, {'name': 'Focus Tower 2F - Sand Coin Passage', 'id': 10, 'game_objects': [{'name': 'Focus Tower 2F - Sand Door Chest', 'object_id': 3, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 221, 'entrance': 16, 'teleporter': [4, 6], 'access': []}, {'target_room': 7, 'entrance': 17, 'teleporter': [17, 0], 'access': []}]}, {'name': 'Focus Tower 2F - River Coin Passage', 'id': 11, 'game_objects': [], 'links': [{'target_room': 8, 'entrance': 18, 'teleporter': [18, 0], 'access': []}, {'target_room': 13, 'entrance': 19, 'teleporter': [20, 0], 'access': []}]}, {'name': 'Focus Tower 2F - Venus Chest Room', 'id': 12, 'game_objects': [{'name': 'Focus Tower 2F - Back Door Chest', 'object_id': 2, 'type': 'Chest', 'access': []}, {'name': 'Focus Tower 2F - Venus Chest', 'object_id': 9, 'type': 'NPC', 'access': ['Bomb', 'VenusKey']}], 'links': [{'target_room': 14, 'entrance': 20, 'teleporter': [19, 0], 'access': []}]}, {'name': 'Focus Tower 3F - Lower Floor', 'id': 13, 'game_objects': [{'name': 'Focus Tower 3F - River Door Box', 'object_id': 34, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 22, 'teleporter': [6, 6], 'access': []}, {'target_room': 11, 'entrance': 23, 'teleporter': [24, 0], 'access': []}]}, {'name': 'Focus Tower 3F - Upper Floor', 'id': 14, 'game_objects': [], 'links': [{'target_room': 223, 'entrance': 24, 'teleporter': [5, 6], 'access': []}, {'target_room': 12, 'entrance': 25, 'teleporter': [23, 0], 'access': []}]}, {'name': 'Level Forest', 'id': 15, 'game_objects': [{'name': 'Level Forest - Northwest Box', 'object_id': 40, 'type': 'Box', 'access': ['Axe']}, {'name': 'Level Forest - Northeast Box', 'object_id': 41, 'type': 'Box', 'access': ['Axe']}, {'name': 'Level Forest - Middle Box', 'object_id': 42, 'type': 'Box', 'access': []}, {'name': 'Level Forest - Southwest Box', 'object_id': 43, 'type': 'Box', 'access': ['Axe']}, {'name': 'Level Forest - Southeast Box', 'object_id': 44, 'type': 'Box', 'access': ['Axe']}, {'name': 'Minotaur', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Minotaur'], 'access': ['Kaeli1']}, {'name': 'Level Forest - Old Man', 'object_id': 0, 'type': 'NPC', 'access': []}, {'name': 'Level Forest - Kaeli', 'object_id': 1, 'type': 'NPC', 'access': ['Kaeli1', 'Minotaur']}], 'links': [{'target_room': 220, 'entrance': 28, 'teleporter': [25, 0], 'access': []}]}, {'name': 'Foresta', 'id': 16, 'game_objects': [{'name': 'Foresta - Outside Box', 'object_id': 45, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 220, 'entrance': 38, 'teleporter': [31, 0], 'access': []}, {'target_room': 17, 'entrance': 44, 'teleporter': [0, 5], 'access': []}, {'target_room': 18, 'entrance': 42, 'teleporter': [32, 4], 'access': []}, {'target_room': 19, 'entrance': 43, 'teleporter': [33, 0], 'access': []}, {'target_room': 20, 'entrance': 45, 'teleporter': [1, 5], 'access': []}]}, {'name': "Kaeli's House", 'id': 17, 'game_objects': [{'name': "Foresta - Kaeli's House Box", 'object_id': 46, 'type': 'Box', 'access': []}, {'name': 'Kaeli Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Kaeli1'], 'access': ['TreeWither']}, {'name': 'Kaeli 2', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Kaeli2'], 'access': ['Kaeli1', 'Minotaur', 'Elixir']}], 'links': [{'target_room': 16, 'entrance': 46, 'teleporter': [86, 3], 'access': []}]}, {'name': "Foresta Houses - Old Man's House Main", 'id': 18, 'game_objects': [], 'links': [{'target_room': 19, 'access': ['BarrelPushed']}, {'target_room': 16, 'entrance': 47, 'teleporter': [34, 0], 'access': []}]}, {'name': "Foresta Houses - Old Man's House Back", 'id': 19, 'game_objects': [{'name': 'Foresta - Old Man House Chest', 'object_id': 5, 'type': 'Chest', 'access': []}, {'name': 'Old Man Barrel', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['BarrelPushed'], 'access': []}], 'links': [{'target_room': 18, 'access': ['BarrelPushed']}, {'target_room': 16, 'entrance': 48, 'teleporter': [35, 0], 'access': []}]}, {'name': 'Foresta Houses - Rest House', 'id': 20, 'game_objects': [{'name': 'Foresta - Rest House Box', 'object_id': 47, 'type': 'Box', 'access': []}], 'links': [{'target_room': 16, 'entrance': 50, 'teleporter': [87, 3], 'access': []}]}, {'name': 'Libra Treehouse', 'id': 21, 'game_objects': [{'name': 'Alive Forest - Libra Treehouse Box', 'object_id': 50, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 51, 'teleporter': [67, 8], 'access': ['LibraCrest']}]}, {'name': 'Gemini Treehouse', 'id': 22, 'game_objects': [{'name': 'Alive Forest - Gemini Treehouse Box', 'object_id': 51, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 52, 'teleporter': [68, 8], 'access': ['GeminiCrest']}]}, {'name': 'Mobius Treehouse', 'id': 23, 'game_objects': [{'name': 'Alive Forest - Mobius Treehouse West Box', 'object_id': 48, 'type': 'Box', 'access': []}, {'name': 'Alive Forest - Mobius Treehouse East Box', 'object_id': 49, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 53, 'teleporter': [69, 8], 'access': ['MobiusCrest']}]}, {'name': 'Sand Temple', 'id': 24, 'game_objects': [{'name': 'Tristam Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Tristam'], 'access': []}], 'links': [{'target_room': 220, 'entrance': 54, 'teleporter': [36, 0], 'access': []}]}, {'name': 'Bone Dungeon 1F', 'id': 25, 'game_objects': [{'name': 'Bone Dungeon 1F - Entrance Room West Box', 'object_id': 53, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon 1F - Entrance Room Middle Box', 'object_id': 54, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon 1F - Entrance Room East Box', 'object_id': 55, 'type': 'Box', 'access': []}], 'links': [{'target_room': 220, 'entrance': 55, 'teleporter': [37, 0], 'access': []}, {'target_room': 26, 'entrance': 56, 'teleporter': [2, 2], 'access': []}]}, {'name': 'Bone Dungeon B1 - Waterway', 'id': 26, 'game_objects': [{'name': 'Bone Dungeon B1 - Skull Chest', 'object_id': 6, 'type': 'Chest', 'access': ['Bomb']}, {'name': 'Bone Dungeon B1 - Tristam', 'object_id': 2, 'type': 'NPC', 'access': ['Tristam']}, {'name': 'Tristam Bone Dungeon Item Given', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['TristamBoneItemGiven'], 'access': ['Tristam']}], 'links': [{'target_room': 25, 'entrance': 59, 'teleporter': [88, 3], 'access': []}, {'target_room': 28, 'entrance': 57, 'teleporter': [3, 2], 'access': ['Bomb']}]}, {'name': 'Bone Dungeon B1 - Checker Room', 'id': 28, 'game_objects': [{'name': 'Bone Dungeon B1 - Checker Room Box', 'object_id': 56, 'type': 'Box', 'access': ['Bomb']}], 'links': [{'target_room': 26, 'entrance': 61, 'teleporter': [89, 3], 'access': []}, {'target_room': 30, 'entrance': 60, 'teleporter': [4, 2], 'access': []}]}, {'name': 'Bone Dungeon B1 - Hidden Room', 'id': 29, 'game_objects': [{'name': 'Bone Dungeon B1 - Ribcage Waterway Box', 'object_id': 57, 'type': 'Box', 'access': []}], 'links': [{'target_room': 31, 'entrance': 62, 'teleporter': [91, 3], 'access': []}]}, {'name': 'Bone Dungeon B2 - Exploding Skull Room - First Room', 'id': 30, 'game_objects': [{'name': 'Bone Dungeon B2 - Spines Room Alcove Box', 'object_id': 59, 'type': 'Box', 'access': []}, {'name': 'Long Spine', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['LongSpineBombed'], 'access': ['Bomb']}], 'links': [{'target_room': 28, 'entrance': 65, 'teleporter': [90, 3], 'access': []}, {'target_room': 31, 'access': ['LongSpineBombed']}]}, {'name': 'Bone Dungeon B2 - Exploding Skull Room - Second Room', 'id': 31, 'game_objects': [{'name': 'Bone Dungeon B2 - Spines Room Looped Hallway Box', 'object_id': 58, 'type': 'Box', 'access': []}, {'name': 'Short Spine', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShortSpineBombed'], 'access': ['Bomb']}], 'links': [{'target_room': 29, 'entrance': 63, 'teleporter': [5, 2], 'access': ['LongSpineBombed']}, {'target_room': 32, 'access': ['ShortSpineBombed']}, {'target_room': 30, 'access': ['LongSpineBombed']}]}, {'name': 'Bone Dungeon B2 - Exploding Skull Room - Third Room', 'id': 32, 'game_objects': [], 'links': [{'target_room': 35, 'entrance': 64, 'teleporter': [6, 2], 'access': []}, {'target_room': 31, 'access': ['ShortSpineBombed']}]}, {'name': 'Bone Dungeon B2 - Box Room', 'id': 33, 'game_objects': [{'name': 'Bone Dungeon B2 - Lone Room Box', 'object_id': 61, 'type': 'Box', 'access': []}], 'links': [{'target_room': 36, 'entrance': 66, 'teleporter': [93, 3], 'access': []}]}, {'name': 'Bone Dungeon B2 - Quake Room', 'id': 34, 'game_objects': [{'name': 'Bone Dungeon B2 - Penultimate Room Chest', 'object_id': 7, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 37, 'entrance': 67, 'teleporter': [94, 3], 'access': []}]}, {'name': 'Bone Dungeon B2 - Two Skulls Room - First Room', 'id': 35, 'game_objects': [{'name': 'Bone Dungeon B2 - Two Skulls Room Box', 'object_id': 60, 'type': 'Box', 'access': []}, {'name': 'Skull 1', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Skull1Bombed'], 'access': ['Bomb']}], 'links': [{'target_room': 32, 'entrance': 71, 'teleporter': [92, 3], 'access': []}, {'target_room': 36, 'access': ['Skull1Bombed']}]}, {'name': 'Bone Dungeon B2 - Two Skulls Room - Second Room', 'id': 36, 'game_objects': [{'name': 'Skull 2', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Skull2Bombed'], 'access': ['Bomb']}], 'links': [{'target_room': 33, 'entrance': 68, 'teleporter': [7, 2], 'access': []}, {'target_room': 37, 'access': ['Skull2Bombed']}, {'target_room': 35, 'access': ['Skull1Bombed']}]}, {'name': 'Bone Dungeon B2 - Two Skulls Room - Third Room', 'id': 37, 'game_objects': [], 'links': [{'target_room': 34, 'entrance': 69, 'teleporter': [8, 2], 'access': []}, {'target_room': 38, 'entrance': 70, 'teleporter': [9, 2], 'access': ['Bomb']}, {'target_room': 36, 'access': ['Skull2Bombed']}]}, {'name': 'Bone Dungeon B2 - Boss Room', 'id': 38, 'game_objects': [{'name': 'Bone Dungeon B2 - North Box', 'object_id': 62, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon B2 - South Box', 'object_id': 63, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon B2 - Flamerus Rex Chest', 'object_id': 8, 'type': 'Chest', 'access': []}, {'name': "Bone Dungeon B2 - Tristam's Treasure Chest", 'object_id': 4, 'type': 'Chest', 'access': []}, {'name': 'Flamerus Rex', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['FlamerusRex'], 'access': []}], 'links': [{'target_room': 37, 'entrance': 74, 'teleporter': [95, 3], 'access': []}]}, {'name': 'Libra Temple', 'id': 39, 'game_objects': [{'name': 'Libra Temple - Box', 'object_id': 64, 'type': 'Box', 'access': []}, {'name': 'Phoebe Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Phoebe1'], 'access': []}], 'links': [{'target_room': 221, 'entrance': 75, 'teleporter': [13, 6], 'access': []}, {'target_room': 51, 'entrance': 76, 'teleporter': [59, 8], 'access': ['LibraCrest']}]}, {'name': 'Aquaria', 'id': 40, 'game_objects': [{'name': 'Summer Aquaria', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['SummerAquaria'], 'access': ['WakeWater']}], 'links': [{'target_room': 221, 'entrance': 77, 'teleporter': [8, 6], 'access': []}, {'target_room': 41, 'entrance': 81, 'teleporter': [10, 5], 'access': []}, {'target_room': 42, 'entrance': 82, 'teleporter': [44, 4], 'access': []}, {'target_room': 44, 'entrance': 83, 'teleporter': [11, 5], 'access': []}, {'target_room': 71, 'entrance': 89, 'teleporter': [42, 0], 'access': ['SummerAquaria']}, {'target_room': 71, 'entrance': 90, 'teleporter': [43, 0], 'access': ['SummerAquaria']}]}, {'name': "Phoebe's House", 'id': 41, 'game_objects': [{'name': "Aquaria - Phoebe's House Chest", 'object_id': 65, 'type': 'Box', 'access': []}], 'links': [{'target_room': 40, 'entrance': 93, 'teleporter': [5, 8], 'access': []}]}, {'name': 'Aquaria Vendor House', 'id': 42, 'game_objects': [{'name': 'Aquaria - Vendor', 'object_id': 4, 'type': 'NPC', 'access': []}, {'name': 'Aquaria - Vendor House Box', 'object_id': 66, 'type': 'Box', 'access': []}], 'links': [{'target_room': 40, 'entrance': 94, 'teleporter': [40, 8], 'access': []}, {'target_room': 43, 'entrance': 95, 'teleporter': [47, 0], 'access': []}]}, {'name': 'Aquaria Gemini Room', 'id': 43, 'game_objects': [], 'links': [{'target_room': 42, 'entrance': 97, 'teleporter': [48, 0], 'access': []}, {'target_room': 81, 'entrance': 96, 'teleporter': [72, 8], 'access': ['GeminiCrest']}]}, {'name': 'Aquaria INN', 'id': 44, 'game_objects': [], 'links': [{'target_room': 40, 'entrance': 98, 'teleporter': [75, 8], 'access': []}]}, {'name': 'Wintry Cave 1F - East Ledge', 'id': 45, 'game_objects': [{'name': 'Wintry Cave 1F - North Box', 'object_id': 67, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 1F - Entrance Box', 'object_id': 70, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 1F - Slippery Cliff Box', 'object_id': 68, 'type': 'Box', 'access': ['Claw']}, {'name': 'Wintry Cave 1F - Phoebe', 'object_id': 5, 'type': 'NPC', 'access': ['Phoebe1']}], 'links': [{'target_room': 221, 'entrance': 99, 'teleporter': [49, 0], 'access': []}, {'target_room': 49, 'entrance': 100, 'teleporter': [14, 2], 'access': ['Bomb']}, {'target_room': 46, 'access': ['Claw']}]}, {'name': 'Wintry Cave 1F - Central Space', 'id': 46, 'game_objects': [{'name': 'Wintry Cave 1F - Scenic Overlook Box', 'object_id': 69, 'type': 'Box', 'access': ['Claw']}], 'links': [{'target_room': 45, 'access': ['Claw']}, {'target_room': 47, 'access': ['Claw']}]}, {'name': 'Wintry Cave 1F - West Ledge', 'id': 47, 'game_objects': [], 'links': [{'target_room': 48, 'entrance': 101, 'teleporter': [15, 2], 'access': ['Bomb']}, {'target_room': 46, 'access': ['Claw']}]}, {'name': 'Wintry Cave 2F', 'id': 48, 'game_objects': [{'name': 'Wintry Cave 2F - West Left Box', 'object_id': 71, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 2F - West Right Box', 'object_id': 72, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 2F - East Left Box', 'object_id': 73, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 2F - East Right Box', 'object_id': 74, 'type': 'Box', 'access': []}], 'links': [{'target_room': 47, 'entrance': 104, 'teleporter': [97, 3], 'access': []}, {'target_room': 50, 'entrance': 103, 'teleporter': [50, 0], 'access': []}]}, {'name': 'Wintry Cave 3F Top', 'id': 49, 'game_objects': [{'name': 'Wintry Cave 3F - West Box', 'object_id': 75, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 3F - East Box', 'object_id': 76, 'type': 'Box', 'access': []}], 'links': [{'target_room': 45, 'entrance': 105, 'teleporter': [96, 3], 'access': []}]}, {'name': 'Wintry Cave 3F Bottom', 'id': 50, 'game_objects': [{'name': 'Wintry Cave 3F - Squidite Chest', 'object_id': 9, 'type': 'Chest', 'access': ['Phanquid']}, {'name': 'Phanquid', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Phanquid'], 'access': []}, {'name': 'Wintry Cave 3F - Before Boss Box', 'object_id': 77, 'type': 'Box', 'access': []}], 'links': [{'target_room': 48, 'entrance': 106, 'teleporter': [51, 0], 'access': []}]}, {'name': 'Life Temple', 'id': 51, 'game_objects': [{'name': 'Life Temple - Box', 'object_id': 78, 'type': 'Box', 'access': []}, {'name': 'Life Temple - Mysterious Man', 'object_id': 6, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 222, 'entrance': 107, 'teleporter': [14, 6], 'access': []}, {'target_room': 39, 'entrance': 108, 'teleporter': [60, 8], 'access': ['LibraCrest']}]}, {'name': 'Fall Basin', 'id': 52, 'game_objects': [{'name': 'Falls Basin - Snow Crab Chest', 'object_id': 10, 'type': 'Chest', 'access': ['FreezerCrab']}, {'name': 'Freezer Crab', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['FreezerCrab'], 'access': []}, {'name': 'Falls Basin - Box', 'object_id': 79, 'type': 'Box', 'access': []}], 'links': [{'target_room': 221, 'entrance': 111, 'teleporter': [53, 0], 'access': []}]}, {'name': 'Ice Pyramid B1 Taunt Room', 'id': 53, 'game_objects': [{'name': 'Ice Pyramid B1 - Chest', 'object_id': 11, 'type': 'Chest', 'access': []}, {'name': 'Ice Pyramid B1 - West Box', 'object_id': 80, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid B1 - North Box', 'object_id': 81, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid B1 - East Box', 'object_id': 82, 'type': 'Box', 'access': []}], 'links': [{'target_room': 68, 'entrance': 113, 'teleporter': [55, 0], 'access': []}]}, {'name': 'Ice Pyramid 1F Maze Lobby', 'id': 54, 'game_objects': [{'name': 'Ice Pyramid 1F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid1FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 221, 'entrance': 114, 'teleporter': [56, 0], 'access': []}, {'target_room': 55, 'access': ['IcePyramid1FStatue']}]}, {'name': 'Ice Pyramid 1F Maze', 'id': 55, 'game_objects': [{'name': 'Ice Pyramid 1F - East Alcove Chest', 'object_id': 13, 'type': 'Chest', 'access': []}, {'name': 'Ice Pyramid 1F - Sandwiched Alcove Box', 'object_id': 83, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 1F - Southwest Left Box', 'object_id': 84, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 1F - Southwest Right Box', 'object_id': 85, 'type': 'Box', 'access': []}], 'links': [{'target_room': 56, 'entrance': 116, 'teleporter': [57, 0], 'access': []}, {'target_room': 57, 'entrance': 117, 'teleporter': [58, 0], 'access': []}, {'target_room': 58, 'entrance': 118, 'teleporter': [59, 0], 'access': []}, {'target_room': 59, 'entrance': 119, 'teleporter': [60, 0], 'access': []}, {'target_room': 60, 'entrance': 120, 'teleporter': [61, 0], 'access': []}, {'target_room': 54, 'access': ['IcePyramid1FStatue']}]}, {'name': 'Ice Pyramid 2F South Tiled Room', 'id': 56, 'game_objects': [{'name': 'Ice Pyramid 2F - South Side Glass Door Box', 'object_id': 87, 'type': 'Box', 'access': ['Sword']}, {'name': 'Ice Pyramid 2F - South Side East Box', 'object_id': 91, 'type': 'Box', 'access': []}], 'links': [{'target_room': 55, 'entrance': 122, 'teleporter': [62, 0], 'access': []}, {'target_room': 61, 'entrance': 123, 'teleporter': [67, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F West Room', 'id': 57, 'game_objects': [{'name': 'Ice Pyramid 2F - Northwest Room Box', 'object_id': 90, 'type': 'Box', 'access': []}], 'links': [{'target_room': 55, 'entrance': 124, 'teleporter': [63, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F Center Room', 'id': 58, 'game_objects': [{'name': 'Ice Pyramid 2F - Center Room Box', 'object_id': 86, 'type': 'Box', 'access': []}], 'links': [{'target_room': 55, 'entrance': 125, 'teleporter': [64, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F Small North Room', 'id': 59, 'game_objects': [{'name': 'Ice Pyramid 2F - North Room Glass Door Box', 'object_id': 88, 'type': 'Box', 'access': ['Sword']}], 'links': [{'target_room': 55, 'entrance': 126, 'teleporter': [65, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F North Corridor', 'id': 60, 'game_objects': [{'name': 'Ice Pyramid 2F - North Corridor Glass Door Box', 'object_id': 89, 'type': 'Box', 'access': ['Sword']}], 'links': [{'target_room': 55, 'entrance': 127, 'teleporter': [66, 0], 'access': []}, {'target_room': 62, 'entrance': 128, 'teleporter': [68, 0], 'access': []}]}, {'name': 'Ice Pyramid 3F Two Boxes Room', 'id': 61, 'game_objects': [{'name': 'Ice Pyramid 3F - Staircase Dead End Left Box', 'object_id': 94, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - Staircase Dead End Right Box', 'object_id': 95, 'type': 'Box', 'access': []}], 'links': [{'target_room': 56, 'entrance': 129, 'teleporter': [69, 0], 'access': []}]}, {'name': 'Ice Pyramid 3F Main Loop', 'id': 62, 'game_objects': [{'name': 'Ice Pyramid 3F - Inner Room North Box', 'object_id': 92, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - Inner Room South Box', 'object_id': 93, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - East Alcove Box', 'object_id': 96, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - Leapfrog Box', 'object_id': 97, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid3FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 60, 'entrance': 130, 'teleporter': [70, 0], 'access': []}, {'target_room': 63, 'access': ['IcePyramid3FStatue']}]}, {'name': 'Ice Pyramid 3F Blocked Room', 'id': 63, 'game_objects': [], 'links': [{'target_room': 64, 'entrance': 131, 'teleporter': [71, 0], 'access': []}, {'target_room': 62, 'access': ['IcePyramid3FStatue']}]}, {'name': 'Ice Pyramid 4F Main Loop', 'id': 64, 'game_objects': [], 'links': [{'target_room': 66, 'entrance': 133, 'teleporter': [73, 0], 'access': []}, {'target_room': 63, 'entrance': 132, 'teleporter': [72, 0], 'access': []}, {'target_room': 65, 'access': ['IcePyramid4FStatue']}]}, {'name': 'Ice Pyramid 4F Treasure Room', 'id': 65, 'game_objects': [{'name': 'Ice Pyramid 4F - Chest', 'object_id': 12, 'type': 'Chest', 'access': []}, {'name': 'Ice Pyramid 4F - Northwest Box', 'object_id': 98, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - West Left Box', 'object_id': 99, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - West Right Box', 'object_id': 100, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - South Left Box', 'object_id': 101, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - South Right Box', 'object_id': 102, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - East Left Box', 'object_id': 103, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - East Right Box', 'object_id': 104, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid4FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 64, 'access': ['IcePyramid4FStatue']}]}, {'name': 'Ice Pyramid 5F Leap of Faith Room', 'id': 66, 'game_objects': [{'name': 'Ice Pyramid 5F - Glass Door Left Box', 'object_id': 105, 'type': 'Box', 'access': ['IcePyramid5FStatue']}, {'name': 'Ice Pyramid 5F - West Ledge Box', 'object_id': 106, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 5F - South Shelf Box', 'object_id': 107, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 5F - South Leapfrog Box', 'object_id': 108, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 5F - Glass Door Right Box', 'object_id': 109, 'type': 'Box', 'access': ['IcePyramid5FStatue']}, {'name': 'Ice Pyramid 5F - North Box', 'object_id': 110, 'type': 'Box', 'access': []}], 'links': [{'target_room': 64, 'entrance': 134, 'teleporter': [74, 0], 'access': []}, {'target_room': 65, 'access': []}, {'target_room': 53, 'access': ['Bomb', 'Claw', 'Sword']}]}, {'name': 'Ice Pyramid 5F Stairs to Ice Golem', 'id': 67, 'game_objects': [{'name': 'Ice Pyramid 5F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid5FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 69, 'entrance': 137, 'teleporter': [76, 0], 'access': []}, {'target_room': 65, 'access': []}, {'target_room': 70, 'entrance': 136, 'teleporter': [75, 0], 'access': []}]}, {'name': 'Ice Pyramid Climbing Wall Room Lower Space', 'id': 68, 'game_objects': [], 'links': [{'target_room': 53, 'entrance': 139, 'teleporter': [78, 0], 'access': []}, {'target_room': 69, 'access': ['Claw']}]}, {'name': 'Ice Pyramid Climbing Wall Room Upper Space', 'id': 69, 'game_objects': [], 'links': [{'target_room': 67, 'entrance': 140, 'teleporter': [79, 0], 'access': []}, {'target_room': 68, 'access': ['Claw']}]}, {'name': 'Ice Pyramid Ice Golem Room', 'id': 70, 'game_objects': [{'name': 'Ice Pyramid 6F - Ice Golem Chest', 'object_id': 14, 'type': 'Chest', 'access': ['IceGolem']}, {'name': 'Ice Golem', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IceGolem'], 'access': []}], 'links': [{'target_room': 67, 'entrance': 141, 'teleporter': [80, 0], 'access': []}, {'target_room': 66, 'access': []}]}, {'name': 'Spencer Waterfall', 'id': 71, 'game_objects': [], 'links': [{'target_room': 72, 'entrance': 143, 'teleporter': [81, 0], 'access': []}, {'target_room': 40, 'entrance': 145, 'teleporter': [82, 0], 'access': []}, {'target_room': 40, 'entrance': 148, 'teleporter': [83, 0], 'access': []}]}, {'name': 'Spencer Cave Normal Main', 'id': 72, 'game_objects': [{'name': "Spencer's Cave - Box", 'object_id': 111, 'type': 'Box', 'access': ['Claw']}, {'name': "Spencer's Cave - Spencer", 'object_id': 8, 'type': 'NPC', 'access': []}, {'name': "Spencer's Cave - Locked Chest", 'object_id': 13, 'type': 'NPC', 'access': ['VenusKey']}], 'links': [{'target_room': 71, 'entrance': 150, 'teleporter': [85, 0], 'access': []}]}, {'name': 'Spencer Cave Normal South Ledge', 'id': 73, 'game_objects': [{'name': "Collapse Spencer's Cave", 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipLiberated'], 'access': ['MegaGrenade']}], 'links': [{'target_room': 227, 'entrance': 151, 'teleporter': [7, 6], 'access': []}, {'target_room': 203, 'access': ['MegaGrenade']}]}, {'name': 'Spencer Cave Caved In Main Loop', 'id': 203, 'game_objects': [], 'links': [{'target_room': 73, 'access': []}, {'target_room': 207, 'entrance': 156, 'teleporter': [36, 8], 'access': ['MobiusCrest']}, {'target_room': 204, 'access': ['Claw']}, {'target_room': 205, 'access': ['Bomb']}]}, {'name': 'Spencer Cave Caved In Waters', 'id': 204, 'game_objects': [{'name': 'Bomb Libra Block', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['SpencerCaveLibraBlockBombed'], 'access': ['MegaGrenade', 'Claw']}], 'links': [{'target_room': 203, 'access': ['Claw']}]}, {'name': 'Spencer Cave Caved In Libra Nook', 'id': 205, 'game_objects': [], 'links': [{'target_room': 206, 'entrance': 153, 'teleporter': [33, 8], 'access': ['LibraCrest']}]}, {'name': 'Spencer Cave Caved In Libra Corridor', 'id': 206, 'game_objects': [], 'links': [{'target_room': 205, 'entrance': 154, 'teleporter': [34, 8], 'access': ['LibraCrest']}, {'target_room': 207, 'access': ['SpencerCaveLibraBlockBombed']}]}, {'name': 'Spencer Cave Caved In Mobius Chest', 'id': 207, 'game_objects': [{'name': "Spencer's Cave - Mobius Chest", 'object_id': 15, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 203, 'entrance': 155, 'teleporter': [35, 8], 'access': ['MobiusCrest']}, {'target_room': 206, 'access': ['Bomb']}]}, {'name': 'Wintry Temple Outer Room', 'id': 74, 'game_objects': [], 'links': [{'target_room': 223, 'entrance': 157, 'teleporter': [15, 6], 'access': []}]}, {'name': 'Wintry Temple Inner Room', 'id': 75, 'game_objects': [{'name': 'Wintry Temple - West Box', 'object_id': 112, 'type': 'Box', 'access': []}, {'name': 'Wintry Temple - North Box', 'object_id': 113, 'type': 'Box', 'access': []}], 'links': [{'target_room': 92, 'entrance': 158, 'teleporter': [62, 8], 'access': ['GeminiCrest']}]}, {'name': 'Fireburg Upper Plaza', 'id': 76, 'game_objects': [], 'links': [{'target_room': 224, 'entrance': 159, 'teleporter': [9, 6], 'access': []}, {'target_room': 80, 'entrance': 163, 'teleporter': [91, 0], 'access': []}, {'target_room': 77, 'entrance': 164, 'teleporter': [98, 8], 'access': []}, {'target_room': 82, 'entrance': 165, 'teleporter': [96, 8], 'access': []}, {'target_room': 208, 'access': ['Claw']}]}, {'name': 'Fireburg Lower Plaza', 'id': 208, 'game_objects': [{'name': 'Fireburg - Hidden Tunnel Box', 'object_id': 116, 'type': 'Box', 'access': []}], 'links': [{'target_room': 76, 'access': ['Claw']}, {'target_room': 78, 'entrance': 166, 'teleporter': [11, 8], 'access': ['MultiKey']}]}, {'name': "Reuben's House", 'id': 77, 'game_objects': [{'name': "Fireburg - Reuben's House Arion", 'object_id': 14, 'type': 'NPC', 'access': ['ReubenDadSaved']}, {'name': 'Reuben Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Reuben1'], 'access': []}, {'name': "Fireburg - Reuben's House Box", 'object_id': 117, 'type': 'Box', 'access': []}], 'links': [{'target_room': 76, 'entrance': 167, 'teleporter': [98, 3], 'access': []}]}, {'name': "GrenadeMan's House", 'id': 78, 'game_objects': [{'name': 'Fireburg - Locked House Man', 'object_id': 12, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 208, 'entrance': 168, 'teleporter': [9, 8], 'access': ['MultiKey']}, {'target_room': 79, 'entrance': 169, 'teleporter': [93, 0], 'access': []}]}, {'name': "GrenadeMan's Mobius Room", 'id': 79, 'game_objects': [], 'links': [{'target_room': 78, 'entrance': 170, 'teleporter': [94, 0], 'access': []}, {'target_room': 161, 'entrance': 171, 'teleporter': [54, 8], 'access': ['MobiusCrest']}]}, {'name': 'Fireburg Vendor House', 'id': 80, 'game_objects': [{'name': 'Fireburg - Vendor', 'object_id': 11, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 76, 'entrance': 172, 'teleporter': [95, 0], 'access': []}, {'target_room': 81, 'entrance': 173, 'teleporter': [96, 0], 'access': []}]}, {'name': 'Fireburg Gemini Room', 'id': 81, 'game_objects': [], 'links': [{'target_room': 80, 'entrance': 174, 'teleporter': [97, 0], 'access': []}, {'target_room': 43, 'entrance': 175, 'teleporter': [45, 8], 'access': ['GeminiCrest']}]}, {'name': 'Fireburg Hotel Lobby', 'id': 82, 'game_objects': [{'name': 'Fireburg - Tristam', 'object_id': 10, 'type': 'NPC', 'access': ['Tristam', 'TristamBoneItemGiven']}], 'links': [{'target_room': 76, 'entrance': 177, 'teleporter': [99, 3], 'access': []}, {'target_room': 83, 'entrance': 176, 'teleporter': [213, 0], 'access': []}]}, {'name': 'Fireburg Hotel Beds', 'id': 83, 'game_objects': [], 'links': [{'target_room': 82, 'entrance': 178, 'teleporter': [214, 0], 'access': []}]}, {'name': 'Mine Exterior North West Platforms', 'id': 84, 'game_objects': [], 'links': [{'target_room': 224, 'entrance': 179, 'teleporter': [98, 0], 'access': []}, {'target_room': 88, 'entrance': 181, 'teleporter': [20, 2], 'access': ['Bomb']}, {'target_room': 85, 'access': ['Claw']}, {'target_room': 86, 'access': ['Claw']}, {'target_room': 87, 'access': ['Claw']}]}, {'name': 'Mine Exterior Central Ledge', 'id': 85, 'game_objects': [], 'links': [{'target_room': 90, 'entrance': 183, 'teleporter': [22, 2], 'access': ['Bomb']}, {'target_room': 84, 'access': ['Claw']}]}, {'name': 'Mine Exterior North Ledge', 'id': 86, 'game_objects': [], 'links': [{'target_room': 89, 'entrance': 182, 'teleporter': [21, 2], 'access': ['Bomb']}, {'target_room': 85, 'access': ['Claw']}]}, {'name': 'Mine Exterior South East Platforms', 'id': 87, 'game_objects': [{'name': 'Jinn', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Jinn'], 'access': []}], 'links': [{'target_room': 91, 'entrance': 180, 'teleporter': [99, 0], 'access': ['Jinn']}, {'target_room': 86, 'access': []}, {'target_room': 85, 'access': ['Claw']}]}, {'name': 'Mine Parallel Room', 'id': 88, 'game_objects': [{'name': 'Mine - Parallel Room West Box', 'object_id': 119, 'type': 'Box', 'access': ['Claw']}, {'name': 'Mine - Parallel Room East Box', 'object_id': 120, 'type': 'Box', 'access': ['Claw']}], 'links': [{'target_room': 84, 'entrance': 185, 'teleporter': [100, 3], 'access': []}]}, {'name': 'Mine Crescent Room', 'id': 89, 'game_objects': [{'name': 'Mine - Crescent Room Chest', 'object_id': 16, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 86, 'entrance': 186, 'teleporter': [101, 3], 'access': []}]}, {'name': 'Mine Climbing Room', 'id': 90, 'game_objects': [{'name': 'Mine - Glitchy Collision Cave Box', 'object_id': 118, 'type': 'Box', 'access': ['Claw']}], 'links': [{'target_room': 85, 'entrance': 187, 'teleporter': [102, 3], 'access': []}]}, {'name': 'Mine Cliff', 'id': 91, 'game_objects': [{'name': 'Mine - Cliff Southwest Box', 'object_id': 121, 'type': 'Box', 'access': []}, {'name': 'Mine - Cliff Northwest Box', 'object_id': 122, 'type': 'Box', 'access': []}, {'name': 'Mine - Cliff Northeast Box', 'object_id': 123, 'type': 'Box', 'access': []}, {'name': 'Mine - Cliff Southeast Box', 'object_id': 124, 'type': 'Box', 'access': []}, {'name': 'Mine - Reuben', 'object_id': 7, 'type': 'NPC', 'access': ['Reuben1']}, {'name': "Reuben's dad Saved", 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ReubenDadSaved'], 'access': ['MegaGrenade']}], 'links': [{'target_room': 87, 'entrance': 188, 'teleporter': [100, 0], 'access': []}]}, {'name': 'Sealed Temple', 'id': 92, 'game_objects': [{'name': 'Sealed Temple - West Box', 'object_id': 125, 'type': 'Box', 'access': []}, {'name': 'Sealed Temple - East Box', 'object_id': 126, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 190, 'teleporter': [16, 6], 'access': []}, {'target_room': 75, 'entrance': 191, 'teleporter': [63, 8], 'access': ['GeminiCrest']}]}, {'name': 'Volcano Base', 'id': 93, 'game_objects': [{'name': 'Volcano - Base Chest', 'object_id': 17, 'type': 'Chest', 'access': []}, {'name': 'Volcano - Base West Box', 'object_id': 127, 'type': 'Box', 'access': []}, {'name': 'Volcano - Base East Left Box', 'object_id': 128, 'type': 'Box', 'access': []}, {'name': 'Volcano - Base East Right Box', 'object_id': 129, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 192, 'teleporter': [103, 0], 'access': []}, {'target_room': 98, 'entrance': 196, 'teleporter': [31, 8], 'access': []}, {'target_room': 96, 'entrance': 197, 'teleporter': [30, 8], 'access': []}]}, {'name': 'Volcano Top Left', 'id': 94, 'game_objects': [{'name': 'Volcano - Medusa Chest', 'object_id': 18, 'type': 'Chest', 'access': ['Medusa']}, {'name': 'Medusa', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Medusa'], 'access': []}, {'name': 'Volcano - Behind Medusa Box', 'object_id': 130, 'type': 'Box', 'access': []}], 'links': [{'target_room': 209, 'entrance': 199, 'teleporter': [26, 8], 'access': []}]}, {'name': 'Volcano Top Right', 'id': 95, 'game_objects': [{'name': 'Volcano - Top of the Volcano Left Box', 'object_id': 131, 'type': 'Box', 'access': []}, {'name': 'Volcano - Top of the Volcano Right Box', 'object_id': 132, 'type': 'Box', 'access': []}], 'links': [{'target_room': 99, 'entrance': 200, 'teleporter': [79, 8], 'access': []}]}, {'name': 'Volcano Right Path', 'id': 96, 'game_objects': [{'name': 'Volcano - Right Path Box', 'object_id': 135, 'type': 'Box', 'access': []}], 'links': [{'target_room': 93, 'entrance': 201, 'teleporter': [15, 8], 'access': []}]}, {'name': 'Volcano Left Path', 'id': 98, 'game_objects': [{'name': 'Volcano - Left Path Box', 'object_id': 134, 'type': 'Box', 'access': []}], 'links': [{'target_room': 93, 'entrance': 204, 'teleporter': [27, 8], 'access': []}, {'target_room': 99, 'entrance': 202, 'teleporter': [25, 2], 'access': []}, {'target_room': 209, 'entrance': 203, 'teleporter': [26, 2], 'access': []}]}, {'name': 'Volcano Cross Left-Right', 'id': 99, 'game_objects': [], 'links': [{'target_room': 95, 'entrance': 206, 'teleporter': [29, 8], 'access': []}, {'target_room': 98, 'entrance': 205, 'teleporter': [103, 3], 'access': []}]}, {'name': 'Volcano Cross Right-Left', 'id': 209, 'game_objects': [{'name': 'Volcano - Crossover Section Box', 'object_id': 133, 'type': 'Box', 'access': []}], 'links': [{'target_room': 98, 'entrance': 208, 'teleporter': [104, 3], 'access': []}, {'target_room': 94, 'entrance': 207, 'teleporter': [28, 8], 'access': []}]}, {'name': 'Lava Dome Inner Ring Main Loop', 'id': 100, 'game_objects': [{'name': 'Lava Dome - Exterior Caldera Near Switch Cliff Box', 'object_id': 136, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Exterior South Cliff Box', 'object_id': 137, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 209, 'teleporter': [104, 0], 'access': []}, {'target_room': 113, 'entrance': 211, 'teleporter': [105, 0], 'access': []}, {'target_room': 114, 'entrance': 212, 'teleporter': [106, 0], 'access': []}, {'target_room': 116, 'entrance': 213, 'teleporter': [108, 0], 'access': []}, {'target_room': 118, 'entrance': 214, 'teleporter': [111, 0], 'access': []}]}, {'name': 'Lava Dome Inner Ring Center Ledge', 'id': 101, 'game_objects': [{'name': 'Lava Dome - Exterior Center Dropoff Ledge Box', 'object_id': 138, 'type': 'Box', 'access': []}], 'links': [{'target_room': 115, 'entrance': 215, 'teleporter': [107, 0], 'access': []}, {'target_room': 100, 'access': ['Claw']}]}, {'name': 'Lava Dome Inner Ring Plate Ledge', 'id': 102, 'game_objects': [{'name': 'Lava Dome Plate', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['LavaDomePlate'], 'access': []}], 'links': [{'target_room': 119, 'entrance': 216, 'teleporter': [109, 0], 'access': []}]}, {'name': 'Lava Dome Inner Ring Upper Ledge West', 'id': 103, 'game_objects': [], 'links': [{'target_room': 111, 'entrance': 219, 'teleporter': [112, 0], 'access': []}, {'target_room': 108, 'entrance': 220, 'teleporter': [113, 0], 'access': []}, {'target_room': 104, 'access': ['Claw']}, {'target_room': 100, 'access': ['Claw']}]}, {'name': 'Lava Dome Inner Ring Upper Ledge East', 'id': 104, 'game_objects': [], 'links': [{'target_room': 110, 'entrance': 218, 'teleporter': [110, 0], 'access': []}, {'target_room': 103, 'access': ['Claw']}]}, {'name': 'Lava Dome Inner Ring Big Door Ledge', 'id': 105, 'game_objects': [], 'links': [{'target_room': 107, 'entrance': 221, 'teleporter': [114, 0], 'access': []}, {'target_room': 121, 'entrance': 222, 'teleporter': [29, 2], 'access': ['LavaDomePlate']}]}, {'name': 'Lava Dome Inner Ring Tiny Bottom Ledge', 'id': 106, 'game_objects': [{'name': 'Lava Dome - Exterior Dead End Caldera Box', 'object_id': 139, 'type': 'Box', 'access': []}], 'links': [{'target_room': 120, 'entrance': 226, 'teleporter': [115, 0], 'access': []}]}, {'name': 'Lava Dome Jump Maze II', 'id': 107, 'game_objects': [{'name': 'Lava Dome - Gold Maze Northwest Box', 'object_id': 140, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Southwest Box', 'object_id': 246, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Northeast Box', 'object_id': 247, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze North Box', 'object_id': 248, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Center Box', 'object_id': 249, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Southeast Box', 'object_id': 250, 'type': 'Box', 'access': []}], 'links': [{'target_room': 105, 'entrance': 227, 'teleporter': [116, 0], 'access': []}, {'target_room': 108, 'entrance': 228, 'teleporter': [119, 0], 'access': []}, {'target_room': 120, 'entrance': 229, 'teleporter': [120, 0], 'access': []}]}, {'name': 'Lava Dome Up-Down Corridor', 'id': 108, 'game_objects': [], 'links': [{'target_room': 107, 'entrance': 231, 'teleporter': [118, 0], 'access': []}, {'target_room': 103, 'entrance': 230, 'teleporter': [117, 0], 'access': []}]}, {'name': 'Lava Dome Jump Maze I', 'id': 109, 'game_objects': [{'name': 'Lava Dome - Bare Maze Leapfrog Alcove North Box', 'object_id': 141, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Bare Maze Leapfrog Alcove South Box', 'object_id': 142, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Bare Maze Center Box', 'object_id': 143, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Bare Maze Southwest Box', 'object_id': 144, 'type': 'Box', 'access': []}], 'links': [{'target_room': 118, 'entrance': 232, 'teleporter': [121, 0], 'access': []}, {'target_room': 111, 'entrance': 233, 'teleporter': [122, 0], 'access': []}]}, {'name': 'Lava Dome Pointless Room', 'id': 110, 'game_objects': [], 'links': [{'target_room': 104, 'entrance': 234, 'teleporter': [123, 0], 'access': []}]}, {'name': 'Lava Dome Lower Moon Helm Room', 'id': 111, 'game_objects': [{'name': 'Lava Dome - U-Bend Room North Box', 'object_id': 146, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - U-Bend Room South Box', 'object_id': 147, 'type': 'Box', 'access': []}], 'links': [{'target_room': 103, 'entrance': 235, 'teleporter': [124, 0], 'access': []}, {'target_room': 109, 'entrance': 236, 'teleporter': [125, 0], 'access': []}]}, {'name': 'Lava Dome Moon Helm Room', 'id': 112, 'game_objects': [{'name': 'Lava Dome - Beyond River Room Chest', 'object_id': 19, 'type': 'Chest', 'access': []}, {'name': 'Lava Dome - Beyond River Room Box', 'object_id': 145, 'type': 'Box', 'access': []}], 'links': [{'target_room': 117, 'entrance': 237, 'teleporter': [126, 0], 'access': []}]}, {'name': 'Lava Dome Three Jumps Room', 'id': 113, 'game_objects': [{'name': 'Lava Dome - Three Jumps Room Box', 'object_id': 150, 'type': 'Box', 'access': []}], 'links': [{'target_room': 100, 'entrance': 238, 'teleporter': [127, 0], 'access': []}]}, {'name': 'Lava Dome Life Chest Room Lower Ledge', 'id': 114, 'game_objects': [{'name': 'Lava Dome - Gold Bar Room Boulder Chest', 'object_id': 28, 'type': 'Chest', 'access': ['MegaGrenade']}], 'links': [{'target_room': 100, 'entrance': 239, 'teleporter': [128, 0], 'access': []}, {'target_room': 115, 'access': ['Claw']}]}, {'name': 'Lava Dome Life Chest Room Upper Ledge', 'id': 115, 'game_objects': [{'name': 'Lava Dome - Gold Bar Room Leapfrog Alcove Box West', 'object_id': 148, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Bar Room Leapfrog Alcove Box East', 'object_id': 149, 'type': 'Box', 'access': []}], 'links': [{'target_room': 101, 'entrance': 240, 'teleporter': [129, 0], 'access': []}, {'target_room': 114, 'access': ['Claw']}]}, {'name': 'Lava Dome Big Jump Room Main Area', 'id': 116, 'game_objects': [{'name': 'Lava Dome - Lava River Room North Box', 'object_id': 152, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Lava River Room East Box', 'object_id': 153, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Lava River Room South Box', 'object_id': 154, 'type': 'Box', 'access': []}], 'links': [{'target_room': 100, 'entrance': 241, 'teleporter': [133, 0], 'access': []}, {'target_room': 119, 'entrance': 243, 'teleporter': [132, 0], 'access': []}, {'target_room': 117, 'access': ['MegaGrenade']}]}, {'name': 'Lava Dome Big Jump Room MegaGrenade Area', 'id': 117, 'game_objects': [], 'links': [{'target_room': 112, 'entrance': 242, 'teleporter': [131, 0], 'access': []}, {'target_room': 116, 'access': ['Bomb']}]}, {'name': 'Lava Dome Split Corridor', 'id': 118, 'game_objects': [{'name': 'Lava Dome - Split Corridor Box', 'object_id': 151, 'type': 'Box', 'access': []}], 'links': [{'target_room': 109, 'entrance': 244, 'teleporter': [130, 0], 'access': []}, {'target_room': 100, 'entrance': 245, 'teleporter': [134, 0], 'access': []}]}, {'name': 'Lava Dome Plate Corridor', 'id': 119, 'game_objects': [], 'links': [{'target_room': 102, 'entrance': 246, 'teleporter': [135, 0], 'access': []}, {'target_room': 116, 'entrance': 247, 'teleporter': [137, 0], 'access': []}]}, {'name': 'Lava Dome Four Boxes Stairs', 'id': 120, 'game_objects': [{'name': 'Lava Dome - Caldera Stairway West Left Box', 'object_id': 155, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Caldera Stairway West Right Box', 'object_id': 156, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Caldera Stairway East Left Box', 'object_id': 157, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Caldera Stairway East Right Box', 'object_id': 158, 'type': 'Box', 'access': []}], 'links': [{'target_room': 107, 'entrance': 248, 'teleporter': [136, 0], 'access': []}, {'target_room': 106, 'entrance': 249, 'teleporter': [16, 0], 'access': []}]}, {'name': 'Lava Dome Hydra Room', 'id': 121, 'game_objects': [{'name': 'Lava Dome - Dualhead Hydra Chest', 'object_id': 20, 'type': 'Chest', 'access': ['DualheadHydra']}, {'name': 'Dualhead Hydra', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['DualheadHydra'], 'access': []}, {'name': 'Lava Dome - Hydra Room Northwest Box', 'object_id': 159, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Hydra Room Southweast Box', 'object_id': 160, 'type': 'Box', 'access': []}], 'links': [{'target_room': 105, 'entrance': 250, 'teleporter': [105, 3], 'access': []}, {'target_room': 122, 'entrance': 251, 'teleporter': [138, 0], 'access': ['DualheadHydra']}]}, {'name': 'Lava Dome Escape Corridor', 'id': 122, 'game_objects': [], 'links': [{'target_room': 121, 'entrance': 253, 'teleporter': [139, 0], 'access': []}]}, {'name': 'Rope Bridge', 'id': 123, 'game_objects': [{'name': 'Rope Bridge - West Box', 'object_id': 163, 'type': 'Box', 'access': []}, {'name': 'Rope Bridge - East Box', 'object_id': 164, 'type': 'Box', 'access': []}], 'links': [{'target_room': 226, 'entrance': 255, 'teleporter': [140, 0], 'access': []}]}, {'name': 'Alive Forest', 'id': 124, 'game_objects': [{'name': 'Alive Forest - Tree Stump Chest', 'object_id': 21, 'type': 'Chest', 'access': ['Axe']}, {'name': 'Alive Forest - Near Entrance Box', 'object_id': 165, 'type': 'Box', 'access': ['Axe']}, {'name': 'Alive Forest - After Bridge Box', 'object_id': 166, 'type': 'Box', 'access': ['Axe']}, {'name': 'Alive Forest - Gemini Stump Box', 'object_id': 167, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 226, 'entrance': 272, 'teleporter': [142, 0], 'access': ['Axe']}, {'target_room': 21, 'entrance': 275, 'teleporter': [64, 8], 'access': ['LibraCrest', 'Axe']}, {'target_room': 22, 'entrance': 276, 'teleporter': [65, 8], 'access': ['GeminiCrest', 'Axe']}, {'target_room': 23, 'entrance': 277, 'teleporter': [66, 8], 'access': ['MobiusCrest', 'Axe']}, {'target_room': 125, 'entrance': 274, 'teleporter': [143, 0], 'access': ['Axe']}]}, {'name': 'Giant Tree 1F Main Area', 'id': 125, 'game_objects': [{'name': 'Giant Tree 1F - Northwest Box', 'object_id': 168, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 1F - Southwest Box', 'object_id': 169, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 1F - Center Box', 'object_id': 170, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 1F - East Box', 'object_id': 171, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 278, 'teleporter': [56, 1], 'access': []}, {'target_room': 202, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 1F North Island', 'id': 202, 'game_objects': [], 'links': [{'target_room': 127, 'entrance': 280, 'teleporter': [144, 0], 'access': []}, {'target_room': 125, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 1F Central Island', 'id': 126, 'game_objects': [], 'links': [{'target_room': 202, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F Main Lobby', 'id': 127, 'game_objects': [{'name': 'Giant Tree 2F - North Box', 'object_id': 172, 'type': 'Box', 'access': []}], 'links': [{'target_room': 126, 'access': ['DragonClaw']}, {'target_room': 125, 'entrance': 281, 'teleporter': [145, 0], 'access': []}, {'target_room': 133, 'entrance': 283, 'teleporter': [149, 0], 'access': []}, {'target_room': 129, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F West Ledge', 'id': 128, 'game_objects': [{'name': 'Giant Tree 2F - Dropdown Ledge Box', 'object_id': 174, 'type': 'Box', 'access': []}], 'links': [{'target_room': 140, 'entrance': 284, 'teleporter': [147, 0], 'access': ['Sword']}, {'target_room': 130, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F Lower Area', 'id': 129, 'game_objects': [{'name': 'Giant Tree 2F - South Box', 'object_id': 173, 'type': 'Box', 'access': []}], 'links': [{'target_room': 130, 'access': ['Claw']}, {'target_room': 131, 'access': ['Claw']}]}, {'name': 'Giant Tree 2F Central Island', 'id': 130, 'game_objects': [], 'links': [{'target_room': 129, 'access': ['Claw']}, {'target_room': 135, 'entrance': 282, 'teleporter': [146, 0], 'access': ['Sword']}]}, {'name': 'Giant Tree 2F East Ledge', 'id': 131, 'game_objects': [], 'links': [{'target_room': 129, 'access': ['Claw']}, {'target_room': 130, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F Meteor Chest Room', 'id': 132, 'game_objects': [{'name': 'Giant Tree 2F - Gidrah Chest', 'object_id': 22, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 133, 'entrance': 285, 'teleporter': [148, 0], 'access': []}]}, {'name': 'Giant Tree 2F Mushroom Room', 'id': 133, 'game_objects': [{'name': 'Giant Tree 2F - Mushroom Tunnel West Box', 'object_id': 175, 'type': 'Box', 'access': ['Axe']}, {'name': 'Giant Tree 2F - Mushroom Tunnel East Box', 'object_id': 176, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 127, 'entrance': 286, 'teleporter': [150, 0], 'access': ['Axe']}, {'target_room': 132, 'entrance': 287, 'teleporter': [151, 0], 'access': ['Axe', 'Gidrah']}]}, {'name': 'Giant Tree 3F Central Island', 'id': 135, 'game_objects': [{'name': 'Giant Tree 3F - Central Island Box', 'object_id': 179, 'type': 'Box', 'access': []}], 'links': [{'target_room': 130, 'entrance': 288, 'teleporter': [152, 0], 'access': []}, {'target_room': 136, 'access': ['Claw']}, {'target_room': 137, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 3F Central Area', 'id': 136, 'game_objects': [{'name': 'Giant Tree 3F - Center North Box', 'object_id': 177, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 3F - Center West Box', 'object_id': 178, 'type': 'Box', 'access': []}], 'links': [{'target_room': 135, 'access': ['Claw']}, {'target_room': 127, 'access': []}, {'target_room': 131, 'access': []}]}, {'name': 'Giant Tree 3F Lower Ledge', 'id': 137, 'game_objects': [], 'links': [{'target_room': 135, 'access': ['DragonClaw']}, {'target_room': 142, 'entrance': 289, 'teleporter': [153, 0], 'access': ['Sword']}]}, {'name': 'Giant Tree 3F West Area', 'id': 138, 'game_objects': [{'name': 'Giant Tree 3F - West Side Box', 'object_id': 180, 'type': 'Box', 'access': []}], 'links': [{'target_room': 128, 'access': []}, {'target_room': 210, 'entrance': 290, 'teleporter': [154, 0], 'access': []}]}, {'name': 'Giant Tree 3F Middle Up Island', 'id': 139, 'game_objects': [], 'links': [{'target_room': 136, 'access': ['Claw']}]}, {'name': 'Giant Tree 3F West Platform', 'id': 140, 'game_objects': [], 'links': [{'target_room': 139, 'access': ['Claw']}, {'target_room': 141, 'access': ['Claw']}, {'target_room': 128, 'entrance': 291, 'teleporter': [155, 0], 'access': []}]}, {'name': 'Giant Tree 3F North Ledge', 'id': 141, 'game_objects': [], 'links': [{'target_room': 143, 'entrance': 292, 'teleporter': [156, 0], 'access': ['Sword']}, {'target_room': 139, 'access': ['Claw']}, {'target_room': 136, 'access': ['Claw']}]}, {'name': 'Giant Tree Worm Room Upper Ledge', 'id': 142, 'game_objects': [{'name': 'Giant Tree 3F - Worm Room North Box', 'object_id': 181, 'type': 'Box', 'access': ['Axe']}, {'name': 'Giant Tree 3F - Worm Room South Box', 'object_id': 182, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 137, 'entrance': 293, 'teleporter': [157, 0], 'access': ['Axe']}, {'target_room': 210, 'access': ['Axe', 'Claw']}]}, {'name': 'Giant Tree Worm Room Lower Ledge', 'id': 210, 'game_objects': [], 'links': [{'target_room': 138, 'entrance': 294, 'teleporter': [158, 0], 'access': []}]}, {'name': 'Giant Tree 4F Lower Floor', 'id': 143, 'game_objects': [], 'links': [{'target_room': 141, 'entrance': 295, 'teleporter': [159, 0], 'access': []}, {'target_room': 148, 'entrance': 296, 'teleporter': [160, 0], 'access': []}, {'target_room': 148, 'entrance': 297, 'teleporter': [161, 0], 'access': []}, {'target_room': 147, 'entrance': 298, 'teleporter': [162, 0], 'access': ['Sword']}]}, {'name': 'Giant Tree 4F Middle Floor', 'id': 144, 'game_objects': [{'name': 'Giant Tree 4F - Highest Platform North Box', 'object_id': 183, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 4F - Highest Platform South Box', 'object_id': 184, 'type': 'Box', 'access': []}], 'links': [{'target_room': 149, 'entrance': 299, 'teleporter': [163, 0], 'access': []}, {'target_room': 145, 'access': ['Claw']}, {'target_room': 146, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 4F Upper Floor', 'id': 145, 'game_objects': [], 'links': [{'target_room': 150, 'entrance': 300, 'teleporter': [164, 0], 'access': ['Sword']}, {'target_room': 144, 'access': ['Claw']}]}, {'name': 'Giant Tree 4F South Ledge', 'id': 146, 'game_objects': [{'name': 'Giant Tree 4F - Hook Ledge Northeast Box', 'object_id': 185, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 4F - Hook Ledge Southwest Box', 'object_id': 186, 'type': 'Box', 'access': []}], 'links': [{'target_room': 144, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 4F Slime Room East Area', 'id': 147, 'game_objects': [{'name': 'Giant Tree 4F - East Slime Room Box', 'object_id': 188, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 143, 'entrance': 304, 'teleporter': [168, 0], 'access': []}]}, {'name': 'Giant Tree 4F Slime Room West Area', 'id': 148, 'game_objects': [], 'links': [{'target_room': 143, 'entrance': 303, 'teleporter': [167, 0], 'access': ['Axe']}, {'target_room': 143, 'entrance': 302, 'teleporter': [166, 0], 'access': ['Axe']}, {'target_room': 149, 'access': ['Axe', 'Claw']}]}, {'name': 'Giant Tree 4F Slime Room Platform', 'id': 149, 'game_objects': [{'name': 'Giant Tree 4F - West Slime Room Box', 'object_id': 187, 'type': 'Box', 'access': []}], 'links': [{'target_room': 144, 'entrance': 301, 'teleporter': [165, 0], 'access': []}, {'target_room': 148, 'access': ['Claw']}]}, {'name': 'Giant Tree 5F Lower Area', 'id': 150, 'game_objects': [{'name': 'Giant Tree 5F - Northwest Left Box', 'object_id': 189, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 5F - Northwest Right Box', 'object_id': 190, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 5F - South Left Box', 'object_id': 191, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 5F - South Right Box', 'object_id': 192, 'type': 'Box', 'access': []}], 'links': [{'target_room': 145, 'entrance': 305, 'teleporter': [169, 0], 'access': []}, {'target_room': 151, 'access': ['Claw']}, {'target_room': 143, 'access': []}]}, {'name': 'Giant Tree 5F Gidrah Platform', 'id': 151, 'game_objects': [{'name': 'Gidrah', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Gidrah'], 'access': []}], 'links': [{'target_room': 150, 'access': ['Claw']}]}, {'name': 'Kaidge Temple Lower Ledge', 'id': 152, 'game_objects': [], 'links': [{'target_room': 226, 'entrance': 307, 'teleporter': [18, 6], 'access': []}, {'target_room': 153, 'access': ['Claw']}]}, {'name': 'Kaidge Temple Upper Ledge', 'id': 153, 'game_objects': [{'name': 'Kaidge Temple - Box', 'object_id': 193, 'type': 'Box', 'access': []}], 'links': [{'target_room': 185, 'entrance': 308, 'teleporter': [71, 8], 'access': ['MobiusCrest']}, {'target_room': 152, 'access': ['Claw']}]}, {'name': 'Windhole Temple', 'id': 154, 'game_objects': [{'name': 'Windhole Temple - Box', 'object_id': 194, 'type': 'Box', 'access': []}], 'links': [{'target_room': 226, 'entrance': 309, 'teleporter': [173, 0], 'access': []}]}, {'name': 'Mount Gale', 'id': 155, 'game_objects': [{'name': 'Mount Gale - Dullahan Chest', 'object_id': 23, 'type': 'Chest', 'access': ['DragonClaw', 'Dullahan']}, {'name': 'Dullahan', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Dullahan'], 'access': ['DragonClaw']}, {'name': 'Mount Gale - East Box', 'object_id': 195, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Mount Gale - West Box', 'object_id': 196, 'type': 'Box', 'access': []}], 'links': [{'target_room': 226, 'entrance': 310, 'teleporter': [174, 0], 'access': []}]}, {'name': 'Windia', 'id': 156, 'game_objects': [], 'links': [{'target_room': 226, 'entrance': 312, 'teleporter': [10, 6], 'access': []}, {'target_room': 157, 'entrance': 320, 'teleporter': [30, 5], 'access': []}, {'target_room': 163, 'entrance': 321, 'teleporter': [97, 8], 'access': []}, {'target_room': 165, 'entrance': 322, 'teleporter': [32, 5], 'access': []}, {'target_room': 159, 'entrance': 323, 'teleporter': [176, 4], 'access': []}, {'target_room': 160, 'entrance': 324, 'teleporter': [177, 4], 'access': []}]}, {'name': "Otto's House", 'id': 157, 'game_objects': [{'name': 'Otto', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['RainbowBridge'], 'access': ['ThunderRock']}], 'links': [{'target_room': 156, 'entrance': 327, 'teleporter': [106, 3], 'access': []}, {'target_room': 158, 'entrance': 326, 'teleporter': [33, 2], 'access': []}]}, {'name': "Otto's Attic", 'id': 158, 'game_objects': [{'name': "Windia - Otto's Attic Box", 'object_id': 197, 'type': 'Box', 'access': []}], 'links': [{'target_room': 157, 'entrance': 328, 'teleporter': [107, 3], 'access': []}]}, {'name': 'Windia Kid House', 'id': 159, 'game_objects': [], 'links': [{'target_room': 156, 'entrance': 329, 'teleporter': [178, 0], 'access': []}, {'target_room': 161, 'entrance': 330, 'teleporter': [180, 0], 'access': []}]}, {'name': 'Windia Old People House', 'id': 160, 'game_objects': [], 'links': [{'target_room': 156, 'entrance': 331, 'teleporter': [179, 0], 'access': []}, {'target_room': 162, 'entrance': 332, 'teleporter': [181, 0], 'access': []}]}, {'name': 'Windia Kid House Basement', 'id': 161, 'game_objects': [], 'links': [{'target_room': 159, 'entrance': 333, 'teleporter': [182, 0], 'access': []}, {'target_room': 79, 'entrance': 334, 'teleporter': [44, 8], 'access': ['MobiusCrest']}]}, {'name': 'Windia Old People House Basement', 'id': 162, 'game_objects': [{'name': 'Windia - Mobius Basement West Box', 'object_id': 200, 'type': 'Box', 'access': []}, {'name': 'Windia - Mobius Basement East Box', 'object_id': 201, 'type': 'Box', 'access': []}], 'links': [{'target_room': 160, 'entrance': 335, 'teleporter': [183, 0], 'access': []}, {'target_room': 186, 'entrance': 336, 'teleporter': [43, 8], 'access': ['MobiusCrest']}]}, {'name': 'Windia Inn Lobby', 'id': 163, 'game_objects': [], 'links': [{'target_room': 156, 'entrance': 338, 'teleporter': [135, 3], 'access': []}, {'target_room': 164, 'entrance': 337, 'teleporter': [102, 8], 'access': []}]}, {'name': 'Windia Inn Beds', 'id': 164, 'game_objects': [{'name': 'Windia - Inn Bedroom North Box', 'object_id': 198, 'type': 'Box', 'access': []}, {'name': 'Windia - Inn Bedroom South Box', 'object_id': 199, 'type': 'Box', 'access': []}, {'name': 'Windia - Kaeli', 'object_id': 15, 'type': 'NPC', 'access': ['Kaeli2']}], 'links': [{'target_room': 163, 'entrance': 339, 'teleporter': [216, 0], 'access': []}]}, {'name': 'Windia Vendor House', 'id': 165, 'game_objects': [{'name': 'Windia - Vendor', 'object_id': 16, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 156, 'entrance': 340, 'teleporter': [108, 3], 'access': []}]}, {'name': 'Pazuzu Tower 1F Main Lobby', 'id': 166, 'game_objects': [{'name': 'Pazuzu 1F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu1F'], 'access': []}], 'links': [{'target_room': 226, 'entrance': 341, 'teleporter': [184, 0], 'access': []}, {'target_room': 180, 'entrance': 345, 'teleporter': [185, 0], 'access': []}]}, {'name': 'Pazuzu Tower 1F Boxes Room', 'id': 167, 'game_objects': [{'name': "Pazuzu's Tower 1F - Descent Bomb Wall West Box", 'object_id': 202, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 1F - Descent Bomb Wall Center Box", 'object_id': 203, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 1F - Descent Bomb Wall East Box", 'object_id': 204, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 1F - Descent Box", 'object_id': 205, 'type': 'Box', 'access': []}], 'links': [{'target_room': 169, 'entrance': 349, 'teleporter': [187, 0], 'access': []}]}, {'name': 'Pazuzu Tower 1F Southern Platform', 'id': 168, 'game_objects': [], 'links': [{'target_room': 169, 'entrance': 346, 'teleporter': [186, 0], 'access': []}, {'target_room': 166, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 2F', 'id': 169, 'game_objects': [{'name': "Pazuzu's Tower 2F - East Room West Box", 'object_id': 206, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 2F - East Room East Box", 'object_id': 207, 'type': 'Box', 'access': []}, {'name': 'Pazuzu 2F Lock', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu2FLock'], 'access': ['Axe']}, {'name': 'Pazuzu 2F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu2F'], 'access': ['Bomb']}], 'links': [{'target_room': 183, 'entrance': 350, 'teleporter': [188, 0], 'access': []}, {'target_room': 168, 'entrance': 351, 'teleporter': [189, 0], 'access': []}, {'target_room': 167, 'entrance': 352, 'teleporter': [190, 0], 'access': []}, {'target_room': 171, 'entrance': 353, 'teleporter': [191, 0], 'access': []}]}, {'name': 'Pazuzu 3F Main Room', 'id': 170, 'game_objects': [{'name': "Pazuzu's Tower 3F - Guest Room West Box", 'object_id': 208, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 3F - Guest Room East Box", 'object_id': 209, 'type': 'Box', 'access': []}, {'name': 'Pazuzu 3F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu3F'], 'access': []}], 'links': [{'target_room': 180, 'entrance': 356, 'teleporter': [192, 0], 'access': []}, {'target_room': 181, 'entrance': 357, 'teleporter': [193, 0], 'access': []}]}, {'name': 'Pazuzu 3F Central Island', 'id': 171, 'game_objects': [], 'links': [{'target_room': 169, 'entrance': 360, 'teleporter': [194, 0], 'access': []}, {'target_room': 170, 'access': ['DragonClaw']}, {'target_room': 172, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 3F Southern Island', 'id': 172, 'game_objects': [{'name': "Pazuzu's Tower 3F - South Ledge Box", 'object_id': 210, 'type': 'Box', 'access': []}], 'links': [{'target_room': 173, 'entrance': 361, 'teleporter': [195, 0], 'access': []}, {'target_room': 171, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 4F', 'id': 173, 'game_objects': [{'name': "Pazuzu's Tower 4F - Elevator West Box", 'object_id': 211, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 4F - Elevator East Box", 'object_id': 212, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 4F - East Storage Room Chest", 'object_id': 24, 'type': 'Chest', 'access': []}, {'name': 'Pazuzu 4F Lock', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu4FLock'], 'access': ['Axe']}, {'name': 'Pazuzu 4F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu4F'], 'access': ['Bomb']}], 'links': [{'target_room': 183, 'entrance': 362, 'teleporter': [196, 0], 'access': []}, {'target_room': 184, 'entrance': 363, 'teleporter': [197, 0], 'access': []}, {'target_room': 172, 'entrance': 364, 'teleporter': [198, 0], 'access': []}, {'target_room': 175, 'entrance': 365, 'teleporter': [199, 0], 'access': []}]}, {'name': 'Pazuzu 5F Pazuzu Loop', 'id': 174, 'game_objects': [{'name': 'Pazuzu 5F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu5F'], 'access': []}], 'links': [{'target_room': 181, 'entrance': 368, 'teleporter': [200, 0], 'access': []}, {'target_room': 182, 'entrance': 369, 'teleporter': [201, 0], 'access': []}]}, {'name': 'Pazuzu 5F Upper Loop', 'id': 175, 'game_objects': [{'name': "Pazuzu's Tower 5F - North Box", 'object_id': 213, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 5F - South Box", 'object_id': 214, 'type': 'Box', 'access': []}], 'links': [{'target_room': 173, 'entrance': 370, 'teleporter': [202, 0], 'access': []}, {'target_room': 176, 'entrance': 371, 'teleporter': [203, 0], 'access': []}]}, {'name': 'Pazuzu 6F', 'id': 176, 'game_objects': [{'name': "Pazuzu's Tower 6F - Box", 'object_id': 215, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 6F - Chest", 'object_id': 25, 'type': 'Chest', 'access': []}, {'name': 'Pazuzu 6F Lock', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu6FLock'], 'access': ['Bomb', 'Axe']}, {'name': 'Pazuzu 6F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu6F'], 'access': ['Bomb']}], 'links': [{'target_room': 184, 'entrance': 374, 'teleporter': [204, 0], 'access': []}, {'target_room': 175, 'entrance': 375, 'teleporter': [205, 0], 'access': []}, {'target_room': 178, 'entrance': 376, 'teleporter': [206, 0], 'access': []}, {'target_room': 178, 'entrance': 377, 'teleporter': [207, 0], 'access': []}]}, {'name': 'Pazuzu 7F Southwest Area', 'id': 177, 'game_objects': [], 'links': [{'target_room': 182, 'entrance': 380, 'teleporter': [26, 0], 'access': []}, {'target_room': 178, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 7F Rest of the Area', 'id': 178, 'game_objects': [], 'links': [{'target_room': 177, 'access': ['DragonClaw']}, {'target_room': 176, 'entrance': 381, 'teleporter': [27, 0], 'access': []}, {'target_room': 176, 'entrance': 382, 'teleporter': [28, 0], 'access': []}, {'target_room': 179, 'access': ['DragonClaw', 'Pazuzu2FLock', 'Pazuzu4FLock', 'Pazuzu6FLock', 'Pazuzu1F', 'Pazuzu2F', 'Pazuzu3F', 'Pazuzu4F', 'Pazuzu5F', 'Pazuzu6F']}]}, {'name': 'Pazuzu 7F Sky Room', 'id': 179, 'game_objects': [{'name': "Pazuzu's Tower 7F - Pazuzu Chest", 'object_id': 26, 'type': 'Chest', 'access': []}, {'name': 'Pazuzu', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu'], 'access': ['Pazuzu2FLock', 'Pazuzu4FLock', 'Pazuzu6FLock', 'Pazuzu1F', 'Pazuzu2F', 'Pazuzu3F', 'Pazuzu4F', 'Pazuzu5F', 'Pazuzu6F']}], 'links': [{'target_room': 178, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 1F to 3F', 'id': 180, 'game_objects': [], 'links': [{'target_room': 166, 'entrance': 385, 'teleporter': [29, 0], 'access': []}, {'target_room': 170, 'entrance': 386, 'teleporter': [30, 0], 'access': []}]}, {'name': 'Pazuzu 3F to 5F', 'id': 181, 'game_objects': [], 'links': [{'target_room': 170, 'entrance': 387, 'teleporter': [40, 0], 'access': []}, {'target_room': 174, 'entrance': 388, 'teleporter': [41, 0], 'access': []}]}, {'name': 'Pazuzu 5F to 7F', 'id': 182, 'game_objects': [], 'links': [{'target_room': 174, 'entrance': 389, 'teleporter': [38, 0], 'access': []}, {'target_room': 177, 'entrance': 390, 'teleporter': [39, 0], 'access': []}]}, {'name': 'Pazuzu 2F to 4F', 'id': 183, 'game_objects': [], 'links': [{'target_room': 169, 'entrance': 391, 'teleporter': [21, 0], 'access': []}, {'target_room': 173, 'entrance': 392, 'teleporter': [22, 0], 'access': []}]}, {'name': 'Pazuzu 4F to 6F', 'id': 184, 'game_objects': [], 'links': [{'target_room': 173, 'entrance': 393, 'teleporter': [2, 0], 'access': []}, {'target_room': 176, 'entrance': 394, 'teleporter': [3, 0], 'access': []}]}, {'name': 'Light Temple', 'id': 185, 'game_objects': [{'name': 'Light Temple - Box', 'object_id': 216, 'type': 'Box', 'access': []}], 'links': [{'target_room': 230, 'entrance': 395, 'teleporter': [19, 6], 'access': []}, {'target_room': 153, 'entrance': 396, 'teleporter': [70, 8], 'access': ['MobiusCrest']}]}, {'name': 'Ship Dock', 'id': 186, 'game_objects': [{'name': 'Ship Dock Access', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipDockAccess'], 'access': []}], 'links': [{'target_room': 228, 'entrance': 399, 'teleporter': [17, 6], 'access': []}, {'target_room': 162, 'entrance': 397, 'teleporter': [61, 8], 'access': ['MobiusCrest']}]}, {'name': 'Mac Ship Deck', 'id': 187, 'game_objects': [{'name': 'Mac Ship Steering Wheel', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipSteeringWheel'], 'access': []}, {'name': "Mac's Ship Deck - North Box", 'object_id': 217, 'type': 'Box', 'access': []}, {'name': "Mac's Ship Deck - Center Box", 'object_id': 218, 'type': 'Box', 'access': []}, {'name': "Mac's Ship Deck - South Box", 'object_id': 219, 'type': 'Box', 'access': []}], 'links': [{'target_room': 229, 'entrance': 400, 'teleporter': [37, 8], 'access': []}, {'target_room': 188, 'entrance': 401, 'teleporter': [50, 8], 'access': []}, {'target_room': 188, 'entrance': 402, 'teleporter': [51, 8], 'access': []}, {'target_room': 188, 'entrance': 403, 'teleporter': [52, 8], 'access': []}, {'target_room': 189, 'entrance': 404, 'teleporter': [53, 8], 'access': []}]}, {'name': 'Mac Ship B1 Outer Ring', 'id': 188, 'game_objects': [{'name': "Mac's Ship B1 - Northwest Hook Platform Box", 'object_id': 228, 'type': 'Box', 'access': ['DragonClaw']}, {'name': "Mac's Ship B1 - Center Hook Platform Box", 'object_id': 229, 'type': 'Box', 'access': ['DragonClaw']}], 'links': [{'target_room': 187, 'entrance': 405, 'teleporter': [208, 0], 'access': []}, {'target_room': 187, 'entrance': 406, 'teleporter': [175, 0], 'access': []}, {'target_room': 187, 'entrance': 407, 'teleporter': [172, 0], 'access': []}, {'target_room': 193, 'entrance': 408, 'teleporter': [88, 0], 'access': []}, {'target_room': 193, 'access': []}]}, {'name': 'Mac Ship B1 Square Room', 'id': 189, 'game_objects': [], 'links': [{'target_room': 187, 'entrance': 409, 'teleporter': [141, 0], 'access': []}, {'target_room': 192, 'entrance': 410, 'teleporter': [87, 0], 'access': []}]}, {'name': 'Mac Ship B1 Central Corridor', 'id': 190, 'game_objects': [{'name': "Mac's Ship B1 - Central Corridor Box", 'object_id': 230, 'type': 'Box', 'access': []}], 'links': [{'target_room': 192, 'entrance': 413, 'teleporter': [86, 0], 'access': []}, {'target_room': 191, 'entrance': 412, 'teleporter': [102, 0], 'access': []}, {'target_room': 193, 'access': []}]}, {'name': 'Mac Ship B2 South Corridor', 'id': 191, 'game_objects': [], 'links': [{'target_room': 190, 'entrance': 415, 'teleporter': [55, 8], 'access': []}, {'target_room': 194, 'entrance': 414, 'teleporter': [57, 1], 'access': []}]}, {'name': 'Mac Ship B2 North Corridor', 'id': 192, 'game_objects': [], 'links': [{'target_room': 190, 'entrance': 416, 'teleporter': [56, 8], 'access': []}, {'target_room': 189, 'entrance': 417, 'teleporter': [57, 8], 'access': []}]}, {'name': 'Mac Ship B2 Outer Ring', 'id': 193, 'game_objects': [{'name': "Mac's Ship B2 - Barrel Room South Box", 'object_id': 223, 'type': 'Box', 'access': []}, {'name': "Mac's Ship B2 - Barrel Room North Box", 'object_id': 224, 'type': 'Box', 'access': []}, {'name': "Mac's Ship B2 - Southwest Room Box", 'object_id': 225, 'type': 'Box', 'access': []}, {'name': "Mac's Ship B2 - Southeast Room Box", 'object_id': 226, 'type': 'Box', 'access': []}], 'links': [{'target_room': 188, 'entrance': 418, 'teleporter': [58, 8], 'access': []}]}, {'name': 'Mac Ship B1 Mac Room', 'id': 194, 'game_objects': [{'name': "Mac's Ship B1 - Mac Room Chest", 'object_id': 27, 'type': 'Chest', 'access': []}, {'name': 'Captain Mac', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipLoaned'], 'access': ['CaptainCap']}], 'links': [{'target_room': 191, 'entrance': 424, 'teleporter': [101, 0], 'access': []}]}, {'name': 'Doom Castle Corridor of Destiny', 'id': 195, 'game_objects': [], 'links': [{'target_room': 201, 'entrance': 428, 'teleporter': [84, 0], 'access': []}, {'target_room': 196, 'entrance': 429, 'teleporter': [35, 2], 'access': []}, {'target_room': 197, 'entrance': 430, 'teleporter': [209, 0], 'access': ['StoneGolem']}, {'target_room': 198, 'entrance': 431, 'teleporter': [211, 0], 'access': ['StoneGolem', 'TwinheadWyvern']}, {'target_room': 199, 'entrance': 432, 'teleporter': [13, 2], 'access': ['StoneGolem', 'TwinheadWyvern', 'Zuh']}]}, {'name': 'Doom Castle Ice Floor', 'id': 196, 'game_objects': [{'name': 'Doom Castle 4F - Northwest Room Box', 'object_id': 231, 'type': 'Box', 'access': ['Sword', 'DragonClaw']}, {'name': 'Doom Castle 4F - Southwest Room Box', 'object_id': 232, 'type': 'Box', 'access': ['Sword', 'DragonClaw']}, {'name': 'Doom Castle 4F - Northeast Room Box', 'object_id': 233, 'type': 'Box', 'access': ['Sword']}, {'name': 'Doom Castle 4F - Southeast Room Box', 'object_id': 234, 'type': 'Box', 'access': ['Sword', 'DragonClaw']}, {'name': 'Stone Golem', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['StoneGolem'], 'access': ['Sword', 'DragonClaw']}], 'links': [{'target_room': 195, 'entrance': 433, 'teleporter': [109, 3], 'access': []}]}, {'name': 'Doom Castle Lava Floor', 'id': 197, 'game_objects': [{'name': 'Doom Castle 5F - North Left Box', 'object_id': 235, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Doom Castle 5F - North Right Box', 'object_id': 236, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Doom Castle 5F - South Left Box', 'object_id': 237, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Doom Castle 5F - South Right Box', 'object_id': 238, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Twinhead Wyvern', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['TwinheadWyvern'], 'access': ['DragonClaw']}], 'links': [{'target_room': 195, 'entrance': 434, 'teleporter': [210, 0], 'access': []}]}, {'name': 'Doom Castle Sky Floor', 'id': 198, 'game_objects': [{'name': 'Doom Castle 6F - West Box', 'object_id': 239, 'type': 'Box', 'access': []}, {'name': 'Doom Castle 6F - East Box', 'object_id': 240, 'type': 'Box', 'access': []}, {'name': 'Zuh', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Zuh'], 'access': ['DragonClaw']}], 'links': [{'target_room': 195, 'entrance': 435, 'teleporter': [212, 0], 'access': []}, {'target_room': 197, 'access': []}]}, {'name': 'Doom Castle Hero Room', 'id': 199, 'game_objects': [{'name': 'Doom Castle Hero Chest 01', 'object_id': 242, 'type': 'Chest', 'access': []}, {'name': 'Doom Castle Hero Chest 02', 'object_id': 243, 'type': 'Chest', 'access': []}, {'name': 'Doom Castle Hero Chest 03', 'object_id': 244, 'type': 'Chest', 'access': []}, {'name': 'Doom Castle Hero Chest 04', 'object_id': 245, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 200, 'entrance': 436, 'teleporter': [54, 0], 'access': []}, {'target_room': 195, 'entrance': 441, 'teleporter': [110, 3], 'access': []}]}, {'name': 'Doom Castle Dark King Room', 'id': 200, 'game_objects': [], 'links': [{'target_room': 199, 'entrance': 442, 'teleporter': [52, 0], 'access': []}]}] +entrances = [{'name': 'Doom Castle - Sand Floor - To Sky Door - Sand Floor', 'id': 0, 'area': 7, 'coordinates': [24, 19], 'teleporter': [0, 0]}, {'name': 'Doom Castle - Sand Floor - Main Entrance - Sand Floor', 'id': 1, 'area': 7, 'coordinates': [19, 43], 'teleporter': [1, 6]}, {'name': 'Doom Castle - Aero Room - Aero Room Entrance', 'id': 2, 'area': 7, 'coordinates': [27, 39], 'teleporter': [1, 0]}, {'name': 'Focus Tower B1 - Main Loop - South Entrance', 'id': 3, 'area': 8, 'coordinates': [43, 60], 'teleporter': [2, 6]}, {'name': 'Focus Tower B1 - Main Loop - To Focus Tower 1F - Main Hall', 'id': 4, 'area': 8, 'coordinates': [37, 41], 'teleporter': [4, 0]}, {'name': 'Focus Tower B1 - Aero Corridor - To Focus Tower 1F - Sun Coin Room', 'id': 5, 'area': 8, 'coordinates': [59, 35], 'teleporter': [5, 0]}, {'name': 'Focus Tower B1 - Aero Corridor - To Sand Floor - Aero Chest', 'id': 6, 'area': 8, 'coordinates': [57, 59], 'teleporter': [8, 0]}, {'name': 'Focus Tower B1 - Inner Loop - To Focus Tower 1F - Sky Door', 'id': 7, 'area': 8, 'coordinates': [51, 49], 'teleporter': [6, 0]}, {'name': 'Focus Tower B1 - Inner Loop - To Doom Castle Sand Floor', 'id': 8, 'area': 8, 'coordinates': [51, 45], 'teleporter': [7, 0]}, {'name': 'Focus Tower 1F - Focus Tower West Entrance', 'id': 9, 'area': 9, 'coordinates': [25, 29], 'teleporter': [3, 6]}, {'name': 'Focus Tower 1F - To Focus Tower 2F - From SandCoin', 'id': 10, 'area': 9, 'coordinates': [16, 4], 'teleporter': [10, 0]}, {'name': 'Focus Tower 1F - To Focus Tower B1 - Main Hall', 'id': 11, 'area': 9, 'coordinates': [4, 23], 'teleporter': [11, 0]}, {'name': 'Focus Tower 1F - To Focus Tower B1 - To Aero Chest', 'id': 12, 'area': 9, 'coordinates': [26, 17], 'teleporter': [12, 0]}, {'name': 'Focus Tower 1F - Sky Door', 'id': 13, 'area': 9, 'coordinates': [16, 24], 'teleporter': [13, 0]}, {'name': 'Focus Tower 1F - To Focus Tower 2F - From RiverCoin', 'id': 14, 'area': 9, 'coordinates': [16, 10], 'teleporter': [14, 0]}, {'name': 'Focus Tower 1F - To Focus Tower B1 - From Sky Door', 'id': 15, 'area': 9, 'coordinates': [16, 29], 'teleporter': [15, 0]}, {'name': 'Focus Tower 2F - Sand Coin Passage - North Entrance', 'id': 16, 'area': 10, 'coordinates': [49, 30], 'teleporter': [4, 6]}, {'name': 'Focus Tower 2F - Sand Coin Passage - To Focus Tower 1F - To SandCoin', 'id': 17, 'area': 10, 'coordinates': [47, 33], 'teleporter': [17, 0]}, {'name': 'Focus Tower 2F - River Coin Passage - To Focus Tower 1F - To RiverCoin', 'id': 18, 'area': 10, 'coordinates': [47, 41], 'teleporter': [18, 0]}, {'name': 'Focus Tower 2F - River Coin Passage - To Focus Tower 3F - Lower Floor', 'id': 19, 'area': 10, 'coordinates': [38, 40], 'teleporter': [20, 0]}, {'name': 'Focus Tower 2F - Venus Chest Room - To Focus Tower 3F - Upper Floor', 'id': 20, 'area': 10, 'coordinates': [56, 40], 'teleporter': [19, 0]}, {'name': 'Focus Tower 2F - Venus Chest Room - Pillar Script', 'id': 21, 'area': 10, 'coordinates': [48, 53], 'teleporter': [13, 8]}, {'name': 'Focus Tower 3F - Lower Floor - To Fireburg Entrance', 'id': 22, 'area': 11, 'coordinates': [11, 39], 'teleporter': [6, 6]}, {'name': 'Focus Tower 3F - Lower Floor - To Focus Tower 2F - Jump on Pillar', 'id': 23, 'area': 11, 'coordinates': [6, 47], 'teleporter': [24, 0]}, {'name': 'Focus Tower 3F - Upper Floor - To Aquaria Entrance', 'id': 24, 'area': 11, 'coordinates': [21, 38], 'teleporter': [5, 6]}, {'name': 'Focus Tower 3F - Upper Floor - To Focus Tower 2F - Venus Chest Room', 'id': 25, 'area': 11, 'coordinates': [24, 47], 'teleporter': [23, 0]}, {'name': 'Level Forest - Boulder Script', 'id': 26, 'area': 14, 'coordinates': [52, 15], 'teleporter': [0, 8]}, {'name': 'Level Forest - Rotten Tree Script', 'id': 27, 'area': 14, 'coordinates': [47, 6], 'teleporter': [2, 8]}, {'name': 'Level Forest - Exit Level Forest 1', 'id': 28, 'area': 14, 'coordinates': [46, 25], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 2', 'id': 29, 'area': 14, 'coordinates': [46, 26], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 3', 'id': 30, 'area': 14, 'coordinates': [47, 25], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 4', 'id': 31, 'area': 14, 'coordinates': [47, 26], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 5', 'id': 32, 'area': 14, 'coordinates': [60, 14], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 6', 'id': 33, 'area': 14, 'coordinates': [61, 14], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 7', 'id': 34, 'area': 14, 'coordinates': [46, 4], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 8', 'id': 35, 'area': 14, 'coordinates': [46, 3], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 9', 'id': 36, 'area': 14, 'coordinates': [47, 4], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest A', 'id': 37, 'area': 14, 'coordinates': [47, 3], 'teleporter': [25, 0]}, {'name': 'Foresta - Exit Foresta 1', 'id': 38, 'area': 15, 'coordinates': [10, 25], 'teleporter': [31, 0]}, {'name': 'Foresta - Exit Foresta 2', 'id': 39, 'area': 15, 'coordinates': [10, 26], 'teleporter': [31, 0]}, {'name': 'Foresta - Exit Foresta 3', 'id': 40, 'area': 15, 'coordinates': [11, 25], 'teleporter': [31, 0]}, {'name': 'Foresta - Exit Foresta 4', 'id': 41, 'area': 15, 'coordinates': [11, 26], 'teleporter': [31, 0]}, {'name': 'Foresta - Old Man House - Front Door', 'id': 42, 'area': 15, 'coordinates': [25, 17], 'teleporter': [32, 4]}, {'name': 'Foresta - Old Man House - Back Door', 'id': 43, 'area': 15, 'coordinates': [25, 14], 'teleporter': [33, 0]}, {'name': "Foresta - Kaeli's House", 'id': 44, 'area': 15, 'coordinates': [7, 21], 'teleporter': [0, 5]}, {'name': 'Foresta - Rest House', 'id': 45, 'area': 15, 'coordinates': [23, 23], 'teleporter': [1, 5]}, {'name': "Kaeli's House - Kaeli's House Entrance", 'id': 46, 'area': 16, 'coordinates': [11, 20], 'teleporter': [86, 3]}, {'name': "Foresta Houses - Old Man's House - Old Man Front Exit", 'id': 47, 'area': 17, 'coordinates': [35, 44], 'teleporter': [34, 0]}, {'name': "Foresta Houses - Old Man's House - Old Man Back Exit", 'id': 48, 'area': 17, 'coordinates': [35, 27], 'teleporter': [35, 0]}, {'name': 'Foresta - Old Man House - Barrel Tile Script', 'id': 483, 'area': 17, 'coordinates': [35, 30], 'teleporter': [13, 8]}, {'name': 'Foresta Houses - Rest House - Bed Script', 'id': 49, 'area': 17, 'coordinates': [30, 6], 'teleporter': [1, 8]}, {'name': 'Foresta Houses - Rest House - Rest House Exit', 'id': 50, 'area': 17, 'coordinates': [35, 20], 'teleporter': [87, 3]}, {'name': 'Foresta Houses - Libra House - Libra House Script', 'id': 51, 'area': 17, 'coordinates': [8, 49], 'teleporter': [67, 8]}, {'name': 'Foresta Houses - Gemini House - Gemini House Script', 'id': 52, 'area': 17, 'coordinates': [26, 55], 'teleporter': [68, 8]}, {'name': 'Foresta Houses - Mobius House - Mobius House Script', 'id': 53, 'area': 17, 'coordinates': [14, 33], 'teleporter': [69, 8]}, {'name': 'Sand Temple - Sand Temple Entrance', 'id': 54, 'area': 18, 'coordinates': [56, 27], 'teleporter': [36, 0]}, {'name': 'Bone Dungeon 1F - Bone Dungeon Entrance', 'id': 55, 'area': 19, 'coordinates': [13, 60], 'teleporter': [37, 0]}, {'name': 'Bone Dungeon 1F - To Bone Dungeon B1', 'id': 56, 'area': 19, 'coordinates': [13, 39], 'teleporter': [2, 2]}, {'name': 'Bone Dungeon B1 - Waterway - Exit Waterway', 'id': 57, 'area': 20, 'coordinates': [27, 39], 'teleporter': [3, 2]}, {'name': "Bone Dungeon B1 - Waterway - Tristam's Script", 'id': 58, 'area': 20, 'coordinates': [27, 45], 'teleporter': [3, 8]}, {'name': 'Bone Dungeon B1 - Waterway - To Bone Dungeon 1F', 'id': 59, 'area': 20, 'coordinates': [54, 61], 'teleporter': [88, 3]}, {'name': 'Bone Dungeon B1 - Checker Room - Exit Checker Room', 'id': 60, 'area': 20, 'coordinates': [23, 40], 'teleporter': [4, 2]}, {'name': 'Bone Dungeon B1 - Checker Room - To Waterway', 'id': 61, 'area': 20, 'coordinates': [39, 49], 'teleporter': [89, 3]}, {'name': 'Bone Dungeon B1 - Hidden Room - To B2 - Exploding Skull Room', 'id': 62, 'area': 20, 'coordinates': [5, 33], 'teleporter': [91, 3]}, {'name': 'Bonne Dungeon B2 - Exploding Skull Room - To Hidden Passage', 'id': 63, 'area': 21, 'coordinates': [19, 13], 'teleporter': [5, 2]}, {'name': 'Bonne Dungeon B2 - Exploding Skull Room - To Two Skulls Room', 'id': 64, 'area': 21, 'coordinates': [29, 15], 'teleporter': [6, 2]}, {'name': 'Bonne Dungeon B2 - Exploding Skull Room - To Checker Room', 'id': 65, 'area': 21, 'coordinates': [8, 25], 'teleporter': [90, 3]}, {'name': 'Bonne Dungeon B2 - Box Room - To B2 - Two Skulls Room', 'id': 66, 'area': 21, 'coordinates': [59, 12], 'teleporter': [93, 3]}, {'name': 'Bonne Dungeon B2 - Quake Room - To B2 - Two Skulls Room', 'id': 67, 'area': 21, 'coordinates': [59, 28], 'teleporter': [94, 3]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To Box Room', 'id': 68, 'area': 21, 'coordinates': [53, 7], 'teleporter': [7, 2]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To Quake Room', 'id': 69, 'area': 21, 'coordinates': [41, 3], 'teleporter': [8, 2]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To Boss Room', 'id': 70, 'area': 21, 'coordinates': [47, 57], 'teleporter': [9, 2]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To B2 - Exploding Skull Room', 'id': 71, 'area': 21, 'coordinates': [54, 23], 'teleporter': [92, 3]}, {'name': 'Bone Dungeon B2 - Boss Room - Flamerus Rex Script', 'id': 72, 'area': 22, 'coordinates': [29, 19], 'teleporter': [4, 8]}, {'name': 'Bone Dungeon B2 - Boss Room - Tristam Leave Script', 'id': 73, 'area': 22, 'coordinates': [29, 23], 'teleporter': [75, 8]}, {'name': 'Bone Dungeon B2 - Boss Room - To B2 - Two Skulls Room', 'id': 74, 'area': 22, 'coordinates': [30, 27], 'teleporter': [95, 3]}, {'name': 'Libra Temple - Entrance', 'id': 75, 'area': 23, 'coordinates': [10, 15], 'teleporter': [13, 6]}, {'name': 'Libra Temple - Libra Tile Script', 'id': 76, 'area': 23, 'coordinates': [9, 8], 'teleporter': [59, 8]}, {'name': 'Aquaria Winter - Winter Entrance 1', 'id': 77, 'area': 24, 'coordinates': [25, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Winter - Winter Entrance 2', 'id': 78, 'area': 24, 'coordinates': [25, 26], 'teleporter': [8, 6]}, {'name': 'Aquaria Winter - Winter Entrance 3', 'id': 79, 'area': 24, 'coordinates': [26, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Winter - Winter Entrance 4', 'id': 80, 'area': 24, 'coordinates': [26, 26], 'teleporter': [8, 6]}, {'name': "Aquaria Winter - Winter Phoebe's House Entrance Script", 'id': 81, 'area': 24, 'coordinates': [8, 19], 'teleporter': [10, 5]}, {'name': 'Aquaria Winter - Winter Vendor House Entrance', 'id': 82, 'area': 24, 'coordinates': [8, 5], 'teleporter': [44, 4]}, {'name': 'Aquaria Winter - Winter INN Entrance', 'id': 83, 'area': 24, 'coordinates': [26, 17], 'teleporter': [11, 5]}, {'name': 'Aquaria Summer - Summer Entrance 1', 'id': 84, 'area': 25, 'coordinates': [57, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Summer - Summer Entrance 2', 'id': 85, 'area': 25, 'coordinates': [57, 26], 'teleporter': [8, 6]}, {'name': 'Aquaria Summer - Summer Entrance 3', 'id': 86, 'area': 25, 'coordinates': [58, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Summer - Summer Entrance 4', 'id': 87, 'area': 25, 'coordinates': [58, 26], 'teleporter': [8, 6]}, {'name': "Aquaria Summer - Summer Phoebe's House Entrance", 'id': 88, 'area': 25, 'coordinates': [40, 19], 'teleporter': [10, 5]}, {'name': "Aquaria Summer - Spencer's Place Entrance Top", 'id': 89, 'area': 25, 'coordinates': [40, 16], 'teleporter': [42, 0]}, {'name': "Aquaria Summer - Spencer's Place Entrance Side", 'id': 90, 'area': 25, 'coordinates': [41, 18], 'teleporter': [43, 0]}, {'name': 'Aquaria Summer - Summer Vendor House Entrance', 'id': 91, 'area': 25, 'coordinates': [40, 5], 'teleporter': [44, 4]}, {'name': 'Aquaria Summer - Summer INN Entrance', 'id': 92, 'area': 25, 'coordinates': [58, 17], 'teleporter': [11, 5]}, {'name': "Phoebe's House - Entrance", 'id': 93, 'area': 26, 'coordinates': [29, 14], 'teleporter': [5, 8]}, {'name': "Aquaria Vendor House - Vendor House Entrance's Script", 'id': 94, 'area': 27, 'coordinates': [7, 10], 'teleporter': [40, 8]}, {'name': 'Aquaria Vendor House - Vendor House Stairs', 'id': 95, 'area': 27, 'coordinates': [1, 4], 'teleporter': [47, 0]}, {'name': 'Aquaria Gemini Room - Gemini Script', 'id': 96, 'area': 27, 'coordinates': [2, 40], 'teleporter': [72, 8]}, {'name': 'Aquaria Gemini Room - Gemini Room Stairs', 'id': 97, 'area': 27, 'coordinates': [4, 39], 'teleporter': [48, 0]}, {'name': 'Aquaria INN - Aquaria INN entrance', 'id': 98, 'area': 27, 'coordinates': [51, 46], 'teleporter': [75, 8]}, {'name': 'Wintry Cave 1F - Main Entrance', 'id': 99, 'area': 28, 'coordinates': [50, 58], 'teleporter': [49, 0]}, {'name': 'Wintry Cave 1F - To 3F Top', 'id': 100, 'area': 28, 'coordinates': [40, 25], 'teleporter': [14, 2]}, {'name': 'Wintry Cave 1F - To 2F', 'id': 101, 'area': 28, 'coordinates': [10, 43], 'teleporter': [15, 2]}, {'name': "Wintry Cave 1F - Phoebe's Script", 'id': 102, 'area': 28, 'coordinates': [44, 37], 'teleporter': [6, 8]}, {'name': 'Wintry Cave 2F - To 3F Bottom', 'id': 103, 'area': 29, 'coordinates': [58, 5], 'teleporter': [50, 0]}, {'name': 'Wintry Cave 2F - To 1F', 'id': 104, 'area': 29, 'coordinates': [38, 18], 'teleporter': [97, 3]}, {'name': 'Wintry Cave 3F Top - Exit from 3F Top', 'id': 105, 'area': 30, 'coordinates': [24, 6], 'teleporter': [96, 3]}, {'name': 'Wintry Cave 3F Bottom - Exit to 2F', 'id': 106, 'area': 31, 'coordinates': [4, 29], 'teleporter': [51, 0]}, {'name': 'Life Temple - Entrance', 'id': 107, 'area': 32, 'coordinates': [9, 60], 'teleporter': [14, 6]}, {'name': 'Life Temple - Libra Tile Script', 'id': 108, 'area': 32, 'coordinates': [3, 55], 'teleporter': [60, 8]}, {'name': 'Life Temple - Mysterious Man Script', 'id': 109, 'area': 32, 'coordinates': [9, 44], 'teleporter': [78, 8]}, {'name': 'Fall Basin - Back Exit Script', 'id': 110, 'area': 33, 'coordinates': [17, 5], 'teleporter': [9, 0]}, {'name': 'Fall Basin - Main Exit', 'id': 111, 'area': 33, 'coordinates': [15, 26], 'teleporter': [53, 0]}, {'name': "Fall Basin - Phoebe's Script", 'id': 112, 'area': 33, 'coordinates': [17, 6], 'teleporter': [9, 8]}, {'name': 'Ice Pyramid B1 Taunt Room - To Climbing Wall Room', 'id': 113, 'area': 34, 'coordinates': [43, 6], 'teleporter': [55, 0]}, {'name': 'Ice Pyramid 1F Maze - Main Entrance 1', 'id': 114, 'area': 35, 'coordinates': [18, 36], 'teleporter': [56, 0]}, {'name': 'Ice Pyramid 1F Maze - Main Entrance 2', 'id': 115, 'area': 35, 'coordinates': [19, 36], 'teleporter': [56, 0]}, {'name': 'Ice Pyramid 1F Maze - West Stairs To 2F South Tiled Room', 'id': 116, 'area': 35, 'coordinates': [3, 27], 'teleporter': [57, 0]}, {'name': 'Ice Pyramid 1F Maze - West Center Stairs to 2F West Room', 'id': 117, 'area': 35, 'coordinates': [11, 15], 'teleporter': [58, 0]}, {'name': 'Ice Pyramid 1F Maze - East Center Stairs to 2F Center Room', 'id': 118, 'area': 35, 'coordinates': [25, 16], 'teleporter': [59, 0]}, {'name': 'Ice Pyramid 1F Maze - Upper Stairs to 2F Small North Room', 'id': 119, 'area': 35, 'coordinates': [31, 1], 'teleporter': [60, 0]}, {'name': 'Ice Pyramid 1F Maze - East Stairs to 2F North Corridor', 'id': 120, 'area': 35, 'coordinates': [34, 9], 'teleporter': [61, 0]}, {'name': "Ice Pyramid 1F Maze - Statue's Script", 'id': 121, 'area': 35, 'coordinates': [21, 32], 'teleporter': [77, 8]}, {'name': 'Ice Pyramid 2F South Tiled Room - To 1F', 'id': 122, 'area': 36, 'coordinates': [4, 26], 'teleporter': [62, 0]}, {'name': 'Ice Pyramid 2F South Tiled Room - To 3F Two Boxes Room', 'id': 123, 'area': 36, 'coordinates': [22, 17], 'teleporter': [67, 0]}, {'name': 'Ice Pyramid 2F West Room - To 1F', 'id': 124, 'area': 36, 'coordinates': [9, 10], 'teleporter': [63, 0]}, {'name': 'Ice Pyramid 2F Center Room - To 1F', 'id': 125, 'area': 36, 'coordinates': [22, 14], 'teleporter': [64, 0]}, {'name': 'Ice Pyramid 2F Small North Room - To 1F', 'id': 126, 'area': 36, 'coordinates': [26, 4], 'teleporter': [65, 0]}, {'name': 'Ice Pyramid 2F North Corridor - To 1F', 'id': 127, 'area': 36, 'coordinates': [32, 8], 'teleporter': [66, 0]}, {'name': 'Ice Pyramid 2F North Corridor - To 3F Main Loop', 'id': 128, 'area': 36, 'coordinates': [12, 7], 'teleporter': [68, 0]}, {'name': 'Ice Pyramid 3F Two Boxes Room - To 2F South Tiled Room', 'id': 129, 'area': 37, 'coordinates': [24, 54], 'teleporter': [69, 0]}, {'name': 'Ice Pyramid 3F Main Loop - To 2F Corridor', 'id': 130, 'area': 37, 'coordinates': [16, 45], 'teleporter': [70, 0]}, {'name': 'Ice Pyramid 3F Main Loop - To 4F', 'id': 131, 'area': 37, 'coordinates': [19, 43], 'teleporter': [71, 0]}, {'name': 'Ice Pyramid 4F Treasure Room - To 3F Main Loop', 'id': 132, 'area': 38, 'coordinates': [52, 5], 'teleporter': [72, 0]}, {'name': 'Ice Pyramid 4F Treasure Room - To 5F Leap of Faith Room', 'id': 133, 'area': 38, 'coordinates': [62, 19], 'teleporter': [73, 0]}, {'name': 'Ice Pyramid 5F Leap of Faith Room - To 4F Treasure Room', 'id': 134, 'area': 39, 'coordinates': [54, 63], 'teleporter': [74, 0]}, {'name': 'Ice Pyramid 5F Leap of Faith Room - Bombed Ice Plate', 'id': 135, 'area': 39, 'coordinates': [47, 54], 'teleporter': [77, 8]}, {'name': 'Ice Pyramid 5F Stairs to Ice Golem - To Ice Golem Room', 'id': 136, 'area': 39, 'coordinates': [39, 43], 'teleporter': [75, 0]}, {'name': 'Ice Pyramid 5F Stairs to Ice Golem - To Climbing Wall Room', 'id': 137, 'area': 39, 'coordinates': [39, 60], 'teleporter': [76, 0]}, {'name': 'Ice Pyramid - Duplicate Ice Golem Room', 'id': 138, 'area': 40, 'coordinates': [44, 43], 'teleporter': [77, 0]}, {'name': 'Ice Pyramid Climbing Wall Room - To Taunt Room', 'id': 139, 'area': 41, 'coordinates': [4, 59], 'teleporter': [78, 0]}, {'name': 'Ice Pyramid Climbing Wall Room - To 5F Stairs', 'id': 140, 'area': 41, 'coordinates': [4, 45], 'teleporter': [79, 0]}, {'name': 'Ice Pyramid Ice Golem Room - To 5F Stairs', 'id': 141, 'area': 42, 'coordinates': [44, 43], 'teleporter': [80, 0]}, {'name': 'Ice Pyramid Ice Golem Room - Ice Golem Script', 'id': 142, 'area': 42, 'coordinates': [53, 32], 'teleporter': [10, 8]}, {'name': 'Spencer Waterfall - To Spencer Cave', 'id': 143, 'area': 43, 'coordinates': [48, 57], 'teleporter': [81, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 1', 'id': 144, 'area': 43, 'coordinates': [40, 5], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 2', 'id': 145, 'area': 43, 'coordinates': [40, 6], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 3', 'id': 146, 'area': 43, 'coordinates': [41, 5], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 4', 'id': 147, 'area': 43, 'coordinates': [41, 6], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Right Exit to Aquaria 1', 'id': 148, 'area': 43, 'coordinates': [46, 8], 'teleporter': [83, 0]}, {'name': 'Spencer Waterfall - Right Exit to Aquaria 2', 'id': 149, 'area': 43, 'coordinates': [47, 8], 'teleporter': [83, 0]}, {'name': 'Spencer Cave Normal Main - To Waterfall', 'id': 150, 'area': 44, 'coordinates': [14, 39], 'teleporter': [85, 0]}, {'name': 'Spencer Cave Normal From Overworld - Exit to Overworld', 'id': 151, 'area': 44, 'coordinates': [15, 57], 'teleporter': [7, 6]}, {'name': 'Spencer Cave Unplug - Exit to Overworld', 'id': 152, 'area': 45, 'coordinates': [40, 29], 'teleporter': [7, 6]}, {'name': 'Spencer Cave Unplug - Libra Teleporter Start Script', 'id': 153, 'area': 45, 'coordinates': [28, 21], 'teleporter': [33, 8]}, {'name': 'Spencer Cave Unplug - Libra Teleporter End Script', 'id': 154, 'area': 45, 'coordinates': [46, 4], 'teleporter': [34, 8]}, {'name': 'Spencer Cave Unplug - Mobius Teleporter Chest Script', 'id': 155, 'area': 45, 'coordinates': [21, 9], 'teleporter': [35, 8]}, {'name': 'Spencer Cave Unplug - Mobius Teleporter Start Script', 'id': 156, 'area': 45, 'coordinates': [29, 28], 'teleporter': [36, 8]}, {'name': 'Wintry Temple Outer Room - Main Entrance', 'id': 157, 'area': 46, 'coordinates': [8, 31], 'teleporter': [15, 6]}, {'name': 'Wintry Temple Inner Room - Gemini Tile to Sealed temple', 'id': 158, 'area': 46, 'coordinates': [9, 24], 'teleporter': [62, 8]}, {'name': 'Fireburg - To Overworld', 'id': 159, 'area': 47, 'coordinates': [4, 13], 'teleporter': [9, 6]}, {'name': 'Fireburg - To Overworld', 'id': 160, 'area': 47, 'coordinates': [5, 13], 'teleporter': [9, 6]}, {'name': 'Fireburg - To Overworld', 'id': 161, 'area': 47, 'coordinates': [28, 15], 'teleporter': [9, 6]}, {'name': 'Fireburg - To Overworld', 'id': 162, 'area': 47, 'coordinates': [27, 15], 'teleporter': [9, 6]}, {'name': 'Fireburg - Vendor House', 'id': 163, 'area': 47, 'coordinates': [10, 24], 'teleporter': [91, 0]}, {'name': 'Fireburg - Reuben House', 'id': 164, 'area': 47, 'coordinates': [14, 6], 'teleporter': [98, 8]}, {'name': 'Fireburg - Hotel', 'id': 165, 'area': 47, 'coordinates': [20, 8], 'teleporter': [96, 8]}, {'name': 'Fireburg - GrenadeMan House Script', 'id': 166, 'area': 47, 'coordinates': [12, 18], 'teleporter': [11, 8]}, {'name': 'Reuben House - Main Entrance', 'id': 167, 'area': 48, 'coordinates': [33, 46], 'teleporter': [98, 3]}, {'name': 'GrenadeMan House - Entrance Script', 'id': 168, 'area': 49, 'coordinates': [55, 60], 'teleporter': [9, 8]}, {'name': 'GrenadeMan House - To Mobius Crest Room', 'id': 169, 'area': 49, 'coordinates': [57, 52], 'teleporter': [93, 0]}, {'name': 'GrenadeMan Mobius Room - Stairs to House', 'id': 170, 'area': 49, 'coordinates': [39, 26], 'teleporter': [94, 0]}, {'name': 'GrenadeMan Mobius Room - Mobius Teleporter Script', 'id': 171, 'area': 49, 'coordinates': [39, 23], 'teleporter': [54, 8]}, {'name': 'Fireburg Vendor House - Entrance Script', 'id': 172, 'area': 49, 'coordinates': [7, 10], 'teleporter': [95, 0]}, {'name': 'Fireburg Vendor House - Stairs to Gemini Room', 'id': 173, 'area': 49, 'coordinates': [1, 4], 'teleporter': [96, 0]}, {'name': 'Fireburg Gemini Room - Stairs to Vendor House', 'id': 174, 'area': 49, 'coordinates': [4, 39], 'teleporter': [97, 0]}, {'name': 'Fireburg Gemini Room - Gemini Teleporter Script', 'id': 175, 'area': 49, 'coordinates': [2, 40], 'teleporter': [45, 8]}, {'name': 'Fireburg Hotel Lobby - Stairs to beds', 'id': 176, 'area': 49, 'coordinates': [4, 50], 'teleporter': [213, 0]}, {'name': 'Fireburg Hotel Lobby - Entrance', 'id': 177, 'area': 49, 'coordinates': [17, 56], 'teleporter': [99, 3]}, {'name': 'Fireburg Hotel Beds - Stairs to Hotel Lobby', 'id': 178, 'area': 49, 'coordinates': [45, 59], 'teleporter': [214, 0]}, {'name': 'Mine Exterior - Main Entrance', 'id': 179, 'area': 50, 'coordinates': [5, 28], 'teleporter': [98, 0]}, {'name': 'Mine Exterior - To Cliff', 'id': 180, 'area': 50, 'coordinates': [58, 29], 'teleporter': [99, 0]}, {'name': 'Mine Exterior - To Parallel Room', 'id': 181, 'area': 50, 'coordinates': [8, 7], 'teleporter': [20, 2]}, {'name': 'Mine Exterior - To Crescent Room', 'id': 182, 'area': 50, 'coordinates': [26, 15], 'teleporter': [21, 2]}, {'name': 'Mine Exterior - To Climbing Room', 'id': 183, 'area': 50, 'coordinates': [21, 35], 'teleporter': [22, 2]}, {'name': 'Mine Exterior - Jinn Fight Script', 'id': 184, 'area': 50, 'coordinates': [58, 31], 'teleporter': [74, 8]}, {'name': 'Mine Parallel Room - To Mine Exterior', 'id': 185, 'area': 51, 'coordinates': [7, 60], 'teleporter': [100, 3]}, {'name': 'Mine Crescent Room - To Mine Exterior', 'id': 186, 'area': 51, 'coordinates': [22, 61], 'teleporter': [101, 3]}, {'name': 'Mine Climbing Room - To Mine Exterior', 'id': 187, 'area': 51, 'coordinates': [56, 21], 'teleporter': [102, 3]}, {'name': 'Mine Cliff - Entrance', 'id': 188, 'area': 52, 'coordinates': [9, 5], 'teleporter': [100, 0]}, {'name': 'Mine Cliff - Reuben Grenade Script', 'id': 189, 'area': 52, 'coordinates': [15, 7], 'teleporter': [12, 8]}, {'name': 'Sealed Temple - To Overworld', 'id': 190, 'area': 53, 'coordinates': [58, 43], 'teleporter': [16, 6]}, {'name': 'Sealed Temple - Gemini Tile Script', 'id': 191, 'area': 53, 'coordinates': [56, 38], 'teleporter': [63, 8]}, {'name': 'Volcano Base - Main Entrance 1', 'id': 192, 'area': 54, 'coordinates': [23, 25], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Main Entrance 2', 'id': 193, 'area': 54, 'coordinates': [23, 26], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Main Entrance 3', 'id': 194, 'area': 54, 'coordinates': [24, 25], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Main Entrance 4', 'id': 195, 'area': 54, 'coordinates': [24, 26], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Left Stairs Script', 'id': 196, 'area': 54, 'coordinates': [20, 5], 'teleporter': [31, 8]}, {'name': 'Volcano Base - Right Stairs Script', 'id': 197, 'area': 54, 'coordinates': [32, 5], 'teleporter': [30, 8]}, {'name': 'Volcano Top Right - Top Exit', 'id': 198, 'area': 55, 'coordinates': [44, 8], 'teleporter': [9, 0]}, {'name': 'Volcano Top Left - To Right-Left Path Script', 'id': 199, 'area': 55, 'coordinates': [40, 24], 'teleporter': [26, 8]}, {'name': 'Volcano Top Right - To Left-Right Path Script', 'id': 200, 'area': 55, 'coordinates': [52, 24], 'teleporter': [79, 8]}, {'name': 'Volcano Right Path - To Volcano Base Script', 'id': 201, 'area': 56, 'coordinates': [48, 42], 'teleporter': [15, 8]}, {'name': 'Volcano Left Path - To Volcano Cross Left-Right', 'id': 202, 'area': 56, 'coordinates': [40, 31], 'teleporter': [25, 2]}, {'name': 'Volcano Left Path - To Volcano Cross Right-Left', 'id': 203, 'area': 56, 'coordinates': [52, 29], 'teleporter': [26, 2]}, {'name': 'Volcano Left Path - To Volcano Base Script', 'id': 204, 'area': 56, 'coordinates': [36, 42], 'teleporter': [27, 8]}, {'name': 'Volcano Cross Left-Right - To Volcano Left Path', 'id': 205, 'area': 56, 'coordinates': [10, 42], 'teleporter': [103, 3]}, {'name': 'Volcano Cross Left-Right - To Volcano Top Right Script', 'id': 206, 'area': 56, 'coordinates': [16, 24], 'teleporter': [29, 8]}, {'name': 'Volcano Cross Right-Left - To Volcano Top Left Script', 'id': 207, 'area': 56, 'coordinates': [8, 22], 'teleporter': [28, 8]}, {'name': 'Volcano Cross Right-Left - To Volcano Left Path', 'id': 208, 'area': 56, 'coordinates': [16, 42], 'teleporter': [104, 3]}, {'name': 'Lava Dome Inner Ring Main Loop - Main Entrance 1', 'id': 209, 'area': 57, 'coordinates': [32, 5], 'teleporter': [104, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - Main Entrance 2', 'id': 210, 'area': 57, 'coordinates': [33, 5], 'teleporter': [104, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Three Steps Room', 'id': 211, 'area': 57, 'coordinates': [14, 5], 'teleporter': [105, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Life Chest Room Lower', 'id': 212, 'area': 57, 'coordinates': [40, 17], 'teleporter': [106, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Big Jump Room Left', 'id': 213, 'area': 57, 'coordinates': [8, 11], 'teleporter': [108, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Split Corridor Room', 'id': 214, 'area': 57, 'coordinates': [11, 19], 'teleporter': [111, 0]}, {'name': 'Lava Dome Inner Ring Center Ledge - To Life Chest Room Higher', 'id': 215, 'area': 57, 'coordinates': [32, 11], 'teleporter': [107, 0]}, {'name': 'Lava Dome Inner Ring Plate Ledge - To Plate Corridor', 'id': 216, 'area': 57, 'coordinates': [12, 23], 'teleporter': [109, 0]}, {'name': 'Lava Dome Inner Ring Plate Ledge - Plate Script', 'id': 217, 'area': 57, 'coordinates': [5, 23], 'teleporter': [47, 8]}, {'name': 'Lava Dome Inner Ring Upper Ledges - To Pointless Room', 'id': 218, 'area': 57, 'coordinates': [0, 9], 'teleporter': [110, 0]}, {'name': 'Lava Dome Inner Ring Upper Ledges - To Lower Moon Helm Room', 'id': 219, 'area': 57, 'coordinates': [0, 15], 'teleporter': [112, 0]}, {'name': 'Lava Dome Inner Ring Upper Ledges - To Up-Down Corridor', 'id': 220, 'area': 57, 'coordinates': [54, 5], 'teleporter': [113, 0]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - To Jumping Maze II', 'id': 221, 'area': 57, 'coordinates': [54, 21], 'teleporter': [114, 0]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 1', 'id': 222, 'area': 57, 'coordinates': [62, 20], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 2', 'id': 223, 'area': 57, 'coordinates': [63, 20], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 3', 'id': 224, 'area': 57, 'coordinates': [62, 21], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 4', 'id': 225, 'area': 57, 'coordinates': [63, 21], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Tiny Bottom Ledge - To Four Boxes Corridor', 'id': 226, 'area': 57, 'coordinates': [50, 25], 'teleporter': [115, 0]}, {'name': 'Lava Dome Jump Maze II - Lower Right Entrance', 'id': 227, 'area': 58, 'coordinates': [55, 28], 'teleporter': [116, 0]}, {'name': 'Lava Dome Jump Maze II - Upper Entrance', 'id': 228, 'area': 58, 'coordinates': [35, 3], 'teleporter': [119, 0]}, {'name': 'Lava Dome Jump Maze II - Lower Left Entrance', 'id': 229, 'area': 58, 'coordinates': [34, 27], 'teleporter': [120, 0]}, {'name': 'Lava Dome Up-Down Corridor - Upper Entrance', 'id': 230, 'area': 58, 'coordinates': [29, 8], 'teleporter': [117, 0]}, {'name': 'Lava Dome Up-Down Corridor - Lower Entrance', 'id': 231, 'area': 58, 'coordinates': [28, 25], 'teleporter': [118, 0]}, {'name': 'Lava Dome Jump Maze I - South Entrance', 'id': 232, 'area': 59, 'coordinates': [20, 27], 'teleporter': [121, 0]}, {'name': 'Lava Dome Jump Maze I - North Entrance', 'id': 233, 'area': 59, 'coordinates': [7, 3], 'teleporter': [122, 0]}, {'name': 'Lava Dome Pointless Room - Entrance', 'id': 234, 'area': 60, 'coordinates': [2, 7], 'teleporter': [123, 0]}, {'name': 'Lava Dome Pointless Room - Visit Quest Script 1', 'id': 490, 'area': 60, 'coordinates': [4, 4], 'teleporter': [99, 8]}, {'name': 'Lava Dome Pointless Room - Visit Quest Script 2', 'id': 491, 'area': 60, 'coordinates': [4, 5], 'teleporter': [99, 8]}, {'name': 'Lava Dome Lower Moon Helm Room - Left Entrance', 'id': 235, 'area': 60, 'coordinates': [2, 19], 'teleporter': [124, 0]}, {'name': 'Lava Dome Lower Moon Helm Room - Right Entrance', 'id': 236, 'area': 60, 'coordinates': [11, 21], 'teleporter': [125, 0]}, {'name': 'Lava Dome Moon Helm Room - Entrance', 'id': 237, 'area': 60, 'coordinates': [15, 23], 'teleporter': [126, 0]}, {'name': 'Lava Dome Three Jumps Room - To Main Loop', 'id': 238, 'area': 61, 'coordinates': [58, 15], 'teleporter': [127, 0]}, {'name': 'Lava Dome Life Chest Room - Lower South Entrance', 'id': 239, 'area': 61, 'coordinates': [38, 27], 'teleporter': [128, 0]}, {'name': 'Lava Dome Life Chest Room - Upper South Entrance', 'id': 240, 'area': 61, 'coordinates': [28, 23], 'teleporter': [129, 0]}, {'name': 'Lava Dome Big Jump Room - Left Entrance', 'id': 241, 'area': 62, 'coordinates': [42, 51], 'teleporter': [133, 0]}, {'name': 'Lava Dome Big Jump Room - North Entrance', 'id': 242, 'area': 62, 'coordinates': [30, 29], 'teleporter': [131, 0]}, {'name': 'Lava Dome Big Jump Room - Lower Right Stairs', 'id': 243, 'area': 62, 'coordinates': [61, 59], 'teleporter': [132, 0]}, {'name': 'Lava Dome Split Corridor - Upper Stairs', 'id': 244, 'area': 62, 'coordinates': [30, 43], 'teleporter': [130, 0]}, {'name': 'Lava Dome Split Corridor - Lower Stairs', 'id': 245, 'area': 62, 'coordinates': [36, 61], 'teleporter': [134, 0]}, {'name': 'Lava Dome Plate Corridor - Right Entrance', 'id': 246, 'area': 63, 'coordinates': [19, 29], 'teleporter': [135, 0]}, {'name': 'Lava Dome Plate Corridor - Left Entrance', 'id': 247, 'area': 63, 'coordinates': [60, 21], 'teleporter': [137, 0]}, {'name': 'Lava Dome Four Boxes Stairs - Upper Entrance', 'id': 248, 'area': 63, 'coordinates': [22, 3], 'teleporter': [136, 0]}, {'name': 'Lava Dome Four Boxes Stairs - Lower Entrance', 'id': 249, 'area': 63, 'coordinates': [22, 17], 'teleporter': [16, 0]}, {'name': 'Lava Dome Hydra Room - South Entrance', 'id': 250, 'area': 64, 'coordinates': [14, 59], 'teleporter': [105, 3]}, {'name': 'Lava Dome Hydra Room - North Exit', 'id': 251, 'area': 64, 'coordinates': [25, 31], 'teleporter': [138, 0]}, {'name': 'Lava Dome Hydra Room - Hydra Script', 'id': 252, 'area': 64, 'coordinates': [14, 36], 'teleporter': [14, 8]}, {'name': 'Lava Dome Escape Corridor - South Entrance', 'id': 253, 'area': 65, 'coordinates': [22, 17], 'teleporter': [139, 0]}, {'name': 'Lava Dome Escape Corridor - North Entrance', 'id': 254, 'area': 65, 'coordinates': [22, 3], 'teleporter': [9, 0]}, {'name': 'Rope Bridge - West Entrance 1', 'id': 255, 'area': 66, 'coordinates': [3, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 2', 'id': 256, 'area': 66, 'coordinates': [3, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 3', 'id': 257, 'area': 66, 'coordinates': [3, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 4', 'id': 258, 'area': 66, 'coordinates': [3, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 5', 'id': 259, 'area': 66, 'coordinates': [4, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 6', 'id': 260, 'area': 66, 'coordinates': [4, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 7', 'id': 261, 'area': 66, 'coordinates': [4, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 8', 'id': 262, 'area': 66, 'coordinates': [4, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 1', 'id': 263, 'area': 66, 'coordinates': [59, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 2', 'id': 264, 'area': 66, 'coordinates': [59, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 3', 'id': 265, 'area': 66, 'coordinates': [59, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 4', 'id': 266, 'area': 66, 'coordinates': [59, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 5', 'id': 267, 'area': 66, 'coordinates': [60, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 6', 'id': 268, 'area': 66, 'coordinates': [60, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 7', 'id': 269, 'area': 66, 'coordinates': [60, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 8', 'id': 270, 'area': 66, 'coordinates': [60, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - Reuben Fall Script', 'id': 271, 'area': 66, 'coordinates': [13, 12], 'teleporter': [15, 8]}, {'name': 'Alive Forest - West Entrance 1', 'id': 272, 'area': 67, 'coordinates': [8, 13], 'teleporter': [142, 0]}, {'name': 'Alive Forest - West Entrance 2', 'id': 273, 'area': 67, 'coordinates': [9, 13], 'teleporter': [142, 0]}, {'name': 'Alive Forest - Giant Tree Entrance', 'id': 274, 'area': 67, 'coordinates': [42, 42], 'teleporter': [143, 0]}, {'name': 'Alive Forest - Libra Teleporter Script', 'id': 275, 'area': 67, 'coordinates': [8, 52], 'teleporter': [64, 8]}, {'name': 'Alive Forest - Gemini Teleporter Script', 'id': 276, 'area': 67, 'coordinates': [57, 49], 'teleporter': [65, 8]}, {'name': 'Alive Forest - Mobius Teleporter Script', 'id': 277, 'area': 67, 'coordinates': [24, 10], 'teleporter': [66, 8]}, {'name': 'Giant Tree 1F - Entrance Script 1', 'id': 278, 'area': 68, 'coordinates': [18, 31], 'teleporter': [56, 1]}, {'name': 'Giant Tree 1F - Entrance Script 2', 'id': 279, 'area': 68, 'coordinates': [19, 31], 'teleporter': [56, 1]}, {'name': 'Giant Tree 1F - North Entrance To 2F', 'id': 280, 'area': 68, 'coordinates': [16, 1], 'teleporter': [144, 0]}, {'name': 'Giant Tree 2F Main Lobby - North Entrance to 1F', 'id': 281, 'area': 69, 'coordinates': [44, 33], 'teleporter': [145, 0]}, {'name': 'Giant Tree 2F Main Lobby - Central Entrance to 3F', 'id': 282, 'area': 69, 'coordinates': [42, 47], 'teleporter': [146, 0]}, {'name': 'Giant Tree 2F Main Lobby - West Entrance to Mushroom Room', 'id': 283, 'area': 69, 'coordinates': [58, 49], 'teleporter': [149, 0]}, {'name': 'Giant Tree 2F West Ledge - To 3F Northwest Ledge', 'id': 284, 'area': 69, 'coordinates': [34, 37], 'teleporter': [147, 0]}, {'name': 'Giant Tree 2F Fall From Vine Script', 'id': 482, 'area': 69, 'coordinates': [46, 51], 'teleporter': [76, 8]}, {'name': 'Giant Tree Meteor Chest Room - To 2F Mushroom Room', 'id': 285, 'area': 69, 'coordinates': [58, 44], 'teleporter': [148, 0]}, {'name': 'Giant Tree 2F Mushroom Room - Entrance', 'id': 286, 'area': 70, 'coordinates': [55, 18], 'teleporter': [150, 0]}, {'name': 'Giant Tree 2F Mushroom Room - North Face to Meteor', 'id': 287, 'area': 70, 'coordinates': [56, 7], 'teleporter': [151, 0]}, {'name': 'Giant Tree 3F Central Room - Central Entrance to 2F', 'id': 288, 'area': 71, 'coordinates': [46, 53], 'teleporter': [152, 0]}, {'name': 'Giant Tree 3F Central Room - East Entrance to Worm Room', 'id': 289, 'area': 71, 'coordinates': [58, 39], 'teleporter': [153, 0]}, {'name': 'Giant Tree 3F Lower Corridor - Entrance from Worm Room', 'id': 290, 'area': 71, 'coordinates': [45, 39], 'teleporter': [154, 0]}, {'name': 'Giant Tree 3F West Platform - Lower Entrance', 'id': 291, 'area': 71, 'coordinates': [33, 43], 'teleporter': [155, 0]}, {'name': 'Giant Tree 3F West Platform - Top Entrance', 'id': 292, 'area': 71, 'coordinates': [52, 25], 'teleporter': [156, 0]}, {'name': 'Giant Tree Worm Room - East Entrance', 'id': 293, 'area': 72, 'coordinates': [20, 58], 'teleporter': [157, 0]}, {'name': 'Giant Tree Worm Room - West Entrance', 'id': 294, 'area': 72, 'coordinates': [6, 56], 'teleporter': [158, 0]}, {'name': 'Giant Tree 4F Lower Floor - Entrance', 'id': 295, 'area': 73, 'coordinates': [20, 7], 'teleporter': [159, 0]}, {'name': 'Giant Tree 4F Lower Floor - Lower West Mouth', 'id': 296, 'area': 73, 'coordinates': [8, 23], 'teleporter': [160, 0]}, {'name': 'Giant Tree 4F Lower Floor - Lower Central Mouth', 'id': 297, 'area': 73, 'coordinates': [14, 25], 'teleporter': [161, 0]}, {'name': 'Giant Tree 4F Lower Floor - Lower East Mouth', 'id': 298, 'area': 73, 'coordinates': [20, 25], 'teleporter': [162, 0]}, {'name': 'Giant Tree 4F Upper Floor - Upper West Mouth', 'id': 299, 'area': 73, 'coordinates': [8, 19], 'teleporter': [163, 0]}, {'name': 'Giant Tree 4F Upper Floor - Upper Central Mouth', 'id': 300, 'area': 73, 'coordinates': [12, 17], 'teleporter': [164, 0]}, {'name': 'Giant Tree 4F Slime Room - Exit', 'id': 301, 'area': 74, 'coordinates': [47, 10], 'teleporter': [165, 0]}, {'name': 'Giant Tree 4F Slime Room - West Entrance', 'id': 302, 'area': 74, 'coordinates': [45, 24], 'teleporter': [166, 0]}, {'name': 'Giant Tree 4F Slime Room - Central Entrance', 'id': 303, 'area': 74, 'coordinates': [50, 24], 'teleporter': [167, 0]}, {'name': 'Giant Tree 4F Slime Room - East Entrance', 'id': 304, 'area': 74, 'coordinates': [57, 28], 'teleporter': [168, 0]}, {'name': 'Giant Tree 5F - Entrance', 'id': 305, 'area': 75, 'coordinates': [14, 51], 'teleporter': [169, 0]}, {'name': 'Giant Tree 5F - Giant Tree Face', 'id': 306, 'area': 75, 'coordinates': [14, 37], 'teleporter': [170, 0]}, {'name': 'Kaidge Temple - Entrance', 'id': 307, 'area': 77, 'coordinates': [44, 63], 'teleporter': [18, 6]}, {'name': 'Kaidge Temple - Mobius Teleporter Script', 'id': 308, 'area': 77, 'coordinates': [35, 57], 'teleporter': [71, 8]}, {'name': 'Windhole Temple - Entrance', 'id': 309, 'area': 78, 'coordinates': [10, 29], 'teleporter': [173, 0]}, {'name': 'Mount Gale - Entrance 1', 'id': 310, 'area': 79, 'coordinates': [1, 45], 'teleporter': [174, 0]}, {'name': 'Mount Gale - Entrance 2', 'id': 311, 'area': 79, 'coordinates': [2, 45], 'teleporter': [174, 0]}, {'name': 'Mount Gale - Visit Quest', 'id': 494, 'area': 79, 'coordinates': [44, 7], 'teleporter': [101, 8]}, {'name': 'Windia - Main Entrance 1', 'id': 312, 'area': 80, 'coordinates': [12, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 2', 'id': 313, 'area': 80, 'coordinates': [13, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 3', 'id': 314, 'area': 80, 'coordinates': [14, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 4', 'id': 315, 'area': 80, 'coordinates': [15, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 5', 'id': 316, 'area': 80, 'coordinates': [12, 41], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 6', 'id': 317, 'area': 80, 'coordinates': [13, 41], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 7', 'id': 318, 'area': 80, 'coordinates': [14, 41], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 8', 'id': 319, 'area': 80, 'coordinates': [15, 41], 'teleporter': [10, 6]}, {'name': "Windia - Otto's House", 'id': 320, 'area': 80, 'coordinates': [21, 39], 'teleporter': [30, 5]}, {'name': "Windia - INN's Script", 'id': 321, 'area': 80, 'coordinates': [18, 34], 'teleporter': [97, 8]}, {'name': 'Windia - Vendor House', 'id': 322, 'area': 80, 'coordinates': [8, 36], 'teleporter': [32, 5]}, {'name': 'Windia - Kid House', 'id': 323, 'area': 80, 'coordinates': [7, 23], 'teleporter': [176, 4]}, {'name': 'Windia - Old People House', 'id': 324, 'area': 80, 'coordinates': [19, 21], 'teleporter': [177, 4]}, {'name': 'Windia - Rainbow Bridge Script', 'id': 325, 'area': 80, 'coordinates': [21, 9], 'teleporter': [10, 6]}, {'name': "Otto's House - Attic Stairs", 'id': 326, 'area': 81, 'coordinates': [2, 19], 'teleporter': [33, 2]}, {'name': "Otto's House - Entrance", 'id': 327, 'area': 81, 'coordinates': [9, 30], 'teleporter': [106, 3]}, {'name': "Otto's Attic - Stairs", 'id': 328, 'area': 81, 'coordinates': [26, 23], 'teleporter': [107, 3]}, {'name': 'Windia Kid House - Entrance Script', 'id': 329, 'area': 82, 'coordinates': [7, 10], 'teleporter': [178, 0]}, {'name': 'Windia Kid House - Basement Stairs', 'id': 330, 'area': 82, 'coordinates': [1, 4], 'teleporter': [180, 0]}, {'name': 'Windia Old People House - Entrance', 'id': 331, 'area': 82, 'coordinates': [55, 12], 'teleporter': [179, 0]}, {'name': 'Windia Old People House - Basement Stairs', 'id': 332, 'area': 82, 'coordinates': [60, 5], 'teleporter': [181, 0]}, {'name': 'Windia Kid House Basement - Stairs', 'id': 333, 'area': 82, 'coordinates': [43, 8], 'teleporter': [182, 0]}, {'name': 'Windia Kid House Basement - Mobius Teleporter', 'id': 334, 'area': 82, 'coordinates': [41, 9], 'teleporter': [44, 8]}, {'name': 'Windia Old People House Basement - Stairs', 'id': 335, 'area': 82, 'coordinates': [39, 26], 'teleporter': [183, 0]}, {'name': 'Windia Old People House Basement - Mobius Teleporter Script', 'id': 336, 'area': 82, 'coordinates': [39, 23], 'teleporter': [43, 8]}, {'name': 'Windia Inn Lobby - Stairs to Beds', 'id': 337, 'area': 82, 'coordinates': [45, 24], 'teleporter': [102, 8]}, {'name': 'Windia Inn Lobby - Exit', 'id': 338, 'area': 82, 'coordinates': [53, 30], 'teleporter': [135, 3]}, {'name': 'Windia Inn Beds - Stairs to Lobby', 'id': 339, 'area': 82, 'coordinates': [33, 59], 'teleporter': [216, 0]}, {'name': 'Windia Vendor House - Entrance', 'id': 340, 'area': 82, 'coordinates': [29, 14], 'teleporter': [108, 3]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 1', 'id': 341, 'area': 83, 'coordinates': [47, 29], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 2', 'id': 342, 'area': 83, 'coordinates': [47, 30], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 3', 'id': 343, 'area': 83, 'coordinates': [48, 29], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 4', 'id': 344, 'area': 83, 'coordinates': [48, 30], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - East Entrance', 'id': 345, 'area': 83, 'coordinates': [55, 12], 'teleporter': [185, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - South Stairs', 'id': 346, 'area': 83, 'coordinates': [51, 25], 'teleporter': [186, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Pazuzu Script 1', 'id': 347, 'area': 83, 'coordinates': [47, 8], 'teleporter': [16, 8]}, {'name': 'Pazuzu Tower 1F Main Lobby - Pazuzu Script 2', 'id': 348, 'area': 83, 'coordinates': [48, 8], 'teleporter': [16, 8]}, {'name': 'Pazuzu Tower 1F Boxes Room - West Stairs', 'id': 349, 'area': 83, 'coordinates': [38, 17], 'teleporter': [187, 0]}, {'name': 'Pazuzu 2F - West Upper Stairs', 'id': 350, 'area': 84, 'coordinates': [7, 11], 'teleporter': [188, 0]}, {'name': 'Pazuzu 2F - South Stairs', 'id': 351, 'area': 84, 'coordinates': [20, 24], 'teleporter': [189, 0]}, {'name': 'Pazuzu 2F - West Lower Stairs', 'id': 352, 'area': 84, 'coordinates': [6, 17], 'teleporter': [190, 0]}, {'name': 'Pazuzu 2F - Central Stairs', 'id': 353, 'area': 84, 'coordinates': [15, 15], 'teleporter': [191, 0]}, {'name': 'Pazuzu 2F - Pazuzu 1', 'id': 354, 'area': 84, 'coordinates': [15, 8], 'teleporter': [17, 8]}, {'name': 'Pazuzu 2F - Pazuzu 2', 'id': 355, 'area': 84, 'coordinates': [16, 8], 'teleporter': [17, 8]}, {'name': 'Pazuzu 3F Main Room - North Stairs', 'id': 356, 'area': 85, 'coordinates': [23, 11], 'teleporter': [192, 0]}, {'name': 'Pazuzu 3F Main Room - West Stairs', 'id': 357, 'area': 85, 'coordinates': [7, 15], 'teleporter': [193, 0]}, {'name': 'Pazuzu 3F Main Room - Pazuzu Script 1', 'id': 358, 'area': 85, 'coordinates': [15, 8], 'teleporter': [18, 8]}, {'name': 'Pazuzu 3F Main Room - Pazuzu Script 2', 'id': 359, 'area': 85, 'coordinates': [16, 8], 'teleporter': [18, 8]}, {'name': 'Pazuzu 3F Central Island - Central Stairs', 'id': 360, 'area': 85, 'coordinates': [15, 14], 'teleporter': [194, 0]}, {'name': 'Pazuzu 3F Central Island - South Stairs', 'id': 361, 'area': 85, 'coordinates': [17, 25], 'teleporter': [195, 0]}, {'name': 'Pazuzu 4F - Northwest Stairs', 'id': 362, 'area': 86, 'coordinates': [39, 12], 'teleporter': [196, 0]}, {'name': 'Pazuzu 4F - Southwest Stairs', 'id': 363, 'area': 86, 'coordinates': [39, 19], 'teleporter': [197, 0]}, {'name': 'Pazuzu 4F - South Stairs', 'id': 364, 'area': 86, 'coordinates': [47, 24], 'teleporter': [198, 0]}, {'name': 'Pazuzu 4F - Northeast Stairs', 'id': 365, 'area': 86, 'coordinates': [54, 9], 'teleporter': [199, 0]}, {'name': 'Pazuzu 4F - Pazuzu Script 1', 'id': 366, 'area': 86, 'coordinates': [47, 8], 'teleporter': [19, 8]}, {'name': 'Pazuzu 4F - Pazuzu Script 2', 'id': 367, 'area': 86, 'coordinates': [48, 8], 'teleporter': [19, 8]}, {'name': 'Pazuzu 5F Pazuzu Loop - West Stairs', 'id': 368, 'area': 87, 'coordinates': [9, 49], 'teleporter': [200, 0]}, {'name': 'Pazuzu 5F Pazuzu Loop - South Stairs', 'id': 369, 'area': 87, 'coordinates': [16, 55], 'teleporter': [201, 0]}, {'name': 'Pazuzu 5F Upper Loop - Northeast Stairs', 'id': 370, 'area': 87, 'coordinates': [22, 40], 'teleporter': [202, 0]}, {'name': 'Pazuzu 5F Upper Loop - Northwest Stairs', 'id': 371, 'area': 87, 'coordinates': [9, 40], 'teleporter': [203, 0]}, {'name': 'Pazuzu 5F Upper Loop - Pazuzu Script 1', 'id': 372, 'area': 87, 'coordinates': [15, 40], 'teleporter': [20, 8]}, {'name': 'Pazuzu 5F Upper Loop - Pazuzu Script 2', 'id': 373, 'area': 87, 'coordinates': [16, 40], 'teleporter': [20, 8]}, {'name': 'Pazuzu 6F - West Stairs', 'id': 374, 'area': 88, 'coordinates': [41, 47], 'teleporter': [204, 0]}, {'name': 'Pazuzu 6F - Northwest Stairs', 'id': 375, 'area': 88, 'coordinates': [41, 40], 'teleporter': [205, 0]}, {'name': 'Pazuzu 6F - Northeast Stairs', 'id': 376, 'area': 88, 'coordinates': [54, 40], 'teleporter': [206, 0]}, {'name': 'Pazuzu 6F - South Stairs', 'id': 377, 'area': 88, 'coordinates': [52, 56], 'teleporter': [207, 0]}, {'name': 'Pazuzu 6F - Pazuzu Script 1', 'id': 378, 'area': 88, 'coordinates': [47, 40], 'teleporter': [21, 8]}, {'name': 'Pazuzu 6F - Pazuzu Script 2', 'id': 379, 'area': 88, 'coordinates': [48, 40], 'teleporter': [21, 8]}, {'name': 'Pazuzu 7F Main Room - Southwest Stairs', 'id': 380, 'area': 89, 'coordinates': [15, 54], 'teleporter': [26, 0]}, {'name': 'Pazuzu 7F Main Room - Northeast Stairs', 'id': 381, 'area': 89, 'coordinates': [21, 40], 'teleporter': [27, 0]}, {'name': 'Pazuzu 7F Main Room - Southeast Stairs', 'id': 382, 'area': 89, 'coordinates': [21, 56], 'teleporter': [28, 0]}, {'name': 'Pazuzu 7F Main Room - Pazuzu Script 1', 'id': 383, 'area': 89, 'coordinates': [15, 44], 'teleporter': [22, 8]}, {'name': 'Pazuzu 7F Main Room - Pazuzu Script 2', 'id': 384, 'area': 89, 'coordinates': [16, 44], 'teleporter': [22, 8]}, {'name': 'Pazuzu 7F Main Room - Crystal Script', 'id': 480, 'area': 89, 'coordinates': [15, 40], 'teleporter': [38, 8]}, {'name': 'Pazuzu 1F to 3F - South Stairs', 'id': 385, 'area': 90, 'coordinates': [43, 60], 'teleporter': [29, 0]}, {'name': 'Pazuzu 1F to 3F - North Stairs', 'id': 386, 'area': 90, 'coordinates': [43, 36], 'teleporter': [30, 0]}, {'name': 'Pazuzu 3F to 5F - South Stairs', 'id': 387, 'area': 91, 'coordinates': [43, 60], 'teleporter': [40, 0]}, {'name': 'Pazuzu 3F to 5F - North Stairs', 'id': 388, 'area': 91, 'coordinates': [43, 36], 'teleporter': [41, 0]}, {'name': 'Pazuzu 5F to 7F - South Stairs', 'id': 389, 'area': 92, 'coordinates': [43, 60], 'teleporter': [38, 0]}, {'name': 'Pazuzu 5F to 7F - North Stairs', 'id': 390, 'area': 92, 'coordinates': [43, 36], 'teleporter': [39, 0]}, {'name': 'Pazuzu 2F to 4F - South Stairs', 'id': 391, 'area': 93, 'coordinates': [43, 60], 'teleporter': [21, 0]}, {'name': 'Pazuzu 2F to 4F - North Stairs', 'id': 392, 'area': 93, 'coordinates': [43, 36], 'teleporter': [22, 0]}, {'name': 'Pazuzu 4F to 6F - South Stairs', 'id': 393, 'area': 94, 'coordinates': [43, 60], 'teleporter': [2, 0]}, {'name': 'Pazuzu 4F to 6F - North Stairs', 'id': 394, 'area': 94, 'coordinates': [43, 36], 'teleporter': [3, 0]}, {'name': 'Light Temple - Entrance', 'id': 395, 'area': 95, 'coordinates': [28, 57], 'teleporter': [19, 6]}, {'name': 'Light Temple - Mobius Teleporter Script', 'id': 396, 'area': 95, 'coordinates': [29, 37], 'teleporter': [70, 8]}, {'name': 'Light Temple - Visit Quest Script 1', 'id': 492, 'area': 95, 'coordinates': [34, 39], 'teleporter': [100, 8]}, {'name': 'Light Temple - Visit Quest Script 2', 'id': 493, 'area': 95, 'coordinates': [35, 39], 'teleporter': [100, 8]}, {'name': 'Ship Dock - Mobius Teleporter Script', 'id': 397, 'area': 96, 'coordinates': [15, 18], 'teleporter': [61, 8]}, {'name': 'Ship Dock - From Overworld', 'id': 398, 'area': 96, 'coordinates': [15, 11], 'teleporter': [73, 0]}, {'name': 'Ship Dock - Entrance', 'id': 399, 'area': 96, 'coordinates': [15, 23], 'teleporter': [17, 6]}, {'name': 'Mac Ship Deck - East Entrance Script', 'id': 400, 'area': 97, 'coordinates': [26, 40], 'teleporter': [37, 8]}, {'name': 'Mac Ship Deck - Central Stairs Script', 'id': 401, 'area': 97, 'coordinates': [16, 47], 'teleporter': [50, 8]}, {'name': 'Mac Ship Deck - West Stairs Script', 'id': 402, 'area': 97, 'coordinates': [8, 34], 'teleporter': [51, 8]}, {'name': 'Mac Ship Deck - East Stairs Script', 'id': 403, 'area': 97, 'coordinates': [24, 36], 'teleporter': [52, 8]}, {'name': 'Mac Ship Deck - North Stairs Script', 'id': 404, 'area': 97, 'coordinates': [12, 9], 'teleporter': [53, 8]}, {'name': 'Mac Ship B1 Outer Ring - South Stairs', 'id': 405, 'area': 98, 'coordinates': [16, 45], 'teleporter': [208, 0]}, {'name': 'Mac Ship B1 Outer Ring - West Stairs', 'id': 406, 'area': 98, 'coordinates': [8, 35], 'teleporter': [175, 0]}, {'name': 'Mac Ship B1 Outer Ring - East Stairs', 'id': 407, 'area': 98, 'coordinates': [25, 37], 'teleporter': [172, 0]}, {'name': 'Mac Ship B1 Outer Ring - Northwest Stairs', 'id': 408, 'area': 98, 'coordinates': [10, 23], 'teleporter': [88, 0]}, {'name': 'Mac Ship B1 Square Room - North Stairs', 'id': 409, 'area': 98, 'coordinates': [14, 9], 'teleporter': [141, 0]}, {'name': 'Mac Ship B1 Square Room - South Stairs', 'id': 410, 'area': 98, 'coordinates': [16, 12], 'teleporter': [87, 0]}, {'name': 'Mac Ship B1 Mac Room - Stairs', 'id': 411, 'area': 98, 'coordinates': [16, 51], 'teleporter': [101, 0]}, {'name': 'Mac Ship B1 Central Corridor - South Stairs', 'id': 412, 'area': 98, 'coordinates': [16, 38], 'teleporter': [102, 0]}, {'name': 'Mac Ship B1 Central Corridor - North Stairs', 'id': 413, 'area': 98, 'coordinates': [16, 26], 'teleporter': [86, 0]}, {'name': 'Mac Ship B2 South Corridor - South Stairs', 'id': 414, 'area': 99, 'coordinates': [48, 51], 'teleporter': [57, 1]}, {'name': 'Mac Ship B2 South Corridor - North Stairs Script', 'id': 415, 'area': 99, 'coordinates': [48, 38], 'teleporter': [55, 8]}, {'name': 'Mac Ship B2 North Corridor - South Stairs Script', 'id': 416, 'area': 99, 'coordinates': [48, 27], 'teleporter': [56, 8]}, {'name': 'Mac Ship B2 North Corridor - North Stairs Script', 'id': 417, 'area': 99, 'coordinates': [48, 12], 'teleporter': [57, 8]}, {'name': 'Mac Ship B2 Outer Ring - Northwest Stairs Script', 'id': 418, 'area': 99, 'coordinates': [55, 11], 'teleporter': [58, 8]}, {'name': 'Mac Ship B1 Outer Ring Cleared - South Stairs', 'id': 419, 'area': 100, 'coordinates': [16, 45], 'teleporter': [208, 0]}, {'name': 'Mac Ship B1 Outer Ring Cleared - West Stairs', 'id': 420, 'area': 100, 'coordinates': [8, 35], 'teleporter': [175, 0]}, {'name': 'Mac Ship B1 Outer Ring Cleared - East Stairs', 'id': 421, 'area': 100, 'coordinates': [25, 37], 'teleporter': [172, 0]}, {'name': 'Mac Ship B1 Square Room Cleared - North Stairs', 'id': 422, 'area': 100, 'coordinates': [14, 9], 'teleporter': [141, 0]}, {'name': 'Mac Ship B1 Square Room Cleared - South Stairs', 'id': 423, 'area': 100, 'coordinates': [16, 12], 'teleporter': [87, 0]}, {'name': 'Mac Ship B1 Mac Room Cleared - Main Stairs', 'id': 424, 'area': 100, 'coordinates': [16, 51], 'teleporter': [101, 0]}, {'name': 'Mac Ship B1 Central Corridor Cleared - South Stairs', 'id': 425, 'area': 100, 'coordinates': [16, 38], 'teleporter': [102, 0]}, {'name': 'Mac Ship B1 Central Corridor Cleared - North Stairs', 'id': 426, 'area': 100, 'coordinates': [16, 26], 'teleporter': [86, 0]}, {'name': 'Mac Ship B1 Central Corridor Cleared - Northwest Stairs', 'id': 427, 'area': 100, 'coordinates': [23, 10], 'teleporter': [88, 0]}, {'name': 'Doom Castle Corridor of Destiny - South Entrance', 'id': 428, 'area': 101, 'coordinates': [59, 29], 'teleporter': [84, 0]}, {'name': 'Doom Castle Corridor of Destiny - Ice Floor Entrance', 'id': 429, 'area': 101, 'coordinates': [59, 21], 'teleporter': [35, 2]}, {'name': 'Doom Castle Corridor of Destiny - Lava Floor Entrance', 'id': 430, 'area': 101, 'coordinates': [59, 13], 'teleporter': [209, 0]}, {'name': 'Doom Castle Corridor of Destiny - Sky Floor Entrance', 'id': 431, 'area': 101, 'coordinates': [59, 5], 'teleporter': [211, 0]}, {'name': 'Doom Castle Corridor of Destiny - Hero Room Entrance', 'id': 432, 'area': 101, 'coordinates': [59, 61], 'teleporter': [13, 2]}, {'name': 'Doom Castle Ice Floor - Entrance', 'id': 433, 'area': 102, 'coordinates': [23, 42], 'teleporter': [109, 3]}, {'name': 'Doom Castle Lava Floor - Entrance', 'id': 434, 'area': 103, 'coordinates': [23, 40], 'teleporter': [210, 0]}, {'name': 'Doom Castle Sky Floor - Entrance', 'id': 435, 'area': 104, 'coordinates': [24, 41], 'teleporter': [212, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 1', 'id': 436, 'area': 106, 'coordinates': [15, 5], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 2', 'id': 437, 'area': 106, 'coordinates': [16, 5], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 3', 'id': 438, 'area': 106, 'coordinates': [15, 4], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 4', 'id': 439, 'area': 106, 'coordinates': [16, 4], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Hero Statue Script', 'id': 440, 'area': 106, 'coordinates': [15, 17], 'teleporter': [24, 8]}, {'name': 'Doom Castle Hero Room - Entrance', 'id': 441, 'area': 106, 'coordinates': [15, 24], 'teleporter': [110, 3]}, {'name': 'Doom Castle Dark King Room - Entrance', 'id': 442, 'area': 107, 'coordinates': [14, 26], 'teleporter': [52, 0]}, {'name': 'Doom Castle Dark King Room - Dark King Script', 'id': 443, 'area': 107, 'coordinates': [14, 15], 'teleporter': [25, 8]}, {'name': 'Doom Castle Dark King Room - Unknown', 'id': 444, 'area': 107, 'coordinates': [47, 54], 'teleporter': [77, 0]}, {'name': 'Overworld - Level Forest', 'id': 445, 'area': 0, 'type': 'Overworld', 'teleporter': [46, 8]}, {'name': 'Overworld - Foresta', 'id': 446, 'area': 0, 'type': 'Overworld', 'teleporter': [2, 1]}, {'name': 'Overworld - Sand Temple', 'id': 447, 'area': 0, 'type': 'Overworld', 'teleporter': [3, 1]}, {'name': 'Overworld - Bone Dungeon', 'id': 448, 'area': 0, 'type': 'Overworld', 'teleporter': [4, 1]}, {'name': 'Overworld - Focus Tower Foresta', 'id': 449, 'area': 0, 'type': 'Overworld', 'teleporter': [5, 1]}, {'name': 'Overworld - Focus Tower Aquaria', 'id': 450, 'area': 0, 'type': 'Overworld', 'teleporter': [19, 1]}, {'name': 'Overworld - Libra Temple', 'id': 451, 'area': 0, 'type': 'Overworld', 'teleporter': [7, 1]}, {'name': 'Overworld - Aquaria', 'id': 452, 'area': 0, 'type': 'Overworld', 'teleporter': [8, 8]}, {'name': 'Overworld - Wintry Cave', 'id': 453, 'area': 0, 'type': 'Overworld', 'teleporter': [10, 1]}, {'name': 'Overworld - Life Temple', 'id': 454, 'area': 0, 'type': 'Overworld', 'teleporter': [11, 1]}, {'name': 'Overworld - Falls Basin', 'id': 455, 'area': 0, 'type': 'Overworld', 'teleporter': [12, 1]}, {'name': 'Overworld - Ice Pyramid', 'id': 456, 'area': 0, 'type': 'Overworld', 'teleporter': [13, 1]}, {'name': "Overworld - Spencer's Place", 'id': 457, 'area': 0, 'type': 'Overworld', 'teleporter': [48, 8]}, {'name': 'Overworld - Wintry Temple', 'id': 458, 'area': 0, 'type': 'Overworld', 'teleporter': [16, 1]}, {'name': 'Overworld - Focus Tower Frozen Strip', 'id': 459, 'area': 0, 'type': 'Overworld', 'teleporter': [17, 1]}, {'name': 'Overworld - Focus Tower Fireburg', 'id': 460, 'area': 0, 'type': 'Overworld', 'teleporter': [18, 1]}, {'name': 'Overworld - Fireburg', 'id': 461, 'area': 0, 'type': 'Overworld', 'teleporter': [20, 1]}, {'name': 'Overworld - Mine', 'id': 462, 'area': 0, 'type': 'Overworld', 'teleporter': [21, 1]}, {'name': 'Overworld - Sealed Temple', 'id': 463, 'area': 0, 'type': 'Overworld', 'teleporter': [22, 1]}, {'name': 'Overworld - Volcano', 'id': 464, 'area': 0, 'type': 'Overworld', 'teleporter': [23, 1]}, {'name': 'Overworld - Lava Dome', 'id': 465, 'area': 0, 'type': 'Overworld', 'teleporter': [24, 1]}, {'name': 'Overworld - Focus Tower Windia', 'id': 466, 'area': 0, 'type': 'Overworld', 'teleporter': [6, 1]}, {'name': 'Overworld - Rope Bridge', 'id': 467, 'area': 0, 'type': 'Overworld', 'teleporter': [25, 1]}, {'name': 'Overworld - Alive Forest', 'id': 468, 'area': 0, 'type': 'Overworld', 'teleporter': [26, 1]}, {'name': 'Overworld - Giant Tree', 'id': 469, 'area': 0, 'type': 'Overworld', 'teleporter': [27, 1]}, {'name': 'Overworld - Kaidge Temple', 'id': 470, 'area': 0, 'type': 'Overworld', 'teleporter': [28, 1]}, {'name': 'Overworld - Windia', 'id': 471, 'area': 0, 'type': 'Overworld', 'teleporter': [29, 1]}, {'name': 'Overworld - Windhole Temple', 'id': 472, 'area': 0, 'type': 'Overworld', 'teleporter': [30, 1]}, {'name': 'Overworld - Mount Gale', 'id': 473, 'area': 0, 'type': 'Overworld', 'teleporter': [31, 1]}, {'name': 'Overworld - Pazuzu Tower', 'id': 474, 'area': 0, 'type': 'Overworld', 'teleporter': [32, 1]}, {'name': 'Overworld - Ship Dock', 'id': 475, 'area': 0, 'type': 'Overworld', 'teleporter': [62, 1]}, {'name': 'Overworld - Doom Castle', 'id': 476, 'area': 0, 'type': 'Overworld', 'teleporter': [33, 1]}, {'name': 'Overworld - Light Temple', 'id': 477, 'area': 0, 'type': 'Overworld', 'teleporter': [34, 1]}, {'name': 'Overworld - Mac Ship', 'id': 478, 'area': 0, 'type': 'Overworld', 'teleporter': [36, 1]}, {'name': 'Overworld - Mac Ship Doom', 'id': 479, 'area': 0, 'type': 'Overworld', 'teleporter': [36, 1]}, {'name': 'Dummy House - Bed Script', 'id': 480, 'area': 17, 'coordinates': [40, 56], 'teleporter': [1, 8]}, {'name': 'Dummy House - Entrance', 'id': 481, 'area': 17, 'coordinates': [41, 59], 'teleporter': [0, 10]}] \ No newline at end of file diff --git a/worlds/ffmq/data/rooms.yaml b/worlds/ffmq/data/rooms.yaml deleted file mode 100644 index e0c2e8d7f9fc..000000000000 --- a/worlds/ffmq/data/rooms.yaml +++ /dev/null @@ -1,4026 +0,0 @@ -- name: Overworld - id: 0 - type: "Overworld" - game_objects: [] - links: - - target_room: 220 # To Forest Subregion - access: [] -- name: Subregion Foresta - id: 220 - type: "Subregion" - region: "Foresta" - game_objects: - - name: "Foresta South Battlefield" - object_id: 0x01 - location: "ForestaSouthBattlefield" - location_slot: "ForestaSouthBattlefield" - type: "BattlefieldXp" - access: [] - - name: "Foresta West Battlefield" - object_id: 0x02 - location: "ForestaWestBattlefield" - location_slot: "ForestaWestBattlefield" - type: "BattlefieldItem" - access: [] - - name: "Foresta East Battlefield" - object_id: 0x03 - location: "ForestaEastBattlefield" - location_slot: "ForestaEastBattlefield" - type: "BattlefieldGp" - access: [] - links: - - target_room: 15 # Level Forest - location: "LevelForest" - location_slot: "LevelForest" - entrance: 445 - teleporter: [0x2E, 8] - access: [] - - target_room: 16 # Foresta - location: "Foresta" - location_slot: "Foresta" - entrance: 446 - teleporter: [0x02, 1] - access: [] - - target_room: 24 # Sand Temple - location: "SandTemple" - location_slot: "SandTemple" - entrance: 447 - teleporter: [0x03, 1] - access: [] - - target_room: 25 # Bone Dungeon - location: "BoneDungeon" - location_slot: "BoneDungeon" - entrance: 448 - teleporter: [0x04, 1] - access: [] - - target_room: 3 # Focus Tower Foresta - location: "FocusTowerForesta" - location_slot: "FocusTowerForesta" - entrance: 449 - teleporter: [0x05, 1] - access: [] - - target_room: 221 - access: ["SandCoin"] - - target_room: 224 - access: ["RiverCoin"] - - target_room: 226 - access: ["SunCoin"] -- name: Subregion Aquaria - id: 221 - type: "Subregion" - region: "Aquaria" - game_objects: - - name: "South of Libra Temple Battlefield" - object_id: 0x04 - location: "AquariaBattlefield01" - location_slot: "AquariaBattlefield01" - type: "BattlefieldXp" - access: [] - - name: "East of Libra Temple Battlefield" - object_id: 0x05 - location: "AquariaBattlefield02" - location_slot: "AquariaBattlefield02" - type: "BattlefieldGp" - access: [] - - name: "South of Aquaria Battlefield" - object_id: 0x06 - location: "AquariaBattlefield03" - location_slot: "AquariaBattlefield03" - type: "BattlefieldItem" - access: [] - - name: "South of Wintry Cave Battlefield" - object_id: 0x07 - location: "WintryBattlefield01" - location_slot: "WintryBattlefield01" - type: "BattlefieldXp" - access: [] - - name: "West of Wintry Cave Battlefield" - object_id: 0x08 - location: "WintryBattlefield02" - location_slot: "WintryBattlefield02" - type: "BattlefieldGp" - access: [] - - name: "Ice Pyramid Battlefield" - object_id: 0x09 - location: "PyramidBattlefield01" - location_slot: "PyramidBattlefield01" - type: "BattlefieldXp" - access: [] - links: - - target_room: 10 # Focus Tower Aquaria - location: "FocusTowerAquaria" - location_slot: "FocusTowerAquaria" - entrance: 450 - teleporter: [0x13, 1] - access: [] - - target_room: 39 # Libra Temple - location: "LibraTemple" - location_slot: "LibraTemple" - entrance: 451 - teleporter: [0x07, 1] - access: [] - - target_room: 40 # Aquaria - location: "Aquaria" - location_slot: "Aquaria" - entrance: 452 - teleporter: [0x08, 8] - access: [] - - target_room: 45 # Wintry Cave - location: "WintryCave" - location_slot: "WintryCave" - entrance: 453 - teleporter: [0x0A, 1] - access: [] - - target_room: 52 # Falls Basin - location: "FallsBasin" - location_slot: "FallsBasin" - entrance: 455 - teleporter: [0x0C, 1] - access: [] - - target_room: 54 # Ice Pyramid - location: "IcePyramid" - location_slot: "IcePyramid" - entrance: 456 - teleporter: [0x0D, 1] # Will be switched to a script - access: [] - - target_room: 220 - access: ["SandCoin"] - - target_room: 224 - access: ["SandCoin", "RiverCoin"] - - target_room: 226 - access: ["SandCoin", "SunCoin"] - - target_room: 223 - access: ["SummerAquaria"] -- name: Subregion Life Temple - id: 222 - type: "Subregion" - region: "LifeTemple" - game_objects: [] - links: - - target_room: 51 # Life Temple - location: "LifeTemple" - location_slot: "LifeTemple" - entrance: 454 - teleporter: [0x0B, 1] - access: [] -- name: Subregion Frozen Fields - id: 223 - type: "Subregion" - region: "AquariaFrozenField" - game_objects: - - name: "North of Libra Temple Battlefield" - object_id: 0x0A - location: "LibraBattlefield01" - location_slot: "LibraBattlefield01" - type: "BattlefieldItem" - access: [] - - name: "Aquaria Frozen Field Battlefield" - object_id: 0x0B - location: "LibraBattlefield02" - location_slot: "LibraBattlefield02" - type: "BattlefieldXp" - access: [] - links: - - target_room: 74 # Wintry Temple - location: "WintryTemple" - location_slot: "WintryTemple" - entrance: 458 - teleporter: [0x10, 1] - access: [] - - target_room: 14 # Focus Tower Frozen Strip - location: "FocusTowerFrozen" - location_slot: "FocusTowerFrozen" - entrance: 459 - teleporter: [0x11, 1] - access: [] - - target_room: 221 - access: [] - - target_room: 225 - access: ["SummerAquaria", "DualheadHydra"] -- name: Subregion Fireburg - id: 224 - type: "Subregion" - region: "Fireburg" - game_objects: - - name: "Path to Fireburg Southern Battlefield" - object_id: 0x0C - location: "FireburgBattlefield01" - location_slot: "FireburgBattlefield01" - type: "BattlefieldGp" - access: [] - - name: "Path to Fireburg Central Battlefield" - object_id: 0x0D - location: "FireburgBattlefield02" - location_slot: "FireburgBattlefield02" - type: "BattlefieldItem" - access: [] - - name: "Path to Fireburg Northern Battlefield" - object_id: 0x0E - location: "FireburgBattlefield03" - location_slot: "FireburgBattlefield03" - type: "BattlefieldXp" - access: [] - - name: "Sealed Temple Battlefield" - object_id: 0x0F - location: "MineBattlefield01" - location_slot: "MineBattlefield01" - type: "BattlefieldGp" - access: [] - - name: "Mine Battlefield" - object_id: 0x10 - location: "MineBattlefield02" - location_slot: "MineBattlefield02" - type: "BattlefieldItem" - access: [] - - name: "Boulder Battlefield" - object_id: 0x11 - location: "MineBattlefield03" - location_slot: "MineBattlefield03" - type: "BattlefieldXp" - access: [] - links: - - target_room: 13 # Focus Tower Fireburg - location: "FocusTowerFireburg" - location_slot: "FocusTowerFireburg" - entrance: 460 - teleporter: [0x12, 1] - access: [] - - target_room: 76 # Fireburg - location: "Fireburg" - location_slot: "Fireburg" - entrance: 461 - teleporter: [0x14, 1] - access: [] - - target_room: 84 # Mine - location: "Mine" - location_slot: "Mine" - entrance: 462 - teleporter: [0x15, 1] - access: [] - - target_room: 92 # Sealed Temple - location: "SealedTemple" - location_slot: "SealedTemple" - entrance: 463 - teleporter: [0x16, 1] - access: [] - - target_room: 93 # Volcano - location: "Volcano" - location_slot: "Volcano" - entrance: 464 - teleporter: [0x17, 1] # Also this one / 0x0F, 8 - access: [] - - target_room: 100 # Lava Dome - location: "LavaDome" - location_slot: "LavaDome" - entrance: 465 - teleporter: [0x18, 1] - access: [] - - target_room: 220 - access: ["RiverCoin"] - - target_room: 221 - access: ["SandCoin", "RiverCoin"] - - target_room: 226 - access: ["RiverCoin", "SunCoin"] - - target_room: 225 - access: ["DualheadHydra"] -- name: Subregion Volcano Battlefield - id: 225 - type: "Subregion" - region: "VolcanoBattlefield" - game_objects: - - name: "Volcano Battlefield" - object_id: 0x12 - location: "VolcanoBattlefield01" - location_slot: "VolcanoBattlefield01" - type: "BattlefieldXp" - access: [] - links: - - target_room: 224 - access: ["DualheadHydra"] - - target_room: 223 - access: ["SummerAquaria"] -- name: Subregion Windia - id: 226 - type: "Subregion" - region: "Windia" - game_objects: - - name: "Kaidge Temple Battlefield" - object_id: 0x13 - location: "WindiaBattlefield01" - location_slot: "WindiaBattlefield01" - type: "BattlefieldXp" - access: ["SandCoin", "RiverCoin"] - - name: "South of Windia Battlefield" - object_id: 0x14 - location: "WindiaBattlefield02" - location_slot: "WindiaBattlefield02" - type: "BattlefieldXp" - access: ["SandCoin", "RiverCoin"] - links: - - target_room: 9 # Focus Tower Windia - location: "FocusTowerWindia" - location_slot: "FocusTowerWindia" - entrance: 466 - teleporter: [0x06, 1] - access: [] - - target_room: 123 # Rope Bridge - location: "RopeBridge" - location_slot: "RopeBridge" - entrance: 467 - teleporter: [0x19, 1] - access: [] - - target_room: 124 # Alive Forest - location: "AliveForest" - location_slot: "AliveForest" - entrance: 468 - teleporter: [0x1A, 1] - access: [] - - target_room: 125 # Giant Tree - location: "GiantTree" - location_slot: "GiantTree" - entrance: 469 - teleporter: [0x1B, 1] - access: ["Barred"] - - target_room: 152 # Kaidge Temple - location: "KaidgeTemple" - location_slot: "KaidgeTemple" - entrance: 470 - teleporter: [0x1C, 1] - access: [] - - target_room: 156 # Windia - location: "Windia" - location_slot: "Windia" - entrance: 471 - teleporter: [0x1D, 1] - access: [] - - target_room: 154 # Windhole Temple - location: "WindholeTemple" - location_slot: "WindholeTemple" - entrance: 472 - teleporter: [0x1E, 1] - access: [] - - target_room: 155 # Mount Gale - location: "MountGale" - location_slot: "MountGale" - entrance: 473 - teleporter: [0x1F, 1] - access: [] - - target_room: 166 # Pazuzu Tower - location: "PazuzusTower" - location_slot: "PazuzusTower" - entrance: 474 - teleporter: [0x20, 1] - access: [] - - target_room: 220 - access: ["SunCoin"] - - target_room: 221 - access: ["SandCoin", "SunCoin"] - - target_room: 224 - access: ["RiverCoin", "SunCoin"] - - target_room: 227 - access: ["RainbowBridge"] -- name: Subregion Spencer's Cave - id: 227 - type: "Subregion" - region: "SpencerCave" - game_objects: [] - links: - - target_room: 73 # Spencer's Place - location: "SpencersPlace" - location_slot: "SpencersPlace" - entrance: 457 - teleporter: [0x30, 8] - access: [] - - target_room: 226 - access: ["RainbowBridge"] -- name: Subregion Ship Dock - id: 228 - type: "Subregion" - region: "ShipDock" - game_objects: [] - links: - - target_room: 186 # Ship Dock - location: "ShipDock" - location_slot: "ShipDock" - entrance: 475 - teleporter: [0x3E, 1] - access: [] - - target_room: 229 - access: ["ShipLiberated", "ShipDockAccess"] -- name: Subregion Mac's Ship - id: 229 - type: "Subregion" - region: "MacShip" - game_objects: [] - links: - - target_room: 187 # Mac Ship - location: "MacsShip" - location_slot: "MacsShip" - entrance: 478 - teleporter: [0x24, 1] - access: [] - - target_room: 228 - access: ["ShipLiberated", "ShipDockAccess"] - - target_room: 231 - access: ["ShipLoaned", "ShipDockAccess", "ShipSteeringWheel"] -- name: Subregion Light Temple - id: 230 - type: "Subregion" - region: "LightTemple" - game_objects: [] - links: - - target_room: 185 # Light Temple - location: "LightTemple" - location_slot: "LightTemple" - entrance: 477 - teleporter: [0x23, 1] - access: [] -- name: Subregion Doom Castle - id: 231 - type: "Subregion" - region: "DoomCastle" - game_objects: [] - links: - - target_room: 1 # Doom Castle - location: "DoomCastle" - location_slot: "DoomCastle" - entrance: 476 - teleporter: [0x21, 1] - access: [] - - target_room: 187 # Mac Ship Doom - location: "MacsShipDoom" - location_slot: "MacsShipDoom" - entrance: 479 - teleporter: [0x24, 1] - access: ["Barred"] - - target_room: 229 - access: ["ShipLoaned", "ShipDockAccess", "ShipSteeringWheel"] -- name: Doom Castle - Sand Floor - id: 1 - game_objects: - - name: "Doom Castle B2 - Southeast Chest" - object_id: 0x01 - type: "Chest" - access: ["Bomb"] - - name: "Doom Castle B2 - Bone Ledge Box" - object_id: 0x1E - type: "Box" - access: [] - - name: "Doom Castle B2 - Hook Platform Box" - object_id: 0x1F - type: "Box" - access: ["DragonClaw"] - links: - - target_room: 231 - entrance: 1 - teleporter: [1, 6] - access: [] - - target_room: 5 - entrance: 0 - teleporter: [0, 0] - access: ["DragonClaw", "MegaGrenade"] -- name: Doom Castle - Aero Room - id: 2 - game_objects: - - name: "Doom Castle B2 - Sun Door Chest" - object_id: 0x00 - type: "Chest" - access: [] - links: - - target_room: 4 - entrance: 2 - teleporter: [1, 0] - access: [] -- name: Focus Tower B1 - Main Loop - id: 3 - game_objects: [] - links: - - target_room: 220 - entrance: 3 - teleporter: [2, 6] - access: [] - - target_room: 6 - entrance: 4 - teleporter: [4, 0] - access: [] -- name: Focus Tower B1 - Aero Corridor - id: 4 - game_objects: [] - links: - - target_room: 9 - entrance: 5 - teleporter: [5, 0] - access: [] - - target_room: 2 - entrance: 6 - teleporter: [8, 0] - access: [] -- name: Focus Tower B1 - Inner Loop - id: 5 - game_objects: [] - links: - - target_room: 1 - entrance: 8 - teleporter: [7, 0] - access: [] - - target_room: 201 - entrance: 7 - teleporter: [6, 0] - access: [] -- name: Focus Tower 1F Main Lobby - id: 6 - game_objects: - - name: "Focus Tower 1F - Main Lobby Box" - object_id: 0x21 - type: "Box" - access: [] - links: - - target_room: 3 - entrance: 11 - teleporter: [11, 0] - access: [] - - target_room: 7 - access: ["SandCoin"] - - target_room: 8 - access: ["RiverCoin"] - - target_room: 9 - access: ["SunCoin"] -- name: Focus Tower 1F SandCoin Room - id: 7 - game_objects: [] - links: - - target_room: 6 - access: ["SandCoin"] - - target_room: 10 - entrance: 10 - teleporter: [10, 0] - access: [] -- name: Focus Tower 1F RiverCoin Room - id: 8 - game_objects: [] - links: - - target_room: 6 - access: ["RiverCoin"] - - target_room: 11 - entrance: 14 - teleporter: [14, 0] - access: [] -- name: Focus Tower 1F SunCoin Room - id: 9 - game_objects: [] - links: - - target_room: 6 - access: ["SunCoin"] - - target_room: 4 - entrance: 12 - teleporter: [12, 0] - access: [] - - target_room: 226 - entrance: 9 - teleporter: [3, 6] - access: [] -- name: Focus Tower 1F SkyCoin Room - id: 201 - game_objects: [] - links: - - target_room: 195 - entrance: 13 - teleporter: [13, 0] - access: ["SkyCoin", "FlamerusRex", "IceGolem", "DualheadHydra", "Pazuzu"] - - target_room: 5 - entrance: 15 - teleporter: [15, 0] - access: [] -- name: Focus Tower 2F - Sand Coin Passage - id: 10 - game_objects: - - name: "Focus Tower 2F - Sand Door Chest" - object_id: 0x03 - type: "Chest" - access: [] - links: - - target_room: 221 - entrance: 16 - teleporter: [4, 6] - access: [] - - target_room: 7 - entrance: 17 - teleporter: [17, 0] - access: [] -- name: Focus Tower 2F - River Coin Passage - id: 11 - game_objects: [] - links: - - target_room: 8 - entrance: 18 - teleporter: [18, 0] - access: [] - - target_room: 13 - entrance: 19 - teleporter: [20, 0] - access: [] -- name: Focus Tower 2F - Venus Chest Room - id: 12 - game_objects: - - name: "Focus Tower 2F - Back Door Chest" - object_id: 0x02 - type: "Chest" - access: [] - - name: "Focus Tower 2F - Venus Chest" - object_id: 9 - type: "NPC" - access: ["Bomb", "VenusKey"] - links: - - target_room: 14 - entrance: 20 - teleporter: [19, 0] - access: [] -- name: Focus Tower 3F - Lower Floor - id: 13 - game_objects: - - name: "Focus Tower 3F - River Door Box" - object_id: 0x22 - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 22 - teleporter: [6, 6] - access: [] - - target_room: 11 - entrance: 23 - teleporter: [24, 0] - access: [] -- name: Focus Tower 3F - Upper Floor - id: 14 - game_objects: [] - links: - - target_room: 223 - entrance: 24 - teleporter: [5, 6] - access: [] - - target_room: 12 - entrance: 25 - teleporter: [23, 0] - access: [] -- name: Level Forest - id: 15 - game_objects: - - name: "Level Forest - Northwest Box" - object_id: 0x28 - type: "Box" - access: ["Axe"] - - name: "Level Forest - Northeast Box" - object_id: 0x29 - type: "Box" - access: ["Axe"] - - name: "Level Forest - Middle Box" - object_id: 0x2A - type: "Box" - access: [] - - name: "Level Forest - Southwest Box" - object_id: 0x2B - type: "Box" - access: ["Axe"] - - name: "Level Forest - Southeast Box" - object_id: 0x2C - type: "Box" - access: ["Axe"] - - name: "Minotaur" - object_id: 0 - type: "Trigger" - on_trigger: ["Minotaur"] - access: ["Kaeli1"] - - name: "Level Forest - Old Man" - object_id: 0 - type: "NPC" - access: [] - - name: "Level Forest - Kaeli" - object_id: 1 - type: "NPC" - access: ["Kaeli1", "Minotaur"] - links: - - target_room: 220 - entrance: 28 - teleporter: [25, 0] - access: [] -- name: Foresta - id: 16 - game_objects: - - name: "Foresta - Outside Box" - object_id: 0x2D - type: "Box" - access: ["Axe"] - links: - - target_room: 220 - entrance: 38 - teleporter: [31, 0] - access: [] - - target_room: 17 - entrance: 44 - teleporter: [0, 5] - access: [] - - target_room: 18 - entrance: 42 - teleporter: [32, 4] - access: [] - - target_room: 19 - entrance: 43 - teleporter: [33, 0] - access: [] - - target_room: 20 - entrance: 45 - teleporter: [1, 5] - access: [] -- name: Kaeli's House - id: 17 - game_objects: - - name: "Foresta - Kaeli's House Box" - object_id: 0x2E - type: "Box" - access: [] - - name: "Kaeli Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Kaeli1"] - access: ["TreeWither"] - - name: "Kaeli 2" - object_id: 0 - type: "Trigger" - on_trigger: ["Kaeli2"] - access: ["Kaeli1", "Minotaur", "Elixir"] - links: - - target_room: 16 - entrance: 46 - teleporter: [86, 3] - access: [] -- name: Foresta Houses - Old Man's House Main - id: 18 - game_objects: [] - links: - - target_room: 19 - access: ["BarrelPushed"] - - target_room: 16 - entrance: 47 - teleporter: [34, 0] - access: [] -- name: Foresta Houses - Old Man's House Back - id: 19 - game_objects: - - name: "Foresta - Old Man House Chest" - object_id: 0x05 - type: "Chest" - access: [] - - name: "Old Man Barrel" - object_id: 0 - type: "Trigger" - on_trigger: ["BarrelPushed"] - access: [] - links: - - target_room: 18 - access: ["BarrelPushed"] - - target_room: 16 - entrance: 48 - teleporter: [35, 0] - access: [] -- name: Foresta Houses - Rest House - id: 20 - game_objects: - - name: "Foresta - Rest House Box" - object_id: 0x2F - type: "Box" - access: [] - links: - - target_room: 16 - entrance: 50 - teleporter: [87, 3] - access: [] -- name: Libra Treehouse - id: 21 - game_objects: - - name: "Alive Forest - Libra Treehouse Box" - object_id: 0x32 - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 51 - teleporter: [67, 8] - access: ["LibraCrest"] -- name: Gemini Treehouse - id: 22 - game_objects: - - name: "Alive Forest - Gemini Treehouse Box" - object_id: 0x33 - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 52 - teleporter: [68, 8] - access: ["GeminiCrest"] -- name: Mobius Treehouse - id: 23 - game_objects: - - name: "Alive Forest - Mobius Treehouse West Box" - object_id: 0x30 - type: "Box" - access: [] - - name: "Alive Forest - Mobius Treehouse East Box" - object_id: 0x31 - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 53 - teleporter: [69, 8] - access: ["MobiusCrest"] -- name: Sand Temple - id: 24 - game_objects: - - name: "Tristam Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Tristam"] - access: [] - links: - - target_room: 220 - entrance: 54 - teleporter: [36, 0] - access: [] -- name: Bone Dungeon 1F - id: 25 - game_objects: - - name: "Bone Dungeon 1F - Entrance Room West Box" - object_id: 0x35 - type: "Box" - access: [] - - name: "Bone Dungeon 1F - Entrance Room Middle Box" - object_id: 0x36 - type: "Box" - access: [] - - name: "Bone Dungeon 1F - Entrance Room East Box" - object_id: 0x37 - type: "Box" - access: [] - links: - - target_room: 220 - entrance: 55 - teleporter: [37, 0] - access: [] - - target_room: 26 - entrance: 56 - teleporter: [2, 2] - access: [] -- name: Bone Dungeon B1 - Waterway - id: 26 - game_objects: - - name: "Bone Dungeon B1 - Skull Chest" - object_id: 0x06 - type: "Chest" - access: ["Bomb"] - - name: "Bone Dungeon B1 - Tristam" - object_id: 2 - type: "NPC" - access: ["Tristam"] - - name: "Tristam Bone Dungeon Item Given" - object_id: 0 - type: "Trigger" - on_trigger: ["TristamBoneItemGiven"] - access: ["Tristam"] - links: - - target_room: 25 - entrance: 59 - teleporter: [88, 3] - access: [] - - target_room: 28 - entrance: 57 - teleporter: [3, 2] - access: ["Bomb"] -- name: Bone Dungeon B1 - Checker Room - id: 28 - game_objects: - - name: "Bone Dungeon B1 - Checker Room Box" - object_id: 0x38 - type: "Box" - access: ["Bomb"] - links: - - target_room: 26 - entrance: 61 - teleporter: [89, 3] - access: [] - - target_room: 30 - entrance: 60 - teleporter: [4, 2] - access: [] -- name: Bone Dungeon B1 - Hidden Room - id: 29 - game_objects: - - name: "Bone Dungeon B1 - Ribcage Waterway Box" - object_id: 0x39 - type: "Box" - access: [] - links: - - target_room: 31 - entrance: 62 - teleporter: [91, 3] - access: [] -- name: Bone Dungeon B2 - Exploding Skull Room - First Room - id: 30 - game_objects: - - name: "Bone Dungeon B2 - Spines Room Alcove Box" - object_id: 0x3B - type: "Box" - access: [] - - name: "Long Spine" - object_id: 0 - type: "Trigger" - on_trigger: ["LongSpineBombed"] - access: ["Bomb"] - links: - - target_room: 28 - entrance: 65 - teleporter: [90, 3] - access: [] - - target_room: 31 - access: ["LongSpineBombed"] -- name: Bone Dungeon B2 - Exploding Skull Room - Second Room - id: 31 - game_objects: - - name: "Bone Dungeon B2 - Spines Room Looped Hallway Box" - object_id: 0x3A - type: "Box" - access: [] - - name: "Short Spine" - object_id: 0 - type: "Trigger" - on_trigger: ["ShortSpineBombed"] - access: ["Bomb"] - links: - - target_room: 29 - entrance: 63 - teleporter: [5, 2] - access: ["LongSpineBombed"] - - target_room: 32 - access: ["ShortSpineBombed"] - - target_room: 30 - access: ["LongSpineBombed"] -- name: Bone Dungeon B2 - Exploding Skull Room - Third Room - id: 32 - game_objects: [] - links: - - target_room: 35 - entrance: 64 - teleporter: [6, 2] - access: [] - - target_room: 31 - access: ["ShortSpineBombed"] -- name: Bone Dungeon B2 - Box Room - id: 33 - game_objects: - - name: "Bone Dungeon B2 - Lone Room Box" - object_id: 0x3D - type: "Box" - access: [] - links: - - target_room: 36 - entrance: 66 - teleporter: [93, 3] - access: [] -- name: Bone Dungeon B2 - Quake Room - id: 34 - game_objects: - - name: "Bone Dungeon B2 - Penultimate Room Chest" - object_id: 0x07 - type: "Chest" - access: [] - links: - - target_room: 37 - entrance: 67 - teleporter: [94, 3] - access: [] -- name: Bone Dungeon B2 - Two Skulls Room - First Room - id: 35 - game_objects: - - name: "Bone Dungeon B2 - Two Skulls Room Box" - object_id: 0x3C - type: "Box" - access: [] - - name: "Skull 1" - object_id: 0 - type: "Trigger" - on_trigger: ["Skull1Bombed"] - access: ["Bomb"] - links: - - target_room: 32 - entrance: 71 - teleporter: [92, 3] - access: [] - - target_room: 36 - access: ["Skull1Bombed"] -- name: Bone Dungeon B2 - Two Skulls Room - Second Room - id: 36 - game_objects: - - name: "Skull 2" - object_id: 0 - type: "Trigger" - on_trigger: ["Skull2Bombed"] - access: ["Bomb"] - links: - - target_room: 33 - entrance: 68 - teleporter: [7, 2] - access: [] - - target_room: 37 - access: ["Skull2Bombed"] - - target_room: 35 - access: ["Skull1Bombed"] -- name: Bone Dungeon B2 - Two Skulls Room - Third Room - id: 37 - game_objects: [] - links: - - target_room: 34 - entrance: 69 - teleporter: [8, 2] - access: [] - - target_room: 38 - entrance: 70 - teleporter: [9, 2] - access: ["Bomb"] - - target_room: 36 - access: ["Skull2Bombed"] -- name: Bone Dungeon B2 - Boss Room - id: 38 - game_objects: - - name: "Bone Dungeon B2 - North Box" - object_id: 0x3E - type: "Box" - access: [] - - name: "Bone Dungeon B2 - South Box" - object_id: 0x3F - type: "Box" - access: [] - - name: "Bone Dungeon B2 - Flamerus Rex Chest" - object_id: 0x08 - type: "Chest" - access: [] - - name: "Bone Dungeon B2 - Tristam's Treasure Chest" - object_id: 0x04 - type: "Chest" - access: [] - - name: "Flamerus Rex" - object_id: 0 - type: "Trigger" - on_trigger: ["FlamerusRex"] - access: [] - links: - - target_room: 37 - entrance: 74 - teleporter: [95, 3] - access: [] -- name: Libra Temple - id: 39 - game_objects: - - name: "Libra Temple - Box" - object_id: 0x40 - type: "Box" - access: [] - - name: "Phoebe Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Phoebe1"] - access: [] - links: - - target_room: 221 - entrance: 75 - teleporter: [13, 6] - access: [] - - target_room: 51 - entrance: 76 - teleporter: [59, 8] - access: ["LibraCrest"] -- name: Aquaria - id: 40 - game_objects: - - name: "Summer Aquaria" - object_id: 0 - type: "Trigger" - on_trigger: ["SummerAquaria"] - access: ["WakeWater"] - links: - - target_room: 221 - entrance: 77 - teleporter: [8, 6] - access: [] - - target_room: 41 - entrance: 81 - teleporter: [10, 5] - access: [] - - target_room: 42 - entrance: 82 - teleporter: [44, 4] - access: [] - - target_room: 44 - entrance: 83 - teleporter: [11, 5] - access: [] - - target_room: 71 - entrance: 89 - teleporter: [42, 0] - access: ["SummerAquaria"] - - target_room: 71 - entrance: 90 - teleporter: [43, 0] - access: ["SummerAquaria"] -- name: Phoebe's House - id: 41 - game_objects: - - name: "Aquaria - Phoebe's House Chest" - object_id: 0x41 - type: "Box" - access: [] - links: - - target_room: 40 - entrance: 93 - teleporter: [5, 8] - access: [] -- name: Aquaria Vendor House - id: 42 - game_objects: - - name: "Aquaria - Vendor" - object_id: 4 - type: "NPC" - access: [] - - name: "Aquaria - Vendor House Box" - object_id: 0x42 - type: "Box" - access: [] - links: - - target_room: 40 - entrance: 94 - teleporter: [40, 8] - access: [] - - target_room: 43 - entrance: 95 - teleporter: [47, 0] - access: [] -- name: Aquaria Gemini Room - id: 43 - game_objects: [] - links: - - target_room: 42 - entrance: 97 - teleporter: [48, 0] - access: [] - - target_room: 81 - entrance: 96 - teleporter: [72, 8] - access: ["GeminiCrest"] -- name: Aquaria INN - id: 44 - game_objects: [] - links: - - target_room: 40 - entrance: 98 - teleporter: [75, 8] - access: [] -- name: Wintry Cave 1F - East Ledge - id: 45 - game_objects: - - name: "Wintry Cave 1F - North Box" - object_id: 0x43 - type: "Box" - access: [] - - name: "Wintry Cave 1F - Entrance Box" - object_id: 0x46 - type: "Box" - access: [] - - name: "Wintry Cave 1F - Slippery Cliff Box" - object_id: 0x44 - type: "Box" - access: ["Claw"] - - name: "Wintry Cave 1F - Phoebe" - object_id: 5 - type: "NPC" - access: ["Phoebe1"] - links: - - target_room: 221 - entrance: 99 - teleporter: [49, 0] - access: [] - - target_room: 49 - entrance: 100 - teleporter: [14, 2] - access: ["Bomb"] - - target_room: 46 - access: ["Claw"] -- name: Wintry Cave 1F - Central Space - id: 46 - game_objects: - - name: "Wintry Cave 1F - Scenic Overlook Box" - object_id: 0x45 - type: "Box" - access: ["Claw"] - links: - - target_room: 45 - access: ["Claw"] - - target_room: 47 - access: ["Claw"] -- name: Wintry Cave 1F - West Ledge - id: 47 - game_objects: [] - links: - - target_room: 48 - entrance: 101 - teleporter: [15, 2] - access: ["Bomb"] - - target_room: 46 - access: ["Claw"] -- name: Wintry Cave 2F - id: 48 - game_objects: - - name: "Wintry Cave 2F - West Left Box" - object_id: 0x47 - type: "Box" - access: [] - - name: "Wintry Cave 2F - West Right Box" - object_id: 0x48 - type: "Box" - access: [] - - name: "Wintry Cave 2F - East Left Box" - object_id: 0x49 - type: "Box" - access: [] - - name: "Wintry Cave 2F - East Right Box" - object_id: 0x4A - type: "Box" - access: [] - links: - - target_room: 47 - entrance: 104 - teleporter: [97, 3] - access: [] - - target_room: 50 - entrance: 103 - teleporter: [50, 0] - access: [] -- name: Wintry Cave 3F Top - id: 49 - game_objects: - - name: "Wintry Cave 3F - West Box" - object_id: 0x4B - type: "Box" - access: [] - - name: "Wintry Cave 3F - East Box" - object_id: 0x4C - type: "Box" - access: [] - links: - - target_room: 45 - entrance: 105 - teleporter: [96, 3] - access: [] -- name: Wintry Cave 3F Bottom - id: 50 - game_objects: - - name: "Wintry Cave 3F - Squidite Chest" - object_id: 0x09 - type: "Chest" - access: ["Phanquid"] - - name: "Phanquid" - object_id: 0 - type: "Trigger" - on_trigger: ["Phanquid"] - access: [] - - name: "Wintry Cave 3F - Before Boss Box" - object_id: 0x4D - type: "Box" - access: [] - links: - - target_room: 48 - entrance: 106 - teleporter: [51, 0] - access: [] -- name: Life Temple - id: 51 - game_objects: - - name: "Life Temple - Box" - object_id: 0x4E - type: "Box" - access: [] - - name: "Life Temple - Mysterious Man" - object_id: 6 - type: "NPC" - access: [] - links: - - target_room: 222 - entrance: 107 - teleporter: [14, 6] - access: [] - - target_room: 39 - entrance: 108 - teleporter: [60, 8] - access: ["LibraCrest"] -- name: Fall Basin - id: 52 - game_objects: - - name: "Falls Basin - Snow Crab Chest" - object_id: 0x0A - type: "Chest" - access: ["FreezerCrab"] - - name: "Freezer Crab" - object_id: 0 - type: "Trigger" - on_trigger: ["FreezerCrab"] - access: [] - - name: "Falls Basin - Box" - object_id: 0x4F - type: "Box" - access: [] - links: - - target_room: 221 - entrance: 111 - teleporter: [53, 0] - access: [] -- name: Ice Pyramid B1 Taunt Room - id: 53 - game_objects: - - name: "Ice Pyramid B1 - Chest" - object_id: 0x0B - type: "Chest" - access: [] - - name: "Ice Pyramid B1 - West Box" - object_id: 0x50 - type: "Box" - access: [] - - name: "Ice Pyramid B1 - North Box" - object_id: 0x51 - type: "Box" - access: [] - - name: "Ice Pyramid B1 - East Box" - object_id: 0x52 - type: "Box" - access: [] - links: - - target_room: 68 - entrance: 113 - teleporter: [55, 0] - access: [] -- name: Ice Pyramid 1F Maze Lobby - id: 54 - game_objects: - - name: "Ice Pyramid 1F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid1FStatue"] - access: ["Sword"] - links: - - target_room: 221 - entrance: 114 - teleporter: [56, 0] - access: [] - - target_room: 55 - access: ["IcePyramid1FStatue"] -- name: Ice Pyramid 1F Maze - id: 55 - game_objects: - - name: "Ice Pyramid 1F - East Alcove Chest" - object_id: 0x0D - type: "Chest" - access: [] - - name: "Ice Pyramid 1F - Sandwiched Alcove Box" - object_id: 0x53 - type: "Box" - access: [] - - name: "Ice Pyramid 1F - Southwest Left Box" - object_id: 0x54 - type: "Box" - access: [] - - name: "Ice Pyramid 1F - Southwest Right Box" - object_id: 0x55 - type: "Box" - access: [] - links: - - target_room: 56 - entrance: 116 - teleporter: [57, 0] - access: [] - - target_room: 57 - entrance: 117 - teleporter: [58, 0] - access: [] - - target_room: 58 - entrance: 118 - teleporter: [59, 0] - access: [] - - target_room: 59 - entrance: 119 - teleporter: [60, 0] - access: [] - - target_room: 60 - entrance: 120 - teleporter: [61, 0] - access: [] - - target_room: 54 - access: ["IcePyramid1FStatue"] -- name: Ice Pyramid 2F South Tiled Room - id: 56 - game_objects: - - name: "Ice Pyramid 2F - South Side Glass Door Box" - object_id: 0x57 - type: "Box" - access: ["Sword"] - - name: "Ice Pyramid 2F - South Side East Box" - object_id: 0x5B - type: "Box" - access: [] - links: - - target_room: 55 - entrance: 122 - teleporter: [62, 0] - access: [] - - target_room: 61 - entrance: 123 - teleporter: [67, 0] - access: [] -- name: Ice Pyramid 2F West Room - id: 57 - game_objects: - - name: "Ice Pyramid 2F - Northwest Room Box" - object_id: 0x5A - type: "Box" - access: [] - links: - - target_room: 55 - entrance: 124 - teleporter: [63, 0] - access: [] -- name: Ice Pyramid 2F Center Room - id: 58 - game_objects: - - name: "Ice Pyramid 2F - Center Room Box" - object_id: 0x56 - type: "Box" - access: [] - links: - - target_room: 55 - entrance: 125 - teleporter: [64, 0] - access: [] -- name: Ice Pyramid 2F Small North Room - id: 59 - game_objects: - - name: "Ice Pyramid 2F - North Room Glass Door Box" - object_id: 0x58 - type: "Box" - access: ["Sword"] - links: - - target_room: 55 - entrance: 126 - teleporter: [65, 0] - access: [] -- name: Ice Pyramid 2F North Corridor - id: 60 - game_objects: - - name: "Ice Pyramid 2F - North Corridor Glass Door Box" - object_id: 0x59 - type: "Box" - access: ["Sword"] - links: - - target_room: 55 - entrance: 127 - teleporter: [66, 0] - access: [] - - target_room: 62 - entrance: 128 - teleporter: [68, 0] - access: [] -- name: Ice Pyramid 3F Two Boxes Room - id: 61 - game_objects: - - name: "Ice Pyramid 3F - Staircase Dead End Left Box" - object_id: 0x5E - type: "Box" - access: [] - - name: "Ice Pyramid 3F - Staircase Dead End Right Box" - object_id: 0x5F - type: "Box" - access: [] - links: - - target_room: 56 - entrance: 129 - teleporter: [69, 0] - access: [] -- name: Ice Pyramid 3F Main Loop - id: 62 - game_objects: - - name: "Ice Pyramid 3F - Inner Room North Box" - object_id: 0x5C - type: "Box" - access: [] - - name: "Ice Pyramid 3F - Inner Room South Box" - object_id: 0x5D - type: "Box" - access: [] - - name: "Ice Pyramid 3F - East Alcove Box" - object_id: 0x60 - type: "Box" - access: [] - - name: "Ice Pyramid 3F - Leapfrog Box" - object_id: 0x61 - type: "Box" - access: [] - - name: "Ice Pyramid 3F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid3FStatue"] - access: ["Sword"] - links: - - target_room: 60 - entrance: 130 - teleporter: [70, 0] - access: [] - - target_room: 63 - access: ["IcePyramid3FStatue"] -- name: Ice Pyramid 3F Blocked Room - id: 63 - game_objects: [] - links: - - target_room: 64 - entrance: 131 - teleporter: [71, 0] - access: [] - - target_room: 62 - access: ["IcePyramid3FStatue"] -- name: Ice Pyramid 4F Main Loop - id: 64 - game_objects: [] - links: - - target_room: 66 - entrance: 133 - teleporter: [73, 0] - access: [] - - target_room: 63 - entrance: 132 - teleporter: [72, 0] - access: [] - - target_room: 65 - access: ["IcePyramid4FStatue"] -- name: Ice Pyramid 4F Treasure Room - id: 65 - game_objects: - - name: "Ice Pyramid 4F - Chest" - object_id: 0x0C - type: "Chest" - access: [] - - name: "Ice Pyramid 4F - Northwest Box" - object_id: 0x62 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - West Left Box" - object_id: 0x63 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - West Right Box" - object_id: 0x64 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - South Left Box" - object_id: 0x65 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - South Right Box" - object_id: 0x66 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - East Left Box" - object_id: 0x67 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - East Right Box" - object_id: 0x68 - type: "Box" - access: [] - - name: "Ice Pyramid 4F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid4FStatue"] - access: ["Sword"] - links: - - target_room: 64 - access: ["IcePyramid4FStatue"] -- name: Ice Pyramid 5F Leap of Faith Room - id: 66 - game_objects: - - name: "Ice Pyramid 5F - Glass Door Left Box" - object_id: 0x69 - type: "Box" - access: ["IcePyramid5FStatue"] - - name: "Ice Pyramid 5F - West Ledge Box" - object_id: 0x6A - type: "Box" - access: [] - - name: "Ice Pyramid 5F - South Shelf Box" - object_id: 0x6B - type: "Box" - access: [] - - name: "Ice Pyramid 5F - South Leapfrog Box" - object_id: 0x6C - type: "Box" - access: [] - - name: "Ice Pyramid 5F - Glass Door Right Box" - object_id: 0x6D - type: "Box" - access: ["IcePyramid5FStatue"] - - name: "Ice Pyramid 5F - North Box" - object_id: 0x6E - type: "Box" - access: [] - links: - - target_room: 64 - entrance: 134 - teleporter: [74, 0] - access: [] - - target_room: 65 - access: [] - - target_room: 53 - access: ["Bomb", "Claw", "Sword"] -- name: Ice Pyramid 5F Stairs to Ice Golem - id: 67 - game_objects: - - name: "Ice Pyramid 5F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid5FStatue"] - access: ["Sword"] - links: - - target_room: 69 - entrance: 137 - teleporter: [76, 0] - access: [] - - target_room: 65 - access: [] - - target_room: 70 - entrance: 136 - teleporter: [75, 0] - access: [] -- name: Ice Pyramid Climbing Wall Room Lower Space - id: 68 - game_objects: [] - links: - - target_room: 53 - entrance: 139 - teleporter: [78, 0] - access: [] - - target_room: 69 - access: ["Claw"] -- name: Ice Pyramid Climbing Wall Room Upper Space - id: 69 - game_objects: [] - links: - - target_room: 67 - entrance: 140 - teleporter: [79, 0] - access: [] - - target_room: 68 - access: ["Claw"] -- name: Ice Pyramid Ice Golem Room - id: 70 - game_objects: - - name: "Ice Pyramid 6F - Ice Golem Chest" - object_id: 0x0E - type: "Chest" - access: ["IceGolem"] - - name: "Ice Golem" - object_id: 0 - type: "Trigger" - on_trigger: ["IceGolem"] - access: [] - links: - - target_room: 67 - entrance: 141 - teleporter: [80, 0] - access: [] - - target_room: 66 - access: [] -- name: Spencer Waterfall - id: 71 - game_objects: [] - links: - - target_room: 72 - entrance: 143 - teleporter: [81, 0] - access: [] - - target_room: 40 - entrance: 145 - teleporter: [82, 0] - access: [] - - target_room: 40 - entrance: 148 - teleporter: [83, 0] - access: [] -- name: Spencer Cave Normal Main - id: 72 - game_objects: - - name: "Spencer's Cave - Box" - object_id: 0x6F - type: "Box" - access: ["Claw"] - - name: "Spencer's Cave - Spencer" - object_id: 8 - type: "NPC" - access: [] - - name: "Spencer's Cave - Locked Chest" - object_id: 13 - type: "NPC" - access: ["VenusKey"] - links: - - target_room: 71 - entrance: 150 - teleporter: [85, 0] - access: [] -- name: Spencer Cave Normal South Ledge - id: 73 - game_objects: - - name: "Collapse Spencer's Cave" - object_id: 0 - type: "Trigger" - on_trigger: ["ShipLiberated"] - access: ["MegaGrenade"] - links: - - target_room: 227 - entrance: 151 - teleporter: [7, 6] - access: [] - - target_room: 203 - access: ["MegaGrenade"] -# - target_room: 72 # access to spencer? -# access: ["MegaGrenade"] -- name: Spencer Cave Caved In Main Loop - id: 203 - game_objects: [] - links: - - target_room: 73 - access: [] - - target_room: 207 - entrance: 156 - teleporter: [36, 8] - access: ["MobiusCrest"] - - target_room: 204 - access: ["Claw"] - - target_room: 205 - access: ["Bomb"] -- name: Spencer Cave Caved In Waters - id: 204 - game_objects: - - name: "Bomb Libra Block" - object_id: 0 - type: "Trigger" - on_trigger: ["SpencerCaveLibraBlockBombed"] - access: ["MegaGrenade", "Claw"] - links: - - target_room: 203 - access: ["Claw"] -- name: Spencer Cave Caved In Libra Nook - id: 205 - game_objects: [] - links: - - target_room: 206 - entrance: 153 - teleporter: [33, 8] - access: ["LibraCrest"] -- name: Spencer Cave Caved In Libra Corridor - id: 206 - game_objects: [] - links: - - target_room: 205 - entrance: 154 - teleporter: [34, 8] - access: ["LibraCrest"] - - target_room: 207 - access: ["SpencerCaveLibraBlockBombed"] -- name: Spencer Cave Caved In Mobius Chest - id: 207 - game_objects: - - name: "Spencer's Cave - Mobius Chest" - object_id: 0x0F - type: "Chest" - access: [] - links: - - target_room: 203 - entrance: 155 - teleporter: [35, 8] - access: ["MobiusCrest"] - - target_room: 206 - access: ["Bomb"] -- name: Wintry Temple Outer Room - id: 74 - game_objects: [] - links: - - target_room: 223 - entrance: 157 - teleporter: [15, 6] - access: [] -- name: Wintry Temple Inner Room - id: 75 - game_objects: - - name: "Wintry Temple - West Box" - object_id: 0x70 - type: "Box" - access: [] - - name: "Wintry Temple - North Box" - object_id: 0x71 - type: "Box" - access: [] - links: - - target_room: 92 - entrance: 158 - teleporter: [62, 8] - access: ["GeminiCrest"] -- name: Fireburg Upper Plaza - id: 76 - game_objects: [] - links: - - target_room: 224 - entrance: 159 - teleporter: [9, 6] - access: [] - - target_room: 80 - entrance: 163 - teleporter: [91, 0] - access: [] - - target_room: 77 - entrance: 164 - teleporter: [98, 8] # original value [16, 2] - access: [] - - target_room: 82 - entrance: 165 - teleporter: [96, 8] # original value [17, 2] - access: [] - - target_room: 208 - access: ["Claw"] -- name: Fireburg Lower Plaza - id: 208 - game_objects: - - name: "Fireburg - Hidden Tunnel Box" - object_id: 0x74 - type: "Box" - access: [] - links: - - target_room: 76 - access: ["Claw"] - - target_room: 78 - entrance: 166 - teleporter: [11, 8] - access: ["MultiKey"] -- name: Reuben's House - id: 77 - game_objects: - - name: "Fireburg - Reuben's House Arion" - object_id: 14 - type: "NPC" - access: ["ReubenDadSaved"] - - name: "Reuben Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Reuben1"] - access: [] - - name: "Fireburg - Reuben's House Box" - object_id: 0x75 - type: "Box" - access: [] - links: - - target_room: 76 - entrance: 167 - teleporter: [98, 3] - access: [] -- name: GrenadeMan's House - id: 78 - game_objects: - - name: "Fireburg - Locked House Man" - object_id: 12 - type: "NPC" - access: [] - links: - - target_room: 208 - entrance: 168 - teleporter: [9, 8] - access: ["MultiKey"] - - target_room: 79 - entrance: 169 - teleporter: [93, 0] - access: [] -- name: GrenadeMan's Mobius Room - id: 79 - game_objects: [] - links: - - target_room: 78 - entrance: 170 - teleporter: [94, 0] - access: [] - - target_room: 161 - entrance: 171 - teleporter: [54, 8] - access: ["MobiusCrest"] -- name: Fireburg Vendor House - id: 80 - game_objects: - - name: "Fireburg - Vendor" - object_id: 11 - type: "NPC" - access: [] - links: - - target_room: 76 - entrance: 172 - teleporter: [95, 0] - access: [] - - target_room: 81 - entrance: 173 - teleporter: [96, 0] - access: [] -- name: Fireburg Gemini Room - id: 81 - game_objects: [] - links: - - target_room: 80 - entrance: 174 - teleporter: [97, 0] - access: [] - - target_room: 43 - entrance: 175 - teleporter: [45, 8] - access: ["GeminiCrest"] -- name: Fireburg Hotel Lobby - id: 82 - game_objects: - - name: "Fireburg - Tristam" - object_id: 10 - type: "NPC" - access: ["Tristam", "TristamBoneItemGiven"] - links: - - target_room: 76 - entrance: 177 - teleporter: [99, 3] - access: [] - - target_room: 83 - entrance: 176 - teleporter: [213, 0] - access: [] -- name: Fireburg Hotel Beds - id: 83 - game_objects: [] - links: - - target_room: 82 - entrance: 178 - teleporter: [214, 0] - access: [] -- name: Mine Exterior North West Platforms - id: 84 - game_objects: [] - links: - - target_room: 224 - entrance: 179 - teleporter: [98, 0] - access: [] - - target_room: 88 - entrance: 181 - teleporter: [20, 2] - access: ["Bomb"] - - target_room: 85 - access: ["Claw"] - - target_room: 86 - access: ["Claw"] - - target_room: 87 - access: ["Claw"] -- name: Mine Exterior Central Ledge - id: 85 - game_objects: [] - links: - - target_room: 90 - entrance: 183 - teleporter: [22, 2] - access: ["Bomb"] - - target_room: 84 - access: ["Claw"] -- name: Mine Exterior North Ledge - id: 86 - game_objects: [] - links: - - target_room: 89 - entrance: 182 - teleporter: [21, 2] - access: ["Bomb"] - - target_room: 85 - access: ["Claw"] -- name: Mine Exterior South East Platforms - id: 87 - game_objects: - - name: "Jinn" - object_id: 0 - type: "Trigger" - on_trigger: ["Jinn"] - access: [] - links: - - target_room: 91 - entrance: 180 - teleporter: [99, 0] - access: ["Jinn"] - - target_room: 86 - access: [] - - target_room: 85 - access: ["Claw"] -- name: Mine Parallel Room - id: 88 - game_objects: - - name: "Mine - Parallel Room West Box" - object_id: 0x77 - type: "Box" - access: ["Claw"] - - name: "Mine - Parallel Room East Box" - object_id: 0x78 - type: "Box" - access: ["Claw"] - links: - - target_room: 84 - entrance: 185 - teleporter: [100, 3] - access: [] -- name: Mine Crescent Room - id: 89 - game_objects: - - name: "Mine - Crescent Room Chest" - object_id: 0x10 - type: "Chest" - access: [] - links: - - target_room: 86 - entrance: 186 - teleporter: [101, 3] - access: [] -- name: Mine Climbing Room - id: 90 - game_objects: - - name: "Mine - Glitchy Collision Cave Box" - object_id: 0x76 - type: "Box" - access: ["Claw"] - links: - - target_room: 85 - entrance: 187 - teleporter: [102, 3] - access: [] -- name: Mine Cliff - id: 91 - game_objects: - - name: "Mine - Cliff Southwest Box" - object_id: 0x79 - type: "Box" - access: [] - - name: "Mine - Cliff Northwest Box" - object_id: 0x7A - type: "Box" - access: [] - - name: "Mine - Cliff Northeast Box" - object_id: 0x7B - type: "Box" - access: [] - - name: "Mine - Cliff Southeast Box" - object_id: 0x7C - type: "Box" - access: [] - - name: "Mine - Reuben" - object_id: 7 - type: "NPC" - access: ["Reuben1"] - - name: "Reuben's dad Saved" - object_id: 0 - type: "Trigger" - on_trigger: ["ReubenDadSaved"] - access: ["MegaGrenade"] - links: - - target_room: 87 - entrance: 188 - teleporter: [100, 0] - access: [] -- name: Sealed Temple - id: 92 - game_objects: - - name: "Sealed Temple - West Box" - object_id: 0x7D - type: "Box" - access: [] - - name: "Sealed Temple - East Box" - object_id: 0x7E - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 190 - teleporter: [16, 6] - access: [] - - target_room: 75 - entrance: 191 - teleporter: [63, 8] - access: ["GeminiCrest"] -- name: Volcano Base - id: 93 - game_objects: - - name: "Volcano - Base Chest" - object_id: 0x11 - type: "Chest" - access: [] - - name: "Volcano - Base West Box" - object_id: 0x7F - type: "Box" - access: [] - - name: "Volcano - Base East Left Box" - object_id: 0x80 - type: "Box" - access: [] - - name: "Volcano - Base East Right Box" - object_id: 0x81 - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 192 - teleporter: [103, 0] - access: [] - - target_room: 98 - entrance: 196 - teleporter: [31, 8] - access: [] - - target_room: 96 - entrance: 197 - teleporter: [30, 8] - access: [] -- name: Volcano Top Left - id: 94 - game_objects: - - name: "Volcano - Medusa Chest" - object_id: 0x12 - type: "Chest" - access: ["Medusa"] - - name: "Medusa" - object_id: 0 - type: "Trigger" - on_trigger: ["Medusa"] - access: [] - - name: "Volcano - Behind Medusa Box" - object_id: 0x82 - type: "Box" - access: [] - links: - - target_room: 209 - entrance: 199 - teleporter: [26, 8] - access: [] -- name: Volcano Top Right - id: 95 - game_objects: - - name: "Volcano - Top of the Volcano Left Box" - object_id: 0x83 - type: "Box" - access: [] - - name: "Volcano - Top of the Volcano Right Box" - object_id: 0x84 - type: "Box" - access: [] - links: - - target_room: 99 - entrance: 200 - teleporter: [79, 8] - access: [] -- name: Volcano Right Path - id: 96 - game_objects: - - name: "Volcano - Right Path Box" - object_id: 0x87 - type: "Box" - access: [] - links: - - target_room: 93 - entrance: 201 - teleporter: [15, 8] - access: [] -- name: Volcano Left Path - id: 98 - game_objects: - - name: "Volcano - Left Path Box" - object_id: 0x86 - type: "Box" - access: [] - links: - - target_room: 93 - entrance: 204 - teleporter: [27, 8] - access: [] - - target_room: 99 - entrance: 202 - teleporter: [25, 2] - access: [] - - target_room: 209 - entrance: 203 - teleporter: [26, 2] - access: [] -- name: Volcano Cross Left-Right - id: 99 - game_objects: [] - links: - - target_room: 95 - entrance: 206 - teleporter: [29, 8] - access: [] - - target_room: 98 - entrance: 205 - teleporter: [103, 3] - access: [] -- name: Volcano Cross Right-Left - id: 209 - game_objects: - - name: "Volcano - Crossover Section Box" - object_id: 0x85 - type: "Box" - access: [] - links: - - target_room: 98 - entrance: 208 - teleporter: [104, 3] - access: [] - - target_room: 94 - entrance: 207 - teleporter: [28, 8] - access: [] -- name: Lava Dome Inner Ring Main Loop - id: 100 - game_objects: - - name: "Lava Dome - Exterior Caldera Near Switch Cliff Box" - object_id: 0x88 - type: "Box" - access: [] - - name: "Lava Dome - Exterior South Cliff Box" - object_id: 0x89 - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 209 - teleporter: [104, 0] - access: [] - - target_room: 113 - entrance: 211 - teleporter: [105, 0] - access: [] - - target_room: 114 - entrance: 212 - teleporter: [106, 0] - access: [] - - target_room: 116 - entrance: 213 - teleporter: [108, 0] - access: [] - - target_room: 118 - entrance: 214 - teleporter: [111, 0] - access: [] -- name: Lava Dome Inner Ring Center Ledge - id: 101 - game_objects: - - name: "Lava Dome - Exterior Center Dropoff Ledge Box" - object_id: 0x8A - type: "Box" - access: [] - links: - - target_room: 115 - entrance: 215 - teleporter: [107, 0] - access: [] - - target_room: 100 - access: ["Claw"] -- name: Lava Dome Inner Ring Plate Ledge - id: 102 - game_objects: - - name: "Lava Dome Plate" - object_id: 0 - type: "Trigger" - on_trigger: ["LavaDomePlate"] - access: [] - links: - - target_room: 119 - entrance: 216 - teleporter: [109, 0] - access: [] -- name: Lava Dome Inner Ring Upper Ledge West - id: 103 - game_objects: [] - links: - - target_room: 111 - entrance: 219 - teleporter: [112, 0] - access: [] - - target_room: 108 - entrance: 220 - teleporter: [113, 0] - access: [] - - target_room: 104 - access: ["Claw"] - - target_room: 100 - access: ["Claw"] -- name: Lava Dome Inner Ring Upper Ledge East - id: 104 - game_objects: [] - links: - - target_room: 110 - entrance: 218 - teleporter: [110, 0] - access: [] - - target_room: 103 - access: ["Claw"] -- name: Lava Dome Inner Ring Big Door Ledge - id: 105 - game_objects: [] - links: - - target_room: 107 - entrance: 221 - teleporter: [114, 0] - access: [] - - target_room: 121 - entrance: 222 - teleporter: [29, 2] - access: ["LavaDomePlate"] -- name: Lava Dome Inner Ring Tiny Bottom Ledge - id: 106 - game_objects: - - name: "Lava Dome - Exterior Dead End Caldera Box" - object_id: 0x8B - type: "Box" - access: [] - links: - - target_room: 120 - entrance: 226 - teleporter: [115, 0] - access: [] -- name: Lava Dome Jump Maze II - id: 107 - game_objects: - - name: "Lava Dome - Gold Maze Northwest Box" - object_id: 0x8C - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Southwest Box" - object_id: 0xF6 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Northeast Box" - object_id: 0xF7 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze North Box" - object_id: 0xF8 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Center Box" - object_id: 0xF9 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Southeast Box" - object_id: 0xFA - type: "Box" - access: [] - links: - - target_room: 105 - entrance: 227 - teleporter: [116, 0] - access: [] - - target_room: 108 - entrance: 228 - teleporter: [119, 0] - access: [] - - target_room: 120 - entrance: 229 - teleporter: [120, 0] - access: [] -- name: Lava Dome Up-Down Corridor - id: 108 - game_objects: [] - links: - - target_room: 107 - entrance: 231 - teleporter: [118, 0] - access: [] - - target_room: 103 - entrance: 230 - teleporter: [117, 0] - access: [] -- name: Lava Dome Jump Maze I - id: 109 - game_objects: - - name: "Lava Dome - Bare Maze Leapfrog Alcove North Box" - object_id: 0x8D - type: "Box" - access: [] - - name: "Lava Dome - Bare Maze Leapfrog Alcove South Box" - object_id: 0x8E - type: "Box" - access: [] - - name: "Lava Dome - Bare Maze Center Box" - object_id: 0x8F - type: "Box" - access: [] - - name: "Lava Dome - Bare Maze Southwest Box" - object_id: 0x90 - type: "Box" - access: [] - links: - - target_room: 118 - entrance: 232 - teleporter: [121, 0] - access: [] - - target_room: 111 - entrance: 233 - teleporter: [122, 0] - access: [] -- name: Lava Dome Pointless Room - id: 110 - game_objects: [] - links: - - target_room: 104 - entrance: 234 - teleporter: [123, 0] - access: [] -- name: Lava Dome Lower Moon Helm Room - id: 111 - game_objects: - - name: "Lava Dome - U-Bend Room North Box" - object_id: 0x92 - type: "Box" - access: [] - - name: "Lava Dome - U-Bend Room South Box" - object_id: 0x93 - type: "Box" - access: [] - links: - - target_room: 103 - entrance: 235 - teleporter: [124, 0] - access: [] - - target_room: 109 - entrance: 236 - teleporter: [125, 0] - access: [] -- name: Lava Dome Moon Helm Room - id: 112 - game_objects: - - name: "Lava Dome - Beyond River Room Chest" - object_id: 0x13 - type: "Chest" - access: [] - - name: "Lava Dome - Beyond River Room Box" - object_id: 0x91 - type: "Box" - access: [] - links: - - target_room: 117 - entrance: 237 - teleporter: [126, 0] - access: [] -- name: Lava Dome Three Jumps Room - id: 113 - game_objects: - - name: "Lava Dome - Three Jumps Room Box" - object_id: 0x96 - type: "Box" - access: [] - links: - - target_room: 100 - entrance: 238 - teleporter: [127, 0] - access: [] -- name: Lava Dome Life Chest Room Lower Ledge - id: 114 - game_objects: - - name: "Lava Dome - Gold Bar Room Boulder Chest" - object_id: 0x1C - type: "Chest" - access: ["MegaGrenade"] - links: - - target_room: 100 - entrance: 239 - teleporter: [128, 0] - access: [] - - target_room: 115 - access: ["Claw"] -- name: Lava Dome Life Chest Room Upper Ledge - id: 115 - game_objects: - - name: "Lava Dome - Gold Bar Room Leapfrog Alcove Box West" - object_id: 0x94 - type: "Box" - access: [] - - name: "Lava Dome - Gold Bar Room Leapfrog Alcove Box East" - object_id: 0x95 - type: "Box" - access: [] - links: - - target_room: 101 - entrance: 240 - teleporter: [129, 0] - access: [] - - target_room: 114 - access: ["Claw"] -- name: Lava Dome Big Jump Room Main Area - id: 116 - game_objects: - - name: "Lava Dome - Lava River Room North Box" - object_id: 0x98 - type: "Box" - access: [] - - name: "Lava Dome - Lava River Room East Box" - object_id: 0x99 - type: "Box" - access: [] - - name: "Lava Dome - Lava River Room South Box" - object_id: 0x9A - type: "Box" - access: [] - links: - - target_room: 100 - entrance: 241 - teleporter: [133, 0] - access: [] - - target_room: 119 - entrance: 243 - teleporter: [132, 0] - access: [] - - target_room: 117 - access: ["MegaGrenade"] -- name: Lava Dome Big Jump Room MegaGrenade Area - id: 117 - game_objects: [] - links: - - target_room: 112 - entrance: 242 - teleporter: [131, 0] - access: [] - - target_room: 116 - access: ["Bomb"] -- name: Lava Dome Split Corridor - id: 118 - game_objects: - - name: "Lava Dome - Split Corridor Box" - object_id: 0x97 - type: "Box" - access: [] - links: - - target_room: 109 - entrance: 244 - teleporter: [130, 0] - access: [] - - target_room: 100 - entrance: 245 - teleporter: [134, 0] - access: [] -- name: Lava Dome Plate Corridor - id: 119 - game_objects: [] - links: - - target_room: 102 - entrance: 246 - teleporter: [135, 0] - access: [] - - target_room: 116 - entrance: 247 - teleporter: [137, 0] - access: [] -- name: Lava Dome Four Boxes Stairs - id: 120 - game_objects: - - name: "Lava Dome - Caldera Stairway West Left Box" - object_id: 0x9B - type: "Box" - access: [] - - name: "Lava Dome - Caldera Stairway West Right Box" - object_id: 0x9C - type: "Box" - access: [] - - name: "Lava Dome - Caldera Stairway East Left Box" - object_id: 0x9D - type: "Box" - access: [] - - name: "Lava Dome - Caldera Stairway East Right Box" - object_id: 0x9E - type: "Box" - access: [] - links: - - target_room: 107 - entrance: 248 - teleporter: [136, 0] - access: [] - - target_room: 106 - entrance: 249 - teleporter: [16, 0] - access: [] -- name: Lava Dome Hydra Room - id: 121 - game_objects: - - name: "Lava Dome - Dualhead Hydra Chest" - object_id: 0x14 - type: "Chest" - access: ["DualheadHydra"] - - name: "Dualhead Hydra" - object_id: 0 - type: "Trigger" - on_trigger: ["DualheadHydra"] - access: [] - - name: "Lava Dome - Hydra Room Northwest Box" - object_id: 0x9F - type: "Box" - access: [] - - name: "Lava Dome - Hydra Room Southweast Box" - object_id: 0xA0 - type: "Box" - access: [] - links: - - target_room: 105 - entrance: 250 - teleporter: [105, 3] - access: [] - - target_room: 122 - entrance: 251 - teleporter: [138, 0] - access: ["DualheadHydra"] -- name: Lava Dome Escape Corridor - id: 122 - game_objects: [] - links: - - target_room: 121 - entrance: 253 - teleporter: [139, 0] - access: [] -- name: Rope Bridge - id: 123 - game_objects: - - name: "Rope Bridge - West Box" - object_id: 0xA3 - type: "Box" - access: [] - - name: "Rope Bridge - East Box" - object_id: 0xA4 - type: "Box" - access: [] - links: - - target_room: 226 - entrance: 255 - teleporter: [140, 0] - access: [] -- name: Alive Forest - id: 124 - game_objects: - - name: "Alive Forest - Tree Stump Chest" - object_id: 0x15 - type: "Chest" - access: ["Axe"] - - name: "Alive Forest - Near Entrance Box" - object_id: 0xA5 - type: "Box" - access: ["Axe"] - - name: "Alive Forest - After Bridge Box" - object_id: 0xA6 - type: "Box" - access: ["Axe"] - - name: "Alive Forest - Gemini Stump Box" - object_id: 0xA7 - type: "Box" - access: ["Axe"] - links: - - target_room: 226 - entrance: 272 - teleporter: [142, 0] - access: ["Axe"] - - target_room: 21 - entrance: 275 - teleporter: [64, 8] - access: ["LibraCrest", "Axe"] - - target_room: 22 - entrance: 276 - teleporter: [65, 8] - access: ["GeminiCrest", "Axe"] - - target_room: 23 - entrance: 277 - teleporter: [66, 8] - access: ["MobiusCrest", "Axe"] - - target_room: 125 - entrance: 274 - teleporter: [143, 0] - access: ["Axe"] -- name: Giant Tree 1F Main Area - id: 125 - game_objects: - - name: "Giant Tree 1F - Northwest Box" - object_id: 0xA8 - type: "Box" - access: [] - - name: "Giant Tree 1F - Southwest Box" - object_id: 0xA9 - type: "Box" - access: [] - - name: "Giant Tree 1F - Center Box" - object_id: 0xAA - type: "Box" - access: [] - - name: "Giant Tree 1F - East Box" - object_id: 0xAB - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 278 - teleporter: [56, 1] # [49, 8] script restored if no map shuffling - access: [] - - target_room: 202 - access: ["DragonClaw"] -- name: Giant Tree 1F North Island - id: 202 - game_objects: [] - links: - - target_room: 127 - entrance: 280 - teleporter: [144, 0] - access: [] - - target_room: 125 - access: ["DragonClaw"] -- name: Giant Tree 1F Central Island - id: 126 - game_objects: [] - links: - - target_room: 202 - access: ["DragonClaw"] -- name: Giant Tree 2F Main Lobby - id: 127 - game_objects: - - name: "Giant Tree 2F - North Box" - object_id: 0xAC - type: "Box" - access: [] - links: - - target_room: 126 - access: ["DragonClaw"] - - target_room: 125 - entrance: 281 - teleporter: [145, 0] - access: [] - - target_room: 133 - entrance: 283 - teleporter: [149, 0] - access: [] - - target_room: 129 - access: ["DragonClaw"] -- name: Giant Tree 2F West Ledge - id: 128 - game_objects: - - name: "Giant Tree 2F - Dropdown Ledge Box" - object_id: 0xAE - type: "Box" - access: [] - links: - - target_room: 140 - entrance: 284 - teleporter: [147, 0] - access: ["Sword"] - - target_room: 130 - access: ["DragonClaw"] -- name: Giant Tree 2F Lower Area - id: 129 - game_objects: - - name: "Giant Tree 2F - South Box" - object_id: 0xAD - type: "Box" - access: [] - links: - - target_room: 130 - access: ["Claw"] - - target_room: 131 - access: ["Claw"] -- name: Giant Tree 2F Central Island - id: 130 - game_objects: [] - links: - - target_room: 129 - access: ["Claw"] - - target_room: 135 - entrance: 282 - teleporter: [146, 0] - access: ["Sword"] -- name: Giant Tree 2F East Ledge - id: 131 - game_objects: [] - links: - - target_room: 129 - access: ["Claw"] - - target_room: 130 - access: ["DragonClaw"] -- name: Giant Tree 2F Meteor Chest Room - id: 132 - game_objects: - - name: "Giant Tree 2F - Gidrah Chest" - object_id: 0x16 - type: "Chest" - access: [] - links: - - target_room: 133 - entrance: 285 - teleporter: [148, 0] - access: [] -- name: Giant Tree 2F Mushroom Room - id: 133 - game_objects: - - name: "Giant Tree 2F - Mushroom Tunnel West Box" - object_id: 0xAF - type: "Box" - access: ["Axe"] - - name: "Giant Tree 2F - Mushroom Tunnel East Box" - object_id: 0xB0 - type: "Box" - access: ["Axe"] - links: - - target_room: 127 - entrance: 286 - teleporter: [150, 0] - access: ["Axe"] - - target_room: 132 - entrance: 287 - teleporter: [151, 0] - access: ["Axe", "Gidrah"] -- name: Giant Tree 3F Central Island - id: 135 - game_objects: - - name: "Giant Tree 3F - Central Island Box" - object_id: 0xB3 - type: "Box" - access: [] - links: - - target_room: 130 - entrance: 288 - teleporter: [152, 0] - access: [] - - target_room: 136 - access: ["Claw"] - - target_room: 137 - access: ["DragonClaw"] -- name: Giant Tree 3F Central Area - id: 136 - game_objects: - - name: "Giant Tree 3F - Center North Box" - object_id: 0xB1 - type: "Box" - access: [] - - name: "Giant Tree 3F - Center West Box" - object_id: 0xB2 - type: "Box" - access: [] - links: - - target_room: 135 - access: ["Claw"] - - target_room: 127 - access: [] - - target_room: 131 - access: [] -- name: Giant Tree 3F Lower Ledge - id: 137 - game_objects: [] - links: - - target_room: 135 - access: ["DragonClaw"] - - target_room: 142 - entrance: 289 - teleporter: [153, 0] - access: ["Sword"] -- name: Giant Tree 3F West Area - id: 138 - game_objects: - - name: "Giant Tree 3F - West Side Box" - object_id: 0xB4 - type: "Box" - access: [] - links: - - target_room: 128 - access: [] - - target_room: 210 - entrance: 290 - teleporter: [154, 0] - access: [] -- name: Giant Tree 3F Middle Up Island - id: 139 - game_objects: [] - links: - - target_room: 136 - access: ["Claw"] -- name: Giant Tree 3F West Platform - id: 140 - game_objects: [] - links: - - target_room: 139 - access: ["Claw"] - - target_room: 141 - access: ["Claw"] - - target_room: 128 - entrance: 291 - teleporter: [155, 0] - access: [] -- name: Giant Tree 3F North Ledge - id: 141 - game_objects: [] - links: - - target_room: 143 - entrance: 292 - teleporter: [156, 0] - access: ["Sword"] - - target_room: 139 - access: ["Claw"] - - target_room: 136 - access: ["Claw"] -- name: Giant Tree Worm Room Upper Ledge - id: 142 - game_objects: - - name: "Giant Tree 3F - Worm Room North Box" - object_id: 0xB5 - type: "Box" - access: ["Axe"] - - name: "Giant Tree 3F - Worm Room South Box" - object_id: 0xB6 - type: "Box" - access: ["Axe"] - links: - - target_room: 137 - entrance: 293 - teleporter: [157, 0] - access: ["Axe"] - - target_room: 210 - access: ["Axe", "Claw"] -- name: Giant Tree Worm Room Lower Ledge - id: 210 - game_objects: [] - links: - - target_room: 138 - entrance: 294 - teleporter: [158, 0] - access: [] -- name: Giant Tree 4F Lower Floor - id: 143 - game_objects: [] - links: - - target_room: 141 - entrance: 295 - teleporter: [159, 0] - access: [] - - target_room: 148 - entrance: 296 - teleporter: [160, 0] - access: [] - - target_room: 148 - entrance: 297 - teleporter: [161, 0] - access: [] - - target_room: 147 - entrance: 298 - teleporter: [162, 0] - access: ["Sword"] -- name: Giant Tree 4F Middle Floor - id: 144 - game_objects: - - name: "Giant Tree 4F - Highest Platform North Box" - object_id: 0xB7 - type: "Box" - access: [] - - name: "Giant Tree 4F - Highest Platform South Box" - object_id: 0xB8 - type: "Box" - access: [] - links: - - target_room: 149 - entrance: 299 - teleporter: [163, 0] - access: [] - - target_room: 145 - access: ["Claw"] - - target_room: 146 - access: ["DragonClaw"] -- name: Giant Tree 4F Upper Floor - id: 145 - game_objects: [] - links: - - target_room: 150 - entrance: 300 - teleporter: [164, 0] - access: ["Sword"] - - target_room: 144 - access: ["Claw"] -- name: Giant Tree 4F South Ledge - id: 146 - game_objects: - - name: "Giant Tree 4F - Hook Ledge Northeast Box" - object_id: 0xB9 - type: "Box" - access: [] - - name: "Giant Tree 4F - Hook Ledge Southwest Box" - object_id: 0xBA - type: "Box" - access: [] - links: - - target_room: 144 - access: ["DragonClaw"] -- name: Giant Tree 4F Slime Room East Area - id: 147 - game_objects: - - name: "Giant Tree 4F - East Slime Room Box" - object_id: 0xBC - type: "Box" - access: ["Axe"] - links: - - target_room: 143 - entrance: 304 - teleporter: [168, 0] - access: [] -- name: Giant Tree 4F Slime Room West Area - id: 148 - game_objects: [] - links: - - target_room: 143 - entrance: 303 - teleporter: [167, 0] - access: ["Axe"] - - target_room: 143 - entrance: 302 - teleporter: [166, 0] - access: ["Axe"] - - target_room: 149 - access: ["Axe", "Claw"] -- name: Giant Tree 4F Slime Room Platform - id: 149 - game_objects: - - name: "Giant Tree 4F - West Slime Room Box" - object_id: 0xBB - type: "Box" - access: [] - links: - - target_room: 144 - entrance: 301 - teleporter: [165, 0] - access: [] - - target_room: 148 - access: ["Claw"] -- name: Giant Tree 5F Lower Area - id: 150 - game_objects: - - name: "Giant Tree 5F - Northwest Left Box" - object_id: 0xBD - type: "Box" - access: [] - - name: "Giant Tree 5F - Northwest Right Box" - object_id: 0xBE - type: "Box" - access: [] - - name: "Giant Tree 5F - South Left Box" - object_id: 0xBF - type: "Box" - access: [] - - name: "Giant Tree 5F - South Right Box" - object_id: 0xC0 - type: "Box" - access: [] - links: - - target_room: 145 - entrance: 305 - teleporter: [169, 0] - access: [] - - target_room: 151 - access: ["Claw"] - - target_room: 143 - access: [] -- name: Giant Tree 5F Gidrah Platform - id: 151 - game_objects: - - name: "Gidrah" - object_id: 0 - type: "Trigger" - on_trigger: ["Gidrah"] - access: [] - links: - - target_room: 150 - access: ["Claw"] -- name: Kaidge Temple Lower Ledge - id: 152 - game_objects: [] - links: - - target_room: 226 - entrance: 307 - teleporter: [18, 6] - access: [] - - target_room: 153 - access: ["Claw"] -- name: Kaidge Temple Upper Ledge - id: 153 - game_objects: - - name: "Kaidge Temple - Box" - object_id: 0xC1 - type: "Box" - access: [] - links: - - target_room: 185 - entrance: 308 - teleporter: [71, 8] - access: ["MobiusCrest"] - - target_room: 152 - access: ["Claw"] -- name: Windhole Temple - id: 154 - game_objects: - - name: "Windhole Temple - Box" - object_id: 0xC2 - type: "Box" - access: [] - links: - - target_room: 226 - entrance: 309 - teleporter: [173, 0] - access: [] -- name: Mount Gale - id: 155 - game_objects: - - name: "Mount Gale - Dullahan Chest" - object_id: 0x17 - type: "Chest" - access: ["DragonClaw", "Dullahan"] - - name: "Dullahan" - object_id: 0 - type: "Trigger" - on_trigger: ["Dullahan"] - access: ["DragonClaw"] - - name: "Mount Gale - East Box" - object_id: 0xC3 - type: "Box" - access: ["DragonClaw"] - - name: "Mount Gale - West Box" - object_id: 0xC4 - type: "Box" - access: [] - links: - - target_room: 226 - entrance: 310 - teleporter: [174, 0] - access: [] -- name: Windia - id: 156 - game_objects: [] - links: - - target_room: 226 - entrance: 312 - teleporter: [10, 6] - access: [] - - target_room: 157 - entrance: 320 - teleporter: [30, 5] - access: [] - - target_room: 163 - entrance: 321 - teleporter: [97, 8] - access: [] - - target_room: 165 - entrance: 322 - teleporter: [32, 5] - access: [] - - target_room: 159 - entrance: 323 - teleporter: [176, 4] - access: [] - - target_room: 160 - entrance: 324 - teleporter: [177, 4] - access: [] -- name: Otto's House - id: 157 - game_objects: - - name: "Otto" - object_id: 0 - type: "Trigger" - on_trigger: ["RainbowBridge"] - access: ["ThunderRock"] - links: - - target_room: 156 - entrance: 327 - teleporter: [106, 3] - access: [] - - target_room: 158 - entrance: 326 - teleporter: [33, 2] - access: [] -- name: Otto's Attic - id: 158 - game_objects: - - name: "Windia - Otto's Attic Box" - object_id: 0xC5 - type: "Box" - access: [] - links: - - target_room: 157 - entrance: 328 - teleporter: [107, 3] - access: [] -- name: Windia Kid House - id: 159 - game_objects: [] - links: - - target_room: 156 - entrance: 329 - teleporter: [178, 0] - access: [] - - target_room: 161 - entrance: 330 - teleporter: [180, 0] - access: [] -- name: Windia Old People House - id: 160 - game_objects: [] - links: - - target_room: 156 - entrance: 331 - teleporter: [179, 0] - access: [] - - target_room: 162 - entrance: 332 - teleporter: [181, 0] - access: [] -- name: Windia Kid House Basement - id: 161 - game_objects: [] - links: - - target_room: 159 - entrance: 333 - teleporter: [182, 0] - access: [] - - target_room: 79 - entrance: 334 - teleporter: [44, 8] - access: ["MobiusCrest"] -- name: Windia Old People House Basement - id: 162 - game_objects: - - name: "Windia - Mobius Basement West Box" - object_id: 0xC8 - type: "Box" - access: [] - - name: "Windia - Mobius Basement East Box" - object_id: 0xC9 - type: "Box" - access: [] - links: - - target_room: 160 - entrance: 335 - teleporter: [183, 0] - access: [] - - target_room: 186 - entrance: 336 - teleporter: [43, 8] - access: ["MobiusCrest"] -- name: Windia Inn Lobby - id: 163 - game_objects: [] - links: - - target_room: 156 - entrance: 338 - teleporter: [135, 3] - access: [] - - target_room: 164 - entrance: 337 - teleporter: [102, 8] - access: [] -- name: Windia Inn Beds - id: 164 - game_objects: - - name: "Windia - Inn Bedroom North Box" - object_id: 0xC6 - type: "Box" - access: [] - - name: "Windia - Inn Bedroom South Box" - object_id: 0xC7 - type: "Box" - access: [] - - name: "Windia - Kaeli" - object_id: 15 - type: "NPC" - access: ["Kaeli2"] - links: - - target_room: 163 - entrance: 339 - teleporter: [216, 0] - access: [] -- name: Windia Vendor House - id: 165 - game_objects: - - name: "Windia - Vendor" - object_id: 16 - type: "NPC" - access: [] - links: - - target_room: 156 - entrance: 340 - teleporter: [108, 3] - access: [] -- name: Pazuzu Tower 1F Main Lobby - id: 166 - game_objects: - - name: "Pazuzu 1F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu1F"] - access: [] - links: - - target_room: 226 - entrance: 341 - teleporter: [184, 0] - access: [] - - target_room: 180 - entrance: 345 - teleporter: [185, 0] - access: [] -- name: Pazuzu Tower 1F Boxes Room - id: 167 - game_objects: - - name: "Pazuzu's Tower 1F - Descent Bomb Wall West Box" - object_id: 0xCA - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 1F - Descent Bomb Wall Center Box" - object_id: 0xCB - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 1F - Descent Bomb Wall East Box" - object_id: 0xCC - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 1F - Descent Box" - object_id: 0xCD - type: "Box" - access: [] - links: - - target_room: 169 - entrance: 349 - teleporter: [187, 0] - access: [] -- name: Pazuzu Tower 1F Southern Platform - id: 168 - game_objects: [] - links: - - target_room: 169 - entrance: 346 - teleporter: [186, 0] - access: [] - - target_room: 166 - access: ["DragonClaw"] -- name: Pazuzu 2F - id: 169 - game_objects: - - name: "Pazuzu's Tower 2F - East Room West Box" - object_id: 0xCE - type: "Box" - access: [] - - name: "Pazuzu's Tower 2F - East Room East Box" - object_id: 0xCF - type: "Box" - access: [] - - name: "Pazuzu 2F Lock" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu2FLock"] - access: ["Axe"] - - name: "Pazuzu 2F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu2F"] - access: ["Bomb"] - links: - - target_room: 183 - entrance: 350 - teleporter: [188, 0] - access: [] - - target_room: 168 - entrance: 351 - teleporter: [189, 0] - access: [] - - target_room: 167 - entrance: 352 - teleporter: [190, 0] - access: [] - - target_room: 171 - entrance: 353 - teleporter: [191, 0] - access: [] -- name: Pazuzu 3F Main Room - id: 170 - game_objects: - - name: "Pazuzu's Tower 3F - Guest Room West Box" - object_id: 0xD0 - type: "Box" - access: [] - - name: "Pazuzu's Tower 3F - Guest Room East Box" - object_id: 0xD1 - type: "Box" - access: [] - - name: "Pazuzu 3F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu3F"] - access: [] - links: - - target_room: 180 - entrance: 356 - teleporter: [192, 0] - access: [] - - target_room: 181 - entrance: 357 - teleporter: [193, 0] - access: [] -- name: Pazuzu 3F Central Island - id: 171 - game_objects: [] - links: - - target_room: 169 - entrance: 360 - teleporter: [194, 0] - access: [] - - target_room: 170 - access: ["DragonClaw"] - - target_room: 172 - access: ["DragonClaw"] -- name: Pazuzu 3F Southern Island - id: 172 - game_objects: - - name: "Pazuzu's Tower 3F - South Ledge Box" - object_id: 0xD2 - type: "Box" - access: [] - links: - - target_room: 173 - entrance: 361 - teleporter: [195, 0] - access: [] - - target_room: 171 - access: ["DragonClaw"] -- name: Pazuzu 4F - id: 173 - game_objects: - - name: "Pazuzu's Tower 4F - Elevator West Box" - object_id: 0xD3 - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 4F - Elevator East Box" - object_id: 0xD4 - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 4F - East Storage Room Chest" - object_id: 0x18 - type: "Chest" - access: [] - - name: "Pazuzu 4F Lock" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu4FLock"] - access: ["Axe"] - - name: "Pazuzu 4F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu4F"] - access: ["Bomb"] - links: - - target_room: 183 - entrance: 362 - teleporter: [196, 0] - access: [] - - target_room: 184 - entrance: 363 - teleporter: [197, 0] - access: [] - - target_room: 172 - entrance: 364 - teleporter: [198, 0] - access: [] - - target_room: 175 - entrance: 365 - teleporter: [199, 0] - access: [] -- name: Pazuzu 5F Pazuzu Loop - id: 174 - game_objects: - - name: "Pazuzu 5F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu5F"] - access: [] - links: - - target_room: 181 - entrance: 368 - teleporter: [200, 0] - access: [] - - target_room: 182 - entrance: 369 - teleporter: [201, 0] - access: [] -- name: Pazuzu 5F Upper Loop - id: 175 - game_objects: - - name: "Pazuzu's Tower 5F - North Box" - object_id: 0xD5 - type: "Box" - access: [] - - name: "Pazuzu's Tower 5F - South Box" - object_id: 0xD6 - type: "Box" - access: [] - links: - - target_room: 173 - entrance: 370 - teleporter: [202, 0] - access: [] - - target_room: 176 - entrance: 371 - teleporter: [203, 0] - access: [] -- name: Pazuzu 6F - id: 176 - game_objects: - - name: "Pazuzu's Tower 6F - Box" - object_id: 0xD7 - type: "Box" - access: [] - - name: "Pazuzu's Tower 6F - Chest" - object_id: 0x19 - type: "Chest" - access: [] - - name: "Pazuzu 6F Lock" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu6FLock"] - access: ["Bomb", "Axe"] - - name: "Pazuzu 6F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu6F"] - access: ["Bomb"] - links: - - target_room: 184 - entrance: 374 - teleporter: [204, 0] - access: [] - - target_room: 175 - entrance: 375 - teleporter: [205, 0] - access: [] - - target_room: 178 - entrance: 376 - teleporter: [206, 0] - access: [] - - target_room: 178 - entrance: 377 - teleporter: [207, 0] - access: [] -- name: Pazuzu 7F Southwest Area - id: 177 - game_objects: [] - links: - - target_room: 182 - entrance: 380 - teleporter: [26, 0] - access: [] - - target_room: 178 - access: ["DragonClaw"] -- name: Pazuzu 7F Rest of the Area - id: 178 - game_objects: [] - links: - - target_room: 177 - access: ["DragonClaw"] - - target_room: 176 - entrance: 381 - teleporter: [27, 0] - access: [] - - target_room: 176 - entrance: 382 - teleporter: [28, 0] - access: [] - - target_room: 179 - access: ["DragonClaw", "Pazuzu2FLock", "Pazuzu4FLock", "Pazuzu6FLock", "Pazuzu1F", "Pazuzu2F", "Pazuzu3F", "Pazuzu4F", "Pazuzu5F", "Pazuzu6F"] -- name: Pazuzu 7F Sky Room - id: 179 - game_objects: - - name: "Pazuzu's Tower 7F - Pazuzu Chest" - object_id: 0x1A - type: "Chest" - access: [] - - name: "Pazuzu" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu"] - access: ["Pazuzu2FLock", "Pazuzu4FLock", "Pazuzu6FLock", "Pazuzu1F", "Pazuzu2F", "Pazuzu3F", "Pazuzu4F", "Pazuzu5F", "Pazuzu6F"] - links: - - target_room: 178 - access: ["DragonClaw"] -- name: Pazuzu 1F to 3F - id: 180 - game_objects: [] - links: - - target_room: 166 - entrance: 385 - teleporter: [29, 0] - access: [] - - target_room: 170 - entrance: 386 - teleporter: [30, 0] - access: [] -- name: Pazuzu 3F to 5F - id: 181 - game_objects: [] - links: - - target_room: 170 - entrance: 387 - teleporter: [40, 0] - access: [] - - target_room: 174 - entrance: 388 - teleporter: [41, 0] - access: [] -- name: Pazuzu 5F to 7F - id: 182 - game_objects: [] - links: - - target_room: 174 - entrance: 389 - teleporter: [38, 0] - access: [] - - target_room: 177 - entrance: 390 - teleporter: [39, 0] - access: [] -- name: Pazuzu 2F to 4F - id: 183 - game_objects: [] - links: - - target_room: 169 - entrance: 391 - teleporter: [21, 0] - access: [] - - target_room: 173 - entrance: 392 - teleporter: [22, 0] - access: [] -- name: Pazuzu 4F to 6F - id: 184 - game_objects: [] - links: - - target_room: 173 - entrance: 393 - teleporter: [2, 0] - access: [] - - target_room: 176 - entrance: 394 - teleporter: [3, 0] - access: [] -- name: Light Temple - id: 185 - game_objects: - - name: "Light Temple - Box" - object_id: 0xD8 - type: "Box" - access: [] - links: - - target_room: 230 - entrance: 395 - teleporter: [19, 6] - access: [] - - target_room: 153 - entrance: 396 - teleporter: [70, 8] - access: ["MobiusCrest"] -- name: Ship Dock - id: 186 - game_objects: - - name: "Ship Dock Access" - object_id: 0 - type: "Trigger" - on_trigger: ["ShipDockAccess"] - access: [] - links: - - target_room: 228 - entrance: 399 - teleporter: [17, 6] - access: [] - - target_room: 162 - entrance: 397 - teleporter: [61, 8] - access: ["MobiusCrest"] -- name: Mac Ship Deck - id: 187 - game_objects: - - name: "Mac Ship Steering Wheel" - object_id: 00 - type: "Trigger" - on_trigger: ["ShipSteeringWheel"] - access: [] - - name: "Mac's Ship Deck - North Box" - object_id: 0xD9 - type: "Box" - access: [] - - name: "Mac's Ship Deck - Center Box" - object_id: 0xDA - type: "Box" - access: [] - - name: "Mac's Ship Deck - South Box" - object_id: 0xDB - type: "Box" - access: [] - links: - - target_room: 229 - entrance: 400 - teleporter: [37, 8] - access: [] - - target_room: 188 - entrance: 401 - teleporter: [50, 8] - access: [] - - target_room: 188 - entrance: 402 - teleporter: [51, 8] - access: [] - - target_room: 188 - entrance: 403 - teleporter: [52, 8] - access: [] - - target_room: 189 - entrance: 404 - teleporter: [53, 8] - access: [] -- name: Mac Ship B1 Outer Ring - id: 188 - game_objects: - - name: "Mac's Ship B1 - Northwest Hook Platform Box" - object_id: 0xE4 - type: "Box" - access: ["DragonClaw"] - - name: "Mac's Ship B1 - Center Hook Platform Box" - object_id: 0xE5 - type: "Box" - access: ["DragonClaw"] - links: - - target_room: 187 - entrance: 405 - teleporter: [208, 0] - access: [] - - target_room: 187 - entrance: 406 - teleporter: [175, 0] - access: [] - - target_room: 187 - entrance: 407 - teleporter: [172, 0] - access: [] - - target_room: 193 - entrance: 408 - teleporter: [88, 0] - access: [] - - target_room: 193 - access: [] -- name: Mac Ship B1 Square Room - id: 189 - game_objects: [] - links: - - target_room: 187 - entrance: 409 - teleporter: [141, 0] - access: [] - - target_room: 192 - entrance: 410 - teleporter: [87, 0] - access: [] -- name: Mac Ship B1 Central Corridor - id: 190 - game_objects: - - name: "Mac's Ship B1 - Central Corridor Box" - object_id: 0xE6 - type: "Box" - access: [] - links: - - target_room: 192 - entrance: 413 - teleporter: [86, 0] - access: [] - - target_room: 191 - entrance: 412 - teleporter: [102, 0] - access: [] - - target_room: 193 - access: [] -- name: Mac Ship B2 South Corridor - id: 191 - game_objects: [] - links: - - target_room: 190 - entrance: 415 - teleporter: [55, 8] - access: [] - - target_room: 194 - entrance: 414 - teleporter: [57, 1] - access: [] -- name: Mac Ship B2 North Corridor - id: 192 - game_objects: [] - links: - - target_room: 190 - entrance: 416 - teleporter: [56, 8] - access: [] - - target_room: 189 - entrance: 417 - teleporter: [57, 8] - access: [] -- name: Mac Ship B2 Outer Ring - id: 193 - game_objects: - - name: "Mac's Ship B2 - Barrel Room South Box" - object_id: 0xDF - type: "Box" - access: [] - - name: "Mac's Ship B2 - Barrel Room North Box" - object_id: 0xE0 - type: "Box" - access: [] - - name: "Mac's Ship B2 - Southwest Room Box" - object_id: 0xE1 - type: "Box" - access: [] - - name: "Mac's Ship B2 - Southeast Room Box" - object_id: 0xE2 - type: "Box" - access: [] - links: - - target_room: 188 - entrance: 418 - teleporter: [58, 8] - access: [] -- name: Mac Ship B1 Mac Room - id: 194 - game_objects: - - name: "Mac's Ship B1 - Mac Room Chest" - object_id: 0x1B - type: "Chest" - access: [] - - name: "Captain Mac" - object_id: 0 - type: "Trigger" - on_trigger: ["ShipLoaned"] - access: ["CaptainCap"] - links: - - target_room: 191 - entrance: 424 - teleporter: [101, 0] - access: [] -- name: Doom Castle Corridor of Destiny - id: 195 - game_objects: [] - links: - - target_room: 201 - entrance: 428 - teleporter: [84, 0] - access: [] - - target_room: 196 - entrance: 429 - teleporter: [35, 2] - access: [] - - target_room: 197 - entrance: 430 - teleporter: [209, 0] - access: ["StoneGolem"] - - target_room: 198 - entrance: 431 - teleporter: [211, 0] - access: ["StoneGolem", "TwinheadWyvern"] - - target_room: 199 - entrance: 432 - teleporter: [13, 2] - access: ["StoneGolem", "TwinheadWyvern", "Zuh"] -- name: Doom Castle Ice Floor - id: 196 - game_objects: - - name: "Doom Castle 4F - Northwest Room Box" - object_id: 0xE7 - type: "Box" - access: ["Sword", "DragonClaw"] - - name: "Doom Castle 4F - Southwest Room Box" - object_id: 0xE8 - type: "Box" - access: ["Sword", "DragonClaw"] - - name: "Doom Castle 4F - Northeast Room Box" - object_id: 0xE9 - type: "Box" - access: ["Sword"] - - name: "Doom Castle 4F - Southeast Room Box" - object_id: 0xEA - type: "Box" - access: ["Sword", "DragonClaw"] - - name: "Stone Golem" - object_id: 0 - type: "Trigger" - on_trigger: ["StoneGolem"] - access: ["Sword", "DragonClaw"] - links: - - target_room: 195 - entrance: 433 - teleporter: [109, 3] - access: [] -- name: Doom Castle Lava Floor - id: 197 - game_objects: - - name: "Doom Castle 5F - North Left Box" - object_id: 0xEB - type: "Box" - access: ["DragonClaw"] - - name: "Doom Castle 5F - North Right Box" - object_id: 0xEC - type: "Box" - access: ["DragonClaw"] - - name: "Doom Castle 5F - South Left Box" - object_id: 0xED - type: "Box" - access: ["DragonClaw"] - - name: "Doom Castle 5F - South Right Box" - object_id: 0xEE - type: "Box" - access: ["DragonClaw"] - - name: "Twinhead Wyvern" - object_id: 0 - type: "Trigger" - on_trigger: ["TwinheadWyvern"] - access: ["DragonClaw"] - links: - - target_room: 195 - entrance: 434 - teleporter: [210, 0] - access: [] -- name: Doom Castle Sky Floor - id: 198 - game_objects: - - name: "Doom Castle 6F - West Box" - object_id: 0xEF - type: "Box" - access: [] - - name: "Doom Castle 6F - East Box" - object_id: 0xF0 - type: "Box" - access: [] - - name: "Zuh" - object_id: 0 - type: "Trigger" - on_trigger: ["Zuh"] - access: ["DragonClaw"] - links: - - target_room: 195 - entrance: 435 - teleporter: [212, 0] - access: [] - - target_room: 197 - access: [] -- name: Doom Castle Hero Room - id: 199 - game_objects: - - name: "Doom Castle Hero Chest 01" - object_id: 0xF2 - type: "Chest" - access: [] - - name: "Doom Castle Hero Chest 02" - object_id: 0xF3 - type: "Chest" - access: [] - - name: "Doom Castle Hero Chest 03" - object_id: 0xF4 - type: "Chest" - access: [] - - name: "Doom Castle Hero Chest 04" - object_id: 0xF5 - type: "Chest" - access: [] - links: - - target_room: 200 - entrance: 436 - teleporter: [54, 0] - access: [] - - target_room: 195 - entrance: 441 - teleporter: [110, 3] - access: [] -- name: Doom Castle Dark King Room - id: 200 - game_objects: [] - links: - - target_room: 199 - entrance: 442 - teleporter: [52, 0] - access: [] diff --git a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md index dd4ea354fab1..4e093930739d 100644 --- a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md +++ b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md @@ -1,8 +1,11 @@ # Final Fantasy Mystic Quest -## Where is the settings page? +## Game page in other languages: +* [Français](/games/Final%20Fantasy%20Mystic%20Quest/info/fr) -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md new file mode 100644 index 000000000000..70c2d938bfc6 --- /dev/null +++ b/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md @@ -0,0 +1,36 @@ +# Final Fantasy Mystic Quest + +## Page d'info dans d'autres langues : +* [English](/games/Final%20Fantasy%20Mystic%20Quest/info/en) + +## Où se situe la page d'options? + +La [page de configuration](../player-options) contient toutes les options nécessaires pour créer un fichier de configuration. + +## Qu'est-ce qui est rendu aléatoire dans ce jeu? + +Outre les objets mélangés, il y a plusieurs options pour aussi mélanger les villes et donjons, les pièces dans les donjons, les téléporteurs et les champs de bataille. +Il y a aussi plusieurs autres options afin d'ajuster la difficulté du jeu et la vitesse d'une partie. + +## Quels objets et emplacements sont mélangés? + +Les objets normalement reçus des coffres rouges, des PNJ et des champs de bataille sont mélangés. Vous pouvez aussi +inclure les objets des coffres bruns (qui contiennent normalement des consommables) dans les objets mélangés. + +## Quels objets peuvent être dans les mondes des autres joueurs? + +Tous les objets qui ont été déterminés mélangés dans les options peuvent être placés dans d'autres mondes. + +## À quoi ressemblent les objets des autres joueurs dans Final Fantasy Mystic Quest? + +Les emplacements qui étaient à l'origine des coffres (rouges ou bruns si ceux-ci sont inclus) apparaîtront comme des coffres. +Les coffres rouges seront des objets utiles ou de progression, alors que les coffres bruns seront des objets de remplissage. +Les pièges peuvent apparaître comme des coffres rouges ou bruns. +Lorsque vous ouvrirez un coffre contenant un objet d'un autre joueur, vous recevrez l'icône d'Archipelago et +la boîte de dialogue vous indiquera avoir reçu un "Archipelago Item". + + +## Lorsqu'un joueur reçoit un objet, qu'arrive-t-il? + +Une boîte de dialogue apparaîtra pour vous montrer l'objet que vous avez reçu. Vous ne pourrez pas recevoir d'objet si vous êtes +en combat, dans la mappemonde ou dans les menus (à l'exception de lorsque vous fermez le menu). diff --git a/worlds/ffmq/docs/setup_en.md b/worlds/ffmq/docs/setup_en.md index 61b8d7e306bf..77569c93f0c8 100644 --- a/worlds/ffmq/docs/setup_en.md +++ b/worlds/ffmq/docs/setup_en.md @@ -12,11 +12,17 @@ - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other compatible hardware -- Your legally obtained Final Fantasy Mystic Quest 1.1 ROM file, probably named `Final Fantasy - Mystic Quest (U) (V1.1).sfc` +- Your legally obtained Final Fantasy Mystic Quest NA 1.0 or 1.1 ROM file, probably named `Final Fantasy - Mystic Quest (U) (V1.0).sfc` or `Final Fantasy - Mystic Quest (U) (V1.1).sfc` The Archipelago community cannot supply you with this. ## Installation Procedures +### Linux Setup + +1. Download and install [Archipelago](). **The installer + file is located in the assets section at the bottom of the version information. You'll likely be looking for the `.AppImage`.** +2. It is recommended to use either RetroArch or BizHawk if you run on linux, as snes9x-rr isn't compatible. + ### Windows Setup 1. Download and install [Archipelago](). **The installer @@ -39,8 +45,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Final Fantasy Mystic Quest Player Settings Page](/games/Final%20Fantasy%20Mystic%20Quest/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Final Fantasy Mystic Quest Player Options Page](/games/Final%20Fantasy%20Mystic%20Quest/player-options) ### Verifying your config file @@ -49,12 +55,12 @@ validator page: [YAML Validation page](/mysterycheck) ## Generating a Single-Player Game -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [Final Fantasy Mystic Quest Player Settings Page](/games/Final%20Fantasy%20Mystic%20Quest/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [Final Fantasy Mystic Quest Player Options Page](/games/Final%20Fantasy%20Mystic%20Quest/player-options) 2. You will be presented with a "Seed Info" page. 3. Click the "Create New Room" link. 4. You will be presented with a server page, from which you can download your `.apmq` patch file. -5. Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest 1.1 ROM +5. Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest ROM and the .apmq file you received, choose optional preferences, and click `Generate` to get your patched ROM. 7. Since this is a single-player game, you will no longer need the client, so feel free to close it. @@ -66,7 +72,7 @@ When you join a multiworld game, you will be asked to provide your config file t the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch files. Your patch file should have a `.apmq` extension. -Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest 1.1 ROM +Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest ROM and the .apmq file you received, choose optional preferences, and click `Generate` to get your patched ROM. Manually launch the SNI Client, and run the patched ROM in your chosen software or hardware. @@ -75,8 +81,7 @@ Manually launch the SNI Client, and run the patched ROM in your chosen software #### With an emulator -When the client launched automatically, SNI should have also automatically launched in the background. If this is its -first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +If this is the first time SNI launches, you may be prompted to allow it to communicate through the Windows Firewall. ##### snes9x-rr @@ -133,10 +138,10 @@ page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platfor ### Connect to the Archipelago Server -The patch file which launched your client should have automatically connected you to the AP Server. There are a few -reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the -client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it -into the "Server" input field then press enter. +SNI serves as the interface between your emulator and the server. Since you launched it manually, you need to tell it what server to connect to. +If the server is hosted on Archipelago.gg, get the port the server hosts your game on at the top of the game room (last line before the worlds are listed). +In the SNI client, either type `/connect address` (where `address` is the address of the server, for example `/connect archipelago.gg:12345`), or type the address and port on the "Server" input field, then press `Connect`. +If the server is hosted locally, simply ask the host for the address of the server, and copy/paste it into the "Server" input field then press `Connect`. The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". diff --git a/worlds/ffmq/docs/setup_fr.md b/worlds/ffmq/docs/setup_fr.md new file mode 100644 index 000000000000..12ea41c6b3a0 --- /dev/null +++ b/worlds/ffmq/docs/setup_fr.md @@ -0,0 +1,178 @@ +# Final Fantasy Mystic Quest Setup Guide + +## Logiciels requis + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES + - Un émulateur capable d'éxécuter des scripts Lua + - snes9x-rr de: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html), + - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Ou, + - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle + compatible +- Le fichier ROM de la v1.0 ou v1.1 NA de Final Fantasy Mystic Quest obtenu légalement, sûrement nommé `Final Fantasy - Mystic Quest (U) (V1.0).sfc` ou `Final Fantasy - Mystic Quest (U) (V1.1).sfc` +La communauté d'Archipelago ne peut vous fournir avec ce fichier. + +## Procédure d'installation + +### Installation sur Linux + +1. Téléchargez et installez [Archipelago](). +** Le fichier d'installation est situé dans la section "assets" dans le bas de la fenêtre d'information de la version. Vous voulez probablement le `.AppImage`** +2. L'utilisation de RetroArch ou BizHawk est recommandé pour les utilisateurs linux, puisque snes9x-rr n'est pas compatible. + +### Installation sur Windows + +1. Téléchargez et installez [Archipelago](). +** Le fichier d'installation est situé dans la section "assets" dans le bas de la fenêtre d'information de la version.** +2. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme + programme par défaut pour ouvrir vos ROMs. + 1. Extrayez votre dossier d'émulateur sur votre Bureau, ou à un endroit dont vous vous souviendrez. + 2. Faites un clic droit sur un fichier ROM et sélectionnez **Ouvrir avec...** + 3. Cochez la case à côté de **Toujours utiliser cette application pour ouvrir les fichiers `.sfc`** + 4. Descendez jusqu'en bas de la liste et sélectionnez **Rechercher une autre application sur ce PC** + 5. Naviguez dans les dossiers jusqu'au fichier `.exe` de votre émulateur et choisissez **Ouvrir**. Ce fichier + devrait se trouver dans le dossier que vous avez extrait à la première étape. + + +## Créer son fichier de configuration (.yaml) + +### Qu'est-ce qu'un fichier de configuration et pourquoi en ai-je besoin ? + +Votre fichier de configuration contient un ensemble d'options de configuration pour indiquer au générateur +comment il devrait générer votre seed. Chaque joueur d'un multiworld devra fournir son propre fichier de configuration. Cela permet +à chaque joueur d'apprécier une expérience personalisée. Les différents joueurs d'un même multiworld +pouront avoir des options de génération différentes. +Vous pouvez lire le [guide pour créer un YAML de base](/tutorial/Archipelago/setup/en) en anglais. + +### Où est-ce que j'obtiens un fichier de configuration ? + +La [page d'options sur le site](/games/Final%20Fantasy%20Mystic%20Quest/player-options) vous permet de choisir vos +options de génération et de les exporter vers un fichier de configuration. +Il vous est aussi possible de trouver le fichier de configuration modèle de Mystic Quest dans votre répertoire d'installation d'Archipelago, +dans le dossier Players/Templates. + +### Vérifier son fichier de configuration + +Si vous voulez valider votre fichier de configuration pour être sûr qu'il fonctionne, vous pouvez le vérifier sur la page du +[Validateur de YAML](/mysterycheck). + +## Générer une partie pour un joueur + +1. Aller sur la page [Génération de partie](/games/Final%20Fantasy%20Mystic%20Quest/player-options), configurez vos options, + et cliquez sur le bouton "Generate Game". +2. Il vous sera alors présenté une page d'informations sur la seed +3. Cliquez sur le lien "Create New Room". +4. Vous verrez s'afficher la page du server, de laquelle vous pourrez télécharger votre fichier patch `.apmq`. +5. Rendez-vous sur le [site FFMQR](https://ffmqrando.net/Archipelago). +Sur cette page, sélectionnez votre ROM Final Fantasy Mystic Quest original dans le boîte "ROM", puis votre ficher patch `.apmq` dans la boîte "Load Archipelago Config File". +Cliquez sur "Generate". Un téléchargement avec votre ROM aléatoire devrait s'amorcer. +6. Puisque cette partie est à un seul joueur, vous n'avez plus besoin du client Archipelago ni du serveur, sentez-vous libre de les fermer. + +## Rejoindre un MultiWorld + +### Obtenir son patch et créer sa ROM + +Quand vous rejoignez un multiworld, il vous sera demandé de fournir votre fichier de configuration à celui qui héberge la partie ou +s'occupe de la génération. Une fois cela fait, l'hôte vous fournira soit un lien pour télécharger votre patch, soit un +fichier `.zip` contenant les patchs de tous les joueurs. Votre patch devrait avoir l'extension `.apmq`. + +Allez au [site FFMQR](https://ffmqrando.net/Archipelago) et sélectionnez votre ROM Final Fantasy Mystic Quest original dans le boîte "ROM", puis votre ficher patch `.apmq` dans la boîte "Load Archipelago Config File". +Cliquez sur "Generate". Un téléchargement avec votre ROM aléatoire devrait s'amorcer. + +Ouvrez le client SNI (sur Windows ArchipelagoSNIClient.exe, sur Linux ouvrez le `.appImage` puis cliquez sur SNI Client), puis ouvrez le ROM téléchargé avec votre émulateur choisi. + +### Se connecter au client + +#### Avec un émulateur + +Quand le client se lance automatiquement, QUsb2Snes devrait également se lancer automatiquement en arrière-plan. Si +c'est la première fois qu'il démarre, il vous sera peut-être demandé de l'autoriser à communiquer à travers le pare-feu +Windows. + +##### snes9x-rr + +1. Chargez votre ROM si ce n'est pas déjà fait. +2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting** +3. Cliquez alors sur **New Lua Script Window...** +4. Dans la nouvelle fenêtre, sélectionnez **Browse...** +5. Sélectionnez le fichier connecteur lua fourni avec votre client + - Regardez dans le dossier Archipelago et cherchez `/SNI/lua/x64` ou `/SNI/lua/x86`, dépendemment de si votre emulateur + est 64-bit ou 32-bit. +6. Si vous obtenez une erreur `socket.dll missing` ou une erreur similaire lorsque vous chargez le script lua, vous devez naviguer dans le dossier +contenant le script lua, puis copier le fichier `socket.dll` dans le dossier d'installation de votre emulateur snes9x. + +##### BizHawk + +1. Assurez vous d'avoir le coeur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant + ces options de menu : + `Config --> Cores --> SNES --> BSNES` + Une fois le coeur changé, vous devez redémarrer BizHawk. +2. Chargez votre ROM si ce n'est pas déjà fait. +3. Cliquez sur le menu "Tools" et cliquez sur **Lua Console** +4. Cliquez sur le bouton pour ouvrir un nouveau script Lua, soit par le bouton avec un icône "Ouvrir un dossier", + en cliquant `Open Script...` dans le menu Script ou en appuyant sur `ctrl-O`. +5. Sélectionnez le fichier `Connector.lua` inclus avec le client + - Regardez dans le dossier Archipelago et cherchez `/SNI/lua/x64` ou `/SNI/lua/x86`, dépendemment de si votre emulateur + est 64-bit ou 32-bit. Notez que les versions les plus récentes de BizHawk ne sont que 64-bit. + +##### RetroArch 1.10.1 ou plus récent + +Vous ne devez faire ces étapes qu'une fois. À noter que RetroArch 1.9.x ne fonctionnera pas puisqu'il s'agit d'une version moins récente que 1.10.1. + +1. Entrez dans le menu principal de RetroArch. +2. Allez dans Settings --> User Interface. Activez l'option "Show Advanced Settings". +3. Allez dans Settings --> Network. Activez l'option "Network Commands", qui se trouve sous "Request Device 16". + Laissez le "Network Command Port" à sa valeur par defaut, qui devrait être 55355. + + +![Capture d'écran du menu Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Allez dans le Menu Principal --> Online Updater --> Core Downloader. Trouvez et sélectionnez "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +Lorsque vous chargez un ROM pour Archipelago, assurez vous de toujours sélectionner le coeur **bsnes-mercury**. +Ce sont les seuls coeurs qui permettent à des outils extérieurs de lire les données du ROM. + +#### Avec une solution matérielle + +Ce guide suppose que vous avez téléchargé le bon micro-logiciel pour votre appareil. Si ce n'est pas déjà le cas, faites +le maintenant. Les utilisateurs de SD2SNES et de FXPak Pro peuvent télécharger le micro-logiciel approprié +[ici](https://github.com/RedGuyyyy/sd2snes/releases). Pour les autres solutions, de l'aide peut être trouvée +[sur cette page](http://usb2snes.com/#supported-platforms). + +1. Fermez votre émulateur, qui s'est potentiellement lancé automatiquement. +2. Ouvrez votre appareil et chargez le ROM. + +### Se connecter au MultiServer + +Puisque vous avez lancé SNI manuellement, vous devrez probablement lui indiquer l'adresse à laquelle il doit se connecter. +Si le serveur est hébergé sur le site d'Archipelago, vous verrez l'adresse à laquelle vous connecter dans le haut de la page, dernière ligne avant la liste des mondes. +Tapez `/connect adresse` (ou le "adresse" est remplacé par l'adresse archipelago, par exemple `/connect archipelago.gg:12345`) dans la boîte de commande au bas de votre client SNI, ou encore écrivez l'adresse dans la boîte "server" dans le haut du client, puis cliquez `Connect`. +Si le serveur n'est pas hébergé sur le site d'Archipelago, demandez à l'hôte l'adresse du serveur, puis tapez `/connect adresse` (ou "adresse" est remplacé par l'adresse fourni par l'hôte) ou copiez/collez cette adresse dans le champ "Server" puis appuyez sur "Connect". + +Le client essaiera de vous reconnecter à la nouvelle adresse du serveur, et devrait mentionner "Server Status: +Connected". Si le client ne se connecte pas après quelques instants, il faudra peut-être rafraîchir la page de +l'interface Web. + +### Jouer au jeu + +Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations +pour avoir rejoint un multiworld ! + +## Héberger un MultiWorld + +La méthode recommandée pour héberger une partie est d'utiliser le service d'hébergement fourni par +Archipelago. Le processus est relativement simple : + +1. Récupérez les fichiers de configuration (.yaml) des joueurs. +2. Créez une archive zip contenant ces fichiers de configuration. +3. Téléversez l'archive zip sur le lien ci-dessous. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Attendez un moment que la seed soit générée. +5. Lorsque la seed est générée, vous serez redirigé vers une page d'informations "Seed Info". +6. Cliquez sur "Create New Room". Cela vous amènera à la page du serveur. Fournissez le lien de cette page aux autres + joueurs afin qu'ils puissent récupérer leurs patchs. +7. Remarquez qu'un lien vers le traqueur du MultiWorld est en haut de la page de la salle. Vous devriez également + fournir ce lien aux joueurs pour qu'ils puissent suivre la progression de la partie. N'importe quelle personne voulant + observer devrait avoir accès à ce lien. +8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer. diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index c434351e9493..e930c4b8d6e9 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -14,16 +14,16 @@ ItemRule = typing.Callable[[object], bool] -def locality_needed(world: MultiWorld) -> bool: - for player in world.player_ids: - if world.local_items[player].value: +def locality_needed(multiworld: MultiWorld) -> bool: + for player in multiworld.player_ids: + if multiworld.worlds[player].options.local_items.value: return True - if world.non_local_items[player].value: + if multiworld.worlds[player].options.non_local_items.value: return True # Group - for group_id, group in world.groups.items(): - if set(world.player_ids) == set(group["players"]): + for group_id, group in multiworld.groups.items(): + if set(multiworld.player_ids) == set(group["players"]): continue if group["local_items"]: return True @@ -31,8 +31,8 @@ def locality_needed(world: MultiWorld) -> bool: return True -def locality_rules(world: MultiWorld): - if locality_needed(world): +def locality_rules(multiworld: MultiWorld): + if locality_needed(multiworld): forbid_data: typing.Dict[int, typing.Dict[int, typing.Set[str]]] = \ collections.defaultdict(lambda: collections.defaultdict(set)) @@ -40,32 +40,32 @@ def locality_rules(world: MultiWorld): def forbid(sender: int, receiver: int, items: typing.Set[str]): forbid_data[sender][receiver].update(items) - for receiving_player in world.player_ids: - local_items: typing.Set[str] = world.worlds[receiving_player].options.local_items.value + for receiving_player in multiworld.player_ids: + local_items: typing.Set[str] = multiworld.worlds[receiving_player].options.local_items.value if local_items: - for sending_player in world.player_ids: + for sending_player in multiworld.player_ids: if receiving_player != sending_player: forbid(sending_player, receiving_player, local_items) - non_local_items: typing.Set[str] = world.worlds[receiving_player].options.non_local_items.value + non_local_items: typing.Set[str] = multiworld.worlds[receiving_player].options.non_local_items.value if non_local_items: forbid(receiving_player, receiving_player, non_local_items) # Group - for receiving_group_id, receiving_group in world.groups.items(): - if set(world.player_ids) == set(receiving_group["players"]): + for receiving_group_id, receiving_group in multiworld.groups.items(): + if set(multiworld.player_ids) == set(receiving_group["players"]): continue if receiving_group["local_items"]: - for sending_player in world.player_ids: + for sending_player in multiworld.player_ids: if sending_player not in receiving_group["players"]: forbid(sending_player, receiving_group_id, receiving_group["local_items"]) if receiving_group["non_local_items"]: - for sending_player in world.player_ids: + for sending_player in multiworld.player_ids: if sending_player in receiving_group["players"]: forbid(sending_player, receiving_group_id, receiving_group["non_local_items"]) # create fewer lambda's to save memory and cache misses func_cache = {} - for location in world.get_locations(): + for location in multiworld.get_locations(): if (location.player, location.item_rule) in func_cache: location.item_rule = func_cache[location.player, location.item_rule] # empty rule that just returns True, overwrite @@ -90,7 +90,7 @@ def exclusion_rules(multiworld: MultiWorld, player: int, exclude_locations: typi if loc_name not in multiworld.worlds[player].location_name_to_id: raise Exception(f"Unable to exclude location {loc_name} in player {player}'s world.") from e else: - if not location.event: + if not location.advancement: location.progress_type = LocationProgressType.EXCLUDED else: logging.warning(f"Unable to exclude location {loc_name} in player {player}'s world.") diff --git a/worlds/generic/__init__.py b/worlds/generic/__init__.py index 6b2ffdfee180..29f808b20272 100644 --- a/worlds/generic/__init__.py +++ b/worlds/generic/__init__.py @@ -40,7 +40,6 @@ class GenericWorld(World): } hidden = True web = GenericWeb() - data_version = 1 def generate_early(self): self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator @@ -69,9 +68,3 @@ def failed(self, warning: str, exception=Exception): raise exception(warning) else: self.warn(warning) - - -class PlandoConnection(NamedTuple): - entrance: str - exit: str - direction: str # entrance, exit or both diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index 6d5e20462f13..37467eeb468e 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -2,27 +2,27 @@ This guide covers more the more advanced options available in YAML files. This guide is intended for the user who plans to edit their YAML file manually. This guide should take about 10 minutes to read. -If you would like to generate a basic, fully playable YAML without editing a file, then visit the settings page for the -game you intend to play. The weighted settings page can also handle most of the advanced settings discussed here. +If you would like to generate a basic, fully playable YAML without editing a file, then visit the options page for the +game you intend to play. + +The options page can be found on the supported games page, just click the "Options Page" link under the name of the +game you would like. -The settings page can be found on the supported games page, just click the "Settings Page" link under the name of the -game you would like. * Supported games page: [Archipelago Games List](/games) -* Weighted settings page: [Archipelago Weighted Settings](/weighted-settings) -Clicking on the "Export Settings" button at the bottom-left will provide you with a pre-filled YAML with your options. -The player settings page also has a link to download a full template file for that game which will have every option +Clicking on the "Export Options" button at the bottom-left will provide you with a pre-filled YAML with your options. +The player options page also has a link to download a full template file for that game which will have every option possible for the game including some that don't display correctly on the site. ## YAML Overview The Archipelago system generates games using player configuration files as input. These are going to be YAML files and -each world will have one of these containing their custom settings for the game that world will play. +each world will have one of these containing their custom options for the game that world will play. ## YAML Formatting YAML files are a format of human-readable config files. The basic syntax of a yaml file will have a `root` node and then -different levels of `nested` nodes that the generator reads in order to determine your settings. +different levels of `nested` nodes that the generator reads in order to determine your options. To nest text, the correct syntax is to indent **two spaces over** from its root option. A YAML file can be edited with whatever text editor you choose to use though I personally recommend that you use Sublime Text. Sublime text @@ -30,7 +30,8 @@ website: [SublimeText Website](https://www.sublimetext.com) This program out of the box supports the correct formatting for the YAML file, so you will be able to use the tab key and get proper highlighting for any potential errors made while editing the file. If using any other text editor you -should ensure your indentation is done correctly with two spaces. +should ensure your indentation is done correctly with two spaces. After editing your YAML file, you can validate it at +the website's [validation page](/check). A typical YAML file will look like: @@ -53,13 +54,13 @@ so `option_one_setting_one` is guaranteed to occur. For `nested_option_two`, `option_two_setting_one` will be rolled 14 times and `option_two_setting_two` will be rolled 43 times against each other. This means `option_two_setting_two` will be more likely to occur, but it isn't guaranteed, -adding more randomness and "mystery" to your settings. Every configurable setting supports weights. +adding more randomness and "mystery" to your options. Every configurable setting supports weights. ## Root Options Currently, there are only a few options that are root options. Everything else should be nested within one of these root options or in some cases nested within other nested options. The only options that should exist in root -are `description`, `name`, `game`, `requires`, and the name of the games you want settings for. +are `description`, `name`, `game`, `requires`, and the name of the games you want options for. * `description` is ignored by the generator and is simply a good way for you to organize if you have multiple files using this to detail the intention of the file. @@ -78,16 +79,16 @@ are `description`, `name`, `game`, `requires`, and the name of the games you wan different weights. * `requires` details different requirements from the generator for the YAML to work as you expect it to. Generally this - is good for detailing the version of Archipelago this YAML was prepared for as, if it is rolled on an older version, - settings may be missing and as such it will not work as expected. If any plando is used in the file then requiring it + is good for detailing the version of Archipelago this YAML was prepared for. If it is rolled on an older version, + options may be missing and as such it will not work as expected. If any plando is used in the file then requiring it here to ensure it will be used is good practice. ## Game Options -One of your root settings will be the name of the game you would like to populate with settings. Since it is possible to +One of your root options will be the name of the game you would like to populate with options. Since it is possible to give a weight to any option, it is possible to have one file that can generate a seed for you where you don't know which game you'll play. For these cases you'll want to fill the game options for every game that can be rolled by these -settings. If a game can be rolled it **must** have a settings section even if it is empty. +settings. If a game can be rolled it **must** have an options section even if it is empty. ### Universal Game Options @@ -130,12 +131,13 @@ guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en) the location without using any hint points. * `start_location_hints` is the same as `start_hints` but for locations, allowing you to hint for the item contained there without using any hint points. -* `exclude_locations` lets you define any locations that you don't want to do and during generation will force a "junk" - item which isn't necessary for progression to go in these locations. -* `priority_locations` is the inverse of `exclude_locations`, forcing a progression item in the defined locations. +* `exclude_locations` lets you define any locations that you don't want to do and forces a filler or trap item which + isn't necessary for progression into these locations. +* `priority_locations` lets you define any locations that you want to do and forces a progression item into these + locations. * `item_links` allows players to link their items into a group with the same item link name and game. The items declared in `item_pool` get combined and when an item is found for the group, all players in the group receive it. Item links - can also have local and non local items, forcing the items to either be placed within the worlds of the group or in + can also have local and non-local items, forcing the items to either be placed within the worlds of the group or in worlds outside the group. If players have a varying amount of a specific item in the link, the lowest amount from the players will be the amount put into the group. @@ -275,12 +277,13 @@ one file, removing the need to manage separate files if one chooses to do so. As a precautionary measure, before submitting a multi-game yaml like this one in a synchronous/sync multiworld, please confirm that the other players in the multi are OK with what you are submitting, and please be fairly reasonable about -the submission. (ie. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered +the submission. (i.e. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered reasonable, but submitting a ChecksFinder alongside another game OR submitting multiple Slay the Spire runs is likely OK) To configure your file to generate multiple worlds, use 3 dashes `---` on an empty line to separate the ending of one -world and the beginning of another world. +world and the beginning of another world. You can also combine multiple files by uploading them to the +[validation page](/check). ### Example @@ -292,7 +295,7 @@ requires: version: 0.3.2 Super Mario 64: progression_balancing: 50 - accessibilty: items + accessibility: items EnableCoinStars: false StrictCapRequirements: true StrictCannonRequirements: true @@ -312,7 +315,7 @@ name: Minecraft game: Minecraft Minecraft: progression_balancing: 50 - accessibilty: items + accessibility: items advancement_goal: 40 combat_difficulty: hard include_hard_advancements: false @@ -338,7 +341,7 @@ game: ChecksFinder ChecksFinder: progression_balancing: 50 - accessibilty: items + accessibility: items ``` The above example will generate 3 worlds - one Super Mario 64, one Minecraft, and one ChecksFinder. diff --git a/worlds/generic/docs/commands_en.md b/worlds/generic/docs/commands_en.md index fe12f10ee3af..317f724109e1 100644 --- a/worlds/generic/docs/commands_en.md +++ b/worlds/generic/docs/commands_en.md @@ -95,7 +95,9 @@ The following commands are available in the clients that use the CommonClient, f - `/received` Displays all the items you have received from all players, including yourself. - `/missing` Displays all the locations along with their current status (checked/missing). - `/items` Lists all the item names for the current game. +- `/item_groups` Lists all the item group names for the current game. - `/locations` Lists all the location names for the current game. +- `/location_groups` Lists all the location group names for the current game. - `/ready` Sends ready status to the server. - Typing anything that doesn't start with `/` will broadcast a message to all players. diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index d6a09cf4e610..161b1e465b33 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -201,7 +201,7 @@ Kirby's Dream Land 3: As this is currently only supported by A Link to the Past, instead of finding an explanation here, please refer to the relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the%20Past/plando/en) -## Connections Plando +## Connection Plando This is currently only supported by a few games, including A Link to the Past, Minecraft, and Ocarina of Time. As the way that these games interact with their connections is different, only the basics are explained here. More specific information for connection plando in A Link to the Past can be found in diff --git a/worlds/generic/docs/setup_en.md b/worlds/generic/docs/setup_en.md index b99cdbc0fe54..22622cd0e94d 100644 --- a/worlds/generic/docs/setup_en.md +++ b/worlds/generic/docs/setup_en.md @@ -11,8 +11,8 @@ Some steps also assume use of Windows, so may vary with your OS. ## Installing the Archipelago software -The most recent public release of Archipelago can be found on the GitHub Releases page: -[Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases). +The most recent public release of Archipelago can be found on GitHub: +[Archipelago Latest Release](https://github.com/ArchipelagoMW/Archipelago/releases/latest). Run the exe file, and after accepting the license agreement you will be asked which components you would like to install. @@ -39,7 +39,7 @@ to your Archipelago installation. ### What is a YAML? YAML is the file format which Archipelago uses in order to configure a player's world. It allows you to dictate which -game you will be playing as well as the settings you would like for that game. +game you will be playing as well as the options you would like for that game. YAML is a format very similar to JSON however it is made to be more human-readable. If you are ever unsure of the validity of your YAML file you may check the file by uploading it to the check page on the Archipelago website: @@ -48,10 +48,10 @@ validity of your YAML file you may check the file by uploading it to the check p ### Creating a YAML YAML files may be generated on the Archipelago website by visiting the [games page](/games) and clicking the -"Settings Page" link under the relevant game. Clicking "Export Settings" in a game's settings page will download the +"Options Page" link under the relevant game. Clicking "Export Options" in a game's options page will download the YAML to your system. -Alternatively, you can run `ArchipelagoLauncher.exe` and click on `Generate Template Settings` to create a set of template +Alternatively, you can run `ArchipelagoLauncher.exe` and click on `Generate Template Options` to create a set of template YAMLs for each game in your Archipelago install (including for APWorlds). These will be placed in your `Players/Templates` folder. In a multiworld there must be one YAML per world. Any number of players can play on each world using either the game's @@ -66,17 +66,17 @@ each player is planning on playing their own game then they will each need a YAM #### On the website The easiest way to get started playing an Archipelago generated game, after following the base setup from the game's -setup guide, is to find the game on the [Archipelago Games List](/games), click on `Settings Page`, set the settings for +setup guide, is to find the game on the [Archipelago Games List](/games), click on `Options Page`, set the options for how you want to play, and click `Generate Game` at the bottom of the page. This will create a page for the seed, from which you can create a room, and then [connect](#connecting-to-an-archipelago-server). -If you have downloaded the settings, or have created a settings file manually, this file can be uploaded on the +If you have downloaded the options, or have created an options file manually, this file can be uploaded on the [Generation Page](/generate) where you can also set any specific hosting settings. #### On your local installation To generate a game on your local machine, make sure to install the Archipelago software. Navigate to your Archipelago -installation (usually C:\ProgramData\Archipelago), and place the settings file you have either created or downloaded +installation (usually C:\ProgramData\Archipelago), and place the options file you have either created or downloaded from the website in the `Players` folder. Run `ArchipelagoGenerate.exe`, or click on `Generate` in the launcher, and it will inform you whether the generation @@ -97,7 +97,7 @@ resources, and host the resulting multiworld on the website. #### Gather All Player YAMLs -All players that wish to play in the generated multiworld must have a YAML file which contains the settings that they +All players that wish to play in the generated multiworld must have a YAML file which contains the options that they wish to play with. One person should gather all files from all participants in the generated multiworld. It is possible for a single player to have multiple games, or even multiple slots of a single game, but each YAML must have a unique player name. @@ -129,7 +129,7 @@ need the corresponding ROM files. Sometimes there are various settings that you may want to change before rolling a seed such as enabling race mode, auto-release, plando support, or setting a password. -All of these settings, plus other options, may be changed by modifying the `host.yaml` file in the Archipelago +All of these settings, plus more, can be changed by modifying the `host.yaml` file in the Archipelago installation folder. You can quickly access this file by clicking on `Open host.yaml` in the launcher. The settings chosen here are baked into the `.archipelago` file that gets output with the other files after generation, so if you are rolling locally, ensure this file is edited to your liking **before** rolling the seed. This file is overwritten @@ -207,4 +207,4 @@ when creating your [YAML file](#creating-a-yaml). If the game is hosted on the w room page. The name is case-sensitive. * `Password` is the password set by the host in order to join the multiworld. By default, this will be empty and is almost never required, but one can be set when generating the game. Generally, leave this field blank when it exists, -unless you know that a password was set, and what that password is. \ No newline at end of file +unless you know that a password was set, and what that password is. diff --git a/worlds/generic/docs/triggers_en.md b/worlds/generic/docs/triggers_en.md index a9ffebb4669a..b751b8a3ec01 100644 --- a/worlds/generic/docs/triggers_en.md +++ b/worlds/generic/docs/triggers_en.md @@ -6,7 +6,7 @@ about 5 minutes to read. ## What are triggers? -Triggers allow you to customize your game settings by allowing you to define one or many options which only occur under +Triggers allow you to customize your game options by allowing you to define one or many options which only occur under specific conditions. These are essentially "if, then" statements for options in your game. A good example of what you can do with triggers is the [custom mercenary mode YAML ](https://github.com/alwaysintreble/Archipelago-yaml-dump/blob/main/Snippets/Mercenary%20Mode%20Snippet.yaml) that was @@ -121,4 +121,42 @@ For example: In this example (thanks to @Black-Sliver), if the `pupdunk` option is rolled, then the difficulty values will be rolled again using the new options `normal`, `pupdunk_hard`, and `pupdunk_mystery`, and the exp modifier will be rerolled using new weights for 150 and 200. This allows for two more triggers that will only be used for the new `pupdunk_hard` -and `pupdunk_mystery` options so that they will only be triggered on "pupdunk AND hard/mystery". \ No newline at end of file +and `pupdunk_mystery` options so that they will only be triggered on "pupdunk AND hard/mystery". + +## Adding or Removing from a List, Set, or Dict Option + +List, set, and dict options can additionally have values added to or removed from itself without overriding the existing +option value by prefixing the option name in the trigger block with `+` (add) or `-` (remove). The exact behavior for +each will depend on the option type. + +- For sets, `+` will add the value(s) to the set and `-` will remove the value(s) from the set. Sets do not allow + duplicates. +- For lists, `+` will add new values(s) to the list and `-` will remove the first matching values(s) it comes across. + Lists allow duplicate values. +- For dicts, `+` will add the value(s) to the given key(s) inside the dict if it exists, or add it otherwise. `-` is the + inverse operation of addition (and negative values are allowed). + +For example: + +```yaml +Super Metroid: + start_location: + landing_site: 50 + aqueduct: 50 + start_hints: + - Morph Ball + start_inventory: + Power Bombs: 1 + triggers: + - option_category: Super Metroid + option_name: start_location + option_result: aqueduct + options: + Super Metroid: + +start_hints: + - Gravity Suit +``` + +In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be +created. If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph +Ball. diff --git a/worlds/heretic/Locations.py b/worlds/heretic/Locations.py index f9590de77660..ff32df7b34c5 100644 --- a/worlds/heretic/Locations.py +++ b/worlds/heretic/Locations.py @@ -1266,7 +1266,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 10, 'doom_type': 79, - 'region': "The River of Fire (E2M3) Main"}, + 'region': "The River of Fire (E2M3) Green"}, 371179: {'name': 'The River of Fire (E2M3) - Green key', 'episode': 2, 'check_sanity': False, @@ -1301,7 +1301,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 122, 'doom_type': 2003, - 'region': "The River of Fire (E2M3) Main"}, + 'region': "The River of Fire (E2M3) Green"}, 371184: {'name': 'The River of Fire (E2M3) - Hellstaff', 'episode': 2, 'check_sanity': False, @@ -1364,7 +1364,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 299, 'doom_type': 32, - 'region': "The River of Fire (E2M3) Main"}, + 'region': "The River of Fire (E2M3) Green"}, 371193: {'name': 'The River of Fire (E2M3) - Morph Ovum', 'episode': 2, 'check_sanity': False, @@ -1385,7 +1385,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 413, 'doom_type': 2002, - 'region': "The River of Fire (E2M3) Main"}, + 'region': "The River of Fire (E2M3) Green"}, 371196: {'name': 'The River of Fire (E2M3) - Firemace 2', 'episode': 2, 'check_sanity': True, @@ -2610,7 +2610,7 @@ class LocationDict(TypedDict, total=False): 'map': 2, 'index': 172, 'doom_type': 33, - 'region': "The Cesspool (E3M2) Main"}, + 'region': "The Cesspool (E3M2) Yellow"}, 371371: {'name': 'The Cesspool (E3M2) - Bag of Holding 2', 'episode': 3, 'check_sanity': False, @@ -4360,7 +4360,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 297, 'doom_type': 2002, - 'region': "Ambulatory (E4M3) Green"}, + 'region': "Ambulatory (E4M3) Green Lock"}, 371621: {'name': 'Ambulatory (E4M3) - Firemace 2', 'episode': 4, 'check_sanity': False, @@ -6040,7 +6040,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 234, 'doom_type': 85, - 'region': "Quay (E5M3) Blue"}, + 'region': "Quay (E5M3) Cyan"}, 371861: {'name': 'Quay (E5M3) - Map Scroll', 'episode': 5, 'check_sanity': True, @@ -6075,7 +6075,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 239, 'doom_type': 86, - 'region': "Quay (E5M3) Blue"}, + 'region': "Quay (E5M3) Cyan"}, 371866: {'name': 'Quay (E5M3) - Torch', 'episode': 5, 'check_sanity': False, @@ -6089,7 +6089,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 242, 'doom_type': 2002, - 'region': "Quay (E5M3) Blue"}, + 'region': "Quay (E5M3) Cyan"}, 371868: {'name': 'Quay (E5M3) - Firemace 2', 'episode': 5, 'check_sanity': False, @@ -6124,7 +6124,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': 247, 'doom_type': 2002, - 'region': "Quay (E5M3) Blue"}, + 'region': "Quay (E5M3) Cyan"}, 371873: {'name': 'Quay (E5M3) - Bag of Holding 2', 'episode': 5, 'check_sanity': True, @@ -6138,7 +6138,7 @@ class LocationDict(TypedDict, total=False): 'map': 3, 'index': -1, 'doom_type': -1, - 'region': "Quay (E5M3) Blue"}, + 'region': "Quay (E5M3) Cyan"}, 371875: {'name': 'Courtyard (E5M4) - Blue key', 'episode': 5, 'check_sanity': False, diff --git a/worlds/heretic/Options.py b/worlds/heretic/Options.py index 34255f39eb5a..75e2257a7336 100644 --- a/worlds/heretic/Options.py +++ b/worlds/heretic/Options.py @@ -1,6 +1,5 @@ -import typing - -from Options import AssembleOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool +from Options import PerGameCommonOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool +from dataclasses import dataclass class Goal(Choice): @@ -146,22 +145,22 @@ class Episode5(Toggle): display_name = "Episode 5" -options: typing.Dict[str, AssembleOptions] = { - "start_inventory_from_pool": StartInventoryPool, - "goal": Goal, - "difficulty": Difficulty, - "random_monsters": RandomMonsters, - "random_pickups": RandomPickups, - "random_music": RandomMusic, - "allow_death_logic": AllowDeathLogic, - "pro": Pro, - "check_sanity": CheckSanity, - "start_with_map_scrolls": StartWithMapScrolls, - "reset_level_on_death": ResetLevelOnDeath, - "death_link": DeathLink, - "episode1": Episode1, - "episode2": Episode2, - "episode3": Episode3, - "episode4": Episode4, - "episode5": Episode5 -} +@dataclass +class HereticOptions(PerGameCommonOptions): + start_inventory_from_pool: StartInventoryPool + goal: Goal + difficulty: Difficulty + random_monsters: RandomMonsters + random_pickups: RandomPickups + random_music: RandomMusic + allow_death_logic: AllowDeathLogic + pro: Pro + check_sanity: CheckSanity + start_with_map_scrolls: StartWithMapScrolls + reset_level_on_death: ResetLevelOnDeath + death_link: DeathLink + episode1: Episode1 + episode2: Episode2 + episode3: Episode3 + episode4: Episode4 + episode5: Episode5 diff --git a/worlds/heretic/Regions.py b/worlds/heretic/Regions.py index a30f0120a0c4..81a4c9ce49dc 100644 --- a/worlds/heretic/Regions.py +++ b/worlds/heretic/Regions.py @@ -604,7 +604,8 @@ class RegionDict(TypedDict, total=False): "connections":[ {"target":"Ambulatory (E4M3) Blue","pro":False}, {"target":"Ambulatory (E4M3) Yellow","pro":False}, - {"target":"Ambulatory (E4M3) Green","pro":False}]}, + {"target":"Ambulatory (E4M3) Green","pro":False}, + {"target":"Ambulatory (E4M3) Green Lock","pro":False}]}, {"name":"Ambulatory (E4M3) Blue", "connects_to_hub":False, "episode":4, @@ -619,6 +620,12 @@ class RegionDict(TypedDict, total=False): "connects_to_hub":False, "episode":4, "connections":[{"target":"Ambulatory (E4M3) Main","pro":False}]}, + {"name":"Ambulatory (E4M3) Green Lock", + "connects_to_hub":False, + "episode":4, + "connections":[ + {"target":"Ambulatory (E4M3) Green","pro":False}, + {"target":"Ambulatory (E4M3) Main","pro":False}]}, # Sepulcher (E4M4) {"name":"Sepulcher (E4M4) Main", @@ -767,9 +774,7 @@ class RegionDict(TypedDict, total=False): {"name":"Quay (E5M3) Blue", "connects_to_hub":False, "episode":5, - "connections":[ - {"target":"Quay (E5M3) Green","pro":False}, - {"target":"Quay (E5M3) Main","pro":False}]}, + "connections":[{"target":"Quay (E5M3) Main","pro":False}]}, {"name":"Quay (E5M3) Yellow", "connects_to_hub":False, "episode":5, @@ -779,7 +784,11 @@ class RegionDict(TypedDict, total=False): "episode":5, "connections":[ {"target":"Quay (E5M3) Main","pro":False}, - {"target":"Quay (E5M3) Blue","pro":False}]}, + {"target":"Quay (E5M3) Cyan","pro":False}]}, + {"name":"Quay (E5M3) Cyan", + "connects_to_hub":False, + "episode":5, + "connections":[{"target":"Quay (E5M3) Main","pro":False}]}, # Courtyard (E5M4) {"name":"Courtyard (E5M4) Main", diff --git a/worlds/heretic/Rules.py b/worlds/heretic/Rules.py index 7ef15d7920dd..579fd8b77179 100644 --- a/worlds/heretic/Rules.py +++ b/worlds/heretic/Rules.py @@ -7,185 +7,185 @@ from . import HereticWorld -def set_episode1_rules(player, world, pro): +def set_episode1_rules(player, multiworld, pro): # The Docks (E1M1) - set_rule(world.get_entrance("Hub -> The Docks (E1M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Docks (E1M1) Main", player), lambda state: state.has("The Docks (E1M1)", player, 1)) - set_rule(world.get_entrance("The Docks (E1M1) Main -> The Docks (E1M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Docks (E1M1) Main -> The Docks (E1M1) Yellow", player), lambda state: state.has("The Docks (E1M1) - Yellow key", player, 1)) # The Dungeons (E1M2) - set_rule(world.get_entrance("Hub -> The Dungeons (E1M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Dungeons (E1M2) Main", player), lambda state: (state.has("The Dungeons (E1M2)", player, 1)) and (state.has("Dragon Claw", player, 1) or state.has("Ethereal Crossbow", player, 1))) - set_rule(world.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Yellow", player), lambda state: state.has("The Dungeons (E1M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Green", player), lambda state: state.has("The Dungeons (E1M2) - Green key", player, 1)) - set_rule(world.get_entrance("The Dungeons (E1M2) Blue -> The Dungeons (E1M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Dungeons (E1M2) Blue -> The Dungeons (E1M2) Yellow", player), lambda state: state.has("The Dungeons (E1M2) - Blue key", player, 1)) - set_rule(world.get_entrance("The Dungeons (E1M2) Yellow -> The Dungeons (E1M2) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Dungeons (E1M2) Yellow -> The Dungeons (E1M2) Blue", player), lambda state: state.has("The Dungeons (E1M2) - Blue key", player, 1)) # The Gatehouse (E1M3) - set_rule(world.get_entrance("Hub -> The Gatehouse (E1M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Gatehouse (E1M3) Main", player), lambda state: (state.has("The Gatehouse (E1M3)", player, 1)) and (state.has("Ethereal Crossbow", player, 1) or state.has("Dragon Claw", player, 1))) - set_rule(world.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Yellow", player), lambda state: state.has("The Gatehouse (E1M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Sea", player), lambda state: + set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Sea", player), lambda state: state.has("The Gatehouse (E1M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Green", player), lambda state: state.has("The Gatehouse (E1M3) - Green key", player, 1)) - set_rule(world.get_entrance("The Gatehouse (E1M3) Green -> The Gatehouse (E1M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Green -> The Gatehouse (E1M3) Main", player), lambda state: state.has("The Gatehouse (E1M3) - Green key", player, 1)) # The Guard Tower (E1M4) - set_rule(world.get_entrance("Hub -> The Guard Tower (E1M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Guard Tower (E1M4) Main", player), lambda state: (state.has("The Guard Tower (E1M4)", player, 1)) and (state.has("Ethereal Crossbow", player, 1) or state.has("Dragon Claw", player, 1))) - set_rule(world.get_entrance("The Guard Tower (E1M4) Main -> The Guard Tower (E1M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Guard Tower (E1M4) Main -> The Guard Tower (E1M4) Yellow", player), lambda state: state.has("The Guard Tower (E1M4) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Guard Tower (E1M4) Yellow -> The Guard Tower (E1M4) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Guard Tower (E1M4) Yellow -> The Guard Tower (E1M4) Green", player), lambda state: state.has("The Guard Tower (E1M4) - Green key", player, 1)) - set_rule(world.get_entrance("The Guard Tower (E1M4) Green -> The Guard Tower (E1M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Guard Tower (E1M4) Green -> The Guard Tower (E1M4) Yellow", player), lambda state: state.has("The Guard Tower (E1M4) - Green key", player, 1)) # The Citadel (E1M5) - set_rule(world.get_entrance("Hub -> The Citadel (E1M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Citadel (E1M5) Main", player), lambda state: (state.has("The Citadel (E1M5)", player, 1) and state.has("Ethereal Crossbow", player, 1)) and (state.has("Dragon Claw", player, 1) or state.has("Gauntlets of the Necromancer", player, 1))) - set_rule(world.get_entrance("The Citadel (E1M5) Main -> The Citadel (E1M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Citadel (E1M5) Main -> The Citadel (E1M5) Yellow", player), lambda state: state.has("The Citadel (E1M5) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Citadel (E1M5) Blue -> The Citadel (E1M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Citadel (E1M5) Blue -> The Citadel (E1M5) Green", player), lambda state: state.has("The Citadel (E1M5) - Blue key", player, 1)) - set_rule(world.get_entrance("The Citadel (E1M5) Yellow -> The Citadel (E1M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Citadel (E1M5) Yellow -> The Citadel (E1M5) Green", player), lambda state: state.has("The Citadel (E1M5) - Green key", player, 1)) - set_rule(world.get_entrance("The Citadel (E1M5) Green -> The Citadel (E1M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Citadel (E1M5) Green -> The Citadel (E1M5) Blue", player), lambda state: state.has("The Citadel (E1M5) - Blue key", player, 1)) # The Cathedral (E1M6) - set_rule(world.get_entrance("Hub -> The Cathedral (E1M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Cathedral (E1M6) Main", player), lambda state: (state.has("The Cathedral (E1M6)", player, 1) and state.has("Ethereal Crossbow", player, 1)) and (state.has("Gauntlets of the Necromancer", player, 1) or state.has("Dragon Claw", player, 1))) - set_rule(world.get_entrance("The Cathedral (E1M6) Main -> The Cathedral (E1M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Cathedral (E1M6) Main -> The Cathedral (E1M6) Yellow", player), lambda state: state.has("The Cathedral (E1M6) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Cathedral (E1M6) Yellow -> The Cathedral (E1M6) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Cathedral (E1M6) Yellow -> The Cathedral (E1M6) Green", player), lambda state: state.has("The Cathedral (E1M6) - Green key", player, 1)) # The Crypts (E1M7) - set_rule(world.get_entrance("Hub -> The Crypts (E1M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Crypts (E1M7) Main", player), lambda state: (state.has("The Crypts (E1M7)", player, 1) and state.has("Ethereal Crossbow", player, 1)) and (state.has("Gauntlets of the Necromancer", player, 1) or state.has("Dragon Claw", player, 1))) - set_rule(world.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Yellow", player), lambda state: state.has("The Crypts (E1M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Green", player), lambda state: state.has("The Crypts (E1M7) - Green key", player, 1)) - set_rule(world.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Green", player), lambda state: state.has("The Crypts (E1M7) - Green key", player, 1)) - set_rule(world.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Blue", player), lambda state: state.has("The Crypts (E1M7) - Blue key", player, 1)) - set_rule(world.get_entrance("The Crypts (E1M7) Green -> The Crypts (E1M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Crypts (E1M7) Green -> The Crypts (E1M7) Main", player), lambda state: state.has("The Crypts (E1M7) - Green key", player, 1)) # Hell's Maw (E1M8) - set_rule(world.get_entrance("Hub -> Hell's Maw (E1M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Hell's Maw (E1M8) Main", player), lambda state: state.has("Hell's Maw (E1M8)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) # The Graveyard (E1M9) - set_rule(world.get_entrance("Hub -> The Graveyard (E1M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Graveyard (E1M9) Main", player), lambda state: state.has("The Graveyard (E1M9)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) - set_rule(world.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Yellow", player), lambda state: state.has("The Graveyard (E1M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Green", player), lambda state: state.has("The Graveyard (E1M9) - Green key", player, 1)) - set_rule(world.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Blue", player), lambda state: state.has("The Graveyard (E1M9) - Blue key", player, 1)) - set_rule(world.get_entrance("The Graveyard (E1M9) Yellow -> The Graveyard (E1M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Graveyard (E1M9) Yellow -> The Graveyard (E1M9) Main", player), lambda state: state.has("The Graveyard (E1M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Graveyard (E1M9) Green -> The Graveyard (E1M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Graveyard (E1M9) Green -> The Graveyard (E1M9) Main", player), lambda state: state.has("The Graveyard (E1M9) - Green key", player, 1)) -def set_episode2_rules(player, world, pro): +def set_episode2_rules(player, multiworld, pro): # The Crater (E2M1) - set_rule(world.get_entrance("Hub -> The Crater (E2M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Crater (E2M1) Main", player), lambda state: state.has("The Crater (E2M1)", player, 1)) - set_rule(world.get_entrance("The Crater (E2M1) Main -> The Crater (E2M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Crater (E2M1) Main -> The Crater (E2M1) Yellow", player), lambda state: state.has("The Crater (E2M1) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Crater (E2M1) Yellow -> The Crater (E2M1) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Crater (E2M1) Yellow -> The Crater (E2M1) Green", player), lambda state: state.has("The Crater (E2M1) - Green key", player, 1)) - set_rule(world.get_entrance("The Crater (E2M1) Green -> The Crater (E2M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Crater (E2M1) Green -> The Crater (E2M1) Yellow", player), lambda state: state.has("The Crater (E2M1) - Green key", player, 1)) # The Lava Pits (E2M2) - set_rule(world.get_entrance("Hub -> The Lava Pits (E2M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Lava Pits (E2M2) Main", player), lambda state: (state.has("The Lava Pits (E2M2)", player, 1)) and (state.has("Ethereal Crossbow", player, 1) or state.has("Dragon Claw", player, 1))) - set_rule(world.get_entrance("The Lava Pits (E2M2) Main -> The Lava Pits (E2M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Main -> The Lava Pits (E2M2) Yellow", player), lambda state: state.has("The Lava Pits (E2M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Green", player), lambda state: state.has("The Lava Pits (E2M2) - Green key", player, 1)) - set_rule(world.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Main", player), lambda state: state.has("The Lava Pits (E2M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Lava Pits (E2M2) Green -> The Lava Pits (E2M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Green -> The Lava Pits (E2M2) Yellow", player), lambda state: state.has("The Lava Pits (E2M2) - Green key", player, 1)) # The River of Fire (E2M3) - set_rule(world.get_entrance("Hub -> The River of Fire (E2M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The River of Fire (E2M3) Main", player), lambda state: state.has("The River of Fire (E2M3)", player, 1) and state.has("Dragon Claw", player, 1) and state.has("Ethereal Crossbow", player, 1)) - set_rule(world.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Yellow", player), lambda state: state.has("The River of Fire (E2M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Blue", player), lambda state: state.has("The River of Fire (E2M3) - Blue key", player, 1)) - set_rule(world.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Green", player), lambda state: state.has("The River of Fire (E2M3) - Green key", player, 1)) - set_rule(world.get_entrance("The River of Fire (E2M3) Blue -> The River of Fire (E2M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("The River of Fire (E2M3) Blue -> The River of Fire (E2M3) Main", player), lambda state: state.has("The River of Fire (E2M3) - Blue key", player, 1)) - set_rule(world.get_entrance("The River of Fire (E2M3) Yellow -> The River of Fire (E2M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("The River of Fire (E2M3) Yellow -> The River of Fire (E2M3) Main", player), lambda state: state.has("The River of Fire (E2M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("The River of Fire (E2M3) Green -> The River of Fire (E2M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("The River of Fire (E2M3) Green -> The River of Fire (E2M3) Main", player), lambda state: state.has("The River of Fire (E2M3) - Green key", player, 1)) # The Ice Grotto (E2M4) - set_rule(world.get_entrance("Hub -> The Ice Grotto (E2M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Ice Grotto (E2M4) Main", player), lambda state: (state.has("The Ice Grotto (E2M4)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) and (state.has("Hellstaff", player, 1) or state.has("Firemace", player, 1))) - set_rule(world.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Green", player), lambda state: state.has("The Ice Grotto (E2M4) - Green key", player, 1)) - set_rule(world.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Yellow", player), lambda state: state.has("The Ice Grotto (E2M4) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Ice Grotto (E2M4) Blue -> The Ice Grotto (E2M4) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Blue -> The Ice Grotto (E2M4) Green", player), lambda state: state.has("The Ice Grotto (E2M4) - Blue key", player, 1)) - set_rule(world.get_entrance("The Ice Grotto (E2M4) Yellow -> The Ice Grotto (E2M4) Magenta", player), lambda state: + set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Yellow -> The Ice Grotto (E2M4) Magenta", player), lambda state: state.has("The Ice Grotto (E2M4) - Green key", player, 1) and state.has("The Ice Grotto (E2M4) - Blue key", player, 1)) - set_rule(world.get_entrance("The Ice Grotto (E2M4) Green -> The Ice Grotto (E2M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Green -> The Ice Grotto (E2M4) Blue", player), lambda state: state.has("The Ice Grotto (E2M4) - Blue key", player, 1)) # The Catacombs (E2M5) - set_rule(world.get_entrance("Hub -> The Catacombs (E2M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Catacombs (E2M5) Main", player), lambda state: (state.has("The Catacombs (E2M5)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -193,17 +193,17 @@ def set_episode2_rules(player, world, pro): (state.has("Phoenix Rod", player, 1) or state.has("Firemace", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("The Catacombs (E2M5) Main -> The Catacombs (E2M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Catacombs (E2M5) Main -> The Catacombs (E2M5) Yellow", player), lambda state: state.has("The Catacombs (E2M5) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Catacombs (E2M5) Blue -> The Catacombs (E2M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Catacombs (E2M5) Blue -> The Catacombs (E2M5) Green", player), lambda state: state.has("The Catacombs (E2M5) - Blue key", player, 1)) - set_rule(world.get_entrance("The Catacombs (E2M5) Yellow -> The Catacombs (E2M5) Green", player), lambda state: - state.has("The Catacombs (E2M5) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Catacombs (E2M5) Green -> The Catacombs (E2M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Catacombs (E2M5) Yellow -> The Catacombs (E2M5) Green", player), lambda state: + state.has("The Catacombs (E2M5) - Green key", player, 1)) + set_rule(multiworld.get_entrance("The Catacombs (E2M5) Green -> The Catacombs (E2M5) Blue", player), lambda state: state.has("The Catacombs (E2M5) - Blue key", player, 1)) # The Labyrinth (E2M6) - set_rule(world.get_entrance("Hub -> The Labyrinth (E2M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Labyrinth (E2M6) Main", player), lambda state: (state.has("The Labyrinth (E2M6)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -211,17 +211,17 @@ def set_episode2_rules(player, world, pro): (state.has("Phoenix Rod", player, 1) or state.has("Firemace", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Blue", player), lambda state: state.has("The Labyrinth (E2M6) - Blue key", player, 1)) - set_rule(world.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Yellow", player), lambda state: state.has("The Labyrinth (E2M6) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Green", player), lambda state: state.has("The Labyrinth (E2M6) - Green key", player, 1)) - set_rule(world.get_entrance("The Labyrinth (E2M6) Blue -> The Labyrinth (E2M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Blue -> The Labyrinth (E2M6) Main", player), lambda state: state.has("The Labyrinth (E2M6) - Blue key", player, 1)) # The Great Hall (E2M7) - set_rule(world.get_entrance("Hub -> The Great Hall (E2M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Great Hall (E2M7) Main", player), lambda state: (state.has("The Great Hall (E2M7)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -229,19 +229,19 @@ def set_episode2_rules(player, world, pro): state.has("Firemace", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Yellow", player), lambda state: state.has("The Great Hall (E2M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Green", player), lambda state: state.has("The Great Hall (E2M7) - Green key", player, 1)) - set_rule(world.get_entrance("The Great Hall (E2M7) Blue -> The Great Hall (E2M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Great Hall (E2M7) Blue -> The Great Hall (E2M7) Yellow", player), lambda state: state.has("The Great Hall (E2M7) - Blue key", player, 1)) - set_rule(world.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Blue", player), lambda state: state.has("The Great Hall (E2M7) - Blue key", player, 1)) - set_rule(world.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Main", player), lambda state: state.has("The Great Hall (E2M7) - Yellow key", player, 1)) # The Portals of Chaos (E2M8) - set_rule(world.get_entrance("Hub -> The Portals of Chaos (E2M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Portals of Chaos (E2M8) Main", player), lambda state: state.has("The Portals of Chaos (E2M8)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -251,7 +251,7 @@ def set_episode2_rules(player, world, pro): state.has("Hellstaff", player, 1)) # The Glacier (E2M9) - set_rule(world.get_entrance("Hub -> The Glacier (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Glacier (E2M9) Main", player), lambda state: (state.has("The Glacier (E2M9)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -259,51 +259,51 @@ def set_episode2_rules(player, world, pro): state.has("Firemace", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Yellow", player), lambda state: state.has("The Glacier (E2M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Blue", player), lambda state: state.has("The Glacier (E2M9) - Blue key", player, 1)) - set_rule(world.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Green", player), lambda state: state.has("The Glacier (E2M9) - Green key", player, 1)) - set_rule(world.get_entrance("The Glacier (E2M9) Blue -> The Glacier (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Glacier (E2M9) Blue -> The Glacier (E2M9) Main", player), lambda state: state.has("The Glacier (E2M9) - Blue key", player, 1)) - set_rule(world.get_entrance("The Glacier (E2M9) Yellow -> The Glacier (E2M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Glacier (E2M9) Yellow -> The Glacier (E2M9) Main", player), lambda state: state.has("The Glacier (E2M9) - Yellow key", player, 1)) -def set_episode3_rules(player, world, pro): +def set_episode3_rules(player, multiworld, pro): # The Storehouse (E3M1) - set_rule(world.get_entrance("Hub -> The Storehouse (E3M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Storehouse (E3M1) Main", player), lambda state: state.has("The Storehouse (E3M1)", player, 1)) - set_rule(world.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Yellow", player), lambda state: state.has("The Storehouse (E3M1) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Green", player), lambda state: state.has("The Storehouse (E3M1) - Green key", player, 1)) - set_rule(world.get_entrance("The Storehouse (E3M1) Yellow -> The Storehouse (E3M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Storehouse (E3M1) Yellow -> The Storehouse (E3M1) Main", player), lambda state: state.has("The Storehouse (E3M1) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Storehouse (E3M1) Green -> The Storehouse (E3M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Storehouse (E3M1) Green -> The Storehouse (E3M1) Main", player), lambda state: state.has("The Storehouse (E3M1) - Green key", player, 1)) # The Cesspool (E3M2) - set_rule(world.get_entrance("Hub -> The Cesspool (E3M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Cesspool (E3M2) Main", player), lambda state: state.has("The Cesspool (E3M2)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and state.has("Firemace", player, 1) and state.has("Hellstaff", player, 1)) - set_rule(world.get_entrance("The Cesspool (E3M2) Main -> The Cesspool (E3M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Cesspool (E3M2) Main -> The Cesspool (E3M2) Yellow", player), lambda state: state.has("The Cesspool (E3M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Cesspool (E3M2) Blue -> The Cesspool (E3M2) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Cesspool (E3M2) Blue -> The Cesspool (E3M2) Green", player), lambda state: state.has("The Cesspool (E3M2) - Blue key", player, 1)) - set_rule(world.get_entrance("The Cesspool (E3M2) Yellow -> The Cesspool (E3M2) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Cesspool (E3M2) Yellow -> The Cesspool (E3M2) Green", player), lambda state: state.has("The Cesspool (E3M2) - Green key", player, 1)) - set_rule(world.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Blue", player), lambda state: state.has("The Cesspool (E3M2) - Blue key", player, 1)) - set_rule(world.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Yellow", player), lambda state: state.has("The Cesspool (E3M2) - Green key", player, 1)) # The Confluence (E3M3) - set_rule(world.get_entrance("Hub -> The Confluence (E3M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Confluence (E3M3) Main", player), lambda state: (state.has("The Confluence (E3M3)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) and @@ -311,19 +311,19 @@ def set_episode3_rules(player, world, pro): state.has("Phoenix Rod", player, 1) or state.has("Firemace", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Green", player), lambda state: state.has("The Confluence (E3M3) - Green key", player, 1)) - set_rule(world.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Yellow", player), lambda state: state.has("The Confluence (E3M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Confluence (E3M3) Blue -> The Confluence (E3M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Confluence (E3M3) Blue -> The Confluence (E3M3) Green", player), lambda state: state.has("The Confluence (E3M3) - Blue key", player, 1)) - set_rule(world.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Main", player), lambda state: state.has("The Confluence (E3M3) - Green key", player, 1)) - set_rule(world.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Blue", player), lambda state: state.has("The Confluence (E3M3) - Blue key", player, 1)) # The Azure Fortress (E3M4) - set_rule(world.get_entrance("Hub -> The Azure Fortress (E3M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Azure Fortress (E3M4) Main", player), lambda state: (state.has("The Azure Fortress (E3M4)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -331,13 +331,13 @@ def set_episode3_rules(player, world, pro): (state.has("Firemace", player, 1) or state.has("Phoenix Rod", player, 1) or state.has("Gauntlets of the Necromancer", player, 1))) - set_rule(world.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Green", player), lambda state: state.has("The Azure Fortress (E3M4) - Green key", player, 1)) - set_rule(world.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Yellow", player), lambda state: state.has("The Azure Fortress (E3M4) - Yellow key", player, 1)) # The Ophidian Lair (E3M5) - set_rule(world.get_entrance("Hub -> The Ophidian Lair (E3M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Ophidian Lair (E3M5) Main", player), lambda state: (state.has("The Ophidian Lair (E3M5)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -345,13 +345,13 @@ def set_episode3_rules(player, world, pro): (state.has("Gauntlets of the Necromancer", player, 1) or state.has("Phoenix Rod", player, 1) or state.has("Firemace", player, 1))) - set_rule(world.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Yellow", player), lambda state: state.has("The Ophidian Lair (E3M5) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Green", player), lambda state: state.has("The Ophidian Lair (E3M5) - Green key", player, 1)) # The Halls of Fear (E3M6) - set_rule(world.get_entrance("Hub -> The Halls of Fear (E3M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Halls of Fear (E3M6) Main", player), lambda state: (state.has("The Halls of Fear (E3M6)", player, 1) and state.has("Firemace", player, 1) and state.has("Hellstaff", player, 1) and @@ -359,17 +359,17 @@ def set_episode3_rules(player, world, pro): state.has("Ethereal Crossbow", player, 1)) and (state.has("Gauntlets of the Necromancer", player, 1) or state.has("Phoenix Rod", player, 1))) - set_rule(world.get_entrance("The Halls of Fear (E3M6) Main -> The Halls of Fear (E3M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Main -> The Halls of Fear (E3M6) Yellow", player), lambda state: state.has("The Halls of Fear (E3M6) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Halls of Fear (E3M6) Blue -> The Halls of Fear (E3M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Blue -> The Halls of Fear (E3M6) Yellow", player), lambda state: state.has("The Halls of Fear (E3M6) - Blue key", player, 1)) - set_rule(world.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Blue", player), lambda state: state.has("The Halls of Fear (E3M6) - Blue key", player, 1)) - set_rule(world.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Green", player), lambda state: state.has("The Halls of Fear (E3M6) - Green key", player, 1)) # The Chasm (E3M7) - set_rule(world.get_entrance("Hub -> The Chasm (E3M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Chasm (E3M7) Main", player), lambda state: (state.has("The Chasm (E3M7)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -377,19 +377,19 @@ def set_episode3_rules(player, world, pro): state.has("Hellstaff", player, 1)) and (state.has("Gauntlets of the Necromancer", player, 1) or state.has("Phoenix Rod", player, 1))) - set_rule(world.get_entrance("The Chasm (E3M7) Main -> The Chasm (E3M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (E3M7) Main -> The Chasm (E3M7) Yellow", player), lambda state: state.has("The Chasm (E3M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Main", player), lambda state: state.has("The Chasm (E3M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Green", player), lambda state: state.has("The Chasm (E3M7) - Green key", player, 1)) - set_rule(world.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Blue", player), lambda state: state.has("The Chasm (E3M7) - Blue key", player, 1)) - set_rule(world.get_entrance("The Chasm (E3M7) Green -> The Chasm (E3M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Chasm (E3M7) Green -> The Chasm (E3M7) Yellow", player), lambda state: state.has("The Chasm (E3M7) - Green key", player, 1)) # D'Sparil'S Keep (E3M8) - set_rule(world.get_entrance("Hub -> D'Sparil'S Keep (E3M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> D'Sparil'S Keep (E3M8) Main", player), lambda state: state.has("D'Sparil'S Keep (E3M8)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -399,7 +399,7 @@ def set_episode3_rules(player, world, pro): state.has("Hellstaff", player, 1)) # The Aquifier (E3M9) - set_rule(world.get_entrance("Hub -> The Aquifier (E3M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> The Aquifier (E3M9) Main", player), lambda state: state.has("The Aquifier (E3M9)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -407,23 +407,23 @@ def set_episode3_rules(player, world, pro): state.has("Phoenix Rod", player, 1) and state.has("Firemace", player, 1) and state.has("Hellstaff", player, 1)) - set_rule(world.get_entrance("The Aquifier (E3M9) Main -> The Aquifier (E3M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Aquifier (E3M9) Main -> The Aquifier (E3M9) Yellow", player), lambda state: state.has("The Aquifier (E3M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Green", player), lambda state: + set_rule(multiworld.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Green", player), lambda state: state.has("The Aquifier (E3M9) - Green key", player, 1)) - set_rule(world.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Main", player), lambda state: state.has("The Aquifier (E3M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("The Aquifier (E3M9) Green -> The Aquifier (E3M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("The Aquifier (E3M9) Green -> The Aquifier (E3M9) Yellow", player), lambda state: state.has("The Aquifier (E3M9) - Green key", player, 1)) -def set_episode4_rules(player, world, pro): +def set_episode4_rules(player, multiworld, pro): # Catafalque (E4M1) - set_rule(world.get_entrance("Hub -> Catafalque (E4M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Catafalque (E4M1) Main", player), lambda state: state.has("Catafalque (E4M1)", player, 1)) - set_rule(world.get_entrance("Catafalque (E4M1) Main -> Catafalque (E4M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Catafalque (E4M1) Main -> Catafalque (E4M1) Yellow", player), lambda state: state.has("Catafalque (E4M1) - Yellow key", player, 1)) - set_rule(world.get_entrance("Catafalque (E4M1) Yellow -> Catafalque (E4M1) Green", player), lambda state: + set_rule(multiworld.get_entrance("Catafalque (E4M1) Yellow -> Catafalque (E4M1) Green", player), lambda state: (state.has("Catafalque (E4M1) - Green key", player, 1)) and (state.has("Ethereal Crossbow", player, 1) or state.has("Dragon Claw", player, 1) or state.has("Phoenix Rod", player, 1) or @@ -431,23 +431,23 @@ def set_episode4_rules(player, world, pro): state.has("Hellstaff", player, 1))) # Blockhouse (E4M2) - set_rule(world.get_entrance("Hub -> Blockhouse (E4M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Blockhouse (E4M2) Main", player), lambda state: state.has("Blockhouse (E4M2)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) - set_rule(world.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Yellow", player), lambda state: state.has("Blockhouse (E4M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Green", player), lambda state: + set_rule(multiworld.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Green", player), lambda state: state.has("Blockhouse (E4M2) - Green key", player, 1)) - set_rule(world.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Blue", player), lambda state: state.has("Blockhouse (E4M2) - Blue key", player, 1)) - set_rule(world.get_entrance("Blockhouse (E4M2) Green -> Blockhouse (E4M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Blockhouse (E4M2) Green -> Blockhouse (E4M2) Main", player), lambda state: state.has("Blockhouse (E4M2) - Green key", player, 1)) - set_rule(world.get_entrance("Blockhouse (E4M2) Blue -> Blockhouse (E4M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Blockhouse (E4M2) Blue -> Blockhouse (E4M2) Main", player), lambda state: state.has("Blockhouse (E4M2) - Blue key", player, 1)) # Ambulatory (E4M3) - set_rule(world.get_entrance("Hub -> Ambulatory (E4M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Ambulatory (E4M3) Main", player), lambda state: (state.has("Ambulatory (E4M3)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -455,15 +455,17 @@ def set_episode4_rules(player, world, pro): (state.has("Phoenix Rod", player, 1) or state.has("Firemace", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Blue", player), lambda state: state.has("Ambulatory (E4M3) - Blue key", player, 1)) - set_rule(world.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Yellow", player), lambda state: state.has("Ambulatory (E4M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Green", player), lambda state: + state.has("Ambulatory (E4M3) - Green key", player, 1)) + set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Green Lock", player), lambda state: state.has("Ambulatory (E4M3) - Green key", player, 1)) # Sepulcher (E4M4) - set_rule(world.get_entrance("Hub -> Sepulcher (E4M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Sepulcher (E4M4) Main", player), lambda state: (state.has("Sepulcher (E4M4)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -473,7 +475,7 @@ def set_episode4_rules(player, world, pro): state.has("Hellstaff", player, 1))) # Great Stair (E4M5) - set_rule(world.get_entrance("Hub -> Great Stair (E4M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Great Stair (E4M5) Main", player), lambda state: (state.has("Great Stair (E4M5)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -481,19 +483,19 @@ def set_episode4_rules(player, world, pro): state.has("Firemace", player, 1)) and (state.has("Hellstaff", player, 1) or state.has("Phoenix Rod", player, 1))) - set_rule(world.get_entrance("Great Stair (E4M5) Main -> Great Stair (E4M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Great Stair (E4M5) Main -> Great Stair (E4M5) Yellow", player), lambda state: state.has("Great Stair (E4M5) - Yellow key", player, 1)) - set_rule(world.get_entrance("Great Stair (E4M5) Blue -> Great Stair (E4M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("Great Stair (E4M5) Blue -> Great Stair (E4M5) Green", player), lambda state: state.has("Great Stair (E4M5) - Blue key", player, 1)) - set_rule(world.get_entrance("Great Stair (E4M5) Yellow -> Great Stair (E4M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("Great Stair (E4M5) Yellow -> Great Stair (E4M5) Green", player), lambda state: state.has("Great Stair (E4M5) - Green key", player, 1)) - set_rule(world.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Blue", player), lambda state: state.has("Great Stair (E4M5) - Blue key", player, 1)) - set_rule(world.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Yellow", player), lambda state: state.has("Great Stair (E4M5) - Green key", player, 1)) # Halls of the Apostate (E4M6) - set_rule(world.get_entrance("Hub -> Halls of the Apostate (E4M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Halls of the Apostate (E4M6) Main", player), lambda state: (state.has("Halls of the Apostate (E4M6)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -501,19 +503,19 @@ def set_episode4_rules(player, world, pro): state.has("Firemace", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Halls of the Apostate (E4M6) Main -> Halls of the Apostate (E4M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Main -> Halls of the Apostate (E4M6) Yellow", player), lambda state: state.has("Halls of the Apostate (E4M6) - Yellow key", player, 1)) - set_rule(world.get_entrance("Halls of the Apostate (E4M6) Blue -> Halls of the Apostate (E4M6) Green", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Blue -> Halls of the Apostate (E4M6) Green", player), lambda state: state.has("Halls of the Apostate (E4M6) - Blue key", player, 1)) - set_rule(world.get_entrance("Halls of the Apostate (E4M6) Yellow -> Halls of the Apostate (E4M6) Green", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Yellow -> Halls of the Apostate (E4M6) Green", player), lambda state: state.has("Halls of the Apostate (E4M6) - Green key", player, 1)) - set_rule(world.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Yellow", player), lambda state: state.has("Halls of the Apostate (E4M6) - Green key", player, 1)) - set_rule(world.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Blue", player), lambda state: state.has("Halls of the Apostate (E4M6) - Blue key", player, 1)) # Ramparts of Perdition (E4M7) - set_rule(world.get_entrance("Hub -> Ramparts of Perdition (E4M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Ramparts of Perdition (E4M7) Main", player), lambda state: (state.has("Ramparts of Perdition (E4M7)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -521,21 +523,21 @@ def set_episode4_rules(player, world, pro): state.has("Firemace", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Main -> Ramparts of Perdition (E4M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Main -> Ramparts of Perdition (E4M7) Yellow", player), lambda state: state.has("Ramparts of Perdition (E4M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Blue -> Ramparts of Perdition (E4M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Blue -> Ramparts of Perdition (E4M7) Yellow", player), lambda state: state.has("Ramparts of Perdition (E4M7) - Blue key", player, 1)) - set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Main", player), lambda state: state.has("Ramparts of Perdition (E4M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Green", player), lambda state: state.has("Ramparts of Perdition (E4M7) - Green key", player, 1)) - set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Blue", player), lambda state: state.has("Ramparts of Perdition (E4M7) - Blue key", player, 1)) - set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Green -> Ramparts of Perdition (E4M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Green -> Ramparts of Perdition (E4M7) Yellow", player), lambda state: state.has("Ramparts of Perdition (E4M7) - Green key", player, 1)) # Shattered Bridge (E4M8) - set_rule(world.get_entrance("Hub -> Shattered Bridge (E4M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Shattered Bridge (E4M8) Main", player), lambda state: state.has("Shattered Bridge (E4M8)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -543,13 +545,13 @@ def set_episode4_rules(player, world, pro): state.has("Phoenix Rod", player, 1) and state.has("Firemace", player, 1) and state.has("Hellstaff", player, 1)) - set_rule(world.get_entrance("Shattered Bridge (E4M8) Main -> Shattered Bridge (E4M8) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Shattered Bridge (E4M8) Main -> Shattered Bridge (E4M8) Yellow", player), lambda state: state.has("Shattered Bridge (E4M8) - Yellow key", player, 1)) - set_rule(world.get_entrance("Shattered Bridge (E4M8) Yellow -> Shattered Bridge (E4M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Shattered Bridge (E4M8) Yellow -> Shattered Bridge (E4M8) Main", player), lambda state: state.has("Shattered Bridge (E4M8) - Yellow key", player, 1)) # Mausoleum (E4M9) - set_rule(world.get_entrance("Hub -> Mausoleum (E4M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Mausoleum (E4M9) Main", player), lambda state: (state.has("Mausoleum (E4M9)", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Ethereal Crossbow", player, 1) and @@ -557,102 +559,100 @@ def set_episode4_rules(player, world, pro): state.has("Firemace", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Mausoleum (E4M9) Main -> Mausoleum (E4M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Mausoleum (E4M9) Main -> Mausoleum (E4M9) Yellow", player), lambda state: state.has("Mausoleum (E4M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("Mausoleum (E4M9) Yellow -> Mausoleum (E4M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Mausoleum (E4M9) Yellow -> Mausoleum (E4M9) Main", player), lambda state: state.has("Mausoleum (E4M9) - Yellow key", player, 1)) -def set_episode5_rules(player, world, pro): +def set_episode5_rules(player, multiworld, pro): # Ochre Cliffs (E5M1) - set_rule(world.get_entrance("Hub -> Ochre Cliffs (E5M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Ochre Cliffs (E5M1) Main", player), lambda state: state.has("Ochre Cliffs (E5M1)", player, 1)) - set_rule(world.get_entrance("Ochre Cliffs (E5M1) Main -> Ochre Cliffs (E5M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Main -> Ochre Cliffs (E5M1) Yellow", player), lambda state: state.has("Ochre Cliffs (E5M1) - Yellow key", player, 1)) - set_rule(world.get_entrance("Ochre Cliffs (E5M1) Blue -> Ochre Cliffs (E5M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Blue -> Ochre Cliffs (E5M1) Yellow", player), lambda state: state.has("Ochre Cliffs (E5M1) - Blue key", player, 1)) - set_rule(world.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Main", player), lambda state: + set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Main", player), lambda state: state.has("Ochre Cliffs (E5M1) - Yellow key", player, 1)) - set_rule(world.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Green", player), lambda state: + set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Green", player), lambda state: state.has("Ochre Cliffs (E5M1) - Green key", player, 1)) - set_rule(world.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Blue", player), lambda state: state.has("Ochre Cliffs (E5M1) - Blue key", player, 1)) - set_rule(world.get_entrance("Ochre Cliffs (E5M1) Green -> Ochre Cliffs (E5M1) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Green -> Ochre Cliffs (E5M1) Yellow", player), lambda state: state.has("Ochre Cliffs (E5M1) - Green key", player, 1)) # Rapids (E5M2) - set_rule(world.get_entrance("Hub -> Rapids (E5M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Rapids (E5M2) Main", player), lambda state: state.has("Rapids (E5M2)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) - set_rule(world.get_entrance("Rapids (E5M2) Main -> Rapids (E5M2) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Rapids (E5M2) Main -> Rapids (E5M2) Yellow", player), lambda state: state.has("Rapids (E5M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Main", player), lambda state: + set_rule(multiworld.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Main", player), lambda state: state.has("Rapids (E5M2) - Yellow key", player, 1)) - set_rule(world.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Green", player), lambda state: + set_rule(multiworld.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Green", player), lambda state: state.has("Rapids (E5M2) - Green key", player, 1)) # Quay (E5M3) - set_rule(world.get_entrance("Hub -> Quay (E5M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Quay (E5M3) Main", player), lambda state: (state.has("Quay (E5M3)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1) or state.has("Firemace", player, 1))) - set_rule(world.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Yellow", player), lambda state: state.has("Quay (E5M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Green", player), lambda state: state.has("Quay (E5M3) - Green key", player, 1)) - set_rule(world.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Blue", player), lambda state: - state.has("Quay (E5M3) - Blue key", player, 1)) - set_rule(world.get_entrance("Quay (E5M3) Blue -> Quay (E5M3) Green", player), lambda state: + set_rule(multiworld.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Blue", player), lambda state: state.has("Quay (E5M3) - Blue key", player, 1)) - set_rule(world.get_entrance("Quay (E5M3) Yellow -> Quay (E5M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Quay (E5M3) Yellow -> Quay (E5M3) Main", player), lambda state: state.has("Quay (E5M3) - Yellow key", player, 1)) - set_rule(world.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Main", player), lambda state: + set_rule(multiworld.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Main", player), lambda state: state.has("Quay (E5M3) - Green key", player, 1)) - set_rule(world.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Cyan", player), lambda state: state.has("Quay (E5M3) - Blue key", player, 1)) # Courtyard (E5M4) - set_rule(world.get_entrance("Hub -> Courtyard (E5M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Courtyard (E5M4) Main", player), lambda state: (state.has("Courtyard (E5M4)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Firemace", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Kakis", player), lambda state: + set_rule(multiworld.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Kakis", player), lambda state: state.has("Courtyard (E5M4) - Yellow key", player, 1) or state.has("Courtyard (E5M4) - Green key", player, 1)) - set_rule(world.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Blue", player), lambda state: state.has("Courtyard (E5M4) - Blue key", player, 1)) - set_rule(world.get_entrance("Courtyard (E5M4) Blue -> Courtyard (E5M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Courtyard (E5M4) Blue -> Courtyard (E5M4) Main", player), lambda state: state.has("Courtyard (E5M4) - Blue key", player, 1)) - set_rule(world.get_entrance("Courtyard (E5M4) Kakis -> Courtyard (E5M4) Main", player), lambda state: + set_rule(multiworld.get_entrance("Courtyard (E5M4) Kakis -> Courtyard (E5M4) Main", player), lambda state: state.has("Courtyard (E5M4) - Yellow key", player, 1) or state.has("Courtyard (E5M4) - Green key", player, 1)) # Hydratyr (E5M5) - set_rule(world.get_entrance("Hub -> Hydratyr (E5M5) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Hydratyr (E5M5) Main", player), lambda state: (state.has("Hydratyr (E5M5)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and state.has("Firemace", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Hydratyr (E5M5) Main -> Hydratyr (E5M5) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Hydratyr (E5M5) Main -> Hydratyr (E5M5) Yellow", player), lambda state: state.has("Hydratyr (E5M5) - Yellow key", player, 1)) - set_rule(world.get_entrance("Hydratyr (E5M5) Blue -> Hydratyr (E5M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("Hydratyr (E5M5) Blue -> Hydratyr (E5M5) Green", player), lambda state: state.has("Hydratyr (E5M5) - Blue key", player, 1)) - set_rule(world.get_entrance("Hydratyr (E5M5) Yellow -> Hydratyr (E5M5) Green", player), lambda state: + set_rule(multiworld.get_entrance("Hydratyr (E5M5) Yellow -> Hydratyr (E5M5) Green", player), lambda state: state.has("Hydratyr (E5M5) - Green key", player, 1)) - set_rule(world.get_entrance("Hydratyr (E5M5) Green -> Hydratyr (E5M5) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Hydratyr (E5M5) Green -> Hydratyr (E5M5) Blue", player), lambda state: state.has("Hydratyr (E5M5) - Blue key", player, 1)) # Colonnade (E5M6) - set_rule(world.get_entrance("Hub -> Colonnade (E5M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Colonnade (E5M6) Main", player), lambda state: (state.has("Colonnade (E5M6)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -660,19 +660,19 @@ def set_episode5_rules(player, world, pro): state.has("Gauntlets of the Necromancer", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Yellow", player), lambda state: state.has("Colonnade (E5M6) - Yellow key", player, 1)) - set_rule(world.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Blue", player), lambda state: state.has("Colonnade (E5M6) - Blue key", player, 1)) - set_rule(world.get_entrance("Colonnade (E5M6) Blue -> Colonnade (E5M6) Main", player), lambda state: + set_rule(multiworld.get_entrance("Colonnade (E5M6) Blue -> Colonnade (E5M6) Main", player), lambda state: state.has("Colonnade (E5M6) - Blue key", player, 1)) - set_rule(world.get_entrance("Colonnade (E5M6) Yellow -> Colonnade (E5M6) Green", player), lambda state: + set_rule(multiworld.get_entrance("Colonnade (E5M6) Yellow -> Colonnade (E5M6) Green", player), lambda state: state.has("Colonnade (E5M6) - Green key", player, 1)) - set_rule(world.get_entrance("Colonnade (E5M6) Green -> Colonnade (E5M6) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Colonnade (E5M6) Green -> Colonnade (E5M6) Yellow", player), lambda state: state.has("Colonnade (E5M6) - Green key", player, 1)) # Foetid Manse (E5M7) - set_rule(world.get_entrance("Hub -> Foetid Manse (E5M7) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Foetid Manse (E5M7) Main", player), lambda state: (state.has("Foetid Manse (E5M7)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -680,15 +680,15 @@ def set_episode5_rules(player, world, pro): state.has("Gauntlets of the Necromancer", player, 1)) and (state.has("Phoenix Rod", player, 1) or state.has("Hellstaff", player, 1))) - set_rule(world.get_entrance("Foetid Manse (E5M7) Main -> Foetid Manse (E5M7) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Foetid Manse (E5M7) Main -> Foetid Manse (E5M7) Yellow", player), lambda state: state.has("Foetid Manse (E5M7) - Yellow key", player, 1)) - set_rule(world.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Green", player), lambda state: + set_rule(multiworld.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Green", player), lambda state: state.has("Foetid Manse (E5M7) - Green key", player, 1)) - set_rule(world.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Blue", player), lambda state: state.has("Foetid Manse (E5M7) - Blue key", player, 1)) # Field of Judgement (E5M8) - set_rule(world.get_entrance("Hub -> Field of Judgement (E5M8) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Field of Judgement (E5M8) Main", player), lambda state: state.has("Field of Judgement (E5M8)", player, 1) and state.has("Ethereal Crossbow", player, 1) and state.has("Dragon Claw", player, 1) and @@ -699,7 +699,7 @@ def set_episode5_rules(player, world, pro): state.has("Bag of Holding", player, 1)) # Skein of D'Sparil (E5M9) - set_rule(world.get_entrance("Hub -> Skein of D'Sparil (E5M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Hub -> Skein of D'Sparil (E5M9) Main", player), lambda state: state.has("Skein of D'Sparil (E5M9)", player, 1) and state.has("Bag of Holding", player, 1) and state.has("Hellstaff", player, 1) and @@ -708,29 +708,29 @@ def set_episode5_rules(player, world, pro): state.has("Ethereal Crossbow", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and state.has("Firemace", player, 1)) - set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Blue", player), lambda state: + set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Blue", player), lambda state: state.has("Skein of D'Sparil (E5M9) - Blue key", player, 1)) - set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Yellow", player), lambda state: + set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Yellow", player), lambda state: state.has("Skein of D'Sparil (E5M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Green", player), lambda state: + set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Green", player), lambda state: state.has("Skein of D'Sparil (E5M9) - Green key", player, 1)) - set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Yellow -> Skein of D'Sparil (E5M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Yellow -> Skein of D'Sparil (E5M9) Main", player), lambda state: state.has("Skein of D'Sparil (E5M9) - Yellow key", player, 1)) - set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Green -> Skein of D'Sparil (E5M9) Main", player), lambda state: + set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Green -> Skein of D'Sparil (E5M9) Main", player), lambda state: state.has("Skein of D'Sparil (E5M9) - Green key", player, 1)) def set_rules(heretic_world: "HereticWorld", included_episodes, pro): player = heretic_world.player - world = heretic_world.multiworld + multiworld = heretic_world.multiworld if included_episodes[0]: - set_episode1_rules(player, world, pro) + set_episode1_rules(player, multiworld, pro) if included_episodes[1]: - set_episode2_rules(player, world, pro) + set_episode2_rules(player, multiworld, pro) if included_episodes[2]: - set_episode3_rules(player, world, pro) + set_episode3_rules(player, multiworld, pro) if included_episodes[3]: - set_episode4_rules(player, world, pro) + set_episode4_rules(player, multiworld, pro) if included_episodes[4]: - set_episode5_rules(player, world, pro) + set_episode5_rules(player, multiworld, pro) diff --git a/worlds/heretic/__init__.py b/worlds/heretic/__init__.py index b0b2bfce8f26..bc0a54698a59 100644 --- a/worlds/heretic/__init__.py +++ b/worlds/heretic/__init__.py @@ -2,9 +2,10 @@ import logging from typing import Any, Dict, List, Set -from BaseClasses import Entrance, CollectionState, Item, ItemClassification, Location, MultiWorld, Region, Tutorial +from BaseClasses import Entrance, CollectionState, Item, Location, MultiWorld, Region, Tutorial from worlds.AutoWorld import WebWorld, World -from . import Items, Locations, Maps, Options, Regions, Rules +from . import Items, Locations, Maps, Regions, Rules +from .Options import HereticOptions logger = logging.getLogger("Heretic") @@ -36,10 +37,10 @@ class HereticWorld(World): """ Heretic is a dark fantasy first-person shooter video game released in December 1994. It was developed by Raven Software. """ - option_definitions = Options.options + options_dataclass = HereticOptions + options: HereticOptions game = "Heretic" web = HereticWeb() - data_version = 3 required_client_version = (0, 3, 9) item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()} @@ -56,7 +57,7 @@ class HereticWorld(World): "Ochre Cliffs (E5M1)" ] - boss_level_for_espidoes: List[str] = [ + boss_level_for_episode: List[str] = [ "Hell's Maw (E1M8)", "The Portals of Chaos (E2M8)", "D'Sparil'S Keep (E3M8)", @@ -70,6 +71,7 @@ class HereticWorld(World): "Tome of Power": 16, "Silver Shield": 10, "Enchanted Shield": 5, + "Torch": 5, "Morph Ovum": 3, "Mystic Urn": 2, "Chaos Device": 1, @@ -77,27 +79,30 @@ class HereticWorld(World): "Shadowsphere": 1 } - def __init__(self, world: MultiWorld, player: int): + def __init__(self, multiworld: MultiWorld, player: int): self.included_episodes = [1, 1, 1, 0, 0] self.location_count = 0 - super().__init__(world, player) + super().__init__(multiworld, player) def get_episode_count(self): return functools.reduce(lambda count, episode: count + episode, self.included_episodes) def generate_early(self): # Cache which episodes are included - for i in range(5): - self.included_episodes[i] = getattr(self.multiworld, f"episode{i + 1}")[self.player].value + self.included_episodes[0] = self.options.episode1.value + self.included_episodes[1] = self.options.episode2.value + self.included_episodes[2] = self.options.episode3.value + self.included_episodes[3] = self.options.episode4.value + self.included_episodes[4] = self.options.episode5.value # If no episodes selected, select Episode 1 if self.get_episode_count() == 0: self.included_episodes[0] = 1 def create_regions(self): - pro = getattr(self.multiworld, "pro")[self.player].value - check_sanity = getattr(self.multiworld, "check_sanity")[self.player].value + pro = self.options.pro.value + check_sanity = self.options.check_sanity.value # Main regions menu_region = Region("Menu", self.player, self.multiworld) @@ -148,8 +153,8 @@ def create_regions(self): def completion_rule(self, state: CollectionState): goal_levels = Maps.map_names - if getattr(self.multiworld, "goal")[self.player].value: - goal_levels = self.boss_level_for_espidoes + if self.options.goal.value: + goal_levels = self.boss_level_for_episode for map_name in goal_levels: if map_name + " - Exit" not in self.location_name_to_id: @@ -167,8 +172,8 @@ def completion_rule(self, state: CollectionState): return True def set_rules(self): - pro = getattr(self.multiworld, "pro")[self.player].value - allow_death_logic = getattr(self.multiworld, "allow_death_logic")[self.player].value + pro = self.options.pro.value + allow_death_logic = self.options.allow_death_logic.value Rules.set_rules(self, self.included_episodes, pro) self.multiworld.completion_condition[self.player] = lambda state: self.completion_rule(state) @@ -177,7 +182,7 @@ def set_rules(self): # platform) Unless the user allows for it. if not allow_death_logic: for death_logic_location in Locations.death_logic_locations: - self.multiworld.exclude_locations[self.player].value.add(death_logic_location) + self.options.exclude_locations.value.add(death_logic_location) def create_item(self, name: str) -> HereticItem: item_id: int = self.item_name_to_id[name] @@ -185,7 +190,7 @@ def create_item(self, name: str) -> HereticItem: def create_items(self): itempool: List[HereticItem] = [] - start_with_map_scrolls: bool = getattr(self.multiworld, "start_with_map_scrolls")[self.player].value + start_with_map_scrolls: bool = self.options.start_with_map_scrolls.value # Items for item_id, item in Items.item_table.items(): @@ -225,7 +230,7 @@ def create_items(self): self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i])) # Give Computer area maps if option selected - if getattr(self.multiworld, "start_with_map_scrolls")[self.player].value: + if self.options.start_with_map_scrolls.value: for item_id, item_dict in Items.item_table.items(): item_episode = item_dict["episode"] if item_episode > 0: @@ -238,6 +243,7 @@ def create_items(self): self.create_ratioed_items("Mystic Urn", itempool) self.create_ratioed_items("Ring of Invincibility", itempool) self.create_ratioed_items("Shadowsphere", itempool) + self.create_ratioed_items("Torch", itempool) self.create_ratioed_items("Timebomb of the Ancients", itempool) self.create_ratioed_items("Tome of Power", itempool) self.create_ratioed_items("Silver Shield", itempool) @@ -275,7 +281,7 @@ def create_ratioed_items(self, item_name: str, itempool: List[HereticItem]): itempool.append(self.create_item(item_name)) def fill_slot_data(self) -> Dict[str, Any]: - slot_data = self.options.as_dict("difficulty", "random_monsters", "random_pickups", "random_music", "allow_death_logic", "pro", "death_link", "reset_level_on_death", "check_sanity") + slot_data = self.options.as_dict("goal", "difficulty", "random_monsters", "random_pickups", "random_music", "allow_death_logic", "pro", "death_link", "reset_level_on_death", "check_sanity") # Make sure we send proper episode settings slot_data["episode1"] = self.included_episodes[0] diff --git a/worlds/heretic/docs/en_Heretic.md b/worlds/heretic/docs/en_Heretic.md index 97d371de2c2f..a7ae3d1ea748 100644 --- a/worlds/heretic/docs/en_Heretic.md +++ b/worlds/heretic/docs/en_Heretic.md @@ -1,8 +1,8 @@ # Heretic -## Where is the settings page? +## Where is the options page? -The [player settings page](../player-settings) contains the options needed to configure your game session. +The [player options page](../player-options) contains the options needed to configure your game session. ## What does randomization do to this game? diff --git a/worlds/heretic/docs/setup_en.md b/worlds/heretic/docs/setup_en.md index e01d616e8ff1..41b7fdab8078 100644 --- a/worlds/heretic/docs/setup_en.md +++ b/worlds/heretic/docs/setup_en.md @@ -13,7 +13,7 @@ 1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it. 2. Copy HERETIC.WAD from your steam install into the extracted folder. You can find the folder in steam by finding the game in your library, - right clicking it and choosing *Manage→Browse Local Files*. + right clicking it and choosing *Manage→Browse Local Files*. The WAD file is in the `/base/` folder. ## Joining a MultiWorld Game diff --git a/worlds/hk/GodhomeData.py b/worlds/hk/GodhomeData.py new file mode 100644 index 000000000000..a2dd69ed73ef --- /dev/null +++ b/worlds/hk/GodhomeData.py @@ -0,0 +1,55 @@ +from functools import partial + + +godhome_event_names = ["Godhome_Flower_Quest", "Defeated_Pantheon_5", "GG_Atrium_Roof", "Defeated_Pantheon_1", "Defeated_Pantheon_2", "Defeated_Pantheon_3", "Opened_Pantheon_4", "Defeated_Pantheon_4", "GG_Atrium", "Hit_Pantheon_5_Unlock_Orb", "GG_Workshop", "Can_Damage_Crystal_Guardian", 'Defeated_Any_Soul_Warrior', "Defeated_Colosseum_3", "COMBAT[Radiance]", "COMBAT[Pantheon_1]", "COMBAT[Pantheon_2]", "COMBAT[Pantheon_3]", "COMBAT[Pantheon_4]", "COMBAT[Pantheon_5]", "COMBAT[Colosseum_3]", 'Warp-Junk_Pit_to_Godhome', 'Bench-Godhome_Atrium', 'Bench-Hall_of_Gods', "GODTUNERUNLOCK", "GG_Waterways", "Warp-Godhome_to_Junk_Pit", "NAILCOMBAT", "BOSS", "AERIALMINIBOSS"] + + +def set_godhome_rules(hk_world, hk_set_rule): + player = hk_world.player + fn = partial(hk_set_rule, hk_world) + + required_events = { + "Godhome_Flower_Quest": lambda state: state.count('Defeated_Pantheon_5', player) and state.count('Room_Mansion[left1]', player) and state.count('Fungus3_49[right1]', player) and state.has('Godtuner', player), + + "Defeated_Pantheon_5": lambda state: state.has('GG_Atrium_Roof', player) and state.has('WINGS', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) and ((state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player) and state.has('Defeated_Pantheon_4', player) and state.has('COMBAT[Radiance]', player))), + "GG_Atrium_Roof": lambda state: state.has('GG_Atrium', player) and state.has('Hit_Pantheon_5_Unlock_Orb', player) and state.has('LEFTCLAW', player), + + "Defeated_Pantheon_1": lambda state: state.has('GG_Atrium', player) and ((state.has('Defeated_Gruz_Mother', player) and state.has('Defeated_False_Knight', player) and (state.has('Fungus1_29[left1]', player) or state.has('Fungus1_29[right1]', player)) and state.has('Defeated_Hornet_1', player) and state.has('Defeated_Gorb', player) and state.has('Defeated_Dung_Defender', player) and state.has('Defeated_Any_Soul_Warrior', player) and state.has('Defeated_Brooding_Mawlek', player))), + "Defeated_Pantheon_2": lambda state: state.has('GG_Atrium', player) and ((state.has('Defeated_Xero', player) and state.has('Defeated_Crystal_Guardian', player) and state.has('Defeated_Soul_Master', player) and state.has('Defeated_Colosseum_2', player) and state.has('Defeated_Mantis_Lords', player) and state.has('Defeated_Marmu', player) and state.has('Defeated_Nosk', player) and state.has('Defeated_Flukemarm', player) and state.has('Defeated_Broken_Vessel', player))), + "Defeated_Pantheon_3": lambda state: state.has('GG_Atrium', player) and ((state.has('Defeated_Hive_Knight', player) and state.has('Defeated_Elder_Hu', player) and state.has('Defeated_Collector', player) and state.has('Defeated_Colosseum_2', player) and state.has('Defeated_Grimm', player) and state.has('Defeated_Galien', player) and state.has('Defeated_Uumuu', player) and state.has('Defeated_Hornet_2', player))), + "Opened_Pantheon_4": lambda state: state.has('GG_Atrium', player) and (state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player)), + "Defeated_Pantheon_4": lambda state: state.has('GG_Atrium', player) and state.has('Opened_Pantheon_4', player) and ((state.has('Defeated_Enraged_Guardian', player) and state.has('Defeated_Broken_Vessel', player) and state.has('Defeated_No_Eyes', player) and state.has('Defeated_Traitor_Lord', player) and state.has('Defeated_Dung_Defender', player) and state.has('Defeated_False_Knight', player) and state.has('Defeated_Markoth', player) and state.has('Defeated_Watcher_Knights', player) and state.has('Defeated_Soul_Master', player))), + "GG_Atrium": lambda state: state.has('Warp-Junk_Pit_to_Godhome', player) and (state.has('RIGHTCLAW', player) or state.has('WINGS', player) or state.has('LEFTCLAW', player) and state.has('RIGHTSUPERDASH', player)) or state.has('GG_Workshop', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player) and state.has('WINGS', player)) or state.has('Bench-Godhome_Atrium', player), + "Hit_Pantheon_5_Unlock_Orb": lambda state: state.has('GG_Atrium', player) and state.has('WINGS', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) and (((state.has('Queen_Fragment', player) and state.has('King_Fragment', player) and state.has('Void_Heart', player)) and state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player) and state.has('Defeated_Pantheon_4', player))), + "GG_Workshop": lambda state: state.has('GG_Atrium', player) or state.has('Bench-Hall_of_Gods', player), + "Can_Damage_Crystal_Guardian": lambda state: state.has('UPSLASH', player) or state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('CYCLONE', player) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')) and (state.has('CYCLONE', player) or state.has('Great_Slash', player)) and (state.has('DREAMNAIL', player) and (state.has('SPELLS', player) or state.has('FOCUS', player) and state.has('Spore_Shroom', player) or state.has('Glowing_Womb', player)) or state.has('Weaversong', player)), + 'Defeated_Any_Soul_Warrior': lambda state: state.has('Defeated_Sanctum_Warrior', player) or state.has('Defeated_Elegant_Warrior', player) or state.has('Room_Colosseum_01[left1]', player) and state.has('Defeated_Colosseum_3', player), + "Defeated_Colosseum_3": lambda state: state.has('Room_Colosseum_01[left1]', player) and state.has('Can_Replenish_Geo', player) and ((state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) or ((state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')) and state.has('WINGS', player))) and state.has('COMBAT[Colosseum_3]', player), + + # MACROS + "COMBAT[Radiance]": lambda state: (state.has('LEFTDASH', player) and state.has('RIGHTDASH', player)) and ((((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('LEFTDASH', player)) and ((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('RIGHTDASH', player))) or state.has('QUAKE', player)) and (state.count('FIREBALL', player) > 1 and state.has('UPSLASH', player) or state.count('SCREAM', player) > 1 and state.has('UPSLASH', player) or state._hk_option(player, 'RemoveSpellUpgrades') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('UPSLASH', player) or (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_1]": lambda state: state.has('AERIALMINIBOSS', player) and state.count('SPELLS', player) > 1 and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_2]": lambda state: state.has('AERIALMINIBOSS', player) and state.count('SPELLS', player) > 1 and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))) and state.has('Can_Damage_Crystal_Guardian', player), + "COMBAT[Pantheon_3]": lambda state: state.has('AERIALMINIBOSS', player) and state.count('SPELLS', player) > 1 and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_4]": lambda state: state.has('AERIALMINIBOSS', player) and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))) and state.has('Can_Damage_Crystal_Guardian', player) and (state.has('LEFTDASH', player) and state.has('RIGHTDASH', player)) and ((((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('LEFTDASH', player)) and ((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('RIGHTDASH', player))) or state.has('QUAKE', player)) and (state.count('FIREBALL', player) > 1 and state.has('UPSLASH', player) or state.count('SCREAM', player) > 1 and state.has('UPSLASH', player) or state._hk_option(player, 'RemoveSpellUpgrades') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('UPSLASH', player) or (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_5]": lambda state: state.has('AERIALMINIBOSS', player) and state.has('FOCUS', player) and state.has('Can_Damage_Crystal_Guardian', player) and (state.has('LEFTDASH', player) and state.has('RIGHTDASH', player)) and ((((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('LEFTDASH', player)) and ((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('RIGHTDASH', player))) or state.has('QUAKE', player)) and (state.count('FIREBALL', player) > 1 and state.has('UPSLASH', player) or state.count('SCREAM', player) > 1 and state.has('UPSLASH', player) or state._hk_option(player, 'RemoveSpellUpgrades') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('UPSLASH', player) or (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Colosseum_3]": lambda state: state.has('BOSS', player) and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + + # MISC + 'Warp-Junk_Pit_to_Godhome': lambda state: state.has('GG_Waterways', player) and state.has('GODTUNERUNLOCK', player) and state.has('DREAMNAIL', player), + 'Bench-Godhome_Atrium': lambda state: state.has('GG_Atrium', player) and (state.has('RIGHTCLAW', player) and (state.has('RIGHTDASH', player) or state.has('LEFTCLAW', player) and state.has('RIGHTSUPERDASH', player) or state.has('WINGS', player)) or state.has('LEFTCLAW', player) and state.has('WINGS', player)), + 'Bench-Hall_of_Gods': lambda state: state.has('GG_Workshop', player) and ((state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player))), + + "GODTUNERUNLOCK": lambda state: state.count('SIMPLE', player) > 3, + "GG_Waterways": lambda state: state.has('GG_Waterways[door1]', player) or state.has('GG_Waterways[right1]', player) and (state.has('LEFTSUPERDASH', player) or state.has('SWIM', player)) or state.has('Warp-Godhome_to_Junk_Pit', player), + "Warp-Godhome_to_Junk_Pit": lambda state: state.has('Warp-Junk_Pit_to_Godhome', player) or state.has('GG_Atrium', player), + + # COMBAT MACROS + "NAILCOMBAT": lambda state: (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('CYCLONE', player) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')), + "BOSS": lambda state: state.count('SPELLS', player) > 1 and ((state.has('LEFTDASH', player) or state.has('RIGHTDASH', player)) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player)) or state._hk_option(player, 'ProficientCombat') and state.has('NAILCOMBAT', player)), + "AERIALMINIBOSS": lambda state: (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('LEFTDASH', player) or state.has('RIGHTDASH', player)) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player)) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player) or state.has('CYCLONE', player) or state.has('Great_Slash', player)), + + } + + for item, rule in required_events.items(): + fn(item, rule) diff --git a/worlds/hk/Items.py b/worlds/hk/Items.py index def5c3298102..8515465826a5 100644 --- a/worlds/hk/Items.py +++ b/worlds/hk/Items.py @@ -1,5 +1,6 @@ from typing import Dict, Set, NamedTuple from .ExtractedData import items, logic_items, item_effects +from .GodhomeData import godhome_event_names item_table = {} @@ -14,6 +15,9 @@ class HKItemData(NamedTuple): item_table[item_name] = HKItemData(advancement=item_name in logic_items or item_name in item_effects, id=i, type=item_type) +for item_name in godhome_event_names: + item_table[item_name] = HKItemData(advancement=True, id=None, type=None) + lookup_id_to_name: Dict[int, str] = {data.id: item_name for item_name, data in item_table.items()} lookup_type_to_names: Dict[str, Set[str]] = {} for item, item_data in item_table.items(): @@ -35,6 +39,7 @@ class HKItemData(NamedTuple): "GeoChests": lookup_type_to_names["Geo"], "GeoRocks": lookup_type_to_names["Rock"], "GrimmkinFlames": lookup_type_to_names["Flame"], + "Grimmchild": {"Grimmchild1", "Grimmchild2"}, "Grubs": lookup_type_to_names["Grub"], "JournalEntries": lookup_type_to_names["Journal"], "JunkPitChests": lookup_type_to_names["JunkPitChest"], @@ -59,3 +64,4 @@ class HKItemData(NamedTuple): }) item_name_groups['Horizontal'] = item_name_groups['Cloak'] | item_name_groups['CDash'] item_name_groups['Vertical'] = item_name_groups['Claw'] | {'Monarch_Wings'} +item_name_groups['Skills'] |= item_name_groups['Vertical'] | item_name_groups['Horizontal'] diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index ef7fbd0dfef6..e2602036a24e 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -1,8 +1,12 @@ import typing +import re +from dataclasses import dataclass, make_dataclass + from .ExtractedData import logic_options, starts, pool_options from .Rules import cost_terms +from schema import And, Schema, Optional -from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink +from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink, PerGameCommonOptions from .Charms import vanilla_costs, names as charm_names if typing.TYPE_CHECKING: @@ -11,12 +15,16 @@ else: Random = typing.Any - locations = {"option_" + start: i for i, start in enumerate(starts)} # This way the dynamic start names are picked up by the MetaClass Choice belongs to -StartLocation = type("StartLocation", (Choice,), {"__module__": __name__, "auto_display_name": False, **locations, - "__doc__": "Choose your start location. " - "This is currently only locked to King's Pass."}) +StartLocation = type("StartLocation", (Choice,), { + "__module__": __name__, + "auto_display_name": False, + "display_name": "Start Location", + "__doc__": "Choose your start location. " + "This is currently only locked to King's Pass.", + **locations, +}) del (locations) option_docstrings = { @@ -49,8 +57,7 @@ "RandomizeBossEssence": "Randomize boss essence drops, such as those for defeating Warrior Dreams, into the item " "pool and open their locations\n for randomization.", "RandomizeGrubs": "Randomize Grubs into the item pool and open their locations for randomization.", - "RandomizeMimics": "Randomize Mimic Grubs into the item pool and open their locations for randomization." - "Mimic Grubs are always placed\n in your own game.", + "RandomizeMimics": "Randomize Mimic Grubs into the item pool and open their locations for randomization.", "RandomizeMaps": "Randomize Maps into the item pool. This causes Cornifer to give you a message allowing you to see" " and buy an item\n that is randomized into that location as well.", "RandomizeStags": "Randomize Stag Stations unlocks into the item pool as well as placing randomized items " @@ -99,8 +106,12 @@ "RandomizeKeys", "RandomizeMaskShards", "RandomizeVesselFragments", + "RandomizeCharmNotches", "RandomizePaleOre", - "RandomizeRelics" + "RandomizeRancidEggs", + "RandomizeRelics", + "RandomizeStags", + "RandomizeLifebloodCocoons" } shop_to_option = { @@ -117,6 +128,7 @@ hollow_knight_randomize_options: typing.Dict[str, type(Option)] = {} +splitter_pattern = re.compile(r'(? typing.List[int]: random_source.shuffle(charms) return charms else: - charms = [0]*self.charm_count + charms = [0] * self.charm_count for x in range(self.value): - index = random_source.randint(0, self.charm_count-1) + index = random_source.randint(0, self.charm_count - 1) while charms[index] > 5: - index = random_source.randint(0, self.charm_count-1) + index = random_source.randint(0, self.charm_count - 1) charms[index] += 1 return charms @@ -283,6 +299,9 @@ class PlandoCharmCosts(OptionDict): This is set after any random Charm Notch costs, if applicable.""" display_name = "Charm Notch Cost Plando" valid_keys = frozenset(charm_names) + schema = Schema({ + Optional(name): And(int, lambda n: 6 >= n >= 0) for name in charm_names + }) def get_costs(self, charm_costs: typing.List[int]) -> typing.List[int]: for name, cost in self.value.items(): @@ -384,8 +403,8 @@ class Goal(Choice): option_hollowknight = 1 option_siblings = 2 option_radiance = 3 - # Client support exists for this, but logic is a nightmare - # option_godhome = 4 + option_godhome = 4 + option_godhome_flower = 5 default = 0 @@ -404,6 +423,17 @@ class WhitePalace(Choice): class ExtraPlatforms(DefaultOnToggle): """Places additional platforms to make traveling throughout Hallownest more convenient.""" + display_name = "Extra Platforms" + + +class AddUnshuffledLocations(Toggle): + """Adds non-randomized locations to the location pool, which allows syncing + of location state with co-op or automatic collection via collect. + + Note: This will increase the number of location checks required to purchase + hints to the total maximum. + """ + display_name = "Add Unshuffled Locations" class DeathLinkShade(Choice): @@ -421,6 +451,7 @@ class DeathLinkShade(Choice): option_shadeless = 1 option_shade = 2 default = 2 + display_name = "Deathlink Shade Handling" class DeathLinkBreaksFragileCharms(Toggle): @@ -430,6 +461,7 @@ class DeathLinkBreaksFragileCharms(Toggle): ** Self-death fragile charm behavior is not changed; if a self-death normally breaks fragile charms in vanilla, it will continue to do so. """ + display_name = "Deathlink Breaks Fragile Charms" class StartingGeo(Range): @@ -453,18 +485,20 @@ class CostSanity(Choice): alias_yes = 1 option_shopsonly = 2 option_notshops = 3 - display_name = "Cost Sanity" + display_name = "Costsanity" class CostSanityHybridChance(Range): """The chance that a CostSanity cost will include two components instead of one, e.g. Grubs + Essence""" range_end = 100 default = 10 + display_name = "Costsanity Hybrid Chance" cost_sanity_weights: typing.Dict[str, type(Option)] = {} for term, cost in cost_terms.items(): option_name = f"CostSanity{cost.option}Weight" + display_name = f"Costsanity {cost.option} Weight" extra_data = { "__module__": __name__, "range_end": 1000, "__doc__": ( @@ -477,10 +511,10 @@ class CostSanityHybridChance(Range): extra_data["__doc__"] += " Geo costs will never be chosen for Grubfather, Seer, or Egg Shop." option = type(option_name, (Range,), extra_data) + option.display_name = display_name globals()[option.__name__] = option cost_sanity_weights[option.__name__] = option - hollow_knight_options: typing.Dict[str, type(Option)] = { **hollow_knight_randomize_options, RandomizeElevatorPass.__name__: RandomizeElevatorPass, @@ -488,7 +522,7 @@ class CostSanityHybridChance(Range): **{ option.__name__: option for option in ( - StartLocation, Goal, WhitePalace, ExtraPlatforms, StartingGeo, + StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo, DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms, MinimumGeoPrice, MaximumGeoPrice, MinimumGrubPrice, MaximumGrubPrice, @@ -506,3 +540,5 @@ class CostSanityHybridChance(Range): }, **cost_sanity_weights } + +HKOptions = make_dataclass("HKOptions", [(name, option) for name, option in hollow_knight_options.items()], bases=(PerGameCommonOptions,)) diff --git a/worlds/hk/Rules.py b/worlds/hk/Rules.py index 2dc512eca76e..e162e1dfa81c 100644 --- a/worlds/hk/Rules.py +++ b/worlds/hk/Rules.py @@ -1,6 +1,7 @@ from ..generic.Rules import set_rule, add_rule from ..AutoWorld import World from .GeneratedRules import set_generated_rules +from .GodhomeData import set_godhome_rules from typing import NamedTuple @@ -39,6 +40,7 @@ def hk_set_rule(hk_world: World, location: str, rule): def set_rules(hk_world: World): player = hk_world.player set_generated_rules(hk_world, hk_set_rule) + set_godhome_rules(hk_world, hk_set_rule) # Shop costs for location in hk_world.multiworld.get_locations(player): @@ -47,3 +49,42 @@ def set_rules(hk_world: World): if term == "GEO": # No geo logic! continue add_rule(location, lambda state, term=term, amount=amount: state.count(term, player) >= amount) + + +def _hk_nail_combat(state, player) -> bool: + return state.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player) + + +def _hk_can_beat_thk(state, player) -> bool: + return ( + state.has('Opened_Black_Egg_Temple', player) + and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1 + and _hk_nail_combat(state, player) + and ( + state.has_any({'LEFTDASH', 'RIGHTDASH'}, player) + or state._hk_option(player, 'ProficientCombat') + ) + and state.has('FOCUS', player) + ) + + +def _hk_siblings_ending(state, player) -> bool: + return _hk_can_beat_thk(state, player) and state.has('WHITEFRAGMENT', player, 3) + + +def _hk_can_beat_radiance(state, player) -> bool: + return ( + state.has('Opened_Black_Egg_Temple', player) + and _hk_nail_combat(state, player) + and state.has('WHITEFRAGMENT', player, 3) + and state.has('DREAMNAIL', player) + and ( + (state.has('LEFTCLAW', player) and state.has('RIGHTCLAW', player)) + or state.has('WINGS', player) + ) + and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1 + and ( + (state.has('LEFTDASH', player, 2) and state.has('RIGHTDASH', player, 2)) # Both Shade Cloaks + or (state._hk_option(player, 'ProficientCombat') and state.has('QUAKE', player)) # or Dive + ) + ) diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 8b07b34eb0f2..e5065876ddf3 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -10,9 +10,9 @@ from .Items import item_table, lookup_type_to_names, item_name_groups from .Regions import create_regions -from .Rules import set_rules, cost_terms +from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \ - shop_to_option + shop_to_option, HKOptions from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \ event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs from .Charms import names as charm_names @@ -142,7 +142,8 @@ class HKWorld(World): As the enigmatic Knight, you’ll traverse the depths, unravel its mysteries and conquer its evils. """ # from https://www.hollowknight.com game: str = "Hollow Knight" - option_definitions = hollow_knight_options + options_dataclass = HKOptions + options: HKOptions web = HKWeb() @@ -154,10 +155,9 @@ class HKWorld(World): ranges: typing.Dict[str, typing.Tuple[int, int]] charm_costs: typing.List[int] cached_filler_items = {} - data_version = 2 - def __init__(self, world, player): - super(HKWorld, self).__init__(world, player) + def __init__(self, multiworld, player): + super(HKWorld, self).__init__(multiworld, player) self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = { location: list() for location in multi_locations } @@ -166,29 +166,29 @@ def __init__(self, world, player): self.vanilla_shop_costs = deepcopy(vanilla_shop_costs) def generate_early(self): - world = self.multiworld - charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random) - self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs) - # world.exclude_locations[self.player].value.update(white_palace_locations) + options = self.options + charm_costs = options.RandomCharmCosts.get_costs(self.random) + self.charm_costs = options.PlandoCharmCosts.get_costs(charm_costs) + # options.exclude_locations.value.update(white_palace_locations) for term, data in cost_terms.items(): - mini = getattr(world, f"Minimum{data.option}Price")[self.player] - maxi = getattr(world, f"Maximum{data.option}Price")[self.player] + mini = getattr(options, f"Minimum{data.option}Price") + maxi = getattr(options, f"Maximum{data.option}Price") # if minimum > maximum, set minimum to maximum mini.value = min(mini.value, maxi.value) self.ranges[term] = mini.value, maxi.value - world.push_precollected(HKItem(starts[world.StartLocation[self.player].current_key], + self.multiworld.push_precollected(HKItem(starts[options.StartLocation.current_key], True, None, "Event", self.player)) def white_palace_exclusions(self): exclusions = set() - wp = self.multiworld.WhitePalace[self.player] + wp = self.options.WhitePalace if wp <= WhitePalace.option_nopathofpain: exclusions.update(path_of_pain_locations) if wp <= WhitePalace.option_kingfragment: exclusions.update(white_palace_checks) if wp == WhitePalace.option_exclude: exclusions.add("King_Fragment") - if self.multiworld.RandomizeCharms[self.player]: + if self.options.RandomizeCharms: # If charms are randomized, this will be junk-filled -- so transitions and events are not progression exclusions.update(white_palace_transitions) exclusions.update(white_palace_events) @@ -199,8 +199,14 @@ def create_regions(self): self.multiworld.regions.append(menu_region) # wp_exclusions = self.white_palace_exclusions() + # check for any goal that godhome events are relevant to + all_event_names = event_names.copy() + if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]: + from .GodhomeData import godhome_event_names + all_event_names.update(set(godhome_event_names)) + # Link regions - for event_name in event_names: + for event_name in all_event_names: #if event_name in wp_exclusions: # continue loc = HKLocation(self.player, event_name, None, menu_region) @@ -225,16 +231,16 @@ def create_items(self): pool: typing.List[HKItem] = [] wp_exclusions = self.white_palace_exclusions() junk_replace: typing.Set[str] = set() - if self.multiworld.RemoveSpellUpgrades[self.player]: + if self.options.RemoveSpellUpgrades: junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark")) randomized_starting_items = set() for attr, items in randomizable_starting_items.items(): - if getattr(self.multiworld, attr)[self.player]: + if getattr(self.options, attr): randomized_starting_items.update(items) # noinspection PyShadowingNames - def _add(item_name: str, location_name: str): + def _add(item_name: str, location_name: str, randomized: bool): """ Adds a pairing of an item and location, doing appropriate checks to see if it should be vanilla or not. """ @@ -252,7 +258,7 @@ def _add(item_name: str, location_name: str): if item_name in junk_replace: item_name = self.get_filler_item_name() - item = self.create_item(item_name) + item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.options.AddUnshuffledLocations else self.create_event(item_name) if location_name == "Start": if item_name in randomized_starting_items: @@ -276,50 +282,55 @@ def _add(item_name: str, location_name: str): location.progress_type = LocationProgressType.EXCLUDED for option_key, option in hollow_knight_randomize_options.items(): - randomized = getattr(self.multiworld, option_key)[self.player] + randomized = getattr(self.options, option_key) + if all([not randomized, option_key in logicless_options, not self.options.AddUnshuffledLocations]): + continue for item_name, location_name in zip(option.items, option.locations): if item_name in junk_replace: item_name = self.get_filler_item_name() - if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \ - (item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]): - _add("Left_" + item_name, location_name) - _add("Right_" + item_name, "Split_" + location_name) + if (item_name == "Crystal_Heart" and self.options.SplitCrystalHeart) or \ + (item_name == "Mothwing_Cloak" and self.options.SplitMothwingCloak): + _add("Left_" + item_name, location_name, randomized) + _add("Right_" + item_name, "Split_" + location_name, randomized) continue - if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]: - _add("Left_" + item_name, "Left_" + location_name) - _add("Right_" + item_name, "Right_" + location_name) + if item_name == "Mantis_Claw" and self.options.SplitMantisClaw: + _add("Left_" + item_name, "Left_" + location_name, randomized) + _add("Right_" + item_name, "Right_" + location_name, randomized) continue - if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]: - if self.multiworld.random.randint(0, 1): + if item_name == "Shade_Cloak" and self.options.SplitMothwingCloak: + if self.random.randint(0, 1): item_name = "Left_Mothwing_Cloak" else: item_name = "Right_Mothwing_Cloak" + if item_name == "Grimmchild2" and self.options.RandomizeGrimmkinFlames and self.options.RandomizeCharms: + _add("Grimmchild1", location_name, randomized) + continue - _add(item_name, location_name) + _add(item_name, location_name, randomized) - if self.multiworld.RandomizeElevatorPass[self.player]: + if self.options.RandomizeElevatorPass: randomized = True - _add("Elevator_Pass", "Elevator_Pass") + _add("Elevator_Pass", "Elevator_Pass", randomized) for shop, locations in self.created_multi_locations.items(): - for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value): + for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value): loc = self.create_location(shop) unfilled_locations += 1 # Balance the pool item_count = len(pool) - additional_shop_items = max(item_count - unfilled_locations, self.multiworld.ExtraShopSlots[self.player].value) + additional_shop_items = max(item_count - unfilled_locations, self.options.ExtraShopSlots.value) # Add additional shop items, as needed. if additional_shop_items > 0: shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16) - if not self.multiworld.EggShopSlots[self.player].value: # No eggshop, so don't place items there + if not self.options.EggShopSlots: # No eggshop, so don't place items there shops.remove('Egg_Shop') if shops: for _ in range(additional_shop_items): - shop = self.multiworld.random.choice(shops) + shop = self.random.choice(shops) loc = self.create_location(shop) unfilled_locations += 1 if len(self.created_multi_locations[shop]) >= 16: @@ -345,7 +356,7 @@ def sort_shops_by_cost(self): loc.costs = costs def apply_costsanity(self): - setting = self.multiworld.CostSanity[self.player].value + setting = self.options.CostSanity.value if not setting: return # noop @@ -359,10 +370,10 @@ def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: return {k: v for k, v in weights.items() if v} - random = self.multiworld.random - hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value + random = self.random + hybrid_chance = getattr(self.options, f"CostSanityHybridChance").value weights = { - data.term: getattr(self.multiworld, f"CostSanity{data.option}Weight")[self.player].value + data.term: getattr(self.options, f"CostSanity{data.option}Weight").value for data in cost_terms.values() } weights_geoless = dict(weights) @@ -395,7 +406,7 @@ def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: continue if setting == CostSanity.option_shopsonly and location.basename not in multi_locations: continue - if location.basename in {'Grubfather', 'Seer', 'Eggshop'}: + if location.basename in {'Grubfather', 'Seer', 'Egg_Shop'}: our_weights = dict(weights_geoless) else: our_weights = dict(weights) @@ -417,18 +428,22 @@ def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: location.sort_costs() def set_rules(self): - world = self.multiworld + multiworld = self.multiworld player = self.player - goal = world.Goal[player] + goal = self.options.Goal if goal == Goal.option_hollowknight: - world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) + multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) elif goal == Goal.option_siblings: - world.completion_condition[player] = lambda state: state._hk_siblings_ending(player) + multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) elif goal == Goal.option_radiance: - world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player) + multiworld.completion_condition[player] = lambda state: _hk_can_beat_radiance(state, player) + elif goal == Goal.option_godhome: + multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) + elif goal == Goal.option_godhome_flower: + multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) else: # Any goal - world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player) + multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player) set_rules(self) @@ -436,8 +451,8 @@ def fill_slot_data(self): slot_data = {} options = slot_data["options"] = {} - for option_name in self.option_definitions: - option = getattr(self.multiworld, option_name)[self.player] + for option_name in hollow_knight_options: + option = getattr(self.options, option_name) try: optionvalue = int(option.value) except TypeError: @@ -446,10 +461,10 @@ def fill_slot_data(self): options[option_name] = optionvalue # 32 bit int - slot_data["seed"] = self.multiworld.per_slot_randoms[self.player].randint(-2147483647, 2147483646) + slot_data["seed"] = self.random.randint(-2147483647, 2147483646) # Backwards compatibility for shop cost data (HKAP < 0.1.0) - if not self.multiworld.CostSanity[self.player]: + if not self.options.CostSanity: for shop, terms in shop_cost_types.items(): unit = cost_terms[next(iter(terms))].option if unit == "Geo": @@ -475,12 +490,16 @@ def create_item(self, name: str) -> HKItem: item_data = item_table[name] return HKItem(name, item_data.advancement, item_data.id, item_data.type, self.player) + def create_event(self, name: str) -> HKItem: + item_data = item_table[name] + return HKItem(name, item_data.advancement, None, item_data.type, self.player) + def create_location(self, name: str, vanilla=False) -> HKLocation: costs = None basename = name if name in shop_cost_types: costs = { - term: self.multiworld.random.randint(*self.ranges[term]) + term: self.random.randint(*self.ranges[term]) for term in shop_cost_types[name] } elif name in vanilla_location_costs: @@ -493,9 +512,15 @@ def create_location(self, name: str, vanilla=False) -> HKLocation: name = f"{name}_{i}" region = self.multiworld.get_region("Menu", self.player) - loc = HKLocation(self.player, name, - self.location_name_to_id[name], region, costs=costs, vanilla=vanilla, - basename=basename) + + if vanilla and not self.options.AddUnshuffledLocations: + loc = HKLocation(self.player, name, + None, region, costs=costs, vanilla=vanilla, + basename=basename) + else: + loc = HKLocation(self.player, name, + self.location_name_to_id[name], region, costs=costs, vanilla=vanilla, + basename=basename) if multi is not None: multi.append(loc) @@ -530,31 +555,32 @@ def remove(self, state, item: HKItem) -> bool: for effect_name, effect_value in item_effects.get(item.name, {}).items(): if state.prog_items[item.player][effect_name] == effect_value: del state.prog_items[item.player][effect_name] - state.prog_items[item.player][effect_name] -= effect_value + else: + state.prog_items[item.player][effect_name] -= effect_value return change @classmethod - def stage_write_spoiler(cls, world: MultiWorld, spoiler_handle): - hk_players = world.get_game_players(cls.game) + def stage_write_spoiler(cls, multiworld: MultiWorld, spoiler_handle): + hk_players = multiworld.get_game_players(cls.game) spoiler_handle.write('\n\nCharm Notches:') for player in hk_players: - name = world.get_player_name(player) + name = multiworld.get_player_name(player) spoiler_handle.write(f'\n{name}\n') - hk_world: HKWorld = world.worlds[player] + hk_world: HKWorld = multiworld.worlds[player] for charm_number, cost in enumerate(hk_world.charm_costs): spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}") spoiler_handle.write('\n\nShop Prices:') for player in hk_players: - name = world.get_player_name(player) + name = multiworld.get_player_name(player) spoiler_handle.write(f'\n{name}\n') - hk_world: HKWorld = world.worlds[player] + hk_world: HKWorld = multiworld.worlds[player] - if world.CostSanity[player].value: + if hk_world.options.CostSanity: for loc in sorted( ( - loc for loc in itertools.chain(*(region.locations for region in world.get_regions(player))) + loc for loc in itertools.chain(*(region.locations for region in multiworld.get_regions(player))) if loc.costs ), key=operator.attrgetter('name') ): @@ -578,15 +604,15 @@ def get_filler_item_name(self) -> str: 'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests', 'RandomizeRancidEggs' ): - if getattr(self.multiworld, group): + if getattr(self.options, group): fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in exclusions) self.cached_filler_items[self.player] = fillers - return self.multiworld.random.choice(self.cached_filler_items[self.player]) + return self.random.choice(self.cached_filler_items[self.player]) -def create_region(world: MultiWorld, player: int, name: str, location_names=None) -> Region: - ret = Region(name, player, world) +def create_region(multiworld: MultiWorld, player: int, name: str, location_names=None) -> Region: + ret = Region(name, player, multiworld) if location_names: for location in location_names: loc_id = HKWorld.location_name_to_id.get(location, None) @@ -634,6 +660,8 @@ class HKItem(Item): def __init__(self, name, advancement, code, type: str, player: int = None): if name == "Mimic_Grub": classification = ItemClassification.trap + elif name == "Godtuner": + classification = ItemClassification.progression elif type in ("Grub", "DreamWarrior", "Root", "Egg", "Dreamer"): classification = ItemClassification.progression_skip_balancing elif type == "Charm" and name not in progression_charms: @@ -657,42 +685,7 @@ def _hk_notches(self, player: int, *notches: int) -> int: return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches) def _hk_option(self, player: int, option_name: str) -> int: - return getattr(self.multiworld, option_name)[player].value + return getattr(self.multiworld.worlds[player].options, option_name).value def _hk_start(self, player, start_location: str) -> bool: - return self.multiworld.StartLocation[player] == start_location - - def _hk_nail_combat(self, player: int) -> bool: - return self.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player) - - def _hk_can_beat_thk(self, player: int) -> bool: - return ( - self.has('Opened_Black_Egg_Temple', player) - and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1 - and self._hk_nail_combat(player) - and ( - self.has_any({'LEFTDASH', 'RIGHTDASH'}, player) - or self._hk_option(player, 'ProficientCombat') - ) - and self.has('FOCUS', player) - ) - - def _hk_siblings_ending(self, player: int) -> bool: - return self._hk_can_beat_thk(player) and self.has('WHITEFRAGMENT', player, 3) - - def _hk_can_beat_radiance(self, player: int) -> bool: - return ( - self.has('Opened_Black_Egg_Temple', player) - and self._hk_nail_combat(player) - and self.has('WHITEFRAGMENT', player, 3) - and self.has('DREAMNAIL', player) - and ( - (self.has('LEFTCLAW', player) and self.has('RIGHTCLAW', player)) - or self.has('WINGS', player) - ) - and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1 - and ( - (self.has('LEFTDASH', player, 2) and self.has('RIGHTDASH', player, 2)) # Both Shade Cloaks - or (self._hk_option(player, 'ProficientCombat') and self.has('QUAKE', player)) # or Dive - ) - ) + return self.multiworld.worlds[player].options.StartLocation == start_location diff --git a/worlds/hk/docs/en_Hollow Knight.md b/worlds/hk/docs/en_Hollow Knight.md index 6e74c8a1fbbc..94398ec6ac14 100644 --- a/worlds/hk/docs/en_Hollow Knight.md +++ b/worlds/hk/docs/en_Hollow Knight.md @@ -1,18 +1,20 @@ # Hollow Knight -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? Randomization swaps around the locations of items. The items being swapped around are chosen within your YAML. -Shop costs are presently always randomized. +Shop costs are presently always randomized. Items which could be randomized, but are not, will remain unmodified in +their usual locations. In particular, when the items at Grubfather and Seer are partially randomized, randomized items +will be obtained from a chest in the room, while unrandomized items will be given by the NPC as normal. ## What Hollow Knight items can appear in other players' worlds? -This is dependent entirely upon your YAML settings. Some examples include: charms, grubs, lifeblood cocoons, geo, etc. +This is dependent entirely upon your YAML options. Some examples include: charms, grubs, lifeblood cocoons, geo, etc. ## What does another world's item look like in Hollow Knight? diff --git a/worlds/hk/docs/setup_en.md b/worlds/hk/docs/setup_en.md index fef0f051fec0..c046785038d8 100644 --- a/worlds/hk/docs/setup_en.md +++ b/worlds/hk/docs/setup_en.md @@ -3,6 +3,8 @@ ## Required Software * Download and unzip the Lumafly Mod Manager from the [Lumafly website](https://themulhima.github.io/Lumafly/). * A legal copy of Hollow Knight. + * Steam, Gog, and Xbox Game Pass versions of the game are supported. + * Windows, Mac, and Linux (including Steam Deck) are supported. ## Installing the Archipelago Mod using Lumafly 1. Launch Lumafly and ensure it locates your Hollow Knight installation directory. @@ -10,28 +12,28 @@ * If desired, also install "Archipelago Map Mod" to use as an in-game tracker. 3. Launch the game, you're all set! -### What to do if Lumafly fails to find your XBox Game Pass installation directory -1. Enter the XBox app and move your mouse over "Hollow Knight" on the left sidebar. -2. Click the three points then click "Manage". -3. Go to the "Files" tab and select "Browse...". -4. Click "Hollow Knight", then "Content", then click the path bar and copy it. -5. Run Lumafly as an administrator and, when it asks you for the path, paste what you copied in step 4. - -#### Alternative Method: -1. Click on your profile then "Settings". -2. Go to the "General" tab and select "CHANGE FOLDER". -3. Look for a folder where you want to install the game (preferably inside a folder on your desktop) and copy the path. -4. Run Lumafly as an administrator and, when it asks you for the path, paste what you copied in step 3. - -Note: The path folder needs to have the "Hollow Knight_Data" folder inside. +### What to do if Lumafly fails to find your installation directory +1. Find the directory manually. + * Xbox Game Pass: + 1. Enter the XBox app and move your mouse over "Hollow Knight" on the left sidebar. + 2. Click the three points then click "Manage". + 3. Go to the "Files" tab and select "Browse...". + 4. Click "Hollow Knight", then "Content", then click the path bar and copy it. + * Steam: + 1. You likely put your Steam library in a non-standard place. If this is the case, you probably know where + it is. Find your steam library and then find the Hollow Knight folder and copy the path. + * Windows - `C:\Program Files (x86)\Steam\steamapps\common\Hollow Knight` + * Linux/Steam Deck - ~/.local/share/Steam/steamapps/common/Hollow Knight + * Mac - ~/Library/Application Support/Steam/steamapps/common/Hollow Knight/hollow_knight.app +2. Run Lumafly as an administrator and, when it asks you for the path, paste the path you copied. ## Configuring your YAML File ### What is a YAML and why do I need one? -You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) here on the Archipelago website to learn -about why Archipelago uses YAML files and what they're for. +An YAML file is the way that you provide your player options to Archipelago. +See the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) here on the Archipelago website to learn more. ### Where do I get a YAML? -You can use the [game settings page for Hollow Knight](/games/Hollow%20Knight/player-settings) here on the Archipelago +You can use the [game options page for Hollow Knight](/games/Hollow%20Knight/player-options) here on the Archipelago website to generate a YAML using a graphical interface. ### Joining an Archipelago Game in Hollow Knight @@ -44,9 +46,7 @@ website to generate a YAML using a graphical interface. * If you are waiting for a countdown then wait for it to lapse before hitting Start. * Or hit Start then pause the game once you're in it. -## Commands -While playing the multiworld you can interact with the server using various commands listed in the -[commands guide](/tutorial/Archipelago/commands/en). As this game does not have an in-game text client at the moment, -You can optionally connect to the multiworld using the text client, which can be found in the -[main Archipelago installation](https://github.com/ArchipelagoMW/Archipelago/releases) as Archipelago Text Client to -enter these commands. +## Hints and other commands +While playing in a multiworld, you can interact with the server using various commands listed in the +[commands guide](/tutorial/Archipelago/commands/en). You can use the Archipelago Text Client to do this, +which is included in the latest release of the [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/latest). diff --git a/worlds/hylics2/Options.py b/worlds/hylics2/Options.py index ac57e666a1a2..db9c316a7b1b 100644 --- a/worlds/hylics2/Options.py +++ b/worlds/hylics2/Options.py @@ -1,41 +1,79 @@ -from Options import Choice, Toggle, DefaultOnToggle, DeathLink +from dataclasses import dataclass +from Options import Choice, Removed, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions + class PartyShuffle(Toggle): - """Shuffles party members into the pool. - Note that enabling this can potentially increase both the difficulty and length of a run.""" + """ + Shuffles party members into the item pool. + + Note that enabling this can significantly increase both the difficulty and length of a run. + """ display_name = "Shuffle Party Members" + class GestureShuffle(Choice): - """Choose where gestures will appear in the item pool.""" + """ + Choose where gestures will appear in the item pool. + """ display_name = "Shuffle Gestures" option_anywhere = 0 option_tvs_only = 1 option_default_locations = 2 default = 0 + class MedallionShuffle(Toggle): - """Shuffles red medallions into the pool.""" + """ + Shuffles red medallions into the item pool. + """ display_name = "Shuffle Red Medallions" -class RandomStart(Toggle): - """Start the randomizer in 1 of 4 positions. - (Waynehouse, Viewax's Edifice, TV Island, Shield Facility)""" - display_name = "Randomize Start Location" + +class StartLocation(Choice): + """ + Select the starting location from 1 of 4 positions. + """ + display_name = "Start Location" + option_waynehouse = 0 + option_viewaxs_edifice = 1 + option_tv_island = 2 + option_shield_facility = 3 + default = 0 + + @classmethod + def get_option_name(cls, value: int) -> str: + if value == 1: + return "Viewax's Edifice" + if value == 2: + return "TV Island" + return super().get_option_name(value) + class ExtraLogic(DefaultOnToggle): - """Include some extra items in logic (CHARGE UP, 1x PAPER CUP) to prevent the game from becoming too difficult.""" + """ + Include some extra items in logic (CHARGE UP, 1x PAPER CUP) to prevent the game from becoming too difficult. + """ display_name = "Extra Items in Logic" + class Hylics2DeathLink(DeathLink): - """When you die, everyone dies. The reverse is also true. + """ + When you die, everyone dies. The reverse is also true. + Note that this also includes death by using the PERISH gesture. - Can be toggled via in-game console command "/deathlink".""" - -hylics2_options = { - "party_shuffle": PartyShuffle, - "gesture_shuffle" : GestureShuffle, - "medallion_shuffle" : MedallionShuffle, - "random_start" : RandomStart, - "extra_items_in_logic": ExtraLogic, - "death_link": Hylics2DeathLink -} \ No newline at end of file + + Can be toggled via in-game console command "/deathlink". + """ + + +@dataclass +class Hylics2Options(PerGameCommonOptions): + party_shuffle: PartyShuffle + gesture_shuffle: GestureShuffle + medallion_shuffle: MedallionShuffle + start_location: StartLocation + extra_items_in_logic: ExtraLogic + death_link: Hylics2DeathLink + + # Removed options + random_start: Removed diff --git a/worlds/hylics2/Rules.py b/worlds/hylics2/Rules.py index ff9544e0e843..3914054193ce 100644 --- a/worlds/hylics2/Rules.py +++ b/worlds/hylics2/Rules.py @@ -129,6 +129,11 @@ def set_rules(hylics2world): world = hylics2world.multiworld player = hylics2world.player + extra = hylics2world.options.extra_items_in_logic + party = hylics2world.options.party_shuffle + medallion = hylics2world.options.medallion_shuffle + start_location = hylics2world.options.start_location + # Afterlife add_rule(world.get_location("Afterlife: TV", player), lambda state: cave_key(state, player)) @@ -346,7 +351,7 @@ def set_rules(hylics2world): lambda state: upper_chamber_key(state, player)) # extra rules if Extra Items in Logic is enabled - if world.extra_items_in_logic[player]: + if extra: for i in world.get_region("Foglast", player).entrances: add_rule(i, lambda state: charge_up(state, player)) for i in world.get_region("Sage Airship", player).entrances: @@ -368,7 +373,7 @@ def set_rules(hylics2world): )) # extra rules if Shuffle Party Members is enabled - if world.party_shuffle[player]: + if party: for i in world.get_region("Arcade Island", player).entrances: add_rule(i, lambda state: party_3(state, player)) for i in world.get_region("Foglast", player).entrances: @@ -406,33 +411,38 @@ def set_rules(hylics2world): lambda state: party_3(state, player)) # extra rules if Shuffle Red Medallions is enabled - if world.medallion_shuffle[player]: + if medallion: add_rule(world.get_location("New Muldul: Upper House Medallion", player), lambda state: upper_house_key(state, player)) add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player), lambda state: ( enter_foglast(state, player) and bridge_key(state, player) + and air_dash(state, player) )) add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player), lambda state: ( enter_foglast(state, player) and bridge_key(state, player) + and air_dash(state, player) )) add_rule(world.get_location("New Muldul: Vault Center Medallion", player), lambda state: ( enter_foglast(state, player) and bridge_key(state, player) + and air_dash(state, player) )) add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player), lambda state: ( enter_foglast(state, player) and bridge_key(state, player) + and air_dash(state, player) )) add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player), lambda state: ( enter_foglast(state, player) and bridge_key(state, player) + and air_dash(state, player) )) add_rule(world.get_location("Viewax's Edifice: Fort Wall Medallion", player), lambda state: paddle(state, player)) @@ -456,7 +466,7 @@ def set_rules(hylics2world): lambda state: upper_chamber_key(state, player)) # extra rules if Shuffle Red Medallions and Party Shuffle are enabled - if world.party_shuffle[player] and world.medallion_shuffle[player]: + if party and medallion: add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player), lambda state: party_3(state, player)) add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player), @@ -488,8 +498,7 @@ def set_rules(hylics2world): add_rule(i, lambda state: enter_hylemxylem(state, player)) # random start logic (default) - if ((not world.random_start[player]) or \ - (world.random_start[player] and hylics2world.start_location == "Waynehouse")): + if start_location == "waynehouse": # entrances for i in world.get_region("Viewax", player).entrances: add_rule(i, lambda state: ( @@ -504,7 +513,7 @@ def set_rules(hylics2world): add_rule(i, lambda state: airship(state, player)) # random start logic (Viewax's Edifice) - elif (world.random_start[player] and hylics2world.start_location == "Viewax's Edifice"): + elif start_location == "viewaxs_edifice": for i in world.get_region("Waynehouse", player).entrances: add_rule(i, lambda state: ( air_dash(state, player) @@ -534,8 +543,8 @@ def set_rules(hylics2world): for i in world.get_region("Sage Labyrinth", player).entrances: add_rule(i, lambda state: airship(state, player)) - # random start logic (TV Island) - elif (world.random_start[player] and hylics2world.start_location == "TV Island"): + # start logic (TV Island) + elif start_location == "tv_island": for i in world.get_region("Waynehouse", player).entrances: add_rule(i, lambda state: airship(state, player)) for i in world.get_region("New Muldul", player).entrances: @@ -553,8 +562,8 @@ def set_rules(hylics2world): for i in world.get_region("Sage Labyrinth", player).entrances: add_rule(i, lambda state: airship(state, player)) - # random start logic (Shield Facility) - elif (world.random_start[player] and hylics2world.start_location == "Shield Facility"): + # start logic (Shield Facility) + elif start_location == "shield_facility": for i in world.get_region("Waynehouse", player).entrances: add_rule(i, lambda state: airship(state, player)) for i in world.get_region("New Muldul", player).entrances: @@ -568,4 +577,4 @@ def set_rules(hylics2world): for i in world.get_region("TV Island", player).entrances: add_rule(i, lambda state: airship(state, player)) for i in world.get_region("Sage Labyrinth", player).entrances: - add_rule(i, lambda state: airship(state, player)) \ No newline at end of file + add_rule(i, lambda state: airship(state, player)) diff --git a/worlds/hylics2/__init__.py b/worlds/hylics2/__init__.py index 1c51bacc5d67..18bcb0edc143 100644 --- a/worlds/hylics2/__init__.py +++ b/worlds/hylics2/__init__.py @@ -1,7 +1,8 @@ from typing import Dict, List, Any from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification from worlds.generic.Rules import set_rule -from . import Exits, Items, Locations, Options, Rules +from . import Exits, Items, Locations, Rules +from .Options import Hylics2Options from worlds.AutoWorld import WebWorld, World @@ -32,13 +33,9 @@ class Hylics2World(World): item_name_to_id = {data["name"]: item_id for item_id, data in all_items.items()} location_name_to_id = {data["name"]: loc_id for loc_id, data in all_locations.items()} - option_definitions = Options.hylics2_options - topology_present: bool = True - - data_version = 3 - - start_location = "Waynehouse" + options_dataclass = Hylics2Options + options: Hylics2Options def set_rules(self): @@ -51,52 +48,35 @@ def create_item(self, name: str) -> "Hylics2Item": return Hylics2Item(name, self.all_items[item_id]["classification"], item_id, player=self.player) - def add_item(self, name: str, classification: ItemClassification, code: int) -> "Item": - return Hylics2Item(name, classification, code, self.player) - - def create_event(self, event: str): return Hylics2Item(event, ItemClassification.progression_skip_balancing, None, self.player) - # set random starting location if option is enabled - def generate_early(self): - if self.multiworld.random_start[self.player]: - i = self.multiworld.random.randint(0, 3) - if i == 0: - self.start_location = "Waynehouse" - elif i == 1: - self.start_location = "Viewax's Edifice" - elif i == 2: - self.start_location = "TV Island" - elif i == 3: - self.start_location = "Shield Facility" - def create_items(self): # create item pool pool = [] # add regular items - for i, data in Items.item_table.items(): - if data["count"] > 0: - for j in range(data["count"]): - pool.append(self.add_item(data["name"], data["classification"], i)) + for item in Items.item_table.values(): + if item["count"] > 0: + for _ in range(item["count"]): + pool.append(self.create_item(item["name"])) # add party members if option is enabled - if self.multiworld.party_shuffle[self.player]: - for i, data in Items.party_item_table.items(): - pool.append(self.add_item(data["name"], data["classification"], i)) + if self.options.party_shuffle: + for item in Items.party_item_table.values(): + pool.append(self.create_item(item["name"])) # handle gesture shuffle - if not self.multiworld.gesture_shuffle[self.player]: # add gestures to pool like normal - for i, data in Items.gesture_item_table.items(): - pool.append(self.add_item(data["name"], data["classification"], i)) + if not self.options.gesture_shuffle: # add gestures to pool like normal + for item in Items.gesture_item_table.values(): + pool.append(self.create_item(item["name"])) # add '10 Bones' items if medallion shuffle is enabled - if self.multiworld.medallion_shuffle[self.player]: - for i, data in Items.medallion_item_table.items(): - for j in range(data["count"]): - pool.append(self.add_item(data["name"], data["classification"], i)) + if self.options.medallion_shuffle: + for item in Items.medallion_item_table.values(): + for _ in range(item["count"]): + pool.append(self.create_item(item["name"])) # add to world's pool self.multiworld.itempool += pool @@ -104,60 +84,57 @@ def create_items(self): def pre_fill(self): # handle gesture shuffle options - if self.multiworld.gesture_shuffle[self.player] == 2: # vanilla locations + if self.options.gesture_shuffle == 2: # vanilla locations gestures = Items.gesture_item_table self.multiworld.get_location("Waynehouse: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200678]["name"], gestures[200678]["classification"], 200678)) + .place_locked_item(self.create_item("POROMER BLEB")) self.multiworld.get_location("Afterlife: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200683]["name"], gestures[200683]["classification"], 200683)) + .place_locked_item(self.create_item("TELEDENUDATE")) self.multiworld.get_location("New Muldul: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200679]["name"], gestures[200679]["classification"], 200679)) + .place_locked_item(self.create_item("SOUL CRISPER")) self.multiworld.get_location("Viewax's Edifice: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200680]["name"], gestures[200680]["classification"], 200680)) + .place_locked_item(self.create_item("TIME SIGIL")) self.multiworld.get_location("TV Island: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200681]["name"], gestures[200681]["classification"], 200681)) + .place_locked_item(self.create_item("CHARGE UP")) self.multiworld.get_location("Juice Ranch: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200682]["name"], gestures[200682]["classification"], 200682)) + .place_locked_item(self.create_item("FATE SANDBOX")) self.multiworld.get_location("Foglast: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200684]["name"], gestures[200684]["classification"], 200684)) + .place_locked_item(self.create_item("LINK MOLLUSC")) self.multiworld.get_location("Drill Castle: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200688]["name"], gestures[200688]["classification"], 200688)) + .place_locked_item(self.create_item("NEMATODE INTERFACE")) self.multiworld.get_location("Sage Airship: TV", self.player)\ - .place_locked_item(self.add_item(gestures[200685]["name"], gestures[200685]["classification"], 200685)) + .place_locked_item(self.create_item("BOMBO - GENESIS")) - elif self.multiworld.gesture_shuffle[self.player] == 1: # TVs only - gestures = list(Items.gesture_item_table.items()) - tvs = list(Locations.tv_location_table.items()) + elif self.options.gesture_shuffle == 1: # TVs only + gestures = [gesture["name"] for gesture in Items.gesture_item_table.values()] + tvs = [tv["name"] for tv in Locations.tv_location_table.values()] # if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get # placed at Sage Airship: TV or Foglast: TV - if self.multiworld.extra_items_in_logic[self.player]: - tv = self.multiworld.random.choice(tvs) - gest = gestures.index((200681, Items.gesture_item_table[200681])) - while tv[1]["name"] == "Sage Airship: TV" or tv[1]["name"] == "Foglast: TV": - tv = self.multiworld.random.choice(tvs) - self.multiworld.get_location(tv[1]["name"], self.player)\ - .place_locked_item(self.add_item(gestures[gest][1]["name"], gestures[gest][1]["classification"], - gestures[gest])) - gestures.remove(gestures[gest]) + if self.options.extra_items_in_logic: + tv = self.random.choice(tvs) + while tv == "Sage Airship: TV" or tv == "Foglast: TV": + tv = self.random.choice(tvs) + self.multiworld.get_location(tv, self.player)\ + .place_locked_item(self.create_item("CHARGE UP")) + gestures.remove("CHARGE UP") tvs.remove(tv) - for i in range(len(gestures)): - gest = self.multiworld.random.choice(gestures) - tv = self.multiworld.random.choice(tvs) - self.multiworld.get_location(tv[1]["name"], self.player)\ - .place_locked_item(self.add_item(gest[1]["name"], gest[1]["classification"], gest[0])) - gestures.remove(gest) - tvs.remove(tv) + self.random.shuffle(gestures) + self.random.shuffle(tvs) + while gestures: + gesture = gestures.pop() + tv = tvs.pop() + self.get_location(tv).place_locked_item(self.create_item(gesture)) def fill_slot_data(self) -> Dict[str, Any]: slot_data: Dict[str, Any] = { - "party_shuffle": self.multiworld.party_shuffle[self.player].value, - "medallion_shuffle": self.multiworld.medallion_shuffle[self.player].value, - "random_start" : self.multiworld.random_start[self.player].value, - "start_location" : self.start_location, - "death_link": self.multiworld.death_link[self.player].value + "party_shuffle": self.options.party_shuffle.value, + "medallion_shuffle": self.options.medallion_shuffle.value, + "random_start": int(self.options.start_location != "waynehouse"), + "start_location" : self.options.start_location.current_option_name, + "death_link": self.options.death_link.value } return slot_data @@ -195,14 +172,14 @@ def create_regions(self) -> None: # create entrance and connect it to parent and destination regions ent = Entrance(self.player, f"{reg.name} {k}", reg) reg.exits.append(ent) - if k == "New Game" and self.multiworld.random_start[self.player]: - if self.start_location == "Waynehouse": + if k == "New Game": + if self.options.start_location == "waynehouse": ent.connect(region_table[2]) - elif self.start_location == "Viewax's Edifice": + elif self.options.start_location == "viewaxs_edifice": ent.connect(region_table[6]) - elif self.start_location == "TV Island": + elif self.options.start_location == "tv_island": ent.connect(region_table[9]) - elif self.start_location == "Shield Facility": + elif self.options.start_location == "shield_facility": ent.connect(region_table[11]) else: for name, num in Exits.exit_lookup_table.items(): @@ -218,13 +195,13 @@ def create_regions(self) -> None: .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]])) # add party member locations if option is enabled - if self.multiworld.party_shuffle[self.player]: + if self.options.party_shuffle: for i, data in Locations.party_location_table.items(): region_table[data["region"]].locations\ .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]])) # add medallion locations if option is enabled - if self.multiworld.medallion_shuffle[self.player]: + if self.options.medallion_shuffle: for i, data in Locations.medallion_location_table.items(): region_table[data["region"]].locations\ .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]])) diff --git a/worlds/hylics2/docs/en_Hylics 2.md b/worlds/hylics2/docs/en_Hylics 2.md index cb201a52bb3e..4ebd0742eba5 100644 --- a/worlds/hylics2/docs/en_Hylics 2.md +++ b/worlds/hylics2/docs/en_Hylics 2.md @@ -1,8 +1,8 @@ # Hylics 2 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py index c10dd6cebb72..1ca21d550e67 100644 --- a/worlds/kdl3/Client.py +++ b/worlds/kdl3/Client.py @@ -36,8 +36,10 @@ KDL3_GIFTING_FLAG = SRAM_1_START + 0x901C KDL3_LEVEL_ADDR = SRAM_1_START + 0x9020 KDL3_IS_DEMO = SRAM_1_START + 0x5AD5 -KDL3_GAME_STATE = SRAM_1_START + 0x36D0 KDL3_GAME_SAVE = SRAM_1_START + 0x3617 +KDL3_CURRENT_WORLD = SRAM_1_START + 0x363F +KDL3_CURRENT_LEVEL = SRAM_1_START + 0x3641 +KDL3_GAME_STATE = SRAM_1_START + 0x36D0 KDL3_LIFE_COUNT = SRAM_1_START + 0x39CF KDL3_KIRBY_HP = SRAM_1_START + 0x39D1 KDL3_BOSS_HP = SRAM_1_START + 0x39D5 @@ -46,8 +48,6 @@ KDL3_HEART_STARS = SRAM_1_START + 0x53A7 KDL3_WORLD_UNLOCK = SRAM_1_START + 0x53CB KDL3_LEVEL_UNLOCK = SRAM_1_START + 0x53CD -KDL3_CURRENT_WORLD = SRAM_1_START + 0x53CF -KDL3_CURRENT_LEVEL = SRAM_1_START + 0x53D3 KDL3_BOSS_STATUS = SRAM_1_START + 0x53D5 KDL3_INVINCIBILITY_TIMER = SRAM_1_START + 0x54B1 KDL3_MG5_STATUS = SRAM_1_START + 0x5EE4 @@ -74,7 +74,9 @@ 0x0202: " was out-numbered by Pon & Con.", 0x0203: " was defeated by Ado's powerful paintings.", 0x0204: " was clobbered by King Dedede.", - 0x0205: " lost their battle against Dark Matter." + 0x0205: " lost their battle against Dark Matter.", + 0x0300: " couldn't overcome the Boss Butch.", + 0x0400: " is bad at jumping.", }) @@ -281,6 +283,11 @@ async def game_watcher(self, ctx) -> None: for i in range(5): level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14) self.levels[i] = unpack("HHHHHHH", level_data) + self.levels[5] = [0x0205, # Hyper Zone + 0, # MG-5, can't send from here + 0x0300, # Boss Butch + 0x0400, # Jumping + 0, 0, 0] if self.consumables is None: consumables = await snes_read(ctx, KDL3_CONSUMABLE_FLAG, 1) @@ -309,10 +316,13 @@ async def game_watcher(self, ctx) -> None: if current_bgm[0] in (0x00, 0x21, 0x22, 0x23, 0x25, 0x2A, 0x2B): return # null, title screen, opening, save select, true and false endings game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) - current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) if "DeathLink" in ctx.tags and game_state[0] == 0x00 and ctx.last_death_link + 1 < time.time(): + current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + current_world = struct.unpack("H", await snes_read(ctx, KDL3_CURRENT_WORLD, 2))[0] + current_level = struct.unpack("H", await snes_read(ctx, KDL3_CURRENT_LEVEL, 2))[0] currently_dead = current_hp[0] == 0x00 - await ctx.handle_deathlink_state(currently_dead) + message = deathlink_messages[self.levels[current_world][current_level]] + await ctx.handle_deathlink_state(currently_dead, f"{ctx.player_names[ctx.slot]}{message}") recv_count = await snes_read(ctx, KDL3_RECV_COUNT, 2) recv_amount = unpack("H", recv_count)[0] @@ -320,9 +330,9 @@ async def game_watcher(self, ctx) -> None: item = ctx.items_received[recv_amount] recv_amount += 1 logging.info('Received %s from %s (%s) (%d/%d in list)' % ( - color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - ctx.location_names[item.location], recv_amount, len(ctx.items_received))) + ctx.location_names.lookup_in_slot(item.location, item.player), recv_amount, len(ctx.items_received))) snes_buffered_write(ctx, KDL3_RECV_COUNT, pack("H", recv_amount)) item_idx = item.item & 0x00000F @@ -405,7 +415,7 @@ async def game_watcher(self, ctx) -> None: for new_check_id in new_checks: ctx.locations_checked.add(new_check_id) - location = ctx.location_names[new_check_id] + location = ctx.location_names.lookup_in_game(new_check_id) snes_logger.info( f'New Check: {location} ({len(ctx.locations_checked)}/' f'{len(ctx.missing_locations) + len(ctx.checked_locations)})') diff --git a/worlds/kdl3/Options.py b/worlds/kdl3/Options.py index 336bd33bc583..e0a4f12f15dc 100644 --- a/worlds/kdl3/Options.py +++ b/worlds/kdl3/Options.py @@ -2,10 +2,14 @@ from dataclasses import dataclass from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \ - PerGameCommonOptions + PerGameCommonOptions, PlandoConnections from .Names import LocationName +class KDL3PlandoConnections(PlandoConnections): + entrances = exits = {f"{i} {j}" for i in LocationName.level_names for j in range(1, 7)} + + class Goal(Choice): """ Zero: collect the Heart Stars, and defeat Zero in the Hyper Zone. @@ -400,6 +404,7 @@ class Gifting(Toggle): @dataclass class KDL3Options(PerGameCommonOptions): + plando_connections: KDL3PlandoConnections death_link: DeathLink game_language: GameLanguage goal: Goal diff --git a/worlds/kdl3/Regions.py b/worlds/kdl3/Regions.py index ed0d86586615..407dcf9680f4 100644 --- a/worlds/kdl3/Regions.py +++ b/worlds/kdl3/Regions.py @@ -1,8 +1,8 @@ import orjson import os -import typing from pkgutil import get_data +from typing import TYPE_CHECKING, List, Dict, Optional, Union from BaseClasses import Region from worlds.generic.Rules import add_item_rule from .Locations import KDL3Location @@ -10,7 +10,7 @@ from .Options import BossShuffle from .Room import KDL3Room -if typing.TYPE_CHECKING: +if TYPE_CHECKING: from . import KDL3World default_levels = { @@ -28,19 +28,35 @@ 0x77001C, # 5-4 needs Burning } +first_world_limit = { + # We need to limit the number of very restrictive stages in level 1 on solo gens + *first_stage_blacklist, # all three of the blacklist stages need 2+ items for both checks + 0x770007, + 0x770008, + 0x770013, + 0x77001E, -def generate_valid_level(level, stage, possible_stages, slot_random): - new_stage = slot_random.choice(possible_stages) - if level == 1 and stage == 0 and new_stage in first_stage_blacklist: - return generate_valid_level(level, stage, possible_stages, slot_random) - else: - return new_stage +} + + +def generate_valid_level(world: "KDL3World", level: int, stage: int, + possible_stages: List[int], placed_stages: List[int]): + new_stage = world.random.choice(possible_stages) + if level == 1: + if stage == 0 and new_stage in first_stage_blacklist: + return generate_valid_level(world, level, stage, possible_stages, placed_stages) + elif (not (world.multiworld.players > 1 or world.options.consumables or world.options.starsanity) and + new_stage in first_world_limit and + sum(p_stage in first_world_limit for p_stage in placed_stages) + >= (2 if world.options.open_world else 1)): + return generate_valid_level(world, level, stage, possible_stages, placed_stages) + return new_stage -def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing.Dict[int, Region]): +def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]): level_names = {LocationName.level_names[level]: level for level in LocationName.level_names} room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json"))) - rooms: typing.Dict[str, KDL3Room] = dict() + rooms: Dict[str, KDL3Room] = dict() for room_entry in room_data: room = KDL3Room(room_entry["name"], world.player, world.multiworld, None, room_entry["level"], room_entry["stage"], room_entry["room"], room_entry["pointer"], room_entry["music"], @@ -49,8 +65,8 @@ def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else None for location in room_entry["locations"] if (not any(x in location for x in ["1-Up", "Maxim"]) or - world.options.consumables.value) and ("Star" not in location - or world.options.starsanity.value)}, + world.options.consumables.value) and ("Star" not in location + or world.options.starsanity.value)}, KDL3Location) rooms[room.name] = room for location in room.locations: @@ -61,34 +77,26 @@ def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing world.rooms = list(rooms.values()) world.multiworld.regions.extend(world.rooms) - first_rooms: typing.Dict[int, KDL3Room] = dict() - if door_shuffle: - # first, we need to generate the notable edge cases - # 5-6 is the first, being the most restrictive - # half of its rooms are required to be vanilla, but can be in different orders - # the room before it *must* contain the copy ability required to unlock the room's goal - - raise NotImplementedError() - else: - for name, room in rooms.items(): - if room.room == 0: - if room.stage == 7: - first_rooms[0x770200 + room.level - 1] = room - else: - first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room - exits = dict() - for def_exit in room.default_exits: - target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}" - access_rule = tuple(def_exit["access_rule"]) - exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player) - room.add_exits( - exits.keys(), - exits - ) - if world.options.open_world: - if any("Complete" in location.name for location in room.locations): - room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None}, - KDL3Location) + first_rooms: Dict[int, KDL3Room] = dict() + for name, room in rooms.items(): + if room.room == 0: + if room.stage == 7: + first_rooms[0x770200 + room.level - 1] = room + else: + first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room + exits = dict() + for def_exit in room.default_exits: + target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}" + access_rule = tuple(def_exit["access_rule"]) + exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player) + room.add_exits( + exits.keys(), + exits + ) + if world.options.open_world: + if any("Complete" in location.name for location in room.locations): + room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None}, + KDL3Location) for level in world.player_levels: for stage in range(6): @@ -102,13 +110,17 @@ def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing if world.options.open_world or stage == 0: level_regions[level].add_exits([first_rooms[proper_stage].name]) else: - world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage-1]], + world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage - 1]], world.player).parent_region.add_exits([first_rooms[proper_stage].name]) - level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) + if world.options.open_world: + level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) + else: + world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][5]], world.player)\ + .parent_region.add_exits([first_rooms[0x770200 + level - 1].name]) def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_pattern: bool) -> dict: - levels: typing.Dict[int, typing.List[typing.Optional[int]]] = { + levels: Dict[int, List[Optional[int]]] = { 1: [None] * 7, 2: [None] * 7, 3: [None] * 7, @@ -117,8 +129,8 @@ def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_patte } possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)] - if world.multiworld.plando_connections[world.player]: - for connection in world.multiworld.plando_connections[world.player]: + if world.options.plando_connections: + for connection in world.options.plando_connections: try: entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1) stage_world, stage_stage = connection.exit.rsplit(" ", 1) @@ -141,15 +153,14 @@ def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_patte or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage) or (enforce_pattern == enforce_world) ] - new_stage = generate_valid_level(level, stage, stage_candidates, - world.random) + new_stage = generate_valid_level(world, level, stage, stage_candidates, levels[level]) possible_stages.remove(new_stage) levels[level][stage] = new_stage except Exception: raise Exception(f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}") # now handle bosses - boss_shuffle: typing.Union[int, str] = world.options.boss_shuffle.value + boss_shuffle: Union[int, str] = world.options.boss_shuffle.value plando_bosses = [] if isinstance(boss_shuffle, str): # boss plando @@ -218,7 +229,7 @@ def create_levels(world: "KDL3World") -> None: level_shuffle == 1, level_shuffle == 2) - generate_rooms(world, False, levels) + generate_rooms(world, levels) level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location) diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py index 91abc21d0623..6a85ef84f054 100644 --- a/worlds/kdl3/Rules.py +++ b/worlds/kdl3/Rules.py @@ -264,7 +264,7 @@ def set_rules(world: "KDL3World") -> None: for r in [range(1, 31), range(44, 51)]: for i in r: set_rule(world.multiworld.get_location(f"Cloudy Park 4 - Star {i}", world.player), - lambda state: can_reach_clean(state, world.player)) + lambda state: can_reach_coo(state, world.player)) for i in [18, *list(range(20, 25))]: set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player), lambda state: can_reach_ice(state, world.player)) diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index 66c9b17b84f4..8c9f3cc46a4e 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -129,6 +129,8 @@ def pre_fill(self) -> None: # randomize copy abilities valid_abilities = list(copy_ability_access_table.keys()) enemies_to_set = list(self.copy_abilities.keys()) + unplaced_abilities = set(key for key in copy_ability_access_table.keys() + if key not in ("No Ability", "Cutter Ability", "Burning Ability")) # now for the edge cases for abilities, enemies in enemy_restrictive: available_enemies = list() @@ -143,6 +145,7 @@ def pre_fill(self) -> None: chosen_ability = self.random.choice(abilities) self.copy_abilities[chosen_enemy] = chosen_ability enemies_to_set.remove(chosen_enemy) + unplaced_abilities.discard(chosen_ability) # two less restrictive ones, we need to ensure Cutter and Burning appear before their required stages sand_canyon_5 = self.get_region("Sand Canyon 5 - 9") # this is primarily for typing, but if this ever hits it's fine to crash @@ -160,6 +163,13 @@ def pre_fill(self) -> None: if burning_enemy: self.copy_abilities[burning_enemy] = "Burning Ability" enemies_to_set.remove(burning_enemy) + # ensure we place one of every ability + if unplaced_abilities and self.options.accessibility != self.options.accessibility.option_minimal: + # failsafe, on non-minimal we need to guarantee every copy ability exists + for ability in sorted(unplaced_abilities): + enemy = self.random.choice(enemies_to_set) + self.copy_abilities[enemy] = ability + enemies_to_set.remove(enemy) # place remaining for enemy in enemies_to_set: self.copy_abilities[enemy] = self.random.choice(valid_abilities) @@ -193,9 +203,13 @@ def pre_fill(self) -> None: animal_pool.append("Coo Spawn") else: animal_pool.append("Kine Spawn") + # Weird fill hack, this forces ChuChu to be the last animal friend placed + # If Kine is ever the last animal friend placed, he will cause fill errors on closed world + animal_pool.sort() locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns] items = [self.create_item(animal) for animal in animal_pool] allstate = self.multiworld.get_all_state(False) + self.random.shuffle(locations) fill_restrictive(self.multiworld, allstate, locations, items, True, True) else: animal_friends = animal_friend_spawns.copy() @@ -283,6 +297,8 @@ def generate_basic(self) -> None: self.boss_butch_bosses = [True for _ in range(6)] else: self.boss_butch_bosses = [self.random.choice([True, False]) for _ in range(6)] + else: + self.boss_butch_bosses = [False for _ in range(6)] def generate_output(self, output_directory: str): rom_path = "" diff --git a/worlds/kdl3/docs/en_Kirby's Dream Land 3.md b/worlds/kdl3/docs/en_Kirby's Dream Land 3.md index c1e36fed546a..008ee0fcc1ee 100644 --- a/worlds/kdl3/docs/en_Kirby's Dream Land 3.md +++ b/worlds/kdl3/docs/en_Kirby's Dream Land 3.md @@ -1,8 +1,8 @@ # Kirby's Dream Land 3 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -15,6 +15,7 @@ as Heart Stars, 1-Ups, and Invincibility Candy will be shuffled into the pool fo - Purifying a boss after acquiring a certain number of Heart Stars (indicated by their portrait flashing in the level select) - If enabled, 1-Ups and Maxim Tomatoes +- If enabled, every single Star Piece within a stage ## When the player receives an item, what happens? A sound effect will play, and Kirby will immediately receive the effects of that item, such as being able to receive Copy Abilities from enemies that diff --git a/worlds/kdl3/docs/setup_en.md b/worlds/kdl3/docs/setup_en.md index a13a0f1a74cf..a73d248d4d18 100644 --- a/worlds/kdl3/docs/setup_en.md +++ b/worlds/kdl3/docs/setup_en.md @@ -43,8 +43,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page on the website allows you to configure -your personal settings and export a config file from them. +The [Player Options](/games/Kirby's%20Dream%20Land%203/player-options) page on the website allows you to configure +your personal options and export a config file from them. ### Verifying your config file @@ -53,7 +53,7 @@ If you would like to validate your config file to make sure it works, you may do ## Generating a Single-Player Game -1. Navigate to the [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page, configure your options, +1. Navigate to the [Player Options](/games/Kirby's%20Dream%20Land%203/player-options) page, configure your options, and click the "Generate Game" button. 2. You will be presented with a "Seed Info" page. 3. Click the "Create New Room" link. diff --git a/worlds/kdl3/test/__init__.py b/worlds/kdl3/test/__init__.py index 11a17e63b7fa..4d3f4d70faae 100644 --- a/worlds/kdl3/test/__init__.py +++ b/worlds/kdl3/test/__init__.py @@ -2,7 +2,7 @@ from argparse import Namespace from BaseClasses import MultiWorld, PlandoOptions, CollectionState -from test.TestBase import WorldTestBase +from test.bases import WorldTestBase from test.general import gen_steps from worlds import AutoWorld from worlds.AutoWorld import call_all @@ -32,6 +32,5 @@ def world_setup(self, seed: typing.Optional[int] = None) -> None: }) self.multiworld.set_options(args) self.multiworld.plando_options = PlandoOptions.connections - self.multiworld.plando_connections = self.options["plando_connections"] if "plando_connections" in self.options.keys() else [] for step in gen_steps: call_all(self.multiworld, step) diff --git a/worlds/kdl3/test/test_locations.py b/worlds/kdl3/test/test_locations.py index 543f0d83926d..bde9abc409ac 100644 --- a/worlds/kdl3/test/test_locations.py +++ b/worlds/kdl3/test/test_locations.py @@ -1,5 +1,5 @@ from . import KDL3TestBase -from worlds.generic import PlandoConnection +from Options import PlandoConnection from ..Names import LocationName import typing @@ -33,7 +33,8 @@ def test_simple_heart_stars(self): self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"]) self.run_location_test(LocationName.iceberg_samus, ["Ice"]) self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"]) - self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"]) + self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", + "Stone", "Ice"]) def run_location_test(self, location: str, itempool: typing.List[str]): items = itempool.copy() @@ -48,12 +49,10 @@ class TestShiro(KDL3TestBase): options = { "open_world": False, "plando_connections": [ - [], - [ PlandoConnection("Grass Land 1", "Iceberg 5", "both"), PlandoConnection("Grass Land 2", "Ripple Field 5", "both"), PlandoConnection("Grass Land 3", "Grass Land 1", "both") - ]], + ], "stage_shuffle": "shuffled", "plando_options": "connections" } diff --git a/worlds/kh2/Client.py b/worlds/kh2/Client.py index 513d85257b97..e2d2338b7651 100644 --- a/worlds/kh2/Client.py +++ b/worlds/kh2/Client.py @@ -116,12 +116,19 @@ def __init__(self, server_address, password): # self.inBattle = 0x2A0EAC4 + 0x40 # self.onDeath = 0xAB9078 # PC Address anchors - self.Now = 0x0714DB8 - self.Save = 0x09A70B0 + # self.Now = 0x0714DB8 old address + # epic addresses + self.Now = 0x0716DF8 + self.Save = 0x09A92F0 + self.Journal = 0x743260 + self.Shop = 0x743350 + self.Slot1 = 0x2A22FD8 # self.Sys3 = 0x2A59DF0 # self.Bt10 = 0x2A74880 # self.BtlEnd = 0x2A0D3E0 - self.Slot1 = 0x2A20C98 + # self.Slot1 = 0x2A20C98 old address + + self.kh2_game_version = None # can be egs or steam self.chest_set = set(exclusion_table["Chests"]) self.keyblade_set = set(CheckDupingItems["Weapons"]["Keyblades"]) @@ -228,6 +235,9 @@ def kh2_read_int(self, address): def kh2_write_int(self, address, value): self.kh2.write_int(self.kh2.base_address + address, value) + def kh2_read_string(self, address, length): + return self.kh2.read_string(self.kh2.base_address + address, length) + def on_package(self, cmd: str, args: dict): if cmd in {"RoomInfo"}: self.kh2seedname = args['seed_name'] @@ -367,10 +377,26 @@ def on_package(self, cmd: str, args: dict): for weapon_location in all_weapon_slot: all_weapon_location_id.append(self.kh2_loc_name_to_id[weapon_location]) self.all_weapon_location_id = set(all_weapon_location_id) + try: self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") - logger.info("You are now auto-tracking") - self.kh2connected = True + if self.kh2_game_version is None: + if self.kh2_read_string(0x09A9830, 4) == "KH2J": + self.kh2_game_version = "STEAM" + self.Now = 0x0717008 + self.Save = 0x09A9830 + self.Slot1 = 0x2A23518 + self.Journal = 0x7434E0 + self.Shop = 0x7435D0 + + elif self.kh2_read_string(0x09A92F0, 4) == "KH2J": + self.kh2_game_version = "EGS" + else: + self.kh2_game_version = None + logger.info("Your game version is out of date. Please update your game via The Epic Games Store or Steam.") + if self.kh2_game_version is not None: + logger.info(f"You are now auto-tracking. {self.kh2_game_version}") + self.kh2connected = True except Exception as e: if self.kh2connected: @@ -589,8 +615,8 @@ async def IsInShop(self, sellable): # if journal=-1 and shop = 5 then in shop # if journal !=-1 and shop = 10 then journal - journal = self.kh2_read_short(0x741230) - shop = self.kh2_read_short(0x741320) + journal = self.kh2_read_short(self.Journal) + shop = self.kh2_read_short(self.Shop) if (journal == -1 and shop == 5) or (journal != -1 and shop == 10): # print("your in the shop") sellable_dict = {} @@ -599,8 +625,8 @@ async def IsInShop(self, sellable): amount = self.kh2_read_byte(self.Save + itemdata.memaddr) sellable_dict[itemName] = amount while (journal == -1 and shop == 5) or (journal != -1 and shop == 10): - journal = self.kh2_read_short(0x741230) - shop = self.kh2_read_short(0x741320) + journal = self.kh2_read_short(self.Journal) + shop = self.kh2_read_short(self.Shop) await asyncio.sleep(0.5) for item, amount in sellable_dict.items(): itemdata = self.item_name_to_data[item] @@ -750,7 +776,7 @@ async def verifyItems(self): item_data = self.item_name_to_data[item_name] amount_of_items = 0 amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["Magic"][item_name] - if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(0x741320) in {10, 8}: + if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(self.Shop) in {10, 8}: self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items) for item_name in master_stat: @@ -802,7 +828,7 @@ async def verifyItems(self): self.kh2_write_byte(self.Save + 0x2502, current_item_slots + 1) elif self.base_item_slots + amount_of_items < 8: self.kh2_write_byte(self.Save + 0x2502, self.base_item_slots + amount_of_items) - + # if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items \ # and self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and \ # self.kh2_read_byte(self.Save + 0x23DF) & 0x1 << 3 > 0 and self.kh2_read_byte(0x741320) in {10, 8}: @@ -905,8 +931,23 @@ async def kh2_watcher(ctx: KH2Context): await asyncio.sleep(15) ctx.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") if ctx.kh2 is not None: - logger.info("You are now auto-tracking") - ctx.kh2connected = True + if ctx.kh2_game_version is None: + if ctx.kh2_read_string(0x09A9830, 4) == "KH2J": + ctx.kh2_game_version = "STEAM" + ctx.Now = 0x0717008 + ctx.Save = 0x09A9830 + ctx.Slot1 = 0x2A23518 + ctx.Journal = 0x7434E0 + ctx.Shop = 0x7435D0 + + elif ctx.kh2_read_string(0x09A92F0, 4) == "KH2J": + ctx.kh2_game_version = "EGS" + else: + ctx.kh2_game_version = None + logger.info("Your game version is out of date. Please update your game via The Epic Games Store or Steam.") + if ctx.kh2_game_version is not None: + logger.info(f"You are now auto-tracking {ctx.kh2_game_version}") + ctx.kh2connected = True except Exception as e: if ctx.kh2connected: ctx.kh2connected = False diff --git a/worlds/kh2/OpenKH.py b/worlds/kh2/OpenKH.py index 6b0418c9976b..17d7f84e8cfd 100644 --- a/worlds/kh2/OpenKH.py +++ b/worlds/kh2/OpenKH.py @@ -54,29 +54,30 @@ def increaseStat(i): formName = None levelsetting = list() - if self.multiworld.Keyblade_Minimum[self.player].value > self.multiworld.Keyblade_Maximum[self.player].value: + if self.options.Keyblade_Minimum.value > self.options.Keyblade_Maximum.value: logging.info( f"{self.multiworld.get_file_safe_player_name(self.player)} has Keyblade Minimum greater than Keyblade Maximum") - keyblademin = self.multiworld.Keyblade_Maximum[self.player].value - keyblademax = self.multiworld.Keyblade_Minimum[self.player].value + keyblademin = self.options.Keyblade_Maximum.value + keyblademax = self.options.Keyblade_Minimum.value else: - keyblademin = self.multiworld.Keyblade_Minimum[self.player].value - keyblademax = self.multiworld.Keyblade_Maximum[self.player].value + keyblademin = self.options.Keyblade_Minimum.value + keyblademax = self.options.Keyblade_Maximum.value - if self.multiworld.LevelDepth[self.player] == "level_50": + if self.options.LevelDepth == "level_50": levelsetting.extend(exclusion_table["Level50"]) - elif self.multiworld.LevelDepth[self.player] == "level_99": + elif self.options.LevelDepth == "level_99": levelsetting.extend(exclusion_table["Level99"]) - elif self.multiworld.LevelDepth[self.player] != "level_1": + elif self.options.LevelDepth != "level_1": levelsetting.extend(exclusion_table["Level50Sanity"]) - if self.multiworld.LevelDepth[self.player] == "level_99_sanity": + if self.options.LevelDepth == "level_99_sanity": levelsetting.extend(exclusion_table["Level99Sanity"]) mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.get_file_safe_player_name(self.player)}" all_valid_locations = {location for location, data in all_locations.items()} + for location in self.multiworld.get_filled_locations(self.player): if location.name in all_valid_locations: data = all_locations[location.name] @@ -142,11 +143,11 @@ def increaseStat(i): if data.locid == 2: formDict = {1: "Valor", 2: "Wisdom", 3: "Limit", 4: "Master", 5: "Final"} formDictExp = { - 1: self.multiworld.Valor_Form_EXP[self.player].value, - 2: self.multiworld.Wisdom_Form_EXP[self.player].value, - 3: self.multiworld.Limit_Form_EXP[self.player].value, - 4: self.multiworld.Master_Form_EXP[self.player].value, - 5: self.multiworld.Final_Form_EXP[self.player].value + 1: self.options.Valor_Form_EXP.value, + 2: self.options.Wisdom_Form_EXP.value, + 3: self.options.Limit_Form_EXP.value, + 4: self.options.Master_Form_EXP.value, + 5: self.options.Final_Form_EXP.value } formexp = formDictExp[data.charName] formName = formDict[data.charName] @@ -172,7 +173,7 @@ def increaseStat(i): for x in range(1, 7): self.formattedFmlv["Summon"].append({ "Ability": 123, - "Experience": int(formExp[0][x] / self.multiworld.Summon_EXP[self.player].value), + "Experience": int(formExp[0][x] / self.options.Summon_EXP.value), "FormId": 0, "FormLevel": x, "GrowthAbilityLevel": 0, @@ -192,7 +193,7 @@ def increaseStat(i): increaseStat(self.random.randint(0, 3)) itemcode = 0 self.formattedLvup["Sora"][self.i] = { - "Exp": int(soraExp[self.i] / self.multiworld.Sora_Level_EXP[self.player].value), + "Exp": int(soraExp[self.i] / self.options.Sora_Level_EXP.value), "Strength": self.strength, "Magic": self.magic, "Defense": self.defense, @@ -224,7 +225,7 @@ def increaseStat(i): "Unknown": 0 }) self.formattedLvup["Sora"][1] = { - "Exp": int(soraExp[1] / self.multiworld.Sora_Level_EXP[self.player].value), + "Exp": int(soraExp[1] / self.options.Sora_Level_EXP.value), "Strength": 2, "Magic": 6, "Defense": 2, @@ -379,39 +380,41 @@ def increaseStat(i): } lucky_emblem_text = { 0: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.", - 1: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}", + 1: f"Lucky Emblem Required: {self.options.LuckyEmblemsRequired} out of {self.options.LuckyEmblemsAmount}", 2: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.", - 3: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}" + 3: f"Lucky Emblem Required: {self.options.LuckyEmblemsRequired} out of {self.options.LuckyEmblemsAmount}" } hitlist_text = { 0: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs", 1: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs", - 2: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}", - 3: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}", + 2: f"Bounties Required: {self.options.BountyRequired} out of {self.options.BountyAmount}", + 3: f"Bounties Required: {self.options.BountyRequired} out of {self.options.BountyAmount}", } self.pooh_text = [ { 'id': 18326, - 'en': f"Your goal is {goal_to_text[self.multiworld.Goal[self.player].value]}" + 'en': f"Your goal is {goal_to_text[self.options.Goal.value]}" }, { 'id': 18327, - 'en': lucky_emblem_text[self.multiworld.Goal[self.player].value] + 'en': lucky_emblem_text[self.options.Goal.value] }, { 'id': 18328, - 'en': hitlist_text[self.multiworld.Goal[self.player].value] + 'en': hitlist_text[self.options.Goal.value] } ] self.level_depth_text = [ { 'id': 0x3BF1, - 'en': f"Your Level Depth is {self.multiworld.LevelDepth[self.player].current_option_name}" + 'en': f"Your Level Depth is {self.options.LevelDepth.current_option_name}" } ] mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__) + self.mod_yml["title"] = f"Randomizer Seed {mod_name}" + openkhmod = { "TrsrList.yml": yaml.dump(self.formattedTrsr, line_break="\n"), "LvupList.yml": yaml.dump(self.formattedLvup, line_break="\n"), diff --git a/worlds/kh2/Options.py b/worlds/kh2/Options.py index b7caf7437007..ddaf36ebcbf9 100644 --- a/worlds/kh2/Options.py +++ b/worlds/kh2/Options.py @@ -306,16 +306,16 @@ class CorSkipToggle(Toggle): Toggle does not negate fight logic but is an alternative. - Final Chest is also can be put into logic with this skip. + Full Cor Skip is also affected by this Toggle. """ - display_name = "CoR Skip Toggle." + display_name = "CoR Skip Toggle" default = False class CustomItemPoolQuantity(ItemDict): """Add more of an item into the itempool. Note: You cannot take out items from the pool.""" display_name = "Custom Item Pool" - verify_item_name = True + valid_keys = default_itempool_option.keys() default = default_itempool_option diff --git a/worlds/kh2/Regions.py b/worlds/kh2/Regions.py index 235500ec89e4..7fc2ad8a873f 100644 --- a/worlds/kh2/Regions.py +++ b/worlds/kh2/Regions.py @@ -935,7 +935,7 @@ def create_regions(self): for level_region_name in level_region_list: KH2REGIONS[level_region_name] = [] - if multiworld.LevelDepth[player] == "level_50": + if self.options.LevelDepth == "level_50": KH2REGIONS[RegionName.LevelsVS1] = [LocationName.Lvl2, LocationName.Lvl4, LocationName.Lvl7, LocationName.Lvl9, LocationName.Lvl10] KH2REGIONS[RegionName.LevelsVS3] = [LocationName.Lvl12, LocationName.Lvl14, LocationName.Lvl15, @@ -949,7 +949,7 @@ def create_regions(self): KH2REGIONS[RegionName.LevelsVS15] = [LocationName.Lvl50] # level 99 - elif multiworld.LevelDepth[player] == "level_99": + elif self.options.LevelDepth == "level_99": KH2REGIONS[RegionName.LevelsVS1] = [LocationName.Lvl7, LocationName.Lvl9] KH2REGIONS[RegionName.LevelsVS3] = [LocationName.Lvl12, LocationName.Lvl15, LocationName.Lvl17, LocationName.Lvl20] @@ -965,7 +965,7 @@ def create_regions(self): KH2REGIONS[RegionName.LevelsVS26] = [LocationName.Lvl99] # level sanity # has to be [] instead of {} for in - elif multiworld.LevelDepth[player] in ["level_50_sanity", "level_99_sanity"]: + elif self.options.LevelDepth in ["level_50_sanity", "level_99_sanity"]: KH2REGIONS[RegionName.LevelsVS1] = [LocationName.Lvl2, LocationName.Lvl3, LocationName.Lvl4, LocationName.Lvl5, LocationName.Lvl6, LocationName.Lvl7, LocationName.Lvl8, LocationName.Lvl9, LocationName.Lvl10] @@ -986,7 +986,7 @@ def create_regions(self): LocationName.Lvl46, LocationName.Lvl47, LocationName.Lvl48, LocationName.Lvl49, LocationName.Lvl50] # level 99 sanity - if multiworld.LevelDepth[player] == "level_99_sanity": + if self.options.LevelDepth == "level_99_sanity": KH2REGIONS[RegionName.LevelsVS15] = [LocationName.Lvl51, LocationName.Lvl52, LocationName.Lvl53, LocationName.Lvl54, LocationName.Lvl55, LocationName.Lvl56, LocationName.Lvl57, @@ -1012,7 +1012,7 @@ def create_regions(self): LocationName.Lvl95, LocationName.Lvl96, LocationName.Lvl97, LocationName.Lvl98, LocationName.Lvl99] KH2REGIONS[RegionName.Summon] = [] - if multiworld.SummonLevelLocationToggle[player]: + if self.options.SummonLevelLocationToggle: KH2REGIONS[RegionName.Summon] = [LocationName.Summonlvl2, LocationName.Summonlvl3, LocationName.Summonlvl4, diff --git a/worlds/kh2/Rules.py b/worlds/kh2/Rules.py index 1124f8109c54..4370ad36b540 100644 --- a/worlds/kh2/Rules.py +++ b/worlds/kh2/Rules.py @@ -157,7 +157,7 @@ def kh2_has_any(self, items: list, state: CollectionState): def form_list_unlock(self, state: CollectionState, parent_form_list, level_required, fight_logic=False) -> bool: form_access = {parent_form_list} - if self.multiworld.AutoFormLogic[self.player] and state.has(ItemName.SecondChance, self.player) and not fight_logic: + if self.world.options.AutoFormLogic and state.has(ItemName.SecondChance, self.player) and not fight_logic: if parent_form_list == ItemName.MasterForm: if state.has(ItemName.DriveConverter, self.player): form_access.add(auto_form_dict[parent_form_list]) @@ -170,8 +170,8 @@ def get_form_level_requirement(self, state, amount): forms_available = 0 form_list = [ItemName.ValorForm, ItemName.WisdomForm, ItemName.LimitForm, ItemName.MasterForm, ItemName.FinalForm] - if self.world.multiworld.FinalFormLogic[self.player] != "no_light_and_darkness": - if self.world.multiworld.FinalFormLogic[self.player] == "light_and_darkness": + if self.world.options.FinalFormLogic != "no_light_and_darkness": + if self.world.options.FinalFormLogic == "light_and_darkness": if state.has(ItemName.LightDarkness, self.player) and state.has_any(set(form_list), self.player): forms_available += 1 form_list.remove(ItemName.FinalForm) @@ -273,34 +273,35 @@ def set_kh2_rules(self) -> None: def set_kh2_goal(self): final_xemnas_location = self.multiworld.get_location(LocationName.FinalXemnasEventLocation, self.player) - if self.multiworld.Goal[self.player] == "three_proofs": + if self.world.options.Goal == "three_proofs": final_xemnas_location.access_rule = lambda state: self.kh2_has_all(three_proofs, state) - if self.multiworld.FinalXemnas[self.player]: + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: self.multiworld.completion_condition[self.player] = lambda state: self.kh2_has_all(three_proofs, state) # lucky emblem hunt - elif self.multiworld.Goal[self.player] == "lucky_emblem_hunt": - final_xemnas_location.access_rule = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) - if self.multiworld.FinalXemnas[self.player]: + elif self.world.options.Goal == "lucky_emblem_hunt": + final_xemnas_location.access_rule = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: - self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) + self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) # hitlist if == 2 - elif self.multiworld.Goal[self.player] == "hitlist": - final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) - if self.multiworld.FinalXemnas[self.player]: + elif self.world.options.Goal == "hitlist": + final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: - self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) + self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) else: - final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) and \ - state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) - if self.multiworld.FinalXemnas[self.player]: + + final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) and \ + state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: - self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) and \ - state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) + self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) and \ + state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) class KH2FormRules(KH2Rules): @@ -409,7 +410,7 @@ class KH2FightRules(KH2Rules): # if skip rules are of return false def __init__(self, world: KH2World) -> None: super().__init__(world) - self.fight_logic = self.multiworld.FightLogic[self.player].current_key + self.fight_logic = world.options.FightLogic.current_key self.fight_region_rules = { RegionName.ShanYu: lambda state: self.get_shan_yu_rules(state), @@ -935,7 +936,7 @@ def get_cor_first_fight_rules(self, state: CollectionState) -> bool: def get_cor_skip_first_rules(self, state: CollectionState) -> bool: # if option is not allow skips return false else run rules - if not self.multiworld.CorSkipToggle[self.player]: + if not self.world.options.CorSkipToggle: return False # easy: aerial dodge 3,master form,fire # normal: aerial dodge 2, master form,fire diff --git a/worlds/kh2/__init__.py b/worlds/kh2/__init__.py index d02614d3802e..faf0bed88567 100644 --- a/worlds/kh2/__init__.py +++ b/worlds/kh2/__init__.py @@ -240,7 +240,7 @@ def generate_early(self) -> None: self.hitlist_verify() - prio_hitlist = [location for location in self.multiworld.priority_locations[self.player].value if + prio_hitlist = [location for location in self.options.priority_locations.value if location in self.random_super_boss_list] for bounty in range(self.options.BountyAmount.value): if prio_hitlist: @@ -261,11 +261,11 @@ def generate_early(self) -> None: if self.options.WeaponSlotStartHint: for location in all_weapon_slot: - self.multiworld.start_location_hints[self.player].value.add(location) + self.options.start_location_hints.value.add(location) if self.options.FillerItemsLocal: for item in filler_items: - self.multiworld.local_items[self.player].value.add(item) + self.options.local_items.value.add(item) # By imitating remote this doesn't have to be plandoded filler anymore # for location in {LocationName.JunkMedal, LocationName.JunkMedal}: # self.plando_locations[location] = random_stt_item @@ -325,7 +325,7 @@ def donald_gen_early(self): self.item_quantity_dict[random_ability] -= 1 self.total_locations -= 1 self.slot_data_donald_weapon = [item_name.name for item_name in self.donald_weapon_abilities] - if not self.multiworld.DonaldGoofyStatsanity[self.player]: + if not self.options.DonaldGoofyStatsanity: # pre plando donald get bonuses self.donald_get_bonus_abilities += [self.create_item(random_prog_ability)] self.total_locations -= 1 @@ -385,7 +385,7 @@ def goofy_pre_fill(self): location.place_locked_item(random_ability) self.goofy_weapon_abilities.remove(random_ability) - if not self.multiworld.DonaldGoofyStatsanity[self.player]: + if not self.options.DonaldGoofyStatsanity: # plando goofy get bonuses goofy_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in Goofy_Checks.keys() if Goofy_Checks[location].yml != "Keyblade"] @@ -406,7 +406,7 @@ def donald_pre_fill(self): location.place_locked_item(random_ability) self.donald_weapon_abilities.remove(random_ability) - if not self.multiworld.DonaldGoofyStatsanity[self.player]: + if not self.options.DonaldGoofyStatsanity: # plando goofy get bonuses donald_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in Donald_Checks.keys() if Donald_Checks[location].yml != "Keyblade"] @@ -422,21 +422,21 @@ def keyblade_pre_fill(self): keyblade_locations = [self.multiworld.get_location(location, self.player) for location in Keyblade_Slots.keys()] state = self.multiworld.get_all_state(False) keyblade_ability_pool_copy = self.keyblade_ability_pool.copy() - fill_restrictive(self.multiworld, state, keyblade_locations, keyblade_ability_pool_copy, True, True) + fill_restrictive(self.multiworld, state, keyblade_locations, keyblade_ability_pool_copy, True, True, allow_excluded=True) def starting_invo_verify(self): """ Making sure the player doesn't put too many abilities in their starting inventory. """ - for item, value in self.multiworld.start_inventory[self.player].value.items(): + for item, value in self.options.start_inventory.value.items(): if item in ActionAbility_Table \ - or item in SupportAbility_Table or exclusion_item_table["StatUps"] \ + or item in SupportAbility_Table or item in exclusion_item_table["StatUps"] \ or item in DonaldAbility_Table or item in GoofyAbility_Table: # cannot have more than the quantity for abilties if value > item_dictionary_table[item].quantity: logging.info( - f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}" - f"Changing the amount to the max amount") + f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}." + f" Changing the amount to the max amount") value = item_dictionary_table[item].quantity self.item_quantity_dict[item] -= value @@ -461,7 +461,7 @@ def hitlist_verify(self): """ Making sure hitlist have amount>=required. """ - for location in self.multiworld.exclude_locations[self.player].value: + for location in self.options.exclude_locations.value: if location in self.random_super_boss_list: self.random_super_boss_list.remove(location) @@ -491,7 +491,7 @@ def hitlist_verify(self): self.options.BountyAmount.value = temp if self.options.BountyStartingHintToggle: - self.multiworld.start_hints[self.player].value.add(ItemName.Bounty) + self.options.start_hints.value.add(ItemName.Bounty) if ItemName.ProofofNonexistence in self.item_quantity_dict: del self.item_quantity_dict[ItemName.ProofofNonexistence] @@ -503,19 +503,19 @@ def set_excluded_locations(self): # Option to turn off all superbosses. Can do this individually but its like 20+ checks if not self.options.SuperBosses: for superboss in exclusion_table["SuperBosses"]: - self.multiworld.exclude_locations[self.player].value.add(superboss) + self.options.exclude_locations.value.add(superboss) # Option to turn off Olympus Colosseum Cups. if self.options.Cups == "no_cups": for cup in exclusion_table["Cups"]: - self.multiworld.exclude_locations[self.player].value.add(cup) + self.options.exclude_locations.value.add(cup) # exclude only hades paradox. If cups and hades paradox then nothing is excluded elif self.options.Cups == "cups": - self.multiworld.exclude_locations[self.player].value.add(LocationName.HadesCupTrophyParadoxCups) + self.options.exclude_locations.value.add(LocationName.HadesCupTrophyParadoxCups) if not self.options.AtlanticaToggle: for loc in exclusion_table["Atlantica"]: - self.multiworld.exclude_locations[self.player].value.add(loc) + self.options.exclude_locations.value.add(loc) def level_subtraction(self): """ diff --git a/worlds/kh2/docs/en_Kingdom Hearts 2.md b/worlds/kh2/docs/en_Kingdom Hearts 2.md index f08a1fc51fc0..5aae7ad3a70e 100644 --- a/worlds/kh2/docs/en_Kingdom Hearts 2.md +++ b/worlds/kh2/docs/en_Kingdom Hearts 2.md @@ -4,9 +4,9 @@ This randomizer creates a more dynamic play experience by randomizing the locations of most items in Kingdom Hearts 2. Currently all items within Chests, Popups, Get Bonuses, Form Levels, and Sora's Levels are randomized. This allows abilities that Sora would normally have to be placed on Keyblades with random stats. Additionally, there are several options for ways to finish the game, allowing for different goals beyond beating the final boss. -

    Where is the settings page

    +

    Where is the options page

    -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file.

    What is randomized in this game?

    diff --git a/worlds/kh2/docs/setup_en.md b/worlds/kh2/docs/setup_en.md index 96b3b936f338..c6fdb020b8a4 100644 --- a/worlds/kh2/docs/setup_en.md +++ b/worlds/kh2/docs/setup_en.md @@ -2,7 +2,7 @@

    Quick Links

    - [Game Info Page](../../../../games/Kingdom%20Hearts%202/info/en) -- [Player Settings Page](../../../../games/Kingdom%20Hearts%202/player-settings) +- [Player Options Page](../../../../games/Kingdom%20Hearts%202/player-options)

    Required Software:

    `Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) @@ -13,9 +13,10 @@ - Needed for Archipelago 1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases)
    - 2. `Install the mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`
    - 3. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`
    - 4. `AP Randomizer Seed` + 2. `Install the Archipelago Companion mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`
    + 3. `Install the Archipelago Quality Of Life mod from JaredWeakStrike/AP_QOL using OpenKH Mod Manager`
    + 4. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`
    + 5. `AP Randomizer Seed`

    Required: Archipelago Companion Mod

    Load this mod just like the GoA ROM you did during the KH2 Rando setup. `JaredWeakStrike/APCompanion`
    @@ -23,7 +24,7 @@ Have this mod second-highest priority below the .zip seed.
    This mod is based upon Num's Garden of Assemblege Mod and requires it to work. Without Num this could not be possible.

    Required: Auto Save Mod

    -Load this mod just like the GoA ROM you did during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` Location doesn't matter, required in case of crashes. +Load this mod just like the GoA ROM you did during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` Location doesn't matter, required in case of crashes. See [Best Practices](en#best-practices) on how to load the auto save

    Installing A Seed

    @@ -32,7 +33,7 @@ Make sure the seed is on the top of the list (Highest Priority)
    After Installing the seed click `Mod Loader -> Build/Build and Run`. Every slot is a unique mod to install and will be needed be repatched for different slots/rooms.

    What the Mod Manager Should Look Like.

    -![image](https://i.imgur.com/QgRfjP1.png) +![image](https://i.imgur.com/Si4oZ8w.png)

    Using the KH2 Client

    @@ -60,7 +61,7 @@ Enter `The room's port number` into the top box where the x's are and pr - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps. -

    Best Practices

    +

    Best Practices

    - Make a save at the start of the GoA before opening anything. This will be the file to select when loading an autosave if/when your game crashes. - If you don't want to have a save in the GoA. Disconnect the client, load the auto save, and then reconnect the client after it loads the auto save. @@ -71,7 +72,8 @@ Enter `The room's port number` into the top box where the x's are and pr

    Logic Sheet

    Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing)

    F.A.Q.

    - +- Why is my Client giving me a "Cannot Open Process: " error? + - Due to how the client reads kingdom hearts 2 memory some people's computer flags it as a virus. Run the client as admin. - Why is my HP/MP continuously increasing without stopping? - You do not have `JaredWeakStrike/APCompanion` set up correctly. Make sure it is above the `GoA ROM Mod` in the mod manager. - Why is my HP/MP continuously increasing without stopping when I have the APCompanion Mod? diff --git a/worlds/ladx/LADXR/assembler.py b/worlds/ladx/LADXR/assembler.py index 6c35fac4b3a8..c95d4dd9912c 100644 --- a/worlds/ladx/LADXR/assembler.py +++ b/worlds/ladx/LADXR/assembler.py @@ -757,7 +757,7 @@ def getLabels(self) -> ItemsView[str, int]: def const(name: str, value: int) -> None: name = name.upper() - assert name not in CONST_MAP + assert name not in CONST_MAP or CONST_MAP[name] == value CONST_MAP[name] = value diff --git a/worlds/ladx/LADXR/generator.py b/worlds/ladx/LADXR/generator.py index 0406ad51f890..e6f608a92180 100644 --- a/worlds/ladx/LADXR/generator.py +++ b/worlds/ladx/LADXR/generator.py @@ -4,6 +4,7 @@ import os import pkgutil from collections import defaultdict +from typing import TYPE_CHECKING from .romTables import ROMWithTables from . import assembler @@ -65,12 +66,16 @@ from BaseClasses import ItemClassification from ..Locations import LinksAwakeningLocation -from ..Options import TrendyGame, Palette, MusicChangeCondition +from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls + +if TYPE_CHECKING: + from .. import LinksAwakeningWorld # Function to generate a final rom, this patches the rom with all required patches -def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, multiworld=None, player_name=None, player_names=[], player_id = 0): +def generateRom(args, world: "LinksAwakeningWorld"): rom_patches = [] + player_names = list(world.multiworld.player_name.values()) rom = ROMWithTables(args.input_filename, rom_patches) rom.player_names = player_names @@ -84,10 +89,10 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m for pymod in pymods: pymod.prePatch(rom) - if settings.gfxmod: - patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", settings.gfxmod)) + if world.ladxr_settings.gfxmod: + patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod)) - item_list = [item for item in logic.iteminfo_list if not isinstance(item, KeyLocation)] + item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)] assembler.resetConsts() assembler.const("INV_SIZE", 16) @@ -97,7 +102,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m assembler.const("wTradeSequenceItem2", 0xDB7F) # Normally used to store that we have exchanged the trade item, we use it to store flags of which trade items we have assembler.const("wSeashellsCount", 0xDB41) assembler.const("wGoldenLeaves", 0xDB42) # New memory location where to store the golden leaf counter - assembler.const("wCollectedTunics", 0xDB6D) # Memory location where to store which tunic options are available + assembler.const("wCollectedTunics", 0xDB6D) # Memory location where to store which tunic options are available (and boots) assembler.const("wCustomMessage", 0xC0A0) # We store the link info in unused color dungeon flags, so it gets preserved in the savegame. @@ -116,7 +121,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m assembler.const("wLinkSpawnDelay", 0xDE13) #assembler.const("HARDWARE_LINK", 1) - assembler.const("HARD_MODE", 1 if settings.hardmode != "none" else 0) + assembler.const("HARD_MODE", 1 if world.ladxr_settings.hardmode != "none" else 0) patches.core.cleanup(rom) patches.save.singleSaveSlot(rom) @@ -130,7 +135,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m patches.core.easyColorDungeonAccess(rom) patches.owl.removeOwlEvents(rom) patches.enemies.fixArmosKnightAsMiniboss(rom) - patches.bank3e.addBank3E(rom, auth, player_id, player_names) + patches.bank3e.addBank3E(rom, world.multi_key, world.player, player_names) patches.bank3f.addBank3F(rom) patches.bank34.addBank34(rom, item_list) patches.core.removeGhost(rom) @@ -141,10 +146,11 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys - if ap_settings["shuffle_small_keys"] != ShuffleSmallKeys.option_original_dungeon or ap_settings["shuffle_nightmare_keys"] != ShuffleNightmareKeys.option_original_dungeon: + if world.options.shuffle_small_keys != ShuffleSmallKeys.option_original_dungeon or\ + world.options.shuffle_nightmare_keys != ShuffleNightmareKeys.option_original_dungeon: patches.inventory.advancedInventorySubscreen(rom) patches.inventory.moreSlots(rom) - if settings.witch: + if world.ladxr_settings.witch: patches.witch.updateWitch(rom) patches.softlock.fixAll(rom) patches.maptweaks.tweakMap(rom) @@ -158,9 +164,9 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m patches.tarin.updateTarin(rom) patches.fishingMinigame.updateFinishingMinigame(rom) patches.health.upgradeHealthContainers(rom) - if settings.owlstatues in ("dungeon", "both"): + if world.ladxr_settings.owlstatues in ("dungeon", "both"): patches.owl.upgradeDungeonOwlStatues(rom) - if settings.owlstatues in ("overworld", "both"): + if world.ladxr_settings.owlstatues in ("overworld", "both"): patches.owl.upgradeOverworldOwlStatues(rom) patches.goldenLeaf.fixGoldenLeaf(rom) patches.heartPiece.fixHeartPiece(rom) @@ -170,103 +176,110 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m patches.songs.upgradeMarin(rom) patches.songs.upgradeManbo(rom) patches.songs.upgradeMamu(rom) - if settings.tradequest: - patches.tradeSequence.patchTradeSequence(rom, settings.boomerang) + if world.ladxr_settings.tradequest: + patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings.boomerang) else: # Monkey bridge patch, always have the bridge there. rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True) - patches.bowwow.fixBowwow(rom, everywhere=settings.bowwow != 'normal') - if settings.bowwow != 'normal': + patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal') + if world.ladxr_settings.bowwow != 'normal': patches.bowwow.bowwowMapPatches(rom) patches.desert.desertAccess(rom) - if settings.overworld == 'dungeondive': + if world.ladxr_settings.overworld == 'dungeondive': patches.overworld.patchOverworldTilesets(rom) patches.overworld.createDungeonOnlyOverworld(rom) - elif settings.overworld == 'nodungeons': + elif world.ladxr_settings.overworld == 'nodungeons': patches.dungeon.patchNoDungeons(rom) - elif settings.overworld == 'random': + elif world.ladxr_settings.overworld == 'random': patches.overworld.patchOverworldTilesets(rom) - mapgen.store_map(rom, logic.world.map) + mapgen.store_map(rom, world.ladxr_logic.world.map) #if settings.dungeon_items == 'keysy': # patches.dungeon.removeKeyDoors(rom) # patches.reduceRNG.slowdownThreeOfAKind(rom) patches.reduceRNG.fixHorseHeads(rom) patches.bomb.onlyDropBombsWhenHaveBombs(rom) - if ap_settings['music_change_condition'] == MusicChangeCondition.option_always: + if world.options.music_change_condition == MusicChangeCondition.option_always: patches.aesthetics.noSwordMusic(rom) - patches.aesthetics.reduceMessageLengths(rom, rnd) + patches.aesthetics.reduceMessageLengths(rom, world.random) patches.aesthetics.allowColorDungeonSpritesEverywhere(rom) - if settings.music == 'random': - patches.music.randomizeMusic(rom, rnd) - elif settings.music == 'off': + if world.ladxr_settings.music == 'random': + patches.music.randomizeMusic(rom, world.random) + elif world.ladxr_settings.music == 'off': patches.music.noMusic(rom) - if settings.noflash: + if world.ladxr_settings.noflash: patches.aesthetics.removeFlashingLights(rom) - if settings.hardmode == "oracle": + if world.ladxr_settings.hardmode == "oracle": patches.hardMode.oracleMode(rom) - elif settings.hardmode == "hero": + elif world.ladxr_settings.hardmode == "hero": patches.hardMode.heroMode(rom) - elif settings.hardmode == "ohko": + elif world.ladxr_settings.hardmode == "ohko": patches.hardMode.oneHitKO(rom) - if settings.superweapons: + if world.ladxr_settings.superweapons: patches.weapons.patchSuperWeapons(rom) - if settings.textmode == 'fast': + if world.ladxr_settings.textmode == 'fast': patches.aesthetics.fastText(rom) - if settings.textmode == 'none': + if world.ladxr_settings.textmode == 'none': patches.aesthetics.fastText(rom) patches.aesthetics.noText(rom) - if not settings.nagmessages: + if not world.ladxr_settings.nagmessages: patches.aesthetics.removeNagMessages(rom) - if settings.lowhpbeep == 'slow': + if world.ladxr_settings.lowhpbeep == 'slow': patches.aesthetics.slowLowHPBeep(rom) - if settings.lowhpbeep == 'none': + if world.ladxr_settings.lowhpbeep == 'none': patches.aesthetics.removeLowHPBeep(rom) - if 0 <= int(settings.linkspalette): - patches.aesthetics.forceLinksPalette(rom, int(settings.linkspalette)) + if 0 <= int(world.ladxr_settings.linkspalette): + patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette)) if args.romdebugmode: # The default rom has this build in, just need to set a flag and we get this save. rom.patch(0, 0x0003, "00", "01") # Patch the sword check on the shopkeeper turning around. - if settings.steal == 'never': + if world.ladxr_settings.steal == 'never': rom.patch(4, 0x36F9, "FA4EDB", "3E0000") - elif settings.steal == 'always': + elif world.ladxr_settings.steal == 'always': rom.patch(4, 0x36F9, "FA4EDB", "3E0100") - if settings.hpmode == 'inverted': + if world.ladxr_settings.hpmode == 'inverted': patches.health.setStartHealth(rom, 9) - elif settings.hpmode == '1': + elif world.ladxr_settings.hpmode == '1': patches.health.setStartHealth(rom, 1) patches.inventory.songSelectAfterOcarinaSelect(rom) - if settings.quickswap == 'a': + if world.ladxr_settings.quickswap == 'a': patches.core.quickswap(rom, 1) - elif settings.quickswap == 'b': + elif world.ladxr_settings.quickswap == 'b': patches.core.quickswap(rom, 0) + + patches.core.addBootsControls(rom, world.options.boots_controls) + - world_setup = logic.world_setup + world_setup = world.ladxr_logic.world_setup JUNK_HINT = 0.33 RANDOM_HINT= 0.66 # USEFUL_HINT = 1.0 # TODO: filter events, filter unshuffled keys - all_items = multiworld.get_items() - our_items = [item for item in all_items if item.player == player_id and item.location and item.code is not None and item.location.show_in_spoiler] + all_items = world.multiworld.get_items() + our_items = [item for item in all_items + if item.player == world.player + and item.location + and item.code is not None + and item.location.show_in_spoiler] our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification] def gen_hint(): - chance = rnd.uniform(0, 1) + chance = world.random.uniform(0, 1) if chance < JUNK_HINT: return None elif chance < RANDOM_HINT: - location = rnd.choice(our_items).location + location = world.random.choice(our_items).location else: # USEFUL_HINT - location = rnd.choice(our_useful_items).location + location = world.random.choice(our_useful_items).location - if location.item.player == player_id: + if location.item.player == world.player: name = "Your" else: - name = f"{multiworld.player_name[location.item.player]}'s" + name = f"{world.multiworld.player_name[location.item.player]}'s" if isinstance(location, LinksAwakeningLocation): location_name = location.ladxr_item.metadata.name @@ -274,8 +287,8 @@ def gen_hint(): location_name = location.name hint = f"{name} {location.item} is at {location_name}" - if location.player != player_id: - hint += f" in {multiworld.player_name[location.player]}'s world" + if location.player != world.player: + hint += f" in {world.multiworld.player_name[location.player]}'s world" # Cap hint size at 85 # Realistically we could go bigger but let's be safe instead @@ -283,7 +296,7 @@ def gen_hint(): return hint - hints.addHints(rom, rnd, gen_hint) + hints.addHints(rom, world.random, gen_hint) if world_setup.goal == "raft": patches.goal.setRaftGoal(rom) @@ -296,7 +309,7 @@ def gen_hint(): # Patch the generated logic into the rom patches.chest.setMultiChest(rom, world_setup.multichest) - if settings.overworld not in {"dungeondive", "random"}: + if world.ladxr_settings.overworld not in {"dungeondive", "random"}: patches.entrances.changeEntrances(rom, world_setup.entrance_mapping) for spot in item_list: if spot.item and spot.item.startswith("*"): @@ -315,15 +328,16 @@ def gen_hint(): patches.core.addFrameCounter(rom, len(item_list)) patches.core.warpHome(rom) # Needs to be done after setting the start location. - patches.titleScreen.setRomInfo(rom, auth, seed_name, settings, player_name, player_id) - if ap_settings["ap_title_screen"]: + patches.titleScreen.setRomInfo(rom, world.multi_key, world.multiworld.seed_name, world.ladxr_settings, + world.player_name, world.player) + if world.options.ap_title_screen: patches.titleScreen.setTitleGraphics(rom) patches.endscreen.updateEndScreen(rom) patches.aesthetics.updateSpriteData(rom) if args.doubletrouble: patches.enemies.doubleTrouble(rom) - if ap_settings["text_shuffle"]: + if world.options.text_shuffle: buckets = defaultdict(list) # For each ROM bank, shuffle text within the bank for n, data in enumerate(rom.texts._PointerTable__data): @@ -333,20 +347,20 @@ def gen_hint(): for bucket in buckets.values(): # For each bucket, make a copy and shuffle shuffled = bucket.copy() - rnd.shuffle(shuffled) + world.random.shuffle(shuffled) # Then put new text in for bucket_idx, (orig_idx, data) in enumerate(bucket): rom.texts[shuffled[bucket_idx][0]] = data - if ap_settings["trendy_game"] != TrendyGame.option_normal: + if world.options.trendy_game != TrendyGame.option_normal: # TODO: if 0 or 4, 5, remove inaccurate conveyor tiles room_editor = RoomEditor(rom, 0x2A0) - if ap_settings["trendy_game"] == TrendyGame.option_easy: + if world.options.trendy_game == TrendyGame.option_easy: # Set physics flag on all objects for i in range(0, 6): rom.banks[0x4][0x6F1E + i -0x4000] = 0x4 @@ -357,7 +371,7 @@ def gen_hint(): # Add new conveyor to "push" yoshi (it's only a visual) room_editor.objects.append(Object(5, 3, 0xD0)) - if int(ap_settings["trendy_game"]) >= TrendyGame.option_harder: + if world.options.trendy_game >= TrendyGame.option_harder: """ Data_004_76A0:: db $FC, $00, $04, $00, $00 @@ -371,12 +385,12 @@ def gen_hint(): TrendyGame.option_impossible: (3, 16), } def speed(): - return rnd.randint(*speeds[ap_settings["trendy_game"]]) + return world.random.randint(*speeds[world.options.trendy_game]) rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed() rom.banks[0x4][0x76A2-0x4000] = speed() rom.banks[0x4][0x76A6-0x4000] = speed() rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed() - if int(ap_settings["trendy_game"]) >= TrendyGame.option_hardest: + if world.options.trendy_game >= TrendyGame.option_hardest: rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed() rom.banks[0x4][0x76A3-0x4000] = speed() rom.banks[0x4][0x76A5-0x4000] = speed() @@ -400,10 +414,10 @@ def speed(): for channel in range(3): color[channel] = color[channel] * 31 // 0xbc - if ap_settings["warp_improvements"]: - patches.core.addWarpImprovements(rom, ap_settings["additional_warp_points"]) + if world.options.warp_improvements: + patches.core.addWarpImprovements(rom, world.options.additional_warp_points) - palette = ap_settings["palette"] + palette = world.options.palette if palette != Palette.option_normal: ranges = { # Object palettes @@ -469,8 +483,8 @@ def clamp(x, min, max): SEED_LOCATION = 0x0134 # Patch over the title - assert(len(auth) == 12) - rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(auth)) + assert(len(world.multi_key) == 12) + rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key)) for pymod in pymods: pymod.postPatch(rom) diff --git a/worlds/ladx/LADXR/locations/droppedKey.py b/worlds/ladx/LADXR/locations/droppedKey.py index baa093bb3892..d7998633ce65 100644 --- a/worlds/ladx/LADXR/locations/droppedKey.py +++ b/worlds/ladx/LADXR/locations/droppedKey.py @@ -19,7 +19,7 @@ def __init__(self, room=None): extra = 0x01F8 super().__init__(room, extra) def patch(self, rom, option, *, multiworld=None): - if (option.startswith(MAP) and option != MAP) or (option.startswith(COMPASS) and option != COMPASS) or option.startswith(STONE_BEAK) or (option.startswith(NIGHTMARE_KEY) and option != NIGHTMARE_KEY )or (option.startswith(KEY) and option != KEY): + if (option.startswith(MAP) and option != MAP) or (option.startswith(COMPASS) and option != COMPASS) or (option.startswith(STONE_BEAK) and option != STONE_BEAK) or (option.startswith(NIGHTMARE_KEY) and option != NIGHTMARE_KEY) or (option.startswith(KEY) and option != KEY): if option[-1] == 'P': print(option) if self._location.dungeon == int(option[-1]) and multiworld is None and self.room not in {0x166, 0x223}: diff --git a/worlds/ladx/LADXR/locations/startItem.py b/worlds/ladx/LADXR/locations/startItem.py index 95dd6ba54abd..0421c1d6d865 100644 --- a/worlds/ladx/LADXR/locations/startItem.py +++ b/worlds/ladx/LADXR/locations/startItem.py @@ -10,7 +10,6 @@ class StartItem(DroppedKey): # We need to give something here that we can use to progress. # FEATHER OPTIONS = [SWORD, SHIELD, POWER_BRACELET, OCARINA, BOOMERANG, MAGIC_ROD, TAIL_KEY, SHOVEL, HOOKSHOT, PEGASUS_BOOTS, MAGIC_POWDER, BOMB] - MULTIWORLD = False def __init__(self): diff --git a/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm b/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm index b19e879dc30e..57771c17b3ca 100644 --- a/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm +++ b/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm @@ -51,7 +51,7 @@ GiveItemFromChest: dw ChestBow ; CHEST_BOW dw ChestWithItem ; CHEST_HOOKSHOT dw ChestWithItem ; CHEST_MAGIC_ROD - dw ChestWithItem ; CHEST_PEGASUS_BOOTS + dw Boots ; CHEST_PEGASUS_BOOTS dw ChestWithItem ; CHEST_OCARINA dw ChestWithItem ; CHEST_FEATHER dw ChestWithItem ; CHEST_SHOVEL @@ -273,6 +273,13 @@ ChestMagicPowder: ld [$DB4C], a jp ChestWithItem +Boots: + ; We use DB6D to store which tunics we have available + ; ...and the boots + ld a, [wCollectedTunics] + or $04 + ld [wCollectedTunics], a + jp ChestWithItem Flippers: ld a, $01 diff --git a/worlds/ladx/LADXR/patches/core.py b/worlds/ladx/LADXR/patches/core.py index c9f3a7c34b60..f4752c82e3da 100644 --- a/worlds/ladx/LADXR/patches/core.py +++ b/worlds/ladx/LADXR/patches/core.py @@ -1,9 +1,11 @@ +from .. import assembler from ..assembler import ASM from ..entranceInfo import ENTRANCE_INFO from ..roomEditor import RoomEditor, ObjectWarp, ObjectHorizontal from ..backgroundEditor import BackgroundEditor from .. import utils +from ...Options import BootsControls def bugfixWrittingWrongRoomStatus(rom): # The normal rom contains a pretty nasty bug where door closing triggers in D7/D8 can effect doors in @@ -391,7 +393,7 @@ def addFrameCounter(rom, check_count): db $20, $20, $20, $00 ;I db $20, $28, $28, $00 ;M db $20, $30, $18, $00 ;E - + db $20, $70, $16, $00 ;D db $20, $78, $18, $00 ;E db $20, $80, $10, $00 ;A @@ -408,7 +410,7 @@ def addFrameCounter(rom, check_count): db $68, $38, $%02x, $00 ;0 db $68, $40, $%02x, $00 ;0 db $68, $48, $%02x, $00 ;0 - + """ % ((((check_count // 100) % 10) * 2) | 0x40, (((check_count // 10) % 10) * 2) | 0x40, ((check_count % 10) * 2) | 0x40), 0x469D), fill_nop=True) # Lower line of credits roll into XX XX XX rom.patch(0x17, 0x0784, 0x082D, ASM(""" @@ -425,7 +427,7 @@ def addFrameCounter(rom, check_count): call updateOAM ld a, [$B001] ; seconds call updateOAM - + ld a, [$DB58] ; death count high call updateOAM ld a, [$DB57] ; death count low @@ -473,7 +475,7 @@ def addFrameCounter(rom, check_count): db $68, $18, $40, $00 ;0 db $68, $20, $40, $00 ;0 db $68, $28, $40, $00 ;0 - + """, 0x4784), fill_nop=True) # Grab the "mostly" complete A-Z font @@ -539,6 +541,97 @@ def addFrameCounter(rom, check_count): rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high) rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low) +def addBootsControls(rom, boots_controls: BootsControls): + if boots_controls == BootsControls.option_vanilla: + return + consts = { + "INVENTORY_PEGASUS_BOOTS": 0x8, + "INVENTORY_POWER_BRACELET": 0x3, + "UsePegasusBoots": 0x1705, + "J_A": (1 << 4), + "J_B": (1 << 5), + "wAButtonSlot": 0xDB01, + "wBButtonSlot": 0xDB00, + "wPegasusBootsChargeMeter": 0xC14B, + "hPressedButtonsMask": 0xCB + } + for c,v in consts.items(): + assembler.const(c, v) + + BOOTS_START_ADDR = 0x11E8 + condition = { + BootsControls.option_bracelet: """ + ld a, [hl] + ; Check if we are using the bracelet + cp INVENTORY_POWER_BRACELET + jr z, .yesBoots + """, + BootsControls.option_press_a: """ + ; Check if we are using the A slot + cp J_A + jr z, .yesBoots + ld a, [hl] + """, + BootsControls.option_press_b: """ + ; Check if we are using the B slot + cp J_B + jr z, .yesBoots + ld a, [hl] + """ + }[boots_controls.value] + + # The new code fits exactly within Nintendo's poorly space optimzied code while having more features + boots_code = assembler.ASM(""" +CheckBoots: + ; check if we own boots + ld a, [wCollectedTunics] + and $04 + ; if not, move on to the next inventory item (shield) + jr z, .out + + ; Check the B button + ld hl, wBButtonSlot + ld d, J_B + call .maybeBoots + + ; Check the A button + inc l ; l = wAButtonSlot - done this way to save a byte or two + ld d, J_A + call .maybeBoots + + ; If neither, reset charge meter and bail + xor a + ld [wPegasusBootsChargeMeter], a + jr .out + +.maybeBoots: + ; Check if we are holding this button even + ldh a, [hPressedButtonsMask] + and d + ret z + """ + # Check the special condition (also loads the current item for button into a) + + condition + + """ + ; Check if we are just using boots regularly + cp INVENTORY_PEGASUS_BOOTS + ret nz +.yesBoots: + ; We're using boots! Do so. + call UsePegasusBoots + ; If we return now we will go back into CheckBoots, we don't want that + ; We instead want to move onto the next item + ; but if we don't cleanup, the next "ret" will take us back there again + ; So we pop the return address off of the stack + pop af +.out: + """, BOOTS_START_ADDR) + + + + original_code = 'fa00dbfe08200ff0cbe6202805cd05171804afea4bc1fa01dbfe08200ff0cbe6102805cd05171804afea4bc1' + rom.patch(0, BOOTS_START_ADDR, original_code, boots_code, fill_nop=True) + def addWarpImprovements(rom, extra_warps): # Patch in a warp icon tile = utils.createTileData( \ @@ -739,4 +832,3 @@ def addWarpImprovements(rom, extra_warps): exit: ret """)) - diff --git a/worlds/ladx/Locations.py b/worlds/ladx/Locations.py index c7b127ef2b54..f29355f2ba86 100644 --- a/worlds/ladx/Locations.py +++ b/worlds/ladx/Locations.py @@ -60,13 +60,11 @@ class LinksAwakeningLocation(Location): def __init__(self, player: int, region, ladxr_item): name = meta_to_name(ladxr_item.metadata) + address = None - self.event = ladxr_item.event is not None - if self.event: + if ladxr_item.event is not None: name = ladxr_item.event - - address = None - if not self.event: + else: address = locations_to_id[name] super().__init__(player, name, address) self.parent_region = region diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index ec4570640788..c5dcc080537c 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -1,7 +1,9 @@ +from dataclasses import dataclass + import os.path import typing import logging -from Options import Choice, Option, Toggle, DefaultOnToggle, Range, FreeText +from Options import Choice, Toggle, DefaultOnToggle, Range, FreeText, PerGameCommonOptions, OptionGroup from collections import defaultdict import Utils @@ -14,7 +16,7 @@ class LADXROption: def to_ladxr_option(self, all_options): if not self.ladxr_name: return None, None - + return (self.ladxr_name, self.name_lookup[self.value].replace("_", "")) @@ -32,9 +34,10 @@ class Logic(Choice, LADXROption): option_hard = 2 option_glitched = 3 option_hell = 4 - + default = option_normal + class TradeQuest(DefaultOffToggle, LADXROption): """ [On] adds the trade items to the pool (the trade locations will always be local items) @@ -43,11 +46,14 @@ class TradeQuest(DefaultOffToggle, LADXROption): display_name = "Trade Quest" ladxr_name = "tradequest" + class TextShuffle(DefaultOffToggle): """ [On] Shuffles all the text in the game [Off] (default) doesn't shuffle them. """ + display_name = "Text Shuffle" + class Rooster(DefaultOnToggle, LADXROption): """ @@ -57,16 +63,19 @@ class Rooster(DefaultOnToggle, LADXROption): display_name = "Rooster" ladxr_name = "rooster" + class Boomerang(Choice): """ [Normal] requires Magnifying Lens to get the boomerang. [Gift] The boomerang salesman will give you a random item, and the boomerang is shuffled. """ - + display_name = "Boomerang" + normal = 0 gift = 1 default = gift + class EntranceShuffle(Choice, LADXROption): """ [WARNING] Experimental, may fail to fill @@ -75,19 +84,20 @@ class EntranceShuffle(Choice, LADXROption): If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool. Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this.""" - #[Advanced] Simple, but two-way connector caves are shuffled in their own pool as well. - #[Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool. - #[Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool. + # [Advanced] Simple, but two-way connector caves are shuffled in their own pool as well. + # [Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool. + # [Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool. option_none = 0 option_simple = 1 - #option_advanced = 2 - #option_expert = 3 - #option_insanity = 4 + # option_advanced = 2 + # option_expert = 3 + # option_insanity = 4 default = option_none display_name = "Experimental Entrance Shuffle" ladxr_name = "entranceshuffle" + class DungeonShuffle(DefaultOffToggle, LADXROption): """ [WARNING] Experimental, may fail to fill @@ -96,13 +106,16 @@ class DungeonShuffle(DefaultOffToggle, LADXROption): display_name = "Experimental Dungeon Shuffle" ladxr_name = "dungeonshuffle" + class APTitleScreen(DefaultOnToggle): """ Enables AP specific title screen and disables the intro cutscene """ display_name = "AP Title Screen" + class BossShuffle(Choice): + display_name = "Boss Shuffle" none = 0 shuffle = 1 random = 2 @@ -110,15 +123,18 @@ class BossShuffle(Choice): class DungeonItemShuffle(Choice): + display_name = "Dungeon Item Shuffle" option_original_dungeon = 0 option_own_dungeons = 1 option_own_world = 2 option_any_world = 3 option_different_world = 4 - #option_delete = 5 - #option_start_with = 6 + # option_delete = 5 + # option_start_with = 6 alias_true = 3 alias_false = 0 + ladxr_item: str + class ShuffleNightmareKeys(DungeonItemShuffle): """ @@ -132,6 +148,7 @@ class ShuffleNightmareKeys(DungeonItemShuffle): display_name = "Shuffle Nightmare Keys" ladxr_item = "NIGHTMARE_KEY" + class ShuffleSmallKeys(DungeonItemShuffle): """ Shuffle Small Keys @@ -143,6 +160,8 @@ class ShuffleSmallKeys(DungeonItemShuffle): """ display_name = "Shuffle Small Keys" ladxr_item = "KEY" + + class ShuffleMaps(DungeonItemShuffle): """ Shuffle Dungeon Maps @@ -155,6 +174,7 @@ class ShuffleMaps(DungeonItemShuffle): display_name = "Shuffle Maps" ladxr_item = "MAP" + class ShuffleCompasses(DungeonItemShuffle): """ Shuffle Dungeon Compasses @@ -167,6 +187,7 @@ class ShuffleCompasses(DungeonItemShuffle): display_name = "Shuffle Compasses" ladxr_item = "COMPASS" + class ShuffleStoneBeaks(DungeonItemShuffle): """ Shuffle Owl Beaks @@ -179,6 +200,7 @@ class ShuffleStoneBeaks(DungeonItemShuffle): display_name = "Shuffle Stone Beaks" ladxr_item = "STONE_BEAK" + class ShuffleInstruments(DungeonItemShuffle): """ Shuffle Instruments @@ -195,6 +217,7 @@ class ShuffleInstruments(DungeonItemShuffle): option_vanilla = 100 alias_false = 100 + class Goal(Choice, LADXROption): """ The Goal of the game @@ -207,7 +230,7 @@ class Goal(Choice, LADXROption): option_instruments = 1 option_seashells = 2 option_open = 3 - + default = option_instruments def to_ladxr_option(self, all_options): @@ -216,6 +239,7 @@ def to_ladxr_option(self, all_options): else: return LADXROption.to_ladxr_option(self, all_options) + class InstrumentCount(Range, LADXROption): """ Sets the number of instruments required to open the Egg @@ -226,6 +250,7 @@ class InstrumentCount(Range, LADXROption): range_end = 8 default = 8 + class NagMessages(DefaultOffToggle, LADXROption): """ Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else. @@ -233,6 +258,7 @@ class NagMessages(DefaultOffToggle, LADXROption): display_name = "Nag Messages" ladxr_name = "nagmessages" + class MusicChangeCondition(Choice): """ Controls how the music changes. @@ -243,6 +269,8 @@ class MusicChangeCondition(Choice): option_sword = 0 option_always = 1 default = option_always + + # Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default', # description=""" # [Normal} health works as you would expect. @@ -267,10 +295,12 @@ class Bowwow(Choice): [Normal] BowWow is in the item pool, but can be logically expected as a damage source. [Swordless] The progressive swords are removed from the item pool. """ + display_name = "BowWow" normal = 0 swordless = 1 default = normal + class Overworld(Choice, LADXROption): """ [Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld. @@ -284,9 +314,10 @@ class Overworld(Choice, LADXROption): # option_shuffled = 3 default = option_normal -#Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False, + +# Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False, # description='All items will be more powerful, faster, harder, bigger stronger. You name it.'), -#Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none', +# Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none', # description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.', # aesthetic=True), # Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast', @@ -316,6 +347,21 @@ class Overworld(Choice, LADXROption): # [Disable] no music in the whole game""", # aesthetic=True), +class BootsControls(Choice): + """ + Adds additional button to activate Pegasus Boots (does nothing if you haven't picked up your boots!) + [Vanilla] Nothing changes, you have to equip the boots to use them + [Bracelet] Holding down the button for the bracelet also activates boots (somewhat like Link to the Past) + [Press A] Holding down A activates boots + [Press B] Holding down B activates boots + """ + display_name = "Boots Controls" + option_vanilla = 0 + option_bracelet = 1 + option_press_a = 2 + option_press_b = 3 + + class LinkPalette(Choice, LADXROption): """ Sets link's palette @@ -337,6 +383,7 @@ class LinkPalette(Choice, LADXROption): def to_ladxr_option(self, all_options): return self.ladxr_name, str(self.value) + class TrendyGame(Choice): """ [Easy] All of the items hold still for you @@ -355,6 +402,7 @@ class TrendyGame(Choice): option_impossible = 5 default = option_normal + class GfxMod(FreeText, LADXROption): """ Sets the sprite for link, among other things @@ -365,7 +413,7 @@ class GfxMod(FreeText, LADXROption): normal = '' default = 'Link' - __spriteDir: str = Utils.local_path(os.path.join('data', 'sprites','ladx')) + __spriteDir: str = Utils.local_path(os.path.join('data', 'sprites', 'ladx')) __spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list) extensions = [".bin", ".bdiff", ".png", ".bmp"] @@ -374,16 +422,15 @@ class GfxMod(FreeText, LADXROption): name, extension = os.path.splitext(file) if extension in extensions: __spriteFiles[name].append(file) - + def __init__(self, value: str): super().__init__(value) - def verify(self, world, player_name: str, plando_options) -> None: if self.value == "Link" or self.value in GfxMod.__spriteFiles: return - raise Exception(f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}") - + raise Exception( + f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}") def to_ladxr_option(self, all_options): if self.value == -1 or self.value == "Link": @@ -392,10 +439,12 @@ def to_ladxr_option(self, all_options): assert self.value in GfxMod.__spriteFiles if len(GfxMod.__spriteFiles[self.value]) > 1: - logger.warning(f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}") + logger.warning( + f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}") return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0] + class Palette(Choice): """ Sets the palette for the game. @@ -415,18 +464,19 @@ class Palette(Choice): option_pink = 4 option_inverted = 5 + class Music(Choice, LADXROption): """ [Vanilla] Regular Music [Shuffled] Shuffled Music [Off] No music """ + display_name = "Music" ladxr_name = "music" option_vanilla = 0 option_shuffled = 1 option_off = 2 - def to_ladxr_option(self, all_options): s = "" if self.value == self.option_shuffled: @@ -435,55 +485,97 @@ def to_ladxr_option(self, all_options): s = "off" return self.ladxr_name, s + class WarpImprovements(DefaultOffToggle): """ [On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select. [Off] No change """ + display_name = "Warp Improvements" + class AdditionalWarpPoints(DefaultOffToggle): """ [On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower [Off] No change """ - - -links_awakening_options: typing.Dict[str, typing.Type[Option]] = { - 'logic': Logic, + display_name = "Additional Warp Points" + +ladx_option_groups = [ + OptionGroup("Goal Options", [ + Goal, + InstrumentCount, + ]), + OptionGroup("Shuffles", [ + ShuffleInstruments, + ShuffleNightmareKeys, + ShuffleSmallKeys, + ShuffleMaps, + ShuffleCompasses, + ShuffleStoneBeaks + ]), + OptionGroup("Warp Points", [ + WarpImprovements, + AdditionalWarpPoints, + ]), + OptionGroup("Miscellaneous", [ + TradeQuest, + Rooster, + TrendyGame, + NagMessages, + BootsControls + ]), + OptionGroup("Experimental", [ + DungeonShuffle, + EntranceShuffle + ]), + OptionGroup("Visuals & Sound", [ + LinkPalette, + Palette, + TextShuffle, + APTitleScreen, + GfxMod, + Music, + MusicChangeCondition + ]) +] + +@dataclass +class LinksAwakeningOptions(PerGameCommonOptions): + logic: Logic # 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'), # 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'), # 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'), # 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'), - 'tradequest': TradeQuest, # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'), + tradequest: TradeQuest # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'), # 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'), - 'rooster': Rooster, # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'), + rooster: Rooster # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'), # 'boomerang': Boomerang, # 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'), - 'experimental_dungeon_shuffle': DungeonShuffle, # 'Randomizes the dungeon that each dungeon entrance leads to'), - 'experimental_entrance_shuffle': EntranceShuffle, + experimental_dungeon_shuffle: DungeonShuffle # 'Randomizes the dungeon that each dungeon entrance leads to'), + experimental_entrance_shuffle: EntranceShuffle # 'bossshuffle': BossShuffle, # 'minibossshuffle': BossShuffle, - 'goal': Goal, - 'instrument_count': InstrumentCount, + goal: Goal + instrument_count: InstrumentCount # 'itempool': ItemPool, # 'bowwow': Bowwow, # 'overworld': Overworld, - 'link_palette': LinkPalette, - 'warp_improvements': WarpImprovements, - 'additional_warp_points': AdditionalWarpPoints, - 'trendy_game': TrendyGame, - 'gfxmod': GfxMod, - 'palette': Palette, - 'text_shuffle': TextShuffle, - 'shuffle_nightmare_keys': ShuffleNightmareKeys, - 'shuffle_small_keys': ShuffleSmallKeys, - 'shuffle_maps': ShuffleMaps, - 'shuffle_compasses': ShuffleCompasses, - 'shuffle_stone_beaks': ShuffleStoneBeaks, - 'music': Music, - 'shuffle_instruments': ShuffleInstruments, - 'music_change_condition': MusicChangeCondition, - 'nag_messages': NagMessages, - 'ap_title_screen': APTitleScreen, - -} + link_palette: LinkPalette + warp_improvements: WarpImprovements + additional_warp_points: AdditionalWarpPoints + trendy_game: TrendyGame + gfxmod: GfxMod + palette: Palette + text_shuffle: TextShuffle + shuffle_nightmare_keys: ShuffleNightmareKeys + shuffle_small_keys: ShuffleSmallKeys + shuffle_maps: ShuffleMaps + shuffle_compasses: ShuffleCompasses + shuffle_stone_beaks: ShuffleStoneBeaks + music: Music + shuffle_instruments: ShuffleInstruments + music_change_condition: MusicChangeCondition + nag_messages: NagMessages + ap_title_screen: APTitleScreen + boots_controls: BootsControls diff --git a/worlds/ladx/Rom.py b/worlds/ladx/Rom.py index eb573fe5b2cb..8ae1fac0fa31 100644 --- a/worlds/ladx/Rom.py +++ b/worlds/ladx/Rom.py @@ -1,4 +1,4 @@ - +import settings import worlds.Files import hashlib import Utils @@ -32,7 +32,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: def get_base_rom_path(file_name: str = "") -> str: - options = Utils.get_options() + options = settings.get_settings() if not file_name: file_name = options["ladx_options"]["rom_file"] if not os.path.exists(file_name): diff --git a/worlds/ladx/Tracker.py b/worlds/ladx/Tracker.py index 29dce401b895..851fca164453 100644 --- a/worlds/ladx/Tracker.py +++ b/worlds/ladx/Tracker.py @@ -1,4 +1,4 @@ -from worlds.ladx.LADXR.checkMetadata import checkMetadataTable +from .LADXR.checkMetadata import checkMetadataTable import json import logging import websockets diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index 9de3462ad059..c958ef212fe4 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -1,4 +1,5 @@ import binascii +import dataclasses import os import pkgutil import tempfile @@ -7,7 +8,7 @@ import bsdiff4 import settings -from BaseClasses import Entrance, Item, ItemClassification, Location, Tutorial +from BaseClasses import Entrance, Item, ItemClassification, Location, Tutorial, MultiWorld from Fill import fill_restrictive from worlds.AutoWorld import WebWorld, World from .Common import * @@ -17,14 +18,14 @@ from .LADXR.itempool import ItemPool as LADXRItemPool from .LADXR.locations.constants import CHEST_ITEMS from .LADXR.locations.instrument import Instrument -from .LADXR.logic import Logic as LAXDRLogic +from .LADXR.logic import Logic as LADXRLogic from .LADXR.main import get_parser from .LADXR.settings import Settings as LADXRSettings from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion, create_regions_from_ladxr, get_locations_to_id) -from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments -from .Rom import LADXDeltaPatch +from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups +from .Rom import LADXDeltaPatch, get_base_rom_path DEVELOPER_MODE = False @@ -64,7 +65,7 @@ class LinksAwakeningWebWorld(WebWorld): ["zig"] )] theme = "dirt" - + option_groups = ladx_option_groups class LinksAwakeningWorld(World): """ @@ -73,16 +74,12 @@ class LinksAwakeningWorld(World): """ game = LINKS_AWAKENING # name of the game/world web = LinksAwakeningWebWorld() - - option_definitions = links_awakening_options # options the player can set + + options_dataclass = LinksAwakeningOptions + options: LinksAwakeningOptions settings: typing.ClassVar[LinksAwakeningSettings] topology_present = True # show path to required location checks in spoiler - # data_version is used to signal that items, locations or their names - # changed. Set this to 0 during development so other games' clients do not - # cache any texts, then increase by 1 for each release that makes changes. - data_version = 1 - # ID of first item and location, could be hard-coded but code may be easier # to read with this as a propery. base_id = BASE_ID @@ -101,13 +98,20 @@ class LinksAwakeningWorld(World): # Items can be grouped using their names to allow easy checking if any item # from that group has been collected. Group names can also be used for !hint - #item_name_groups = { - # "weapons": {"sword", "lance"} - #} + item_name_groups = { + "Instruments": { + "Full Moon Cello", "Conch Horn", "Sea Lily's Bell", "Surf Harp", + "Wind Marimba", "Coral Triangle", "Organ of Evening Calm", "Thunder Drum" + }, + } prefill_dungeon_items = None - player_options = None + ladxr_settings: LADXRSettings + ladxr_logic: LADXRLogic + ladxr_itempool: LADXRItemPool + + multi_key: bytearray rupees = { ItemName.RUPEES_20: 20, @@ -118,17 +122,13 @@ class LinksAwakeningWorld(World): } def convert_ap_options_to_ladxr_logic(self): - self.player_options = { - option: getattr(self.multiworld, option)[self.player] for option in self.option_definitions - } + self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options)) - self.laxdr_options = LADXRSettings(self.player_options) - - self.laxdr_options.validate() + self.ladxr_settings.validate() world_setup = LADXRWorldSetup() - world_setup.randomize(self.laxdr_options, self.multiworld.random) - self.ladxr_logic = LAXDRLogic(configuration_options=self.laxdr_options, world_setup=world_setup) - self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.laxdr_options, self.multiworld.random).toDict() + world_setup.randomize(self.ladxr_settings, self.random) + self.ladxr_logic = LADXRLogic(configuration_options=self.ladxr_settings, world_setup=world_setup) + self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random).toDict() def create_regions(self) -> None: # Initialize @@ -154,7 +154,7 @@ def create_regions(self) -> None: # Place RAFT, other access events for region in regions: for loc in region.locations: - if loc.event: + if loc.address is None: loc.place_locked_item(self.create_event(loc.ladxr_item.event)) # Connect Windfish -> Victory @@ -184,19 +184,22 @@ def create_items(self) -> None: self.pre_fill_items = [] # For any and different world, set item rule instead - for option in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]: - option = "shuffle_" + option - option = self.player_options[option] + for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]: + option_name = "shuffle_" + dungeon_item_type + option: DungeonItemShuffle = getattr(self.options, option_name) dungeon_item_types[option.ladxr_item] = option.value + # The color dungeon does not contain an instrument + num_items = 8 if dungeon_item_type == "instruments" else 9 + if option.value == DungeonItemShuffle.option_own_world: - self.multiworld.local_items[self.player].value |= { - ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, 10) + self.options.local_items.value |= { + ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1) } elif option.value == DungeonItemShuffle.option_different_world: - self.multiworld.non_local_items[self.player].value |= { - ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, 10) + self.options.non_local_items.value |= { + ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1) } # option_original_dungeon = 0 # option_own_dungeons = 1 @@ -217,7 +220,7 @@ def create_items(self) -> None: else: item = self.create_item(item_name) - if not self.multiworld.tradequest[self.player] and isinstance(item.item_data, TradeItemData): + if not self.options.tradequest and isinstance(item.item_data, TradeItemData): location = self.multiworld.get_location(item.item_data.vanilla_location, self.player) location.place_locked_item(item) location.show_in_spoiler = False @@ -289,7 +292,7 @@ def force_start_item(self): if item.player == self.player and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location] if possible_start_items: - index = self.multiworld.random.choice(possible_start_items) + index = self.random.choice(possible_start_items) start_item = self.multiworld.itempool.pop(index) start_loc.place_locked_item(start_item) @@ -338,7 +341,7 @@ def pre_fill(self) -> None: # Get the list of locations and shuffle all_dungeon_locs_to_fill = sorted(all_dungeon_locs) - self.multiworld.random.shuffle(all_dungeon_locs_to_fill) + self.random.shuffle(all_dungeon_locs_to_fill) # Get the list of items and sort by priority def priority(item): @@ -435,6 +438,12 @@ def guess_icon_for_other_world(self, other): return "TRADING_ITEM_LETTER" + @classmethod + def stage_assert_generate(cls, multiworld: MultiWorld): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + def generate_output(self, output_directory: str): # copy items back to locations for r in self.multiworld.get_regions(self.player): @@ -461,34 +470,19 @@ def generate_output(self, output_directory: str): loc.ladxr_item.location_owner = self.player rom_name = Rom.get_base_rom_path() - out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc" + out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.player_name}.gbc" out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc") parser = get_parser() args = parser.parse_args([rom_name, "-o", out_name, "--dump"]) - name_for_rom = self.multiworld.player_name[self.player] - - all_names = [self.multiworld.player_name[i + 1] for i in range(len(self.multiworld.player_name))] - - rom = generator.generateRom( - args, - self.laxdr_options, - self.player_options, - self.multi_key, - self.multiworld.seed_name, - self.ladxr_logic, - rnd=self.multiworld.per_slot_randoms[self.player], - player_name=name_for_rom, - player_names=all_names, - player_id = self.player, - multiworld=self.multiworld) + rom = generator.generateRom(args, self) with open(out_path, "wb") as handle: rom.save(handle, name="LADXR") # Write title screen after everything else is done - full gfxmods may stomp over the egg tiles - if self.player_options["ap_title_screen"]: + if self.options.ap_title_screen: with tempfile.NamedTemporaryFile(delete=False) as title_patch: title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4")) @@ -496,16 +490,16 @@ def generate_output(self, output_directory: str): os.unlink(title_patch.name) patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player, - player_name=self.multiworld.player_name[self.player], patched_path=out_path) + player_name=self.player_name, patched_path=out_path) patch.write() if not DEVELOPER_MODE: os.unlink(out_path) def generate_multi_key(self): - return bytearray(self.multiworld.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big') + return bytearray(self.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big') def modify_multidata(self, multidata: dict): - multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.multiworld.player_name[self.player]] + multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.player_name] def collect(self, state, item: Item) -> bool: change = super().collect(state, item) diff --git a/worlds/ladx/docs/en_Links Awakening DX.md b/worlds/ladx/docs/en_Links Awakening DX.md index bceda4bc89c1..91a34107c169 100644 --- a/worlds/ladx/docs/en_Links Awakening DX.md +++ b/worlds/ladx/docs/en_Links Awakening DX.md @@ -1,8 +1,8 @@ # Links Awakening DX -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -85,7 +85,7 @@ Title screen graphics by toomanyteeth✨ (https://instagram.com/toomanyyyteeth)

    The walrus is moved a bit, so that you can access the desert without taking Marin on a date.

    Logic

    -

    Depending on your settings, you can only steal after you find the sword, always, or never.

    +

    Depending on your options, you can only steal after you find the sword, always, or never.

    Do not forget that there are two items in the rafting ride. You can access this with just Hookshot or Flippers.

    Killing enemies with bombs is in normal logic. You can switch to casual logic if you do not want this.

    D7 confuses some people, but by dropping down pits on the 2nd floor you can access almost all of this dungeon, even without feather and power bracelet.

    diff --git a/worlds/ladx/docs/setup_en.md b/worlds/ladx/docs/setup_en.md index aad077d73037..d12f9b8b3b84 100644 --- a/worlds/ladx/docs/setup_en.md +++ b/worlds/ladx/docs/setup_en.md @@ -35,8 +35,8 @@ options. ### Where do I get a config file? -The [Player Settings](/games/Links%20Awakening%20DX/player-settings) page on the website allows you to configure -your personal settings and export a config file from them. +The [Player Options](/games/Links%20Awakening%20DX/player-options) page on the website allows you to configure +your personal options and export a config file from them. ### Verifying your config file @@ -45,7 +45,7 @@ If you would like to validate your config file to make sure it works, you may do ## Generating a Single-Player Game -1. Navigate to the [Player Settings](/games/Links%20Awakening%20DX/player-settings) page, configure your options, +1. Navigate to the [Player Options](/games/Links%20Awakening%20DX/player-options) page, configure your options, and click the "Generate Game" button. 2. You will be presented with a "Seed Info" page. 3. Click the "Create New Room" link. diff --git a/worlds/landstalker/Hints.py b/worlds/landstalker/Hints.py index 93274f1d68bb..5309e85032ea 100644 --- a/worlds/landstalker/Hints.py +++ b/worlds/landstalker/Hints.py @@ -30,6 +30,9 @@ def generate_lithograph_hint(world: "LandstalkerWorld"): jewel_items = world.jewel_items for item in jewel_items: + if item.location is None: + continue + # Jewel hints are composed of 4 'words' shuffled randomly: # - the name of the player whose world contains said jewel (if not ours) # - the color of the jewel (if relevant) @@ -61,7 +64,7 @@ def generate_random_hints(world: "LandstalkerWorld"): excluded_items = ["Life Stock", "EkeEke"] progression_items = [item for item in multiworld.itempool if item.advancement and - item.name not in excluded_items] + item.name not in excluded_items and item.location is not None] local_own_progression_items = [item for item in progression_items if item.player == this_player and item.location.player == this_player] diff --git a/worlds/landstalker/Locations.py b/worlds/landstalker/Locations.py index 5e42fbecda72..b0148269eab3 100644 --- a/worlds/landstalker/Locations.py +++ b/worlds/landstalker/Locations.py @@ -1,8 +1,9 @@ from typing import Dict, Optional -from BaseClasses import Location +from BaseClasses import Location, ItemClassification, Item from .Regions import LandstalkerRegion from .data.item_source import ITEM_SOURCES_JSON +from .data.world_path import WORLD_PATHS_JSON BASE_LOCATION_ID = 4000 BASE_GROUND_LOCATION_ID = BASE_LOCATION_ID + 256 @@ -28,6 +29,18 @@ def create_locations(player: int, regions_table: Dict[str, LandstalkerRegion], n new_location = LandstalkerLocation(player, data["name"], name_to_id_table[data["name"]], region, data["type"]) region.locations.append(new_location) + # Create fake event locations that will be used to determine if some key regions has been visited + regions_with_entrance_checks = [] + for data in WORLD_PATHS_JSON: + if "requiredNodes" in data: + regions_with_entrance_checks.extend([region_id for region_id in data["requiredNodes"]]) + regions_with_entrance_checks = list(set(regions_with_entrance_checks)) + for region_id in regions_with_entrance_checks: + region = regions_table[region_id] + location = LandstalkerLocation(player, 'event_visited_' + region_id, None, region, "event") + location.place_locked_item(Item("event_visited_" + region_id, ItemClassification.progression, None, player)) + region.locations.append(location) + # Create a specific end location that will contain a fake win-condition item end_location = LandstalkerLocation(player, "End", None, regions_table["end"], "reward") regions_table["end"].locations.append(end_location) diff --git a/worlds/landstalker/Regions.py b/worlds/landstalker/Regions.py index 21704194f157..27e5e2e993d0 100644 --- a/worlds/landstalker/Regions.py +++ b/worlds/landstalker/Regions.py @@ -37,7 +37,7 @@ def create_regions(world: "LandstalkerWorld"): for code, region_data in WORLD_NODES_JSON.items(): random_hint_name = None if "hints" in region_data: - random_hint_name = multiworld.random.choice(region_data["hints"]) + random_hint_name = world.random.choice(region_data["hints"]) region = LandstalkerRegion(code, region_data["name"], player, multiworld, random_hint_name) regions_table[code] = region multiworld.regions.append(region) diff --git a/worlds/landstalker/Rules.py b/worlds/landstalker/Rules.py index 51357c9480b0..94171944d7b2 100644 --- a/worlds/landstalker/Rules.py +++ b/worlds/landstalker/Rules.py @@ -10,7 +10,7 @@ def _landstalker_has_visited_regions(state: CollectionState, player: int, regions): - return all([state.can_reach(region, None, player) for region in regions]) + return all(state.has("event_visited_" + region.code, player) for region in regions) def _landstalker_has_health(state: CollectionState, player: int, health): diff --git a/worlds/landstalker/__init__.py b/worlds/landstalker/__init__.py index baa1deb620a4..2b3dc41239c3 100644 --- a/worlds/landstalker/__init__.py +++ b/worlds/landstalker/__init__.py @@ -204,6 +204,9 @@ def set_rules(self): for location in self.multiworld.get_locations(self.player): if location.parent_region.name in excluded_regions: location.progress_type = LocationProgressType.EXCLUDED + # We need to make that event non-progression since it would crash generation in reach_kazalt goal + if location.item is not None and location.item.name == "event_visited_king_nole_labyrinth_raft_entrance": + location.item.classification = ItemClassification.filler def get_starting_health(self): spawn_id = self.options.spawn_region.current_key diff --git a/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md b/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md index 90a79f8bd986..9239f741b436 100644 --- a/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md +++ b/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md @@ -1,8 +1,8 @@ # Landstalker: The Treasures of King Nole -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains most of the options you need to +The [player options page for this game](../player-options) contains most of the options you need to configure and export a config file. ## What does randomization do to this game? @@ -35,7 +35,7 @@ All key doors are gone, except three of them : The secondary shop of Mercator requiring to do the traders sidequest in the original game is now unlocked by having **Buyer Card** in your inventory. -You will need as many **jewels** as specified in the settings to use the teleporter to go to Kazalt and the final dungeon. +You will need as many **jewels** as specified in the options to use the teleporter to go to Kazalt and the final dungeon. If you find and use the **Lithograph**, it will tell you in which world are each one of your jewels. Each seed, there is a random dungeon which is chosen to be the "dark dungeon" where you won't see anything unless you @@ -54,7 +54,7 @@ be significantly harder, both combat-wise and logic-wise. Having fully open & shuffled teleportation trees is an interesting way to play, but is discouraged for beginners as well since it can force you to go in late-game zones with few Life Stocks. -Overall, the default settings are good for a beginner-friendly seed, and if you don't feel too confident, you can also +Overall, the default options are good for a beginner-friendly seed, and if you don't feel too confident, you can also lower the combat difficulty to make it more forgiving. *Have fun on your adventure!* diff --git a/worlds/landstalker/docs/landstalker_setup_en.md b/worlds/landstalker/docs/landstalker_setup_en.md index 32e46a4b3354..30f85dd8f19b 100644 --- a/worlds/landstalker/docs/landstalker_setup_en.md +++ b/worlds/landstalker/docs/landstalker_setup_en.md @@ -30,8 +30,10 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The [Player Settings Page](/games/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/player-settings) on the website allows -you to easily configure your personal settings + +The [Player Options Page](/games/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/player-options) on the website allows +you to easily configure your personal options. + ## How-to-play diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index 2f9354193250..9853be73fa9b 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -3,17 +3,20 @@ """ from logging import warning -from BaseClasses import Item, ItemClassification, Tutorial +from BaseClasses import CollectionState, Item, ItemClassification, Tutorial +from Options import OptionError from worlds.AutoWorld import WebWorld, World -from .items import ALL_ITEM_TABLE, LingoItem -from .locations import ALL_LOCATION_TABLE -from .options import LingoOptions +from .datatypes import Room, RoomEntrance +from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem +from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP +from .options import LingoOptions, lingo_option_groups, SunwarpAccess, VictoryCondition from .player_logic import LingoPlayerLogic from .regions import create_regions -from .static_logic import Room, RoomEntrance class LingoWebWorld(WebWorld): + option_groups = lingo_option_groups + rich_text_options_doc = True theme = "grass" tutorials = [Tutorial( "Multiworld Setup Guide", @@ -35,7 +38,6 @@ class LingoWorld(World): base_id = 444400 topology_present = True - data_version = 1 options_dataclass = LingoOptions options: LingoOptions @@ -46,22 +48,59 @@ class LingoWorld(World): location_name_to_id = { name: data.code for name, data in ALL_LOCATION_TABLE.items() } + item_name_groups = ITEMS_BY_GROUP + location_name_groups = LOCATIONS_BY_GROUP player_logic: LingoPlayerLogic def generate_early(self): - if not (self.options.shuffle_doors or self.options.shuffle_colors): + if not (self.options.shuffle_doors or self.options.shuffle_colors or + (self.options.sunwarp_access >= SunwarpAccess.option_unlock and + self.options.victory_condition == VictoryCondition.option_pilgrimage)): if self.multiworld.players == 1: - warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression" - f" items. Please turn on Door Shuffle or Color Shuffle if that doesn't seem right.") + warning(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on Door" + f" Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage victory condition" + f" if that doesn't seem right.") else: - raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any" - f" progression items. Please turn on Door Shuffle or Color Shuffle.") + raise OptionError(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on" + f" Door Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage" + f" victory condition.") self.player_logic = LingoPlayerLogic(self) def create_regions(self): - create_regions(self, self.player_logic) + create_regions(self) + + if not self.options.shuffle_postgame: + state = CollectionState(self.multiworld) + state.collect(LingoItem("Prevent Victory", ItemClassification.progression, None, self.player), True) + + # Note: relies on the assumption that real_items is a definitive list of real progression items in this + # world, and is not modified after being created. + for item in self.player_logic.real_items: + state.collect(self.create_item(item), True) + + # Exception to the above: a forced good item is not considered a "real item", but needs to be here anyway. + if self.player_logic.forced_good_item != "": + state.collect(self.create_item(self.player_logic.forced_good_item), True) + + all_locations = self.multiworld.get_locations(self.player) + state.sweep_for_events(locations=all_locations) + + unreachable_locations = [location for location in all_locations + if not state.can_reach_location(location.name, self.player)] + + for location in unreachable_locations: + if location.name in self.player_logic.event_loc_to_item.keys(): + continue + + self.player_logic.real_locations.remove(location.name) + location.parent_region.locations.remove(location) + + if len(self.player_logic.real_items) > len(self.player_logic.real_locations): + raise OptionError(f"{self.player_name}'s Lingo world does not have enough locations to fit the number" + f" of required items without shuffling the postgame. Either enable postgame" + f" shuffling, or choose different options.") def create_items(self): pool = [self.create_item(name) for name in self.player_logic.real_items] @@ -89,10 +128,23 @@ def create_items(self): pool.append(self.create_item("Puzzle Skip")) if traps: - traps_list = ["Slowness Trap", "Iceland Trap", "Atbash Trap"] + total_weight = sum(self.options.trap_weights.values()) + + if total_weight == 0: + raise OptionError("Sum of trap weights must be at least one.") - for i in range(0, traps): - pool.append(self.create_item(traps_list[i % len(traps_list)])) + trap_counts = {name: int(weight * traps / total_weight) + for name, weight in self.options.trap_weights.items()} + + trap_difference = traps - sum(trap_counts.values()) + if trap_difference > 0: + allowed_traps = [name for name in TRAP_ITEMS if self.options.trap_weights[name] > 0] + for i in range(0, trap_difference): + trap_counts[allowed_traps[i % len(allowed_traps)]] += 1 + + for name, count in trap_counts.items(): + for i in range(0, count): + pool.append(self.create_item(name)) self.multiworld.itempool += pool @@ -100,9 +152,9 @@ def create_item(self, name: str) -> Item: item = ALL_ITEM_TABLE[name] classification = item.classification - if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0\ - and len(item.door_ids) == 0 and all(painting_id not in self.player_logic.painting_mapping - for painting_id in item.painting_ids)\ + if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0 \ + and not item.has_doors and all(painting_id not in self.player_logic.painting_mapping + for painting_id in item.painting_ids) \ and "pilgrim_painting2" not in item.painting_ids: # If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings # go nowhere, then this item should not be progression. The Pilgrim Room painting is special and needs to be @@ -117,7 +169,9 @@ def set_rules(self): def fill_slot_data(self): slot_options = [ "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", - "mastery_achievements", "level_2_requirement", "location_checks", "early_color_hallways" + "enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks", + "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps", + "group_doors" ] slot_data = { @@ -128,6 +182,9 @@ def fill_slot_data(self): if self.options.shuffle_paintings: slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping + if self.options.shuffle_sunwarps: + slot_data["sunwarp_permutation"] = self.player_logic.sunwarp_mapping + return slot_data def get_filler_item_name(self) -> str: diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index f72e63c1427e..950fd326743a 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -1,6 +1,13 @@ --- # This file is an associative array where the keys are region names. Rooms - # have four properties: entrances, panels, doors, and paintings. + # have a number of properties: + # - entrances + # - panels + # - doors + # - panel_doors + # - paintings + # - progression + # - sunwarps # # entrances is an array of regions from which this room can be accessed. The # key of each entry is the room that can access this one. The value is a list @@ -13,7 +20,7 @@ # room that the door is in. The room name may be omitted if the door is # located in the current room. # - # panels is an array of panels in the room. The key of the array is an + # panels is a named array of panels in the room. The key of the array is an # arbitrary name for the panel. Panels can have the following fields: # - id: The internal ID of the panel in the LINGO map # - required_room: In addition to having access to this room, the player must @@ -45,7 +52,7 @@ # - hunt: If True, the tracker will show this panel even when it is # not a check. Used for hunts like the Number Hunt. # - # doors is an array of doors associated with this room. When door + # doors is a named array of doors associated with this room. When door # randomization is enabled, each of these is an item. The key is a name that # will be displayed as part of the item's name. Doors can have the following # fields: @@ -54,6 +61,8 @@ # this door will open the doors listed here. # - painting_id: An internal ID of a painting that should be moved upon # receiving this door. + # - warp_id: An internal ID or IDs of warps that should be disabled + # until receiving this door. # - panels: These are the panels that canonically open this door. If # there is only one panel for the door, then that panel is a # check. If there is more than one panel, then that entire @@ -63,22 +72,31 @@ # - item_name: Overrides the name of the item generated for this door. # If not specified, the item name will be generated from # the room name and the door name. + # - item_group: If set, this item will be in the specified item group. # - location_name: Overrides the name of the location generated for this # door. If not specified, the location name will be # generated using the names of the panels. # - skip_location: If true, no location is generated for this door. # - skip_item: If true, no item is generated for this door. - # - group: When simple doors is used, all doors with the same group + # - door_group: When simple doors is used, all doors with the same group # will be covered by a single item. # - include_reduce: Door checks are assumed to be EXCLUDED when reduce checks # is on. This option includes the check anyway. - # - junk_item: If on, the item for this door will be considered a junk - # item instead of a progression item. Only use this for - # doors that could never gate progression regardless of - # options and state. # - event: Denotes that the door is event only. This is similar to # setting both skip_location and skip_item. # + # panel_doors is a named array of "panel doors" associated with this room. + # When panel door shuffle is enabled, each of these becomes an item, and those + # items block access to the listed panels. The key is a name for internal + # reference only. Panel doors can have the following fields: + # - panels: Required. This is the set of panels that are blocked by this + # panel door. + # - item_name: Overrides the name of the item generated for this panel + # door. If not specified, the item name will be generated from + # the room name and the name(s) of the panel(s). + # - panel_group: When region grouping is enabled, all panel doors with the + # same group will be covered by a single item. + # # paintings is an array of paintings in the room. This is used for painting # shuffling. # - id: The internal painting ID from the LINGO map. @@ -105,9 +123,59 @@ # Use "req_blocked_when_no_doors" instead if it would be # fine in door shuffle mode. # - move: Denotes that the painting is able to move. + # + # progression is a named array of items that define an ordered set of items. + # progression items do not have any true connection to the rooms that they + # are defined in, but it is best to place them in a thematically appropriate + # room. The key for a progression entry is the name of the item that will be + # created. A progression entry is a dictionary with one or both of a "doors" + # key and a "panel_doors" key. These fields should be lists of doors or + # panel doors that will be contained in this progressive item. + # + # sunwarps is an array of sunwarps in the room. This is used for sunwarp + # shuffling. + # - dots: The number of dots on this sunwarp. + # - direction: "enter" or "exit" + # - entrance_indicator_pos: Coordinates for where the entrance indicator + # should be placed if this becomes an entrance. + # - orientation: One of north/south/east/west. Starting Room: entrances: - Menu: True + Menu: + warp: True + Outside The Wise: + painting: True + Rhyme Room (Circle): + painting: True + Rhyme Room (Target): + painting: True + Wondrous Lobby: + painting: True + Orange Tower Third Floor: + painting: True + Color Hunt: + painting: True + Owl Hallway: + painting: True + The Wondrous: + room: The Wondrous + door: Exit + painting: True + Orange Tower Sixth Floor: + painting: True + Orange Tower Basement: + painting: True + The Colorful: + painting: True + Welcome Back Area: + room: Welcome Back Area + door: Shortcut to Starting Room + Second Room: + door: Main Door + Hidden Room: + door: Back Right Door + Rhyme Room (Looped Square): + door: Rhyme Room Entrance panels: HI: id: Entry Room/Panel_hi_hi @@ -144,7 +212,7 @@ - Palindrome Room Area Doors/Door_racecar_racecar_2 - Palindrome Room Area Doors/Door_solos_solos_2 skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - room: The Tenacious panel: LEVEL (Black) @@ -152,6 +220,10 @@ panel: RACECAR (Black) - room: The Tenacious panel: SOLOS (Black) + panel_doors: + HIDDEN: + panels: + - HIDDEN paintings: - id: arrows_painting exit_only: True @@ -231,7 +303,7 @@ Dead End Door: id: Appendix Room Area Doors/Door_rat_tar_2 skip_location: true - group: Dead End Area Access + door_group: Dead End Area Access panels: - room: Hub Room panel: RAT @@ -244,6 +316,7 @@ Seeker Entrance: id: Entry Room Area Doors/Door_entrance_entrance item_name: The Seeker - Entrance + item_group: Achievement Room Entrances panels: - OPEN Rhyme Room Entrance: @@ -251,7 +324,7 @@ - Appendix Room Area Doors/Door_rat_tar_3 - Double Room Area Doors/Door_room_entry_stairs skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - room: The Tenacious panel: LEVEL (Black) @@ -261,6 +334,10 @@ panel: SOLOS (Black) - room: Hub Room panel: RAT + panel_doors: + OPEN: + panels: + - OPEN paintings: - id: owl_painting orientation: north @@ -275,7 +352,13 @@ panels: Achievement: id: Countdown Panels/Panel_seeker_seeker - required_room: Hidden Room + # The Seeker uniquely has the property that 1) it can be entered (through the Pilgrim Room) without opening the + # front door in panels mode door shuffle, and 2) the front door panel is part of the CDP. This necessitates this + # required_panel clause, because the entrance panel needs to be solvable for the achievement even if an + # alternate entrance to the room is used. + required_panel: + room: Hidden Room + panel: OPEN tag: forbid check: True achievement: The Seeker @@ -414,7 +497,7 @@ The Traveled: door: Traveled Entrance Roof: True # through the sunwarp - Outside The Undeterred: # (NOTE: used in hardcoded pilgrimage) + Outside The Undeterred: room: Outside The Undeterred door: Green Painting painting: True @@ -450,6 +533,7 @@ id: Shuffle Room/Panel_lost_found colors: black tag: botblack + check: True FORWARD: id: Entry Room/Panel_forward_forward tag: midwhite @@ -461,40 +545,64 @@ tag: midwhite doors: Crossroads Entrance: - id: Shuffle Room Area Doors/Door_chaos + id: + - Shuffle Room Area Doors/Door_chaos + - Shuffle Room Area Doors/Door_swap + - Shuffle Room Area Doors/Door_swap2 + - Shuffle Room Area Doors/Door_swap3 + - Shuffle Room Area Doors/Door_swap4 panels: - ORDER Tenacious Entrance: id: Palindrome Room Area Doors/Door_slaughter_laughter - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - SLAUGHTER Shortcut to Hedge Maze: id: Maze Area Doors/Door_trace_trace - group: Hedge Maze Doors + door_group: Hedge Maze Doors panels: - TRACE Near RAT Door: id: Appendix Room Area Doors/Door_deadend_deadened skip_location: True - group: Dead End Area Access + door_group: Dead End Area Access panels: - room: Hidden Room panel: DEAD END Traveled Entrance: id: Appendix Room Area Doors/Door_open_open item_name: The Traveled - Entrance - group: Entrance to The Traveled + door_group: Entrance to The Traveled + item_group: Achievement Room Entrances panels: - OPEN - Lost Door: - id: Shuffle Room Area Doors/Door_lost_found - junk_item: True + panel_doors: + ORDER: + panels: + - ORDER + SLAUGHTER: + panel_group: Tenacious Entrance Panels + panels: + - SLAUGHTER + TRACE: + panels: + - TRACE + RAT: + panels: + - RAT + OPEN: panels: - - LOST + - OPEN paintings: - id: maze_painting orientation: west + sunwarps: + - dots: 1 + direction: enter + entrance_indicator_pos: [18, 2.5, -17.01] + orientation: north Dead End Area: entrances: Hidden Room: @@ -521,12 +629,53 @@ paintings: - id: smile_painting_6 orientation: north - Pilgrim Antechamber: - # Let's not shuffle the paintings yet. + Sunwarps: + # This is a special, meta-ish room. entrances: - # The pilgrimage is hardcoded in rules.py - Starting Room: - door: Sun Painting + Menu: True + doors: + 1 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_1 + door_group: Sunwarps + skip_location: True + item_name: "1 Sunwarp" + 2 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_2 + door_group: Sunwarps + skip_location: True + item_name: 2 Sunwarp + 3 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_3 + door_group: Sunwarps + skip_location: True + item_name: "3 Sunwarp" + 4 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_4 + door_group: Sunwarps + skip_location: True + item_name: 4 Sunwarp + 5 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_5 + door_group: Sunwarps + skip_location: True + item_name: "5 Sunwarp" + 6 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_6 + door_group: Sunwarps + skip_location: True + item_name: "6 Sunwarp" + progression: + Progressive Pilgrimage: + doors: + - 1 Sunwarp + - 2 Sunwarp + - 3 Sunwarp + - 4 Sunwarp + - 5 Sunwarp + - 6 Sunwarp + Pilgrim Antechamber: + # The entrances to this room are special. When pilgrimage is enabled, we use a special access rule to determine + # whether a pilgrimage can succeed. When pilgrimage is disabled, the sun painting will be added to the pool. panels: HOT CRUST: id: Lingo Room/Panel_shortcut @@ -536,6 +685,7 @@ id: Lingo Room/Panel_pilgrim colors: blue tag: midblue + check: True MASTERY: id: Master Room/Panel_mastery_mastery14 tag: midwhite @@ -546,6 +696,7 @@ doors: Sun Painting: item_name: Pilgrim Room - Sun Painting + item_group: Paintings location_name: Pilgrim Room - HOT CRUST painting_id: pilgrim_painting2 panels: @@ -630,11 +781,19 @@ - THIS Crossroads: entrances: - Hub Room: True # The sunwarp means that we never need the ORDER door - Color Hallways: True + Hub Room: + - room: Sunwarps + door: 1 Sunwarp + sunwarp: True + - room: Hub Room + door: Crossroads Entrance + Color Hallways: + warp: True The Tenacious: door: Tenacious Entrance - Orange Tower Fourth Floor: True # through IRK HORN + Orange Tower Fourth Floor: + - warp: True # through IRK HORN + - door: Tower Entrance Amen Name Area: room: Lost Area door: Exit @@ -694,6 +853,8 @@ door: Hollow Hallway tag: midwhite SWAP: + # In vanilla doors, solving this panel will open the way to Hub Room. This does not impact logic at all because + # Hub Room is always sphere 1 in vanilla doors. id: Shuffle Room/Panel_swap_wasp colors: yellow tag: midyellow @@ -715,12 +876,14 @@ doors: Tenacious Entrance: id: Palindrome Room Area Doors/Door_decay_day - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - DECAY Discerning Entrance: id: Shuffle Room Area Doors/Door_nope_open item_name: The Discerning - Entrance + item_group: Achievement Room Entrances panels: - NOPE Tower Entrance: @@ -729,13 +892,13 @@ - Shuffle Room Area Doors/Door_tower2 - Shuffle Room Area Doors/Door_tower3 - Shuffle Room Area Doors/Door_tower4 - group: Crossroads - Tower Entrances + door_group: Crossroads - Tower Entrances panels: - WE ROT Tower Back Entrance: id: Shuffle Room Area Doors/Door_runt location_name: Crossroads - TURN/RUNT - group: Crossroads - Tower Entrances + door_group: Crossroads - Tower Entrances panels: - TURN - room: Orange Tower Fourth Floor @@ -744,20 +907,19 @@ id: - Shuffle Room Area Doors/Door_words_shuffle_3 - Shuffle Room Area Doors/Door_words_shuffle_4 - group: Crossroads Doors + door_group: Crossroads Doors panels: - WORDS - SWORD Eye Wall: id: Shuffle Room Area Doors/Door_behind - junk_item: True - group: Crossroads Doors + door_group: Crossroads Doors panels: - BEND HI Hollow Hallway: id: Shuffle Room Area Doors/Door_crossroads6 skip_location: True - group: Crossroads Doors + door_group: Crossroads Doors panels: - BEND HI Roof Access: @@ -776,6 +938,26 @@ panel: DRAWL + RUNS - room: Owl Hallway panel: READS + RUST + - room: Ending Area + panel: THE END + panel_doors: + DECAY: + panel_group: Tenacious Entrance Panels + panels: + - DECAY + NOPE: + panels: + - NOPE + WE ROT: + panels: + - WE ROT + WORDS SWORD: + panels: + - WORDS + - SWORD + BEND HI: + panels: + - BEND HI paintings: - id: eye_painting disable: True @@ -785,6 +967,19 @@ door: Eye Wall - id: smile_painting_4 orientation: south + sunwarps: + - dots: 1 + direction: exit + entrance_indicator_pos: [ -17, 2.5, -41.01 ] + orientation: north + progression: + Progressive Suits Area: + panel_doors: + - WORDS SWORD + - room: Lost Area + panel_door: LOST + - room: Amen Name Area + panel_door: AMEN NAME Lost Area: entrances: Outside The Agreeable: @@ -810,6 +1005,11 @@ panels: - LOST (1) - LOST (2) + panel_doors: + LOST: + panels: + - LOST (1) + - LOST (2) Amen Name Area: entrances: Crossroads: @@ -843,6 +1043,11 @@ panels: - AMEN - NAME + panel_doors: + AMEN NAME: + panels: + - AMEN + - NAME Suits Area: entrances: Amen Name Area: @@ -934,7 +1139,7 @@ - Palindrome Room Area Doors/Door_racecar_racecar_1 - Palindrome Room Area Doors/Door_solos_solos_1 location_name: The Tenacious - Palindromes - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious panels: - LEVEL (Black) - RACECAR (Black) @@ -946,6 +1151,13 @@ - LEVEL (White) - RACECAR (White) - SOLOS (White) + panel_doors: + Black Palindromes: + item_name: The Tenacious - Black Palindromes (Panels) + panels: + - LEVEL (Black) + - RACECAR (Black) + - SOLOS (Black) Near Far Area: entrances: Hub Room: True @@ -965,12 +1177,27 @@ id: - Symmetry Room Area Doors/Door_near_far - Symmetry Room Area Doors/Door_far_near - group: Symmetry Doors + door_group: Symmetry Doors item_name: Symmetry Room - Near Far Door location_name: Symmetry Room - NEAR, FAR panels: - NEAR - FAR + panel_doors: + NEAR FAR: + item_name: Symmetry Room - NEAR, FAR (Panels) + panel_group: Symmetry Room Panels + panels: + - NEAR + - FAR + progression: + Progressive Symmetry Room: + panel_doors: + - NEAR FAR + - room: Warts Straw Area + panel_door: WARTS STRAW + - room: Leaf Feel Area + panel_door: LEAF FEEL Warts Straw Area: entrances: Near Far Area: @@ -992,12 +1219,19 @@ id: - Symmetry Room Area Doors/Door_warts_straw - Symmetry Room Area Doors/Door_straw_warts - group: Symmetry Doors + door_group: Symmetry Doors item_name: Symmetry Room - Warts Straw Door location_name: Symmetry Room - WARTS, STRAW panels: - WARTS - STRAW + panel_doors: + WARTS STRAW: + item_name: Symmetry Room - WARTS, STRAW (Panels) + panel_group: Symmetry Room Panels + panels: + - WARTS + - STRAW Leaf Feel Area: entrances: Warts Straw Area: @@ -1019,18 +1253,26 @@ id: - Symmetry Room Area Doors/Door_leaf_feel - Symmetry Room Area Doors/Door_feel_leaf - group: Symmetry Doors + door_group: Symmetry Doors item_name: Symmetry Room - Leaf Feel Door location_name: Symmetry Room - LEAF, FEEL panels: - LEAF - FEEL + panel_doors: + LEAF FEEL: + item_name: Symmetry Room - LEAF, FEEL (Panels) + panel_group: Symmetry Room Panels + panels: + - LEAF + - FEEL Outside The Agreeable: - # Let's ignore the blue warp thing for now because the lookout is a dead - # end. Later on it could be filler checks. entrances: - # We don't have to list Lost Area because of Crossroads. - Crossroads: True + Crossroads: + warp: True + Lost Area: + room: Lost Area + door: Exit The Tenacious: door: Tenacious Entrance The Agreeable: @@ -1043,12 +1285,11 @@ Starting Room: door: Painting Shortcut painting: True - Hallway Room (2): True - Hallway Room (3): True - Hallway Room (4): True + Hallway Room (1): + warp: True Hedge Maze: True # through the door to the sectioned-off part of the hedge maze - Cellar: - door: Lookout Entrance + Compass Room: + warp: True panels: MASSACRED: id: Palindrome Room/Panel_massacred_sacred @@ -1094,11 +1335,6 @@ required_door: room: Outside The Undeterred door: Fives - OUT: - id: Hallway Room/Panel_out_out - check: True - exclude_reduce: True - tag: midwhite HIDE: id: Maze Room/Panel_hide_seek_4 colors: black @@ -1107,26 +1343,67 @@ id: Maze Room/Panel_daze_maze colors: purple tag: midpurp - WALL: - id: Hallway Room/Panel_castle_1 - colors: blue - tag: quad bot blue - link: qbb CASTLE - KEEP: - id: Hallway Room/Panel_castle_2 - colors: blue - tag: quad bot blue - link: qbb CASTLE - BAILEY: - id: Hallway Room/Panel_castle_3 - colors: blue - tag: quad bot blue - link: qbb CASTLE - TOWER: - id: Hallway Room/Panel_castle_4 - colors: blue - tag: quad bot blue - link: qbb CASTLE + doors: + Tenacious Entrance: + id: Palindrome Room Area Doors/Door_massacred_sacred + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances + panels: + - MASSACRED + Black Door: + id: Symmetry Room Area Doors/Door_black_white + door_group: Entrances to The Tenacious + panels: + - BLACK + Agreeable Entrance: + id: Symmetry Room Area Doors/Door_close_open + item_name: The Agreeable - Entrance + item_group: Achievement Room Entrances + panels: + - CLOSE + Painting Shortcut: + item_name: Starting Room - Street Painting + item_group: Paintings + painting_id: eyes_yellow_painting2 + panels: + - RIGHT + Purple Barrier: + id: Color Arrow Room Doors/Door_purple_3 + door_group: Color Hunt Barriers + skip_location: True + panels: + - room: Color Hunt + panel: PURPLE + panel_doors: + MASSACRED: + panel_group: Tenacious Entrance Panels + panels: + - MASSACRED + BLACK: + panels: + - BLACK + CLOSE: + panels: + - CLOSE + RIGHT: + panels: + - RIGHT + paintings: + - id: eyes_yellow_painting + orientation: east + sunwarps: + - dots: 6 + direction: enter + entrance_indicator_pos: [ 3, 2.5, -55.01 ] + orientation: north + Compass Room: + entrances: + Outside The Agreeable: + warp: True + Cellar: + door: Lookout Entrance + warp: True + panels: NORTH: id: Cross Room/Panel_north_missing colors: green @@ -1154,42 +1431,6 @@ tag: forbid required_room: Orange Tower Fifth Floor doors: - Tenacious Entrance: - id: Palindrome Room Area Doors/Door_massacred_sacred - group: Entrances to The Tenacious - panels: - - MASSACRED - Black Door: - id: Symmetry Room Area Doors/Door_black_white - group: Entrances to The Tenacious - panels: - - BLACK - Agreeable Entrance: - id: Symmetry Room Area Doors/Door_close_open - item_name: The Agreeable - Entrance - panels: - - CLOSE - Painting Shortcut: - item_name: Starting Room - Street Painting - painting_id: eyes_yellow_painting2 - panels: - - RIGHT - Purple Barrier: - id: Color Arrow Room Doors/Door_purple_3 - group: Color Hunt Barriers - skip_location: True - panels: - - room: Color Hunt - panel: PURPLE - Hallway Door: - id: Red Blue Purple Room Area Doors/Door_room_2 - group: Hallway Room Doors - location_name: Hallway Room - First Room - panels: - - WALL - - KEEP - - BAILEY - - TOWER Lookout Entrance: id: Cross Room Doors/Door_missing location_name: Outside The Agreeable - Lookout Panels @@ -1198,22 +1439,17 @@ - WINTER - DIAMONDS - FIRE + panel_doors: + Lookout: + item_name: Compass Room Panels + panels: + - NORTH + - WINTER + - DIAMONDS + - FIRE paintings: - - id: panda_painting - orientation: south - - id: eyes_yellow_painting - orientation: east - id: pencil_painting7 orientation: north - progression: - Progressive Hallway Room: - - Hallway Door - - room: Hallway Room (2) - door: Exit - - room: Hallway Room (3) - door: Exit - - room: Hallway Room (4) - door: Exit Dread Hallway: entrances: Outside The Agreeable: @@ -1229,7 +1465,8 @@ doors: Tenacious Entrance: id: Palindrome Room Area Doors/Door_dread_dead - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - DREAD The Agreeable: @@ -1287,7 +1524,7 @@ id: Antonym Room/Panel_star_rats colors: black tag: midblack - TAME: + TUBE: id: Antonym Room/Panel_tame_mate colors: black tag: topblack @@ -1298,7 +1535,8 @@ doors: Shortcut to Hedge Maze: id: Symmetry Room Area Doors/Door_bye_hi - group: Hedge Maze Doors + item_group: Achievement Room Entrances + door_group: Hedge Maze Doors panels: - BYE Hedge Maze: @@ -1306,7 +1544,8 @@ Hub Room: room: Hub Room door: Shortcut to Hedge Maze - Color Hallways: True + Color Hallways: + warp: True The Agreeable: room: The Agreeable door: Shortcut to Hedge Maze @@ -1317,6 +1556,8 @@ room: Owl Hallway door: Shortcut to Hedge Maze Roof: True + The Incomparable: + door: Observant Entrance panels: DOWN: id: Maze Room/Panel_down_up @@ -1391,12 +1632,14 @@ Perceptive Entrance: id: Maze Area Doors/Door_maze_maze item_name: The Perceptive - Entrance - group: Hedge Maze Doors + door_group: Hedge Maze Doors + item_group: Achievement Room Entrances panels: - DOWN Painting Shortcut: painting_id: garden_painting_tower2 item_name: Starting Room - Hedge Maze Painting + item_group: Paintings skip_location: True panels: - DOWN @@ -1407,7 +1650,8 @@ - Maze Area Doors/Door_look_room_3 skip_location: True item_name: The Observant - Entrance - group: Observant Doors + door_group: Observant Doors + item_group: Achievement Room Entrances panels: - room: The Perceptive panel: GAZE @@ -1421,6 +1665,10 @@ - HIDE (3) - room: Outside The Agreeable panel: HIDE + panel_doors: + DOWN: + panels: + - DOWN The Perceptive: entrances: Starting Room: @@ -1442,12 +1690,17 @@ check: True exclude_reduce: True tag: botwhite + panel_doors: + GAZE: + panels: + - GAZE paintings: - id: garden_painting_tower orientation: north The Fearless (First Floor): entrances: - The Perceptive: True + The Perceptive: + warp: True panels: SPAN: id: Naps Room/Panel_naps_span @@ -1473,7 +1726,7 @@ Second Floor: id: Naps Room Doors/Door_hider_5 location_name: The Fearless - First Floor Puzzles - group: Fearless Doors + door_group: Fearless Doors panels: - SPAN - TEAM @@ -1482,14 +1735,16 @@ - EAT progression: Progressive Fearless: - - Second Floor - - room: The Fearless (Second Floor) - door: Third Floor + doors: + - Second Floor + - room: The Fearless (Second Floor) + door: Third Floor The Fearless (Second Floor): entrances: The Fearless (First Floor): room: The Fearless (First Floor) door: Second Floor + warp: True panels: NONE: id: Naps Room/Panel_one_many @@ -1525,7 +1780,7 @@ - Naps Room Doors/Door_hider_1b2 - Naps Room Doors/Door_hider_new1 location_name: The Fearless - Second Floor Puzzles - group: Fearless Doors + door_group: Fearless Doors panels: - NONE - SUM @@ -1539,6 +1794,7 @@ The Fearless (First Floor): room: The Fearless (Second Floor) door: Third Floor + warp: True panels: Achievement: id: Countdown Panels/Panel_fearless_fearless @@ -1567,7 +1823,8 @@ Hedge Maze: room: Hedge Maze door: Observant Entrance - The Incomparable: True + The Incomparable: + warp: True panels: Achievement: id: Countdown Panels/Panel_observant_observant @@ -1576,6 +1833,10 @@ tag: forbid required_door: door: Stairs + required_panel: + - panel: FOUR (1) + - panel: FOUR (2) + - panel: SIX achievement: The Observant FOUR (1): id: Look Room/Panel_four_back @@ -1680,23 +1941,37 @@ doors: Backside Door: id: Maze Area Doors/Door_backside - group: Backside Doors + door_group: Backside Doors panels: - FOUR (1) - FOUR (2) Stairs: id: Maze Area Doors/Door_stairs - group: Observant Doors + door_group: Observant Doors + panels: + - SIX + panel_doors: + BACKSIDE: + item_name: The Observant - Backside Entrance Panels + panel_group: Backside Entrance Panels + panels: + - FOUR (1) + - FOUR (2) + STAIRS: panels: - SIX The Incomparable: entrances: - The Observant: True # Assuming that access to The Observant includes access to the right entrance + The Observant: + warp: True Eight Room: True Eight Alcove: door: Eight Door Orange Tower Sixth Floor: painting: True + Hedge Maze: + room: Hedge Maze + door: Observant Entrance panels: Achievement: id: Countdown Panels/Panel_incomparable_incomparable @@ -1704,9 +1979,12 @@ check: True tag: forbid required_room: - - Elements Area - - Courtyard - Eight Room + required_panel: + - room: Courtyard + panel: I + - room: Elements Area + panel: A achievement: The Incomparable A (One): id: Strand Room/Panel_blank_a @@ -1764,7 +2042,16 @@ Eight Door: id: Red Blue Purple Room Area Doors/Door_a_strands location_name: Giant Sevens - group: Observant Doors + door_group: Observant Doors + panels: + - I (Seven) + - room: Courtyard + panel: I + - room: Elements Area + panel: A + panel_doors: + Giant Sevens: + item_name: Giant Seven Panels panels: - I (Seven) - room: Courtyard @@ -1878,14 +2165,31 @@ panel: DRAWL + RUNS - room: Owl Hallway panel: READS + RUST + panel_doors: + Access: + item_name: Orange Tower Panels + panels: + - room: Orange Tower First Floor + panel: DADS + ALE + - room: Outside The Undeterred + panel: ART + ART + - room: Orange Tower Third Floor + panel: DEER + WREN + - room: Orange Tower Fourth Floor + panel: LEARNS + UNSEW + - room: Orange Tower Fifth Floor + panel: DRAWL + RUNS + - room: Owl Hallway + panel: READS + RUST progression: Progressive Orange Tower: - - Second Floor - - Third Floor - - Fourth Floor - - Fifth Floor - - Sixth Floor - - Seventh Floor + doors: + - Second Floor + - Third Floor + - Fourth Floor + - Fifth Floor + - Sixth Floor + - Seventh Floor Orange Tower First Floor: entrances: Hub Room: @@ -1893,9 +2197,11 @@ Outside The Wanderer: room: Outside The Wanderer door: Tower Entrance + warp: True Orange Tower Second Floor: room: Orange Tower door: Second Floor + warp: True Directional Gallery: door: Salt Pepper Door Roof: True # through the sunwarp @@ -1915,26 +2221,38 @@ doors: Shortcut to Hub Room: id: Shuffle Room Area Doors/Door_secret_secret - group: Orange Tower First Floor - Shortcuts + door_group: Orange Tower First Floor - Shortcuts panels: - SECRET Salt Pepper Door: id: Count Up Room Area Doors/Door_salt_pepper location_name: Orange Tower First Floor - Salt Pepper Door - group: Orange Tower First Floor - Shortcuts + door_group: Orange Tower First Floor - Shortcuts panels: - SALT - room: Directional Gallery panel: PEPPER + panel_doors: + SECRET: + panels: + - SECRET + sunwarps: + - dots: 4 + direction: enter + entrance_indicator_pos: [ -32, 2.5, -14.99 ] + orientation: south Orange Tower Second Floor: entrances: Orange Tower First Floor: room: Orange Tower door: Second Floor + warp: True Orange Tower Third Floor: room: Orange Tower door: Third Floor - Outside The Undeterred: True + warp: True + Outside The Undeterred: + warp: True Orange Tower Third Floor: entrances: Knight Night Exit: @@ -1943,15 +2261,23 @@ Orange Tower Second Floor: room: Orange Tower door: Third Floor + warp: True Orange Tower Fourth Floor: room: Orange Tower door: Fourth Floor - Hot Crusts Area: True # sunwarp - Bearer Side Area: # This is complicated because of The Bearer's topology + warp: True + Hot Crusts Area: + room: Sunwarps + door: 2 Sunwarp + sunwarp: True + Bearer Side Area: room: Bearer Side Area door: Shortcut to Tower Rhyme Room (Smiley): door: Rhyme Room Entrance + Art Gallery: + warp: True + Roof: True # by parkouring through the Bearer shortcut panels: RED: id: Color Arrow Room/Panel_red_afar @@ -1967,7 +2293,7 @@ doors: Red Barrier: id: Color Arrow Room Doors/Door_red_6 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -1975,7 +2301,7 @@ Rhyme Room Entrance: id: Double Room Area Doors/Door_room_entry_stairs2 skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - room: The Tenacious panel: LEVEL (Black) @@ -1989,7 +2315,7 @@ - Color Arrow Room Doors/Door_orange_hider_2 - Color Arrow Room Doors/Door_orange_hider_3 location_name: Color Barriers - RED and YELLOW - group: Color Hunt Barriers + door_group: Color Hunt Barriers item_name: Color Hunt - Orange Barrier panels: - RED @@ -2000,14 +2326,25 @@ orientation: east - id: flower_painting_5 orientation: south + sunwarps: + - dots: 2 + direction: exit + entrance_indicator_pos: [ 24.01, 2.5, 38 ] + orientation: west + - dots: 3 + direction: enter + entrance_indicator_pos: [ 28.01, 2.5, 29 ] + orientation: west Orange Tower Fourth Floor: entrances: Orange Tower Third Floor: room: Orange Tower door: Fourth Floor + warp: True Orange Tower Fifth Floor: room: Orange Tower door: Fifth Floor + warp: True Hot Crusts Area: door: Hot Crusts Door Crossroads: @@ -2015,7 +2352,10 @@ door: Tower Entrance - room: Crossroads door: Tower Back Entrance - Courtyard: True + Courtyard: + - warp: True + - room: Crossroads + door: Tower Entrance Roof: True # through the sunwarp panels: RUNT (1): @@ -2048,6 +2388,15 @@ id: Shuffle Room Area Doors/Door_hotcrust_shortcuts panels: - HOT CRUSTS + panel_doors: + HOT CRUSTS: + panels: + - HOT CRUSTS + sunwarps: + - dots: 5 + direction: enter + entrance_indicator_pos: [ -20, 3, -64.01 ] + orientation: north Hot Crusts Area: entrances: Orange Tower Fourth Floor: @@ -2065,28 +2414,31 @@ paintings: - id: smile_painting_8 orientation: north + sunwarps: + - dots: 2 + direction: enter + entrance_indicator_pos: [ -26, 3.5, -80.01 ] + orientation: north Orange Tower Fifth Floor: entrances: Orange Tower Fourth Floor: room: Orange Tower door: Fifth Floor + warp: True Orange Tower Sixth Floor: room: Orange Tower door: Sixth Floor + warp: True Cellar: room: Room Room door: Cellar Exit + warp: True Welcome Back Area: door: Welcome Back - Art Gallery: - room: Art Gallery - door: Exit - The Bearer: - room: Art Gallery - door: Exit Outside The Initiated: room: Art Gallery door: Exit + warp: True panels: SIZE (Small): id: Entry Room/Panel_size_small @@ -2150,7 +2502,13 @@ doors: Welcome Back: id: Entry Room Area Doors/Door_sizes - group: Welcome Back Doors + door_group: Welcome Back Doors + panels: + - SIZE (Small) + - SIZE (Big) + panel_doors: + SIZE: + item_name: Orange Tower Fifth Floor - SIZE Panels panels: - SIZE (Small) - SIZE (Big) @@ -2166,6 +2524,7 @@ Orange Tower Fifth Floor: room: Orange Tower door: Sixth Floor + warp: True The Scientific: painting: True paintings: @@ -2189,17 +2548,30 @@ orientation: east - id: hi_solved_painting orientation: west - Orange Tower Seventh Floor: + Ending Area: entrances: Orange Tower Sixth Floor: room: Orange Tower door: Seventh Floor + warp: True panels: THE END: id: EndPanel/Panel_end_end check: True tag: forbid non_counting: True + location_name: Orange Tower Seventh Floor - THE END + doors: + End: + event: True + panels: + - THE END + Orange Tower Seventh Floor: + entrances: + Ending Area: + room: Ending Area + door: End + panels: THE MASTER: # We will set up special rules for this in code. id: Countdown Panels/Panel_master_master @@ -2209,6 +2581,7 @@ # This is the MASTERY on the other side of THE FEARLESS. It can only be # accessed by jumping from the top of the tower. id: Master Room/Panel_mastery_mastery8 + location_name: The Fearless - MASTERY tag: midwhite hunt: True required_door: @@ -2370,7 +2743,10 @@ Courtyard: entrances: Roof: True - Orange Tower Fourth Floor: True + Orange Tower Fourth Floor: + - warp: True + - room: Crossroads + door: Tower Entrance Arrow Garden: painting: True Starting Room: @@ -2404,6 +2780,7 @@ Painting Shortcut: painting_id: flower_painting_8 item_name: Starting Room - Flower Painting + item_group: Paintings skip_location: True panels: - room: First Second Third Fourth @@ -2416,7 +2793,7 @@ panel: FOURTH Green Barrier: id: Color Arrow Room Doors/Door_green_5 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -2470,7 +2847,7 @@ doors: Backside Door: id: Count Up Room Area Doors/Door_yellow_backside - group: Backside Doors + door_group: Backside Doors location_name: Courtyard - FIRST, SECOND, THIRD, FOURTH item_name: Courtyard - Backside Door panels: @@ -2478,6 +2855,15 @@ - SECOND - THIRD - FOURTH + panel_doors: + FIRST SECOND THIRD FOURTH: + item_name: Courtyard - Ordinal Panels + panel_group: Backside Entrance Panels + panels: + - FIRST + - SECOND + - THIRD + - FOURTH The Colorful (White): entrances: Courtyard: True @@ -2491,10 +2877,16 @@ Progress Door: id: Doorway Room Doors/Door_white item_name: The Colorful - White Door - group: Colorful Doors + door_group: Colorful Doors location_name: The Colorful - White panels: - BEGIN + panel_doors: + BEGIN: + item_name: The Colorful - BEGIN (Panel) + panel_group: Colorful Panels + panels: + - BEGIN The Colorful (Black): entrances: The Colorful (White): @@ -2512,7 +2904,13 @@ id: Doorway Room Doors/Door_black item_name: The Colorful - Black Door location_name: The Colorful - Black - group: Colorful Doors + door_group: Colorful Doors + panels: + - FOUND + panel_doors: + FOUND: + item_name: The Colorful - FOUND (Panel) + panel_group: Colorful Panels panels: - FOUND The Colorful (Red): @@ -2532,7 +2930,13 @@ id: Doorway Room Doors/Door_red item_name: The Colorful - Red Door location_name: The Colorful - Red - group: Colorful Doors + door_group: Colorful Doors + panels: + - LOAF + panel_doors: + LOAF: + item_name: The Colorful - LOAF (Panel) + panel_group: Colorful Panels panels: - LOAF The Colorful (Yellow): @@ -2552,7 +2956,13 @@ id: Doorway Room Doors/Door_yellow item_name: The Colorful - Yellow Door location_name: The Colorful - Yellow - group: Colorful Doors + door_group: Colorful Doors + panels: + - CREAM + panel_doors: + CREAM: + item_name: The Colorful - CREAM (Panel) + panel_group: Colorful Panels panels: - CREAM The Colorful (Blue): @@ -2572,7 +2982,13 @@ id: Doorway Room Doors/Door_blue item_name: The Colorful - Blue Door location_name: The Colorful - Blue - group: Colorful Doors + door_group: Colorful Doors + panels: + - SUN + panel_doors: + SUN: + item_name: The Colorful - SUN (Panel) + panel_group: Colorful Panels panels: - SUN The Colorful (Purple): @@ -2592,7 +3008,13 @@ id: Doorway Room Doors/Door_purple item_name: The Colorful - Purple Door location_name: The Colorful - Purple - group: Colorful Doors + door_group: Colorful Doors + panels: + - SPOON + panel_doors: + SPOON: + item_name: The Colorful - SPOON (Panel) + panel_group: Colorful Panels panels: - SPOON The Colorful (Orange): @@ -2612,7 +3034,13 @@ id: Doorway Room Doors/Door_orange item_name: The Colorful - Orange Door location_name: The Colorful - Orange - group: Colorful Doors + door_group: Colorful Doors + panels: + - LETTERS + panel_doors: + LETTERS: + item_name: The Colorful - LETTERS (Panel) + panel_group: Colorful Panels panels: - LETTERS The Colorful (Green): @@ -2632,7 +3060,13 @@ id: Doorway Room Doors/Door_green item_name: The Colorful - Green Door location_name: The Colorful - Green - group: Colorful Doors + door_group: Colorful Doors + panels: + - WALLS + panel_doors: + WALLS: + item_name: The Colorful - WALLS (Panel) + panel_group: Colorful Panels panels: - WALLS The Colorful (Brown): @@ -2652,7 +3086,13 @@ id: Doorway Room Doors/Door_brown item_name: The Colorful - Brown Door location_name: The Colorful - Brown - group: Colorful Doors + door_group: Colorful Doors + panels: + - IRON + panel_doors: + IRON: + item_name: The Colorful - IRON (Panel) + panel_group: Colorful Panels panels: - IRON The Colorful (Gray): @@ -2672,7 +3112,13 @@ id: Doorway Room Doors/Door_gray item_name: The Colorful - Gray Door location_name: The Colorful - Gray - group: Colorful Doors + door_group: Colorful Doors + panels: + - OBSTACLE + panel_doors: + OBSTACLE: + item_name: The Colorful - OBSTACLE (Panel) + panel_group: Colorful Panels panels: - OBSTACLE The Colorful: @@ -2713,39 +3159,70 @@ orientation: north progression: Progressive Colorful: - - room: The Colorful (White) - door: Progress Door - - room: The Colorful (Black) - door: Progress Door - - room: The Colorful (Red) - door: Progress Door - - room: The Colorful (Yellow) - door: Progress Door - - room: The Colorful (Blue) - door: Progress Door - - room: The Colorful (Purple) - door: Progress Door - - room: The Colorful (Orange) - door: Progress Door - - room: The Colorful (Green) - door: Progress Door - - room: The Colorful (Brown) - door: Progress Door - - room: The Colorful (Gray) - door: Progress Door + doors: + - room: The Colorful (White) + door: Progress Door + - room: The Colorful (Black) + door: Progress Door + - room: The Colorful (Red) + door: Progress Door + - room: The Colorful (Yellow) + door: Progress Door + - room: The Colorful (Blue) + door: Progress Door + - room: The Colorful (Purple) + door: Progress Door + - room: The Colorful (Orange) + door: Progress Door + - room: The Colorful (Green) + door: Progress Door + - room: The Colorful (Brown) + door: Progress Door + - room: The Colorful (Gray) + door: Progress Door + panel_doors: + - room: The Colorful (White) + panel_door: BEGIN + - room: The Colorful (Black) + panel_door: FOUND + - room: The Colorful (Red) + panel_door: LOAF + - room: The Colorful (Yellow) + panel_door: CREAM + - room: The Colorful (Blue) + panel_door: SUN + - room: The Colorful (Purple) + panel_door: SPOON + - room: The Colorful (Orange) + panel_door: LETTERS + - room: The Colorful (Green) + panel_door: WALLS + - room: The Colorful (Brown) + panel_door: IRON + - room: The Colorful (Gray) + panel_door: OBSTACLE Welcome Back Area: entrances: Starting Room: door: Shortcut to Starting Room - Hub Room: True - Outside The Wondrous: True - Outside The Undeterred: True - Outside The Agreeable: True - Outside The Wanderer: True - The Observant: True - Art Gallery: True - The Scientific: True - Cellar: True + Hub Room: + warp: True + Outside The Wondrous: + warp: True + Outside The Undeterred: + warp: True + Outside The Agreeable: + warp: True + Outside The Wanderer: + warp: True + The Observant: + warp: True + Art Gallery: + warp: True + The Scientific: + warp: True + Cellar: + warp: True Orange Tower Fifth Floor: room: Orange Tower Fifth Floor door: Welcome Back @@ -2768,7 +3245,7 @@ doors: Shortcut to Starting Room: id: Entry Room Area Doors/Door_return_return - group: Welcome Back Doors + door_group: Welcome Back Doors include_reduce: True panels: - WELCOME BACK @@ -2793,7 +3270,11 @@ doors: Shortcut to Hedge Maze: id: Maze Area Doors/Door_strays_maze - group: Hedge Maze Doors + door_group: Hedge Maze Doors + panels: + - STRAYS + panel_doors: + STRAYS: panels: - STRAYS paintings: @@ -2813,10 +3294,21 @@ Knight Night Exit: room: Knight Night (Final) door: Exit - Orange Tower Third Floor: True # sunwarp + Orange Tower Third Floor: + room: Sunwarps + door: 3 Sunwarp + sunwarp: True Orange Tower Fifth Floor: room: Art Gallery door: Exit + warp: True + Art Gallery: + room: Art Gallery + door: Exit + warp: True + The Bearer: + room: Art Gallery + door: Exit Eight Alcove: door: Eight Door The Optimistic: True @@ -2916,14 +3408,14 @@ - UNCOVER Blue Barrier: id: Color Arrow Room Doors/Door_blue_3 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt panel: BLUE Orange Barrier: id: Color Arrow Room Doors/Door_orange_3 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -2931,6 +3423,7 @@ Initiated Entrance: id: Red Blue Purple Room Area Doors/Door_locked_knocked item_name: The Initiated - Entrance + item_group: Achievement Room Entrances panels: - OXEN # These would be more appropriate in Champion's Rest, but as currently @@ -2940,7 +3433,7 @@ id: Color Arrow Room Doors/Door_green_hider_1 location_name: Color Barriers - BLUE and YELLOW item_name: Color Hunt - Green Barrier - group: Color Hunt Barriers + door_group: Color Hunt Barriers panels: - BLUE - room: Directional Gallery @@ -2952,7 +3445,7 @@ - Color Arrow Room Doors/Door_purple_hider_3 location_name: Color Barriers - RED and BLUE item_name: Color Hunt - Purple Barrier - group: Color Hunt Barriers + door_group: Color Hunt Barriers panels: - BLUE - room: Orange Tower Third Floor @@ -2972,6 +3465,7 @@ panel: PURPLE Eight Door: id: Red Blue Purple Room Area Doors/Door_a_strands2 + item_group: Achievement Room Entrances skip_location: True panels: - room: The Incomparable @@ -2980,11 +3474,23 @@ panel: I - room: Elements Area panel: A + panel_doors: + UNCOVER: + panels: + - UNCOVER + OXEN: + panels: + - OXEN paintings: - id: clock_painting_5 orientation: east - id: smile_painting_1 orientation: north + sunwarps: + - dots: 3 + direction: exit + entrance_indicator_pos: [ 89.99, 2.5, 1 ] + orientation: east The Initiated: entrances: Outside The Initiated: @@ -3189,7 +3695,8 @@ doors: Color Hallways Entrance: id: Appendix Room Area Doors/Door_hello_hi - group: Entrance to The Traveled + door_group: Entrance to The Traveled + item_group: Achievement Room Entrances panels: - HELLO Color Hallways: @@ -3197,22 +3704,32 @@ The Traveled: room: The Traveled door: Color Hallways Entrance - Outside The Bold: True - Outside The Undeterred: True - Crossroads: True - Hedge Maze: True - The Optimistic: True # backside - Directional Gallery: True # backside - Yellow Backside Area: True + Outside The Bold: + warp: True + Outside The Undeterred: + warp: True + Crossroads: + warp: True + Hedge Maze: + warp: True + The Optimistic: + warp: True # backside + Directional Gallery: + warp: True # backside + Yellow Backside Area: + warp: True The Bearer: room: The Bearer door: Backside Door + warp: True The Observant: room: The Observant door: Backside Door + warp: True Outside The Bold: entrances: - Color Hallways: True + Color Hallways: + warp: True Color Hunt: room: Color Hunt door: Shortcut to The Steady @@ -3230,7 +3747,7 @@ door: Painting Shortcut painting: True Room Room: True # trapdoor - Outside The Agreeable: + Compass Room: painting: True panels: UNOPEN: @@ -3305,17 +3822,20 @@ Bold Entrance: id: Red Blue Purple Room Area Doors/Door_unopened_open item_name: The Bold - Entrance + item_group: Achievement Room Entrances panels: - UNOPEN Painting Shortcut: painting_id: pencil_painting6 skip_location: True item_name: Starting Room - Pencil Painting + item_group: Paintings panels: - UNOPEN Steady Entrance: id: Rock Room Doors/Door_2 item_name: The Steady - Entrance + item_group: Achievement Room Entrances panels: - BEGIN Lilac Entrance: @@ -3330,6 +3850,13 @@ - RISE (Sunrise) - ZEN - SON + panel_doors: + UNOPEN: + panels: + - UNOPEN + BEGIN: + panels: + - BEGIN paintings: - id: pencil_painting2 orientation: west @@ -3429,13 +3956,22 @@ tag: botred Outside The Undeterred: entrances: - Color Hallways: True - Orange Tower First Floor: True # sunwarp - Orange Tower Second Floor: True - The Artistic (Smiley): True - The Artistic (Panda): True - The Artistic (Apple): True - The Artistic (Lattice): True + Color Hallways: + warp: True + Orange Tower First Floor: + room: Sunwarps + door: 4 Sunwarp + sunwarp: True + Orange Tower Second Floor: + warp: True + The Artistic (Smiley): + warp: True + The Artistic (Panda): + warp: True + The Artistic (Apple): + warp: True + The Artistic (Lattice): + warp: True Yellow Backside Area: painting: True Number Hunt: @@ -3536,6 +4072,7 @@ Undeterred Entrance: id: Red Blue Purple Room Area Doors/Door_pen_open item_name: The Undeterred - Entrance + item_group: Achievement Room Entrances panels: - PEN Painting Shortcut: @@ -3544,11 +4081,13 @@ - arrows_painting3 skip_location: True item_name: Starting Room - Blue Painting + item_group: Paintings panels: - PEN Green Painting: painting_id: maze_painting_3 skip_location: True + item_group: Paintings panels: - FOUR Twos: @@ -3556,6 +4095,7 @@ - Count Up Room Area Doors/Door_two_hider - Count Up Room Area Doors/Door_two_hider_2 include_reduce: True + item_group: Numbers panels: - ONE Threes: @@ -3565,6 +4105,7 @@ - Count Up Room Area Doors/Door_three_hider_3 location_name: Twos include_reduce: True + item_group: Numbers panels: - TWO (1) - TWO (2) @@ -3583,18 +4124,54 @@ - Count Up Room Area Doors/Door_four_hider_3 - Count Up Room Area Doors/Door_four_hider_4 skip_location: True + item_group: Numbers + panels: + - THREE (1) + - THREE (2) + - THREE (3) + Fives: + id: + - Count Up Room Area Doors/Door_five_hider + - Count Up Room Area Doors/Door_five_hider_4 + - Count Up Room Area Doors/Door_five_hider_5 + location_name: Fours + item_name: Number Hunt - Fives + item_group: Numbers + include_reduce: True + panels: + - FOUR + - room: Hub Room + panel: FOUR + - room: Dead End Area + panel: FOUR + - room: The Traveled + panel: FOUR + Challenge Entrance: + id: Count Up Room Area Doors/Door_zero_unlocked + item_name: Number Hunt - Challenge Entrance + item_group: Achievement Room Entrances + panels: + - ZERO + panel_doors: + ZERO: + panels: + - ZERO + PEN: + panels: + - PEN + TWO: + item_name: Two Panels + panels: + - TWO (1) + - TWO (2) + THREE: + item_name: Three Panels panels: - THREE (1) - THREE (2) - THREE (3) - Fives: - id: - - Count Up Room Area Doors/Door_five_hider - - Count Up Room Area Doors/Door_five_hider_4 - - Count Up Room Area Doors/Door_five_hider_5 - location_name: Fours - item_name: Number Hunt - Fives - include_reduce: True + FOUR: + item_name: Four Panels panels: - FOUR - room: Hub Room @@ -3603,11 +4180,6 @@ panel: FOUR - room: The Traveled panel: FOUR - Challenge Entrance: - id: Count Up Room Area Doors/Door_zero_unlocked - item_name: Number Hunt - Challenge Entrance - panels: - - ZERO paintings: - id: maze_painting_3 enter_only: True @@ -3617,6 +4189,11 @@ door: Green Painting - id: blueman_painting_2 orientation: east + sunwarps: + - dots: 4 + direction: exit + entrance_indicator_pos: [ -89.01, 2.5, 4 ] + orientation: east The Undeterred: entrances: Outside The Undeterred: @@ -3752,7 +4329,7 @@ doors: Door to Directional Gallery: id: Count Up Room Area Doors/Door_five_unlocked - group: Directional Gallery Doors + door_group: Directional Gallery Doors skip_location: True panels: - FIVE @@ -3766,6 +4343,7 @@ - Count Up Room Area Doors/Door_six_hider_6 painting_id: pencil_painting3 # See note in Outside The Bold location_name: Fives + item_group: Numbers include_reduce: True panels: - FIVE @@ -3777,6 +4355,10 @@ panel: FIVE (1) - room: Directional Gallery panel: FIVE (2) + First Six: + event: True + panels: + - SIX Sevens: id: - Count Up Room Area Doors/Door_seven_hider @@ -3788,6 +4370,7 @@ - Count Up Room Area Doors/Door_seven_hider_6 - Count Up Room Area Doors/Door_seven_hider_7 location_name: Sixes + item_group: Numbers include_reduce: True panels: - SIX @@ -3813,6 +4396,7 @@ - Count Up Room Area Doors/Door_eight_hider_7 - Count Up Room Area Doors/Door_eight_hider_8 location_name: Sevens + item_group: Numbers include_reduce: True panels: - SEVEN @@ -3840,6 +4424,7 @@ - Count Up Room Area Doors/Door_nine_hider_8 - Count Up Room Area Doors/Door_nine_hider_9 location_name: Eights + item_group: Numbers include_reduce: True panels: - EIGHT @@ -3862,6 +4447,7 @@ id: Count Up Room Area Doors/Door_zero_hider_2 location_name: Nines item_name: Outside The Undeterred - Zero Door + item_group: Numbers include_reduce: True panels: - NINE @@ -3881,15 +4467,115 @@ panel: NINE - room: Elements Area panel: NINE + panel_doors: + FIVE: + item_name: Five Panels + panels: + - FIVE + - room: Outside The Agreeable + panel: FIVE (1) + - room: Outside The Agreeable + panel: FIVE (2) + - room: Directional Gallery + panel: FIVE (1) + - room: Directional Gallery + panel: FIVE (2) + SIX: + item_name: Six Panels + panels: + - SIX + - room: Outside The Bold + panel: SIX + - room: Directional Gallery + panel: SIX (1) + - room: Directional Gallery + panel: SIX (2) + - room: The Bearer (East) + panel: SIX + - room: The Bearer (South) + panel: SIX + SEVEN: + item_name: Seven Panels + panels: + - SEVEN + - room: Directional Gallery + panel: SEVEN + - room: Knight Night Exit + panel: SEVEN (1) + - room: Knight Night Exit + panel: SEVEN (2) + - room: Knight Night Exit + panel: SEVEN (3) + - room: Outside The Initiated + panel: SEVEN (1) + - room: Outside The Initiated + panel: SEVEN (2) + EIGHT: + item_name: Eight Panels + panels: + - EIGHT + - room: Directional Gallery + panel: EIGHT + - room: The Eyes They See + panel: EIGHT + - room: Dead End Area + panel: EIGHT + - room: Crossroads + panel: EIGHT + - room: Hot Crusts Area + panel: EIGHT + - room: Art Gallery + panel: EIGHT + - room: Outside The Initiated + panel: EIGHT + NINE: + item_name: Nine Panels + panels: + - NINE + - room: Directional Gallery + panel: NINE + - room: Amen Name Area + panel: NINE + - room: Yellow Backside Area + panel: NINE + - room: Outside The Initiated + panel: NINE + - room: Outside The Bold + panel: NINE + - room: Rhyme Room (Cross) + panel: NINE + - room: Orange Tower Fifth Floor + panel: NINE + - room: Elements Area + panel: NINE paintings: - id: smile_painting_5 enter_only: True orientation: east required_door: door: Eights + progression: + Progressive Number Hunt: + panel_doors: + - room: Outside The Undeterred + panel_door: TWO + - room: Outside The Undeterred + panel_door: THREE + - room: Outside The Undeterred + panel_door: FOUR + - FIVE + - SIX + - SEVEN + - EIGHT + - NINE + - room: Outside The Undeterred + panel_door: ZERO Directional Gallery: entrances: - Outside The Agreeable: True # sunwarp + Outside The Agreeable: + room: Sunwarps + door: 6 Sunwarp + sunwarp: True Orange Tower First Floor: room: Orange Tower First Floor door: Salt Pepper Door @@ -3898,6 +4584,7 @@ Number Hunt: room: Number Hunt door: Door to Directional Gallery + Roof: True # through ceiling of sunwarp panels: PEPPER: id: Backside Room/Panel_pepper_salt @@ -3970,7 +4657,7 @@ tag: midorange required_door: room: Number Hunt - door: Sixes + door: First Six PARANOID: id: Backside Room/Panel_paranoid_paranoid tag: midwhite @@ -3978,7 +4665,7 @@ exclude_reduce: True required_door: room: Number Hunt - door: Sixes + door: First Six YELLOW: id: Color Arrow Room/Panel_yellow_afar tag: midwhite @@ -4030,17 +4717,22 @@ doors: Shortcut to The Undeterred: id: Count Up Room Area Doors/Door_return_double - group: Directional Gallery Doors + door_group: Directional Gallery Doors panels: - TURN - LEARN Yellow Barrier: id: Color Arrow Room Doors/Door_yellow_4 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt panel: YELLOW + panel_doors: + TURN LEARN: + panels: + - TURN + - LEARN paintings: - id: smile_painting_7 orientation: south @@ -4052,16 +4744,24 @@ move: True required_door: room: Number Hunt - door: Sixes + door: First Six - id: boxes_painting orientation: south - id: cherry_painting orientation: east + sunwarps: + - dots: 6 + direction: exit + entrance_indicator_pos: [ -39, 2.5, -7.01 ] + orientation: north Color Hunt: entrances: Outside The Bold: door: Shortcut to The Steady - Orange Tower Fourth Floor: True # sunwarp + Orange Tower Fourth Floor: + room: Sunwarps + door: 5 Sunwarp + sunwarp: True Roof: True # through ceiling of sunwarp Champion's Rest: room: Outside The Initiated @@ -4111,6 +4811,34 @@ id: Rock Room Doors/Door_hint panels: - EXIT + panel_doors: + EXIT: + panels: + - EXIT + RED: + panel_group: Color Hunt Panels + panels: + - RED + BLUE: + panel_group: Color Hunt Panels + panels: + - BLUE + YELLOW: + panel_group: Color Hunt Panels + panels: + - YELLOW + ORANGE: + panel_group: Color Hunt Panels + panels: + - ORANGE + PURPLE: + panel_group: Color Hunt Panels + panels: + - PURPLE + GREEN: + panel_group: Color Hunt Panels + panels: + - GREEN paintings: - id: arrows_painting_7 orientation: east @@ -4120,6 +4848,11 @@ required_door: room: Outside The Initiated door: Entrance + sunwarps: + - dots: 5 + direction: exit + entrance_indicator_pos: [ 54, 2.5, 69.99 ] + orientation: north Champion's Rest: entrances: Color Hunt: @@ -4153,7 +4886,7 @@ entrances: Outside The Bold: door: Entrance - Orange Tower Fifth Floor: + Outside The Initiated: room: Art Gallery door: Exit The Bearer (East): True @@ -4231,17 +4964,26 @@ doors: Entrance: id: Red Blue Purple Room Area Doors/Door_middle_middle + item_group: Achievement Room Entrances panels: - MIDDLE Backside Door: id: Red Blue Purple Room Area Doors/Door_locked_knocked2 # yeah... - group: Backside Doors + door_group: Backside Doors panels: - FARTHER East Entrance: event: True panels: - HEART + panel_doors: + FARTHER: + panel_group: Backside Entrance Panels + panels: + - FARTHER + MIDDLE: + panels: + - MIDDLE The Bearer (East): entrances: Cross Tower (East): True @@ -4600,7 +5342,8 @@ tag: midyellow The Steady (Lime): entrances: - The Steady (Sunflower): True + The Steady (Sunflower): + warp: True The Steady (Emerald): room: The Steady door: Reveal @@ -4622,7 +5365,8 @@ orientation: south The Steady (Lemon): entrances: - The Steady (Emerald): True + The Steady (Emerald): + warp: True The Steady (Orange): room: The Steady door: Reveal @@ -4952,7 +5696,7 @@ colors: - red - blue - tag: mid red blue + tag: chain mid red blue required_panel: - room: Knight Night (Right Lower Segment) panel: ADJUST @@ -4979,8 +5723,10 @@ Knight Night (Outer Ring): room: Knight Night (Outer Ring) door: Fore Door + warp: True Knight Night (Right Lower Segment): door: Segment Door + warp: True panels: RUST (1): id: Appendix Room/Panel_rust_trust @@ -5009,9 +5755,11 @@ Knight Night (Right Upper Segment): room: Knight Night (Right Upper Segment) door: Segment Door + warp: True Knight Night (Outer Ring): room: Knight Night (Outer Ring) door: New Door + warp: True panels: ADJUST: id: Appendix Room/Panel_adjust_readjusted @@ -5057,9 +5805,11 @@ Knight Night (Outer Ring): room: Knight Night (Outer Ring) door: To End + warp: True Knight Night (Right Upper Segment): room: Knight Night (Outer Ring) door: To End + warp: True panels: TRUSTED: id: Appendix Room/Panel_trusted_readjusted @@ -5086,6 +5836,11 @@ item_name: Knight Night Room - Exit panels: - TRUSTED + panel_doors: + TRUSTED: + item_name: Knight Night Room - TRUSTED (Panel) + panels: + - TRUSTED Knight Night Exit: entrances: Knight Night (Outer Ring): @@ -5168,6 +5923,7 @@ - The Artistic (Apple) - The Artistic (Lattice) check: True + location_name: The Artistic - Achievement achievement: The Artistic FINE: id: Ceiling Room/Panel_yellow_top_5 @@ -5223,7 +5979,7 @@ - Ceiling Room Doors/Door_blue - Ceiling Room Doors/Door_blue2 location_name: The Artistic - Smiley and Panda - group: Artistic Doors + door_group: Artistic Doors panels: - FINE - BLADE @@ -5255,7 +6011,7 @@ entrances: Orange Tower Sixth Floor: painting: True - Outside The Agreeable: + Hallway Room (1): painting: True The Artistic (Smiley): room: The Artistic (Smiley) @@ -5333,7 +6089,7 @@ - Ceiling Room Doors/Door_red - Ceiling Room Doors/Door_red2 location_name: The Artistic - Panda and Lattice - group: Artistic Doors + door_group: Artistic Doors panels: - EYE (Top) - EYE (Bottom) @@ -5444,7 +6200,7 @@ - Ceiling Room Doors/Door_black - Ceiling Room Doors/Door_black2 location_name: The Artistic - Lattice and Apple - group: Artistic Doors + door_group: Artistic Doors panels: - POSH - MALL @@ -5557,7 +6313,7 @@ - Ceiling Room Doors/Door_yellow - Ceiling Room Doors/Door_yellow2 location_name: The Artistic - Apple and Smiley - group: Artistic Doors + door_group: Artistic Doors panels: - SPRIG - RELEASES @@ -5706,7 +6462,8 @@ painting: True Wondrous Lobby: door: Exit - Directional Gallery: True + Directional Gallery: + warp: True panels: NEAR: id: Shuffle Room/Panel_near_near @@ -5721,7 +6478,7 @@ doors: Exit: id: Count Up Room Area Doors/Door_near_near - group: Crossroads Doors + door_group: Crossroads Doors panels: - NEAR paintings: @@ -5741,7 +6498,8 @@ tag: midwhite Wondrous Lobby: entrances: - Directional Gallery: True + Directional Gallery: + warp: True The Eyes They See: room: The Eyes They See door: Exit @@ -5750,10 +6508,12 @@ orientation: east Outside The Wondrous: entrances: - Wondrous Lobby: True + Wondrous Lobby: + warp: True The Wondrous (Doorknob): door: Wondrous Entrance - The Wondrous (Window): True + The Wondrous (Window): + warp: True panels: SHRINK: id: Wonderland Room/Panel_shrink_shrink @@ -5762,6 +6522,11 @@ Wondrous Entrance: id: Red Blue Purple Room Area Doors/Door_wonderland item_name: The Wondrous - Entrance + item_group: Achievement Room Entrances + panels: + - SHRINK + panel_doors: + SHRINK: panels: - SHRINK The Wondrous (Doorknob): @@ -5774,7 +6539,9 @@ painting: True The Wondrous (Chandelier): painting: True - The Wondrous (Table): True # There is a way that doesn't use the painting + The Wondrous (Table): + - painting: True + - warp: True doors: Painting Shortcut: painting_id: @@ -5782,6 +6549,7 @@ - arrows_painting2 skip_location: True item_name: Starting Room - Symmetry Painting + item_group: Paintings panels: - room: Outside The Wondrous panel: SHRINK @@ -5816,7 +6584,7 @@ paintings: - id: symmetry_painting_a_5 orientation: east - - id: symmetry_painting_a_5 + - id: symmetry_painting_b_5 disable: True The Wondrous (Window): entrances: @@ -5859,7 +6627,8 @@ required: True The Wondrous: entrances: - The Wondrous (Table): True + The Wondrous (Table): + warp: True Arrow Garden: door: Exit panels: @@ -5886,6 +6655,7 @@ doors: Exit: id: Red Blue Purple Room Area Doors/Door_wonderland_exit + item_group: Paintings painting_id: arrows_painting_9 include_reduce: True panels: @@ -5924,11 +6694,88 @@ paintings: - id: flower_painting_6 orientation: south - Hallway Room (2): + Hallway Room (1): entrances: Outside The Agreeable: - room: Outside The Agreeable - door: Hallway Door + warp: True + Hallway Room (2): + warp: True + Hallway Room (3): + warp: True + Hallway Room (4): + warp: True + panels: + OUT: + id: Hallway Room/Panel_out_out + check: True + exclude_reduce: True + tag: midwhite + WALL: + id: Hallway Room/Panel_castle_1 + colors: blue + tag: quad bot blue + link: qbb CASTLE + KEEP: + id: Hallway Room/Panel_castle_2 + colors: blue + tag: quad bot blue + link: qbb CASTLE + BAILEY: + id: Hallway Room/Panel_castle_3 + colors: blue + tag: quad bot blue + link: qbb CASTLE + TOWER: + id: Hallway Room/Panel_castle_4 + colors: blue + tag: quad bot blue + link: qbb CASTLE + doors: + Exit: + id: Red Blue Purple Room Area Doors/Door_room_2 + door_group: Hallway Room Doors + location_name: Hallway Room - First Room + panels: + - WALL + - KEEP + - BAILEY + - TOWER + panel_doors: + CASTLE: + item_name: Hallway Room - First Room Panels + panel_group: Hallway Room Panels + panels: + - WALL + - KEEP + - BAILEY + - TOWER + paintings: + - id: panda_painting + orientation: south + progression: + Progressive Hallway Room: + doors: + - Exit + - room: Hallway Room (2) + door: Exit + - room: Hallway Room (3) + door: Exit + - room: Hallway Room (4) + door: Exit + panel_doors: + - CASTLE + - room: Hallway Room (2) + panel_door: COUNTERCLOCKWISE + - room: Hallway Room (3) + panel_door: TRANSFORMATION + - room: Hallway Room (4) + panel_door: WHEELBARROW + Hallway Room (2): + entrances: + Hallway Room (1): + room: Hallway Room (1) + door: Exit + warp: True Elements Area: True panels: WISE: @@ -5955,7 +6802,16 @@ Exit: id: Red Blue Purple Room Area Doors/Door_room_3 location_name: Hallway Room - Second Room - group: Hallway Room Doors + door_group: Hallway Room Doors + panels: + - WISE + - CLOCK + - ER + - COUNT + panel_doors: + COUNTERCLOCKWISE: + item_name: Hallway Room - Second Room Panels + panel_group: Hallway Room Panels panels: - WISE - CLOCK @@ -5966,6 +6822,7 @@ Hallway Room (2): room: Hallway Room (2) door: Exit + warp: True # No entrance from Elements Area. The winding hallway does not connect. panels: TRANCE: @@ -5992,7 +6849,16 @@ Exit: id: Red Blue Purple Room Area Doors/Door_room_4 location_name: Hallway Room - Third Room - group: Hallway Room Doors + door_group: Hallway Room Doors + panels: + - TRANCE + - FORM + - A + - SHUN + panel_doors: + TRANSFORMATION: + item_name: Hallway Room - Third Room Panels + panel_group: Hallway Room Panels panels: - TRANCE - FORM @@ -6003,6 +6869,7 @@ Hallway Room (3): room: Hallway Room (3) door: Exit + warp: True Elements Area: True panels: WHEEL: @@ -6014,17 +6881,24 @@ id: - Red Blue Purple Room Area Doors/Door_room_5 - Red Blue Purple Room Area Doors/Door_room_6 # this is the connection to The Artistic - group: Hallway Room Doors + door_group: Hallway Room Doors location_name: Hallway Room - Fourth Room panels: - WHEEL include_reduce: True + panel_doors: + WHEELBARROW: + item_name: Hallway Room - WHEEL + panel_group: Hallway Room Panels + panels: + - WHEEL Elements Area: entrances: Roof: True Hallway Room (4): room: Hallway Room (4) door: Exit + # If this door is open, then a non-warp entrance from the first hallway room is available The Artistic (Smiley): room: Hallway Room (4) door: Exit @@ -6069,6 +6943,7 @@ entrances: Orange Tower First Floor: door: Tower Entrance + warp: True Rhyme Room (Cross): room: Rhyme Room (Cross) door: Exit @@ -6082,6 +6957,7 @@ Wanderer Entrance: id: Tower Room Area Doors/Door_wanderer_entrance item_name: The Wanderer - Entrance + item_group: Achievement Room Entrances panels: - WANDERLUST Tower Entrance: @@ -6090,11 +6966,16 @@ panels: - room: The Wanderer panel: Achievement + panel_doors: + WANDERLUST: + panels: + - WANDERLUST The Wanderer: entrances: Outside The Wanderer: room: Outside The Wanderer door: Wanderer Entrance + warp: True panels: Achievement: id: Countdown Panels/Panel_1234567890_wanderlust @@ -6136,12 +7017,17 @@ tag: midorange Art Gallery: entrances: - Orange Tower Third Floor: True - Art Gallery (Second Floor): True - Art Gallery (Third Floor): True - Art Gallery (Fourth Floor): True - Orange Tower Fifth Floor: + Orange Tower Third Floor: + warp: True + Art Gallery (Second Floor): + warp: True + Art Gallery (Third Floor): + warp: True + Art Gallery (Fourth Floor): + warp: True + Outside The Initiated: door: Exit + warp: True panels: EIGHT: id: Backside Room/Panel_eight_eight_6 @@ -6222,6 +7108,11 @@ id: Tower Room Area Doors/Door_painting_exit include_reduce: True item_name: Orange Tower Fifth Floor - Quadruple Intersection + item_group: Achievement Room Entrances + panels: + - ORDER + panel_doors: + ORDER: panels: - ORDER paintings: @@ -6237,10 +7128,11 @@ orientation: south progression: Progressive Art Gallery: - - Second Floor - - Third Floor - - Fourth Floor - - Fifth Floor + doors: + - Second Floor + - Third Floor + - Fourth Floor + - Fifth Floor Art Gallery (Second Floor): entrances: Art Gallery: @@ -6417,7 +7309,7 @@ - Double Room Area Doors/Door_room_3a - Double Room Area Doors/Door_room_3bc skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - SCHEME - FANTASY @@ -6511,14 +7403,11 @@ tag: syn rhyme subtag: bot link: rhyme FALL - LEAP: - id: Double Room/Panel_leap_leap - tag: midwhite doors: Exit: id: Double Room Area Doors/Door_room_exit location_name: Rhyme Room (Cross) - Exit Puzzles - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - PLUMP - BOUNCE @@ -6581,7 +7470,7 @@ - Double Room Area Doors/Door_room_2b - Double Room Area Doors/Door_room_3b location_name: Rhyme Room - Circle/Smiley Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - BIRD - LETTER @@ -6664,7 +7553,7 @@ - Double Room Area Doors/Door_room_2a - Double Room Area Doors/Door_room_1c location_name: Rhyme Room - Circle/Looped Square Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - WALKED - OBSTRUCTED @@ -6683,7 +7572,7 @@ - Double Room Area Doors/Door_room_1a - Double Room Area Doors/Door_room_5a location_name: Rhyme Room - Cross/Looped Square Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - SKIES - SWELL @@ -6702,7 +7591,7 @@ - Double Room Area Doors/Door_room_1b - Double Room Area Doors/Door_room_4b location_name: Rhyme Room - Target/Looped Square Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - PENNED - CLIMB @@ -6721,6 +7610,7 @@ Rhyme Room (Smiley): # one-way room: Rhyme Room (Smiley) door: Door to Target + warp: True Rhyme Room (Looped Square): room: Rhyme Room (Looped Square) door: Door to Target @@ -6761,11 +7651,16 @@ tag: syn rhyme subtag: bot link: rhyme CREATIVE + LEAP: + id: Double Room/Panel_leap_leap + tag: midwhite + required_door: + door: Door to Cross doors: Door to Cross: id: Double Room Area Doors/Door_room_4a location_name: Rhyme Room (Target) - Puzzles Toward Cross - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - PISTOL - GEM @@ -6784,7 +7679,8 @@ # For pretty much the same reason, I don't want to shuffle the paintings in # here. entrances: - Orange Tower Fourth Floor: True + Orange Tower Fourth Floor: + warp: True panels: DOOR (1): id: Panel Room/Panel_room_door_1 @@ -6950,8 +7846,8 @@ id: Panel Room/Panel_broomed_bedroom colors: yellow tag: midyellow - required_door: - door: Excavation + required_panel: + panel: WALL (1) LAYS: id: Panel Room/Panel_lays_maze colors: purple @@ -6967,6 +7863,7 @@ MASTERY: id: Master Room/Panel_mastery_mastery tag: midwhite + hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery @@ -6977,13 +7874,24 @@ Excavation: event: True panels: - - WALL (1) + - STAIRS Cellar Exit: id: - Tower Room Area Doors/Door_panel_basement - Tower Room Area Doors/Door_panel_basement2 panels: - BASE + panel_doors: + STAIRS: + panel_group: Room Room Panels + panels: + - STAIRS + Colors: + panel_group: Room Room Panels + panels: + - BROOMED + - LAYS + - BASE Cellar: entrances: Room Room: @@ -6992,9 +7900,11 @@ Orange Tower Fifth Floor: room: Room Room door: Cellar Exit - Outside The Agreeable: - room: Outside The Agreeable + warp: True + Compass Room: + room: Compass Room door: Lookout Entrance + warp: True Outside The Wise: entrances: Orange Tower Sixth Floor: @@ -7016,6 +7926,12 @@ Wise Entrance: id: Clock Room Area Doors/Door_time_start item_name: The Wise - Entrance + item_group: Achievement Room Entrances + panels: + - KITTEN + - CAT + panel_doors: + KITTEN CAT: panels: - KITTEN - CAT @@ -7031,6 +7947,7 @@ Outside The Wise: room: Outside The Wise door: Wise Entrance + warp: True # The Wise is so full of warps panels: Achievement: id: Countdown Panels/Panel_intelligent_wise @@ -7269,6 +8186,11 @@ Scientific Entrance: id: Red Blue Purple Room Area Doors/Door_chemistry_lab item_name: The Scientific - Entrance + item_group: Achievement Room Entrances + panels: + - OPEN + panel_doors: + OPEN: panels: - OPEN The Scientific: @@ -7704,5 +8626,6 @@ doors: Welcome Door: id: Entry Room Area Doors/Door_challenge_challenge + item_group: Achievement Room Entrances panels: - WELCOME diff --git a/worlds/lingo/data/README.md b/worlds/lingo/data/README.md new file mode 100644 index 000000000000..fe834cef0d47 --- /dev/null +++ b/worlds/lingo/data/README.md @@ -0,0 +1,5 @@ +# lingo data + +The source of truth for the Lingo randomizer is (currently) the LL1.yaml and ids.yaml files located here. These files are used by the generator, the game client, and the tracker, in order to have logic that is consistent across them all. + +The generator does not actually read in the yaml files. Instead, a compiled datafile called generated.dat is also located in this directory. If you update LL1.yaml and/or ids.yaml, you must also regenerate the datafile using `python worlds/lingo/utils/pickle_static_data.py`. A unit test will fail if you don't. diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat new file mode 100644 index 0000000000000000000000000000000000000000..9a49d3d9d4b9078c1d8244f5023cb1d49584241a GIT binary patch literal 149055 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
    :` on the textfield on top and press enter (if the +server uses password, type in the bottom textfield `/connect
    : [password]`) diff --git a/worlds/mmbn3/Options.py b/worlds/mmbn3/Options.py index 96a01290a5c7..4ed64e3d9dbf 100644 --- a/worlds/mmbn3/Options.py +++ b/worlds/mmbn3/Options.py @@ -1,4 +1,5 @@ -from Options import Choice, Range, DefaultOnToggle +from dataclasses import dataclass +from Options import Choice, Range, DefaultOnToggle, PerGameCommonOptions class ExtraRanks(Range): @@ -41,8 +42,9 @@ class TradeQuestHinting(Choice): default = 2 -MMBN3Options = { - "extra_ranks": ExtraRanks, - "include_jobs": IncludeJobs, - "trade_quest_hinting": TradeQuestHinting, -} +@dataclass +class MMBN3Options(PerGameCommonOptions): + extra_ranks: ExtraRanks + include_jobs: IncludeJobs + trade_quest_hinting: TradeQuestHinting + \ No newline at end of file diff --git a/worlds/mmbn3/__init__.py b/worlds/mmbn3/__init__.py index 762bfd11ae4a..97725e728bae 100644 --- a/worlds/mmbn3/__init__.py +++ b/worlds/mmbn3/__init__.py @@ -7,6 +7,7 @@ LocationProgressType from worlds.AutoWorld import WebWorld, World + from .Rom import MMBN3DeltaPatch, LocalRom, get_base_rom_path from .Items import MMBN3Item, ItemData, item_table, all_items, item_frequencies, items_by_id, ItemType from .Locations import Location, MMBN3Location, all_locations, location_table, location_data_table, \ @@ -51,12 +52,11 @@ class MMBN3World(World): threat the Internet has ever faced! """ game = "MegaMan Battle Network 3" - option_definitions = MMBN3Options + options_dataclass = MMBN3Options + options: MMBN3Options settings: typing.ClassVar[MMBN3Settings] topology_present = False - data_version = 1 - item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations} @@ -71,10 +71,10 @@ def generate_early(self) -> None: Already has access to player options and RNG. """ self.item_frequencies = item_frequencies.copy() - if self.multiworld.extra_ranks[self.player] > 0: - self.item_frequencies[ItemName.Progressive_Undernet_Rank] = 8 + self.multiworld.extra_ranks[self.player] + if self.options.extra_ranks > 0: + self.item_frequencies[ItemName.Progressive_Undernet_Rank] = 8 + self.options.extra_ranks - if not self.multiworld.include_jobs[self.player]: + if not self.options.include_jobs: self.excluded_locations = always_excluded_locations + [job.name for job in jobs] else: self.excluded_locations = always_excluded_locations @@ -160,7 +160,7 @@ def create_items(self) -> None: remaining = len(all_locations) - len(required_items) for i in range(remaining): - filler_item_name = self.multiworld.random.choice(filler_items) + filler_item_name = self.random.choice(filler_items) item = self.create_item(filler_item_name) self.multiworld.itempool.append(item) filler_items.remove(filler_item_name) @@ -411,10 +411,10 @@ def generate_output(self, output_directory: str) -> None: long_item_text = "" # No item hinting - if self.multiworld.trade_quest_hinting[self.player] == 0: + if self.options.trade_quest_hinting == 0: item_name_text = "Check" # Partial item hinting - elif self.multiworld.trade_quest_hinting[self.player] == 1: + elif self.options.trade_quest_hinting == 1: if item.progression == ItemClassification.progression \ or item.progression == ItemClassification.progression_skip_balancing: item_name_text = "Progress" @@ -466,7 +466,7 @@ def create_event(self, event: str): return MMBN3Item(event, ItemClassification.progression, None, self.player) def fill_slot_data(self): - return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions} + return self.options.as_dict("extra_ranks", "include_jobs", "trade_quest_hinting") def explore_score(self, state): diff --git a/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md b/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md index 7ffa4665fd2a..bb9d2c15af2c 100644 --- a/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md +++ b/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md @@ -1,8 +1,8 @@ # MegaMan Battle Network 3 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/mmbn3/docs/setup_en.md b/worlds/mmbn3/docs/setup_en.md index e9181ea54881..b26403f78bb9 100644 --- a/worlds/mmbn3/docs/setup_en.md +++ b/worlds/mmbn3/docs/setup_en.md @@ -18,11 +18,12 @@ on Steam, you can obtain a copy of this ROM from the game's files, see instructi Once Bizhawk has been installed, open Bizhawk and change the following settings: -- Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to - "Lua+LuaInterface". This is required for the Lua script to function correctly. - **NOTE: Even if "Lua+LuaInterface" is already selected, toggle between the two options and reselect it. Fresh installs** - **of newer versions of Bizhawk have a tendency to show "Lua+LuaInterface" as the default selected option but still load** - **"NLua+KopiLua" until this step is done.** +- **If you are using a version of BizHawk older than 2.9**, you will need to modify the Lua Core. + Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to + "Lua+LuaInterface". This is required for the Lua script to function correctly. + **NOTE:** Even if "Lua+LuaInterface" is already selected, toggle between the two options and reselect it. Fresh installs + of newer versions of Bizhawk have a tendency to show "Lua+LuaInterface" as the default selected option but still load + "NLua+KopiLua" until this step is done. - Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button. This reduces the possibility of losing save data in emulator crashes. - Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to @@ -37,7 +38,7 @@ and select EmuHawk.exe. ## Extracting a ROM from the Legacy Collection -The Steam version of the Legacy Collection contains unmodified GBA ROMs in its files. You can extract these for use with Archipelago. +The Steam version of the Battle Network Legacy Collection contains unmodified GBA ROMs in its files. You can extract these for use with Archipelago. 1. Open the Legacy Collection Vol. 1's Game Files (Right click on the game in your Library, then open Properties -> Installed Files -> Browse) 2. Open the file `exe/data/exe3b.dat` in a zip-extracting program such as 7-Zip or WinRAR. @@ -53,8 +54,8 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -You can customize your settings by visiting the -[MegaMan Battle Network 3 Player Settings Page](/games/MegaMan%20Battle%20Network%203/player-settings) +You can customize your options by visiting the +[MegaMan Battle Network 3 Player Options Page](/games/MegaMan%20Battle%20Network%203/player-options) ## Joining a MultiWorld Game @@ -73,7 +74,9 @@ to the emulator as recommended). Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools" menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script. -Navigate to your Archipelago install folder and open `data/lua/connector_mmbn3.lua`. +Navigate to your Archipelago install folder and open `data/lua/connector_mmbn3.lua`. +**NOTE:** The MMBN3 Lua file depends on other shared Lua files inside of the `data` directory in the Archipelago +installation. Do not move this Lua file from its default location or you may run into issues connecting. To connect the client to the multiserver simply put `
    :` on the textfield on top and press enter (if the server uses password, type in the bottom textfield `/connect
    : [password]`) diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py index 20bb8decebcc..576a106df7cc 100644 --- a/worlds/musedash/MuseDashCollection.py +++ b/worlds/musedash/MuseDashCollection.py @@ -22,12 +22,15 @@ class MuseDashCollections: ] MUSE_PLUS_DLC: str = "Muse Plus" + + # Ordering matters for webhost. Order goes: Muse Plus, Time Limited Muse Plus Dlcs, Paid Dlcs DLC: List[str] = [ - # MUSE_PLUS_DLC, # To be included when OptionSets are rendered as part of basic settings. - # "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026. - "Miku in Museland", # Paid DLC not included in Muse Plus - "Rin Len's Mirrorland", # Paid DLC not included in Muse Plus - "MSR Anthology", # Now no longer available. + MUSE_PLUS_DLC, + "CHUNITHM COURSE MUSE", # Part of Muse Plus. Goes away 22nd May 2027. + "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026. + "MSR Anthology", # Now no longer available. + "Miku in Museland", # Paid DLC not included in Muse Plus + "Rin Len's Mirrorland", # Paid DLC not included in Muse Plus ] DIFF_OVERRIDES: List[str] = [ @@ -35,13 +38,14 @@ class MuseDashCollections: "Rush-Hour", "Find this Month's Featured Playlist", "PeroPero in the Universe", - "umpopoff" + "umpopoff", + "P E R O P E R O Brother Dance", ] REMOVED_SONGS = [ "CHAOS Glitch", "FM 17314 SUGAR RADIO", - "Yume Ou Mono Yo Secret" + "Yume Ou Mono Yo Secret", ] album_items: Dict[str, AlbumData] = {} @@ -49,7 +53,7 @@ class MuseDashCollections: song_items: Dict[str, SongData] = {} song_locations: Dict[str, int] = {} - vfx_trap_items: Dict[str, int] = { + trap_items: Dict[str, int] = { "Bad Apple Trap": STARTING_CODE + 1, "Pixelate Trap": STARTING_CODE + 2, "Ripple Trap": STARTING_CODE + 3, @@ -57,14 +61,29 @@ class MuseDashCollections: "Chromatic Aberration Trap": STARTING_CODE + 5, "Background Freeze Trap": STARTING_CODE + 6, "Gray Scale Trap": STARTING_CODE + 7, - } - - sfx_trap_items: Dict[str, int] = { "Nyaa SFX Trap": STARTING_CODE + 8, "Error SFX Trap": STARTING_CODE + 9, + "Focus Line Trap": STARTING_CODE + 10, + } + + sfx_trap_items: List[str] = [ + "Nyaa SFX Trap", + "Error SFX Trap", + ] + + filler_items: Dict[str, int] = { + "Great To Perfect (10 Pack)": STARTING_CODE + 30, + "Miss To Great (5 Pack)": STARTING_CODE + 31, + "Extra Life": STARTING_CODE + 32, } - item_names_to_id: ChainMap = ChainMap({}, sfx_trap_items, vfx_trap_items) + filler_item_weights: Dict[str, int] = { + "Great To Perfect (10 Pack)": 10, + "Miss To Great (5 Pack)": 3, + "Extra Life": 1, + } + + item_names_to_id: ChainMap = ChainMap({}, filler_items, trap_items) location_names_to_id: ChainMap = ChainMap(song_locations, album_locations) def __init__(self) -> None: @@ -157,6 +176,9 @@ def get_songs_with_settings(self, dlc_songs: Set[str], streamer_mode_active: boo return filtered_list + def filter_songs_to_dlc(self, song_list: List[str], dlc_songs: Set[str]) -> List[str]: + return [song for song in song_list if self.song_matches_dlc_filter(self.song_items[song], dlc_songs)] + def song_matches_dlc_filter(self, song: SongData, dlc_songs: Set[str]) -> bool: if song.album in self.FREE_ALBUMS: return True diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt index 620c1968bda8..6f48d6af9fdd 100644 --- a/worlds/musedash/MuseDashData.txt +++ b/worlds/musedash/MuseDashData.txt @@ -518,8 +518,8 @@ Haunted Dance|43-48|MD Plus Project|False|6|9|11| Hey Vincent.|43-49|MD Plus Project|True|6|8|10| Meteor feat. TEA|43-50|MD Plus Project|True|3|6|9| Narcissism Angel|43-51|MD Plus Project|True|1|3|6| -AlterLuna|43-52|MD Plus Project|True|6|8|11| -Niki Tousen|43-53|MD Plus Project|True|6|8|10|11 +AlterLuna|43-52|MD Plus Project|True|6|8|11|12 +Niki Tousen|43-53|MD Plus Project|True|6|8|10|12 Rettou Joutou|70-0|Rin Len's Mirrorland|False|4|7|9| Telecaster B-Boy|70-1|Rin Len's Mirrorland|False|5|7|10| Iya Iya Iya|70-2|Rin Len's Mirrorland|False|2|4|7| @@ -537,4 +537,33 @@ Ruler Of My Heart VIVINOS|71-1|Valentine Stage|False|2|4|6| Reality Show|71-2|Valentine Stage|False|5|7|10| SIG feat.Tobokegao|71-3|Valentine Stage|True|3|6|8| Rose Love|71-4|Valentine Stage|True|2|4|7| -Euphoria|71-5|Valentine Stage|True|1|3|6| \ No newline at end of file +Euphoria|71-5|Valentine Stage|True|1|3|6| +P E R O P E R O Brother Dance|72-0|Legends of Muse Warriors|True|0|?|0| +PA PPA PANIC|72-1|Legends of Muse Warriors|False|4|8|10| +How To Make Music Game Song!|72-2|Legends of Muse Warriors|True|6|8|10|11 +Re Re|72-3|Legends of Muse Warriors|True|7|9|11|12 +Marmalade Twins|72-4|Legends of Muse Warriors|True|5|8|10| +DOMINATOR|72-5|Legends of Muse Warriors|True|7|9|11| +Teshikani TESHiKANi|72-6|Legends of Muse Warriors|True|5|7|9| +Urban Magic|73-0|Happy Otaku Pack Vol.19|True|3|5|7| +Maid's Prank|73-1|Happy Otaku Pack Vol.19|True|5|7|10| +Dance Dance Good Night Dance|73-2|Happy Otaku Pack Vol.19|True|2|4|7| +Ops Limone|73-3|Happy Otaku Pack Vol.19|True|5|8|11| +NOVA|73-4|Happy Otaku Pack Vol.19|True|6|8|10| +Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10| +Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10| +World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11 +Tsukuyomi Ni Naru|74-2|CHUNITHM COURSE MUSE|False|5|7|9| +The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11 +Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11 +Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12 +HIT ME UP|43-54|MD Plus Project|True|4|6|8| +Test Me feat. Uyeon|43-55|MD Plus Project|True|3|5|9| +Assault TAXI|43-56|MD Plus Project|True|4|7|10| +No|43-57|MD Plus Project|False|4|6|9| +Pop it|43-58|MD Plus Project|True|1|3|6| +HEARTBEAT! KyunKyun!|43-59|MD Plus Project|True|4|6|9| +SUPERHERO|75-0|Novice Rider Pack|False|2|4|7| +Highway_Summer|75-1|Novice Rider Pack|True|2|4|6| +Mx. Black Box|75-2|Novice Rider Pack|True|5|7|9| +Sweet Encounter|75-3|Novice Rider Pack|True|2|4|7| diff --git a/worlds/musedash/Options.py b/worlds/musedash/Options.py index 26ad5ff5d967..7164aa3e1362 100644 --- a/worlds/musedash/Options.py +++ b/worlds/musedash/Options.py @@ -1,24 +1,26 @@ -from typing import Dict -from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions +from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions, OptionGroup, Removed from dataclasses import dataclass from .MuseDashCollection import MuseDashCollections -class AllowJustAsPlannedDLCSongs(Toggle): - """Whether [Muse Plus] DLC Songs, and all the albums included in it, can be chosen as randomised songs. - Note: The [Just As Planned] DLC contains all [Muse Plus] songs.""" - display_name = "Allow [Muse Plus] DLC Songs" class DLCMusicPacks(OptionSet): - """Which non-[Muse Plus] DLC packs can be chosen as randomised songs.""" + """ + Choose which DLC Packs will be included in the pool of chooseable songs. + + Note: The [Just As Planned] DLC contains all [Muse Plus] songs. + """ display_name = "DLC Packs" default = {} valid_keys = [dlc for dlc in MuseDashCollections.DLC] class StreamerModeEnabled(Toggle): - """In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming. - If this is enabled, only songs available under Streamer Mode will be available for randomization.""" + """ + In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming. + + If this is enabled, only songs available under Streamer Mode will be available for randomization. + """ display_name = "Streamer Mode Only Songs" @@ -31,18 +33,20 @@ class StartingSongs(Range): class AdditionalSongs(Range): - """The total number of songs that will be placed in the randomization pool. + """ + The total number of songs that will be placed in the randomization pool. - This does not count any starting songs or the goal song. - The final song count may be lower due to other settings. """ range_start = 15 - range_end = 528 # Note will probably not reach this high if any other settings are done. + range_end = 534 # Note will probably not reach this high if any other settings are done. default = 40 display_name = "Additional Song Count" class DifficultyMode(Choice): - """Ensures that at any chosen song has at least 1 value falling within these values. + """ + Ensures that at any chosen song has at least 1 value falling within these values. - Any: All songs are available - Easy: 1, 2 or 3 - Medium: 4, 5 @@ -64,8 +68,11 @@ class DifficultyMode(Choice): # Todo: Investigate options to make this non randomizable class DifficultyModeOverrideMin(Range): - """Ensures that 1 difficulty has at least 1 this value or higher per song. - - Difficulty Mode must be set to Manual.""" + """ + Ensures that 1 difficulty has at least 1 this value or higher per song. + + Note: Difficulty Mode must be set to Manual. + """ display_name = "Manual Difficulty Min" range_start = 1 range_end = 11 @@ -74,8 +81,11 @@ class DifficultyModeOverrideMin(Range): # Todo: Investigate options to make this non randomizable class DifficultyModeOverrideMax(Range): - """Ensures that 1 difficulty has at least 1 this value or lower per song. - - Difficulty Mode must be set to Manual.""" + """ + Ensures that 1 difficulty has at least 1 this value or lower per song. + + Note: Difficulty Mode must be set to Manual. + """ display_name = "Manual Difficulty Max" range_start = 1 range_end = 11 @@ -83,7 +93,8 @@ class DifficultyModeOverrideMax(Range): class GradeNeeded(Choice): - """Completing a song will require a grade of this value or higher in order to unlock items. + """ + Completing a song will require a grade of this value or higher in order to unlock items. The grades are as follows: - Silver S (SS): >= 95% accuracy - Pink S (S): >= 90% accuracy @@ -101,20 +112,12 @@ class GradeNeeded(Choice): default = 0 -class AdditionalItemPercentage(Range): - """The percentage of songs that will have 2 items instead of 1 when completing them. - - Starting Songs will always have 2 items. - - Locations will be filled with duplicate songs if there are not enough items. - """ - display_name = "Additional Item %" - range_start = 50 - default = 80 - range_end = 100 - - class MusicSheetCountPercentage(Range): - """Collecting enough Music Sheets will unlock the goal song needed for completion. - This option controls how many are in the item pool, based on the total number of songs.""" + """ + Controls how many music sheets are added to the pool based on the number of songs, including starting songs. + + Higher numbers leads to more consistent game lengths, but will cause individual music sheets to be less important. + """ range_start = 10 range_end = 40 default = 20 @@ -129,19 +132,18 @@ class MusicSheetWinCountPercentage(Range): display_name = "Music Sheets Needed to Win" -class TrapTypes(Choice): - """This controls the types of traps that can be added to the pool. +class ChosenTraps(OptionSet): + """ + This controls the types of traps that can be added to the pool. + - Traps last the length of a song, or until you die. - VFX Traps consist of visual effects that play over the song. (i.e. Grayscale.) - SFX Traps consist of changing your sfx setting to one possibly more annoying sfx. - Traps last the length of a song, or until you die. + Note: SFX traps are only available if [Just as Planned] DLC songs are enabled. """ - display_name = "Available Trap Types" - option_None = 0 - option_VFX = 1 - option_SFX = 2 - option_All = 3 - default = 3 + display_name = "Chosen Traps" + default = {} + valid_keys = {trap for trap in MuseDashCollections.trap_items.keys()} class TrapCountPercentage(Range): @@ -153,37 +155,65 @@ class TrapCountPercentage(Range): class IncludeSongs(ItemSet): - """Any song listed here will be guaranteed to be included as part of the seed. - - Difficulty options will be skipped for these songs. - - If there being too many included songs, songs will be randomly chosen without regard for difficulty. - - If you want these songs immediately, use start_inventory instead. + """ + These songs will be guaranteed to show up within the seed. + - You must have the DLC enabled to play these songs. + - Difficulty options will not affect these songs. + - If there are too many included songs, this will act as a whitelist ignoring song difficulty. """ verify_item_name = True display_name = "Include Songs" class ExcludeSongs(ItemSet): - """Any song listed here will be excluded from being a part of the seed.""" + """ + These songs will be guaranteed to not show up within the seed. + + Note: Does not affect songs within the "Include Songs" list. + """ verify_item_name = True display_name = "Exclude Songs" +md_option_groups = [ + OptionGroup("Song Choice", [ + DLCMusicPacks, + StreamerModeEnabled, + IncludeSongs, + ExcludeSongs, + ]), + OptionGroup("Difficulty", [ + GradeNeeded, + DifficultyMode, + DifficultyModeOverrideMin, + DifficultyModeOverrideMax, + DeathLink, + ]), + OptionGroup("Traps", [ + ChosenTraps, + TrapCountPercentage, + ]), +] + + @dataclass class MuseDashOptions(PerGameCommonOptions): - allow_just_as_planned_dlc_songs: AllowJustAsPlannedDLCSongs dlc_packs: DLCMusicPacks streamer_mode_enabled: StreamerModeEnabled starting_song_count: StartingSongs additional_song_count: AdditionalSongs - additional_item_percentage: AdditionalItemPercentage song_difficulty_mode: DifficultyMode song_difficulty_min: DifficultyModeOverrideMin song_difficulty_max: DifficultyModeOverrideMax grade_needed: GradeNeeded music_sheet_count_percentage: MusicSheetCountPercentage music_sheet_win_count_percentage: MusicSheetWinCountPercentage - available_trap_types: TrapTypes + chosen_traps: ChosenTraps trap_count_percentage: TrapCountPercentage death_link: DeathLink include_songs: IncludeSongs exclude_songs: ExcludeSongs + + # Removed + allow_just_as_planned_dlc_songs: Removed + available_trap_types: Removed diff --git a/worlds/musedash/Presets.py b/worlds/musedash/Presets.py index 64591118021e..fe314edbc9b5 100644 --- a/worlds/musedash/Presets.py +++ b/worlds/musedash/Presets.py @@ -3,28 +3,25 @@ MuseDashPresets: Dict[str, Dict[str, Any]] = { # An option to support Short Sync games. 40 songs. "No DLC - Short": { - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "starting_song_count": 5, "additional_song_count": 34, - "additional_item_percentage": 80, "music_sheet_count_percentage": 20, "music_sheet_win_count_percentage": 90, }, # An option to support Short Sync games but adds variety. 40 songs. "DLC - Short": { - "allow_just_as_planned_dlc_songs": True, + "dlc_packs": ["Muse Plus"], "starting_song_count": 5, "additional_song_count": 34, - "additional_item_percentage": 80, "music_sheet_count_percentage": 20, "music_sheet_win_count_percentage": 90, }, # An option to support Longer Sync/Async games. 100 songs. "DLC - Long": { - "allow_just_as_planned_dlc_songs": True, + "dlc_packs": ["Muse Plus"], "starting_song_count": 8, "additional_song_count": 91, - "additional_item_percentage": 80, "music_sheet_count_percentage": 20, "music_sheet_win_count_percentage": 90, }, diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py index af2d4cc207da..ab3a4819fc48 100644 --- a/worlds/musedash/__init__.py +++ b/worlds/musedash/__init__.py @@ -1,10 +1,10 @@ from worlds.AutoWorld import World, WebWorld -from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial -from typing import List, ClassVar, Type +from BaseClasses import Region, Item, ItemClassification, Tutorial +from typing import List, ClassVar, Type, Set from math import floor from Options import PerGameCommonOptions -from .Options import MuseDashOptions +from .Options import MuseDashOptions, md_option_groups from .Items import MuseDashSongItem, MuseDashFixedItem from .Locations import MuseDashLocation from .MuseDashCollection import MuseDashCollections @@ -35,6 +35,7 @@ class MuseDashWebWorld(WebWorld): tutorials = [setup_en, setup_es] options_presets = MuseDashPresets + option_groups = md_option_groups class MuseDashWorld(World): @@ -57,6 +58,8 @@ class MuseDashWorld(World): # Necessary Data md_collection = MuseDashCollections() + filler_item_names = list(md_collection.filler_item_weights.keys()) + filler_item_weights = list(md_collection.filler_item_weights.values()) item_name_to_id = {name: code for name, code in md_collection.item_names_to_id.items()} location_name_to_id = {name: code for name, code in md_collection.location_names_to_id.items()} @@ -70,8 +73,6 @@ class MuseDashWorld(World): def generate_early(self): dlc_songs = {key for key in self.options.dlc_packs.value} - if (self.options.allow_just_as_planned_dlc_songs.value): - dlc_songs.add(self.md_collection.MUSE_PLUS_DLC) streamer_mode = self.options.streamer_mode_enabled (lower_diff_threshold, higher_diff_threshold) = self.get_difficulty_range() @@ -84,9 +85,9 @@ def generate_early(self): while True: # In most cases this should only need to run once available_song_keys = self.md_collection.get_songs_with_settings( - dlc_songs, streamer_mode, lower_diff_threshold, higher_diff_threshold) + dlc_songs, bool(streamer_mode.value), lower_diff_threshold, higher_diff_threshold) - available_song_keys = self.handle_plando(available_song_keys) + available_song_keys = self.handle_plando(available_song_keys, dlc_songs) count_needed_for_start = max(0, starter_song_count - len(self.starting_songs)) if len(available_song_keys) + len(self.included_songs) >= count_needed_for_start + 11: @@ -107,7 +108,7 @@ def generate_early(self): for song in self.starting_songs: self.multiworld.push_precollected(self.create_item(song)) - def handle_plando(self, available_song_keys: List[str]) -> List[str]: + def handle_plando(self, available_song_keys: List[str], dlc_songs: Set[str]) -> List[str]: song_items = self.md_collection.song_items start_items = self.options.start_inventory.value.keys() @@ -115,7 +116,9 @@ def handle_plando(self, available_song_keys: List[str]) -> List[str]: exclude_songs = self.options.exclude_songs.value self.starting_songs = [s for s in start_items if s in song_items] + self.starting_songs = self.md_collection.filter_songs_to_dlc(self.starting_songs, dlc_songs) self.included_songs = [s for s in include_songs if s in song_items and s not in self.starting_songs] + self.included_songs = self.md_collection.filter_songs_to_dlc(self.included_songs, dlc_songs) return [s for s in available_song_keys if s not in start_items and s not in include_songs and s not in exclude_songs] @@ -146,7 +149,7 @@ def create_song_pool(self, available_song_keys: List[str]): self.victory_song_name = available_song_keys[chosen_song - included_song_count] del available_song_keys[chosen_song - included_song_count] - # Next, make sure the starting songs are fufilled + # Next, make sure the starting songs are fulfilled if len(self.starting_songs) < starting_song_count: for _ in range(len(self.starting_songs), starting_song_count): if len(available_song_keys) > 0: @@ -154,31 +157,25 @@ def create_song_pool(self, available_song_keys: List[str]): else: self.starting_songs.append(self.included_songs.pop()) - # Then attempt to fufill any remaining songs for interim songs + # Then attempt to fulfill any remaining songs for interim songs if len(self.included_songs) < additional_song_count: for _ in range(len(self.included_songs), self.options.additional_song_count): if len(available_song_keys) <= 0: break self.included_songs.append(available_song_keys.pop()) - self.location_count = len(self.starting_songs) + len(self.included_songs) - location_multiplier = 1 + (self.get_additional_item_percentage() / 100.0) - self.location_count = floor(self.location_count * location_multiplier) - - minimum_location_count = len(self.included_songs) + self.get_music_sheet_count() - if self.location_count < minimum_location_count: - self.location_count = minimum_location_count + self.location_count = 2 * (len(self.starting_songs) + len(self.included_songs)) def create_item(self, name: str) -> Item: if name == self.md_collection.MUSIC_SHEET_NAME: return MuseDashFixedItem(name, ItemClassification.progression_skip_balancing, self.md_collection.MUSIC_SHEET_CODE, self.player) - trap = self.md_collection.vfx_trap_items.get(name) - if trap: - return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player) + filler = self.md_collection.filler_items.get(name) + if filler: + return MuseDashFixedItem(name, ItemClassification.filler, filler, self.player) - trap = self.md_collection.sfx_trap_items.get(name) + trap = self.md_collection.trap_items.get(name) if trap: return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player) @@ -189,6 +186,9 @@ def create_item(self, name: str) -> Item: song = self.md_collection.song_items.get(name) return MuseDashSongItem(name, self.player, song) + def get_filler_item_name(self) -> str: + return self.random.choices(self.filler_item_names, self.filler_item_weights)[0] + def create_items(self) -> None: song_keys_in_pool = self.included_songs.copy() @@ -199,8 +199,13 @@ def create_items(self) -> None: for _ in range(0, item_count): self.multiworld.itempool.append(self.create_item(self.md_collection.MUSIC_SHEET_NAME)) - # Then add all traps - trap_count = self.get_trap_count() + # Then add 1 copy of every song + item_count += len(self.included_songs) + for song in self.included_songs: + self.multiworld.itempool.append(self.create_item(song)) + + # Then add all traps, making sure we don't over fill + trap_count = min(self.location_count - item_count, self.get_trap_count()) trap_list = self.get_available_traps() if len(trap_list) > 0 and trap_count > 0: for _ in range(0, trap_count): @@ -209,29 +214,42 @@ def create_items(self) -> None: item_count += trap_count - # Next fill all remaining slots with song items - needed_item_count = self.location_count - while item_count < needed_item_count: - # If we have more items needed than keys, just iterate the list and add them all - if len(song_keys_in_pool) <= needed_item_count - item_count: - for key in song_keys_in_pool: - self.multiworld.itempool.append(self.create_item(key)) + # At this point, if a player is using traps, it's possible that they have filled all locations + items_left = self.location_count - item_count + if items_left <= 0: + return + + # When it comes to filling remaining spaces, we have 2 options. A useless filler or additional songs. + # First fill 50% with the filler. The rest is to be duplicate songs. + filler_count = floor(0.5 * items_left) + items_left -= filler_count - item_count += len(song_keys_in_pool) - continue + for _ in range(0, filler_count): + self.multiworld.itempool.append(self.create_item(self.get_filler_item_name())) - # Otherwise add a random assortment of songs - self.random.shuffle(song_keys_in_pool) - for i in range(0, needed_item_count - item_count): - self.multiworld.itempool.append(self.create_item(song_keys_in_pool[i])) + # All remaining spots are filled with duplicate songs. Duplicates are set to useful instead of progression + # to cut down on the number of progression items that Muse Dash puts into the pool. - item_count = needed_item_count + # This is for the extraordinary case of needing to fill a lot of items. + while items_left > len(song_keys_in_pool): + for key in song_keys_in_pool: + item = self.create_item(key) + item.classification = ItemClassification.useful + self.multiworld.itempool.append(item) + + items_left -= len(song_keys_in_pool) + continue + + # Otherwise add a random assortment of songs + self.random.shuffle(song_keys_in_pool) + for i in range(0, items_left): + item = self.create_item(song_keys_in_pool[i]) + item.classification = ItemClassification.useful + self.multiworld.itempool.append(item) def create_regions(self) -> None: menu_region = Region("Menu", self.player, self.multiworld) - song_select_region = Region("Song Select", self.player, self.multiworld) - self.multiworld.regions += [menu_region, song_select_region] - menu_region.connect(song_select_region) + self.multiworld.regions += [menu_region] # Make a collection of all songs available for this rando. # 1. All starting songs @@ -245,50 +263,36 @@ def create_regions(self) -> None: self.random.shuffle(included_song_copy) all_selected_locations.extend(included_song_copy) - two_item_location_count = self.location_count - len(all_selected_locations) - - # Make a region per song/album, then adds 1-2 item locations to them + # Adds 2 item locations per song/album to the menu region. for i in range(0, len(all_selected_locations)): name = all_selected_locations[i] - region = Region(name, self.player, self.multiworld) - self.multiworld.regions.append(region) - song_select_region.connect(region, name, lambda state, place=name: state.has(place, self.player)) + loc1 = MuseDashLocation(self.player, name + "-0", self.md_collection.song_locations[name + "-0"], menu_region) + loc1.access_rule = lambda state, place=name: state.has(place, self.player) + menu_region.locations.append(loc1) - # Up to 2 Locations are defined per song - region.add_locations({name + "-0": self.md_collection.song_locations[name + "-0"]}, MuseDashLocation) - if i < two_item_location_count: - region.add_locations({name + "-1": self.md_collection.song_locations[name + "-1"]}, MuseDashLocation) + loc2 = MuseDashLocation(self.player, name + "-1", self.md_collection.song_locations[name + "-1"], menu_region) + loc2.access_rule = lambda state, place=name: state.has(place, self.player) + menu_region.locations.append(loc2) def set_rules(self) -> None: self.multiworld.completion_condition[self.player] = lambda state: \ state.has(self.md_collection.MUSIC_SHEET_NAME, self.player, self.get_music_sheet_win_count()) def get_available_traps(self) -> List[str]: - sfx_traps_available = self.options.allow_just_as_planned_dlc_songs.value - - trap_list = [] - if self.options.available_trap_types.value & 1 != 0: - trap_list += self.md_collection.vfx_trap_items.keys() - - # SFX options are only available under Just as Planned DLC. - if sfx_traps_available and self.options.available_trap_types.value & 2 != 0: - trap_list += self.md_collection.sfx_trap_items.keys() - - return trap_list + full_trap_list = self.md_collection.trap_items.keys() + if self.md_collection.MUSE_PLUS_DLC not in self.options.dlc_packs.value: + full_trap_list = [trap for trap in full_trap_list if trap not in self.md_collection.sfx_trap_items] - def get_additional_item_percentage(self) -> int: - trap_count = self.options.trap_count_percentage.value - song_count = self.options.music_sheet_count_percentage.value - return max(trap_count + song_count, self.options.additional_item_percentage.value) + return [trap for trap in full_trap_list if trap in self.options.chosen_traps.value] def get_trap_count(self) -> int: multiplier = self.options.trap_count_percentage.value / 100.0 - trap_count = (len(self.starting_songs) * 2) + len(self.included_songs) + trap_count = len(self.starting_songs) + len(self.included_songs) return max(0, floor(trap_count * multiplier)) def get_music_sheet_count(self) -> int: multiplier = self.options.music_sheet_count_percentage.value / 100.0 - song_count = (len(self.starting_songs) * 2) + len(self.included_songs) + song_count = len(self.starting_songs) + len(self.included_songs) return max(1, floor(song_count * multiplier)) def get_music_sheet_win_count(self) -> int: @@ -329,5 +333,4 @@ def fill_slot_data(self): "deathLink": self.options.death_link.value, "musicSheetWinCount": self.get_music_sheet_win_count(), "gradeNeeded": self.options.grade_needed.value, - "hasFiller": True, } diff --git a/worlds/musedash/docs/en_Muse Dash.md b/worlds/musedash/docs/en_Muse Dash.md index 008fd4d2df0c..29d1465ed098 100644 --- a/worlds/musedash/docs/en_Muse Dash.md +++ b/worlds/musedash/docs/en_Muse Dash.md @@ -2,10 +2,10 @@ ## Quick Links - [Setup Guide](../../../tutorial/Muse%20Dash/setup/en) -- [Settings Page](../player-settings) +- [Options Page](../player-options) ## What Does Randomization do to this Game? -- You will be given a number of starting songs. The number of which depends on your settings. +- You will be given a number of starting songs. The number of which depends on your options. - Completing any song will give you 1 or 2 rewards. - The rewards for completing songs will range from songs to traps and **Music Sheets**. diff --git a/worlds/musedash/docs/setup_en.md b/worlds/musedash/docs/setup_en.md index ebf165c7dd78..a3c2f43a91f0 100644 --- a/worlds/musedash/docs/setup_en.md +++ b/worlds/musedash/docs/setup_en.md @@ -2,7 +2,7 @@ ## Quick Links - [Main Page](../../../../games/Muse%20Dash/info/en) -- [Settings Page](../../../../games/Muse%20Dash/player-settings) +- [Options Page](../../../../games/Muse%20Dash/player-options) ## Required Software @@ -17,17 +17,18 @@ ## Installing the Archipelago mod to Muse Dash 1. Download [MelonLoader.Installer.exe](https://github.com/LavaGang/MelonLoader/releases/latest) and run it. -2. Choose the automated tab, click the select button and browse to `MuseDash.exe`. Then click install. +2. Choose the automated tab, click the select button and browse to `MuseDash.exe`. - You can find the folder in steam by finding the game in your library, right clicking it and choosing *Manage→Browse Local Files*. - If you click the bar at the top telling you your current folder, this will give you a path you can copy. If you paste that into the window popped up by **MelonLoader**, it will automatically go to the same folder. -3. Run the game once, and wait until you get to the Muse Dash start screen before exiting. -4. Download the latest [Muse Dash Archipelago Mod](https://github.com/DeamonHunter/ArchipelagoMuseDash/releases/latest) and then extract that into the newly created `/Mods/` folder in MuseDash's install location. +3. Uncheck "Latest" and select v0.6.1. Then click install. +4. Run the game once, and wait until you get to the Muse Dash start screen before exiting. +5. Download the latest [Muse Dash Archipelago Mod](https://github.com/DeamonHunter/ArchipelagoMuseDash/releases/latest) and then extract that into the newly created `/Mods/` folder in MuseDash's install location. - All files must be under the `/Mods/` folder and not within a sub folder inside of `/Mods/` If you've successfully installed everything, a button will appear in the bottom right which will allow you to log into an Archipelago server. ## Generating a MultiWorld Game -1. Visit the [Player Settings](/games/Muse%20Dash/player-settings) page and configure the game-specific settings to your taste. +1. Visit the [Player Options](/games/Muse%20Dash/player-options) page and configure the game-specific options to your taste. 2. Export your yaml file and use it to generate a new randomized game - (For instructions on how to generate an Archipelago game, refer to the [Archipelago Web Guide](/tutorial/Archipelago/setup/en)) diff --git a/worlds/musedash/docs/setup_es.md b/worlds/musedash/docs/setup_es.md index 0d737c26d726..fe5358921def 100644 --- a/worlds/musedash/docs/setup_es.md +++ b/worlds/musedash/docs/setup_es.md @@ -2,7 +2,7 @@ ## Enlaces rápidos - [Página Principal](../../../../games/Muse%20Dash/info/en) -- [Página de Configuraciones](../../../../games/Muse%20Dash/player-settings) +- [Página de Configuraciones](../../../../games/Muse%20Dash/player-options) ## Software Requerido @@ -17,17 +17,18 @@ ## Instalar el mod de Archipelago en Muse Dash 1. Descarga [MelonLoader.Installer.exe](https://github.com/LavaGang/MelonLoader/releases/latest) y ejecutalo. -2. Elije la pestaña "automated", haz clic en el botón "select" y busca tu `MuseDash.exe`. Luego haz clic en "install". +2. Elije la pestaña "automated", haz clic en el botón "select" y busca tu `MuseDash.exe`. - Puedes encontrar la carpeta en Steam buscando el juego en tu biblioteca, haciendo clic derecho sobre el y elegir *Administrar→Ver archivos locales*. - Si haces clic en la barra superior que te indica la carpeta en la que estas, te dará la dirección de ésta para que puedas copiarla. Al pegar esa dirección en la ventana que **MelonLoader** abre, irá automaticamente a esa carpeta. -3. Ejecuta el juego una vez, y espera hasta que aparezca la pantalla de inicio de Muse Dash antes de cerrarlo. -4. Descarga la última version de [Muse Dash Archipelago Mod](https://github.com/DeamonHunter/ArchipelagoMuseDash/releases/latest) y extraelo en la nueva carpeta creada llamada `/Mods/`, localizada en la carpeta de instalación de Muse Dash. +3. Desmarca "Latest" y selecciona v0.6.1. Luego haz clic en "install". +4. Ejecuta el juego una vez, y espera hasta que aparezca la pantalla de inicio de Muse Dash antes de cerrarlo. +5. Descarga la última version de [Muse Dash Archipelago Mod](https://github.com/DeamonHunter/ArchipelagoMuseDash/releases/latest) y extraelo en la nueva carpeta creada llamada `/Mods/`, localizada en la carpeta de instalación de Muse Dash. - Todos los archivos deben ir directamente en la carpeta `/Mods/`, y NO en una subcarpeta dentro de la carpeta `/Mods/` Si todo fue instalado correctamente, un botón aparecerá en la parte inferior derecha del juego una vez abierto, que te permitirá conectarte al servidor de Archipelago. ## Generar un juego MultiWorld -1. Entra a la página de [configuraciones de jugador](/games/Muse%20Dash/player-settings) y configura las opciones del juego a tu gusto. +1. Entra a la página de [configuraciones de jugador](/games/Muse%20Dash/player-options) y configura las opciones del juego a tu gusto. 2. Genera tu archivo YAML y úsalo para generar un juego nuevo en el radomizer - (Instrucciones sobre como generar un juego en Archipelago disponibles en la [guía web de Archipelago en Inglés](/tutorial/Archipelago/setup/en)) diff --git a/worlds/musedash/test/TestCollection.py b/worlds/musedash/test/TestCollection.py index 48cb69e403ad..c8c2b39acb4d 100644 --- a/worlds/musedash/test/TestCollection.py +++ b/worlds/musedash/test/TestCollection.py @@ -9,25 +9,26 @@ def test_all_names_are_ascii(self) -> None: for name in collection.song_items.keys(): for c in name: # This is taken directly from OoT. Represents the generally excepted characters. - if (0x20 <= ord(c) < 0x7e): + if 0x20 <= ord(c) < 0x7e: continue bad_names.append(name) break - self.assertEqual(len(bad_names), 0, f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}") + self.assertEqual(len(bad_names), 0, + f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}") def test_ids_dont_change(self) -> None: collection = MuseDashCollections() - itemsBefore = {name: code for name, code in collection.item_names_to_id.items()} - locationsBefore = {name: code for name, code in collection.location_names_to_id.items()} + items_before = {name: code for name, code in collection.item_names_to_id.items()} + locations_before = {name: code for name, code in collection.location_names_to_id.items()} collection.__init__() - itemsAfter = {name: code for name, code in collection.item_names_to_id.items()} - locationsAfter = {name: code for name, code in collection.location_names_to_id.items()} + items_after = {name: code for name, code in collection.item_names_to_id.items()} + locations_after = {name: code for name, code in collection.location_names_to_id.items()} - self.assertDictEqual(itemsBefore, itemsAfter, "Item ID changed after secondary init.") - self.assertDictEqual(locationsBefore, locationsAfter, "Location ID changed after secondary init.") + self.assertDictEqual(items_before, items_after, "Item ID changed after secondary init.") + self.assertDictEqual(locations_before, locations_after, "Location ID changed after secondary init.") def test_free_dlc_included_in_base_songs(self) -> None: collection = MuseDashCollections() diff --git a/worlds/musedash/test/TestDifficultyRanges.py b/worlds/musedash/test/TestDifficultyRanges.py index af3469aa080f..a9c36985afae 100644 --- a/worlds/musedash/test/TestDifficultyRanges.py +++ b/worlds/musedash/test/TestDifficultyRanges.py @@ -3,31 +3,31 @@ class DifficultyRanges(MuseDashTestBase): def test_all_difficulty_ranges(self) -> None: - muse_dash_world = self.multiworld.worlds[1] + muse_dash_world = self.get_world() dlc_set = {x for x in muse_dash_world.md_collection.DLC} - difficulty_choice = self.multiworld.song_difficulty_mode[1] - difficulty_min = self.multiworld.song_difficulty_min[1] - difficulty_max = self.multiworld.song_difficulty_max[1] + difficulty_choice = muse_dash_world.options.song_difficulty_mode + difficulty_min = muse_dash_world.options.song_difficulty_min + difficulty_max = muse_dash_world.options.song_difficulty_max - def test_range(inputRange, lower, upper): - self.assertEqual(inputRange[0], lower) - self.assertEqual(inputRange[1], upper) + def test_range(input_range, lower, upper): + self.assertEqual(input_range[0], lower) + self.assertEqual(input_range[1], upper) - songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, inputRange[0], inputRange[1]) + songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, input_range[0], input_range[1]) for songKey in songs: song = muse_dash_world.md_collection.song_items[songKey] - if (song.easy is not None and inputRange[0] <= song.easy <= inputRange[1]): + if song.easy is not None and input_range[0] <= song.easy <= input_range[1]: continue - if (song.hard is not None and inputRange[0] <= song.hard <= inputRange[1]): + if song.hard is not None and input_range[0] <= song.hard <= input_range[1]: continue - if (song.master is not None and inputRange[0] <= song.master <= inputRange[1]): + if song.master is not None and input_range[0] <= song.master <= input_range[1]: continue - self.fail(f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'") + self.fail(f"Invalid song '{songKey}' was given for range '{input_range[0]} to {input_range[1]}'") - #auto ranges + # auto ranges difficulty_choice.value = 0 test_range(muse_dash_world.get_difficulty_range(), 0, 12) difficulty_choice.value = 1 @@ -61,16 +61,16 @@ def test_range(inputRange, lower, upper): test_range(muse_dash_world.get_difficulty_range(), 4, 6) def test_songs_have_difficulty(self) -> None: - muse_dash_world = self.multiworld.worlds[1] + muse_dash_world = self.get_world() for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES: song = muse_dash_world.md_collection.song_items[song_name] - # umpopoff is a one time weird song. Its currently the only song in the game - # with non-standard difficulties and also doesn't have 3 or more difficulties. - if song_name == 'umpopoff': + # Some songs are weird and have less than the usual 3 difficulties. + # So this override is to avoid failing on these songs. + if song_name in ("umpopoff", "P E R O P E R O Brother Dance"): self.assertTrue(song.easy is None and song.hard is not None and song.master is None, f"Song '{song_name}' difficulty not set when it should be.") else: self.assertTrue(song.easy is not None and song.hard is not None and song.master is not None, - f"Song '{song_name}' difficulty not set when it should be.") + f"Song '{song_name}' difficulty not set when it should be.") diff --git a/worlds/musedash/test/TestPlandoSettings.py b/worlds/musedash/test/TestPlandoSettings.py index 4b23a4afa90a..2617b7a4e02c 100644 --- a/worlds/musedash/test/TestPlandoSettings.py +++ b/worlds/musedash/test/TestPlandoSettings.py @@ -4,7 +4,32 @@ class TestPlandoSettings(MuseDashTestBase): options = { "additional_song_count": 15, - "allow_just_as_planned_dlc_songs": True, + "dlc_packs": {"Muse Plus"}, + "include_songs": [ + "Lunatic", + "Out of Sense", + "Magic Knight Girl", + ] + } + + def test_included_songs_didnt_grow_item_count(self) -> None: + muse_dash_world = self.get_world() + self.assertEqual(len(muse_dash_world.included_songs), 15, "Logical songs size grew when it shouldn't.") + + def test_included_songs_plando(self) -> None: + muse_dash_world = self.get_world() + songs = muse_dash_world.included_songs.copy() + songs.append(muse_dash_world.victory_song_name) + + self.assertIn("Lunatic", songs, "Logical songs is missing a plando song: Lunatic") + self.assertIn("Out of Sense", songs, "Logical songs is missing a plando song: Out of Sense") + self.assertIn("Magic Knight Girl", songs, "Logical songs is missing a plando song: Magic Knight Girl") + + +class TestFilteredPlandoSettings(MuseDashTestBase): + options = { + "additional_song_count": 15, + "dlc_packs": {"MSR Anthology"}, "include_songs": [ "Operation Blade", "Autumn Moods", @@ -13,15 +38,15 @@ class TestPlandoSettings(MuseDashTestBase): } def test_included_songs_didnt_grow_item_count(self) -> None: - muse_dash_world = self.multiworld.worlds[1] - self.assertEqual(len(muse_dash_world.included_songs), 15, - f"Logical songs size grew when it shouldn't. Expected 15. Got {len(muse_dash_world.included_songs)}") + muse_dash_world = self.get_world() + self.assertEqual(len(muse_dash_world.included_songs), 15, "Logical songs size grew when it shouldn't.") - def test_included_songs_plando(self) -> None: - muse_dash_world = self.multiworld.worlds[1] + # Tests for excluding included songs when the right dlc isn't enabled + def test_filtered_included_songs_plando(self) -> None: + muse_dash_world = self.get_world() songs = muse_dash_world.included_songs.copy() songs.append(muse_dash_world.victory_song_name) self.assertIn("Operation Blade", songs, "Logical songs is missing a plando song: Operation Blade") self.assertIn("Autumn Moods", songs, "Logical songs is missing a plando song: Autumn Moods") - self.assertIn("Fireflies", songs, "Logical songs is missing a plando song: Fireflies") \ No newline at end of file + self.assertNotIn("Fireflies", songs, "Logical songs has added a filtered a plando song: Fireflies") diff --git a/worlds/musedash/test/TestTrapOption.py b/worlds/musedash/test/TestTrapOption.py new file mode 100644 index 000000000000..ca0579c1f66c --- /dev/null +++ b/worlds/musedash/test/TestTrapOption.py @@ -0,0 +1,33 @@ +from . import MuseDashTestBase + + +class TestNoTraps(MuseDashTestBase): + def test_no_traps(self) -> None: + md_world = self.get_world() + md_world.options.chosen_traps.value.clear() + self.assertEqual(len(md_world.get_available_traps()), 0, "Got an available trap when we expected none.") + + def test_all_traps(self) -> None: + md_world = self.get_world() + md_world.options.dlc_packs.value.add(md_world.md_collection.MUSE_PLUS_DLC) + + for trap in md_world.md_collection.trap_items.keys(): + md_world.options.chosen_traps.value.add(trap) + + trap_count = len(md_world.get_available_traps()) + true_count = len(md_world.md_collection.trap_items.keys()) + + self.assertEqual(trap_count, true_count, "Got a different amount of traps than what was expected.") + + def test_exclude_sfx_traps(self) -> None: + md_world = self.get_world() + if "Muse Plus" in md_world.options.dlc_packs.value: + md_world.options.dlc_packs.value.remove("Muse Plus") + + for trap in md_world.md_collection.trap_items.keys(): + md_world.options.chosen_traps.value.add(trap) + + trap_count = len(md_world.get_available_traps()) + true_count = len(md_world.md_collection.trap_items.keys()) - len(md_world.md_collection.sfx_trap_items) + + self.assertEqual(trap_count, true_count, "Got a different amount of traps than what was expected.") diff --git a/worlds/musedash/test/TestWorstCaseSettings.py b/worlds/musedash/test/TestWorstCaseSettings.py index eeedfa5c3a5f..fd39651d1203 100644 --- a/worlds/musedash/test/TestWorstCaseSettings.py +++ b/worlds/musedash/test/TestWorstCaseSettings.py @@ -4,30 +4,33 @@ # This ends up with only 25 valid songs that can be chosen. # These tests ensure that this won't fail generation + class TestWorstCaseHighDifficulty(MuseDashTestBase): options = { "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "streamer_mode_enabled": True, "song_difficulty_mode": 6, "song_difficulty_min": 11, "song_difficulty_max": 11, } + class TestWorstCaseMidDifficulty(MuseDashTestBase): options = { "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "streamer_mode_enabled": True, "song_difficulty_mode": 6, "song_difficulty_min": 6, "song_difficulty_max": 6, } + class TestWorstCaseLowDifficulty(MuseDashTestBase): options = { "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "streamer_mode_enabled": True, "song_difficulty_mode": 6, "song_difficulty_min": 1, diff --git a/worlds/musedash/test/__init__.py b/worlds/musedash/test/__init__.py index 818fd357cd97..ff9d988c65c2 100644 --- a/worlds/musedash/test/__init__.py +++ b/worlds/musedash/test/__init__.py @@ -1,5 +1,10 @@ -from test.TestBase import WorldTestBase - +from test.bases import WorldTestBase +from .. import MuseDashWorld +from typing import cast class MuseDashTestBase(WorldTestBase): game = "Muse Dash" + + def get_world(self) -> MuseDashWorld: + return cast(MuseDashWorld, self.multiworld.worlds[1]) + diff --git a/worlds/noita/__init__.py b/worlds/noita/__init__.py index b8f8e4ae8346..af2921768d6a 100644 --- a/worlds/noita/__init__.py +++ b/worlds/noita/__init__.py @@ -34,14 +34,13 @@ class NoitaWorld(World): item_name_groups = items.item_name_groups location_name_groups = locations.location_name_groups - data_version = 2 web = NoitaWeb() - def generate_early(self): + def generate_early(self) -> None: if not self.multiworld.get_player_name(self.player).isascii(): raise Exception("Noita yaml's slot name has invalid character(s).") - + # Returned items will be sent over to the client def fill_slot_data(self) -> Dict[str, Any]: return self.options.as_dict("death_link", "victory_condition", "path_option", "hidden_chests", diff --git a/worlds/noita/docs/en_Noita.md b/worlds/noita/docs/en_Noita.md index b1480068e96c..1e560cfcb748 100644 --- a/worlds/noita/docs/en_Noita.md +++ b/worlds/noita/docs/en_Noita.md @@ -1,15 +1,15 @@ # Noita -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? Noita is a procedurally generated action roguelike. During runs in Noita you will find potions, wands, spells, perks, chests, etc. Shop items, chests/hearts hidden in the environment, and pedestal items will be replaced with location -checks. Orbs and boss drops can give location checks as well, if they are enabled in the settings. +checks. Orbs and boss drops can give location checks as well, if their respective options are enabled. Noita items that can be found in other players' games include specific perks, orbs (optional), wands, hearts, gold, potions, and other items. If traps are enabled, some randomized negative effects can affect your game when found. @@ -50,9 +50,9 @@ Traps consist of all "Bad" and "Awful" events from Noita's native stream integra ## How many location checks are there? -When using the default settings, there are 109 location checks. The number of checks in the game is dependent on the settings that you choose. -Please check the information boxes next to the settings when setting up your YAML to see how many checks the individual options add. -There are always 42 Holy Mountain checks and 4 Secret Shop checks in the pool which are not affected by your YAML settings. +When using the default options, there are 109 location checks. The number of checks in the game is dependent on the options that you choose. +Please check the information boxes next to the options when setting up your YAML to see how many checks the individual options add. +There are always 42 Holy Mountain checks and 4 Secret Shop checks in the pool which are not affected by your YAML options. ## What does another world's item look like in Noita? diff --git a/worlds/noita/docs/setup_en.md b/worlds/noita/docs/setup_en.md index b67e840bb94c..25c6cbc948bc 100644 --- a/worlds/noita/docs/setup_en.md +++ b/worlds/noita/docs/setup_en.md @@ -44,7 +44,7 @@ Please note that Noita only allows you to type certain characters for your slot These characters are: `` !#$%&'()+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~<>|\/`` ### Where do I get a YAML? -You can use the [game settings page for Noita](/games/Noita/player-settings) here on the Archipelago website to +You can use the [game options page for Noita](/games/Noita/player-options) here on the Archipelago website to generate a YAML using a graphical interface. ## Poptracker Pack diff --git a/worlds/noita/locations.py b/worlds/noita/locations.py index afe16c54e4b2..5dd87b5b0387 100644 --- a/worlds/noita/locations.py +++ b/worlds/noita/locations.py @@ -12,7 +12,7 @@ class NoitaLocation(Location): class LocationData(NamedTuple): id: int flag: int = 0 - ltype: Optional[str] = "shop" + ltype: str = "Shop" class LocationFlag(IntEnum): @@ -25,8 +25,8 @@ class LocationFlag(IntEnum): # Mapping of items in each region. # Only the first Hidden Chest and Pedestal are mapped here, the others are created in Regions. -# ltype key: "chest" = Hidden Chests, "pedestal" = Pedestals, "boss" = Boss, "orb" = Orb. -# 110000-110649 +# ltype key: "Chest" = Hidden Chests, "Pedestal" = Pedestals, "Boss" = Boss, "Orb" = Orb. +# 110000-110671 location_region_mapping: Dict[str, Dict[str, LocationData]] = { "Coal Pits Holy Mountain": { "Coal Pits Holy Mountain Shop Item 1": LocationData(110000), @@ -90,113 +90,119 @@ class LocationFlag(IntEnum): "Secret Shop Item 3": LocationData(110044), "Secret Shop Item 4": LocationData(110045), }, + "The Sky": { + "Kivi": LocationData(110670, LocationFlag.main_world, "Boss"), + }, "Floating Island": { - "Floating Island Orb": LocationData(110658, LocationFlag.main_path, "orb"), + "Floating Island Orb": LocationData(110658, LocationFlag.main_path, "Orb"), }, "Pyramid": { - "Kolmisilmän Koipi": LocationData(110649, LocationFlag.main_world, "boss"), - "Pyramid Orb": LocationData(110659, LocationFlag.main_world, "orb"), - "Sandcave Orb": LocationData(110662, LocationFlag.main_world, "orb"), + "Kolmisilmän Koipi": LocationData(110649, LocationFlag.main_world, "Boss"), + "Pyramid Orb": LocationData(110659, LocationFlag.main_world, "Orb"), + "Sandcave Orb": LocationData(110662, LocationFlag.main_world, "Orb"), }, "Overgrown Cavern": { - "Overgrown Cavern Chest": LocationData(110526, LocationFlag.main_world, "chest"), - "Overgrown Cavern Pedestal": LocationData(110546, LocationFlag.main_world, "pedestal"), + "Overgrown Cavern Chest": LocationData(110526, LocationFlag.main_world, "Chest"), + "Overgrown Cavern Pedestal": LocationData(110546, LocationFlag.main_world, "Pedestal"), }, "Lake": { - "Syväolento": LocationData(110651, LocationFlag.main_world, "boss"), + "Syväolento": LocationData(110651, LocationFlag.main_world, "Boss"), + "Tapion vasalli": LocationData(110669, LocationFlag.main_world, "Boss"), }, "Frozen Vault": { - "Frozen Vault Orb": LocationData(110660, LocationFlag.main_world, "orb"), - "Frozen Vault Chest": LocationData(110566, LocationFlag.main_world, "chest"), - "Frozen Vault Pedestal": LocationData(110586, LocationFlag.main_world, "pedestal"), + "Frozen Vault Orb": LocationData(110660, LocationFlag.main_world, "Orb"), + "Frozen Vault Chest": LocationData(110566, LocationFlag.main_world, "Chest"), + "Frozen Vault Pedestal": LocationData(110586, LocationFlag.main_world, "Pedestal"), }, "Mines": { - "Mines Chest": LocationData(110046, LocationFlag.main_path, "chest"), - "Mines Pedestal": LocationData(110066, LocationFlag.main_path, "pedestal"), - }, - # Collapsed Mines is a very small area, combining it with the Mines. Leaving this here in case we change our minds. - # "Collapsed Mines": { - # "Collapsed Mines Chest": LocationData(110086, LocationFlag.main_path, "chest"), - # "Collapsed Mines Pedestal": LocationData(110106, LocationFlag.main_path, "pedestal"), - # }, + "Mines Chest": LocationData(110046, LocationFlag.main_path, "Chest"), + "Mines Pedestal": LocationData(110066, LocationFlag.main_path, "Pedestal"), + }, + # Collapsed Mines is a very small area, combining it with the Mines. Leaving this here as a reminder + "Ancient Laboratory": { - "Ylialkemisti": LocationData(110656, LocationFlag.side_path, "boss"), + "Ylialkemisti": LocationData(110656, LocationFlag.side_path, "Boss"), }, "Abyss Orb Room": { - "Sauvojen Tuntija": LocationData(110650, LocationFlag.side_path, "boss"), - "Abyss Orb": LocationData(110665, LocationFlag.main_path, "orb"), + "Sauvojen Tuntija": LocationData(110650, LocationFlag.side_path, "Boss"), + "Abyss Orb": LocationData(110665, LocationFlag.main_path, "Orb"), }, "Below Lava Lake": { - "Lava Lake Orb": LocationData(110661, LocationFlag.side_path, "orb"), + "Lava Lake Orb": LocationData(110661, LocationFlag.side_path, "Orb"), }, "Coal Pits": { - "Coal Pits Chest": LocationData(110126, LocationFlag.main_path, "chest"), - "Coal Pits Pedestal": LocationData(110146, LocationFlag.main_path, "pedestal"), + "Coal Pits Chest": LocationData(110126, LocationFlag.main_path, "Chest"), + "Coal Pits Pedestal": LocationData(110146, LocationFlag.main_path, "Pedestal"), }, "Fungal Caverns": { - "Fungal Caverns Chest": LocationData(110166, LocationFlag.side_path, "chest"), - "Fungal Caverns Pedestal": LocationData(110186, LocationFlag.side_path, "pedestal"), + "Fungal Caverns Chest": LocationData(110166, LocationFlag.side_path, "Chest"), + "Fungal Caverns Pedestal": LocationData(110186, LocationFlag.side_path, "Pedestal"), }, "Snowy Depths": { - "Snowy Depths Chest": LocationData(110206, LocationFlag.main_path, "chest"), - "Snowy Depths Pedestal": LocationData(110226, LocationFlag.main_path, "pedestal"), + "Snowy Depths Chest": LocationData(110206, LocationFlag.main_path, "Chest"), + "Snowy Depths Pedestal": LocationData(110226, LocationFlag.main_path, "Pedestal"), }, "Magical Temple": { - "Magical Temple Orb": LocationData(110663, LocationFlag.side_path, "orb"), + "Magical Temple Orb": LocationData(110663, LocationFlag.side_path, "Orb"), }, "Hiisi Base": { - "Hiisi Base Chest": LocationData(110246, LocationFlag.main_path, "chest"), - "Hiisi Base Pedestal": LocationData(110266, LocationFlag.main_path, "pedestal"), + "Hiisi Base Chest": LocationData(110246, LocationFlag.main_path, "Chest"), + "Hiisi Base Pedestal": LocationData(110266, LocationFlag.main_path, "Pedestal"), }, "Underground Jungle": { - "Suomuhauki": LocationData(110648, LocationFlag.main_path, "boss"), - "Underground Jungle Chest": LocationData(110286, LocationFlag.main_path, "chest"), - "Underground Jungle Pedestal": LocationData(110306, LocationFlag.main_path, "pedestal"), + "Suomuhauki": LocationData(110648, LocationFlag.main_path, "Boss"), + "Underground Jungle Chest": LocationData(110286, LocationFlag.main_path, "Chest"), + "Underground Jungle Pedestal": LocationData(110306, LocationFlag.main_path, "Pedestal"), }, "Lukki Lair": { - "Lukki Lair Orb": LocationData(110664, LocationFlag.side_path, "orb"), - "Lukki Lair Chest": LocationData(110326, LocationFlag.side_path, "chest"), - "Lukki Lair Pedestal": LocationData(110346, LocationFlag.side_path, "pedestal"), + "Lukki Lair Orb": LocationData(110664, LocationFlag.side_path, "Orb"), + "Lukki Lair Chest": LocationData(110326, LocationFlag.side_path, "Chest"), + "Lukki Lair Pedestal": LocationData(110346, LocationFlag.side_path, "Pedestal"), }, "The Vault": { - "The Vault Chest": LocationData(110366, LocationFlag.main_path, "chest"), - "The Vault Pedestal": LocationData(110386, LocationFlag.main_path, "pedestal"), + "The Vault Chest": LocationData(110366, LocationFlag.main_path, "Chest"), + "The Vault Pedestal": LocationData(110386, LocationFlag.main_path, "Pedestal"), }, "Temple of the Art": { - "Gate Guardian": LocationData(110652, LocationFlag.main_path, "boss"), - "Temple of the Art Chest": LocationData(110406, LocationFlag.main_path, "chest"), - "Temple of the Art Pedestal": LocationData(110426, LocationFlag.main_path, "pedestal"), + "Gate Guardian": LocationData(110652, LocationFlag.main_path, "Boss"), + "Temple of the Art Chest": LocationData(110406, LocationFlag.main_path, "Chest"), + "Temple of the Art Pedestal": LocationData(110426, LocationFlag.main_path, "Pedestal"), }, "The Tower": { - "The Tower Chest": LocationData(110606, LocationFlag.main_world, "chest"), - "The Tower Pedestal": LocationData(110626, LocationFlag.main_world, "pedestal"), + "The Tower Chest": LocationData(110606, LocationFlag.main_world, "Chest"), + "The Tower Pedestal": LocationData(110626, LocationFlag.main_world, "Pedestal"), }, "Wizards' Den": { - "Mestarien Mestari": LocationData(110655, LocationFlag.main_world, "boss"), - "Wizards' Den Orb": LocationData(110668, LocationFlag.main_world, "orb"), - "Wizards' Den Chest": LocationData(110446, LocationFlag.main_world, "chest"), - "Wizards' Den Pedestal": LocationData(110466, LocationFlag.main_world, "pedestal"), + "Mestarien Mestari": LocationData(110655, LocationFlag.main_world, "Boss"), + "Wizards' Den Orb": LocationData(110668, LocationFlag.main_world, "Orb"), + "Wizards' Den Chest": LocationData(110446, LocationFlag.main_world, "Chest"), + "Wizards' Den Pedestal": LocationData(110466, LocationFlag.main_world, "Pedestal"), }, "Powerplant": { - "Kolmisilmän silmä": LocationData(110657, LocationFlag.main_world, "boss"), - "Power Plant Chest": LocationData(110486, LocationFlag.main_world, "chest"), - "Power Plant Pedestal": LocationData(110506, LocationFlag.main_world, "pedestal"), + "Kolmisilmän silmä": LocationData(110657, LocationFlag.main_world, "Boss"), + "Power Plant Chest": LocationData(110486, LocationFlag.main_world, "Chest"), + "Power Plant Pedestal": LocationData(110506, LocationFlag.main_world, "Pedestal"), }, "Snow Chasm": { - "Unohdettu": LocationData(110653, LocationFlag.main_world, "boss"), - "Snow Chasm Orb": LocationData(110667, LocationFlag.main_world, "orb"), + "Unohdettu": LocationData(110653, LocationFlag.main_world, "Boss"), + "Snow Chasm Orb": LocationData(110667, LocationFlag.main_world, "Orb"), + }, + "Meat Realm": { + "Meat Realm Chest": LocationData(110086, LocationFlag.main_world, "Chest"), + "Meat Realm Pedestal": LocationData(110106, LocationFlag.main_world, "Pedestal"), + "Limatoukka": LocationData(110647, LocationFlag.main_world, "Boss"), }, - "Deep Underground": { - "Limatoukka": LocationData(110647, LocationFlag.main_world, "boss"), + "West Meat Realm": { + "Kolmisilmän sydän": LocationData(110671, LocationFlag.main_world, "Boss"), }, "The Laboratory": { - "Kolmisilmä": LocationData(110646, LocationFlag.main_path, "boss"), + "Kolmisilmä": LocationData(110646, LocationFlag.main_path, "Boss"), }, "Friend Cave": { - "Toveri": LocationData(110654, LocationFlag.main_world, "boss"), + "Toveri": LocationData(110654, LocationFlag.main_world, "Boss"), }, "The Work (Hell)": { - "The Work (Hell) Orb": LocationData(110666, LocationFlag.main_world, "orb"), + "The Work (Hell) Orb": LocationData(110666, LocationFlag.main_world, "Orb"), }, } @@ -207,18 +213,20 @@ def make_location_range(location_name: str, base_id: int, amt: int) -> Dict[str, return {f"{location_name} {i+1}": base_id + i for i in range(amt)} -location_name_groups: Dict[str, Set[str]] = {"shop": set(), "orb": set(), "boss": set(), "chest": set(), - "pedestal": set()} +location_name_groups: Dict[str, Set[str]] = {"Shop": set(), "Orb": set(), "Boss": set(), "Chest": set(), + "Pedestal": set()} location_name_to_id: Dict[str, int] = {} -for location_group in location_region_mapping.values(): +for region_name, location_group in location_region_mapping.items(): + location_name_groups[region_name] = set() for locname, locinfo in location_group.items(): # Iterating the hidden chest and pedestal locations here to avoid clutter above - amount = 20 if locinfo.ltype in ["chest", "pedestal"] else 1 + amount = 20 if locinfo.ltype in ["Chest", "Pedestal"] else 1 entries = make_location_range(locname, locinfo.id, amount) location_name_to_id.update(entries) location_name_groups[locinfo.ltype].update(entries.keys()) + location_name_groups[region_name].update(entries.keys()) shop_locations = {name for name in location_name_to_id.keys() if "Shop Item" in name} diff --git a/worlds/noita/options.py b/worlds/noita/options.py index 7d987571a589..0fdd62365a5a 100644 --- a/worlds/noita/options.py +++ b/worlds/noita/options.py @@ -3,11 +3,13 @@ class PathOption(Choice): - """Choose where you would like Hidden Chest and Pedestal checks to be placed. + """ + Choose where you would like Hidden Chest and Pedestal checks to be placed. Main Path includes the main 7 biomes you typically go through to get to the final boss. Side Path includes the Lukki Lair and Fungal Caverns. 9 biomes total. - Main World includes the full world (excluding parallel worlds). 14 biomes total. - Note: The Collapsed Mines have been combined into the Mines as the biome is tiny.""" + Main World includes the full world (excluding parallel worlds). 15 biomes total. + Note: The Collapsed Mines have been combined into the Mines as the biome is tiny. + """ display_name = "Path Option" option_main_path = 1 option_side_path = 2 @@ -16,7 +18,9 @@ class PathOption(Choice): class HiddenChests(Range): - """Number of hidden chest checks added to the applicable biomes.""" + """ + Number of hidden chest checks added to the applicable biomes. + """ display_name = "Hidden Chests per Biome" range_start = 0 range_end = 20 @@ -24,7 +28,9 @@ class HiddenChests(Range): class PedestalChecks(Range): - """Number of checks that will spawn on pedestals in the applicable biomes.""" + """ + Number of checks that will spawn on pedestals in the applicable biomes. + """ display_name = "Pedestal Checks per Biome" range_start = 0 range_end = 20 @@ -32,15 +38,19 @@ class PedestalChecks(Range): class Traps(DefaultOnToggle): - """Whether negative effects on the Noita world are added to the item pool.""" + """ + Whether negative effects on the Noita world are added to the item pool. + """ display_name = "Traps" class OrbsAsChecks(Choice): - """Decides whether finding the orbs that naturally spawn in the world count as checks. + """ + Decides whether finding the orbs that naturally spawn in the world count as checks. The Main Path option includes only the Floating Island and Abyss Orb Room orbs. The Side Path option includes the Main Path, Magical Temple, Lukki Lair, and Lava Lake orbs. - The Main World option includes all 11 orbs.""" + The Main World option includes all 11 orbs. + """ display_name = "Orbs as Location Checks" option_no_orbs = 0 option_main_path = 1 @@ -50,10 +60,12 @@ class OrbsAsChecks(Choice): class BossesAsChecks(Choice): - """Makes bosses count as location checks. The boss only needs to die, you do not need the kill credit. + """ + Makes bosses count as location checks. The boss only needs to die, you do not need the kill credit. The Main Path option includes Gate Guardian, Suomuhauki, and Kolmisilmä. The Side Path option includes the Main Path bosses, Sauvojen Tuntija, and Ylialkemisti. - The All Bosses option includes all 12 bosses.""" + The All Bosses option includes all 15 bosses. + """ display_name = "Bosses as Location Checks" option_no_bosses = 0 option_main_path = 1 @@ -65,11 +77,13 @@ class BossesAsChecks(Choice): # Note: the Sampo is an item that is picked up to trigger the boss fight at the normal ending location. # The sampo is required for every ending (having orbs and bringing the sampo to a different spot changes the ending). class VictoryCondition(Choice): - """Greed is to get to the bottom, beat the boss, and win the game. + """ + Greed is to get to the bottom, beat the boss, and win the game. Pure is to get 11 orbs, grab the sampo, and bring it to the mountain altar. Peaceful is to get all 33 orbs, grab the sampo, and bring it to the mountain altar. Orbs will be added to the randomizer pool based on which victory condition you chose. - The base game orbs will not count towards these victory conditions.""" + The base game orbs will not count towards these victory conditions. + """ display_name = "Victory Condition" option_greed_ending = 0 option_pure_ending = 1 @@ -78,10 +92,11 @@ class VictoryCondition(Choice): class ExtraOrbs(Range): - """Add extra orbs to your item pool, to prevent you from needing to wait as long - for the last orb you need for your victory condition. + """ + Add extra orbs to your item pool, to prevent you from needing to wait as long for the last orb you need for your victory condition. Extra orbs received past your victory condition's amount will be received as hearts instead. - Can be turned on for the Greed Ending goal, but will only really make it harder.""" + Can be turned on for the Greed Ending goal, but will only really make it harder. + """ display_name = "Extra Orbs" range_start = 0 range_end = 10 @@ -89,8 +104,10 @@ class ExtraOrbs(Range): class ShopPrice(Choice): - """Reduce the costs of Archipelago items in shops. - By default, the price of Archipelago items matches the price of wands at that shop.""" + """ + Reduce the costs of Archipelago items in shops. + By default, the price of Archipelago items matches the price of wands at that shop. + """ display_name = "Shop Price Reduction" option_full_price = 100 option_25_percent_off = 75 @@ -99,10 +116,17 @@ class ShopPrice(Choice): default = 100 +class NoitaDeathLink(DeathLink): + """ + When you die, everyone dies. Of course, the reverse is true too. + You can disable this in the in-game mod options. + """ + + @dataclass class NoitaOptions(PerGameCommonOptions): start_inventory_from_pool: StartInventoryPool - death_link: DeathLink + death_link: NoitaDeathLink bad_effects: Traps victory_condition: VictoryCondition path_option: PathOption diff --git a/worlds/noita/regions.py b/worlds/noita/regions.py index 6a9c86772381..184cd96018cf 100644 --- a/worlds/noita/regions.py +++ b/worlds/noita/regions.py @@ -15,14 +15,14 @@ def create_locations(world: "NoitaWorld", region: Region) -> None: location_type = location_data.ltype flag = location_data.flag - is_orb_allowed = location_type == "orb" and flag <= world.options.orbs_as_checks - is_boss_allowed = location_type == "boss" and flag <= world.options.bosses_as_checks + is_orb_allowed = location_type == "Orb" and flag <= world.options.orbs_as_checks + is_boss_allowed = location_type == "Boss" and flag <= world.options.bosses_as_checks amount = 0 if flag == locations.LocationFlag.none or is_orb_allowed or is_boss_allowed: amount = 1 - elif location_type == "chest" and flag <= world.options.path_option: + elif location_type == "Chest" and flag <= world.options.path_option: amount = world.options.hidden_chests.value - elif location_type == "pedestal" and flag <= world.options.path_option: + elif location_type == "Pedestal" and flag <= world.options.path_option: amount = world.options.pedestal_checks.value region.add_locations(locations.make_location_range(location_name, location_data.id, amount), @@ -41,7 +41,7 @@ def create_regions(world: "NoitaWorld") -> Dict[str, Region]: # An "Entrance" is really just a connection between two regions -def create_entrance(player: int, source: str, destination: str, regions: Dict[str, Region]): +def create_entrance(player: int, source: str, destination: str, regions: Dict[str, Region]) -> Entrance: entrance = Entrance(player, f"From {source} To {destination}", regions[source]) entrance.connect(regions[destination]) return entrance @@ -72,7 +72,7 @@ def create_all_regions_and_connections(world: "NoitaWorld") -> None: # - Snow Chasm is disconnected from the Snowy Wasteland # - Pyramid is connected to the Hiisi Base instead of the Desert due to similar difficulty # - Frozen Vault is connected to the Vault instead of the Snowy Wasteland due to similar difficulty -# - Lake is connected to The Laboratory, since the boss is hard without specific set-ups (which means late game) +# - Lake is connected to The Laboratory, since the bosses are hard without specific set-ups (which means late game) # - Snowy Depths connects to Lava Lake orb since you need digging for it, so fairly early is acceptable # - Ancient Laboratory is connected to the Coal Pits, so that Ylialkemisti isn't sphere 1 noita_connections: Dict[str, List[str]] = { @@ -99,7 +99,7 @@ def create_all_regions_and_connections(world: "NoitaWorld") -> None: ### "Underground Jungle Holy Mountain": ["Underground Jungle"], - "Underground Jungle": ["Dragoncave", "Overgrown Cavern", "Vault Holy Mountain", "Lukki Lair", "Snow Chasm"], + "Underground Jungle": ["Dragoncave", "Overgrown Cavern", "Vault Holy Mountain", "Lukki Lair", "Snow Chasm", "West Meat Realm"], ### "Vault Holy Mountain": ["The Vault"], @@ -109,11 +109,11 @@ def create_all_regions_and_connections(world: "NoitaWorld") -> None: "Temple of the Art Holy Mountain": ["Temple of the Art"], "Temple of the Art": ["Laboratory Holy Mountain", "The Tower", "Wizards' Den"], "Wizards' Den": ["Powerplant"], - "Powerplant": ["Deep Underground"], + "Powerplant": ["Meat Realm"], ### "Laboratory Holy Mountain": ["The Laboratory"], - "The Laboratory": ["The Work", "Friend Cave", "The Work (Hell)", "Lake"], + "The Laboratory": ["The Work", "Friend Cave", "The Work (Hell)", "Lake", "The Sky"], ### } diff --git a/worlds/noita/rules.py b/worlds/noita/rules.py index 95039bee4635..65871a804ea0 100644 --- a/worlds/noita/rules.py +++ b/worlds/noita/rules.py @@ -68,7 +68,7 @@ def has_orb_count(state: CollectionState, player: int, amount: int) -> bool: return state.count("Orb", player) >= amount -def forbid_items_at_locations(world: "NoitaWorld", shop_locations: Set[str], forbidden_items: Set[str]): +def forbid_items_at_locations(world: "NoitaWorld", shop_locations: Set[str], forbidden_items: Set[str]) -> None: for shop_location in shop_locations: location = world.multiworld.get_location(shop_location, world.player) GenericRules.forbid_items_for_player(location, forbidden_items, world.player) @@ -129,7 +129,7 @@ def holy_mountain_unlock_conditions(world: "NoitaWorld") -> None: ) -def biome_unlock_conditions(world: "NoitaWorld"): +def biome_unlock_conditions(world: "NoitaWorld") -> None: lukki_entrances = world.multiworld.get_region("Lukki Lair", world.player).entrances magical_entrances = world.multiworld.get_region("Magical Temple", world.player).entrances wizard_entrances = world.multiworld.get_region("Wizards' Den", world.player).entrances diff --git a/worlds/oot/Location.py b/worlds/oot/Location.py index 3f7d75517e30..f924dd048da1 100644 --- a/worlds/oot/Location.py +++ b/worlds/oot/Location.py @@ -44,14 +44,11 @@ def __init__(self, player, name='', code=None, address1=None, address2=None, self.vanilla_item = vanilla_item if filter_tags is None: self.filter_tags = None - else: + else: self.filter_tags = list(filter_tags) self.never = False # no idea what this does self.disabled = DisableType.ENABLED - if type == 'Event': - self.event = True - @property def dungeon(self): return self.parent_region.dungeon diff --git a/worlds/oot/Options.py b/worlds/oot/Options.py index 2543cdc715c7..daf072adb59c 100644 --- a/worlds/oot/Options.py +++ b/worlds/oot/Options.py @@ -1,6 +1,7 @@ import typing import random -from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink +from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections +from .EntranceShuffle import entrance_shuffle_table from .LogicTricks import normalized_name_tricks from .ColorSFXOptions import * @@ -29,6 +30,11 @@ def from_any(cls, data: typing.Any) -> Range: raise RuntimeError(f"All options specified in \"{cls.display_name}\" are weighted as zero.") +class OoTPlandoConnections(PlandoConnections): + entrances = set([connection[1][0] for connection in entrance_shuffle_table]) + exits = set([connection[2][0] for connection in entrance_shuffle_table if len(connection) > 2]) + + class Logic(Choice): """Set the logic used for the generator. Glitchless: Normal gameplay. Can enable more difficult logical paths using the Logic Tricks option. @@ -1277,6 +1283,7 @@ class LogicTricks(OptionList): # All options assembled into a single dict oot_options: typing.Dict[str, type(Option)] = { + "plando_connections": OoTPlandoConnections, "logic_rules": Logic, "logic_no_night_tokens_without_suns_song": NightTokens, **open_options, diff --git a/worlds/oot/Patches.py b/worlds/oot/Patches.py index 0f1d3f4dcb8c..2219d7bb95a8 100644 --- a/worlds/oot/Patches.py +++ b/worlds/oot/Patches.py @@ -29,14 +29,14 @@ from .texture_util import ci4_rgba16patch_to_ci8, rgba16_patch from .Utils import __version__ -from worlds.Files import APContainer +from worlds.Files import APPatch from Utils import __version__ as ap_version AP_PROGRESSION = 0xD4 AP_JUNK = 0xD5 -class OoTContainer(APContainer): +class OoTContainer(APPatch): game: str = 'Ocarina of Time' def __init__(self, patch_data: bytes, base_path: str, output_directory: str, diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index 2f06500e81b6..89f10a5a2da0 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -32,7 +32,7 @@ from Utils import get_options from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType -from Options import Range, Toggle, VerifyKeys, Accessibility +from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections from Fill import fill_restrictive, fast_fill, FillError from worlds.generic.Rules import exclusion_rules, add_item_rule from ..AutoWorld import World, AutoLogicRegister, WebWorld @@ -150,8 +150,6 @@ class OOTWorld(World): location_name_to_id = location_name_to_id web = OOTWeb() - data_version = 3 - required_client_version = (0, 4, 0) item_name_groups = { @@ -175,6 +173,15 @@ class OOTWorld(World): "Adult Trade Item": {"Pocket Egg", "Pocket Cucco", "Cojiro", "Odd Mushroom", "Odd Potion", "Poachers Saw", "Broken Sword", "Prescription", "Eyeball Frog", "Eyedrops", "Claim Check"}, + "Keys": {"Small Key (Bottom of the Well)", "Small Key (Fire Temple)", "Small Key (Forest Temple)", + "Small Key (Ganons Castle)", "Small Key (Gerudo Training Ground)", "Small Key (Shadow Temple)", + "Small Key (Spirit Temple)", "Small Key (Thieves Hideout)", "Small Key (Water Temple)", + "Small Key Ring (Bottom of the Well)", "Small Key Ring (Fire Temple)", + "Small Key Ring (Forest Temple)", "Small Key Ring (Ganons Castle)", + "Small Key Ring (Gerudo Training Ground)", "Small Key Ring (Shadow Temple)", + "Small Key Ring (Spirit Temple)", "Small Key Ring (Thieves Hideout)", "Small Key Ring (Water Temple)", + "Boss Key (Fire Temple)", "Boss Key (Forest Temple)", "Boss Key (Ganons Castle)", + "Boss Key (Shadow Temple)", "Boss Key (Spirit Temple)", "Boss Key (Water Temple)"}, } location_name_groups = build_location_name_groups() @@ -203,6 +210,8 @@ def generate_early(self): option_value = bool(result) elif isinstance(result, VerifyKeys): option_value = result.value + elif isinstance(result, PlandoConnections): + option_value = result.value else: option_value = result.current_key setattr(self, option_name, option_value) @@ -717,7 +726,6 @@ def make_event_item(self, name, location, item=None): item = self.create_item(name, allow_arbitrary_name=True) self.multiworld.push_item(location, item, collect=False) location.locked = True - location.event = True if name not in item_table: location.internal = True return item @@ -842,7 +850,7 @@ def generate_basic(self): # mostly killing locations that shouldn't exist by se all_state.sweep_for_events(locations=all_locations) reachable = self.multiworld.get_reachable_locations(all_state, self.player) unreachable = [loc for loc in all_locations if - (loc.internal or loc.type == 'Drop') and loc.event and loc.locked and loc not in reachable] + (loc.internal or loc.type == 'Drop') and loc.address is None and loc.locked and loc not in reachable] for loc in unreachable: loc.parent_region.locations.remove(loc) # Exception: Sell Big Poe is an event which is only reachable if Bottle with Big Poe is in the item pool. @@ -972,7 +980,6 @@ def prefill_state(base_state): for location in song_locations: location.item = None location.locked = False - location.event = False else: break @@ -1034,6 +1041,31 @@ def prefill_state(base_state): def generate_output(self, output_directory: str): + + # Write entrances to spoiler log + all_entrances = self.get_shuffled_entrances() + all_entrances.sort(reverse=True, key=lambda x: (x.type, x.name)) + if not self.decouple_entrances: + while all_entrances: + loadzone = all_entrances.pop() + if loadzone.type != 'Overworld': + if loadzone.primary: + entrance = loadzone + else: + entrance = loadzone.reverse + if entrance.reverse is not None: + self.multiworld.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player) + else: + self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player) + else: + reverse = loadzone.replaces.reverse + if reverse in all_entrances: + all_entrances.remove(reverse) + self.multiworld.spoiler.set_entrance(loadzone, reverse, 'both', self.player) + else: + for entrance in all_entrances: + self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player) + if self.hints != 'none': self.hint_data_available.wait() @@ -1150,10 +1182,35 @@ def hint_type_players(hint_type: str) -> set: def fill_slot_data(self): self.collectible_flags_available.wait() - return { + + slot_data = { 'collectible_override_flags': self.collectible_override_flags, 'collectible_flag_offsets': self.collectible_flag_offsets } + slot_data.update(self.options.as_dict( + "open_forest", "open_kakariko", "open_door_of_time", "zora_fountain", "gerudo_fortress", + "bridge", "bridge_stones", "bridge_medallions", "bridge_rewards", "bridge_tokens", "bridge_hearts", + "shuffle_ganon_bosskey", "ganon_bosskey_medallions", "ganon_bosskey_stones", "ganon_bosskey_rewards", + "ganon_bosskey_tokens", "ganon_bosskey_hearts", "trials", + "triforce_hunt", "triforce_goal", "extra_triforce_percentage", + "shopsanity", "shop_slots", "shopsanity_prices", "tokensanity", + "dungeon_shortcuts", "dungeon_shortcuts_list", + "mq_dungeons_mode", "mq_dungeons_list", "mq_dungeons_count", + "shuffle_interior_entrances", "shuffle_grotto_entrances", "shuffle_dungeon_entrances", + "shuffle_overworld_entrances", "shuffle_bosses", "key_rings", "key_rings_list", "enhance_map_compass", + "shuffle_mapcompass", "shuffle_smallkeys", "shuffle_hideoutkeys", "shuffle_bosskeys", + "logic_rules", "logic_no_night_tokens_without_suns_song", "logic_tricks", + "warp_songs", "shuffle_song_items","shuffle_medigoron_carpet_salesman", "shuffle_frog_song_rupees", + "shuffle_scrubs", "shuffle_child_trade", "shuffle_freestanding_items", "shuffle_pots", "shuffle_crates", + "shuffle_cows", "shuffle_beehives", "shuffle_kokiri_sword", "shuffle_ocarinas", "shuffle_gerudo_card", + "shuffle_beans", "starting_age", "bombchus_in_logic", "spawn_positions", "owl_drops", + "no_epona_race", "skip_some_minigame_phases", "complete_mask_quest", "free_scarecrow", "plant_beans", + "chicken_count", "big_poe_count", "fae_torch_count", "blue_fire_arrows", + "damage_multiplier", "deadly_bonks", "starting_tod", "junk_ice_traps", + "start_with_consumables", "adult_trade_start", "plando_connections" + ) + ) + return slot_data def modify_multidata(self, multidata: dict): @@ -1229,6 +1286,8 @@ def get_entrance_to_region(region): def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: required_trials_str = ", ".join(t for t in self.skipped_trials if not self.skipped_trials[t]) + if required_trials_str == "": + required_trials_str = "None" spoiler_handle.write(f"\n\nTrials ({self.multiworld.get_player_name(self.player)}): {required_trials_str}\n") if self.shopsanity != 'off': @@ -1236,31 +1295,6 @@ def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: for k, v in self.shop_prices.items(): spoiler_handle.write(f"{k}: {v} Rupees\n") - # Write entrances to spoiler log - all_entrances = self.get_shuffled_entrances() - all_entrances.sort(reverse=True, key=lambda x: x.name) - all_entrances.sort(reverse=True, key=lambda x: x.type) - if not self.decouple_entrances: - while all_entrances: - loadzone = all_entrances.pop() - if loadzone.type != 'Overworld': - if loadzone.primary: - entrance = loadzone - else: - entrance = loadzone.reverse - if entrance.reverse is not None: - self.multiworld.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player) - else: - self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player) - else: - reverse = loadzone.replaces.reverse - if reverse in all_entrances: - all_entrances.remove(reverse) - self.multiworld.spoiler.set_entrance(loadzone, reverse, 'both', self.player) - else: - for entrance in all_entrances: - self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player) - # Key ring handling: # Key rings are multiple items glued together into one, so we need to give diff --git a/worlds/oot/docs/en_Ocarina of Time.md b/worlds/oot/docs/en_Ocarina of Time.md index fa8e148957c7..5a480d864124 100644 --- a/worlds/oot/docs/en_Ocarina of Time.md +++ b/worlds/oot/docs/en_Ocarina of Time.md @@ -1,8 +1,8 @@ # Ocarina of Time -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -37,4 +37,4 @@ business! The following commands are only available when using the OoTClient to play with Archipelago. - `/n64` Check N64 Connection State -- `/deathlink` Toggle deathlink from client. Overrides default setting. +- `/deathlink` Toggle deathlink from client. Overrides default option. diff --git a/worlds/oot/docs/setup_en.md b/worlds/oot/docs/setup_en.md index 4d27019fa771..553f1820c3ea 100644 --- a/worlds/oot/docs/setup_en.md +++ b/worlds/oot/docs/setup_en.md @@ -50,8 +50,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Ocarina of Time Player Settings Page](/games/Ocarina%20of%20Time/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Ocarina of Time Player Options Page](/games/Ocarina%20of%20Time/player-options) ### Verifying your config file diff --git a/worlds/oot/docs/setup_fr.md b/worlds/oot/docs/setup_fr.md index f5915e18782f..40b0e8f571df 100644 --- a/worlds/oot/docs/setup_fr.md +++ b/worlds/oot/docs/setup_fr.md @@ -46,7 +46,7 @@ guide : [Guide de configuration de base de Multiworld](/tutorial/Archipelago/set ### Où puis-je obtenir un fichier de configuration (.yaml) ? -La page Paramètres du lecteur sur le site Web vous permet de configurer vos paramètres personnels et d'exporter un fichier de configuration depuis eux. Page des paramètres du joueur : [Page des paramètres du joueur d'Ocarina of Time](/games/Ocarina%20of%20Time/player-settings) +La page Paramètres du lecteur sur le site Web vous permet de configurer vos paramètres personnels et d'exporter un fichier de configuration depuis eux. Page des paramètres du joueur : [Page des paramètres du joueur d'Ocarina of Time](/games/Ocarina%20of%20Time/player-options) ### Vérification de votre fichier de configuration @@ -67,4 +67,4 @@ Une fois le client et l'émulateur démarrés, vous devez les connecter. Accéde Pour connecter le client au multiserveur, mettez simplement `:` dans le champ de texte en haut et appuyez sur Entrée (si le serveur utilise un mot de passe, tapez dans le champ de texte inférieur `/connect : [mot de passe]`) -Vous êtes maintenant prêt à commencer votre aventure dans Hyrule. \ No newline at end of file +Vous êtes maintenant prêt à commencer votre aventure dans Hyrule. diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py index da0e1890894a..44227d4becaa 100644 --- a/worlds/overcooked2/__init__.py +++ b/worlds/overcooked2/__init__.py @@ -48,7 +48,6 @@ class Overcooked2World(World): web = Overcooked2Web() required_client_version = (0, 3, 8) topology_present: bool = False - data_version = 3 item_name_to_id = item_name_to_id item_id_to_name = item_id_to_name @@ -115,8 +114,6 @@ def add_level_location( region, ) - location.event = is_event - if priority: location.progress_type = LocationProgressType.PRIORITY else: @@ -219,8 +216,6 @@ def get_priority_locations(self) -> List[int]: # Autoworld Hooks def generate_early(self): - self.player_name = self.multiworld.player_name[self.player] - # 0.0 to 1.0 where 1.0 is World Record self.star_threshold_scale = self.options.star_threshold_scale / 100.0 diff --git a/worlds/overcooked2/docs/en_Overcooked! 2.md b/worlds/overcooked2/docs/en_Overcooked! 2.md index 298c33683ce7..d4cb6fba1f9a 100644 --- a/worlds/overcooked2/docs/en_Overcooked! 2.md +++ b/worlds/overcooked2/docs/en_Overcooked! 2.md @@ -2,7 +2,7 @@ ## Quick Links - [Setup Guide](../../../../tutorial/Overcooked!%202/setup/en) -- [Settings Page](../../../../games/Overcooked!%202/player-settings) +- [Options Page](../../../../games/Overcooked!%202/player-options) - [OC2-Modding GitHub](https://github.com/toasterparty/oc2-modding) ## How Does Randomizer Work in the Kitchen? @@ -55,7 +55,7 @@ The following items were invented for Randomizer: - Ramp Buttons (x7) - Bonus Star (Filler Item*) -**Note: Bonus star count varies with settings* +**Note: Bonus star count varies with options* ## Other Game Modifications diff --git a/worlds/overcooked2/docs/setup_en.md b/worlds/overcooked2/docs/setup_en.md index 1b21642cfe03..bb4c4959a7d4 100644 --- a/worlds/overcooked2/docs/setup_en.md +++ b/worlds/overcooked2/docs/setup_en.md @@ -2,7 +2,7 @@ ## Quick Links - [Main Page](../../../../games/Overcooked!%202/info/en) -- [Settings Page](../../../../games/Overcooked!%202/player-settings) +- [Options Page](../../../../games/Overcooked!%202/player-options) - [OC2-Modding GitHub](https://github.com/toasterparty/oc2-modding) ## Required Software @@ -49,9 +49,7 @@ To completely remove *OC2-Modding*, navigate to your game's installation folder ## Generate a MultiWorld Game -1. Visit the [Player Settings](../../../../games/Overcooked!%202/player-settings) page and configure the game-specific settings to taste - -*By default, these settings will only use levels from the base game and the "Seasonal" free DLC updates. If you own any of the paid DLC, you may select individual DLC packs to include/exclude on the [Weighted Settings](../../../../weighted-settings) page* +1. Visit the [Player Options](../../../../games/Overcooked!%202/player-options) page and configure the game-specific options to taste 2. Export your yaml file and use it to generate a new randomized game @@ -84,11 +82,11 @@ To completely remove *OC2-Modding*, navigate to your game's installation folder Since the goal of randomizer isn't necessarily to achieve new personal high scores, players may find themselves waiting for a level timer to expire once they've met their objective. A new feature called *Auto-Complete* has been added to automatically complete levels once a target star count has been achieved. -To enable *Auto-Complete*, press the **Show** button near the top of your screen to expand the modding controls. Then, repeatedly press the **Auto-Complete** button until it shows the desired setting. +To enable *Auto-Complete*, press the **Show** button near the top of your screen to expand the modding controls. Then, repeatedly press the **Auto-Complete** button until it shows the desired option. ## Overworld Sequence Breaking -In the world's settings, there is an option called "Overworld Tricks" which allows the generator to make games which require doing tricks with the food truck to complete. This includes: +In the world's options, there is an option called "Overworld Tricks" which allows the generator to make games which require doing tricks with the food truck to complete. This includes: - Dashing across gaps diff --git a/worlds/overcooked2/test/TestOvercooked2.py b/worlds/overcooked2/test/TestOvercooked2.py index ee0b44a86e9f..a3c1c3dc0d3e 100644 --- a/worlds/overcooked2/test/TestOvercooked2.py +++ b/worlds/overcooked2/test/TestOvercooked2.py @@ -4,10 +4,11 @@ from worlds.AutoWorld import AutoWorldRegister from test.general import setup_solo_multiworld -from worlds.overcooked2.Items import * -from worlds.overcooked2.Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, OverworldRegion, overworld_region_by_level, level_id_to_shortname -from worlds.overcooked2.Logic import level_logic, overworld_region_logic, level_shuffle_factory -from worlds.overcooked2.Locations import oc2_location_name_to_id +from ..Items import * +from ..Overcooked2Levels import (Overcooked2Dlc, Overcooked2Level, OverworldRegion, overworld_region_by_level, + level_id_to_shortname) +from ..Logic import level_logic, overworld_region_logic, level_shuffle_factory +from ..Locations import oc2_location_name_to_id class Overcooked2Test(unittest.TestCase): diff --git a/worlds/pokemon_emerald/CHANGELOG.md b/worlds/pokemon_emerald/CHANGELOG.md new file mode 100644 index 000000000000..0437c0dae8ff --- /dev/null +++ b/worlds/pokemon_emerald/CHANGELOG.md @@ -0,0 +1,223 @@ +# 2.2.0 + +### Features + +- When you blacklist species from wild encounters and turn on dexsanity, blacklisted species are not added as locations +and won't show up in the wild. Previously they would be forced to show up exactly once. +- Added support for some new autotracking events. +- Updated option descriptions. +- Added `full` alias for `100` on TM and HM compatibility options. + +### Fixes + +- The Lilycove Wailmer now logically block you from the east. Actual game behavior is still unchanged for now. +- Water encounters in Slateport now correctly require Surf. +- Mirage Tower can no longer be your only logical access to a species in the wild, since it can permanently disappear. +- Updated the tracker link in the setup guide. + +# 2.1.1 + +### Features + +- You no longer need a copy of Pokemon Emerald to generate a game, patch files generate much faster. + +# 2.1.0 + +_Separately released, branching from 2.0.0. Included procedure patch migration, but none of the 2.0.1 fixes._ + +# 2.0.1 + +### Fixes + +- Changed "Ho-oh" to "Ho-Oh" in options. +- Temporary fix to alleviate problems with sometimes not receiving certain items just after connecting if `remote_items` +is `true`. +- Temporarily disable a possible location for Marine Cave to spawn, as it causes an overflow. +- Water encounters in Dewford now correctly require Surf. + +# 2.0.0 + +### Features +- Picking up items for other players will display the actual item name and receiving player in-game instead of +"ARCHIPELAGO ITEM". (This does have a limit, but you're unlikely to reach it in all but the largest multiworlds.) +- New goal `legendary_hunt`. Your goal is to catch/defeat some number of legendary encounters. That is, the static +encounters themselves, whatever species they may be. Legendary species found in the wild don't count. + - You can force the goal to require captures with `legendary_hunt_catch`. If you accidentally faint a legendary, you + can respawn it by beating the champion. + - The number of legendaries needed is controlled by the `legendary_hunt_count` option. + - The caves containing Kyogre and Groudon are fixed to one location per seed. You need to go to the weather + institute to trigger a permanent weather event at the corresponding locations. Only one weather event can be active + at a time. + - The move tutor for the move Sleep Talk has been changed to Dig and is unlimited use (for Sealed Chamber). + - Relicanth and Wailord are guaranteed to be reachable in the wild (for Sealed Chamber). Interacting with the Sealed + Chamber wall will give you dex info for Wailord and Relicanth. + - Event legendaries are included for this goal (see below for new ferry behavior and event tickets). + - The roamer is included in this count. It will _always_ be Latios no matter what your options are. Otherwise you + might not have any way of knowing which species is roaming to be able to track it. In legendary hunt, Latios will + never appear as a wild pokemon to make tracking it easier. The television broadcast that creates the roamer will + give you dex info for Latios. + - You can set which encounters are considered for this goal with the `allowed_legendary_hunt_encounters` option. +- New option `dexsanity`. Adds pokedex entries as locations. + - Added locations contribute either a Poke Ball, Great Ball, or Ultra Ball to the item pool, based on the evolution + stage. + - Logic uses only wild encounters for now. + - Defeating a gym leader awards "seen" info on 1/8th of the pokedex. +- New option `trainersanity`. Defeating a trainer awards a random item. + - Trainers no longer award money upon defeat. Instead they add a sellable item to the item pool. + - Missable trainers are prevented from disappearing when this is enabled. + - Gym trainers remain active after their leader is defeated. + - Does not include trainers in the Trick House. +- New option `berry_trees`. Adds berry trees as locations. + - All soil patches start with a fully grown berry tree that gives one item. + - There are 88 berry trees. + - Berries cannot be planted in soil with this option enabled. + - Soil that doesn't start with a tree on a fresh save contributes a Sitrus Berry to the item pool. +- New option `death_link`. Forgive me, Figment. +- Added Artisan Cave locations + - Requires Wailmer Pail and the ability to Surf to access. +- Added Trick House locations. The Trick Master is finally here! + - He will make new layouts only if you have the corresponding badge (or beat the game) and have completed the + previous layout (all vanilla behavior). + - If you neglect to pick up an item in a puzzle before completing it, the Trick Master will give the item to you + alongside the prize. + - Locations are enabled or disabled with their broader categories (npc gifts, overworld items, etc...) +- Added daily berry gift locations. There are a half dozen or so NPCs that give you one or two berries per day. + - All these locations are considered NPC gifts. + - The NPCs have been reworked to give this gift once permanently so they can be added as locations. +- New option `remote_items`. All randomized items are sent from the server instead of being patched into your game +(except for start inventory, which remains in the PC) + - As a side effect, when you pick up your own item, there will be a gap between the item disappearing from the + overworld and your game actually receiving it. It also causes gifts from NPCs which contain your own items to not + show up until after their text box closes. It can feel odd, but there should be no danger to it. + - If the seed is in race mode, this is forcibly enabled. + - Benefits include: + - Two players can play the same slot and both receive items that slot picks up for itself (as long as it was + randomized) + - You receive items you picked up for yourself if you lose progress on your save + - Competitive integrity; the patch file no longer has any knowledge of item placement +- New option `match_trainer_levels`. This is a sort of pseudo level cap for a randomizer context. + - When you start a trainer fight, all your pokemon have their levels temporarily set to the highest level in the + opponent's party. + - During the battle, all earned exp is set to 0 (EVs are still gained during battle as normal). When the outcome of + the battle is decided, your pokemon have their levels reset to what they were before the fight and exp is awarded as + it would have been without this option. Think of it as holding earned exp in reserve and awarding it at the end + instead, even giving it to fainted pokemon if they earned any before fainting. + - Exp gain is based on _your_ party's average level to moderate exp over the course of a seed. Wild battles are + entirely unchanged by this option. +- New option `match_trainer_levels_bonus`. A flat bonus to apply to your party's levels when using +`match_trainer_levels`. In case you want to give yourself a nerf or buff while still approximately matching your +opponent. +- New option `force_fully_evolved`. Define a level at which trainers will stop using pokemon that have further evolution +stages. +- New option `move_blacklist`. Define a list of moves that should not be given randomly to learnsets or TMs. Move names +are accurate to Gen 3 except for capitalization. +- New option `extra_bumpy_slope`. Adds a "bumpy slope" to Route 115 that lets you hop up the ledge with the Acro Bike. +- New option `modify_118`. Changes Route 118 so that it must be crossed with the Acro Bike, and cannot be crossed by +surfing. +- Changed `require_flash` option to a choice between none, only granite cave, only victory road, or both caves. +- Removed `static_encounters` option. +- New option `legendary_encounters`. Replaces `static_encounters`, but only concerns legendaries. +- New option `misc_pokemon`. Replaces `static_encounters`, but only concerns non-legendaries. +- Removed `fly_without_badge` option. (Don't worry) +- New option `hm_requirements`. Will eventually be able to give you more control over the badge requirements for all +HMs. For now, only includes the presets `vanilla` and `fly_without_badge`. +- Removed `allow_wild_legendaries`, `allow_starter_legendaries`, and `allow_trainer_legendaries` options. +- New options `wild_encounter_blacklist`, `starter_blacklist`, and `trainer_party_blacklist`. + - These take lists of species and prevent them from randomizing into the corresponding categories + - If adhering to your blacklist would make it impossible to choose a random species, your blacklist is ignored in + that case + - All three include a shorthand for excluding legendaries +- Removed `enable_ferry` option. + - The ferry is now always present. + - The S.S. Ticket item/location is now part of `key_items`. +- Added event tickets and islands. + - All event tickets are given to the player by Norman after defeating the Champion alongside the S.S. Ticket. + - As in vanilla, these tickets are only usable from Lilycove. Not Slateport or the Battle Frontier. +- New option `event_tickets`. Randomizes the above-mentioned tickets into the item pool. +- New option `enable_wonder_trading`. You can participate in Wonder Trading by interacting with the center receptionist +on the second floor of Pokemon Centers. + - Why is this an option instead of just being enabled? You might want to disable wonder trading in a meta yaml to + make sure certain rules can't be broken. Or you may want to turn it off for yourself to definitively prevent being + asked for help if you prefer to keep certain walls up between your game and others. Trades _do_ include items and + known moves, which means there is potential for an extra level of cooperation and even ways to go out of logic. But + that's not a boundary everyone wants broken down all the time. Please be respectful of someone's choice to not + participate if that's their preference. + - A lot of time was spent trying to make this all work without having to touch your client. Hopefully it goes + smoothly, but there's room for jank. Anything you decide to give to her you should consider gone forever, whether + because it was traded away or because something "went wrong in transit" and the pokemon's data got lost after being + removed from the server. + - Wonder Trading is _not_ resistant to save scumming in either direction. You _could_ abuse it to dupe pokemon, + because there's not realistically a way for me to prevent it, but I'd urge you to stick to the spirit of the design + unless everyone involved doesn't mind. + - The wonder trades you receive are stored in your save data even before you pick them up, so if you save after the + client tells you that you received a wonder trade, it's safe. You don't need to retrieve it from a poke center for + it to persist. However, if you reset your game to a point in time before your client popped the "Wonder trade + received" message, that pokemon is lost forever. +- New `easter_egg` passphrase system. + - All valid easter egg passphrases will be a phrase that it's possible to submit as a trendy phrase in Dewford Town. + Changing the trendy phrase does ***not*** trigger easter eggs. Only the phrase you put in your YAML can trigger an + easter egg. + - There may be other ways to learn more information. + - Phrases are case insensitive. Here are a couple examples of possible phrases: `"GET FOE"`, + `"HERE GOES GRANDMOTHER"`, `"late eh?"` (None of those do anything, but I'd love to hear what you think they would.) +- Added three new easter egg effects. +- Changed the original easter egg phrase to use the new system. +- Renamed `tm_moves` to `tm_tutor_moves`. Move tutors are also affected by this option (except the new Dig tutor). +- Renamed `tm_compatibility` to `tm_tutor_compatibility`. Move tutors are also affected by this option. +- Changed `tm_tutor_compatibility` to be a percent chance instead of a choice. Use `-1` for vanilla. +- Changed `hm_compatibility` to be a percent chance instead of a choice. Use `-1` for vanilla. +- New option `music`. Shuffles all looping music. Includes FRLG tracks and possibly some unused stuff. +- New option `fanfares`. Shuffles all fanfares. Includes FRLG tracks. When this is enabled, pressing B will interrupt +most fanfares. +- New option `purge_spinners`. Trainers that change which direction they face will do so predictably, and will no longer +turn to face you when you run. +- New option `normalize_encounter_rates`. Sets every encounter slot to (almost) equal probability. Does NOT make every +species equally likely to appear, but makes rare encounters less rare. +- Added `Trick House` location group. +- Removed `Postgame Locations` location group. + +### QoL + +- Can teach moves over HM moves. +- Fishing is much less random; pokemon will always bite if there's an encounter there. +- Mirage Island is now always present. +- Waking Rayquaza is no longer required. After releasing Kyogre, going to Sootopolis will immediately trigger the +Rayquaza cutscene. +- Renamed some locations to be more accurate. +- Most trainers will no longer ask to be registered in your Pokegear after battle. Also removed most step-based match +calls. +- Removed a ledge on Route 123. With careful routing, it's now possible to check every location without having to save +scum or go back around. +- Added "GO HOME" button on the start menu where "EXIT" used to be. Will teleport you to Littleroot. +- Some locations which are directly blocked by completing your goal are automatically excluded. + - For example, the S.S. Ticket and a Champion goal, or the Sludge Bomb TM and the Norman goal. + - Your particular options might still result in locations that can't be reached until after your goal. For example, + setting a Norman goal and setting your E4 requirement to 8 gyms means that post-Champion locations will not be + reachable before defeating Norman, but they are NOT excluded by this modification. That's one of the simpler + examples. It is extremely tedious to try to detect these sorts of situations, so I'll instead leave it to you to be + aware of your own options. +- Species in the pokedex are searchable by type even if you haven't caught that species yet + +### Fixes + +- Mt. Pyre summit state no longer changes when you finish the Sootopolis events, which would lock you out of one or two +locations. +- Whiting out under certain conditions no longer softlocks you by moving Mr. Briney to an inaccessible area. +- It's no longer possible to join a room using the wrong patch file, even if the slot names match. +- NPCs now stop moving while you're receiving an item. +- Creating a secret base no longer triggers sending the Secret Power TM location. +- Hopefully fix bug where receiving an item while walking over a trigger can skip that trigger (the Moving +Truck/Petalburg wrong warp) + +## Easter Eggs + +There are plenty among you who are capable of ~~cheating~~ finding information about the easter egg phrases by reading +source code, writing brute force scripts, and inspecting memory for clues and answers. By all means, go ahead, that can +be your version of this puzzle and I don't intend to stand in your way. **However**, I would ask that any information +you come up with by doing this, you keep entirely to yourself until the community as a whole has figured out what you +know. There was not previously a way to reasonably learn about or make guesses at the easter egg, but that has changed. +There are mechanisms by which solutions can be found or guessed over the course of multiple games by multiple people, +and I'd rather the fun not get spoiled immediately. + +Once a solution has been found I'd _still_ prefer discussion about hints and effects remain behind spoiler tags just in +case there are people who want to do the hunt on their own. Thank you all, and good luck. diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 4d40dd196688..abdee26f572f 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -5,30 +5,28 @@ import copy import logging import os -from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar +import pkgutil +from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType from Fill import FillError, fill_restrictive -from Options import Toggle +from Options import OptionError, Toggle import settings from worlds.AutoWorld import WebWorld, World from .client import PokemonEmeraldClient # Unused, but required to register with BizHawkClient -from .data import (SpeciesData, MapData, EncounterTableData, LearnsetMove, TrainerPokemonData, StaticEncounterData, - TrainerData, data as emerald_data) +from .data import LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, data as emerald_data from .items import (ITEM_GROUPS, PokemonEmeraldItem, create_item_label_to_code_map, get_item_classification, offset_item_value) from .locations import (LOCATION_GROUPS, PokemonEmeraldLocation, create_location_label_to_id_map, - create_locations_with_tags) -from .options import (Goal, ItemPoolType, RandomizeWildPokemon, RandomizeBadges, RandomizeTrainerParties, RandomizeHms, - RandomizeStarters, LevelUpMoves, RandomizeAbilities, RandomizeTypes, TmCompatibility, - HmCompatibility, RandomizeStaticEncounters, NormanRequirement, PokemonEmeraldOptions) -from .pokemon import get_random_species, get_random_move, get_random_damaging_move, get_random_type -from .regions import create_regions -from .rom import PokemonEmeraldDeltaPatch, generate_output, location_visited_event_to_id_map -from .rules import set_rules -from .sanity_check import validate_regions -from .util import int_to_bool_array, bool_array_to_int + create_locations_with_tags, set_free_fly, set_legendary_cave_entrances) +from .opponents import randomize_opponent_parties +from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType, PokemonEmeraldOptions, + RandomizeWildPokemon, RandomizeBadges, RandomizeHms, NormanRequirement) +from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets, + randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters, + randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters) +from .rom import PokemonEmeraldProcedurePatch, write_tokens class PokemonEmeraldWebWorld(WebWorld): @@ -54,8 +52,17 @@ class PokemonEmeraldWebWorld(WebWorld): "setup/es", ["nachocua"] ) + + setup_sv = Tutorial( + "Multivärld Installations Guide", + "En guide för att kunna spela Pokémon Emerald med Archipelago.", + "Svenska", + "setup_sv.md", + "setup/sv", + ["Tsukino"] + ) - tutorials = [setup_en, setup_es] + tutorials = [setup_en, setup_es, setup_sv] class PokemonEmeraldSettings(settings.Group): @@ -63,7 +70,7 @@ class PokemonEmeraldRomFile(settings.UserFilePath): """File name of your English Pokemon Emerald ROM""" description = "Pokemon Emerald ROM File" copy_to = "Pokemon - Emerald Version (USA, Europe).gba" - md5s = [PokemonEmeraldDeltaPatch.hash] + md5s = [PokemonEmeraldProcedurePatch.hash] rom_file: PokemonEmeraldRomFile = PokemonEmeraldRomFile(PokemonEmeraldRomFile.copy_to) @@ -89,24 +96,44 @@ class PokemonEmeraldWorld(World): item_name_groups = ITEM_GROUPS location_name_groups = LOCATION_GROUPS - data_version = 1 - required_client_version = (0, 4, 3) + required_client_version = (0, 4, 6) - badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] = None - hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] = None - free_fly_location_id: int = 0 + badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] + hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] + free_fly_location_id: int + blacklisted_moves: Set[int] + blacklisted_wilds: Set[int] + blacklisted_starters: Set[int] + blacklisted_opponent_pokemon: Set[int] + hm_requirements: Dict[str, Union[int, List[str]]] + auth: bytes - modified_species: List[Optional[SpeciesData]] - modified_maps: List[MapData] + modified_species: Dict[int, SpeciesData] + modified_maps: Dict[str, MapData] modified_tmhm_moves: List[int] - modified_static_encounters: List[int] + modified_legendary_encounters: List[int] modified_starters: Tuple[int, int, int] modified_trainers: List[TrainerData] + def __init__(self, multiworld, player): + super(PokemonEmeraldWorld, self).__init__(multiworld, player) + self.badge_shuffle_info = None + self.hm_shuffle_info = None + self.free_fly_location_id = 0 + self.blacklisted_moves = set() + self.blacklisted_wilds = set() + self.blacklisted_starters = set() + self.blacklisted_opponent_pokemon = set() + self.modified_maps = copy.deepcopy(emerald_data.maps) + self.modified_species = copy.deepcopy(emerald_data.species) + self.modified_tmhm_moves = [] + self.modified_starters = emerald_data.starters + self.modified_trainers = [] + self.modified_legendary_encounters = [] + @classmethod def stage_assert_generate(cls, multiworld: MultiWorld) -> None: - if not os.path.exists(cls.settings.rom_file): - raise FileNotFoundError(cls.settings.rom_file) + from .sanity_check import validate_regions assert validate_regions() @@ -114,15 +141,83 @@ def get_filler_item_name(self) -> str: return "Great Ball" def generate_early(self) -> None: - # If badges or HMs are vanilla, Norman locks you from using Surf, which means you're not guaranteed to be - # able to reach Fortree Gym, Mossdeep Gym, or Sootopolis Gym. So we can't require reaching those gyms to - # challenge Norman or it creates a circular dependency. - # This is never a problem for completely random badges/hms because the algo will not place Surf/Balance Badge - # on Norman on its own. It's never a problem for shuffled badges/hms because there is no scenario where Cut or - # the Stone Badge can be a lynchpin for access to any gyms, so they can always be put on Norman in a worst case - # scenario. - # This will also be a problem in warp rando if direct access to Norman's room requires Surf or if access - # any gym leader in general requires Surf. We will probably have to force this to 0 in that case. + self.hm_requirements = { + "HM01 Cut": ["Stone Badge"], + "HM02 Fly": ["Feather Badge"], + "HM03 Surf": ["Balance Badge"], + "HM04 Strength": ["Heat Badge"], + "HM05 Flash": ["Knuckle Badge"], + "HM06 Rock Smash": ["Dynamo Badge"], + "HM07 Waterfall": ["Rain Badge"], + "HM08 Dive": ["Mind Badge"], + } + if self.options.hm_requirements == HmRequirements.option_fly_without_badge: + self.hm_requirements["HM02 Fly"] = 0 + + self.blacklisted_moves = {emerald_data.move_labels[label] for label in self.options.move_blacklist.value} + + self.blacklisted_wilds = { + get_species_id_by_label(species_name) + for species_name in self.options.wild_encounter_blacklist.value + if species_name != "_Legendaries" + } + if "_Legendaries" in self.options.wild_encounter_blacklist.value: + self.blacklisted_wilds |= LEGENDARY_POKEMON + + self.blacklisted_starters = { + get_species_id_by_label(species_name) + for species_name in self.options.starter_blacklist.value + if species_name != "_Legendaries" + } + if "_Legendaries" in self.options.starter_blacklist.value: + self.blacklisted_starters |= LEGENDARY_POKEMON + + self.blacklisted_opponent_pokemon = { + get_species_id_by_label(species_name) + for species_name in self.options.trainer_party_blacklist.value + if species_name != "_Legendaries" + } + if "_Legendaries" in self.options.starter_blacklist.value: + self.blacklisted_opponent_pokemon |= LEGENDARY_POKEMON + + # In race mode we don't patch any item location information into the ROM + if self.multiworld.is_race and not self.options.remote_items: + logging.warning("Pokemon Emerald: Forcing Player %s (%s) to use remote items due to race mode.", + self.player, self.player_name) + self.options.remote_items.value = Toggle.option_true + + if self.options.goal == Goal.option_legendary_hunt: + # Prevent turning off all legendary encounters + if len(self.options.allowed_legendary_hunt_encounters.value) == 0: + raise OptionError(f"Pokemon Emerald: Player {self.player} ({self.player_name}) needs to allow at " + "least one legendary encounter when goal is legendary hunt.") + + # Prevent setting the number of required legendaries higher than the number of enabled legendaries + if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value): + logging.warning("Pokemon Emerald: Legendary hunt count for Player %s (%s) higher than number of allowed " + "legendary encounters. Reducing to number of allowed encounters.", self.player, + self.player_name) + self.options.legendary_hunt_count.value = len(self.options.allowed_legendary_hunt_encounters.value) + + # Require random wild encounters if dexsanity is enabled + if self.options.dexsanity and self.options.wild_pokemon == RandomizeWildPokemon.option_vanilla: + raise OptionError(f"Pokemon Emerald: Player {self.player} ({self.player_name}) must not leave wild " + "encounters vanilla if enabling dexsanity.") + + # If badges or HMs are vanilla, Norman locks you from using Surf, + # which means you're not guaranteed to be able to reach Fortree Gym, + # Mossdeep Gym, or Sootopolis Gym. So we can't require reaching those + # gyms to challenge Norman or it creates a circular dependency. + # + # This is never a problem for completely random badges/hms because the + # algo will not place Surf/Balance Badge on Norman on its own. It's + # never a problem for shuffled badges/hms because there is no scenario + # where Cut or the Stone Badge can be a lynchpin for access to any gyms, + # so they can always be put on Norman in a worst case scenario. + # + # This will also be a problem in warp rando if direct access to Norman's + # room requires Surf or if access any gym leader in general requires + # Surf. We will probably have to force this to 0 in that case. max_norman_count = 7 if self.options.badges == RandomizeBadges.option_vanilla: @@ -137,21 +232,26 @@ def generate_early(self) -> None: if self.options.norman_count.value > max_norman_count: logging.warning("Pokemon Emerald: Norman requirements for Player %s (%s) are unsafe in combination with " - "other settings. Reducing to 4.", self.player, self.multiworld.get_player_name(self.player)) + "other settings. Reducing to 4.", self.player, self.player_name) self.options.norman_count.value = max_norman_count def create_regions(self) -> None: + from .regions import create_regions regions = create_regions(self) - tags = {"Badge", "HM", "KeyItem", "Rod", "Bike"} + tags = {"Badge", "HM", "KeyItem", "Rod", "Bike", "EventTicket"} # Tags with progression items always included if self.options.overworld_items: tags.add("OverworldItem") if self.options.hidden_items: tags.add("HiddenItem") if self.options.npc_gifts: tags.add("NpcGift") - if self.options.enable_ferry: - tags.add("Ferry") + if self.options.berry_trees: + tags.add("BerryTree") + if self.options.dexsanity: + tags.add("Pokedex") + if self.options.trainersanity: + tags.add("Trainer") create_locations_with_tags(self, regions, tags) self.multiworld.regions.extend(regions.values()) @@ -166,18 +266,18 @@ def exclude_locations(location_names: List[str]): continue # Location not in multiworld if self.options.goal == Goal.option_champion: - # Always required to beat champion before receiving this + # Always required to beat champion before receiving these exclude_locations([ - "Littleroot Town - S.S. Ticket from Norman" + "Littleroot Town - S.S. Ticket from Norman", + "Littleroot Town - Aurora Ticket from Norman", + "Littleroot Town - Eon Ticket from Norman", + "Littleroot Town - Mystic Ticket from Norman", + "Littleroot Town - Old Sea Map from Norman", + "Ever Grande City - Champion Wallace", + "Meteor Falls 1F - Rival Steven", + "Trick House Puzzle 8 - Item", ]) - # S.S. Ticket requires beating champion, so ferry is not accessible until after goal - if not self.options.enable_ferry: - exclude_locations([ - "SS Tidal - Hidden Item in Lower Deck Trash Can", - "SS Tidal - TM49 from Thief" - ]) - # Construction workers don't move until champion is defeated if "Safari Zone Construction Workers" not in self.options.remove_roadblocks.value: exclude_locations([ @@ -186,8 +286,12 @@ def exclude_locations(location_names: List[str]): "Safari Zone NE - Item on Ledge", "Safari Zone SE - Hidden Item in South Grass 1", "Safari Zone SE - Hidden Item in South Grass 2", - "Safari Zone SE - Item in Grass" + "Safari Zone SE - Item in Grass", ]) + elif self.options.goal == Goal.option_steven: + exclude_locations([ + "Meteor Falls 1F - Rival Steven", + ]) elif self.options.goal == Goal.option_norman: # If the player sets their options such that Surf or the Balance # Badge is vanilla, a very large number of locations become @@ -202,12 +306,13 @@ def exclude_locations(location_names: List[str]): # Locations which are directly unlocked by defeating Norman. exclude_locations([ + "Petalburg Gym - Leader Norman", "Petalburg Gym - Balance Badge", "Petalburg Gym - TM42 from Norman", "Petalburg City - HM03 from Wally's Uncle", "Dewford Town - TM36 from Sludge Bomb Man", "Mauville City - Basement Key from Wattson", - "Mauville City - TM24 from Wattson" + "Mauville City - TM24 from Wattson", ]) def create_items(self) -> None: @@ -217,8 +322,9 @@ def create_items(self) -> None: if location.address is not None ] - # Filter progression items which shouldn't be shuffled into the itempool. Their locations - # still exist, but event items will be placed and locked at their vanilla locations instead. + # Filter progression items which shouldn't be shuffled into the itempool. + # Their locations will still exist, but event items will be placed and + # locked at their vanilla locations instead. filter_tags = set() if not self.options.key_items: @@ -227,12 +333,17 @@ def create_items(self) -> None: filter_tags.add("Rod") if not self.options.bikes: filter_tags.add("Bike") + if not self.options.event_tickets: + filter_tags.add("EventTicket") if self.options.badges in {RandomizeBadges.option_vanilla, RandomizeBadges.option_shuffle}: filter_tags.add("Badge") if self.options.hms in {RandomizeHms.option_vanilla, RandomizeHms.option_shuffle}: filter_tags.add("HM") + # If Badges and HMs are set to the `shuffle` option, don't add them to + # the normal item pool, but do create their items and save them and + # their locations for use in `pre_fill` later. if self.options.badges == RandomizeBadges.option_shuffle: self.badge_shuffle_info = [ (location, self.create_item_by_code(location.default_item_code)) @@ -244,14 +355,18 @@ def create_items(self) -> None: for location in [l for l in item_locations if "HM" in l.tags] ] + # Filter down locations to actual items that will be filled and create + # the itempool. item_locations = [location for location in item_locations if len(filter_tags & location.tags) == 0] default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations] + # Take the itempool as-is if self.options.item_pool_type == ItemPoolType.option_shuffled: self.multiworld.itempool += default_itempool - elif self.options.item_pool_type in {ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced}: - item_categories = ["Ball", "Heal", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc"] + # Recreate the itempool from random items + elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced): + item_categories = ["Ball", "Heal", "Candy", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc", "Berry"] # Count occurrences of types of vanilla items in pool item_category_counter = Counter() @@ -306,38 +421,23 @@ def refresh_tm_choices() -> None: self.multiworld.itempool.append(item) def set_rules(self) -> None: + from .rules import set_rules set_rules(self) def generate_basic(self) -> None: - locations: List[PokemonEmeraldLocation] = self.multiworld.get_locations(self.player) - - # Set our free fly location - # If not enabled, set it to Littleroot Town by default - fly_location_name = "EVENT_VISITED_LITTLEROOT_TOWN" - if self.options.free_fly_location: - fly_location_name = self.random.choice([ - "EVENT_VISITED_SLATEPORT_CITY", - "EVENT_VISITED_MAUVILLE_CITY", - "EVENT_VISITED_VERDANTURF_TOWN", - "EVENT_VISITED_FALLARBOR_TOWN", - "EVENT_VISITED_LAVARIDGE_TOWN", - "EVENT_VISITED_FORTREE_CITY", - "EVENT_VISITED_LILYCOVE_CITY", - "EVENT_VISITED_MOSSDEEP_CITY", - "EVENT_VISITED_SOOTOPOLIS_CITY", - "EVENT_VISITED_EVER_GRANDE_CITY" - ]) + # Create auth + # self.auth = self.random.randbytes(16) # Requires >=3.9 + self.auth = self.random.getrandbits(16 * 8).to_bytes(16, "little") - self.free_fly_location_id = location_visited_event_to_id_map[fly_location_name] - - free_fly_location_location = self.multiworld.get_location("FREE_FLY_LOCATION", self.player) - free_fly_location_location.item = None - free_fly_location_location.place_locked_item(self.create_event(fly_location_name)) + randomize_types(self) + randomize_wild_encounters(self) + set_free_fly(self) + set_legendary_cave_entrances(self) # Key items which are considered in access rules but not randomized are converted to events and placed # in their vanilla locations so that the player can have them in their inventory for logic. def convert_unrandomized_items_to_events(tag: str) -> None: - for location in locations: + for location in self.multiworld.get_locations(self.player): if location.tags is not None and tag in location.tags: location.place_locked_item(self.create_event(self.item_id_to_name[location.default_item_code])) location.progress_type = LocationProgressType.DEFAULT @@ -351,30 +451,36 @@ def convert_unrandomized_items_to_events(tag: str) -> None: convert_unrandomized_items_to_events("Rod") if not self.options.bikes: convert_unrandomized_items_to_events("Bike") + if not self.options.event_tickets: + convert_unrandomized_items_to_events("EventTicket") if not self.options.key_items: convert_unrandomized_items_to_events("KeyItem") def pre_fill(self) -> None: - # Items which are shuffled between their own locations + # Badges and HMs that are set to shuffle need to be placed at + # their own subset of locations if self.options.badges == RandomizeBadges.option_shuffle: badge_locations: List[PokemonEmeraldLocation] badge_items: List[PokemonEmeraldItem] # Sort order makes `fill_restrictive` try to place important badges later, which # makes it less likely to have to swap at all, and more likely for swaps to work. - # In the case of vanilla HMs, navigating Granite Cave is required to access more than 2 gyms, - # so Knuckle Badge deserves highest priority if Flash is logically required. badge_locations, badge_items = [list(l) for l in zip(*self.badge_shuffle_info)] badge_priority = { - "Knuckle Badge": 0 if (self.options.hms == RandomizeHms.option_vanilla and self.options.require_flash) else 3, + "Knuckle Badge": 3, "Balance Badge": 1, "Dynamo Badge": 1, "Mind Badge": 2, "Heat Badge": 2, "Rain Badge": 3, "Stone Badge": 4, - "Feather Badge": 5 + "Feather Badge": 5, } + # In the case of vanilla HMs, navigating Granite Cave is required to access more than 2 gyms, + # so Knuckle Badge deserves highest priority if Flash is logically required. + if self.options.hms == RandomizeHms.option_vanilla and \ + self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): + badge_priority["Knuckle Badge"] = 0 badge_items.sort(key=lambda item: badge_priority.get(item.name, 0)) # Un-exclude badge locations, since we need to put progression items on them @@ -384,6 +490,11 @@ def pre_fill(self) -> None: else location.progress_type collection_state = self.multiworld.get_all_state(False) + + # If HM shuffle is on, HMs are not placed and not in the pool, so + # `get_all_state` did not contain them. Collect them manually for + # this fill. We know that they will be included in all state after + # this stage. if self.hm_shuffle_info is not None: for _, item in self.hm_shuffle_info: collection_state.collect(item) @@ -406,25 +517,29 @@ def pre_fill(self) -> None: logging.debug(f"Failed to shuffle badges for player {self.player}. Retrying.") continue + # Badges are guaranteed to be either placed or in the multiworld's itempool now if self.options.hms == RandomizeHms.option_shuffle: hm_locations: List[PokemonEmeraldLocation] hm_items: List[PokemonEmeraldItem] # Sort order makes `fill_restrictive` try to place important HMs later, which # makes it less likely to have to swap at all, and more likely for swaps to work. - # In the case of vanilla badges, navigating Granite Cave is required to access more than 2 gyms, - # so Flash deserves highest priority if it's logically required. hm_locations, hm_items = [list(l) for l in zip(*self.hm_shuffle_info)] hm_priority = { - "HM05 Flash": 0 if (self.options.badges == RandomizeBadges.option_vanilla and self.options.require_flash) else 3, + "HM05 Flash": 3, "HM03 Surf": 1, "HM06 Rock Smash": 1, "HM08 Dive": 2, "HM04 Strength": 2, "HM07 Waterfall": 3, "HM01 Cut": 4, - "HM02 Fly": 5 + "HM02 Fly": 5, } + # In the case of vanilla badges, navigating Granite Cave is required to access more than 2 gyms, + # so Flash deserves highest priority if it's logically required. + if self.options.badges == RandomizeBadges.option_vanilla and \ + self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): + hm_priority["HM05 Flash"] = 0 hm_items.sort(key=lambda item: hm_priority.get(item.name, 0)) # Un-exclude HM locations, since we need to put progression items on them @@ -454,465 +569,116 @@ def pre_fill(self) -> None: continue def generate_output(self, output_directory: str) -> None: - def randomize_abilities() -> None: - # Creating list of potential abilities - ability_label_to_value = {ability.label.lower(): ability.ability_id for ability in emerald_data.abilities} - - ability_blacklist_labels = {"cacophony"} - option_ability_blacklist = self.options.ability_blacklist.value - if option_ability_blacklist is not None: - ability_blacklist_labels |= {ability_label.lower() for ability_label in option_ability_blacklist} - - ability_blacklist = {ability_label_to_value[label] for label in ability_blacklist_labels} - ability_whitelist = [a.ability_id for a in emerald_data.abilities if a.ability_id not in ability_blacklist] - - if self.options.abilities == RandomizeAbilities.option_follow_evolutions: - already_modified: Set[int] = set() - - # Loops through species and only tries to modify abilities if the pokemon has no pre-evolution - # or if the pre-evolution has already been modified. Then tries to modify all species that evolve - # from this one which have the same abilities. - # The outer while loop only runs three times for vanilla ordering: Once for a first pass, once for - # Hitmonlee/Hitmonchan, and once to verify that there's nothing left to do. - while True: - had_clean_pass = True - for species in self.modified_species: - if species is None: - continue - if species.species_id in already_modified: - continue - if species.pre_evolution is not None and species.pre_evolution not in already_modified: - continue - - had_clean_pass = False - - old_abilities = species.abilities - new_abilities = ( - 0 if old_abilities[0] == 0 else self.random.choice(ability_whitelist), - 0 if old_abilities[1] == 0 else self.random.choice(ability_whitelist) - ) - - evolutions = [species] - while len(evolutions) > 0: - evolution = evolutions.pop() - if evolution.abilities == old_abilities: - evolution.abilities = new_abilities - already_modified.add(evolution.species_id) - evolutions += [ - self.modified_species[evolution.species_id] - for evolution in evolution.evolutions - if evolution.species_id not in already_modified - ] - - if had_clean_pass: - break - else: # Not following evolutions - for species in self.modified_species: - if species is None: - continue - - old_abilities = species.abilities - new_abilities = ( - 0 if old_abilities[0] == 0 else self.random.choice(ability_whitelist), - 0 if old_abilities[1] == 0 else self.random.choice(ability_whitelist) - ) - - species.abilities = new_abilities - - def randomize_types() -> None: - if self.options.types == RandomizeTypes.option_shuffle: - type_map = list(range(18)) - self.random.shuffle(type_map) - - # We never want to map to the ??? type, so swap whatever index maps to ??? with ??? - # So ??? will always map to itself, and there are no pokemon which have the ??? type - mystery_type_index = type_map.index(9) - type_map[mystery_type_index], type_map[9] = type_map[9], type_map[mystery_type_index] - - for species in self.modified_species: - if species is not None: - species.types = (type_map[species.types[0]], type_map[species.types[1]]) - elif self.options.types == RandomizeTypes.option_completely_random: - for species in self.modified_species: - if species is not None: - new_type_1 = get_random_type(self.random) - new_type_2 = new_type_1 - if species.types[0] != species.types[1]: - while new_type_1 == new_type_2: - new_type_2 = get_random_type(self.random) - - species.types = (new_type_1, new_type_2) - elif self.options.types == RandomizeTypes.option_follow_evolutions: - already_modified: Set[int] = set() - - # Similar to follow evolutions for abilities, but only needs to loop through once. - # For every pokemon without a pre-evolution, generates a random mapping from old types to new types - # and then walks through the evolution tree applying that map. This means that evolutions that share - # types will have those types mapped to the same new types, and evolutions with new or diverging types - # will still have new or diverging types. - # Consider: - # - Charmeleon (Fire/Fire) -> Charizard (Fire/Flying) - # - Onyx (Rock/Ground) -> Steelix (Steel/Ground) - # - Nincada (Bug/Ground) -> Ninjask (Bug/Flying) && Shedinja (Bug/Ghost) - # - Azurill (Normal/Normal) -> Marill (Water/Water) - for species in self.modified_species: - if species is None: - continue - if species.species_id in already_modified: - continue - if species.pre_evolution is not None and species.pre_evolution not in already_modified: - continue - - type_map = list(range(18)) - self.random.shuffle(type_map) - - # We never want to map to the ??? type, so swap whatever index maps to ??? with ??? - # So ??? will always map to itself, and there are no pokemon which have the ??? type - mystery_type_index = type_map.index(9) - type_map[mystery_type_index], type_map[9] = type_map[9], type_map[mystery_type_index] - - evolutions = [species] - while len(evolutions) > 0: - evolution = evolutions.pop() - evolution.types = (type_map[evolution.types[0]], type_map[evolution.types[1]]) - already_modified.add(evolution.species_id) - evolutions += [self.modified_species[evo.species_id] for evo in evolution.evolutions] - - def randomize_learnsets() -> None: - type_bias = self.options.move_match_type_bias.value - normal_bias = self.options.move_normal_type_bias.value - - for species in self.modified_species: - if species is None: - continue - - old_learnset = species.learnset - new_learnset: List[LearnsetMove] = [] - - i = 0 - # Replace filler MOVE_NONEs at start of list - while old_learnset[i].move_id == 0: - if self.options.level_up_moves == LevelUpMoves.option_start_with_four_moves: - new_move = get_random_move(self.random, {move.move_id for move in new_learnset}, type_bias, - normal_bias, species.types) - else: - new_move = 0 - new_learnset.append(LearnsetMove(old_learnset[i].level, new_move)) - i += 1 - - while i < len(old_learnset): - # Guarantees the starter has a good damaging move - if i == 3: - new_move = get_random_damaging_move(self.random, {move.move_id for move in new_learnset}) - else: - new_move = get_random_move(self.random, {move.move_id for move in new_learnset}, type_bias, - normal_bias, species.types) - new_learnset.append(LearnsetMove(old_learnset[i].level, new_move)) - i += 1 - - species.learnset = new_learnset - - def randomize_tm_hm_compatibility() -> None: - for species in self.modified_species: - if species is None: - continue - - combatibility_array = int_to_bool_array(species.tm_hm_compatibility) - - # TMs - for i in range(0, 50): - if self.options.tm_compatibility == TmCompatibility.option_fully_compatible: - combatibility_array[i] = True - elif self.options.tm_compatibility == TmCompatibility.option_completely_random: - combatibility_array[i] = self.random.choice([True, False]) - - # HMs - for i in range(50, 58): - if self.options.hm_compatibility == HmCompatibility.option_fully_compatible: - combatibility_array[i] = True - elif self.options.hm_compatibility == HmCompatibility.option_completely_random: - combatibility_array[i] = self.random.choice([True, False]) + self.modified_trainers = copy.deepcopy(emerald_data.trainers) + self.modified_tmhm_moves = copy.deepcopy(emerald_data.tmhm_moves) + self.modified_legendary_encounters = copy.deepcopy(emerald_data.legendary_encounters) + self.modified_misc_pokemon = copy.deepcopy(emerald_data.misc_pokemon) + self.modified_starters = copy.deepcopy(emerald_data.starters) - species.tm_hm_compatibility = bool_array_to_int(combatibility_array) + # Modify catch rate + min_catch_rate = min(self.options.min_catch_rate.value, 255) + for species in self.modified_species.values(): + species.catch_rate = max(species.catch_rate, min_catch_rate) - def randomize_tm_moves() -> None: + # Modify TM moves + if self.options.tm_tutor_moves: new_moves: Set[int] = set() for i in range(50): - new_move = get_random_move(self.random, new_moves) + new_move = get_random_move(self.random, new_moves | self.blacklisted_moves) new_moves.add(new_move) self.modified_tmhm_moves[i] = new_move - def randomize_wild_encounters() -> None: - should_match_bst = self.options.wild_pokemon in { - RandomizeWildPokemon.option_match_base_stats, - RandomizeWildPokemon.option_match_base_stats_and_type - } - should_match_type = self.options.wild_pokemon in { - RandomizeWildPokemon.option_match_type, - RandomizeWildPokemon.option_match_base_stats_and_type - } - should_allow_legendaries = self.options.allow_wild_legendaries == Toggle.option_true - - for map_data in self.modified_maps: - new_encounters: List[Optional[EncounterTableData]] = [None, None, None] - old_encounters = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters] - - for i, table in enumerate(old_encounters): - if table is not None: - species_old_to_new_map: Dict[int, int] = {} - for species_id in table.slots: - if species_id not in species_old_to_new_map: - original_species = emerald_data.species[species_id] - target_bst = sum(original_species.base_stats) if should_match_bst else None - target_type = self.random.choice(original_species.types) if should_match_type else None - - species_old_to_new_map[species_id] = get_random_species( - self.random, - self.modified_species, - target_bst, - target_type, - should_allow_legendaries - ).species_id - - new_slots: List[int] = [] - for species_id in table.slots: - new_slots.append(species_old_to_new_map[species_id]) - - new_encounters[i] = EncounterTableData(new_slots, table.rom_address) - - map_data.land_encounters = new_encounters[0] - map_data.water_encounters = new_encounters[1] - map_data.fishing_encounters = new_encounters[2] - - def randomize_static_encounters() -> None: - if self.options.static_encounters == RandomizeStaticEncounters.option_shuffle: - shuffled_species = [encounter.species_id for encounter in emerald_data.static_encounters] - self.random.shuffle(shuffled_species) - - self.modified_static_encounters = [] - for i, encounter in enumerate(emerald_data.static_encounters): - self.modified_static_encounters.append(StaticEncounterData( - shuffled_species[i], - encounter.rom_address - )) - else: - should_match_bst = self.options.static_encounters in { - RandomizeStaticEncounters.option_match_base_stats, - RandomizeStaticEncounters.option_match_base_stats_and_type - } - should_match_type = self.options.static_encounters in { - RandomizeStaticEncounters.option_match_type, - RandomizeStaticEncounters.option_match_base_stats_and_type - } - - for encounter in emerald_data.static_encounters: - original_species = self.modified_species[encounter.species_id] - target_bst = sum(original_species.base_stats) if should_match_bst else None - target_type = self.random.choice(original_species.types) if should_match_type else None - - self.modified_static_encounters.append(StaticEncounterData( - get_random_species(self.random, self.modified_species, target_bst, target_type).species_id, - encounter.rom_address - )) - - def randomize_opponent_parties() -> None: - should_match_bst = self.options.trainer_parties in { - RandomizeTrainerParties.option_match_base_stats, - RandomizeTrainerParties.option_match_base_stats_and_type + randomize_abilities(self) + randomize_learnsets(self) + randomize_tm_hm_compatibility(self) + randomize_legendary_encounters(self) + randomize_misc_pokemon(self) + randomize_opponent_parties(self) + randomize_starters(self) + + patch = PokemonEmeraldProcedurePatch(player=self.player, player_name=self.player_name) + patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/base_patch.bsdiff4")) + write_tokens(self, patch) + + del self.modified_trainers + del self.modified_tmhm_moves + del self.modified_legendary_encounters + del self.modified_misc_pokemon + del self.modified_starters + del self.modified_species + + # Write Output + out_file_name = self.multiworld.get_out_file_name_base(self.player) + patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}")) + + def write_spoiler(self, spoiler_handle: TextIO): + if self.options.dexsanity: + from collections import defaultdict + + spoiler_handle.write(f"\n\nWild Pokemon ({self.player_name}):\n\n") + + species_maps = defaultdict(set) + for map in self.modified_maps.values(): + if map.land_encounters is not None: + for encounter in map.land_encounters.slots: + species_maps[encounter].add(map.name[4:]) + + if map.water_encounters is not None: + for encounter in map.water_encounters.slots: + species_maps[encounter].add(map.name[4:]) + + if map.fishing_encounters is not None: + for encounter in map.fishing_encounters.slots: + species_maps[encounter].add(map.name[4:]) + + lines = [f"{emerald_data.species[species].label}: {', '.join(maps)}\n" + for species, maps in species_maps.items()] + lines.sort() + for line in lines: + spoiler_handle.write(line) + + del self.modified_maps + + def extend_hint_information(self, hint_data): + if self.options.dexsanity: + from collections import defaultdict + + slot_to_rod = { + 0: "_OLD_ROD", + 1: "_OLD_ROD", + 2: "_GOOD_ROD", + 3: "_GOOD_ROD", + 4: "_GOOD_ROD", + 5: "_SUPER_ROD", + 6: "_SUPER_ROD", + 7: "_SUPER_ROD", + 8: "_SUPER_ROD", + 9: "_SUPER_ROD", } - should_match_type = self.options.trainer_parties in { - RandomizeTrainerParties.option_match_type, - RandomizeTrainerParties.option_match_base_stats_and_type - } - allow_legendaries = self.options.allow_trainer_legendaries == Toggle.option_true - - per_species_tmhm_moves: Dict[int, List[int]] = {} - - for trainer in self.modified_trainers: - new_party = [] - for pokemon in trainer.party.pokemon: - original_species = emerald_data.species[pokemon.species_id] - target_bst = sum(original_species.base_stats) if should_match_bst else None - target_type = self.random.choice(original_species.types) if should_match_type else None - - new_species = get_random_species( - self.random, - self.modified_species, - target_bst, - target_type, - allow_legendaries - ) - - if new_species.species_id not in per_species_tmhm_moves: - per_species_tmhm_moves[new_species.species_id] = list({ - self.modified_tmhm_moves[i] - for i, is_compatible in enumerate(int_to_bool_array(new_species.tm_hm_compatibility)) - if is_compatible - }) - - tm_hm_movepool = per_species_tmhm_moves[new_species.species_id] - level_up_movepool = list({ - move.move_id - for move in new_species.learnset - if move.move_id != 0 and move.level <= pokemon.level - }) - - new_moves = ( - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool), - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool), - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool), - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool) - ) - - new_party.append(TrainerPokemonData(new_species.species_id, pokemon.level, new_moves)) - - trainer.party.pokemon = new_party - - def randomize_starters() -> None: - match_bst = self.options.starters in { - RandomizeStarters.option_match_base_stats, - RandomizeStarters.option_match_base_stats_and_type - } - match_type = self.options.starters in { - RandomizeStarters.option_match_type, - RandomizeStarters.option_match_base_stats_and_type - } - allow_legendaries = self.options.allow_starter_legendaries == Toggle.option_true - - starter_bsts = ( - sum(emerald_data.species[emerald_data.starters[0]].base_stats) if match_bst else None, - sum(emerald_data.species[emerald_data.starters[1]].base_stats) if match_bst else None, - sum(emerald_data.species[emerald_data.starters[2]].base_stats) if match_bst else None - ) - - starter_types = ( - self.random.choice(emerald_data.species[emerald_data.starters[0]].types) if match_type else None, - self.random.choice(emerald_data.species[emerald_data.starters[1]].types) if match_type else None, - self.random.choice(emerald_data.species[emerald_data.starters[2]].types) if match_type else None - ) - - new_starters = ( - get_random_species(self.random, self.modified_species, - starter_bsts[0], starter_types[0], allow_legendaries), - get_random_species(self.random, self.modified_species, - starter_bsts[1], starter_types[1], allow_legendaries), - get_random_species(self.random, self.modified_species, - starter_bsts[2], starter_types[2], allow_legendaries) - ) - - egg_code = self.options.easter_egg.value - egg_check_1 = 0 - egg_check_2 = 0 - - for i in egg_code: - egg_check_1 += ord(i) - egg_check_2 += egg_check_1 * egg_check_1 - - egg = 96 + egg_check_2 - (egg_check_1 * 0x077C) - if egg_check_2 == 0x14E03A and egg < 411 and egg > 0 and egg not in range(252, 277): - self.modified_starters = (egg, egg, egg) - else: - self.modified_starters = ( - new_starters[0].species_id, - new_starters[1].species_id, - new_starters[2].species_id - ) - - # Putting the unchosen starter onto the rival's team - rival_teams: List[List[Tuple[str, int, bool]]] = [ - [ - ("TRAINER_BRENDAN_ROUTE_103_TREECKO", 0, False), - ("TRAINER_BRENDAN_RUSTBORO_TREECKO", 1, False), - ("TRAINER_BRENDAN_ROUTE_110_TREECKO", 2, True ), - ("TRAINER_BRENDAN_ROUTE_119_TREECKO", 2, True ), - ("TRAINER_BRENDAN_LILYCOVE_TREECKO", 3, True ), - ("TRAINER_MAY_ROUTE_103_TREECKO", 0, False), - ("TRAINER_MAY_RUSTBORO_TREECKO", 1, False), - ("TRAINER_MAY_ROUTE_110_TREECKO", 2, True ), - ("TRAINER_MAY_ROUTE_119_TREECKO", 2, True ), - ("TRAINER_MAY_LILYCOVE_TREECKO", 3, True ) - ], - [ - ("TRAINER_BRENDAN_ROUTE_103_TORCHIC", 0, False), - ("TRAINER_BRENDAN_RUSTBORO_TORCHIC", 1, False), - ("TRAINER_BRENDAN_ROUTE_110_TORCHIC", 2, True ), - ("TRAINER_BRENDAN_ROUTE_119_TORCHIC", 2, True ), - ("TRAINER_BRENDAN_LILYCOVE_TORCHIC", 3, True ), - ("TRAINER_MAY_ROUTE_103_TORCHIC", 0, False), - ("TRAINER_MAY_RUSTBORO_TORCHIC", 1, False), - ("TRAINER_MAY_ROUTE_110_TORCHIC", 2, True ), - ("TRAINER_MAY_ROUTE_119_TORCHIC", 2, True ), - ("TRAINER_MAY_LILYCOVE_TORCHIC", 3, True ) - ], - [ - ("TRAINER_BRENDAN_ROUTE_103_MUDKIP", 0, False), - ("TRAINER_BRENDAN_RUSTBORO_MUDKIP", 1, False), - ("TRAINER_BRENDAN_ROUTE_110_MUDKIP", 2, True ), - ("TRAINER_BRENDAN_ROUTE_119_MUDKIP", 2, True ), - ("TRAINER_BRENDAN_LILYCOVE_MUDKIP", 3, True ), - ("TRAINER_MAY_ROUTE_103_MUDKIP", 0, False), - ("TRAINER_MAY_RUSTBORO_MUDKIP", 1, False), - ("TRAINER_MAY_ROUTE_110_MUDKIP", 2, True ), - ("TRAINER_MAY_ROUTE_119_MUDKIP", 2, True ), - ("TRAINER_MAY_LILYCOVE_MUDKIP", 3, True ) - ] - ] - for i, starter in enumerate([new_starters[1], new_starters[2], new_starters[0]]): - potential_evolutions = [evolution.species_id for evolution in starter.evolutions] - picked_evolution = starter.species_id - if len(potential_evolutions) > 0: - picked_evolution = self.random.choice(potential_evolutions) + species_maps = defaultdict(set) + for map in self.modified_maps.values(): + if map.land_encounters is not None: + for encounter in map.land_encounters.slots: + species_maps[encounter].add(map.name[4:] + "_GRASS") - for trainer_name, starter_position, is_evolved in rival_teams[i]: - trainer_data = self.modified_trainers[emerald_data.constants[trainer_name]] - trainer_data.party.pokemon[starter_position].species_id = picked_evolution if is_evolved else starter.species_id + if map.water_encounters is not None: + for encounter in map.water_encounters.slots: + species_maps[encounter].add(map.name[4:] + "_WATER") - self.modified_species = copy.deepcopy(emerald_data.species) - self.modified_trainers = copy.deepcopy(emerald_data.trainers) - self.modified_maps = copy.deepcopy(emerald_data.maps) - self.modified_tmhm_moves = copy.deepcopy(emerald_data.tmhm_moves) - self.modified_static_encounters = copy.deepcopy(emerald_data.static_encounters) - self.modified_starters = copy.deepcopy(emerald_data.starters) - - # Randomize species data - if self.options.abilities != RandomizeAbilities.option_vanilla: - randomize_abilities() - - if self.options.types != RandomizeTypes.option_vanilla: - randomize_types() - - if self.options.level_up_moves != LevelUpMoves.option_vanilla: - randomize_learnsets() - - randomize_tm_hm_compatibility() # Options are checked within this function + if map.fishing_encounters is not None: + for slot, encounter in enumerate(map.fishing_encounters.slots): + species_maps[encounter].add(map.name[4:] + slot_to_rod[slot]) - min_catch_rate = min(self.options.min_catch_rate.value, 255) - for species in self.modified_species: - if species is not None: - species.catch_rate = max(species.catch_rate, min_catch_rate) - - if self.options.tm_moves: - randomize_tm_moves() - - # Randomize wild encounters - if self.options.wild_pokemon != RandomizeWildPokemon.option_vanilla: - randomize_wild_encounters() - - # Randomize static encounters - if self.options.static_encounters != RandomizeStaticEncounters.option_vanilla: - randomize_static_encounters() - - # Randomize opponents - if self.options.trainer_parties != RandomizeTrainerParties.option_vanilla: - randomize_opponent_parties() - - # Randomize starters - if self.options.starters != RandomizeStarters.option_vanilla: - randomize_starters() + hint_data[self.player] = { + self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(maps) + for species, maps in species_maps.items() + } - generate_output(self, output_directory) + def modify_multidata(self, multidata: Dict[str, Any]): + import base64 + multidata["connect_names"][base64.b64encode(self.auth).decode("ascii")] = multidata["connect_names"][self.player_name] def fill_slot_data(self) -> Dict[str, Any]: slot_data = self.options.as_dict( @@ -921,23 +687,33 @@ def fill_slot_data(self) -> Dict[str, Any]: "hms", "key_items", "bikes", + "event_tickets", "rods", "overworld_items", "hidden_items", "npc_gifts", + "berry_trees", "require_itemfinder", "require_flash", - "enable_ferry", "elite_four_requirement", "elite_four_count", "norman_requirement", "norman_count", + "legendary_hunt_catch", + "legendary_hunt_count", "extra_boulders", "remove_roadblocks", + "allowed_legendary_hunt_encounters", + "extra_bumpy_slope", "free_fly_location", - "fly_without_badge", + "remote_items", + "dexsanity", + "trainersanity", + "modify_118", + "death_link", ) slot_data["free_fly_location_id"] = self.free_fly_location_id + slot_data["hm_requirements"] = self.hm_requirements return slot_data def create_item(self, name: str) -> PokemonEmeraldItem: diff --git a/worlds/pokemon_emerald/client.py b/worlds/pokemon_emerald/client.py index d8b4b8d5878f..a830957e9c7e 100644 --- a/worlds/pokemon_emerald/client.py +++ b/worlds/pokemon_emerald/client.py @@ -1,19 +1,28 @@ -from typing import TYPE_CHECKING, Dict, Set +import asyncio +import copy +import orjson +import random +import time +from typing import TYPE_CHECKING, Optional, Dict, Set, Tuple +import uuid from NetUtils import ClientStatus +from Options import Toggle +import Utils import worlds._bizhawk as bizhawk from worlds._bizhawk.client import BizHawkClient -from .data import BASE_OFFSET, data -from .options import Goal +from .data import BASE_OFFSET, POKEDEX_OFFSET, data +from .options import Goal, RemoteItems +from .util import pokemon_data_to_json, json_to_pokemon_data if TYPE_CHECKING: from worlds._bizhawk.context import BizHawkClientContext -EXPECTED_ROM_NAME = "pokemon emerald version / AP 2" +EXPECTED_ROM_NAME = "pokemon emerald version / AP 5" -IS_CHAMPION_FLAG = data.constants["FLAG_IS_CHAMPION"] +DEFEATED_WALLACE_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_WALLACE"] DEFEATED_STEVEN_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_STEVEN"] DEFEATED_NORMAN_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_NORMAN_1"] @@ -31,7 +40,7 @@ "FLAG_RECEIVED_POKENAV", # Talk to Mr. Stone "FLAG_DELIVERED_STEVEN_LETTER", "FLAG_DELIVERED_DEVON_GOODS", - "FLAG_HIDE_ROUTE_119_TEAM_AQUA", # Clear Weather Institute + "FLAG_HIDE_ROUTE_119_TEAM_AQUA_SHELLY", # Clear Weather Institute "FLAG_MET_ARCHIE_METEOR_FALLS", # Magma steals meteorite "FLAG_GROUDON_AWAKENED_MAGMA_HIDEOUT", # Clear Magma Hideout "FLAG_MET_TEAM_AQUA_HARBOR", # Aqua steals submarine @@ -41,19 +50,26 @@ "FLAG_HIDE_SKY_PILLAR_TOP_RAYQUAZA", # Rayquaza departs for Sootopolis "FLAG_OMIT_DIVE_FROM_STEVEN_LETTER", # Steven gives Dive HM (clears seafloor cavern grunt) "FLAG_IS_CHAMPION", - "FLAG_PURCHASED_HARBOR_MAIL" + "FLAG_PURCHASED_HARBOR_MAIL", + "FLAG_REGI_DOORS_OPENED", + "FLAG_RETURNED_DEVON_GOODS", + "FLAG_DOCK_REJECTED_DEVON_GOODS", + "FLAG_DEFEATED_EVIL_TEAM_MT_CHIMNEY", + "FLAG_WINGULL_SENT_ON_ERRAND", + "FLAG_WINGULL_DELIVERED_MAIL", + "FLAG_MET_PRETTY_PETAL_SHOP_OWNER", ] EVENT_FLAG_MAP = {data.constants[flag_name]: flag_name for flag_name in TRACKER_EVENT_FLAGS} KEY_LOCATION_FLAGS = [ - "NPC_GIFT_RECEIVED_HM01", - "NPC_GIFT_RECEIVED_HM02", - "NPC_GIFT_RECEIVED_HM03", - "NPC_GIFT_RECEIVED_HM04", - "NPC_GIFT_RECEIVED_HM05", - "NPC_GIFT_RECEIVED_HM06", - "NPC_GIFT_RECEIVED_HM07", - "NPC_GIFT_RECEIVED_HM08", + "NPC_GIFT_RECEIVED_HM_CUT", + "NPC_GIFT_RECEIVED_HM_FLY", + "NPC_GIFT_RECEIVED_HM_SURF", + "NPC_GIFT_RECEIVED_HM_STRENGTH", + "NPC_GIFT_RECEIVED_HM_FLASH", + "NPC_GIFT_RECEIVED_HM_ROCK_SMASH", + "NPC_GIFT_RECEIVED_HM_WATERFALL", + "NPC_GIFT_RECEIVED_HM_DIVE", "NPC_GIFT_RECEIVED_ACRO_BIKE", "NPC_GIFT_RECEIVED_WAILMER_PAIL", "NPC_GIFT_RECEIVED_DEVON_GOODS_RUSTURF_TUNNEL", @@ -70,14 +86,37 @@ "HIDDEN_ITEM_ABANDONED_SHIP_RM_1_KEY", "HIDDEN_ITEM_ABANDONED_SHIP_RM_4_KEY", "HIDDEN_ITEM_ABANDONED_SHIP_RM_6_KEY", - "ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_4_SCANNER", + "ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_2_SCANNER", "ITEM_ABANDONED_SHIP_CAPTAINS_OFFICE_STORAGE_KEY", "NPC_GIFT_RECEIVED_OLD_ROD", "NPC_GIFT_RECEIVED_GOOD_ROD", "NPC_GIFT_RECEIVED_SUPER_ROD", + "NPC_GIFT_RECEIVED_EON_TICKET", + "NPC_GIFT_RECEIVED_AURORA_TICKET", + "NPC_GIFT_RECEIVED_MYSTIC_TICKET", + "NPC_GIFT_RECEIVED_OLD_SEA_MAP", ] KEY_LOCATION_FLAG_MAP = {data.locations[location_name].flag: location_name for location_name in KEY_LOCATION_FLAGS} +# .lower() keys for backward compatibility between 0.4.5 and 0.4.6 +LEGENDARY_NAMES = {k.lower(): v for k, v in { + "Groudon": "GROUDON", + "Kyogre": "KYOGRE", + "Rayquaza": "RAYQUAZA", + "Latias": "LATIAS", + "Latios": "LATIOS", + "Regirock": "REGIROCK", + "Regice": "REGICE", + "Registeel": "REGISTEEL", + "Mew": "MEW", + "Deoxys": "DEOXYS", + "Ho-Oh": "HO_OH", + "Lugia": "LUGIA", +}.items()} + +DEFEATED_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_DEFEATED_{name}"]: name for name in LEGENDARY_NAMES.values()} +CAUGHT_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_CAUGHT_{name}"]: name for name in LEGENDARY_NAMES.values()} + class PokemonEmeraldClient(BizHawkClient): game = "Pokemon Emerald" @@ -86,14 +125,31 @@ class PokemonEmeraldClient(BizHawkClient): local_checked_locations: Set[int] local_set_events: Dict[str, bool] local_found_key_items: Dict[str, bool] - goal_flag: int + local_defeated_legendaries: Dict[str, bool] + goal_flag: Optional[int] + + wonder_trade_update_event: asyncio.Event + latest_wonder_trade_reply: dict + wonder_trade_cooldown: int + wonder_trade_cooldown_timer: int + + death_counter: Optional[int] + previous_death_link: float + ignore_next_death_link: bool def __init__(self) -> None: super().__init__() self.local_checked_locations = set() self.local_set_events = {} self.local_found_key_items = {} - self.goal_flag = IS_CHAMPION_FLAG + self.local_defeated_legendaries = {} + self.goal_flag = None + self.wonder_trade_update_event = asyncio.Event() + self.wonder_trade_cooldown = 5000 + self.wonder_trade_cooldown_timer = 0 + self.death_counter = None + self.previous_death_link = 0 + self.ignore_next_death_link = False async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: from CommonClient import logger @@ -123,88 +179,110 @@ async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: ctx.want_slot_data = True ctx.watcher_timeout = 0.125 + self.death_counter = None + self.previous_death_link = 0 + self.ignore_next_death_link = False + return True async def set_auth(self, ctx: "BizHawkClientContext") -> None: - slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(data.rom_addresses["gArchipelagoInfo"], 64, "ROM")]))[0] - ctx.auth = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8") + import base64 + auth_raw = (await bizhawk.read(ctx.bizhawk_ctx, [(data.rom_addresses["gArchipelagoInfo"], 16, "ROM")]))[0] + ctx.auth = base64.b64encode(auth_raw).decode("utf-8") async def game_watcher(self, ctx: "BizHawkClientContext") -> None: - if ctx.slot_data is not None: - if ctx.slot_data["goal"] == Goal.option_champion: - self.goal_flag = IS_CHAMPION_FLAG - elif ctx.slot_data["goal"] == Goal.option_steven: - self.goal_flag = DEFEATED_STEVEN_FLAG - elif ctx.slot_data["goal"] == Goal.option_norman: - self.goal_flag = DEFEATED_NORMAN_FLAG + if ctx.server is None or ctx.server.socket.closed or ctx.slot_data is None: + return + + if ctx.slot_data["goal"] == Goal.option_champion: + self.goal_flag = DEFEATED_WALLACE_FLAG + elif ctx.slot_data["goal"] == Goal.option_steven: + self.goal_flag = DEFEATED_STEVEN_FLAG + elif ctx.slot_data["goal"] == Goal.option_norman: + self.goal_flag = DEFEATED_NORMAN_FLAG + elif ctx.slot_data["goal"] == Goal.option_legendary_hunt: + self.goal_flag = None + + if ctx.slot_data["remote_items"] == RemoteItems.option_true and not ctx.items_handling & 0b010: + ctx.items_handling = 0b011 + Utils.async_start(ctx.send_msgs([{ + "cmd": "ConnectUpdate", + "items_handling": ctx.items_handling + }])) + + # Need to make sure items handling updates and we get the correct list of received items + # before continuing. Otherwise we might give some duplicate items and skip others. + # Should patch remote_items option value into the ROM in the future to guarantee we get the + # right item list before entering this part of the code + await asyncio.sleep(0.75) + return try: - # Checks that the player is in the overworld - overworld_guard = (data.ram_addresses["gMain"] + 4, (data.ram_addresses["CB2_Overworld"] + 1).to_bytes(4, "little"), "System Bus") + guards: Dict[str, Tuple[int, bytes, str]] = {} - # Read save block address - read_result = await bizhawk.guarded_read( - ctx.bizhawk_ctx, - [(data.ram_addresses["gSaveBlock1Ptr"], 4, "System Bus")], - [overworld_guard] + # Checks that the player is in the overworld + guards["IN OVERWORLD"] = ( + data.ram_addresses["gMain"] + 4, + (data.ram_addresses["CB2_Overworld"] + 1).to_bytes(4, "little"), + "System Bus" ) - if read_result is None: # Not in overworld - return - - # Checks that the save block hasn't moved - save_block_address_guard = (data.ram_addresses["gSaveBlock1Ptr"], read_result[0], "System Bus") - - save_block_address = int.from_bytes(read_result[0], "little") - # Handle giving the player items - read_result = await bizhawk.guarded_read( + # Read save block addresses + read_result = await bizhawk.read( ctx.bizhawk_ctx, [ - (save_block_address + 0x3778, 2, "System Bus"), # Number of received items - (data.ram_addresses["gArchipelagoReceivedItem"] + 4, 1, "System Bus") # Received item struct full? - ], - [overworld_guard, save_block_address_guard] + (data.ram_addresses["gSaveBlock1Ptr"], 4, "System Bus"), + (data.ram_addresses["gSaveBlock2Ptr"], 4, "System Bus"), + ] ) - if read_result is None: # Not in overworld, or save block moved - return - num_received_items = int.from_bytes(read_result[0], "little") - received_item_is_empty = read_result[1][0] == 0 + # Checks that the save data hasn't moved + guards["SAVE BLOCK 1"] = (data.ram_addresses["gSaveBlock1Ptr"], read_result[0], "System Bus") + guards["SAVE BLOCK 2"] = (data.ram_addresses["gSaveBlock2Ptr"], read_result[1], "System Bus") - # If the game hasn't received all items yet and the received item struct doesn't contain an item, then - # fill it with the next item - if num_received_items < len(ctx.items_received) and received_item_is_empty: - next_item = ctx.items_received[num_received_items] - await bizhawk.write(ctx.bizhawk_ctx, [ - (data.ram_addresses["gArchipelagoReceivedItem"] + 0, (next_item.item - BASE_OFFSET).to_bytes(2, "little"), "System Bus"), - (data.ram_addresses["gArchipelagoReceivedItem"] + 2, (num_received_items + 1).to_bytes(2, "little"), "System Bus"), - (data.ram_addresses["gArchipelagoReceivedItem"] + 4, [1], "System Bus"), # Mark struct full - (data.ram_addresses["gArchipelagoReceivedItem"] + 5, [next_item.flags & 1], "System Bus"), - ]) + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + sb2_address = int.from_bytes(guards["SAVE BLOCK 2"][1], "little") + + await self.handle_death_link(ctx, guards) + await self.handle_received_items(ctx, guards) + await self.handle_wonder_trade(ctx, guards) # Read flags in 2 chunks read_result = await bizhawk.guarded_read( ctx.bizhawk_ctx, - [(save_block_address + 0x1450, 0x96, "System Bus")], # Flags - [overworld_guard, save_block_address_guard] + [(sb1_address + 0x1450, 0x96, "System Bus")], # Flags + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] ) if read_result is None: # Not in overworld, or save block moved return - flag_bytes = read_result[0] read_result = await bizhawk.guarded_read( ctx.bizhawk_ctx, - [(save_block_address + 0x14E6, 0x96, "System Bus")], # Flags - [overworld_guard, save_block_address_guard] + [(sb1_address + 0x14E6, 0x96, "System Bus")], # Flags continued + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] ) if read_result is not None: flag_bytes += read_result[0] + # Read pokedex flags + pokedex_caught_bytes = bytes(0) + if ctx.slot_data["dexsanity"] == Toggle.option_true: + # Read pokedex flags + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, + [(sb2_address + 0x28, 0x34, "System Bus")], + [guards["IN OVERWORLD"], guards["SAVE BLOCK 2"]] + ) + if read_result is not None: + pokedex_caught_bytes = read_result[0] + game_clear = False local_checked_locations = set() local_set_events = {flag_name: False for flag_name in TRACKER_EVENT_FLAGS} local_found_key_items = {location_name: False for location_name in KEY_LOCATION_FLAGS} + defeated_legendaries = {legendary_name: False for legendary_name in LEGENDARY_NAMES.values()} + caught_legendaries = {legendary_name: False for legendary_name in LEGENDARY_NAMES.values()} # Check set flags for byte_i, byte in enumerate(flag_bytes): @@ -219,12 +297,45 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: if flag_id == self.goal_flag: game_clear = True + if flag_id in DEFEATED_LEGENDARY_FLAG_MAP: + defeated_legendaries[DEFEATED_LEGENDARY_FLAG_MAP[flag_id]] = True + + if flag_id in CAUGHT_LEGENDARY_FLAG_MAP: + caught_legendaries[CAUGHT_LEGENDARY_FLAG_MAP[flag_id]] = True + if flag_id in EVENT_FLAG_MAP: local_set_events[EVENT_FLAG_MAP[flag_id]] = True if flag_id in KEY_LOCATION_FLAG_MAP: local_found_key_items[KEY_LOCATION_FLAG_MAP[flag_id]] = True + # Check pokedex + if ctx.slot_data["dexsanity"] == Toggle.option_true: + for byte_i, byte in enumerate(pokedex_caught_bytes): + for i in range(8): + if byte & (1 << i) != 0: + dex_number = (byte_i * 8 + i) + 1 + + location_id = dex_number + BASE_OFFSET + POKEDEX_OFFSET + if location_id in ctx.server_locations: + local_checked_locations.add(location_id) + + # Count legendary hunt flags + if ctx.slot_data["goal"] == Goal.option_legendary_hunt: + # If legendary hunt doesn't require catching, add defeated legendaries to caught_legendaries + if ctx.slot_data["legendary_hunt_catch"] == Toggle.option_false: + for legendary, is_defeated in defeated_legendaries.items(): + if is_defeated: + caught_legendaries[legendary] = True + + num_caught = 0 + for legendary, is_caught in caught_legendaries.items(): + if is_caught and legendary in [LEGENDARY_NAMES[name.lower()] for name in ctx.slot_data["allowed_legendary_hunt_encounters"]]: + num_caught += 1 + + if num_caught >= ctx.slot_data["legendary_hunt_count"]: + game_clear = True + # Send locations if local_checked_locations != self.local_checked_locations: self.local_checked_locations = local_checked_locations @@ -232,14 +343,14 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: if local_checked_locations is not None: await ctx.send_msgs([{ "cmd": "LocationChecks", - "locations": list(local_checked_locations) + "locations": list(local_checked_locations), }]) # Send game clear if not ctx.finished_game and game_clear: await ctx.send_msgs([{ "cmd": "StatusUpdate", - "status": ClientStatus.CLIENT_GOAL + "status": ClientStatus.CLIENT_GOAL, }]) # Send tracker event flags @@ -254,7 +365,7 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: "key": f"pokemon_emerald_events_{ctx.team}_{ctx.slot}", "default": 0, "want_reply": False, - "operations": [{"operation": "or", "value": event_bitfield}] + "operations": [{"operation": "or", "value": event_bitfield}], }]) self.local_set_events = local_set_events @@ -269,9 +380,315 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: "key": f"pokemon_emerald_keys_{ctx.team}_{ctx.slot}", "default": 0, "want_reply": False, - "operations": [{"operation": "or", "value": key_bitfield}] + "operations": [{"operation": "or", "value": key_bitfield}], }]) self.local_found_key_items = local_found_key_items + + if ctx.slot_data["goal"] == Goal.option_legendary_hunt: + if caught_legendaries != self.local_defeated_legendaries and ctx.slot is not None: + legendary_bitfield = 0 + for i, legendary_name in enumerate(LEGENDARY_NAMES.values()): + if caught_legendaries[legendary_name]: + legendary_bitfield |= 1 << i + + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_emerald_legendaries_{ctx.team}_{ctx.slot}", + "default": 0, + "want_reply": False, + "operations": [{"operation": "or", "value": legendary_bitfield}], + }]) + self.local_defeated_legendaries = caught_legendaries except bizhawk.RequestFailedError: # Exit handler and return to main loop to reconnect pass + + async def handle_death_link(self, ctx: "BizHawkClientContext", guards: Dict[str, Tuple[int, bytes, str]]) -> None: + """ + Checks whether the player has died while connected and sends a death link if so. Queues a death link in the game + if a new one has been received. + """ + if ctx.slot_data.get("death_link", Toggle.option_false) == Toggle.option_true: + if "DeathLink" not in ctx.tags: + await ctx.update_death_link(True) + self.previous_death_link = ctx.last_death_link + + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + sb2_address = int.from_bytes(guards["SAVE BLOCK 2"][1], "little") + + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, [ + (sb1_address + 0x177C + (52 * 4), 4, "System Bus"), # White out stat + (sb1_address + 0x177C + (22 * 4), 4, "System Bus"), # Canary stat + (sb2_address + 0xAC, 4, "System Bus"), # Encryption key + ], + [guards["SAVE BLOCK 1"], guards["SAVE BLOCK 2"]] + ) + if read_result is None: # Save block moved + return + + encryption_key = int.from_bytes(read_result[2], "little") + times_whited_out = int.from_bytes(read_result[0], "little") ^ encryption_key + + # Canary is an unused stat that will always be 0. There is a low chance that we've done this read on + # a frame where the user has just entered a battle and the encryption key has been changed, but the data + # has not yet been encrypted with the new key. If `canary` is 0, `times_whited_out` is correct. + canary = int.from_bytes(read_result[1], "little") ^ encryption_key + + # Skip all deathlink code if save is not yet loaded (encryption key is zero) or white out stat not yet + # initialized (starts at 100 as a safety for subtracting values from an unsigned int). + if canary == 0 and encryption_key != 0 and times_whited_out >= 100: + if self.previous_death_link != ctx.last_death_link: + self.previous_death_link = ctx.last_death_link + if self.ignore_next_death_link: + self.ignore_next_death_link = False + else: + await bizhawk.write( + ctx.bizhawk_ctx, + [(data.ram_addresses["gArchipelagoDeathLinkQueued"], [1], "System Bus")] + ) + + if self.death_counter is None: + self.death_counter = times_whited_out + elif times_whited_out > self.death_counter: + await ctx.send_death(f"{ctx.player_names[ctx.slot]} is out of usable POKéMON! " + f"{ctx.player_names[ctx.slot]} whited out!") + self.ignore_next_death_link = True + self.death_counter = times_whited_out + + async def handle_received_items(self, ctx: "BizHawkClientContext", guards: Dict[str, Tuple[int, bytes, str]]) -> None: + """ + Checks the index of the most recently received item and whether the item queue is full. Writes the next item + into the game if necessary. + """ + received_item_address = data.ram_addresses["gArchipelagoReceivedItem"] + + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, + [ + (sb1_address + 0x3778, 2, "System Bus"), # Number of received items + (received_item_address + 4, 1, "System Bus") # Received item struct full? + ], + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] + ) + if read_result is None: # Not in overworld, or save block moved + return + + num_received_items = int.from_bytes(read_result[0], "little") + received_item_is_empty = read_result[1][0] == 0 + + # If the game hasn't received all items yet and the received item struct doesn't contain an item, then + # fill it with the next item + if num_received_items < len(ctx.items_received) and received_item_is_empty: + next_item = ctx.items_received[num_received_items] + should_display = 1 if next_item.flags & 1 or next_item.player == ctx.slot else 0 + await bizhawk.write(ctx.bizhawk_ctx, [ + (received_item_address + 0, (next_item.item - BASE_OFFSET).to_bytes(2, "little"), "System Bus"), + (received_item_address + 2, (num_received_items + 1).to_bytes(2, "little"), "System Bus"), + (received_item_address + 4, [1], "System Bus"), + (received_item_address + 5, [should_display], "System Bus"), + ]) + + async def handle_wonder_trade(self, ctx: "BizHawkClientContext", guards: Dict[str, Tuple[int, bytes, str]]) -> None: + """ + Read wonder trade status from save data and either send a queued pokemon to data storage or attempt to retrieve + one from data storage and write it into the save. + """ + from CommonClient import logger + + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, + [ + (sb1_address + 0x377C, 0x50, "System Bus"), # Wonder trade data + (sb1_address + 0x37CC, 1, "System Bus"), # Is wonder trade sent + ], + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] + ) + + if read_result is not None: + wonder_trade_pokemon_data = read_result[0] + trade_is_sent = read_result[1][0] + + if trade_is_sent == 0 and wonder_trade_pokemon_data[19] == 2: + # Game has wonder trade data to send. Send it to data storage, remove it from the game's memory, + # and mark that the game is waiting on receiving a trade + Utils.async_start(self.wonder_trade_send(ctx, pokemon_data_to_json(wonder_trade_pokemon_data))) + await bizhawk.write(ctx.bizhawk_ctx, [ + (sb1_address + 0x377C, bytes(0x50), "System Bus"), + (sb1_address + 0x37CC, [1], "System Bus"), + ]) + elif trade_is_sent != 0 and wonder_trade_pokemon_data[19] != 2: + # Game is waiting on receiving a trade. See if there are any available trades that were not + # sent by this player, and if so, try to receive one. + if self.wonder_trade_cooldown_timer <= 0 and f"pokemon_wonder_trades_{ctx.team}" in ctx.stored_data: + if any(item[0] != ctx.slot + for key, item in ctx.stored_data.get(f"pokemon_wonder_trades_{ctx.team}", {}).items() + if key != "_lock" and orjson.loads(item[1])["species"] <= 386): + received_trade = await self.wonder_trade_receive(ctx) + if received_trade is None: + self.wonder_trade_cooldown_timer = self.wonder_trade_cooldown + self.wonder_trade_cooldown *= 2 + self.wonder_trade_cooldown += random.randrange(0, 500) + else: + await bizhawk.write(ctx.bizhawk_ctx, [ + (sb1_address + 0x377C, json_to_pokemon_data(received_trade), "System Bus"), + ]) + logger.info("Wonder trade received!") + self.wonder_trade_cooldown = 5000 + + else: + # Very approximate "time since last loop", but extra delay is fine for this + self.wonder_trade_cooldown_timer -= int(ctx.watcher_timeout * 1000) + + async def wonder_trade_acquire(self, ctx: "BizHawkClientContext", keep_trying: bool = False) -> Optional[dict]: + """ + Acquires a lock on the `pokemon_wonder_trades_{ctx.team}` key in + datastorage. Locking the key means you have exclusive access + to modifying the value until you unlock it or the key expires (5 + seconds). + + If `keep_trying` is `True`, it will keep trying to acquire the lock + until successful. Otherwise it will return `None` if it fails to + acquire the lock. + """ + while not ctx.exit_event.is_set(): + lock = int(time.time_ns() / 1000000) + message_uuid = str(uuid.uuid4()) + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "want_reply": True, + "operations": [{"operation": "update", "value": {"_lock": lock}}], + "uuid": message_uuid, + }]) + + self.wonder_trade_update_event.clear() + try: + await asyncio.wait_for(self.wonder_trade_update_event.wait(), 5) + except asyncio.TimeoutError: + if not keep_trying: + return None + continue + + reply = copy.deepcopy(self.latest_wonder_trade_reply) + + # Make sure the most recently received update was triggered by our lock attempt + if reply.get("uuid", None) != message_uuid: + if not keep_trying: + return None + await asyncio.sleep(self.wonder_trade_cooldown) + continue + + # Make sure the current value of the lock is what we set it to + # (I think this should theoretically never run) + if reply["value"]["_lock"] != lock: + if not keep_trying: + return None + await asyncio.sleep(self.wonder_trade_cooldown) + continue + + # Make sure that the lock value we replaced is at least 5 seconds old + # If it was unlocked before our change, its value was 0 and it will look decades old + if lock - reply["original_value"]["_lock"] < 5000: + # Multiple clients trying to lock the key may get stuck in a loop of checking the lock + # by trying to set it, which will extend its expiration. So if we see that the lock was + # too new when we replaced it, we should wait for increasingly longer periods so that + # eventually the lock will expire and a client will acquire it. + self.wonder_trade_cooldown *= 2 + self.wonder_trade_cooldown += random.randrange(0, 500) + + if not keep_trying: + self.wonder_trade_cooldown_timer = self.wonder_trade_cooldown + return None + await asyncio.sleep(self.wonder_trade_cooldown) + continue + + # We have the lock, reset the cooldown and return + self.wonder_trade_cooldown = 5000 + return reply + + async def wonder_trade_send(self, ctx: "BizHawkClientContext", data: str) -> None: + """ + Sends a wonder trade pokemon to data storage + """ + from CommonClient import logger + + reply = await self.wonder_trade_acquire(ctx, True) + + wonder_trade_slot = 0 + while str(wonder_trade_slot) in reply["value"]: + wonder_trade_slot += 1 + + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [{"operation": "update", "value": { + "_lock": 0, + str(wonder_trade_slot): (ctx.slot, data), + }}], + }]) + + logger.info("Wonder trade sent! We'll notify you here when a trade has been found.") + + async def wonder_trade_receive(self, ctx: "BizHawkClientContext") -> Optional[str]: + """ + Tries to pop a pokemon out of the wonder trades. Returns `None` if + for some reason it can't immediately remove a compatible pokemon. + """ + reply = await self.wonder_trade_acquire(ctx) + + if reply is None: + return None + + candidate_slots = [ + int(slot) + for slot in reply["value"] + if slot != "_lock" \ + and reply["value"][slot][0] != ctx.slot \ + and orjson.loads(reply["value"][slot][1])["species"] <= 386 + ] + + if len(candidate_slots) == 0: + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [{"operation": "update", "value": {"_lock": 0}}], + }]) + return None + + wonder_trade_slot = max(candidate_slots) + + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [ + {"operation": "update", "value": {"_lock": 0}}, + {"operation": "pop", "value": str(wonder_trade_slot)}, + ] + }]) + + return reply["value"][str(wonder_trade_slot)][1] + + def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: + if cmd == "Connected": + Utils.async_start(ctx.send_msgs([{ + "cmd": "SetNotify", + "keys": [f"pokemon_wonder_trades_{ctx.team}"], + }, { + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [{"operation": "default", "value": None}] # value is ignored + }])) + elif cmd == "SetReply": + if args.get("key", "") == f"pokemon_wonder_trades_{ctx.team}": + self.latest_wonder_trade_reply = args + self.wonder_trade_update_event.set() diff --git a/worlds/pokemon_emerald/data.py b/worlds/pokemon_emerald/data.py index bc51d84963c5..d89ab5febb33 100644 --- a/worlds/pokemon_emerald/data.py +++ b/worlds/pokemon_emerald/data.py @@ -5,7 +5,6 @@ and sorting, and Warp methods. """ from dataclasses import dataclass -import copy from enum import IntEnum import orjson from typing import Dict, List, NamedTuple, Optional, Set, FrozenSet, Tuple, Any, Union @@ -16,6 +15,32 @@ BASE_OFFSET = 3860000 +POKEDEX_OFFSET = 10000 + +IGNORABLE_MAPS = { + "MAP_ALTERING_CAVE", + "MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1", + "MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2", + "MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3", +} +"""These maps exist but don't show up in the rando or are unused, and so should be discarded""" + +OUT_OF_LOGIC_MAPS = { + "MAP_DESERT_UNDERPASS", + "MAP_SAFARI_ZONE_NORTHEAST", + "MAP_SAFARI_ZONE_SOUTHEAST", + "MAP_METEOR_FALLS_STEVENS_CAVE", + "MAP_MIRAGE_TOWER_1F", + "MAP_MIRAGE_TOWER_2F", + "MAP_MIRAGE_TOWER_3F", + "MAP_MIRAGE_TOWER_4F", +} +""" +These maps have encounters and are locked behind beating the champion or are missable. +Those encounter slots should be ignored for logical access to a species. +""" + +NUM_REAL_SPECIES = 386 class Warp: @@ -55,14 +80,14 @@ def encode(self) -> str: return f"{self.source_map}:{source_ids_string}/{self.dest_map}:{dest_ids_string}{'!' if self.is_one_way else ''}" - def connects_to(self, other: 'Warp') -> bool: + def connects_to(self, other: "Warp") -> bool: """ Returns true if this warp sends the player to `other` """ return self.dest_map == other.source_map and set(self.dest_ids) <= set(other.source_ids) @staticmethod - def decode(encoded_string: str) -> 'Warp': + def decode(encoded_string: str) -> "Warp": """ Create a Warp object from an encoded string """ @@ -87,6 +112,7 @@ def decode(encoded_string: str) -> 'Warp': class ItemData(NamedTuple): label: str item_id: int + modern_id: Optional[int] classification: ItemClassification tags: FrozenSet[str] @@ -96,11 +122,25 @@ class LocationData(NamedTuple): label: str parent_region: str default_item: int - rom_address: int + address: Union[int, List[int]] flag: int tags: FrozenSet[str] +class EncounterTableData(NamedTuple): + slots: List[int] + address: int + + +@dataclass +class MapData: + name: str + header_address: int + land_encounters: Optional[EncounterTableData] + water_encounters: Optional[EncounterTableData] + fishing_encounters: Optional[EncounterTableData] + + class EventData(NamedTuple): name: str parent_region: str @@ -108,13 +148,21 @@ class EventData(NamedTuple): class RegionData: name: str + parent_map: MapData + has_grass: bool + has_water: bool + has_fishing: bool exits: List[str] warps: List[str] locations: List[str] events: List[EventData] - def __init__(self, name: str): + def __init__(self, name: str, parent_map: MapData, has_grass: bool, has_water: bool, has_fishing: bool): self.name = name + self.parent_map = parent_map + self.has_grass = has_grass + self.has_water = has_water + self.has_fishing = has_fishing self.exits = [] self.warps = [] self.locations = [] @@ -181,9 +229,9 @@ class EvolutionData(NamedTuple): species_id: int -class StaticEncounterData(NamedTuple): +class MiscPokemonData(NamedTuple): species_id: int - rom_address: int + address: int @dataclass @@ -191,16 +239,18 @@ class SpeciesData: name: str label: str species_id: int + national_dex_number: int base_stats: BaseStats types: Tuple[int, int] abilities: Tuple[int, int] evolutions: List[EvolutionData] pre_evolution: Optional[int] catch_rate: int + friendship: int learnset: List[LearnsetMove] tm_hm_compatibility: int - learnset_rom_address: int - rom_address: int + learnset_address: int + address: int class AbilityData(NamedTuple): @@ -208,19 +258,6 @@ class AbilityData(NamedTuple): label: str -class EncounterTableData(NamedTuple): - slots: List[int] - rom_address: int - - -@dataclass -class MapData: - name: str - land_encounters: Optional[EncounterTableData] - water_encounters: Optional[EncounterTableData] - fishing_encounters: Optional[EncounterTableData] - - class TrainerPokemonDataTypeEnum(IntEnum): NO_ITEM_DEFAULT_MOVES = 0 ITEM_DEFAULT_MOVES = 1 @@ -250,15 +287,16 @@ class TrainerPokemonData: class TrainerPartyData: pokemon: List[TrainerPokemonData] pokemon_data_type: TrainerPokemonDataTypeEnum - rom_address: int + address: int @dataclass class TrainerData: trainer_id: int party: TrainerPartyData - rom_address: int - battle_script_rom_address: int + address: int + script_address: int + battle_type: int class PokemonEmeraldData: @@ -269,11 +307,13 @@ class PokemonEmeraldData: regions: Dict[str, RegionData] locations: Dict[str, LocationData] items: Dict[int, ItemData] - species: List[Optional[SpeciesData]] - static_encounters: List[StaticEncounterData] + species: Dict[int, SpeciesData] + legendary_encounters: List[MiscPokemonData] + misc_pokemon: List[MiscPokemonData] tmhm_moves: List[int] abilities: List[AbilityData] - maps: List[MapData] + move_labels: Dict[str, int] + maps: Dict[str, MapData] warps: Dict[str, Warp] warp_map: Dict[str, Optional[str]] trainers: List[TrainerData] @@ -286,29 +326,20 @@ def __init__(self) -> None: self.regions = {} self.locations = {} self.items = {} - self.species = [] - self.static_encounters = [] + self.species = {} + self.legendary_encounters = [] + self.misc_pokemon = [] self.tmhm_moves = [] self.abilities = [] - self.maps = [] + self.move_labels = {} + self.maps = {} self.warps = {} self.warp_map = {} self.trainers = [] def load_json_data(data_name: str) -> Union[List[Any], Dict[str, Any]]: - return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode('utf-8-sig')) - - -data = PokemonEmeraldData() - -def create_data_copy() -> PokemonEmeraldData: - new_copy = PokemonEmeraldData() - new_copy.species = copy.deepcopy(data.species) - new_copy.tmhm_moves = copy.deepcopy(data.tmhm_moves) - new_copy.maps = copy.deepcopy(data.maps) - new_copy.static_encounters = copy.deepcopy(data.static_encounters) - new_copy.trainers = copy.deepcopy(data.trainers) + return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode("utf-8-sig")) def _init() -> None: @@ -319,6 +350,39 @@ def _init() -> None: location_attributes_json = load_json_data("locations.json") + # Create map data + for map_name, map_json in extracted_data["maps"].items(): + if map_name in IGNORABLE_MAPS: + continue + + land_encounters = None + water_encounters = None + fishing_encounters = None + + if "land_encounters" in map_json: + land_encounters = EncounterTableData( + map_json["land_encounters"]["slots"], + map_json["land_encounters"]["address"] + ) + if "water_encounters" in map_json: + water_encounters = EncounterTableData( + map_json["water_encounters"]["slots"], + map_json["water_encounters"]["address"] + ) + if "fishing_encounters" in map_json: + fishing_encounters = EncounterTableData( + map_json["fishing_encounters"]["slots"], + map_json["fishing_encounters"]["address"] + ) + + data.maps[map_name] = MapData( + map_name, + map_json["header_address"], + land_encounters, + water_encounters, + fishing_encounters + ) + # Load/merge region json files region_json_list = [] for file in pkg_resources.resource_listdir(__name__, "data/regions"): @@ -338,7 +402,13 @@ def _init() -> None: data.regions = {} for region_name, region_json in regions_json.items(): - new_region = RegionData(region_name) + new_region = RegionData( + region_name, + data.maps[region_json["parent_map"]], + region_json["has_grass"], + region_json["has_water"], + region_json["has_fishing"] + ) # Locations for location_name in region_json["locations"]: @@ -346,15 +416,35 @@ def _init() -> None: raise AssertionError(f"Location [{location_name}] was claimed by multiple regions") location_json = extracted_data["locations"][location_name] - new_location = LocationData( - location_name, - location_attributes_json[location_name]["label"], - region_name, - location_json["default_item"], - location_json["rom_address"], - location_json["flag"], - frozenset(location_attributes_json[location_name]["tags"]) - ) + if location_name.startswith("TRAINER_BRENDAN_") or location_name.startswith("TRAINER_MAY_"): + import re + locale = re.match("TRAINER_BRENDAN_([A-Z0-9_]+)_MUDKIP_REWARD", location_name).group(1) + alternate_rival_jsons = [extracted_data["locations"][alternate] for alternate in [ + f"TRAINER_BRENDAN_{locale}_TORCHIC_REWARD", + f"TRAINER_BRENDAN_{locale}_TREECKO_REWARD", + f"TRAINER_MAY_{locale}_MUDKIP_REWARD", + f"TRAINER_MAY_{locale}_TORCHIC_REWARD", + f"TRAINER_MAY_{locale}_TREECKO_REWARD", + ]] + new_location = LocationData( + location_name, + location_attributes_json[location_name]["label"], + region_name, + location_json["default_item"], + [location_json["address"]] + [j["address"] for j in alternate_rival_jsons], + location_json["flag"], + frozenset(location_attributes_json[location_name]["tags"]) + ) + else: + new_location = LocationData( + location_name, + location_attributes_json[location_name]["label"], + region_name, + location_json["default_item"], + location_json["address"], + location_json["flag"], + frozenset(location_attributes_json[location_name]["tags"]) + ) new_region.locations.append(location_name) data.locations[location_name] = new_location claimed_locations.add(location_name) @@ -401,6 +491,7 @@ def _init() -> None: data.items[data.constants[item_constant_name]] = ItemData( attributes["label"], data.constants[item_constant_name], + attributes["modern_id"], item_classification, frozenset(attributes["tags"]) ) @@ -408,408 +499,408 @@ def _init() -> None: # Create species data # Excludes extras like copies of Unown and special species values like SPECIES_EGG. - all_species: List[Tuple[str, str]] = [ - ("SPECIES_BULBASAUR", "Bulbasaur"), - ("SPECIES_IVYSAUR", "Ivysaur"), - ("SPECIES_VENUSAUR", "Venusaur"), - ("SPECIES_CHARMANDER", "Charmander"), - ("SPECIES_CHARMELEON", "Charmeleon"), - ("SPECIES_CHARIZARD", "Charizard"), - ("SPECIES_SQUIRTLE", "Squirtle"), - ("SPECIES_WARTORTLE", "Wartortle"), - ("SPECIES_BLASTOISE", "Blastoise"), - ("SPECIES_CATERPIE", "Caterpie"), - ("SPECIES_METAPOD", "Metapod"), - ("SPECIES_BUTTERFREE", "Butterfree"), - ("SPECIES_WEEDLE", "Weedle"), - ("SPECIES_KAKUNA", "Kakuna"), - ("SPECIES_BEEDRILL", "Beedrill"), - ("SPECIES_PIDGEY", "Pidgey"), - ("SPECIES_PIDGEOTTO", "Pidgeotto"), - ("SPECIES_PIDGEOT", "Pidgeot"), - ("SPECIES_RATTATA", "Rattata"), - ("SPECIES_RATICATE", "Raticate"), - ("SPECIES_SPEAROW", "Spearow"), - ("SPECIES_FEAROW", "Fearow"), - ("SPECIES_EKANS", "Ekans"), - ("SPECIES_ARBOK", "Arbok"), - ("SPECIES_PIKACHU", "Pikachu"), - ("SPECIES_RAICHU", "Raichu"), - ("SPECIES_SANDSHREW", "Sandshrew"), - ("SPECIES_SANDSLASH", "Sandslash"), - ("SPECIES_NIDORAN_F", "Nidoran Female"), - ("SPECIES_NIDORINA", "Nidorina"), - ("SPECIES_NIDOQUEEN", "Nidoqueen"), - ("SPECIES_NIDORAN_M", "Nidoran Male"), - ("SPECIES_NIDORINO", "Nidorino"), - ("SPECIES_NIDOKING", "Nidoking"), - ("SPECIES_CLEFAIRY", "Clefairy"), - ("SPECIES_CLEFABLE", "Clefable"), - ("SPECIES_VULPIX", "Vulpix"), - ("SPECIES_NINETALES", "Ninetales"), - ("SPECIES_JIGGLYPUFF", "Jigglypuff"), - ("SPECIES_WIGGLYTUFF", "Wigglytuff"), - ("SPECIES_ZUBAT", "Zubat"), - ("SPECIES_GOLBAT", "Golbat"), - ("SPECIES_ODDISH", "Oddish"), - ("SPECIES_GLOOM", "Gloom"), - ("SPECIES_VILEPLUME", "Vileplume"), - ("SPECIES_PARAS", "Paras"), - ("SPECIES_PARASECT", "Parasect"), - ("SPECIES_VENONAT", "Venonat"), - ("SPECIES_VENOMOTH", "Venomoth"), - ("SPECIES_DIGLETT", "Diglett"), - ("SPECIES_DUGTRIO", "Dugtrio"), - ("SPECIES_MEOWTH", "Meowth"), - ("SPECIES_PERSIAN", "Persian"), - ("SPECIES_PSYDUCK", "Psyduck"), - ("SPECIES_GOLDUCK", "Golduck"), - ("SPECIES_MANKEY", "Mankey"), - ("SPECIES_PRIMEAPE", "Primeape"), - ("SPECIES_GROWLITHE", "Growlithe"), - ("SPECIES_ARCANINE", "Arcanine"), - ("SPECIES_POLIWAG", "Poliwag"), - ("SPECIES_POLIWHIRL", "Poliwhirl"), - ("SPECIES_POLIWRATH", "Poliwrath"), - ("SPECIES_ABRA", "Abra"), - ("SPECIES_KADABRA", "Kadabra"), - ("SPECIES_ALAKAZAM", "Alakazam"), - ("SPECIES_MACHOP", "Machop"), - ("SPECIES_MACHOKE", "Machoke"), - ("SPECIES_MACHAMP", "Machamp"), - ("SPECIES_BELLSPROUT", "Bellsprout"), - ("SPECIES_WEEPINBELL", "Weepinbell"), - ("SPECIES_VICTREEBEL", "Victreebel"), - ("SPECIES_TENTACOOL", "Tentacool"), - ("SPECIES_TENTACRUEL", "Tentacruel"), - ("SPECIES_GEODUDE", "Geodude"), - ("SPECIES_GRAVELER", "Graveler"), - ("SPECIES_GOLEM", "Golem"), - ("SPECIES_PONYTA", "Ponyta"), - ("SPECIES_RAPIDASH", "Rapidash"), - ("SPECIES_SLOWPOKE", "Slowpoke"), - ("SPECIES_SLOWBRO", "Slowbro"), - ("SPECIES_MAGNEMITE", "Magnemite"), - ("SPECIES_MAGNETON", "Magneton"), - ("SPECIES_FARFETCHD", "Farfetch'd"), - ("SPECIES_DODUO", "Doduo"), - ("SPECIES_DODRIO", "Dodrio"), - ("SPECIES_SEEL", "Seel"), - ("SPECIES_DEWGONG", "Dewgong"), - ("SPECIES_GRIMER", "Grimer"), - ("SPECIES_MUK", "Muk"), - ("SPECIES_SHELLDER", "Shellder"), - ("SPECIES_CLOYSTER", "Cloyster"), - ("SPECIES_GASTLY", "Gastly"), - ("SPECIES_HAUNTER", "Haunter"), - ("SPECIES_GENGAR", "Gengar"), - ("SPECIES_ONIX", "Onix"), - ("SPECIES_DROWZEE", "Drowzee"), - ("SPECIES_HYPNO", "Hypno"), - ("SPECIES_KRABBY", "Krabby"), - ("SPECIES_KINGLER", "Kingler"), - ("SPECIES_VOLTORB", "Voltorb"), - ("SPECIES_ELECTRODE", "Electrode"), - ("SPECIES_EXEGGCUTE", "Exeggcute"), - ("SPECIES_EXEGGUTOR", "Exeggutor"), - ("SPECIES_CUBONE", "Cubone"), - ("SPECIES_MAROWAK", "Marowak"), - ("SPECIES_HITMONLEE", "Hitmonlee"), - ("SPECIES_HITMONCHAN", "Hitmonchan"), - ("SPECIES_LICKITUNG", "Lickitung"), - ("SPECIES_KOFFING", "Koffing"), - ("SPECIES_WEEZING", "Weezing"), - ("SPECIES_RHYHORN", "Rhyhorn"), - ("SPECIES_RHYDON", "Rhydon"), - ("SPECIES_CHANSEY", "Chansey"), - ("SPECIES_TANGELA", "Tangela"), - ("SPECIES_KANGASKHAN", "Kangaskhan"), - ("SPECIES_HORSEA", "Horsea"), - ("SPECIES_SEADRA", "Seadra"), - ("SPECIES_GOLDEEN", "Goldeen"), - ("SPECIES_SEAKING", "Seaking"), - ("SPECIES_STARYU", "Staryu"), - ("SPECIES_STARMIE", "Starmie"), - ("SPECIES_MR_MIME", "Mr. Mime"), - ("SPECIES_SCYTHER", "Scyther"), - ("SPECIES_JYNX", "Jynx"), - ("SPECIES_ELECTABUZZ", "Electabuzz"), - ("SPECIES_MAGMAR", "Magmar"), - ("SPECIES_PINSIR", "Pinsir"), - ("SPECIES_TAUROS", "Tauros"), - ("SPECIES_MAGIKARP", "Magikarp"), - ("SPECIES_GYARADOS", "Gyarados"), - ("SPECIES_LAPRAS", "Lapras"), - ("SPECIES_DITTO", "Ditto"), - ("SPECIES_EEVEE", "Eevee"), - ("SPECIES_VAPOREON", "Vaporeon"), - ("SPECIES_JOLTEON", "Jolteon"), - ("SPECIES_FLAREON", "Flareon"), - ("SPECIES_PORYGON", "Porygon"), - ("SPECIES_OMANYTE", "Omanyte"), - ("SPECIES_OMASTAR", "Omastar"), - ("SPECIES_KABUTO", "Kabuto"), - ("SPECIES_KABUTOPS", "Kabutops"), - ("SPECIES_AERODACTYL", "Aerodactyl"), - ("SPECIES_SNORLAX", "Snorlax"), - ("SPECIES_ARTICUNO", "Articuno"), - ("SPECIES_ZAPDOS", "Zapdos"), - ("SPECIES_MOLTRES", "Moltres"), - ("SPECIES_DRATINI", "Dratini"), - ("SPECIES_DRAGONAIR", "Dragonair"), - ("SPECIES_DRAGONITE", "Dragonite"), - ("SPECIES_MEWTWO", "Mewtwo"), - ("SPECIES_MEW", "Mew"), - ("SPECIES_CHIKORITA", "Chikorita"), - ("SPECIES_BAYLEEF", "Bayleaf"), - ("SPECIES_MEGANIUM", "Meganium"), - ("SPECIES_CYNDAQUIL", "Cindaquil"), - ("SPECIES_QUILAVA", "Quilava"), - ("SPECIES_TYPHLOSION", "Typhlosion"), - ("SPECIES_TOTODILE", "Totodile"), - ("SPECIES_CROCONAW", "Croconaw"), - ("SPECIES_FERALIGATR", "Feraligatr"), - ("SPECIES_SENTRET", "Sentret"), - ("SPECIES_FURRET", "Furret"), - ("SPECIES_HOOTHOOT", "Hoothoot"), - ("SPECIES_NOCTOWL", "Noctowl"), - ("SPECIES_LEDYBA", "Ledyba"), - ("SPECIES_LEDIAN", "Ledian"), - ("SPECIES_SPINARAK", "Spinarak"), - ("SPECIES_ARIADOS", "Ariados"), - ("SPECIES_CROBAT", "Crobat"), - ("SPECIES_CHINCHOU", "Chinchou"), - ("SPECIES_LANTURN", "Lanturn"), - ("SPECIES_PICHU", "Pichu"), - ("SPECIES_CLEFFA", "Cleffa"), - ("SPECIES_IGGLYBUFF", "Igglybuff"), - ("SPECIES_TOGEPI", "Togepi"), - ("SPECIES_TOGETIC", "Togetic"), - ("SPECIES_NATU", "Natu"), - ("SPECIES_XATU", "Xatu"), - ("SPECIES_MAREEP", "Mareep"), - ("SPECIES_FLAAFFY", "Flaafy"), - ("SPECIES_AMPHAROS", "Ampharos"), - ("SPECIES_BELLOSSOM", "Bellossom"), - ("SPECIES_MARILL", "Marill"), - ("SPECIES_AZUMARILL", "Azumarill"), - ("SPECIES_SUDOWOODO", "Sudowoodo"), - ("SPECIES_POLITOED", "Politoed"), - ("SPECIES_HOPPIP", "Hoppip"), - ("SPECIES_SKIPLOOM", "Skiploom"), - ("SPECIES_JUMPLUFF", "Jumpluff"), - ("SPECIES_AIPOM", "Aipom"), - ("SPECIES_SUNKERN", "Sunkern"), - ("SPECIES_SUNFLORA", "Sunflora"), - ("SPECIES_YANMA", "Yanma"), - ("SPECIES_WOOPER", "Wooper"), - ("SPECIES_QUAGSIRE", "Quagsire"), - ("SPECIES_ESPEON", "Espeon"), - ("SPECIES_UMBREON", "Umbreon"), - ("SPECIES_MURKROW", "Murkrow"), - ("SPECIES_SLOWKING", "Slowking"), - ("SPECIES_MISDREAVUS", "Misdreavus"), - ("SPECIES_UNOWN", "Unown"), - ("SPECIES_WOBBUFFET", "Wobbuffet"), - ("SPECIES_GIRAFARIG", "Girafarig"), - ("SPECIES_PINECO", "Pineco"), - ("SPECIES_FORRETRESS", "Forretress"), - ("SPECIES_DUNSPARCE", "Dunsparce"), - ("SPECIES_GLIGAR", "Gligar"), - ("SPECIES_STEELIX", "Steelix"), - ("SPECIES_SNUBBULL", "Snubbull"), - ("SPECIES_GRANBULL", "Granbull"), - ("SPECIES_QWILFISH", "Qwilfish"), - ("SPECIES_SCIZOR", "Scizor"), - ("SPECIES_SHUCKLE", "Shuckle"), - ("SPECIES_HERACROSS", "Heracross"), - ("SPECIES_SNEASEL", "Sneasel"), - ("SPECIES_TEDDIURSA", "Teddiursa"), - ("SPECIES_URSARING", "Ursaring"), - ("SPECIES_SLUGMA", "Slugma"), - ("SPECIES_MAGCARGO", "Magcargo"), - ("SPECIES_SWINUB", "Swinub"), - ("SPECIES_PILOSWINE", "Piloswine"), - ("SPECIES_CORSOLA", "Corsola"), - ("SPECIES_REMORAID", "Remoraid"), - ("SPECIES_OCTILLERY", "Octillery"), - ("SPECIES_DELIBIRD", "Delibird"), - ("SPECIES_MANTINE", "Mantine"), - ("SPECIES_SKARMORY", "Skarmory"), - ("SPECIES_HOUNDOUR", "Houndour"), - ("SPECIES_HOUNDOOM", "Houndoom"), - ("SPECIES_KINGDRA", "Kingdra"), - ("SPECIES_PHANPY", "Phanpy"), - ("SPECIES_DONPHAN", "Donphan"), - ("SPECIES_PORYGON2", "Porygon2"), - ("SPECIES_STANTLER", "Stantler"), - ("SPECIES_SMEARGLE", "Smeargle"), - ("SPECIES_TYROGUE", "Tyrogue"), - ("SPECIES_HITMONTOP", "Hitmontop"), - ("SPECIES_SMOOCHUM", "Smoochum"), - ("SPECIES_ELEKID", "Elekid"), - ("SPECIES_MAGBY", "Magby"), - ("SPECIES_MILTANK", "Miltank"), - ("SPECIES_BLISSEY", "Blissey"), - ("SPECIES_RAIKOU", "Raikou"), - ("SPECIES_ENTEI", "Entei"), - ("SPECIES_SUICUNE", "Suicune"), - ("SPECIES_LARVITAR", "Larvitar"), - ("SPECIES_PUPITAR", "Pupitar"), - ("SPECIES_TYRANITAR", "Tyranitar"), - ("SPECIES_LUGIA", "Lugia"), - ("SPECIES_HO_OH", "Ho-oh"), - ("SPECIES_CELEBI", "Celebi"), - ("SPECIES_TREECKO", "Treecko"), - ("SPECIES_GROVYLE", "Grovyle"), - ("SPECIES_SCEPTILE", "Sceptile"), - ("SPECIES_TORCHIC", "Torchic"), - ("SPECIES_COMBUSKEN", "Combusken"), - ("SPECIES_BLAZIKEN", "Blaziken"), - ("SPECIES_MUDKIP", "Mudkip"), - ("SPECIES_MARSHTOMP", "Marshtomp"), - ("SPECIES_SWAMPERT", "Swampert"), - ("SPECIES_POOCHYENA", "Poochyena"), - ("SPECIES_MIGHTYENA", "Mightyena"), - ("SPECIES_ZIGZAGOON", "Zigzagoon"), - ("SPECIES_LINOONE", "Linoon"), - ("SPECIES_WURMPLE", "Wurmple"), - ("SPECIES_SILCOON", "Silcoon"), - ("SPECIES_BEAUTIFLY", "Beautifly"), - ("SPECIES_CASCOON", "Cascoon"), - ("SPECIES_DUSTOX", "Dustox"), - ("SPECIES_LOTAD", "Lotad"), - ("SPECIES_LOMBRE", "Lombre"), - ("SPECIES_LUDICOLO", "Ludicolo"), - ("SPECIES_SEEDOT", "Seedot"), - ("SPECIES_NUZLEAF", "Nuzleaf"), - ("SPECIES_SHIFTRY", "Shiftry"), - ("SPECIES_NINCADA", "Nincada"), - ("SPECIES_NINJASK", "Ninjask"), - ("SPECIES_SHEDINJA", "Shedinja"), - ("SPECIES_TAILLOW", "Taillow"), - ("SPECIES_SWELLOW", "Swellow"), - ("SPECIES_SHROOMISH", "Shroomish"), - ("SPECIES_BRELOOM", "Breloom"), - ("SPECIES_SPINDA", "Spinda"), - ("SPECIES_WINGULL", "Wingull"), - ("SPECIES_PELIPPER", "Pelipper"), - ("SPECIES_SURSKIT", "Surskit"), - ("SPECIES_MASQUERAIN", "Masquerain"), - ("SPECIES_WAILMER", "Wailmer"), - ("SPECIES_WAILORD", "Wailord"), - ("SPECIES_SKITTY", "Skitty"), - ("SPECIES_DELCATTY", "Delcatty"), - ("SPECIES_KECLEON", "Kecleon"), - ("SPECIES_BALTOY", "Baltoy"), - ("SPECIES_CLAYDOL", "Claydol"), - ("SPECIES_NOSEPASS", "Nosepass"), - ("SPECIES_TORKOAL", "Torkoal"), - ("SPECIES_SABLEYE", "Sableye"), - ("SPECIES_BARBOACH", "Barboach"), - ("SPECIES_WHISCASH", "Whiscash"), - ("SPECIES_LUVDISC", "Luvdisc"), - ("SPECIES_CORPHISH", "Corphish"), - ("SPECIES_CRAWDAUNT", "Crawdaunt"), - ("SPECIES_FEEBAS", "Feebas"), - ("SPECIES_MILOTIC", "Milotic"), - ("SPECIES_CARVANHA", "Carvanha"), - ("SPECIES_SHARPEDO", "Sharpedo"), - ("SPECIES_TRAPINCH", "Trapinch"), - ("SPECIES_VIBRAVA", "Vibrava"), - ("SPECIES_FLYGON", "Flygon"), - ("SPECIES_MAKUHITA", "Makuhita"), - ("SPECIES_HARIYAMA", "Hariyama"), - ("SPECIES_ELECTRIKE", "Electrike"), - ("SPECIES_MANECTRIC", "Manectric"), - ("SPECIES_NUMEL", "Numel"), - ("SPECIES_CAMERUPT", "Camerupt"), - ("SPECIES_SPHEAL", "Spheal"), - ("SPECIES_SEALEO", "Sealeo"), - ("SPECIES_WALREIN", "Walrein"), - ("SPECIES_CACNEA", "Cacnea"), - ("SPECIES_CACTURNE", "Cacturne"), - ("SPECIES_SNORUNT", "Snorunt"), - ("SPECIES_GLALIE", "Glalie"), - ("SPECIES_LUNATONE", "Lunatone"), - ("SPECIES_SOLROCK", "Solrock"), - ("SPECIES_AZURILL", "Azurill"), - ("SPECIES_SPOINK", "Spoink"), - ("SPECIES_GRUMPIG", "Grumpig"), - ("SPECIES_PLUSLE", "Plusle"), - ("SPECIES_MINUN", "Minun"), - ("SPECIES_MAWILE", "Mawile"), - ("SPECIES_MEDITITE", "Meditite"), - ("SPECIES_MEDICHAM", "Medicham"), - ("SPECIES_SWABLU", "Swablu"), - ("SPECIES_ALTARIA", "Altaria"), - ("SPECIES_WYNAUT", "Wynaut"), - ("SPECIES_DUSKULL", "Duskull"), - ("SPECIES_DUSCLOPS", "Dusclops"), - ("SPECIES_ROSELIA", "Roselia"), - ("SPECIES_SLAKOTH", "Slakoth"), - ("SPECIES_VIGOROTH", "Vigoroth"), - ("SPECIES_SLAKING", "Slaking"), - ("SPECIES_GULPIN", "Gulpin"), - ("SPECIES_SWALOT", "Swalot"), - ("SPECIES_TROPIUS", "Tropius"), - ("SPECIES_WHISMUR", "Whismur"), - ("SPECIES_LOUDRED", "Loudred"), - ("SPECIES_EXPLOUD", "Exploud"), - ("SPECIES_CLAMPERL", "Clamperl"), - ("SPECIES_HUNTAIL", "Huntail"), - ("SPECIES_GOREBYSS", "Gorebyss"), - ("SPECIES_ABSOL", "Absol"), - ("SPECIES_SHUPPET", "Shuppet"), - ("SPECIES_BANETTE", "Banette"), - ("SPECIES_SEVIPER", "Seviper"), - ("SPECIES_ZANGOOSE", "Zangoose"), - ("SPECIES_RELICANTH", "Relicanth"), - ("SPECIES_ARON", "Aron"), - ("SPECIES_LAIRON", "Lairon"), - ("SPECIES_AGGRON", "Aggron"), - ("SPECIES_CASTFORM", "Castform"), - ("SPECIES_VOLBEAT", "Volbeat"), - ("SPECIES_ILLUMISE", "Illumise"), - ("SPECIES_LILEEP", "Lileep"), - ("SPECIES_CRADILY", "Cradily"), - ("SPECIES_ANORITH", "Anorith"), - ("SPECIES_ARMALDO", "Armaldo"), - ("SPECIES_RALTS", "Ralts"), - ("SPECIES_KIRLIA", "Kirlia"), - ("SPECIES_GARDEVOIR", "Gardevoir"), - ("SPECIES_BAGON", "Bagon"), - ("SPECIES_SHELGON", "Shelgon"), - ("SPECIES_SALAMENCE", "Salamence"), - ("SPECIES_BELDUM", "Beldum"), - ("SPECIES_METANG", "Metang"), - ("SPECIES_METAGROSS", "Metagross"), - ("SPECIES_REGIROCK", "Regirock"), - ("SPECIES_REGICE", "Regice"), - ("SPECIES_REGISTEEL", "Registeel"), - ("SPECIES_KYOGRE", "Kyogre"), - ("SPECIES_GROUDON", "Groudon"), - ("SPECIES_RAYQUAZA", "Rayquaza"), - ("SPECIES_LATIAS", "Latias"), - ("SPECIES_LATIOS", "Latios"), - ("SPECIES_JIRACHI", "Jirachi"), - ("SPECIES_DEOXYS", "Deoxys"), - ("SPECIES_CHIMECHO", "Chimecho") + all_species: List[Tuple[str, str, int]] = [ + ("SPECIES_BULBASAUR", "Bulbasaur", 1), + ("SPECIES_IVYSAUR", "Ivysaur", 2), + ("SPECIES_VENUSAUR", "Venusaur", 3), + ("SPECIES_CHARMANDER", "Charmander", 4), + ("SPECIES_CHARMELEON", "Charmeleon", 5), + ("SPECIES_CHARIZARD", "Charizard", 6), + ("SPECIES_SQUIRTLE", "Squirtle", 7), + ("SPECIES_WARTORTLE", "Wartortle", 8), + ("SPECIES_BLASTOISE", "Blastoise", 9), + ("SPECIES_CATERPIE", "Caterpie", 10), + ("SPECIES_METAPOD", "Metapod", 11), + ("SPECIES_BUTTERFREE", "Butterfree", 12), + ("SPECIES_WEEDLE", "Weedle", 13), + ("SPECIES_KAKUNA", "Kakuna", 14), + ("SPECIES_BEEDRILL", "Beedrill", 15), + ("SPECIES_PIDGEY", "Pidgey", 16), + ("SPECIES_PIDGEOTTO", "Pidgeotto", 17), + ("SPECIES_PIDGEOT", "Pidgeot", 18), + ("SPECIES_RATTATA", "Rattata", 19), + ("SPECIES_RATICATE", "Raticate", 20), + ("SPECIES_SPEAROW", "Spearow", 21), + ("SPECIES_FEAROW", "Fearow", 22), + ("SPECIES_EKANS", "Ekans", 23), + ("SPECIES_ARBOK", "Arbok", 24), + ("SPECIES_PIKACHU", "Pikachu", 25), + ("SPECIES_RAICHU", "Raichu", 26), + ("SPECIES_SANDSHREW", "Sandshrew", 27), + ("SPECIES_SANDSLASH", "Sandslash", 28), + ("SPECIES_NIDORAN_F", "Nidoran Female", 29), + ("SPECIES_NIDORINA", "Nidorina", 30), + ("SPECIES_NIDOQUEEN", "Nidoqueen", 31), + ("SPECIES_NIDORAN_M", "Nidoran Male", 32), + ("SPECIES_NIDORINO", "Nidorino", 33), + ("SPECIES_NIDOKING", "Nidoking", 34), + ("SPECIES_CLEFAIRY", "Clefairy", 35), + ("SPECIES_CLEFABLE", "Clefable", 36), + ("SPECIES_VULPIX", "Vulpix", 37), + ("SPECIES_NINETALES", "Ninetales", 38), + ("SPECIES_JIGGLYPUFF", "Jigglypuff", 39), + ("SPECIES_WIGGLYTUFF", "Wigglytuff", 40), + ("SPECIES_ZUBAT", "Zubat", 41), + ("SPECIES_GOLBAT", "Golbat", 42), + ("SPECIES_ODDISH", "Oddish", 43), + ("SPECIES_GLOOM", "Gloom", 44), + ("SPECIES_VILEPLUME", "Vileplume", 45), + ("SPECIES_PARAS", "Paras", 46), + ("SPECIES_PARASECT", "Parasect", 47), + ("SPECIES_VENONAT", "Venonat", 48), + ("SPECIES_VENOMOTH", "Venomoth", 49), + ("SPECIES_DIGLETT", "Diglett", 50), + ("SPECIES_DUGTRIO", "Dugtrio", 51), + ("SPECIES_MEOWTH", "Meowth", 52), + ("SPECIES_PERSIAN", "Persian", 53), + ("SPECIES_PSYDUCK", "Psyduck", 54), + ("SPECIES_GOLDUCK", "Golduck", 55), + ("SPECIES_MANKEY", "Mankey", 56), + ("SPECIES_PRIMEAPE", "Primeape", 57), + ("SPECIES_GROWLITHE", "Growlithe", 58), + ("SPECIES_ARCANINE", "Arcanine", 59), + ("SPECIES_POLIWAG", "Poliwag", 60), + ("SPECIES_POLIWHIRL", "Poliwhirl", 61), + ("SPECIES_POLIWRATH", "Poliwrath", 62), + ("SPECIES_ABRA", "Abra", 63), + ("SPECIES_KADABRA", "Kadabra", 64), + ("SPECIES_ALAKAZAM", "Alakazam", 65), + ("SPECIES_MACHOP", "Machop", 66), + ("SPECIES_MACHOKE", "Machoke", 67), + ("SPECIES_MACHAMP", "Machamp", 68), + ("SPECIES_BELLSPROUT", "Bellsprout", 69), + ("SPECIES_WEEPINBELL", "Weepinbell", 70), + ("SPECIES_VICTREEBEL", "Victreebel", 71), + ("SPECIES_TENTACOOL", "Tentacool", 72), + ("SPECIES_TENTACRUEL", "Tentacruel", 73), + ("SPECIES_GEODUDE", "Geodude", 74), + ("SPECIES_GRAVELER", "Graveler", 75), + ("SPECIES_GOLEM", "Golem", 76), + ("SPECIES_PONYTA", "Ponyta", 77), + ("SPECIES_RAPIDASH", "Rapidash", 78), + ("SPECIES_SLOWPOKE", "Slowpoke", 79), + ("SPECIES_SLOWBRO", "Slowbro", 80), + ("SPECIES_MAGNEMITE", "Magnemite", 81), + ("SPECIES_MAGNETON", "Magneton", 82), + ("SPECIES_FARFETCHD", "Farfetch'd", 83), + ("SPECIES_DODUO", "Doduo", 84), + ("SPECIES_DODRIO", "Dodrio", 85), + ("SPECIES_SEEL", "Seel", 86), + ("SPECIES_DEWGONG", "Dewgong", 87), + ("SPECIES_GRIMER", "Grimer", 88), + ("SPECIES_MUK", "Muk", 89), + ("SPECIES_SHELLDER", "Shellder", 90), + ("SPECIES_CLOYSTER", "Cloyster", 91), + ("SPECIES_GASTLY", "Gastly", 92), + ("SPECIES_HAUNTER", "Haunter", 93), + ("SPECIES_GENGAR", "Gengar", 94), + ("SPECIES_ONIX", "Onix", 95), + ("SPECIES_DROWZEE", "Drowzee", 96), + ("SPECIES_HYPNO", "Hypno", 97), + ("SPECIES_KRABBY", "Krabby", 98), + ("SPECIES_KINGLER", "Kingler", 99), + ("SPECIES_VOLTORB", "Voltorb", 100), + ("SPECIES_ELECTRODE", "Electrode", 101), + ("SPECIES_EXEGGCUTE", "Exeggcute", 102), + ("SPECIES_EXEGGUTOR", "Exeggutor", 103), + ("SPECIES_CUBONE", "Cubone", 104), + ("SPECIES_MAROWAK", "Marowak", 105), + ("SPECIES_HITMONLEE", "Hitmonlee", 106), + ("SPECIES_HITMONCHAN", "Hitmonchan", 107), + ("SPECIES_LICKITUNG", "Lickitung", 108), + ("SPECIES_KOFFING", "Koffing", 109), + ("SPECIES_WEEZING", "Weezing", 110), + ("SPECIES_RHYHORN", "Rhyhorn", 111), + ("SPECIES_RHYDON", "Rhydon", 112), + ("SPECIES_CHANSEY", "Chansey", 113), + ("SPECIES_TANGELA", "Tangela", 114), + ("SPECIES_KANGASKHAN", "Kangaskhan", 115), + ("SPECIES_HORSEA", "Horsea", 116), + ("SPECIES_SEADRA", "Seadra", 117), + ("SPECIES_GOLDEEN", "Goldeen", 118), + ("SPECIES_SEAKING", "Seaking", 119), + ("SPECIES_STARYU", "Staryu", 120), + ("SPECIES_STARMIE", "Starmie", 121), + ("SPECIES_MR_MIME", "Mr. Mime", 122), + ("SPECIES_SCYTHER", "Scyther", 123), + ("SPECIES_JYNX", "Jynx", 124), + ("SPECIES_ELECTABUZZ", "Electabuzz", 125), + ("SPECIES_MAGMAR", "Magmar", 126), + ("SPECIES_PINSIR", "Pinsir", 127), + ("SPECIES_TAUROS", "Tauros", 128), + ("SPECIES_MAGIKARP", "Magikarp", 129), + ("SPECIES_GYARADOS", "Gyarados", 130), + ("SPECIES_LAPRAS", "Lapras", 131), + ("SPECIES_DITTO", "Ditto", 132), + ("SPECIES_EEVEE", "Eevee", 133), + ("SPECIES_VAPOREON", "Vaporeon", 134), + ("SPECIES_JOLTEON", "Jolteon", 135), + ("SPECIES_FLAREON", "Flareon", 136), + ("SPECIES_PORYGON", "Porygon", 137), + ("SPECIES_OMANYTE", "Omanyte", 138), + ("SPECIES_OMASTAR", "Omastar", 139), + ("SPECIES_KABUTO", "Kabuto", 140), + ("SPECIES_KABUTOPS", "Kabutops", 141), + ("SPECIES_AERODACTYL", "Aerodactyl", 142), + ("SPECIES_SNORLAX", "Snorlax", 143), + ("SPECIES_ARTICUNO", "Articuno", 144), + ("SPECIES_ZAPDOS", "Zapdos", 145), + ("SPECIES_MOLTRES", "Moltres", 146), + ("SPECIES_DRATINI", "Dratini", 147), + ("SPECIES_DRAGONAIR", "Dragonair", 148), + ("SPECIES_DRAGONITE", "Dragonite", 149), + ("SPECIES_MEWTWO", "Mewtwo", 150), + ("SPECIES_MEW", "Mew", 151), + ("SPECIES_CHIKORITA", "Chikorita", 152), + ("SPECIES_BAYLEEF", "Bayleef", 153), + ("SPECIES_MEGANIUM", "Meganium", 154), + ("SPECIES_CYNDAQUIL", "Cyndaquil", 155), + ("SPECIES_QUILAVA", "Quilava", 156), + ("SPECIES_TYPHLOSION", "Typhlosion", 157), + ("SPECIES_TOTODILE", "Totodile", 158), + ("SPECIES_CROCONAW", "Croconaw", 159), + ("SPECIES_FERALIGATR", "Feraligatr", 160), + ("SPECIES_SENTRET", "Sentret", 161), + ("SPECIES_FURRET", "Furret", 162), + ("SPECIES_HOOTHOOT", "Hoothoot", 163), + ("SPECIES_NOCTOWL", "Noctowl", 164), + ("SPECIES_LEDYBA", "Ledyba", 165), + ("SPECIES_LEDIAN", "Ledian", 166), + ("SPECIES_SPINARAK", "Spinarak", 167), + ("SPECIES_ARIADOS", "Ariados", 168), + ("SPECIES_CROBAT", "Crobat", 169), + ("SPECIES_CHINCHOU", "Chinchou", 170), + ("SPECIES_LANTURN", "Lanturn", 171), + ("SPECIES_PICHU", "Pichu", 172), + ("SPECIES_CLEFFA", "Cleffa", 173), + ("SPECIES_IGGLYBUFF", "Igglybuff", 174), + ("SPECIES_TOGEPI", "Togepi", 175), + ("SPECIES_TOGETIC", "Togetic", 176), + ("SPECIES_NATU", "Natu", 177), + ("SPECIES_XATU", "Xatu", 178), + ("SPECIES_MAREEP", "Mareep", 179), + ("SPECIES_FLAAFFY", "Flaaffy", 180), + ("SPECIES_AMPHAROS", "Ampharos", 181), + ("SPECIES_BELLOSSOM", "Bellossom", 182), + ("SPECIES_MARILL", "Marill", 183), + ("SPECIES_AZUMARILL", "Azumarill", 184), + ("SPECIES_SUDOWOODO", "Sudowoodo", 185), + ("SPECIES_POLITOED", "Politoed", 186), + ("SPECIES_HOPPIP", "Hoppip", 187), + ("SPECIES_SKIPLOOM", "Skiploom", 188), + ("SPECIES_JUMPLUFF", "Jumpluff", 189), + ("SPECIES_AIPOM", "Aipom", 190), + ("SPECIES_SUNKERN", "Sunkern", 191), + ("SPECIES_SUNFLORA", "Sunflora", 192), + ("SPECIES_YANMA", "Yanma", 193), + ("SPECIES_WOOPER", "Wooper", 194), + ("SPECIES_QUAGSIRE", "Quagsire", 195), + ("SPECIES_ESPEON", "Espeon", 196), + ("SPECIES_UMBREON", "Umbreon", 197), + ("SPECIES_MURKROW", "Murkrow", 198), + ("SPECIES_SLOWKING", "Slowking", 199), + ("SPECIES_MISDREAVUS", "Misdreavus", 200), + ("SPECIES_UNOWN", "Unown", 201), + ("SPECIES_WOBBUFFET", "Wobbuffet", 202), + ("SPECIES_GIRAFARIG", "Girafarig", 203), + ("SPECIES_PINECO", "Pineco", 204), + ("SPECIES_FORRETRESS", "Forretress", 205), + ("SPECIES_DUNSPARCE", "Dunsparce", 206), + ("SPECIES_GLIGAR", "Gligar", 207), + ("SPECIES_STEELIX", "Steelix", 208), + ("SPECIES_SNUBBULL", "Snubbull", 209), + ("SPECIES_GRANBULL", "Granbull", 210), + ("SPECIES_QWILFISH", "Qwilfish", 211), + ("SPECIES_SCIZOR", "Scizor", 212), + ("SPECIES_SHUCKLE", "Shuckle", 213), + ("SPECIES_HERACROSS", "Heracross", 214), + ("SPECIES_SNEASEL", "Sneasel", 215), + ("SPECIES_TEDDIURSA", "Teddiursa", 216), + ("SPECIES_URSARING", "Ursaring", 217), + ("SPECIES_SLUGMA", "Slugma", 218), + ("SPECIES_MAGCARGO", "Magcargo", 219), + ("SPECIES_SWINUB", "Swinub", 220), + ("SPECIES_PILOSWINE", "Piloswine", 221), + ("SPECIES_CORSOLA", "Corsola", 222), + ("SPECIES_REMORAID", "Remoraid", 223), + ("SPECIES_OCTILLERY", "Octillery", 224), + ("SPECIES_DELIBIRD", "Delibird", 225), + ("SPECIES_MANTINE", "Mantine", 226), + ("SPECIES_SKARMORY", "Skarmory", 227), + ("SPECIES_HOUNDOUR", "Houndour", 228), + ("SPECIES_HOUNDOOM", "Houndoom", 229), + ("SPECIES_KINGDRA", "Kingdra", 230), + ("SPECIES_PHANPY", "Phanpy", 231), + ("SPECIES_DONPHAN", "Donphan", 232), + ("SPECIES_PORYGON2", "Porygon2", 233), + ("SPECIES_STANTLER", "Stantler", 234), + ("SPECIES_SMEARGLE", "Smeargle", 235), + ("SPECIES_TYROGUE", "Tyrogue", 236), + ("SPECIES_HITMONTOP", "Hitmontop", 237), + ("SPECIES_SMOOCHUM", "Smoochum", 238), + ("SPECIES_ELEKID", "Elekid", 239), + ("SPECIES_MAGBY", "Magby", 240), + ("SPECIES_MILTANK", "Miltank", 241), + ("SPECIES_BLISSEY", "Blissey", 242), + ("SPECIES_RAIKOU", "Raikou", 243), + ("SPECIES_ENTEI", "Entei", 244), + ("SPECIES_SUICUNE", "Suicune", 245), + ("SPECIES_LARVITAR", "Larvitar", 246), + ("SPECIES_PUPITAR", "Pupitar", 247), + ("SPECIES_TYRANITAR", "Tyranitar", 248), + ("SPECIES_LUGIA", "Lugia", 249), + ("SPECIES_HO_OH", "Ho-Oh", 250), + ("SPECIES_CELEBI", "Celebi", 251), + ("SPECIES_TREECKO", "Treecko", 252), + ("SPECIES_GROVYLE", "Grovyle", 253), + ("SPECIES_SCEPTILE", "Sceptile", 254), + ("SPECIES_TORCHIC", "Torchic", 255), + ("SPECIES_COMBUSKEN", "Combusken", 256), + ("SPECIES_BLAZIKEN", "Blaziken", 257), + ("SPECIES_MUDKIP", "Mudkip", 258), + ("SPECIES_MARSHTOMP", "Marshtomp", 259), + ("SPECIES_SWAMPERT", "Swampert", 260), + ("SPECIES_POOCHYENA", "Poochyena", 261), + ("SPECIES_MIGHTYENA", "Mightyena", 262), + ("SPECIES_ZIGZAGOON", "Zigzagoon", 263), + ("SPECIES_LINOONE", "Linoone", 264), + ("SPECIES_WURMPLE", "Wurmple", 265), + ("SPECIES_SILCOON", "Silcoon", 266), + ("SPECIES_BEAUTIFLY", "Beautifly", 267), + ("SPECIES_CASCOON", "Cascoon", 268), + ("SPECIES_DUSTOX", "Dustox", 269), + ("SPECIES_LOTAD", "Lotad", 270), + ("SPECIES_LOMBRE", "Lombre", 271), + ("SPECIES_LUDICOLO", "Ludicolo", 272), + ("SPECIES_SEEDOT", "Seedot", 273), + ("SPECIES_NUZLEAF", "Nuzleaf", 274), + ("SPECIES_SHIFTRY", "Shiftry", 275), + ("SPECIES_NINCADA", "Nincada", 290), + ("SPECIES_NINJASK", "Ninjask", 291), + ("SPECIES_SHEDINJA", "Shedinja", 292), + ("SPECIES_TAILLOW", "Taillow", 276), + ("SPECIES_SWELLOW", "Swellow", 277), + ("SPECIES_SHROOMISH", "Shroomish", 285), + ("SPECIES_BRELOOM", "Breloom", 286), + ("SPECIES_SPINDA", "Spinda", 327), + ("SPECIES_WINGULL", "Wingull", 278), + ("SPECIES_PELIPPER", "Pelipper", 279), + ("SPECIES_SURSKIT", "Surskit", 283), + ("SPECIES_MASQUERAIN", "Masquerain", 284), + ("SPECIES_WAILMER", "Wailmer", 320), + ("SPECIES_WAILORD", "Wailord", 321), + ("SPECIES_SKITTY", "Skitty", 300), + ("SPECIES_DELCATTY", "Delcatty", 301), + ("SPECIES_KECLEON", "Kecleon", 352), + ("SPECIES_BALTOY", "Baltoy", 343), + ("SPECIES_CLAYDOL", "Claydol", 344), + ("SPECIES_NOSEPASS", "Nosepass", 299), + ("SPECIES_TORKOAL", "Torkoal", 324), + ("SPECIES_SABLEYE", "Sableye", 302), + ("SPECIES_BARBOACH", "Barboach", 339), + ("SPECIES_WHISCASH", "Whiscash", 340), + ("SPECIES_LUVDISC", "Luvdisc", 370), + ("SPECIES_CORPHISH", "Corphish", 341), + ("SPECIES_CRAWDAUNT", "Crawdaunt", 342), + ("SPECIES_FEEBAS", "Feebas", 349), + ("SPECIES_MILOTIC", "Milotic", 350), + ("SPECIES_CARVANHA", "Carvanha", 318), + ("SPECIES_SHARPEDO", "Sharpedo", 319), + ("SPECIES_TRAPINCH", "Trapinch", 328), + ("SPECIES_VIBRAVA", "Vibrava", 329), + ("SPECIES_FLYGON", "Flygon", 330), + ("SPECIES_MAKUHITA", "Makuhita", 296), + ("SPECIES_HARIYAMA", "Hariyama", 297), + ("SPECIES_ELECTRIKE", "Electrike", 309), + ("SPECIES_MANECTRIC", "Manectric", 310), + ("SPECIES_NUMEL", "Numel", 322), + ("SPECIES_CAMERUPT", "Camerupt", 323), + ("SPECIES_SPHEAL", "Spheal", 363), + ("SPECIES_SEALEO", "Sealeo", 364), + ("SPECIES_WALREIN", "Walrein", 365), + ("SPECIES_CACNEA", "Cacnea", 331), + ("SPECIES_CACTURNE", "Cacturne", 332), + ("SPECIES_SNORUNT", "Snorunt", 361), + ("SPECIES_GLALIE", "Glalie", 362), + ("SPECIES_LUNATONE", "Lunatone", 337), + ("SPECIES_SOLROCK", "Solrock", 338), + ("SPECIES_AZURILL", "Azurill", 298), + ("SPECIES_SPOINK", "Spoink", 325), + ("SPECIES_GRUMPIG", "Grumpig", 326), + ("SPECIES_PLUSLE", "Plusle", 311), + ("SPECIES_MINUN", "Minun", 312), + ("SPECIES_MAWILE", "Mawile", 303), + ("SPECIES_MEDITITE", "Meditite", 307), + ("SPECIES_MEDICHAM", "Medicham", 308), + ("SPECIES_SWABLU", "Swablu", 333), + ("SPECIES_ALTARIA", "Altaria", 334), + ("SPECIES_WYNAUT", "Wynaut", 360), + ("SPECIES_DUSKULL", "Duskull", 355), + ("SPECIES_DUSCLOPS", "Dusclops", 356), + ("SPECIES_ROSELIA", "Roselia", 315), + ("SPECIES_SLAKOTH", "Slakoth", 287), + ("SPECIES_VIGOROTH", "Vigoroth", 288), + ("SPECIES_SLAKING", "Slaking", 289), + ("SPECIES_GULPIN", "Gulpin", 316), + ("SPECIES_SWALOT", "Swalot", 317), + ("SPECIES_TROPIUS", "Tropius", 357), + ("SPECIES_WHISMUR", "Whismur", 293), + ("SPECIES_LOUDRED", "Loudred", 294), + ("SPECIES_EXPLOUD", "Exploud", 295), + ("SPECIES_CLAMPERL", "Clamperl", 366), + ("SPECIES_HUNTAIL", "Huntail", 367), + ("SPECIES_GOREBYSS", "Gorebyss", 368), + ("SPECIES_ABSOL", "Absol", 359), + ("SPECIES_SHUPPET", "Shuppet", 353), + ("SPECIES_BANETTE", "Banette", 354), + ("SPECIES_SEVIPER", "Seviper", 336), + ("SPECIES_ZANGOOSE", "Zangoose", 335), + ("SPECIES_RELICANTH", "Relicanth", 369), + ("SPECIES_ARON", "Aron", 304), + ("SPECIES_LAIRON", "Lairon", 305), + ("SPECIES_AGGRON", "Aggron", 306), + ("SPECIES_CASTFORM", "Castform", 351), + ("SPECIES_VOLBEAT", "Volbeat", 313), + ("SPECIES_ILLUMISE", "Illumise", 314), + ("SPECIES_LILEEP", "Lileep", 345), + ("SPECIES_CRADILY", "Cradily", 346), + ("SPECIES_ANORITH", "Anorith", 347), + ("SPECIES_ARMALDO", "Armaldo", 348), + ("SPECIES_RALTS", "Ralts", 280), + ("SPECIES_KIRLIA", "Kirlia", 281), + ("SPECIES_GARDEVOIR", "Gardevoir", 282), + ("SPECIES_BAGON", "Bagon", 371), + ("SPECIES_SHELGON", "Shelgon", 372), + ("SPECIES_SALAMENCE", "Salamence", 373), + ("SPECIES_BELDUM", "Beldum", 374), + ("SPECIES_METANG", "Metang", 375), + ("SPECIES_METAGROSS", "Metagross", 376), + ("SPECIES_REGIROCK", "Regirock", 377), + ("SPECIES_REGICE", "Regice", 378), + ("SPECIES_REGISTEEL", "Registeel", 379), + ("SPECIES_KYOGRE", "Kyogre", 382), + ("SPECIES_GROUDON", "Groudon", 383), + ("SPECIES_RAYQUAZA", "Rayquaza", 384), + ("SPECIES_LATIAS", "Latias", 380), + ("SPECIES_LATIOS", "Latios", 381), + ("SPECIES_JIRACHI", "Jirachi", 385), + ("SPECIES_DEOXYS", "Deoxys", 386), + ("SPECIES_CHIMECHO", "Chimecho", 358), ] - species_list: List[SpeciesData] = [] max_species_id = 0 - for species_name, species_label in all_species: + for species_name, species_label, species_dex_number in all_species: species_id = data.constants[species_name] max_species_id = max(species_id, max_species_id) species_data = extracted_data["species"][species_id] learnset = [LearnsetMove(item["level"], item["move_id"]) for item in species_data["learnset"]["moves"]] - species_list.append(SpeciesData( + data.species[species_id] = SpeciesData( species_name, species_label, species_id, + species_dex_number, BaseStats( species_data["base_stats"][0], species_data["base_stats"][1], @@ -827,27 +918,52 @@ def _init() -> None: ) for evolution_json in species_data["evolutions"]], None, species_data["catch_rate"], + species_data["friendship"], learnset, int(species_data["tmhm_learnset"], 16), - species_data["learnset"]["rom_address"], - species_data["rom_address"] - )) - - data.species = [None for i in range(max_species_id + 1)] + species_data["learnset"]["address"], + species_data["address"] + ) - for species_data in species_list: - data.species[species_data.species_id] = species_data + for species in data.species.values(): + for evolution in species.evolutions: + data.species[evolution.species_id].pre_evolution = species.species_id + + # Replace default item for dex entry locations based on evo stage of species + evo_stage_to_ball_map = { + 0: data.constants["ITEM_POKE_BALL"], + 1: data.constants["ITEM_GREAT_BALL"], + 2: data.constants["ITEM_ULTRA_BALL"], + } + for species in data.species.values(): + evo_stage = 0 + pre_evolution = species.pre_evolution + while pre_evolution is not None: + evo_stage += 1 + pre_evolution = data.species[pre_evolution].pre_evolution + + dex_location_name = f"POKEDEX_REWARD_{str(species.national_dex_number).zfill(3)}" + data.locations[dex_location_name] = LocationData( + data.locations[dex_location_name].name, + data.locations[dex_location_name].label, + data.locations[dex_location_name].parent_region, + evo_stage_to_ball_map[evo_stage], + data.locations[dex_location_name].address, + data.locations[dex_location_name].flag, + data.locations[dex_location_name].tags + ) - for species in data.species: - if species is not None: - for evolution in species.evolutions: - data.species[evolution.species_id].pre_evolution = species.species_id + # Create legendary encounter data + for legendary_encounter_json in extracted_data["legendary_encounters"]: + data.legendary_encounters.append(MiscPokemonData( + legendary_encounter_json["species"], + legendary_encounter_json["address"] + )) - # Create static encounter data - for static_encounter_json in extracted_data["static_encounters"]: - data.static_encounters.append(StaticEncounterData( - static_encounter_json["species"], - static_encounter_json["rom_address"] + for misc_pokemon_json in extracted_data["misc_pokemon"]: + data.misc_pokemon.append(MiscPokemonData( + misc_pokemon_json["species"], + misc_pokemon_json["address"] )) # TM moves @@ -868,7 +984,7 @@ def _init() -> None: ("ABILITY_WATER_ABSORB", "Water Absorb"), ("ABILITY_OBLIVIOUS", "Oblivious"), ("ABILITY_CLOUD_NINE", "Cloud Nine"), - ("ABILITY_COMPOUND_EYES", "Compound Eyes"), + ("ABILITY_COMPOUND_EYES", "Compoundeyes"), ("ABILITY_INSOMNIA", "Insomnia"), ("ABILITY_COLOR_CHANGE", "Color Change"), ("ABILITY_IMMUNITY", "Immunity"), @@ -885,7 +1001,7 @@ def _init() -> None: ("ABILITY_SYNCHRONIZE", "Synchronize"), ("ABILITY_CLEAR_BODY", "Clear Body"), ("ABILITY_NATURAL_CURE", "Natural Cure"), - ("ABILITY_LIGHTNING_ROD", "Lightning Rod"), + ("ABILITY_LIGHTNING_ROD", "Lightningrod"), ("ABILITY_SERENE_GRACE", "Serene Grace"), ("ABILITY_SWIFT_SWIM", "Swift Swim"), ("ABILITY_CHLOROPHYLL", "Chlorophyll"), @@ -934,36 +1050,362 @@ def _init() -> None: ("ABILITY_AIR_LOCK", "Air Lock") ]] - # Create map data - for map_name, map_json in extracted_data["maps"].items(): - land_encounters = None - water_encounters = None - fishing_encounters = None - - if map_json["land_encounters"] is not None: - land_encounters = EncounterTableData( - map_json["land_encounters"]["encounter_slots"], - map_json["land_encounters"]["rom_address"] - ) - if map_json["water_encounters"] is not None: - water_encounters = EncounterTableData( - map_json["water_encounters"]["encounter_slots"], - map_json["water_encounters"]["rom_address"] - ) - if map_json["fishing_encounters"] is not None: - fishing_encounters = EncounterTableData( - map_json["fishing_encounters"]["encounter_slots"], - map_json["fishing_encounters"]["rom_address"] - ) - - data.maps.append(MapData( - map_name, - land_encounters, - water_encounters, - fishing_encounters - )) - - data.maps.sort(key=lambda map: map.name) + # Move labels + data.move_labels = {r: data.constants[l] for l, r in [ + ("MOVE_POUND", "Pound"), + ("MOVE_KARATE_CHOP", "Karate Chop"), + ("MOVE_DOUBLE_SLAP", "Doubleslap"), + ("MOVE_COMET_PUNCH", "Comet Punch"), + ("MOVE_MEGA_PUNCH", "Mega Punch"), + ("MOVE_PAY_DAY", "Pay Day"), + ("MOVE_FIRE_PUNCH", "Fire Punch"), + ("MOVE_ICE_PUNCH", "Ice Punch"), + ("MOVE_THUNDER_PUNCH", "Thunderpunch"), + ("MOVE_SCRATCH", "Scratch"), + ("MOVE_VICE_GRIP", "Vicegrip"), + ("MOVE_GUILLOTINE", "Guillotine"), + ("MOVE_RAZOR_WIND", "Razor Wind"), + ("MOVE_SWORDS_DANCE", "Swords Dance"), + ("MOVE_CUT", "Cut"), + ("MOVE_GUST", "Gust"), + ("MOVE_WING_ATTACK", "Wing Attack"), + ("MOVE_WHIRLWIND", "Whirlwind"), + ("MOVE_FLY", "Fly"), + ("MOVE_BIND", "Bind"), + ("MOVE_SLAM", "Slam"), + ("MOVE_VINE_WHIP", "Vine Whip"), + ("MOVE_STOMP", "Stomp"), + ("MOVE_DOUBLE_KICK", "Double Kick"), + ("MOVE_MEGA_KICK", "Mega Kick"), + ("MOVE_JUMP_KICK", "Jump Kick"), + ("MOVE_ROLLING_KICK", "Rolling Kick"), + ("MOVE_SAND_ATTACK", "Sand-Attack"), + ("MOVE_HEADBUTT", "Headbutt"), + ("MOVE_HORN_ATTACK", "Horn Attack"), + ("MOVE_FURY_ATTACK", "Fury Attack"), + ("MOVE_HORN_DRILL", "Horn Drill"), + ("MOVE_TACKLE", "Tackle"), + ("MOVE_BODY_SLAM", "Body Slam"), + ("MOVE_WRAP", "Wrap"), + ("MOVE_TAKE_DOWN", "Take Down"), + ("MOVE_THRASH", "Thrash"), + ("MOVE_DOUBLE_EDGE", "Double-Edge"), + ("MOVE_TAIL_WHIP", "Tail Whip"), + ("MOVE_POISON_STING", "Poison Sting"), + ("MOVE_TWINEEDLE", "Twineedle"), + ("MOVE_PIN_MISSILE", "Pin Missile"), + ("MOVE_LEER", "Leer"), + ("MOVE_BITE", "Bite"), + ("MOVE_GROWL", "Growl"), + ("MOVE_ROAR", "Roar"), + ("MOVE_SING", "Sing"), + ("MOVE_SUPERSONIC", "Supersonic"), + ("MOVE_SONIC_BOOM", "Sonicboom"), + ("MOVE_DISABLE", "Disable"), + ("MOVE_ACID", "Acid"), + ("MOVE_EMBER", "Ember"), + ("MOVE_FLAMETHROWER", "Flamethrower"), + ("MOVE_MIST", "Mist"), + ("MOVE_WATER_GUN", "Water Gun"), + ("MOVE_HYDRO_PUMP", "Hydro Pump"), + ("MOVE_SURF", "Surf"), + ("MOVE_ICE_BEAM", "Ice Beam"), + ("MOVE_BLIZZARD", "Blizzard"), + ("MOVE_PSYBEAM", "Psybeam"), + ("MOVE_BUBBLE_BEAM", "Bubblebeam"), + ("MOVE_AURORA_BEAM", "Aurora Beam"), + ("MOVE_HYPER_BEAM", "Hyper Beam"), + ("MOVE_PECK", "Peck"), + ("MOVE_DRILL_PECK", "Drill Peck"), + ("MOVE_SUBMISSION", "Submission"), + ("MOVE_LOW_KICK", "Low Kick"), + ("MOVE_COUNTER", "Counter"), + ("MOVE_SEISMIC_TOSS", "Seismic Toss"), + ("MOVE_STRENGTH", "Strength"), + ("MOVE_ABSORB", "Absorb"), + ("MOVE_MEGA_DRAIN", "Mega Drain"), + ("MOVE_LEECH_SEED", "Leech Seed"), + ("MOVE_GROWTH", "Growth"), + ("MOVE_RAZOR_LEAF", "Razor Leaf"), + ("MOVE_SOLAR_BEAM", "Solarbeam"), + ("MOVE_POISON_POWDER", "Poisonpowder"), + ("MOVE_STUN_SPORE", "Stun Spore"), + ("MOVE_SLEEP_POWDER", "Sleep Powder"), + ("MOVE_PETAL_DANCE", "Petal Dance"), + ("MOVE_STRING_SHOT", "String Shot"), + ("MOVE_DRAGON_RAGE", "Dragon Rage"), + ("MOVE_FIRE_SPIN", "Fire Spin"), + ("MOVE_THUNDER_SHOCK", "Thundershock"), + ("MOVE_THUNDERBOLT", "Thunderbolt"), + ("MOVE_THUNDER_WAVE", "Thunder Wave"), + ("MOVE_THUNDER", "Thunder"), + ("MOVE_ROCK_THROW", "Rock Throw"), + ("MOVE_EARTHQUAKE", "Earthquake"), + ("MOVE_FISSURE", "Fissure"), + ("MOVE_DIG", "Dig"), + ("MOVE_TOXIC", "Toxic"), + ("MOVE_CONFUSION", "Confusion"), + ("MOVE_PSYCHIC", "Psychic"), + ("MOVE_HYPNOSIS", "Hypnosis"), + ("MOVE_MEDITATE", "Meditate"), + ("MOVE_AGILITY", "Agility"), + ("MOVE_QUICK_ATTACK", "Quick Attack"), + ("MOVE_RAGE", "Rage"), + ("MOVE_TELEPORT", "Teleport"), + ("MOVE_NIGHT_SHADE", "Night Shade"), + ("MOVE_MIMIC", "Mimic"), + ("MOVE_SCREECH", "Screech"), + ("MOVE_DOUBLE_TEAM", "Double Team"), + ("MOVE_RECOVER", "Recover"), + ("MOVE_HARDEN", "Harden"), + ("MOVE_MINIMIZE", "Minimize"), + ("MOVE_SMOKESCREEN", "Smokescreen"), + ("MOVE_CONFUSE_RAY", "Confuse Ray"), + ("MOVE_WITHDRAW", "Withdraw"), + ("MOVE_DEFENSE_CURL", "Defense Curl"), + ("MOVE_BARRIER", "Barrier"), + ("MOVE_LIGHT_SCREEN", "Light Screen"), + ("MOVE_HAZE", "Haze"), + ("MOVE_REFLECT", "Reflect"), + ("MOVE_FOCUS_ENERGY", "Focus Energy"), + ("MOVE_BIDE", "Bide"), + ("MOVE_METRONOME", "Metronome"), + ("MOVE_MIRROR_MOVE", "Mirror Move"), + ("MOVE_SELF_DESTRUCT", "Selfdestruct"), + ("MOVE_EGG_BOMB", "Egg Bomb"), + ("MOVE_LICK", "Lick"), + ("MOVE_SMOG", "Smog"), + ("MOVE_SLUDGE", "Sludge"), + ("MOVE_BONE_CLUB", "Bone Club"), + ("MOVE_FIRE_BLAST", "Fire Blast"), + ("MOVE_WATERFALL", "Waterfall"), + ("MOVE_CLAMP", "Clamp"), + ("MOVE_SWIFT", "Swift"), + ("MOVE_SKULL_BASH", "Skull Bash"), + ("MOVE_SPIKE_CANNON", "Spike Cannon"), + ("MOVE_CONSTRICT", "Constrict"), + ("MOVE_AMNESIA", "Amnesia"), + ("MOVE_KINESIS", "Kinesis"), + ("MOVE_SOFT_BOILED", "Softboiled"), + ("MOVE_HI_JUMP_KICK", "Hi Jump Kick"), + ("MOVE_GLARE", "Glare"), + ("MOVE_DREAM_EATER", "Dream Eater"), + ("MOVE_POISON_GAS", "Poison Gas"), + ("MOVE_BARRAGE", "Barrage"), + ("MOVE_LEECH_LIFE", "Leech Life"), + ("MOVE_LOVELY_KISS", "Lovely Kiss"), + ("MOVE_SKY_ATTACK", "Sky Attack"), + ("MOVE_TRANSFORM", "Transform"), + ("MOVE_BUBBLE", "Bubble"), + ("MOVE_DIZZY_PUNCH", "Dizzy Punch"), + ("MOVE_SPORE", "Spore"), + ("MOVE_FLASH", "Flash"), + ("MOVE_PSYWAVE", "Psywave"), + ("MOVE_SPLASH", "Splash"), + ("MOVE_ACID_ARMOR", "Acid Armor"), + ("MOVE_CRABHAMMER", "Crabhammer"), + ("MOVE_EXPLOSION", "Explosion"), + ("MOVE_FURY_SWIPES", "Fury Swipes"), + ("MOVE_BONEMERANG", "Bonemerang"), + ("MOVE_REST", "Rest"), + ("MOVE_ROCK_SLIDE", "Rock Slide"), + ("MOVE_HYPER_FANG", "Hyper Fang"), + ("MOVE_SHARPEN", "Sharpen"), + ("MOVE_CONVERSION", "Conversion"), + ("MOVE_TRI_ATTACK", "Tri Attack"), + ("MOVE_SUPER_FANG", "Super Fang"), + ("MOVE_SLASH", "Slash"), + ("MOVE_SUBSTITUTE", "Substitute"), + ("MOVE_SKETCH", "Sketch"), + ("MOVE_TRIPLE_KICK", "Triple Kick"), + ("MOVE_THIEF", "Thief"), + ("MOVE_SPIDER_WEB", "Spider Web"), + ("MOVE_MIND_READER", "Mind Reader"), + ("MOVE_NIGHTMARE", "Nightmare"), + ("MOVE_FLAME_WHEEL", "Flame Wheel"), + ("MOVE_SNORE", "Snore"), + ("MOVE_CURSE", "Curse"), + ("MOVE_FLAIL", "Flail"), + ("MOVE_CONVERSION_2", "Conversion 2"), + ("MOVE_AEROBLAST", "Aeroblast"), + ("MOVE_COTTON_SPORE", "Cotton Spore"), + ("MOVE_REVERSAL", "Reversal"), + ("MOVE_SPITE", "Spite"), + ("MOVE_POWDER_SNOW", "Powder Snow"), + ("MOVE_PROTECT", "Protect"), + ("MOVE_MACH_PUNCH", "Mach Punch"), + ("MOVE_SCARY_FACE", "Scary Face"), + ("MOVE_FAINT_ATTACK", "Faint Attack"), + ("MOVE_SWEET_KISS", "Sweet Kiss"), + ("MOVE_BELLY_DRUM", "Belly Drum"), + ("MOVE_SLUDGE_BOMB", "Sludge Bomb"), + ("MOVE_MUD_SLAP", "Mud-Slap"), + ("MOVE_OCTAZOOKA", "Octazooka"), + ("MOVE_SPIKES", "Spikes"), + ("MOVE_ZAP_CANNON", "Zap Cannon"), + ("MOVE_FORESIGHT", "Foresight"), + ("MOVE_DESTINY_BOND", "Destiny Bond"), + ("MOVE_PERISH_SONG", "Perish Song"), + ("MOVE_ICY_WIND", "Icy Wind"), + ("MOVE_DETECT", "Detect"), + ("MOVE_BONE_RUSH", "Bone Rush"), + ("MOVE_LOCK_ON", "Lock-On"), + ("MOVE_OUTRAGE", "Outrage"), + ("MOVE_SANDSTORM", "Sandstorm"), + ("MOVE_GIGA_DRAIN", "Giga Drain"), + ("MOVE_ENDURE", "Endure"), + ("MOVE_CHARM", "Charm"), + ("MOVE_ROLLOUT", "Rollout"), + ("MOVE_FALSE_SWIPE", "False Swipe"), + ("MOVE_SWAGGER", "Swagger"), + ("MOVE_MILK_DRINK", "Milk Drink"), + ("MOVE_SPARK", "Spark"), + ("MOVE_FURY_CUTTER", "Fury Cutter"), + ("MOVE_STEEL_WING", "Steel Wing"), + ("MOVE_MEAN_LOOK", "Mean Look"), + ("MOVE_ATTRACT", "Attract"), + ("MOVE_SLEEP_TALK", "Sleep Talk"), + ("MOVE_HEAL_BELL", "Heal Bell"), + ("MOVE_RETURN", "Return"), + ("MOVE_PRESENT", "Present"), + ("MOVE_FRUSTRATION", "Frustration"), + ("MOVE_SAFEGUARD", "Safeguard"), + ("MOVE_PAIN_SPLIT", "Pain Split"), + ("MOVE_SACRED_FIRE", "Sacred Fire"), + ("MOVE_MAGNITUDE", "Magnitude"), + ("MOVE_DYNAMIC_PUNCH", "Dynamicpunch"), + ("MOVE_MEGAHORN", "Megahorn"), + ("MOVE_DRAGON_BREATH", "Dragonbreath"), + ("MOVE_BATON_PASS", "Baton Pass"), + ("MOVE_ENCORE", "Encore"), + ("MOVE_PURSUIT", "Pursuit"), + ("MOVE_RAPID_SPIN", "Rapid Spin"), + ("MOVE_SWEET_SCENT", "Sweet Scent"), + ("MOVE_IRON_TAIL", "Iron Tail"), + ("MOVE_METAL_CLAW", "Metal Claw"), + ("MOVE_VITAL_THROW", "Vital Throw"), + ("MOVE_MORNING_SUN", "Morning Sun"), + ("MOVE_SYNTHESIS", "Synthesis"), + ("MOVE_MOONLIGHT", "Moonlight"), + ("MOVE_HIDDEN_POWER", "Hidden Power"), + ("MOVE_CROSS_CHOP", "Cross Chop"), + ("MOVE_TWISTER", "Twister"), + ("MOVE_RAIN_DANCE", "Rain Dance"), + ("MOVE_SUNNY_DAY", "Sunny Day"), + ("MOVE_CRUNCH", "Crunch"), + ("MOVE_MIRROR_COAT", "Mirror Coat"), + ("MOVE_PSYCH_UP", "Psych Up"), + ("MOVE_EXTREME_SPEED", "Extremespeed"), + ("MOVE_ANCIENT_POWER", "Ancientpower"), + ("MOVE_SHADOW_BALL", "Shadow Ball"), + ("MOVE_FUTURE_SIGHT", "Future Sight"), + ("MOVE_ROCK_SMASH", "Rock Smash"), + ("MOVE_WHIRLPOOL", "Whirlpool"), + ("MOVE_BEAT_UP", "Beat Up"), + ("MOVE_FAKE_OUT", "Fake Out"), + ("MOVE_UPROAR", "Uproar"), + ("MOVE_STOCKPILE", "Stockpile"), + ("MOVE_SPIT_UP", "Spit Up"), + ("MOVE_SWALLOW", "Swallow"), + ("MOVE_HEAT_WAVE", "Heat Wave"), + ("MOVE_HAIL", "Hail"), + ("MOVE_TORMENT", "Torment"), + ("MOVE_FLATTER", "Flatter"), + ("MOVE_WILL_O_WISP", "Will-O-Wisp"), + ("MOVE_MEMENTO", "Memento"), + ("MOVE_FACADE", "Facade"), + ("MOVE_FOCUS_PUNCH", "Focus Punch"), + ("MOVE_SMELLING_SALT", "Smellingsalt"), + ("MOVE_FOLLOW_ME", "Follow Me"), + ("MOVE_NATURE_POWER", "Nature Power"), + ("MOVE_CHARGE", "Charge"), + ("MOVE_TAUNT", "Taunt"), + ("MOVE_HELPING_HAND", "Helping Hand"), + ("MOVE_TRICK", "Trick"), + ("MOVE_ROLE_PLAY", "Role Play"), + ("MOVE_WISH", "Wish"), + ("MOVE_ASSIST", "Assist"), + ("MOVE_INGRAIN", "Ingrain"), + ("MOVE_SUPERPOWER", "Superpower"), + ("MOVE_MAGIC_COAT", "Magic Coat"), + ("MOVE_RECYCLE", "Recycle"), + ("MOVE_REVENGE", "Revenge"), + ("MOVE_BRICK_BREAK", "Brick Break"), + ("MOVE_YAWN", "Yawn"), + ("MOVE_KNOCK_OFF", "Knock Off"), + ("MOVE_ENDEAVOR", "Endeavor"), + ("MOVE_ERUPTION", "Eruption"), + ("MOVE_SKILL_SWAP", "Skill Swap"), + ("MOVE_IMPRISON", "Imprison"), + ("MOVE_REFRESH", "Refresh"), + ("MOVE_GRUDGE", "Grudge"), + ("MOVE_SNATCH", "Snatch"), + ("MOVE_SECRET_POWER", "Secret Power"), + ("MOVE_DIVE", "Dive"), + ("MOVE_ARM_THRUST", "Arm Thrust"), + ("MOVE_CAMOUFLAGE", "Camouflage"), + ("MOVE_TAIL_GLOW", "Tail Glow"), + ("MOVE_LUSTER_PURGE", "Luster Purge"), + ("MOVE_MIST_BALL", "Mist Ball"), + ("MOVE_FEATHER_DANCE", "Featherdance"), + ("MOVE_TEETER_DANCE", "Teeter Dance"), + ("MOVE_BLAZE_KICK", "Blaze Kick"), + ("MOVE_MUD_SPORT", "Mud Sport"), + ("MOVE_ICE_BALL", "Ice Ball"), + ("MOVE_NEEDLE_ARM", "Needle Arm"), + ("MOVE_SLACK_OFF", "Slack Off"), + ("MOVE_HYPER_VOICE", "Hyper Voice"), + ("MOVE_POISON_FANG", "Poison Fang"), + ("MOVE_CRUSH_CLAW", "Crush Claw"), + ("MOVE_BLAST_BURN", "Blast Burn"), + ("MOVE_HYDRO_CANNON", "Hydro Cannon"), + ("MOVE_METEOR_MASH", "Meteor Mash"), + ("MOVE_ASTONISH", "Astonish"), + ("MOVE_WEATHER_BALL", "Weather Ball"), + ("MOVE_AROMATHERAPY", "Aromatherapy"), + ("MOVE_FAKE_TEARS", "Fake Tears"), + ("MOVE_AIR_CUTTER", "Air Cutter"), + ("MOVE_OVERHEAT", "Overheat"), + ("MOVE_ODOR_SLEUTH", "Odor Sleuth"), + ("MOVE_ROCK_TOMB", "Rock Tomb"), + ("MOVE_SILVER_WIND", "Silver Wind"), + ("MOVE_METAL_SOUND", "Metal Sound"), + ("MOVE_GRASS_WHISTLE", "Grasswhistle"), + ("MOVE_TICKLE", "Tickle"), + ("MOVE_COSMIC_POWER", "Cosmic Power"), + ("MOVE_WATER_SPOUT", "Water Spout"), + ("MOVE_SIGNAL_BEAM", "Signal Beam"), + ("MOVE_SHADOW_PUNCH", "Shadow Punch"), + ("MOVE_EXTRASENSORY", "Extrasensory"), + ("MOVE_SKY_UPPERCUT", "Sky Uppercut"), + ("MOVE_SAND_TOMB", "Sand Tomb"), + ("MOVE_SHEER_COLD", "Sheer Cold"), + ("MOVE_MUDDY_WATER", "Muddy Water"), + ("MOVE_BULLET_SEED", "Bullet Seed"), + ("MOVE_AERIAL_ACE", "Aerial Ace"), + ("MOVE_ICICLE_SPEAR", "Icicle Spear"), + ("MOVE_IRON_DEFENSE", "Iron Defense"), + ("MOVE_BLOCK", "Block"), + ("MOVE_HOWL", "Howl"), + ("MOVE_DRAGON_CLAW", "Dragon Claw"), + ("MOVE_FRENZY_PLANT", "Frenzy Plant"), + ("MOVE_BULK_UP", "Bulk Up"), + ("MOVE_BOUNCE", "Bounce"), + ("MOVE_MUD_SHOT", "Mud Shot"), + ("MOVE_POISON_TAIL", "Poison Tail"), + ("MOVE_COVET", "Covet"), + ("MOVE_VOLT_TACKLE", "Volt Tackle"), + ("MOVE_MAGICAL_LEAF", "Magical Leaf"), + ("MOVE_WATER_SPORT", "Water Sport"), + ("MOVE_CALM_MIND", "Calm Mind"), + ("MOVE_LEAF_BLADE", "Leaf Blade"), + ("MOVE_DRAGON_DANCE", "Dragon Dance"), + ("MOVE_ROCK_BLAST", "Rock Blast"), + ("MOVE_SHOCK_WAVE", "Shock Wave"), + ("MOVE_WATER_PULSE", "Water Pulse"), + ("MOVE_DOOM_DESIRE", "Doom Desire"), + ("MOVE_PSYCHO_BOOST", "Psycho Boost"), + ]} # Create warp map for warp, destination in extracted_data["warps"].items(): @@ -975,21 +1417,57 @@ def _init() -> None: # Create trainer data for i, trainer_json in enumerate(extracted_data["trainers"]): party_json = trainer_json["party"] - pokemon_data_type = _str_to_pokemon_data_type(trainer_json["pokemon_data_type"]) + pokemon_data_type = _str_to_pokemon_data_type(trainer_json["data_type"]) data.trainers.append(TrainerData( i, TrainerPartyData( [TrainerPokemonData( p["species"], p["level"], - (p["moves"][0], p["moves"][1], p["moves"][2], p["moves"][3]) + (p["moves"][0], p["moves"][1], p["moves"][2], p["moves"][3]) if "moves" in p else None ) for p in party_json], pokemon_data_type, - trainer_json["party_rom_address"] + trainer_json["party_address"] ), - trainer_json["rom_address"], - trainer_json["battle_script_rom_address"] + trainer_json["address"], + trainer_json["script_address"], + trainer_json["battle_type"] )) +data = PokemonEmeraldData() _init() + +LEGENDARY_POKEMON = frozenset([data.constants[species] for species in [ + "SPECIES_ARTICUNO", + "SPECIES_ZAPDOS", + "SPECIES_MOLTRES", + "SPECIES_MEWTWO", + "SPECIES_MEW", + "SPECIES_RAIKOU", + "SPECIES_ENTEI", + "SPECIES_SUICUNE", + "SPECIES_LUGIA", + "SPECIES_HO_OH", + "SPECIES_CELEBI", + "SPECIES_REGIROCK", + "SPECIES_REGICE", + "SPECIES_REGISTEEL", + "SPECIES_LATIAS", + "SPECIES_LATIOS", + "SPECIES_KYOGRE", + "SPECIES_GROUDON", + "SPECIES_RAYQUAZA", + "SPECIES_JIRACHI", + "SPECIES_DEOXYS", +]]) +"""Species IDs of legendary pokemon""" + +UNEVOLVED_POKEMON = frozenset({ + species.species_id + for species in data.species.values() + if len(species.evolutions) > 0 +}) +"""Species IDs of pokemon which have further evolution stages in the vanilla game""" + +NATIONAL_ID_TO_SPECIES_ID = {species.national_dex_number: i for i, species in data.species.items()} diff --git a/worlds/pokemon_emerald/data/base_patch.bsdiff4 b/worlds/pokemon_emerald/data/base_patch.bsdiff4 index c1843904a9caa81d52b766eb7056bf6e1bbc6546..0da226f617f6fd7d235c98c85b47767240514ccf 100644 GIT binary patch literal 243175 zcmagFRZtyW(C@u*clQl!U?U;O#zKINySqEV-6g@@U4py2OK^90C%9XHB%D0&srufV zuj=$gudcqBS<^M^U)4Ro5m6PBmXLt3mD2YX2sP zdIKm+y#GIKm%0srL+oxZo292S-}9J^{OC^sH(voP-M;=Pn(J!f7qfXKxvW-EFF)vE zaUSC$gV6eg&TVzQx=~ak_R>Bu#?gwo1$aT42?WZMLZXv;=e<$0#)2UWIOQ>SgRP6fMS6>%Qzf{5_$=Fc8*}xStu+q zPJV$=1_lQF6rQ4Vk+-!M1`Ns`2ScJ4LwN>-As){P;%)6sOMMcZX|@2%PC{xa03971 zwaPP_#o$291X#i{?!&OdS^_G*%7v=^fs^qZHx-V|QJu~7hAiGPloVQ%Qp!^waUjMZ zh7~~o00E%o|CA99`rm@V4g&QjJ)+bl!9&K%Pm|a178ev%7rim$A$L{~h<65MxED&M zJe(DGX_=YjplykSL?&cGvP8t}A$<`YI2J!TIE5!qS%^Ix58+grB`F3aAy5?y05Kq= z3-X9SxW!q^xxCp0Jm4$@F#});m<7NjK>={`05brhKb(X=2H;=H5`_ySf(Nt&Kgg4s zJ!R zK@2FQU8D@h#lRr|rH0m{(}9%gzbnCYVOw8Ur%ZNKlti3viainxIVC#5z{HEWrs?%Z zDdnTqsPAS#XjJI7T}M|k8Gcj`gp}uWws2*f``6l^`KyW!iRX4e^xzYSH9Zv6tC{gW zh~l}5a}y4TAC(G^0;=-F)`_c8j04!YqV+=f*jLcxz4=aj^=(&rMEjV+nO1j2;{-WT zhiB(G*+*DR2BEx8@O;b4)aV}YfP}Bg-IrX}JThXcaBM*;=jas9D(FtqphPl^CanpX zBc;Y34DFLoB?c|#yr47=i=Wj>7FzW!=^tekiB~gZ*LC~m<*=txB#_$r>Z)1#8~YFa z)kVcFj2JrXOG7l&xx`9oB9ret(&<`o+Me{<3NKnp^hQ*{M@4D zf#a4g!IhLxyzAWFU28q113OzDMxS{y9hra#jXTLyI$Il;b#AzQ5YmRGUz61 z)SAhvggZIwImZt91=ZWB>7A1DZim7(;C&*0+ep?&QHc32F4MRbZue=zQV~g$rn)se z`u&Z~F-Sp3+4-5Pa#(w~?_0(B;cM)3#{5l^{$2+tu>TLUy`6-JeD|g7crDM&aU0Wd zoLx4xkh)`G?e(iiZ00hWSC0WkuIM>SnL=&HyLM-4`NxSSDn%JbGm3g;(Ga;Bt3RCj z%8pB1YwpYR-njfRJsgik7h0G+D$n#8ure8ciPR~D=YIbRR^fn;N^_(Tk!L?jk~60V zaqI}i*P@Dt);OoXw0N8L&s%Zr*UfaHZS4YWfEQ_ znqm(|d<>O?@fcQIVM7Yd`UC%6b_s9a8f9r=Iwi@WXjia6K)jv8X^x=yCX=)Dnd0#J zBlii3{BLn{f?yz8WIv5(l)Fu`P&ecC_->l@P(6Px0hpT;Z*H#2^5fgbM}G3Dz`j?) z4!5W7U zF@)?+vD4quH%NBS*LvBtn5!6AET4mbi7TfdDg$O=^3OAd9+yX8j>t%ddswCqU&@HY z*@Fq@+Ct*x|CUHMKTEI|i3=h%HqCOXck0&6d}Q}pEcKTdI{yM$LD1R%ac%h`FtW~U z2x#J!fy7wTq2rCDABBIPAxd%?HgxcUbe3#UMrLZemwxRz<+@0#9V_qtY>|Yp-^#K7 zD_KPXM{6IyfN@x^sbc78g)w*1fae>C?YC_nqUF;XXCcRug+KWSG=^Ofiz z9=9iTy<==@i2h43j9?gRtHH;$^2uJu&#W{iMkRF(Gq?D-W#78pOpa`_`BpTD`KJ8V zkB~lCJ#x9gjQ5haRg54h)mmdY^@h$zT7_&${{HV>-f?lOv2h$FFO!FG)VuW_vUkWveimq9=|UNqYo zcRcxOVF9jdl&y7JWMO_&qkBYFZ71BGt?4Z)=${zaW(0p&$RZEd1n1-6;b-CFqgwr3 zkn9w{AtD*n$qYXFfz~Z9;1;3plKowDs==K!CA`w#iyelPUkJL3dbuX2(YVNwEFRmu!+H~Zs_ z$95hP+kYxkjpY)j6SniZ{V~NQ{>}gq2EfzqaLveq z&KYu+mYu^A&Pu7cofJiJ7!g@Z$SP*0LLb5{h`GmC~U%iW5blZjK?^*k~^9>6)Y zfz~Gu;_|ip1K_>V;9y0RNXL__>wr3i^5Xr91~pm@s{*Gi`1LjeSNiauqcbjLzGHQ2 zjRO6htmtE4L9U)6Os}AS1!Hh2OVI z_cJA4)IMM=UR7C@xs3TTLi zN86j@;~d%x7Y=|P&a&{DSq8_CFYU98Jo{cra4_M#JVY78XIP__-BrNFYmcShtb%|s zoVf1N*KB)vFQU=%#%8_nW)e|u(d!$_8D*JR3oVm(6k14RCe7HXOa-~Jo1oCj1)Z>! zZYae?kIhHzgAwv53PdjJGVL`5sL7s3^t+v8MqReYd zbr3PA>Zm|$#%TP|=DC6J>58Ug4KzGiU}Q`l87U)8D2@ZdSD`;wiy-cA3W$Y*Rd9Vz zrChhEG0pp=aB_`k)}Xw7MN9_Qq|C0$OveZ?nNw%r-L=#~xmR-+0#>6uryW)wwMLzbe5>D_4@u#bu{8|VY-)u* zCgU)p&=H&d+{+}heW-sjq0YH%HUj%o9v(Z5f*L#F$sreb$==J-tiu4K6m9P2T0G=! z*gkhJV#=(RU<M}=XzYerp(AP_Uk*w zByuWQ8Q&mOhcd7fs`8E7&hV>dERGP^)Zj*OS@qt_^zJl9EgP3ui?Uz(=?mg;ucccl zq!JI3;{74PW7N*FzSrh*ENs%ox1UJSv73Z7*9u}^sJc>eIC&UzeeRvoFpX2`9O)(=_F`W%AhS2OXQ7deb4J=e#P01aBufGD|i_?Fb*OxrM+ zQ-{E+>p}0l6X@3N&su@JJa84MrWh9|zVheIbB7noQveurZRVD*))}~RH~479Ee^5- z;&=&RF8#E9T@X3beRaS&dB9uJ2SP=Ixc-RT@RWgU4Eg?O+-E4P?}r9^1jV@`dG>X_ zNr213{^Ewwn5Am)h&7h*bXqeD$@B&oTRwj!<8<+F&+cbXPTpaKu9_VaCm9c&Iv>&ALCV%JE??3FEmBU?7|(I`6hS}CJ% zkg`m%$fNJg-PKqY)!Iw8_o`k|ucU9C&j;HyPxPR6#uXLOWH;nlJq!W4H^v!*l+#l0RMi@tiKz0)?_VVKd>Sk4l}QRYzcg2cRV5>TPjp+LyLfGn z$!$?I)7t6(`t=d55Qt>6Q-RSKrFPzOGeyAq7qB|9v;7NCaZDIm0Q!} zu271VLtOZANmuWWcr$X9d%8AHf#WZxZu349%!t-6ii1VT9gz)@cIJf6ky26Asw^5+ zU$PEn=51$w3hHs*@4RNSR(nS{sgOH0tVl zt4(KTTmkWR5{w!_7+lK5{A#X^B3k={PlC=Jp8!tNmXtDMZuEF#x>fcetLc^9z};#g zik0)R@Y@$$yACeTo~ggnnBx}DMxhTM#`~(=xJXVFI$}5GjSCQBa>B~ts8+H~^av}Y z6AMMc{lOe%I4x?MCYe=^Eo!0#DtC^`YR~KvYN8+X5xE_x(Ui896YE$VQLHjg*qdg` zcf05|CggEn`VOWM;s~{}JfoMUCcyic5S*S3o#8lPp(`Uoma57=i?euv{;~le-KGvR zX~GvpgYhC5S#gZUf;&6BLdpTX_A4C|K63T7D&EPf(;ryVVsyT#Ht0Tg7-jW+BcyyS=8vZD9E&HNsNL^R2!4B&>zantrwk?uMW$0W#~r8m&D8AzT*Q$cvz{SiPpG z&p9R*1(~rKBQ&E&WhR3g^BjvDKfjTlq^2r3PaaAqJEc|*FRq8I5-2E}xR2!;wGUP> zXJ`*=)uf}3zz&iz!W@ZB(hP*tVS8e(l(FnhD;A}x3nG;%69Zz-d9DbbJ8G#ERNVbo z%Sfs~xU}CRCgJ09(})~)=XP8T-U!%s!JKZMN`xAjP=SbmJJ zx3wzQ4z{Kqp6O3h#?}TdOmWq7z{2{6!*TtN&ifRIoChE8j0*Losh(_ zl$Jy9_>>s612Y)_LEw;twoSa~ay`h%wn?KUl3Y~;O@uBFvx+!AC;>@DH^_Sgv=nA{ zJ-pj@=B%YE+g9&F#q%eE9v(&s8Fs+;%X|!>Oc_3s=kZZFbu2IeCw$gzF%Va7lGW-Y zpZ}+`{PV`R6_4U@BNOeE6e4liB>yegK=RJ=r|%c<-Y%8XO6;2X01JP0Px#--D4vy= z3AiHU6uc#?!3>#a^n2wQR^GgcLlt%N3YXfRxLa6oEvui$Zd_=@KJJEyXK_G<$GO3jZ^ zqSpi~0?%t90;L1RBk!H~rnL-A{1rUEan{MEdj6F?yg`<eL&`CM_*-A>VQc}mUlVUELS=$M+J{f`fm9cvio z{k0~orttk?!s=W;mxo`98yfCCs5WxB*G$Jnqq-qql#VmABA@@TzbFP2g$_8h7c4rz zQjC`|*vBF*_0FqmOZLy}nV&a0#;&6HjJ)-p2$k|LXJh4}x4K-<`*wBMe+adRr8-_W zVrcq{aZRg|eP5kUT3YkbyH>FTahadoboMzoN{v`RH)H!-*v4s_D4=_9sYMleQv*HgX$*Mb!OSBjG|eau4QShzxtU+k32pHpUy_A zcy>?(k4AOYe`36Tc$^yFa0|Y+KlOA`X0_Y;&Y~D68Xtas^*d(&wEUSg>tlg&plTWu z8GfaE!>6b3vWhz{SXggnA6_uvFw$RNYgb@?nAW3qFMj|^0Z6gqIc8&mkXe+tP~>tL zgRD`__|}UPu*J=^r`@(2&g+VzP4hHWyEj>1Nt_p5v{&<^fWDU;6T}dS!PgSq~>BM&^`73MSUSocRiIg_$$df5} zkhcFL@bT`lO!LM;M`$5v;%&$KjbOw0$8IeNIhxEBH4x8e>%kEk%%*77uabfp0uLjs zLG>RWm88bEc?4`}2Lq*u(tH+{v~secS+}e$=dA?2rskr#lUHLrYO{ zk|oEJ5MOVjaioUyaeQlGXSY4~=sm>a(oRtFwlib(q2$N#wsrQY47>zQ1?r&McNP3m zmhYU@a{hIZVC~={6X76WxizY~x>bk-^ZKzJZ*XsLe!MuYKO7B^a8pzUmQB?bTgr|I zrP&6>x{|O=JD-55AcTe^vLJP-{J=peYj9C28XAO~w)E{Q4GjaG;ebbB>p}IbCuT_Y z4|8eL{Z0CR}&+y)-u& z$i`}>^RfR8fuP$8tp1Mlm7cg~sn?$AG|nW((47CZbGXeDi}g>gSE$ZDOMat#kn=&8X!a3zJ`6Vu9;tKx}2bW1|}u^q?xi>By8`9kpqfZ)^$L~d8ldv%v9X%_Q#0d%oK5Q zm_s006i9Z(Uxg^N7pYjA{e7Lh+uRWQ&m$#OmbY^Nd*Y8?3?fB(t zeAM3?ZZr8h@*wd>Mbc9E`NGinSu1#HqoN7RL{*j+ZTQA?Fb+LRBB@-RiqdC3bQ%8$ zDR{Zpnnt0LN_{1|l2|?~ayD9ugtxDL&tGd~++O6n4bnIYJDPB0A@18=W{xj%=GzSt z3?W~AU%aoWobvLByAN1I;W4G_RnZz(`ew4wy(MPKZZBpe_-{EKMw|YgQ*vl77j-?%R1`U8(zt=L0Q#fKv!O z3edi8cbka$@{sN2BM;oyaVJgEt$$;qQ0R-1B{*E4{k;MGKCaXudj46m3Z_7+b z=-Awpo#aqF81p+ciG=}LAuV3#Ad)R z30o*hG2OBZzBrDU)aX7U(6kdnjZGL6Qw*TaNw4-9|L-LGaVoFwfd1H)#pOwdp;u4y zZ}OIRQO89tKB5+rZ5(7kG%PwONE{H&i2;ve2bd%*P9`l6fFA@BejEf#6?bXD`t^BY zIn8!yylTw2%HD%YV3_~2#^X8;JT<3aF`+C-Y&PFTT4n;d6{v4=Ds6rEp=2Px87RSDRHfV#lm$UH_uVev>*)H<{bIT)ytmR|qQ;D!c9 z#v;0mT@LUuIa)C zLlr^HFb8q}3Rhor{!4T+V^%p&))GvkMvVdv7M4Pu<5X6USEb|OuS}X=?IT&)`Em7~ zVf?wr2Y%dw^g3r{K;*?mc<}xE=GXN(%AUt|{AZpL&M|TBr=l2zP@l>L>JZ%*k zfT0weUn~01JS+{Q$*;Dv=#uZa=}?F#s+UsL?`>I^=7v8Zl*}=MnwTv>Ir;rtiMRoS z#!%>BeP8M>?qXd+9yAgjf5u9~x!*sUm5N6_UYpT`;e5YRTfIeVusrp#+=$KH)aX~| zRo}ppxr0G6OfQ)h+O(=d<=5sOtIGxFwid69b5C-+*9+K!;BkzoDQszce8zNvk`;x) z%(DjK*PU4FOl)G1zi-TOq!$LgK|AfG-Jg#^0-A?iL+L-aniT?>ib*}uSA^;pyH#l) zB`vtjX_XpVB6N)9?OPPOvt3Ig7rIO&~HSk>&2O#gP66T;y2v|rfs#m;f$;C5&W|!(K zGd|#FsZ^i}YtA4xHtI4^Fi5?;&w278i#@)}q72>lyl7L&Xec|>yXwq6ee9lL?%W@g z^Ugr_xE)#dGQ4-~jW>}d1Vn^fQW9jPisgr*W)L9u5G+NeCfEqKwk6sSJX3lE*wogU zy&5D@@O%igr5gZZhqr@8*k%T_3-b9H;I%T=Vs$~IERt-Gfw910ho$K$(O26XLXL~r zNq;CGaUycOqDA`@&&6ME%`F|dn6Il3SHW=Z7$b0e)f>NfJy_V+USa?U(~>}}ijvko z-gnk>EX@Zkr=<6lx;Z6^n$7EDezW4bVp$Jd zY8jDxRT0?ftoN+{G!R`J_(;FQYwZ-F;Kr#q0r+BxO-tp*ykV$jn3*vaXs$wxBdRoy3Yu;^P|Em z{f@D{Z!%E7#E8hZaC!xP=k_quWQ#N1Hz!Xoz8X+1-&8|j-m>?A^p-HbV%96(P#YC% z($^tR+Y*Rj6i{d!j(9ry@D~o`(a;*{u`#ThDUc5vBIW>Wtsm%sgm_URlhY%7CtfVYyY2E<-~Nyt0T4MvZBqb zZ9neITh=}?&K29n*XU>%UcHl1_?7-LXY|O1qziGOgA3b@t|29T!f=ip^1VyOtg=8o z{~2uDE|)6lcp7*im%K39B^-lFb=uZHX7u8DH`EVVtWIg&OUCL+>Z&5CpQ;O(@x7pv zUodYZo>4Np3d%rn0V_0vnRuKx=9*hV2%?1rHbqRhISTZ#Eka)R&oUGDAEMZeVxl<7 z+9*E{M-Ez_?#bq$s=IXR9mruVvJ3h2ZQR?RMyWehY_4*zABlF)f1uDi{Y8?G*xbOu z`6blSjd#P9LuzkqTJcvAfEw+*^a98#uHnc}#~~b(u*mpQUD;35)j+Bd5+u+s@FeM= z6fYVD7tn+A=?59Rfpvg7r3E)r6uar=HJ5q10d_!qv=N&WF(_EDQqp8jL|<(fTyHF; z3NK&6MQ8*yq7HO3Hc>zg{KpsokrTvt;NfH7q>KU?@QGpY<%QdL>5%zo1EKxY!gY?& zYGi!c;7BoE_+XF|@kGy#?dc3g%joEbH592XZJP@J4XP-I1r%u*Bu zuO0~o;35X{f&+11@L^PF;8moV5;Q9i2vougVfm0pVEPK0WKe@aG}uKE0B{5iC=e6? z0!jtb;0ohLg27@qxh04K62qx&346~S8~x+WV>kiK-s9s#%^8GR>#@LoG=^zK=EJBj zwS=$y%HK@1>`D7G4u9XDrAWYw0CwpOtT}@j36Y2ofg)G3DgNBvk@G#6N)~0JdvBp9YK2VY~=w?#gVg@YGR>ILp~7^RI7u za@Gzc#_5r~#T0Qav|pv4`S6`5wK;@Zvw!#fyro#!of;N;?2F8HzdQldNsgY`R}EH% zkZn+lZmXC)xwsf-^O!Wiwm5`C6iz`5*>2%v;+ z$9No2qebaSh;-mF~(aGpF*%+?}*um2>m{I1SnD~m6Qp*R7; zuLmiK;Q|A%T^z@vDjBa_y!ulfU%^u~rsr z;IDiPBU=fU4-8|g&Eji4gcYgy0!V}gxAUwWE|?3aT)uEK6ut1+mYQyTV`h+2m-Xg@ zBdJo!XS6t9_x@Z=Q%ly?x~Rk~WjER;ZSFM4O;mFHi6r8aAKfV~7MGI{`EdBq*grAv zMg+!lK9)`7LY|YXQM_Uu%rBNSD<+*QmN`nOK&x2LCb1vuD`Y*UwI+?YeA7VAw4D|V zE)tpP{Bkmkh)gXvTTDO(wTZ$}R*|QK+Qo!_`~r&TVrn+Dg3`*c>h;fij1sLTzU^*O zIdD#Rt56#JrbC{xX1X(`j(H$HC-FU5=^PfOwiY`~QPZT{Z7OF|wBVu_lXa_tETp81=+nMu zLFdba^k;5Ldc)gH1Zn*cM8IVbN&$Oyxgs2vpLiHBp6|4LuTBJU%Nmp!Mc|G~8IsrC zr#JjjE7qQ2BWe9PuVp$Zc4aEG!7buxp6dR(>@ZjoIy(l_x|2?Hf{#nntw~i=3{JMV zh{5P6e;?XRP{0QXA(;H_{a;W`&sJ3((ERt?{}WU@K%a!`Sg&kJH0Xt>KW{!aPD~H` zgYlIgH++0{PuGkpO{Wf4@7C439{_I}K_qDQmo^M^TbqMVy8t&ax~pqNpREOK0Qfl< z%fsHac>8&W;`76fU9G5(SVsW9GHI7$gtB{46wBXESL}5Gsfqk2aj5+ z%RtiAR4l6Vs*_Qer9$n0+FxNbQEaeY+7Oua``g;#lhU<-@tL&&6c}48_WlSB!z<8P z4gfxJU>WQ=1z@2OfQn?pRp1Yco1o&aT#^SU&h$XM1ZuB54pxV#}_8p2zZKz<%fu8z2ux904W| zfmDeCK9cRoh`iu2KnYx$L+$@RPl5RaKyUBF!oC3jh$hLR|K*6c1z_^{#{~TeYFi46 zH3SV>b9nr*+WmiB*FR4`drEaZIJiu?A5$)E3+#9nBemo`mv)g&PiUu>rrtVO_0`St zPFwk-`+Udr*m3dA^4RED1(?=c`oZBJO!)K1+CTf&n(w&m`0aR5KXLZFJZ~SjU;EAI zeDrCb>YTdK> zym;(a)!OsZ`)Mz%-$d%E!F3X3Hil^G(0AihIXvm+=6r z=ep6SyW1nu!@0vRY4cfNVq9SN6LVJ5)z)*5=kZKuCmHE-g_ncJEV`D{KxfSUIH0io`eG@v;P3ks{>n$-BEQaziY~-*BzOMS5?g>WaIUBPLymB50?zVmBJjUBSy7<2M_;efc&0Q>S=$}1P9*_rB&#rU|mzzY&H$h=1 zb%`VL5dAewZQ+;}ym^>xHe6BX@*sc+RDO;`ItJV2R@b!)fZuVh^o85gvG>(^=k#&) zWrb6nfY0{jzuEsv^5(PK_SS{XHvL+I#yVzBWlh6F?{lk{8J(t&pu>;<^Qg;05D|5_Fs$$7Cai$_@9%b7tR9!;LSz>0D3$i03~>IfgU(Znogg|4q^Ny z%w9AlH}B2Greq0420&5rU@b>y0Z=mlN`5dYgdKoKPcJ72>cx}8lSfc|kW+oYBehid zDzCbpQ(A&AFC>;Bz)8>Kos;yB0akq^`K(+9nFaa>0_gn#F#rIbJgMbu20I)w05Q-z z6OyAUH#koWtNN(8_y@eskZ6^px_Fn#e#TC_$cg`lmcHas!b$-Csib6KG3Im9ShFyS zR6I0z@ZWxDl|}MYj5RORPUU0ReH%&4#?oY%_LcS`;U5Y{PO?- zQTeimn17-OQ{oSRgF;|I3H0>-DDhVD=`h6f^f-C2M*r$D8x2V(#ZY8yiq3#6&`Xqn zen*^9X2_SH62q{wD@xbnKMJXi=g2K_laS(B+bN%YR^AW5l>*{HVNm=5Kx-4Txe|bI zd?uT94o&HssE8uYSv1qvm=w8N)kjV|rf(HFUaAXB4&#}M^W3G)xI$_UY72`Fa^*>n zinDStlI3RtqVwKy^d&i^&7y;k;zvJ8{RiLY@eHK3BB{a=Y8|9ML#1N z>_kHo?M&oN<#8L87{IDYAps_jO$#Ys>fPFJjbyv3=AEC{v_)eg+H86+*qv@=jwiTt zR5PW(Sq?Yl5<2He!hMKTIBUE0rsb58_)>bj=dtmb-IzJu65Q*+q!p+YQ)D4V9$jHq zCud4vQSG2sryh#05KO*@zfWn-)BvytSV+S~5!2#e7FU6YwoYsZ;QQQ0&sfobV|OGi z7UZerAsa#auh_3^O#9oEH4RaG-($=5N~8wLXhiT)$gQJM3BT0^;bE`{%M5-qW{ymG z6cD6?PnGRq9u2q77n;P}$D#p>X^wehHMb=ETvpvwCs{hb{3Cnt^o@cl4Jv*am$^GA zt%iTX>XSiWa0nE`R7#dHuQSc;n+mhxbAjv;MEo4AQpe^0sKV}KP)}DqEM`1mTJX?E zkqe{6NCKlX8Ovg z4DGim251EqMJ_XmM&Y^x3j*dKQ)DX2*yZHXApd?IAhHNjGHI@T0 z%XCgcwq`f5iYOsQeI@F|{r040QfRrNQ6Ni8 zZ&P0ir%_2;R%!cHj?_UdV>{;(p>-yAYij?1DHMT|H!xN}a(%MnC?4JDm;Xq`v;ou5 zh@coM>bRFepg|sQjbZNG#EH%DWP`o33`q== z`9k(wj{0P0wCr2$m^m^(<8x{debL9&Zg(eLSPn2RGV!whaD~Xp`r{vTz z>;V)pa6z%GvK?A1el0ZunaxAAIzFuP=(m%L(S+9Ui@mBU#|dyMd^l~hhWVjIUS2iD zuRGl-M`EYgYa(Pod|oLI!|1X3d74eRgz*7`T~<-I`v-Rfn@vcge?dJbJg@HO=IC@) zajlG~m?l%V;$jbj^enD*jQUQ-!YWhELbXcr4{A*EM>(k(I#X5fq(rPq%)0VrpT)`}-UIxF<;6BE=p*{Mi z&55mWl+mx8d)I=?{CBi#6v2P~q;;LK_&V^7*QpfIPbNr%QFamyz14^4{L3z!5KS=o zCrxYg`?DLP%yWUxb&!jg<`#+$?N;acSV=bo34-Ip5_I?zwJ~AYzX6?NtK>HaWc2k5 zJevCMh=1hqDA(zVBlp&(@K;bqdx`6L&<}b|fFo}>exOD*27_rhj_O$q^l^!^e(V0Y zdhS4ec$Th`<-DChC#ii<-V?T64T07brt{}bLtC|AczpQ7lx7qaiC{j=Ai>Izj>xd` z5`EMktfFLQgL^U6%mb-Qiz^Al_q+4Za2t4+w)3)nkvz2(a4-)X+~iU%eT|%Gl#3u9 zI%>$P0*AnY|F6^^j=YBon;dE(X#B*9s~r0h{(fbO1D-#zaEi|CBEc2s-;cbRzv zl{RhZ&Q#BjAO{noKROq~_C9hTbmdIpAfarkD zFhA3iwANrZd?Wc`P@>Ohk23TIv!Yhg9?0tbHErtYC26d}Q|*lk$)msoN^G@|E%zE{8ZjD>z#bZ2 zw8S#!DeH$GS_FlMz|Y~zeE5}SjMb6+RZ4a*a;T~*5dywDZC=lekE)uMcY^2_axiwn zi}eCfWP$<59W89gJN(?>78C{H!YEx+BfH-D4JY?r;H{i(Rto`Vy(|e_ zZN^8sa=xA?WqWTFd(q4|Xvm#RddQVr4ThR+CS&Qrz+@q0t$DHpx!oq)2jS|M;%YYO zs}GSuOSQsEb;xYIvFJgI2lrM%VE?+MQ%(Pz)L59MQ8{PwT`S@ic{k!ZSHp?LVl)f+ zDtX<6J-K>!0Di3PaNTd%M}G*!f;^J)?Xd-|-)*cv4So%(+LagJUg@%ll*ul53jc8b ztL@og`4|YoVu}l&qkl%8SLPa(4REZpXqO%K%8)e!drGMl3-c&dbw&}pTkckE;4-Qs z?`D#QgIY+W1_PamwG}?xQ_lvYE8_gl>0$VgUTR z+j@&qf|CK=7s$0%NhmuzZW^of94=sp)#~v)R4mAiVOJAwjco5LRmN3VlDxLmydhRl z?UnjSKuCrs2?7f8C`}kTg9Ue%WV&b2EG8D26lLj~*T~-y1Yd{0To3WhZTv!&C-op- z1|v0_qPZqF!E3IJ4N$b!0yX1GA=sSp>+NsDXjm`Z>Wm&^qD*1SFX%NKA(D5T|l-~tQk*dd=MBUt|awl$CYA)xLgp`KnpEqrsn~0`_K!DZ0D|?W5mpr7sL=^x;Z*mBl$XE99dCg{6Uz!U&_R*0 zSwV>FCWOfgr#C`vEJ%}T9xYpOa3&n!I+DUvrd8Mo@qcM-768InIu`6*)thRz!# zdJd~cb>jG!owE+txa~|7$TYXe1b4!` zi28c2w@QIZKkqbp_{C~W{Q%`hiik^Tf>W6(;f+?*ssBcPwckpe-hbNP-E!YC3vNr6 zJ4kXG*OKB^niR>_sE2gnkuUNvd#_a&>4r{)!Nn z?Pm;^!cM(fK}AIBCvt}9V_kEAkOW32+Oz+pTR~Q8H?-Ea@@5U)f_m*>Hd-X8EAw4{ z`*EbOf3ud6z-?lljkyDDTMLfey*l51m-tkl4>wkjJkX!l_^g&H^=X#>D;E+nKW<#( z+Hd@c1H6IvqR%71oAjH&;@WNTzdVoMeSh$JRqYUy2@ls@6;U@B1dO*`eo0FvjrWf3 z=VznZq;(8RS5^tJ?B@<8+El-L|5QQB)|#W+FB1)BTCTS-F9}Rv5WVg^?F8Vo zi)ttcWyOw$zv{3Id5@SZb_``0fn<(@T?5TAwfw(V!*nlZ?~CMOwK#T$vkQoj-Dv-v zt@D|b15n|@uN;Aw{yUN}<#-TkteG{wHmFQ2QPOgK3bml-iQJ%YYAZ|WNR&y;btDFJ z*vZ&^==0wcGxE_kliPN>f3UWka~^54FYo!2Qyoz+Y9dgoRYEZecZZ(*4kG&WeGT&# zZtd6CMNRf>%vdY(f+_654Gjqywpf%rpz~&#kHc_U`5M;Nlcf1WIe9!)3S}(Qb4Ghd z*&FRsMzSf;_oAXnlqdji87Uek=Oh9ED5SI3Gv@2ppO&~*8j!^d2zV*sl>qoUz_fh2 zj{hX6Ib`G6digTC?PN6CzB}<6&)qM5`s?kDsV9andSiVFPh#uV_gDk2^@Z!mI+Odh z=L6z4q6_EBguf=%-LzGJLFB?@SAu+Re+(5x9BMbE6*i1P(lg4JSNMm$?7on`NTj|e zG~Jn{LOU!lT6i<`sqXzYUG4ZJ=vXl&5IGK+@JC4j|7`QNn`i%ip>PhK1icS z&tH~){rWu_nUK3_he)~hZG4TFiE4;N#z3-Hq9^t1K)W;lV%gRO0@WYEm@~MY)xbTE z(#%S{N3wqttk6p1<3D&`}{p)GuGsR@`WlM*41_;P!amJkZ&TYV-t}L7h=@KmT>7>?FhK`X)m=`=++OapDpzi)qCsw+wj`f5-BZ>T( zP&L;|4YEznk?>VxE~zRLrJxpFqEcE&n_~c3%31glhgH*H$EY64q#?r;Ar)Vc zD}4CgcXys`^oZ5XeplcRgieM71WcLBYZ91AQxtwalY&*Z>BXphRM+EHFnxY^`MuwM zOy^Sv`Uq^OZO&5l1|7s7t}-}*M|OB>A~{ww&Y}7)G@^+16Y|aJ_*vo?Yx?wxSTa!f zBR_=4b7gO$5gP~(a*crnX39`ych!t;n4PqXCKM#_xP{NlL|?sB^VgH9PpSDFhYWqH z6NlqM-ttI@y^#Yc_b%OWk-bs1&@Ypekma*lDBCw`!)XBF(-q!|?-q%i_lG9R=7s+Q zD?rr0B-)7!mx$2724rRonDW_ZAn{AOkNw;>BPMNtbS@jm(A0z#GI=^e=<#a<|SL8%j{oD$P zLV{2URH0NLdPp93$qC5^s5^|LB?qjB%y2O=3X}j7?%AN{pO>+-Z=={80)Q0Iro2zL z2}E$r+Q4Ha5eup9ua(3nfF8g&-jdurv)JzN@f7kqmHb}TL?7|Wr%6?0Rz(B@&T)%P z9JFZH4q&;sRNERf#<516PdT-&ag1(JTLfQ6SnF%95yO{94!Sh;xy%}_w>hA!S~}Ym zXOlrTjX!?W5k*XMn}txS#e+vy)ow35O;W^&RRkLqB>@VcGpTj!_2LDCnD;TCwzYyH z4An>-Lo>`ek(p>6mlw&164>$#3AR6c{SaOs_h0#8GD7>Yif9T-9kUXA$*ky^ZC0_= z@WRuX3NqpbSdK#_<^eHM1SiSsdX54PA_8Vn2bdcyI=ihfz%OO0!9jw|tiLq)yq^cU z-zVSn=da++GZZyJL0q)`yPEkBB(rb6kk(OEB!Ws2UsE)O#5X|Dmnb|^8*xMfOBn`E({zwTBg)mQcGQ7k;Pm0`gX1=NmG10+z3@UPfrq6(O%iY6&&NMM>fix5)oMxC@6vgj2(J@E`494!{o^lw9|f1JN3GI7o|ubf`k+Vg&6dmqPj8R z|EyzxresvxHLm)w+06j4$r=?S*vc1q_Hc1jZBS)N4$-i5qM*0H!%t<2ib@hoUYnjv zN`q!;lqFu$@iHD*FeYO>uPE-v3I?SeCMSOmL37vkqt5&>;Oy}QOb{_mL{ShW0@Dyh z1h5h|ZD;kT*85#=D!kZ z6>jR%7R-a-hYwRU`Aasa4)<#}(o4u&EnqPlJNeE&&AZ=2p6?t1j&q zdYe(*cvx}}-poCqU}SJ=+7PY}y39E8y{)pFr`lyHnKx8r;u>u^5=nh#wGJ|z?C?nh zR27;vhX$LGRo(%cYD%9v2x3S$*osaX_cg}22IwiVQ~-J=$9!aC|GcnAc92~ zo^ow5rvy~%Ou;~jBml%g7w5hgH5@+8Med-fSj4L#%aTJ!3WXsqWmK@GRcW5;VtWp{ z8*u!r1@l?YDmBXyLUG+!LI?y_C#|k|;xP9wS5-_mmKPVKn?O?nR|sH(ppA02k5kq* z&O>nLv@ov1q>%H-$7{wavRL7w!E!1Lt2aX_bpwymQ6yM^v^yCC!W8WZ!n%%S%!;l| z#R*yF@JLB+)pl&qbgjpuL*#H~CV#C|V@W@8{KluV#< z!4zN}2!={L;m1KDaiLzi`IbN?gC3`NcZkHz)Y69D-pH_6SBqDj;5;Fjz=Yy+pjQo$ zRdTrb)TUY}r90=8lXi6wHYI;R#y=7qE5s}dQKDR7Q*kGEWtXOz$}l*N2x05B@^oJo zOWGEN8U;ou;|<%1$nfxH8WPwG1`L~uCJbSBdo)EO5=kHNQvmC82BcD$ndoa9E`XF# zupJE0RAig0Fk3gndW3NiIsvEFtJ!}`6B&gD#CUvP=&>X+9Sk$&$_>v!bQd$i4WW=Z1fOYCz)-X6^m}-YU`HQr+3fDeE^2el8xLWkK5*d;+1!?2 z4hUsA7VkZZ+RbUwXsOo_ij|Xj9|4cTgk0W(-x0TIUrR>%ob9pu!+CJ^&;=*~C{egd zo#|4GSIb{k1N)2uoAnqZI9zmD{2rj5aRY5MPqe(%n&n|H3*DrKjKDo=@9=E%l z2Z%Hk$>f}R$;+NC$m^};9Y*8MVAroT?dbl#;CV@}ce@_9M%;}Bay8{2UgQJ^IDouU z4HaMjqXbJDfy|p!z?d{S8dYMed~a9SjDF}!OOMuS538i)s z@y@;D!CaQjgEb#y>Vf@es9aCK#CJdfg*XLwuOp%)7*tc5BN(F>!FVXtk+A@E>R^)Z zD7MAxFL@6OjfI!lR@odRTEZAH&1fF$d1x3m+^q?^5y~J;0faDP1+`>`CV64m%Hp`1 z03{aeiHS_glFC#y26)ptz+x)u*xgU7<;wA!469VYkU+*oBZ4{JY+Rr(sG%r=&q9Lq zNj!Lk2@ayV;8X=Es23ohnFk0&8@QdO?9?o@DQh;jRNrx9Yby(l5~y=g!mt8ROz~Gs zg(nVr=^QI|b!H4&`eX)x>@Q4d>Cjy@I2jmR0p?BaDp#o^PTX+=VQtjeSe?n_+Tn>&h|KrDWbVb_2$E2 zwuMfn8rBYRXb4CPNLYnimU5^x78VVXs3!77axu|-YD1F40;dNJavTJb5lRIm0~`zx zbNJx=VSFP7wzEQExJdHw--8NdF}9qb(a zyRw929S3*Nuo&NY<7X~{UM6UsPkMkmzEy9-k~v~+Zu)jJVXzkD)qlDBx2N3t8k3Wn zjGiwvN4Mk%akZ6(x76@@UST}-AI9xzbf&ow@-A1{S9=ABs&okQ0tT6pcMb7?AI->Z zpK#tV91ys5RVXU|Q@Sv(Cql$92I@fuz~q?pJ}?}uVQ?(~3-|mpESHD_ePY+nYB;M3 zQV!7pV(rkEz!aJn#jhjc<`u>7oG(h_sy$mfkYsDc8;@K8S;Fmj^_Faj=m7oF z2yBg@qqu9fK|te9&*e`$bYk$bxRdCg6BG+WFTv25>ST*>ZVdjBu>&;&gPt1$B+C%) zoT1M_!GlR4_q)PSDS&D0*&rp#?||sRiJZAXH?fc_@cXQ?5QUaEqgcRf^VE|A5s?uw zb)>?COBgGb5oA6T!zEhQ^K99^^}K}#Q$&+c-Mc(($Jfrq$eY~o&dOA4pf&C0W0(qqbi+hOz)DKeun>^0r8OJa@ z1|FjINJujZ8>fj!i`hF?D58S-18eN=PY=bI2ieCgEe_Ai-Tev2#+-16Q!mY)-W5AL z%UVsGuLX?-7y7*`u5X-DGBrgFjUUuI{PF$nUu{sF%iOe|g8N!P^a(Ng-h3 zvg}eEsfraxz`rqy?k5-q3Yb0m}W{t zG8tXMmpnFIoFA3P56_Y^CGj|Se%Bu6{x8<)hHD=a?CSIOz0z!Hi4!CO8VCagX4y%X z+b=G4n?qfU7%6M;zyMh?>}|qG40}4bsU5>IIM7V1Qt+a6Wsd(j=}N~*!iPfY9}?8~ zIYe-t%cXDEi6|}%wLp~dL@xoF#V`MgTws8}Ys?>Vj)bOuRcswKvzTq#BWD(;QnuqWk*r6R^)cL7{`RMME2cC@Y zE7_?%4CUEW;Vi6?3S+eidm7jJNauF#R1q)0${uk9_SpFUG7 z4Fr2DHYct<=2kzTi=!P#Dl=b3ubbcw+KU|BTWA>ov7AoRPSR>V#y0_Pn+Kn@(l`MD z=}ImIRaS>##6HUf_^)gHC9JcMe5k1eP9`Kq!NovtedBaqgNvoI&xb5Hj~*9f8_3U% zYnk3wSNw?KDV7?tOVwLZkq$U0ISPlBjw{}lI6N>ku57_zYan3bC8x$k1{1~c!GNxB z=&9n=qaes;sHAQx@NrcR1leK41#7+W3u%_BsHqyP`gKM#71fuC@@xipZqi!IHODq1 z-JRn%C=C*xeucpMAC&eUI`a4W_fO)8f(WJxhALvhK!i9T)Bz3YlR1-$wm3KO!fD%; zR_*DA`XPcg00DmcA|&`Ut}|VV0k!Q{T@Bvi-~?XYSC})8p7MTAcf+6UU$O+bPl=oP z68?&gL_|TSP>~`~ibk;f-*?yCdrqYLjcWEhq&vi(_&0GgZ=opBZJrm|K**#TtBb;} z^pJT(qLy+Oprz0-A?;ka1ltN()SDrq`G}qAR8~>3!w=0+HND~=G0Sa;pLZiP17*kn zpx+i(b;zS46aXg;{lWNvjsDNQ_Clp%a=5w{A%mn;nxcjuSl_dyh@0IPfa$vcIz8Ga z3bBzAoSY3SAvpVzKCyEuV*+@`FG5buj*MW@0foaH)A5YzGqg=t+U{iR@Fg z1VvH6LYFQ81Big0U7XTixsE${4xzPUu|zwDa9kJNNv8Q5?N$nBJKGn!QYbBi+(pfc zirHyug36O)5jUWBR=UgE6=}OPs}(ee2OJus0S{HJ&I(j1B?)Q4u)Dj|%F$`TS!JB7 zXjxn^xo~h;Eo)dbP$;o9prANl)U--g7O5t-XrU_0Ky0x`V(hDdX1#2sUZ#^~p~3RI z;fxF6c!|6%X;px462i@5)l6|`%L-uN+~ZOr`58e>T9(4AGiX}GNf9K}E(Bs7X#+&r z*yeQ#g;P>8_$~csuUbzhspxZZa_XM1zu$VJsDTALpaOuJn-P$*r%)qEU8l+9%pn+_KwKqCSZa|D1_y; zOTy>w>=-~9lcg>kxd1Nh7bVJ)1pye*R09LAC#t#Zlo^IMKZ{PVhAp+ zO#`*bqI~(eV7^{aDy^^zVJ8KF1Vk`aAa6Y5t#G(CtS{1;^4oT!3=)IO22QkfElM7LO_U)_JJ` zDf~qO#0Pa+fTB$-R0@?M6;?>>jd+)zj_o1b7i27q)6Bvn+#Ju3%)`@McQz{Q?_&U^i3_r4T3=OqgS)GK@OV+S63npg3_a8w%o* z2;FCurMvaz7bOQk9ORHV%8)=Lp(LOs2q}&qOGVke16X?hrNO9r$k`f2Xr~?8u-Phr ziLhhbwG!PBWIYFMfVCteeepxEa#5u(K=7fMP|$#*N_|0` zV`No})Hx_haVzpJyk=yR2b(&wiP=gdCL{yEv>@ihfcVSpl%;aLzP1d>i3bC!Y6FDG zNoA!eB`^UO=Toc4O%5d}qQ5;=g+hV0nM1Iujly8jMAV$EptT7Q1CY`MK8+orNJ=^* zP&n@zD$>dTwG>m=u*Zfb!xa{zrgAtqRau|w{{!Jjz1V15C`?_*Kz6xg0?jUS3ebV& zeo*9Sgtpr$4`N3)xUxoy#2dekzt`P+(J;3|4w5m#L1s86g|g3RLDnEZwJK{I0ocWb z4I#3VL2;XKQws55E-wn`Ho)qKFrX?zg)wuS^;scC4l{_x2(9xdFHCrJw2W?46$MeQ z94jFxI2Ihg?&q7lD8z%2Qj+UcbzKb1iswpH&UpvpJ20reSlneHNh{g1oXt)*-@$mD zHOHi`YjU|FoEEWYNo^Qp)WE$OZH^)&8+z>qnBZs_j7`oIog6WOr&X~8p#v2vHD)+8 zu?c<%C`)MrX3U}DQLsV{bJGRG3xgwAyg48k;kERPM1aBvO++VHprsTS6GZ^8E|KPf zGsfdng^+z!62V~x!oaA=S}e(hA2-B+BV;s^aDD9Yu7O4(+OMF+VHxyb=-`zuS(i6v za!A%#3lI>3i5LvXFNns{v|^2`xuB%etq@Wa^}-me2%E51xr-3au8czlW=1m6tdV(n zdxeaHSgj&7NMVCuQ{_LlTKvkwsH=jgPvvY-fRGr$3G&T!S!F1I#kXI&k>}fp9x@+L z3;b%|AFeTx?+p+fK5>s(&%v(bE}~tIk0aHA)M1~3MU}(CS#u%j7$U|sjFMo?2=Ot} z$+9@JqJ*D=F*6Gg0v}Tf^-)|oSF_^sdro^5{Ti{Bh7K@&6nd{d2rwUeKQIRa{8`Rx zXPR2xhh}0DE1IE>7ste_&VWO-q)L(?nn9%+LW4*|Qtgz}siBECxt(tR#_G}QWmhlp zy#B2OT$T$q-o!xYMo}!iKEyD{zND%Un z2)MQe2LWBf1-t=VKSC2CE1e9CTf)6*1Ohxa=*ny2Zra6@qA5Tpf9>*bX2cezI}N!_ z`-d6GBp3wI39Gy!5}IX4a0tZS-zbK1`+c~Z^nhEfLU9lV3;>fr%0UQ6K}J+kNs6Nu z^(O*;1ZL%_oLP@^_qQ7!?(1Crs(Z7*_+QPo5y>+DTiuP*F9`p(-9H}1!-A$3KtMa+ zd;SsA*$d`s$4NKE4!=g#LV6ysF4};*`ady`7vIZtphA8#qAj>Vddwvx5r)_8|Uz>=Q-8^+;jvg~ot&aw2+hcGf9?Oc;YfQw5|3=!U3{!Ia1 zg{clL&BBxB^00JJ@_u%fE9Q-2fEqGI$ui@P8J?$emIn$U_s4P!AP&!I0x^eM1|#vj z6aiGQ`$LxFfz4!vqK|r4(ab z?hZNMte?_9chmilcl&-2i0Z`}4_)@Y3tDkOK?bcmpcPrdtLO7Ea$V1nxA^Ustt8F1 zIDVEEo&a=?_O5~lARrBj08*TSprM1%%7H%S-|4)-RVJ{O*f0xw$#6nIIoL={Xe1^X z==5HNYWm+?WCc_;P{CNVQG9#U^$PF)Yvn%AqvOR&RD31-H^p~%j1IkTPUic`2iZa1V5bed|fTAK;ETw`zKNags>0*NlV%MFfO}@i~NZ zx7GA;Qp^;I_x}Rgv+CvD?z-E~RHZW@4ia!aOZ1-H5eK_a9>1q1CrlB{cW@3(CAq=s z19omVmFRW%54Inub@-3u`CQ!ZGp>h^9}Ukdy9eFG!90^UH=z?oRAVCuNKq%n1y+nk zw1or`e^N9*)g-8rAy?IV8SSnNWZA*5pT+7PH*uWqq$Hn--Z*AM4v^(62xGKy@_tv- zde~{u4m?f;`Y!Xf2`U1M%#bPn)zh#lM{q!-{@6ei=`aQi1pw{BJE%YhglIGK!Z(bR zVMac$OnCk!2L>I!q+14<7x(;47R^s8Uyqe&gzir=;(J{Sx#5`e{QL(a{iClrtq~sP z1VUO8*Bx~NT*>dCMh77!(KF>pPXCAyc%Kp>*z`-pTI@oc%^Z)WyCCP0_~*qK787F8 z1S;_$Yu)P}FP;di6+>+E5P*Y>lGizAqgI7}ZZ^3QXVHY~j@j=zCCoZAJOF|~bZzCM zcl33sj*IchE7yrf!K$?t#lda}mu^ff)Zv9GKB=itSCXj^9^QHs1B|>*O2sCH`d2fs zUSH<9!vrQD>__p=5IS3b!xWDGN?-boixs?)HIe%T$IV3mvi*iotgJ~-wjzXOnAX=? z>euL zs()09$aEu*jlQdmN>fD46T3RZ{9Ee4LtF;J?i}C@pR5V$sOW>hG(t z5_O!gA^=`wN|KIEXWIK;+3ayl+w$GFKmgjh&mXEEPp*&WKw%Dh?Ak8LC?*4WND+WQ zU)$toAal*=oriBUBu_5+s3tr0Xz1B2y{uO(iV78VY;3HRFeu!Y8F(~vd-kxz&_&3t zN4tk^xW&<_)nUBC%8lyG3xLIumXggr^jgWfSS@rwO)v~oO%F@}tTx8(kQ}y#GnbGH zhEYyjc<2V0$?uPVk7N=Ls;liPmu6x5r9y{RD1@SeGd1|1F@KwiStsE1>Ab?r`2BIZ zA9)q!r)E37@a76$^GT6PvDa`eTC`Bgh*|kNw8r+!M4gI6=R6WNE z5-1%ji6bnSHB}oa!ePoS3xHLoh8(e9iN3T01=27D=Pt-vf&(%IRE2&zCfFEy z2QWkeGW5pNfnGaMwW5Tpav1izDeK$uyNI8A(^Ko&sw$ue5^&_%mAq>XyacCNOsOnW zftPUDkR5|{U{75^`H9p%L>-(n1ZEBco6XDz?N(|WDG+=zjB6#R3-#>U{F%#cMedN0 z9$5r$iM`22i>L`0XU5E$^wW7BftiZO_ zda5J#6mvrNr9iWaI`HP&42&SkmxV5iu&)Uf1p>S>zAY-D7~-(%A#_m0OaA;TJX^U4oZ3W~ZTC zVQXF0xuw>VBte<2T~$xXMVLK((=<($9g*$tk(oOArbDwTt5utuTVuwT2VtU>pd0E( za)x>R_71M;Ii}q((l>QKs<%oK*uIr?(-yrTSwvALwec{W zP{3wGLMcQWNx4|tx^VM*EHk}OCkQT3nh-m4EcnadK1O7{hRv8m$qRT_DxpZaq>xGR zLm5$8X{wJY(36!xPC9{Q6lAiOPLgf1g+NU#GDa>bnbvF%n|G@?PMkz=n2}ggssKPk zUq41MiSQ8wMZgzy5$-esqgIZZgn>^<_P~qmqJ%;tJnNo$QpgY-zB+=6@*_#KNVtJW zwGf%pTfm^jNTZjxIt2Hb(LO<&CnF1TzMui;fwon^GX&)`RcBItW20=)Hl{c?m+4M{arUpD7vT}Fzfynr#P)Qc< zuUx^{02G^L5zDzo?@Y#uGwLxRs)}eUo}3cjQUkR)3?T$FSTiY+N(ROiqA%A+85Y4A zfMzQ4aZ)7J3`Ag^V=;Tk>ec_$7(ubG9U6f4dPMpec%P<*wx&N_DU?hArgqW6dG<(7ylj=u_ z=mGJk-(|Ps7!Je-tT6ch)1~p?Aa@@Uz^0h9^r(TjsDlk=1Zq`%m3Io{bh!AM`+Xh< z6M>S%97w>mYOV1>MxjcO)M4rD8kutzhT>?lxUdp&)}#~bRLFqCL?(h5c46hfsAKPg<8>^jE2l0mZ1%eqL;NAg-U$Bst7K(I4Gto1}-LJy)zX|%S$~Y zcI}Np3l_p*UuF$dg?hU3SUZhP1$id=?KtE~vJ5YQx?uO?ODgLOhl9-1Gm;yX3bX~U zb;a)rD}b1fMYfazkeimw1~?RP;hopsB>h7lX+2zVJ z%C%G!;E{#}`L~3nwZ|Gni`j+VV9>j)8#JQ`#=Lfmii5Oq|NXk`OsW3b-;T*n~VW0l+I|lLx$K6ar?3nyoe*KWL z-49KjU(E6TI+fg=HtkrYZa1`Hn1 zJM{7&>-a^_+4NvffQNr#Wtj+jN%=UK-hdIW`yN9Jb@Ow(9*d~bNEw3$fZEdgASlie zlP^H{j<;LOvK#DXu}FP14z@UU&sk=)zO4SwoLfZ0Ey%M5vYwH6=Pwjq3FHj-gcefo z*~DDGs@fQhte82ME9L+(k{K6oY4`7+su>w$5R6JP#VCJ)Ip;Wk4omEH!<}R5ROFr` zfXK{<`D02+H(}s9wmbfc)?jfFoTXq&6?3Ca61@2qR5>Gns2-L{HS$OcH+BWztbb!M z$g125+Ic48lROw#Uraby`Uf){H`XcJAq?ZzyMgmM7s+roOpds07zSi#%MpD7vuO!Z zP)GI-BT9y8S0+O@yUhCq!*W@ZowBU>(@ ztlcus%?JtNC0XjfB^V}#b_V~h80Uw<@ZK~GPQ`Jl3U{q;@`%vby7utR9Zdg`F#(-t zIxOt$SGhf22IqbkCCRP%t#aHUjl?qUocGM;RPJg|3O0uT<<&%dncen$YXgoM=N^FX za{73CW0hWebz35jbrxP9#u9KlICC;kQ375xNC6lR1@wEOWu+{WiVsHJQ#S%7Fw@V5 zf_v5AzsXig3cTmc|3ByWe4ThF$MO8b?Q=+DWb%$t zNuTlh=*${^Og=(R5j=Voe+fS@7u_;g4uN59^cxR(I84f)Ek?w2jAAFzf%|3`1ZBuB z_?DPwG*oU!%-zBC87(q?4_c-F50_j*5zG`*FkoVLK4gpV%+^dY42cLPl3PPBOPMBO z=yMI4B|@y6@U)VfI%mnVVwQ zoY^AAQHYztiIJBgf&s_Za6;5QAO*t=#1`0AS zxM(4|cb*H)0#KCvv~Pl$2Yn80$8;>=aTIY?AnouElAbf}7(Cw{WH05<^0x!dJ`Q_e zgZ6vdy-yb`PL%<%etDFDhwg=211UM_d6AKVgR$w50)t^oU8PgG1oC3~5Cb+9aCN=g zmixfQ)D%JM#I`Mab0A$qNt~km?)OBl)A^m9KIJq1whR47h*l?`X#T{_ml#$xgGF0Y9g;eva$1c`y@+Tf2BeTs!jI+{ z#6vASe@gBb`Q=Hexx(XOZe_(DN-CAa{C~&l@%;*LWuFTM4)}~m5h|KDJxzC$V0M-? zZG@}x%(gaC%X+^Lqb?1gVlbXiOanPCp8IS{Sr3=%O)aAabBMz4(Fdi+1Ki+37~opC zm6Yx|85V5_pN)m`Lv7+U41^d60I${9msOt5QG_lb(@UY^OH@4K7Q9 zhH$|_#qo4ciO%@MGk%p*jj;<6`!q5J8;gBDh*$;;h7%<+I@jHY;^6!oEh4bY4vx!k z)tRxGGTbTN3MQlJKUR`1J$tAq^wbr}sqe(a47NRbPTq5_&d4chzoz_agpLLRih~?E zrx?pAtr0i6GB(0Hd@2%WE#?UQiB_t$s4Bb}jPkX_2l9Tt8>0Ac83z*kcSI1}zNiDp zKmiddQ7S(&g7|JdBY08;jdLg$L@P@(2Oy8)J9Hl*we4TOVhxs@6wQvD2gUf%_j2Yc zC!GR>4)kx+cN)d!_$!g@)?tTXfFSS6_x-+`K;^7E7%|!GDv%KFprEi~Ar)ht)|Ek# z#fs5Jz)lU_F$1mW_0FmWDhD@nYYfP@k(|P-K+R}`hzrfEtwUmCIiw*~wv#q;4QPso2?BQ(bnr!yP)a0(f!xR`I=$R;{eMqZo zqM`i<;Q|n{Qy&VTlXmemylZ4s#B zGEA8Wg+$at2X8Lq-<@w-HgTch#f8wK+!4qZ+# zJD~>Va$Btyc%!&v>m^Vyoj6Qpq0zl`P)AnMD@|v3@$IBCGp<3?E~R`zpRd1U&V57a zpYY@-WlGP|!nNR&QJ1htD0at0BI)}{*Amq|bYA3F#on30lO>1KFaYP6QUcGxUZY(a z94okX2bvT)2pFS~(4a8MbeGLH(j}s4%T9QiVb_a{3qnIYJG`wpbGC8~rBihrYIXjF z_56$~2>9#f9~L8*vqXSV&&%5WC#Kh;t|vCSr?r=f3+^P?6A7JTNvaB1KptXDM=sl! zCAbuU!-ntAUmnXl0vhi6X{QXvTuWh<#9{HxD7ekFD}n(;Fte#bQWi6bNFU;3E*vuKvcR!v@V>rWqLoDo z>s;I>M1mOFjE8oO3(*}$_tv$H6q~4RybC z_)L=>Hn4qFHc?v49P*uKj5>hz;NfTCJLEXx+XS&198&O>-EA?c2!$Q)c}IRO>V%+G z*@fL;TKM6<`;A{^Yj2;0Y6Z;~0Z>Aaq%$)iyax{K72KR&_GmzjxneGgqmZ$32vaK$ zv9>r6dDKf&DmK75na292Y_yK#1nZRc&yhhY zDozzGQ5q!!$4T8GEgR@H+E*?@2PkIm@o-y>%dP5m-ciJ1$0I|e`?x#%X>8WXilqYu zILhGtW9D_Vm{kN#fok8HVPFiwn3#FyI(<~8~CVPs~9iF4uj-y0ggePYBPjgHj$! zK9zM`p5y6RWq_~*PfQiH!$MI|Is^a+G5}FBB4Zoip?M0j;3@t+S}S^7|Wi zkRnV@Y590=$GD(h0hTfI4y!z`1EV#kgB{Z=spoYv^*3fOR#OB*0g-fkx*R;`hJG{Z z72*omJ=J8qEh;%pdYJJz?BOY^KN%RJ6Bya(Lwyr^&NAAJbf31?>e|L6F{)V4d5hys zMw?t=?q@7C%ul&NVb?*OKxGB2cRQ;G*NEoEMd7G1%UM9rb?_dMl2`Lq;$*Gn{H{7k zXPAo=^Y}a!y3_bUA@P+;Xbyx)2u(g0(efd8DEQUNlcrFm?i5tdYPHmwW*o1x^3PNL zd=Hf2`H|S=ZL~Yv_zQQ(hKwf;|1^m-s;X2dXGgN`dj62bm_WXUGcJkG43PE3S?|Cc z6BYwnGZ?`qC)wW{c_=u2?Nz(#a`&QEUuKGzdb_OzHufykBgM*U$o(Z?;hC_^<=V3qBj#YO8nqGiLe3A+jw-Hl^BHIrKUn%If0k)(Qg17JZzG9UylatUm}0W&ikv(SQqq(s`GWEVa}y$M=Ll$QKeWC!>a_(L410D zh?XTUYG<8)On1lg4nNG1!=J61qM>ozY+0urvVxR|sNcx6y2SHBm0QptA|_kHwHUD3 zVlhURH9I=$?i1aKd&|##E!M&J=lJaJ=~y{(ZFSP!OCQh&h|PLDL~#(Hg!5Swshorn zu~2ZUo(wV9fbXJ)j{4^Y`@ilzw9`=1Ge;JH89K{#Fxt91{Vf*^)jIEjr^YIPsR;@9 zKh!qdRpK_~brlvjpXX=4PQ!Ws3`G$(_88X$10gWRYJQXHot*zUn$7p(NQTGg*xYcyV#~i#U`+ zmNHN|1=UnePn(GBbK2Jp6`_-xx;9L>mzu*7Frbd8VM4{q7F{K?fbf8lNWNi_6o(=S zAWw1Hkhqa9p&&hj2YGA?0UK0fVO$(^kxkdfVH&7fzeP}Fa3s{po2Z2vCs_w^z8-Cu z0`W&@cy^8DE=r+IH9Mlo;JyPM@y7%PVUj-TY2F+}#)1MmTfWW_g1y;+C$M8g>)=;d zuF+Ap*NxuiRr~yP8k<8fW666$^X)R)=mK2;>NhQ8kiQIlKfb2|*%v-M3v+%O4MdzgH%XJz%aE3~#?3?W;CVpv)K;Gr^QxT~Q z1*D{Am}^>gcn269@FM(r%)+F{jQ1xCZc@W7xnMCwoQGoVzA(MYtYhI{YZ(fr832|c zjrD2;dtw9!Mzmw5<*av%3rBz)XF}toV_9NbaX_I5Nt6Uk2fszG3_xXPzL7u865z8&}2c*J9>m(?6Lv}Wb;kI47X0i^1Ov`EfE7m z)d$Q#!RJx=D**zxEG4GRIU7S2Z2*EoVCuo~cO-M7Ex4Vcm z2#M#DJ4s7>IFg`XFy7AaWcje5WJDI*!CaE#n1xjoV^loh*NbIytw0nQnvhG8ZtI3g ztl@3U2~&2Z$`2$vEf$1#3!rBztAh*$>Ryc}aZyDoqEH571r23Hv#2CdO%>}_oK$6I zIt$fXV~5^eDgw}hl_bUBD4DX!H=JncV`jC1~^SZe~bSCwXQRHR8hj1X_rL zX&_#xgMA+USfUCONnyyh%3&1hV5yKf8ZL)pUG^>Olr5^cSa-s>lmvw;Sfv;iZ!oyt z(t$SyoqwS$NUDWiCShOS`Bf|i8z(#D*EH+!NqxI?qs#K}lFZ_UtRyK`P8<_~lpCG6 zJr{!S8@^SaC9T}KMY5vFc2Nr?3=WR4NyP>xot0OLuLGnJ#zu^xWDk{+m#o;LraqZh z-vdG);JW)9w81Uh+*ANdDNw6c6*x8;lw>6Xg;2+^Y~m`)O+d+;D^RsHcCN6_@dhw{ zWZi&T5a<9(ou)iGkGSmk;CShp(ExD`wJ8kCuQCXMSoj zIlW|Tc*{x8HkbaxBDw|_F(<7nq^&CT88s0AU*`DXsthsl8=LezW&!{Yo{|G}K*NTi zd%`h34p@zSkWo9fKAt>#+C?fuuFn}D^<=3QQ)L`Kz&;w|A17T$B-x0t+A4ctQXI<-dJ`7%~7e4Fp66o6ryeh7_;5pcTl74?c&F z599p}&QwR{n?GLGb$Lf8k=yF~4rOs|R2_6|Ld7)}27I_}r*FnVaapx%-L`pxSXgiSl{N7mS zT_>D*cdQ!|8=~u-JcAV!Fl%~ewu7lk9k*Q69KYZA`TV~0e(&V|v+m=It4a2n-=Apk z4=%?vKV_b7GsvVD@#Q8tyUDwHBfUV`^jPm}e0h@@pWL{6;o{b%Dk%J-+1{ zFuw#Bpi%a?o1dA=k=eHM5dW1PqR+xX(e-u}^y<2`Svkw4uA6S~{XR5+)=>)8cm2&} z2l^aAN;PVnhJGD+001LB53PH$Pf=_>63*efTzunq;!;sD5^dwSRr0+}>2|1GICKIb z|BGX`udmBP+>~?I0Tj^!s-^!l4xjoKW1`Bai%%NMYosdw)x`rCBr?(IL>StvdkPx15IqK| zteOHnkL$Uo_1-}?6(s=F{OTRsj7)sg-@NEphL&M_|O zYTD-#7)nYOkl`-?MG$8xK*g4c0&_G3u*U%cN+QyC>d^U3Byu2HAbw68Ku_WP{CG6e z9Lg|SQ3hCN&F(ZkfdglV|C3kKH_!0k0gj?0B6(@0K@zi^(H?6`$e4tI7XFmZT<0b8 zony4dVcA>mkmOZ$b_8N{d}`C4&Iys6`RC~~2Xk-j888DmJSgVk)(I?1*=-VfApV6UqJDf&V8 z#4-s#SjS#w?OF#02I5j}=4bDayroFw6)M0K4%IH|9IFEpQLSQyC`Gk9gk&r^S%F{) z8(LUVRV8igem}8X1mS)A<0JhV*!m%${3hF}*1g*a(Tp8gieXJdT5a_4Hrj$n2?;AC z7Pz1<30aipKz+gL>R4fv&9f=ASr8qA5Gr&F7xE6m+pySm)^Uv%-vd1Wzz8-~YRh25 z3C6iyotB`Uv0hw_Q)5kmmbLPCBzEKy7Z$ZLg|=0gd`VnkX?av?2fD=?C}HJkoJ-m$ zq2cPZd>-uqX`2Biq?7g5gAv*Rj#K`w>cBD`eTe(cCuU{3n97^uRv1KZl zkmt~I?RdCo8=X-nVCi5qj!l0gG*mZg@v9V^+dZ@w7^kAuQx?5@jbA%r+5S+#15ULR zj~qCPL9jh4O6NuM)+~KuN0I7a+E55_s?KR%(V}YNxb+ki6|^)u<|e)BgDi=+LUnWU znLo%uvt(#E9}yop_AK`Wd0d`F@UzOfQ`M%5eP$kW0w)^6uX_IVtcT!Y*61dcPvL6u zB?r!13oL<5RvuouhYkJ7BU-6%4i$A$S83J!9KY1}Utw|ufv*u(MED8O8G&28{ z>wsIU^W7z>PQ7-kM~Q2p4}{s==uL%xa8?*`?PjPM$wfIcmd=LW)sOk(3%q06VD6)w><*~1W`qJAk#_&o1f|yO@-RTUH>Vt7l;`dda#OB zdLhGz3{yv=7QtSVSfF7c7_U-_d;?T!y0>4ot+32gD8|UZ?O8JZZ5(ivgSd**H?!;< zp(UuDYCand@)VqDtAH82{{3g8R~3(fR`%>F&v#<65)K`InA#cJdWz$2&0cjlf<;P@ zs|vx0GEi`sTqSRcTd`WfDe+pN$$`s~NOB-lX7Z4og$1JaeSLrB6QdCvOP~NcKe5Q- zZR2cjvyUkqZX3?~pVyAW)8B0sR0XN}YC!MzkdrOgAp$m(4Mr(FL%=ym77E8l(H#}L zo5kL7#>;k`_C+}y;Z0pIV+ubaArg$pB|&|K4YM9TI{@h#gup|Bti&s*t}bed5W_%l zZ+yBPvEuB7{RUCaE|fHpIE@JS?&Z31WbsfDFTv5zH7t>ZH()lF1=o z#z|5S5uxBgOXylX?uCGO9UweQ#SSG&9wgCRg>))t6wN(cel7VD7! zOrOKxADi`jB(=egNvsTIsS(qU3fn1hv&2T`GBPCkX)yRB_0Q~e(Lfjl5@?Tg)b<_` z3(V8YEr+S`uxnS0TS^||eZ1778`6p@NR*9pO2`WYfU>|DfHLHw6>Ogeg=hO{fMGYd z=+IV~j{$@qo1r}}=~H!p;~MulVWx)d&Iarxum(~x@{k7<=~NzjxvK)cKgeKw<|7mZ z7R0n`+I-B{7QZG+NZC=p(MEv7Mj$MZhejpIN`1p2UCkd1ol8;R5xm}4w4CA+?y|$H z@T(lpoxfrFHaJl@fM#x7a;Fh+xa->KfSx3lfHMp7H$841F|>GQGLni5hwx_h=!y&(0;9Q#usqY3;rp+VxNly5J3Idk#pr>`kH@791s7A_ zayo(0Qc|~%e~~>eyx(%JGtRcorynmyBU0J`IY&dvBAT!WyT&?5+>c=Xr>oxPpaN}4 zE=gdhgnr;qte}1KV06Re2A{?8M0AIV^^TY-9_4H6eS=UP2AKEMJp4*TgMvNklAxfs zk2CTqn``zl#G2`L!ZTLlNJrj9It=iA)e(3gHjJr?#BI8ty8{u9>R7I?#1?RqW5VHR^>cJT} z%>>YomAN4PY2YS9IG($lp^p+1*$}JNDpi$N_5h%MP5HGDEPq4tENu9Z>2j*AM;bmM z3hD^SVuLo=*zIA6tq&TJ*!>3^AFgRgsVdk#ENL5RBTzJ==tPVkJ0vp>a$S*L<7~($gl=eJ%f|`hf_@K_mc#BM=gRkeTLqFkTLN zoDvkqzCl69^%WG=b~2rYR^2Nd$s)x|E^0ZMh8}DI1uG(lVP3v_GucJ-ZZRO-fSD^U z(-F7R)BOCrT5&1`@smNVedVH5NhP@!Gy_A!g1&DSEB5&uwdE8dfREJpx}v&l#~fxA zfn#`A%lb%5ITcjRuunmQzpY1?@Nv;{@Ny17e@rO!SSa*^g<$F!vk=AFYiOZN<%1Pe zP*a}#s*6%wJ`{(QU`DW9*j~Wdb6l-cI)D$+2WB7M?n)FCT#wY8e&5R~Y%}wu- zr!tXMORklBy14F|SHJL5iJeswEMcriY!N1g4YYjS8zr;TDnpcTX!x@NpDgguOHiY3 ztx__KXiL3Xjvg73gu1>Hc1AKps-J;pY_TB9qA3aBZP>Vz;Tid@ffd6BO$w59&3R@w zY%x|)`t$u-0??*&wA8Dt}dFcRZ$QK zpk?2ji+TJ))f|gXo)1b?6e|5 zhM{OXoE~TMGiz=lo(ZL)2z8r{Xm^Xu8J2pkY7$5}%ExV=4b$9j`%*7M!(1uZ;!Zs> zf%boB^Tu8LYI+q&_?xKC1~u5+CF6BFQqnig7`X1X|MYeaQk~3)lGeTuqjU|6#~G$= z;+h=Fr6PD*rWEZKi2#^EsQ}KHR$ge7nNvZT8HlFJqC<8y#Y~BO;hVDfV^!CHV{s@B zJ6n$qQr7<;4s&>}$SHw|Ds>2XedMB+4<~s6tqJYJdBxnEEzx6gksbdg8D+MWB*{uN z1i+wjP-;{L84u-R@rtFHbsgo474MZgQhI}^>B+C-S+zK=j%%LTwIZ$bqlN?8jgHq$ z;sn)$Gyr0ZF*!yfrjX@GD!`{#72xhw8)__f92rQ3i>fwi!!k48CXT%(mw!U9fE#mK zy5P7JdmSWqUK5v%uyJdO!cyv+eifx@u5l39CED{vMIuPy-Z7x%?AvWXsw#s_c(KX` zG^JA$YQX~ewM}RirqxyOYWU)_MGJjysP2UdI`KACS`&+CMT4r_3+W_TK@=FfvAivq zE0A8C1FwWzhO0;0*XG?!b69m3BeZ7Slo;ieq35vzBeE(vTHbkGp;N|4BSGVhbty;K zKI##AxO?pBV)c~3p%agF?^(#toY`EPgL-5OmSTyCJjLQpL0&INB+stt9#CWi4zN0q z8c6JHO6C_fa01kaBeD=b#fLeypM1e6UVsrK0Z8SgzqTeXVN{3=-GXsTrAO zg5km?%z((w@VjUq7rW!vY=jZw?~KccemIc#e#6e~m6|W`do(l_G56ly? z=cHXq0^n7pb6{8tqR267bGmCp`|l~Qk2ep+!uD;o7m}S1G8?-tyxY0>k>~%$_Kgkf z$~bx3UVFVuGkC9lvf(%Gt};eR zSKGPDF-NzliG%~A1GaJsJt7(JSV#s&;R_w#-9qJGRl*13jZ{}m`6;5g+3*UGQo`Z2 zj>Cb^OTBSe!n3ig_RB}{`l)RHe@4R#hp9Mh%lO}%_1mrv|RZIz$40d`@`U^_U zTl7nUe}zsaRm(@f3z~wrOqJOYchBvSrDO_)80qrvV}l(z9+8~ojNg6a1N(v48hWabWcclblVs;SsC^+ z%*lnuPd8g)2^6H$qYZeCB5-XLR`r1MT2g>a0x4U)6uoG&>ob&7NNWWvD;5ud zv4Uts%F^vhiwah=g;8MRYpURu(76?K&v`HOEeRC|!XQGT0#b3}5+AA5+*)burgxUjXA&!c~ z>Zy>rHkk_jDdl7!(6uteP@oR3urjFOP1Hh8>9$k3od#L*Du2)DV?73%=324juQLl`dDnU>N?%u9R9TWoLOb+aN|cyDn}BpHwBuCsl<5MZvxv@ zujU}6S=@C{9F!6X1(a8Vp3GE(%ArAkNGXZA1c$UUm3088B}&ovBI;QfE~SzZ12IaB zf(sNnqb=C$LWHF74S@r%7OiYi_fpjh3c1zDYx;YwGmR+g!8N)L7*UNw(&7jTe(*T? zzA9ZJfpJ#0bDR4e7L9eWxEM8=*bZ8agfS$6Vibxg97@#LM-_|{RTij*0{yDXUn{dP zHghRxkSA>^e->l{2{q)Ptcb>A(B@qvhMDnDl{DTqzU~!?1CuP6IW|00aQkzpgNd5I z4-Z(|fnw&@d%mr?r6UcFEJRQ&NrQ7ZaA>ryMPzWgr=N~l ze67FJ&%jU;0OnOyPeiiPynzZ_q!cX!K~+8R18t&{JP8RwgD~vR zgPo(-`96h^{w`dCB#`iUmiR>1H_sbpwqbWDBgHbgv)0T3G`oVK4ZmRGIOclN25-Bp z&uxUZtWRc8K+=`Q_7w2fGKnP_>PO#M2{0@E2t5i&zv!$<`}nJ|S~#AT|j8huz521G*Ype|Cq#;U*@;eV4lea;MYDqX1ya9DZ6 zY@qKEAcRxq7~+`q4}^GF$bf$Nmv=GvNe~na6$8o0LHUSc?+G9jsZAqh^=lafg0e_Q z3|dV{GX`S4L{$LNW8p9(nL;uvbHq2?j0A*PL<$_#{BCELd`zq-&_WQ8M=5^HzB2C( zyWNAoPjcVRVWZ42;Cg!L*qVas-=G(n0R$&Y=qEH?!AKpyKDK6UI|*RPG*4h5+JJW8 zbl|1%Bm?i^s%P|QPqu*gF)SZSLF*BhN*lESI+`7Nr?WS~s*Mcz|EcLx zLA)Rrz%WDsnE*lQjD;jag7Q#Woj;RB_=NkDB~dt}D;Na}PB`%6>zvgbKPZSzNx1TB z$#z4ubKy>$2oV{8OPlveiXBpx1Y^@*EC5~Z8;!qjGhXn@Ew`4Vm2JoiV)85uaLsM; z+32PB&ij6Ge2a3mrYRw@mm4y+U#_$lR^0p7H(qT!_Wd{F$xe*%;TP(k4xs}uB7A2( ztQO?#_8Pl=oHfVTN z@xnCWmxG0rn~cj8^;yB(z%!Rpa!Oh@_{PrM!fvlg2z5d3X~y8Nf7V;{Kc^f5D3X?w z_u#@%#8Tg9M&M12^y-yVWz-j|bvYcP$7~@4F3!V6RXayJq2RjRpPqQUQapx8WPte_ z+N#@ep(w5K_tva2HCVFns5x0V&Re%u;I|l~SFsp_U1=f#3YZxv#RhEb<{Qe*3~FMgfB}24rG-)a>6pkDq~aUJM5h4gteT8NRj0v5$d;Zq)&&u+|C2iPSF7In~Q*PtuPqg8; zcVAx6!wd|@EvH541b~bI7AS`f5OY3LIc_AlXoY7`QbF|A@-r8< z%)gyzky?QP@AvSZXIP?eH5J`WqPT4AfYT!|1vas8#5d-my@t39R?K2UE*ghVgLgy! znEsAkGVF0<`dIPwkugndsj97yCliNBGDiLVgv7M*1!xp9Y^%m!0jR9UiBtm?H02r` z!K@g2yo@zCmoj(`!=V+bpvGO3Yv{muU3p6pq23}RJMvAAIt-Ika?3EZfV2oZ}u{JW^=Ba`B=I5C+l{rv)SeLy_AzBae+z z=mtUsKpbZT1kvMRk3U|nEf?+QiRT&ecHf^4|Cz{5sdua_crM3_ISq<7lzQy6%3<8e zvyEBhAz6YMxwaSKGq$F!z5@niV929SqUF$})M*c)82~fuWTk;QuHsuFfvRjqX@0eXoOw0iRWRe9(#qrdd658gr&Jr$vM5S z5e39ZJb8g|GMs@8=aB*NkoCh}M>Q}mr42(oknd)j{O8>llPD2PV1u%B>;OnICx!k z7E_%9)cdnk_!xm`GXeKVLRHllY!BKYArJ2_%JFjm#FCveuQ2LgI#Vq4Z=`Ue zJZv<-jQWHaA`0vdDH(0)rfODemcUIn9MrAMy%-^uhcy{KL%3b@bw7#}?p&^;^QPc~ z_8-JsV6&{iSj>%+H{|EPSGXP`Rm>37mrkjCw6zuSpHoj1df+Ta-Av3BVbP{}dH+SL zxUFffP{U_DJvU#aQ}x(;Tu78z0?(1k>p5kh|+Sc&zV;;Vj4R}CVx zwI8I3N3?5EjVx63X9}c(gVu#PD17UPT&`*b_DjF$#;`kg4^FIhM9^SZADPU9Am%Hi zQJwTNDuTLcZY*loG}XCVJT5OH1zSYyQmKk8>BA_@YfwgIjW$lXOe^e%VVyz&D3UV*yM0p z7L5#0+FVw51vtIxr6O9BWl&R&T#tIzGb|&rB3CKabaG2!j%qCsQv~Je^Z=%l+l%E9 zu|0O&Hx;#J`d_x3v@PmEVUrev3X>UtZBmzdS;Wc}rhDO;iDc5FGk}pyd7ZlyiwU7ppX~P z7zz7PDzlpFBs_otPoF*O}Zx= zljwtpZw&zf0EkKRkioHRfQf#*b=D*98a+c6C@*0^U-SeFsgCe&d=6*G_@;sT6OMTh zq4SnTQo96YQNTfD z7_{l20%|7plW#_k2%z0Zx%o1?gNIu^*S4t)^td|dS|xlr%$Dv0+yzCB)l~tFz>E?D z!-{uC&$u#^78^oXf)bKr)oVeho-!JIJN=9Ii@G)pA>uMR`CzV8>(%8` z=TsH>EIA5oASJvaRmKj^wWwSl$4y7XE;zyF z+E?&PHXpD;1R6)<N zL?k!kP)Qj(?hXBoI`<~#*ol0QCEMIB-YEo>Ab@+s1l_&mI}zGo=SsE)Qh`E+d0@w$ z5kZ*g%)2O^NkU?KrC-mUOSto$2c9l(n5UfUWCEASuBs8s3fVg)vS&Hpwlkq! zJz@rg@f9I=B&E#Fdhc#knlmp=N}N-Rc|nyd&jl$%%QTSG#Bu9h!m5BA1=^d1NAMd(~ufMcC9X`+0jf zDDrnbanfCUovyzycuw%VoMRc2)=uwKd32&ysa3(ORL7*a5GY9wRG_DtqSz`aK_>;A zT@-$>}U>LwvC_;x)fG>pFYTF3%3)$b-K6y+Cf)a7U z&LHu#G7xa0fE5Q` z(TA@3hYdB7g!xs3G+6b=XC~~btc)3$GBYSiVIrD!{W)1^Z zg%Ox;c8-0B4jPvu!4eV;Fos+cCi$KT*NEdX{JC5yBLK=g?%n#ToLD)+4P^6PgBr31 zada^3t1cQXq%(C{LSR6tM}^5&t2^59qx(C298X@)k$7w?(c~D&w;eD9jxD|acX$Ub zEMJ0+4%)6{IWjaLTubmuiv^NQ(VJ$*1-yg8O1Z1yjWXrRxkr1OcI9{%C(Q6RQ5II^=yA4!ht8WxX95{obxMtfn6EC{vT*#EWUy!CYOV?e|uNd-2}n zxcos^U0UH$%z3@_jwVaWf};`LHgS!XoA&wUf*9v{&b446w?wI`y&b z=X_S3-yyL!{Q|JR!^W3DI5t_Y?&@YMYzjm+Oo8wALzT=Kk6j)MZ!$`Yc)Vl^>Onv_ z45c()gbxfnL^_r4uZ@C$Abd#7GUnLYwpJ;hk#8alsev)sKJ$>zRj2GB2H!@AX=e*L zt*O{~1pZv}BxscVix~)k^uE+SZ_%;BV@`r0#2Ok9#1SuoSz3?Gzww?R&!HFoGvcR% z9v4ydEa=MPO1cfXph;fkNZ>lx#PXupR5;CACGw|1wrtUwGRVb!W3f!|Qje(|qn@v( z#r>RA=rJKZPGP-O$+x>E)xO`alxU&jG6bATvLKNNL5`soj8RgtO->^{&?uo26~)v{ zBE(caD@I3+dSb%a`j^l#l`%V2%~O1FyZyXok52abhYqXJjV;eQJq@^4_M~~sO;0(i z&4IL1D6twYMX(~*Fz)4!L{Q+XVj2od;HGaaMVp0LqSVrrn#+osJ=LaKwqL~dzmd9Y zjX~{pnL5v=PH6mW`!!vPIC^_QwCm7c2E4cO4DnaQL%HKsWT@1mI74czJMg z5uaebAF{gkqRuv`Bh@Ut*uD_de)jt_h4+e-pj_ZN8z?{3zsEW@UD%dLax#u3c~}HfZyk`VkyCs z@F}dE#>BCsJRJ{L9Bgqx%JRYR)#+je7!}~Cz{78iFC0ool41d>w&r!OLAzw{ts%9; zW)KVo_2DRD9t4dhlqMqu*AzDCjn_!>oPthThylkmn;jdQULoT&z%G%91}aY8t507E zg3{+1j5NU9;HTe>mm!6{J;VZ!J5}ntXOv_>0t%XFzBue_nw9MIyRuYG8({9!%_B(VP z=y-6#Fj)tO$V6nL6x4i!8s}E32c5#?CA$3Uw8T6qOc?TJBodV>M-b=*+>7E7QeA$g z6VT)?yrKiK87d<%4?x8Q_I|LT{Dq}DAp80KngudejK|>Zz!ps<56ONnVl5J>6ma%X zNzQeQRBnw9_;U|-)?OVMcxZ}e9XB$$vkXsBfX2k)COoy9*~5wv+RtofDqP8TO^4d& zqx7O>fmpmWeV7PmK8E(I@UB0Zj9gx7%F3bDOe!oC3j&-j&f${|_r~QcB1s}2UIp>A z`B^SF^5xC_x%e7(%r|E@q1DXO$Qt`unz%jB$qz^($c&N_1Vto~ktnH(N?0MLYN}#V zlKXi8ihai6_^Y8Tbx{(}Yi23fOLx;JO}{}gnrwClk~ zas}yHdXwagDace3oE#gv8KRQx22Q9TqmY1z&g_oie6w?`ugi+ z1`3*FbP$~~1Dn2~nC|nqUo2@%&{DQ(+sEh`l^F5Q02h%AKrZ}EU#m@hio$2fM_PW* zJFqdWtygX$s;#QVor;aDn%b%D#X_>iW)P;>j3+V9Xf3dSh9xe|kLB@NUP=#t-iwxPM=Q?MqBqnG6%e`%0pOjwn{HQS?^M(yjGLkp9*Ki3$cWWDDC9z|bQ_54Yg}AJ;>6=< zK=M?G@StZze&>5OO<1aP#&pDG)<8n{%DA1>awT-eD^q$mcLG86%8zG9!XUvLUS6HR zb$8cuM(O3PcC5Bz&BUP!c!KMbN2dEDs>t-QaeW^(&87nVp8Q9KPI3EhlW^wl3Kgt! z{UH(6_4|`}&PZUE*~L7j2qG&tj&IUIgYX*0xPFfcRgkLQyWbvuotF%*Wx#T+nGZc;Y=;(TcfZE#DKMTH!Z}e}_jJLmlkVDjgPs#dGh$2ts z5v4=o0sI5;0DiS!FztOYT)1G@P=PIB#puA*dJB{8nPK+Ia-Aj$qoJR}dUSQdZnPi2 zIOCifep!O}P)vVIu&=%iSUE4bAISHLJTt^o%!#J7w^|MJ@^Dck`7*Z;Wabm&41UFU z-tCx3N^C812u`P~`AtTS9skFG?0v`0wH_++Em~EU?}mY<{8NLdz~hbV>~X{j*Qf3KlWA49Wownh2*I0sXGkVvK(qC0 zlY~vC`~B5BTV|z?Ky@6Nj0>)snS~7GF@drb{za_mg(!?DGlo{uuHJ#=@vV)vZv3dk zl)gE#9G#O%X7^j!+qd#6y6JxF;>XFy=j8?&nN)sxN(Z$R@KPK{DWI7SEG!Q`!(8OV zpz|z3NKl;6=~xi8wa-poX-1rq3t!SJoBokXxX|@DTLQGj__edvD{8A7F7}6Zih^NV zSr!xN(y%F0WBH5t&3fc(U0_wSYNMPWDV+F5wX#NAuXt4KN=kPsNj&$;5)>jC!!S@n z3a{;M{T0OJTqZ*t#bSd*f_i?`74c&g60upkB2yfP@K=UPqaVcRZF3^GPySZmL%VR_~z+Qwq8e+o?# z$(S5<&iJmu)YuCSggE$^D**I8 zK$@@#3fCo$#LJ&d>2^K*lT0=;h4N#PW z>HXq4N2ma*`_hJ*h*P-(SN1TNC0+Le10#yoVW!9uu8g|vw zT@@O*cALJ-v&lwJwp5=@MqkDATX&X$h$idjDxxt62_5A|Z#}%NQgl}n)45!6!l|p} zrn>UDE}m6;K~T#kW3cE~<@|liHNG*Wv!#pcO*<%7a~Daxb4L{xwhJ+@#LdxL&JcI; zRbK0AIyaGq7-JnNx3N3EQA{Z2*M5Qfb@aJ4PX{QlQzsGuRoS)m?8|zT-AC}G`~K-U zoO}4Fspbm41z#iGJMOVL_Me!~F2w8>OIbrn;E3xiM6hKcp@_|K=B4udsK%NeEOOZ6 z0t`7|!GeVb;ihWr@l5gPXq}XCbp{lISifA_JSHx*=-=3T(ooGNh>vBY1)QNm4BeZi z7O4j5Mbds00bH2Sa)tbM&}r9|pM>D0SuW%4o_7FnI6p#sJ?PTy_hI?)5dKH~JS#up z$-eZxKBMOnn{_HKPDEJO27ClV@|;bgruy=}XDQI@Jq%m*XL_2q9%9t(+I(@km@e1h z;UkeDYr~l}7ZB9-r<8;5Vbi=`!<0Wt32#(iTeVT5%|Gu)3f5+81^VD z>s#>fxSS@MOS*`*f$yI{#oa#4@aDWb$W;}tsF_1OG)%ywEV~&#UBV4ndo874^ij4MHp^B{9;uEA*~iN>ZRDPP)J0Bcb8_R3!$$Pd^7cQ38~R^< zZ^XXzA`}y~&fi$k3k^XZm0ebK(8hAu=Q-oApwGp{q|S5UO@s(xT-Ea5P5IerTh%Zt zSj|Yl91npZ#*WtZZzX{R4iw4j(g?aRJkMj?-f`;uUbn}ys}v>vjHu1v<);F!7EsHFw zpO=-WuQ@$!Y#9Adqx&D+`vW0wZ|ZAjl8vd=cJN>D9*5|i)uq&EzH_!qa*g~5SCyF%|f=30EcDl-h(AcF2M6XBkmd-UGH#&|=t^|=9 zsW?=*Wb49~3Jd!OX^08|BMFCe1DKEJX)K!$V`7StZ28&tl}uGdi0VVd7eU4*TCP%p z*YkfC^l3Q4=cV9kvMiQls#FX|Y!}Zra2Y$VyIf+Rt{qXfJlbGdi^N$Fld6paDDw{u z7MTs~Gzm4$TCb@ViIET@P6yo9D^P8GsAnTtndmUk6ug8uZsO@KBY9H$85*YSK@rIM zwClEk!HHZr)~-7gY{7KpeAyT6Rc}^Hp%a$8ClL?@cC~zcI}J3PG$Sa;h~|YFI_E4F z*h21G%D|P_^dn%g*2H0y9Oo)ObMY=cw0^=Nes|7Od=pYCVEF6o#f;#GnLK#EjrMtD z&c3>RmT>(Xi|2ZI4q(}NtF}BVZ&khHukF->!;m zC#Zc57r%$6GZ#6GDpg0ZQKMZwtAZrVTvkvyG6o{oXvCoUeomRWYMQ)^5r`PSGeCH$ zuB2qLDq3&+58CawCFO_3kKx{PBAGWRwPu}@S$b@W*h5iN>rh#yOtCpj2!Ly_FKdT9 zy44e0TMEF>mzOpetxZd_pp0e=*>O;!eF}j_6{$`z>1@*gxj0{zJ;*`A$|u1>X0B{Y z2yK!T(KJUcKC0R8>y9l=ct0U~aBz`KZ%5L2X_1#4ZUx2%MXTbtOSo0cu*v0$j%ZvC z_RWQAu17lpqXVI>BWCDxj#yLP+c@UKj`b4xZKL3LOY*An8e3Ab8QhX{gwI?S(8Zy? zhr)ufW5vJ9j%z!kqtULfXy)s^z)IE7JmwHsCR~aTKF2J4=r*qX7U^)&cO3ynT4%mx zIOb-;=uPEtSj@oiq^V@)B!H*H2pt3WjC!wpblpbVPL{^HZS7FOj=AQZiP-?f!IjyW z9UxWWl9kHkfWkOv~XIZ6g)KSYMt zxP?A@If$zbcx5ccyDTIr>RQoI`KrBO$mtirHJrCQou)8i%gA+%DVz~f(XJJIpvFZq$oLrtx zKdB&PUp|{OAxGHyzK+!-kaRjb9{!b>!fm(Ci?>^2syRawT)QLDjYRmK_8Oj&q@P95Mc}+$Dw7LW%LPVy;pBjWpvJ}j+H|DoH#e9 z;9H=M22b!LOenz&41ygBNkr_!xmOU_InEKGVt-L-NTBCc57LhB`)dTZaO-7)HVjn< z0S`q0Dw?ny8mJ0KiWCr2+cm3aGj-W*@6OmpquOmgF+V*xyKW1DOmL1C1|jBpV0A)i zY31(6YAdM6WaGR}S;sc9fGCU9qMZ?otl5UIXuE8}^f1I}qw_fjL8*%}&@Vv{y&;h% z&M9Gpki8bRXy3(SEG~GzDOIS#0b=Er?#8Xb5WJx;s$a0kDJ?|O?KYY#uWru%bso;Y*Z$&EZ+&7&=rP+v+s1<&2K zi9U$qd*30OGJ9CC(y>`Gi5>7snUDweH`t5FTj$!KUrRx_MKhFTNv|vR{9(}1-K!^t zk0YqZDx7fcv91Y?ND2eFL>FgyQ&^@2&iXgjXo}v|8BHGF2&j!TS*Z1F=y33?;TQKJ z%*ycfJO0L08Ak_|ukmHUN|e)T==xfteN~u>2w^RSN6|GI*Kex$TV?SsATC)g%~hhN zjB(-93Q|>UEPhzBmlKJjwstyoJlMF+qRs|$}f!PxANB6kza2y znAjI71QO*-897z{Gv)rvb*&scyt?j~JM~^wU8%s>ZB_bsD1$&@)i#31^0}td=t{e=Pg>F%vGT7?Rj0>LGy*1TqCQ6ioMt15wTa?yPP6{;cD| z>>>9at)qp=uAaH##oy#)T#qzpSH@(QH4XNRLCbH@)w4e4;Vqu*muQRvqd0Tvw!ZTI zZ2oEFxct4_BNG3W;{fO!Cc8GsRLB^dy9Rncd?DivI-$UPXM z9|@s`*yrB_lE>nAngasa3Yu6VdH(}?n1*T^(mjCNVj0tTk|t-a^~<*nqNt@b8w_z8vGm%d z!}K%o*YvMQ>(bKZyt?sE;AxoeX0UMF+^nj(%a0EC(APf$R^l%T z-HFszrcQ?dHxXw>;e<}{(AwzNJ;O6E9X>O7AbCg7I(h)qaP6a*6SdP5eK2ys;jnC?%-g?xY8$GvTJJlihQs|DE_@s7XV#AqQ6Z*xhI3W;?-XSj8)A# z$BJR1M&(u?HeZSKZVkH7PjW`P?Q7G13>P^FE}WEn642gc6p0By z^Gt$B;Qqopa2=*`jgFl}i#_r3AqeXSl-!pCk%Li_uT6vYliA?uvy6|RXGUOp)(~L6 zW!lxui=A^EVtU`vR5@srIyB1oDYA5P8xc-gL!mc55%rO8wa}w63vQMUaP^;pq;LVi zd1sCC*4Nc|OIcoyVd?0R>Vg3v0#Id7d4C6zv0tu6;6kZq;>Rp`o#yn|KPx7Pzs%+4 zL=!g1d_T#q#^$diS0K$5i#%bi%juuE#goq84FuTnDE-798m`5qiZs7H9)vI=Y`_iu z(z%m7!Xj`G(QMb%U1KchoJiMI9OMqM8U4HK+gK!?(b<-fQQ!C#Q)BNqVU}-{=cyeR zx>iiRzI5C7$97ACPs6gJmOK;=dDGsaA~_ca3LmCQ^vz$=e%FX_Mry}Oh)yNr!P^vH z7ZdeV-H$yo5e>?9J6Y?cr$KA4XkjgSa%#A_#<(!23sBRy4_}?_ub_RX^hn$pYi;H2 zJ9f1+(h@f~=ZEEt-PXNIE4j2oseEYqOnMz*6_%d!9<73K9bsXDOjb$onEIF>jcm)LxX zZ%|6LE^gS1pHx0xT0g-u^t83>p3iP+pTm4HM8b{w-KxwSxBQ}kAKdv5R?rb5_?grHU)5I*bO#VM>M|21p9n88gwYG{WzFzcx&ce(1tNW$+WF#5y6}OhNv#RB5N01 zsB$gsomkx)w^!crEO|XSj6XW&#hX-&rzqjQc#-3r;yS04Ww$Z(RdIZjWw=^0h=fx( zxXI|4!Z^k_(TxucCIBC*W$8o#$4t#b*gIJgxp*w~j*~EsA}S^i2}_T@hsTHJ>BH!J zwq9UHYkB<&)q?BgVuP|QDSQBnn8LA6_YSH6#E-HTJV#8Va&E|bEt4JE>8g3z*|p;B zeOJMfsq00n{IV3DgF#v7 z+LpS$Oda@i;h$#fHX+oG>_Evbre?JKrzgPwM0}XZrxJ0=S_B_MQ?Ar z7MAYenR;bqFY*>xm5BV)eomdEN%p87O)eT@?Qq!EhpgSNd|P&DnlmBfnNfNnM6Oyh z^si!GIia_C7^!!-(yHrATJFty;ZLr&N!BUG^ea@8J&auZzJ@>k@qaY(=zibT{e}#1 z-hRE&^MQ6hCLExiBW>_#;-nQVLf|Ip9h#r4_01^Tx=~(qf8x)wepJ_Sm7b7>Nf>OdD)ah=X!)(vwMe) zRON{K6#Oc8u~RY#S=_FdKN2eNa=l@<%+|TM^nA61(iiHqGy%~#p5vIw~Ry&;W-OCf=b{eU>m1@uKl zG${ZKBgB7}gzS3UbjLf$h#*ZE=b1ZthjRB=o?&ha_7cx=JSCQO1C$cHgPSUH(b9w( z+ZLhqV_vf7aFnZgTyr^?o6(a+5$gcHQ-b(@ms)#lKcw~bC9=`?dhVs2cBMY7tLtQp z@NMi8U({EvFpfPe6CHFYIhBN=W7t~u`qnqMeDHYrSIo#_L{xGuxMo4%!!OfU-Gg## zo5DScGPP!Z1jfOJ*u_#~8IageTIvo;l#sQRu}*^Q6?)_+F-}(s1{)WR-IW}m)Iesj z;-47d(WA?Xh)~2pz9ZF?S5ek~^P9w!u+^f+?!RkL%)9N`5H0NaYH95)hce3fW-yt2Ik|@ewo6B79K6v?vuz!He-a5?r ztYbX1`rGm3=3rM3Dl1jowPRT-#8pqAzB;dlA>RH-Qwv;G&8S6Pi?o2aX`P*|MX(ZO zOvlFFNtXh??d^`IK+(a-@0mlVbAu1ZIE+!$fv=~I~L zlYD{YG>eL@#j^cDhjDJC4`YRXbDsEf0WDAGbCPYjE00ZJ1uoO`s$0d*4cV{N_!zIb zm)VE6@+yVaL3OiGmL%bGlJVG8?W-xMYH+1RZi0MXozDcSVJr zjeIF2kJMF?hTgIos5!LBue<2AW3tzotJzCz>dqUcIq-A6vdHDBsB%#@Gm^QHm|9}_vk zT~r?VvkE2V*#ty2x)@A@kAm2Ecy0N~U-J>hqY*sFsCH&g^4nQN9~^DiP8ImxhijX$ z4!w#Tb$Id(8nngfX|y5sSAXO<(! zgfNow$;RuyRZta!@7Kk@+VXJx{+xGMn-eEbheJM$RsAza@(@wZ7ij+BA#nK8fseGLR+5&HE0+aLmBqy9AAM0*quja z*34tEMIUSUQ8=2?h+TARxrAV=a+!teFUN$$9RHa+X*^)Ok=GK|pYvPRfYLG!8fQ8=2{>JW|L#xCk_j1_Ze##>>_ zN_^+`C(&!4N|{LXSYb^NgOlOW9*>Li7YpG%2`=Sx7h-G_)}N`BEGWs;Ed=4Lo=l1~ zu{#V{*?e!|XZ!y>M~>e}wDt&IrTe8jE});=C_eb}+IW;dfSTuZ;xtu1Etm7skE1Et2Dn~6{DXUQ% z9Jsb$Gkb?bjVixD{1P@FlVbV+g_V*P>NIaZ7YP06WDGzRt`EXg?BJkbDil;nUQCD4 zk76(Kr_Z(1aoAf3Nnm17gSjemE?e|^H@iS)gM|rAu1XCtJ*;?gB^Zz}M;lfS3Si$k zS8SF_d>6~<{0G+Wwc$J3V!c`U%l&!$#dD`ADueTuEuZYinadhH2(L{%@M1kyQ{6$A zCWk$MQJ^7EZhoztyy>rjnNMR!Mp2SEOm|Ol!CQ}7xDnINl|beI0^FX}5g&Gsdkt?0 zcPf|1H+t77d#;$Tp%ZH}Ls73YuA#D#sh=z+9>Ajsd0YAnw0b=mv0^p?urwwwXrt9> z_I#J?G-PobNJ){%K&Mp~CxBp|83xZ$27t>*s&ulLe@}~4e#eNB8BD&)lPIm}hFzYU zwzLwBiUyM%cty>(QAux|P~Ul~<#2mm-)Iizo2^SlY)8jM9vo5)YcF+mhrj0VZF_x{ z_D8z;nIq&VIEV?npyMG1g*bGzBV^ks;$2}sKRUrI%^Q{TN!UgiP27jeGV^7KR$9QiL1(d+Vv%be%VN;5?C)WW zKE)bRhYIFLiwc^p1@(@}@XTn+F8sPh4HSKSn|9$6tkk|OF`Ins`Q>|O)h(TPz)=ng zP+`hDzYmH7<$-gY`tfkI7kp!iZw-^GZ%=&60Fc4VWo)@@*i*urvNUgV(q_}|p197` z?8%{n;j!J+-#roc*CUQWk^Fn1@p!m{X=klBK7I$%&mQLEig!#%$BQxo4l-&42w)$G zrA9iH*QX9{c^=!qr4}3I>|NzIi(Wz!ud7WzK#+gxLGG3$F@l+C(PPj?&9s1GLr%V%O3&MP|C*l10k(= zl&%}oq3U;g3G_EEBW1gzyAS0oOwy9{oD7G+H7Fsnuyg3%9c|v;SYuFEqEt^qrVNrh zowstv!Iz6d7%?s7Jz3@}Sz95%(Kj5&Y_y8CudONBanfl5LJ$kQV4az945W6^mtB;B z!8T6LRs|WGP)swsskE}tWuquP>7pLT0NJ3N58Aa4a8T*x81{V4@QR=`?9(0I#HzJ7 zPOY@V1=4Fo5<1KQ)@^NHIR!PP=Z&qqEA#88SHojFc$M)#jk+ zk>sWk8UJ!A`xkW0Ufahg+2i=J`CXjvm$}iSV}h)%FB@2imK5)`!*!))xz}1ea}j(m&Jo}$GXgodi*OeSm&B(}OjjIv7xqOqn?G4u z&)jkN!wGB^0~8Iglx-HJ7^D}4?KTXbBrPeg(1wG@>8}R=gId-gZlF2x%WK0k@=g#pePbv9oxriTFfEipYU^w=A31iek<9(V=Rk6gsO*S#|p$G9s&V5mDnd0o_*=`9) zoQO}izt>aN{FF(sA?rhN;l@#!jC&2IBwXkZg;+h7p9~wq+Npb9vq|QH*T1^$??=?} zJHCYYkKv$8%WolNikj#ENqOccQ?iSFKFnCHxnT!xdndHDFf=MNLBduALJGsN?P%UE z)V=$~i+s}P)?&GW+^t`~h#OY>7ruL>1e`c1?gGNGJ`y~5yLey=_L*;dux8)Ic?hV}8sR^C-} zl*^!ZCU`M~y;=5k8)HfXoraAy`x8YDW1Mkr)#Yf%MC(_lc5C#_o||!EO}J6IdKmUN zD}qROh`zKBee*b~TMRm2lVzcKyP6tjCfSm7m`x}1#1HGxpaV3=(dBQUNd|+O4^aDc zAd_2}K)pj$jvltSbU&8myl{O9$H(#J6;U|H=TCvtU2ww|Hgu=3jueE;I`-C%v^~F4 zD<^nlyDCh}$5!?OHCh_5Zz>EmurnXGNa53i*~ zH6T#@gk%j$24U+014VtDKL+X7-+F~Fj~dxd>UrvVpA@)Kij`HZmVKFPvZ3lC48IfX zbbWTW#V`SVvkddE-RRBw^U4n3R$Q1KjAzt=E4>mX!RFX=-W1TK=;wn-Zf`!fHSWk_ z4u|#|u*1;zbH@PQhaSy@5D6QVK>DA4s5!`!9=*wG@AY=P%`@As-zV3m4^O0jCnv)H zxWdi{q);6@)p8huQ>!d&U+}S4C(Tf#p?d zxr|fsdqW?KYL@W(kF`3Ge7}8cXJMK2y(EI`P8x6+@KxK00y_x@naW0`b#vuY8Va?k zw+O9Qop5E$;2qf2>UL@L^$6!7{OZA602g_x%=y+`VY(qVBQsNxX*!NmA*kY3adArS30K#=@iK)TO}! z5G=@`?9Wp!jE;QpSkAaL*`>ShYZ18hv;(}sNJRF$OZ1QG!T^`}H^x298(!^3*68S~ z8ZBdruH)8gHU$R~uXniwr)zLD+ecS6*Wr!%jr|yRAY1x5Sep)378wC#yBu(f0yU;mIj|vpQz9{w1;WEa zVv}i7Ew6L2KVH%uKCeAP;C=~iRG#Fjss|hFTR;@UrlEE$1rOucOsCL#9Z5@LOx!K( zWP{h!*E2RHT>BId+{4}DLHXuCyHQRRBR>KxAHO^h0CoOuaya29BVbSFFwx={ud@*p!&-b|uY{Sg{@D ztt(Lw0D!u7Xjl@bq-g_9TlecO(H-;qv0WTsq!gUH@{TB0-L|DPDfvM zE5M+}0QLi+qgq0pXAaaIT+ejgpM}|&L@^IAPlmi3?Zu$aQf)6>YTLcI(=y&4Ca*m9k0)zR> z3@P5tah$rhXXIJZhYWGu(AgC#u&GBiZvyZ?+&$8lK7Wqw{Alj-Wdl6AdG7#6q_H$F zn%)Bh-f}~D&^_Zcl=n=UJmex9I%6>zVjm+!#WOTe^-AmRiiAv%dauRggh61kVV}H}lDF}kZGC-8{+L+YmSmhLuAYAe4Gd@lJog@SY+{BTj*c3qCbAo>|0D^>y z987^c94pIKJK(4ktv&#&>lHk+T`@o)fC+ym!Q*G`sH*w zfKU}3vsZZMCJ`a-%%`l2t3aK(jE9+y8}l+!19ljfx_Jx)>h=nb-Llz!ePIO_4tjFC zdJ8Ns&Ol&CM(evjXB6RyB5Vdsiz6~Gj|7j&=k*)pIC1kf3Q-aEz8dzVd720A){ULB zfbooNz--7mJ^3p{LA-q0FazPo(6nYmqH5CVlEV|yu!9az$N)mnHfn~_15v!R{Q0N> zyR4-fkCBG=(SYH{hd7kF5+Rp{wznO&+yzL(5AziR#kd6duj2k+@u-N1=_gUp&12XMR@8?B1X)LZrni zfm~(LF-|pVh+z;01k~=$#BiwEeHG{C#fSo!%pcrH9S=Z3Jcn!#-HL(<6bK369d2H{ z$dpL)7yM|31L!Lx)mx@lnB%KgcDU!CC6Q;K!gA6EI=k~cEZS&95yk?{r%2^vFmkkH zV@>8>>HTb}yO9KO9}PaAt3?)&fmZA?r^mwLQ{Hbthqz-B?741o2fEvR=v`#ciIjM9 zY$?a6>@idQ{EO$vo%Hqg`X6=FDttEtjvD&j?eFgk9>be}!@Ft(Bn%S4P=yGu=bFo? zi>(lDNkVEDt{;7+(@_DCz~5M^+||>_@JYC2gf73ron%1f^4MItT17q1A|I4^!g}R4 zc~nDjvLbigMG*Je2QrNUw%L*rP>KR~RLw}28*-5*Y7(xe-{Yz&e7R1aTh+_Gny^DF z2}?FO*~)$#dOc+kHtaM{B%a54s=REPzqYych*5~zu(0$go$ zG=i{RSrSo0P(`*2SpYlVI|b9azS2q5&-87UV;ro=90Cy`QyiV4LJ|AS$S$LV*R=oz zV5CKPyvqfZXMsj*N*_}S7hi*u7a84vXokL3U&w;P6`7fImAI=FGKVB?Yc1OF-b3os zLW^>A_59Q=9G)_Y03qV4l1~xSXK{rIy5zdB^ZtsABhO^ZiFXh(BNPPDYRapVB8%*9 zAmhnP#mOKNk%3&HVE28>uNz!xSOFaU70w8u?X zuvQu-mQwb9!d zR>GohNk%1`_=*80hxXRTXJhLy9=|$@keAb`@vdIa; zXi$U%)!V9h7+=J`t$WyM;#*><_Bx-x(!30_2tG8=1f6@V^`LI?P+Yd^89%-ACgX^k^9gdvhW}p^RiPOza}5iDbUMJ zeNyQ>^fZ>aF`i;DtfcT&^ zghG^kGlSQBu%F74_rF&r5E%&ogb>NtruYnkc${S_;hUXd*@4k2_?JucOQk*ExrOA1 z>cL$6_&RaW8&M7PLhDfTqA8&O%89*>yy8qG5TcO2Ea)G-A1tHt$@r(K1GpKh>583f zOxPvSHlPP8Cr@`ve01PZ7C_&1^agAnu>dcLjwKOSffG6kZ=xmBTP9B_5b-B)r_(wL z1f)t6?m!wsfTf?82x3TX&MyvNAO^N#CgO1bSKF_-`;g4BG3f_ zl%ev+h&}Rj$e+fCLLT@H17CJOBrhV9-yd(7Uy2{5SMN%maQ)fwed+i}@)nwY)O=I- zqORsBa3c9p45-MCY*M|=DC2;1Vs2u$!5)Yr_;)0#^)Da3r@K1<3Wh9KaF zOC=!e-C9vsR#-@hXdqM+KP;LOfC>O2LV){HAH#+FvVqir?4QXGoTpO$hyzJa(+Ymf zpmJJ8`_TFG6s$^z_E4dvTK?i!-9I!T=^^I`K61WN4v}3Uprk`2q4GkH(|$PW2lwGI zC*73NsCrM@kv)JUFP9wc5!q)4p~ z?n)X{&KE*2#bQE)KOBku`~dX|FHz+PXjF(4Xj(;CkVo#yzZ8I+85N*=fF44R^58za zs(64ffCIe%L(_oc1-c44V5`FuJOx*|0Y~2;r}L#jA1F~uQ3I(&GzfkfSM$KAe$)fW z06-380R>NE5CbPFgA621>}o8C^JPLr>PPT9?Q+Ha4AfbiHIj5YnLT^&`jD~+sPB-= z4}V+sDq}@X_^xE_khF((S6&`$#DpZ`u9lLniNx?X7DvhJXyZ#lLCT#;w!Y~t(S{jr zzKteiCB{fnd0e`@C)_Yd}bSL{fA*+mrbCrCSR#Thxu0i_!?`8$Tj!BPUl6n)9%j91ZPn4*Fqs>Uo_&L7_Pch^JS1>_MF z%q;{%K(iAHGe>dLBzc>xds#wsTK08hY(D5Ks#QWUBJ2P>=I8kVM}>LwyAd!bS*+Jdd?P0s;5GN~#hDDN;ZJK>=}) zR8Ch}T38f0FtQ=QfC8R`JGhD_7>EcLz(hyIND6-Hs!&KN{!D}DlMn#|T3oSI6FxnZ zQ9|+$wD8I;1Vw%~r2!E>bOc0u{k$_X+m*wPghS6Dsu&c2snnmN1y`jM z1i(dAPKS|2jDAU!Gf zXUB_L6sxHb<3prD2le2ei3tP&{rCd%Hq@MW0iy&Y6c6#iK>T7Tq48pwy;#yO&wz%s zLF#nqHDaiFkP~`A6~z@gBCJ(0+PI9jXGja(z*Xu&1Ca&uNTKCGF_H)_{4$XMehC2( z(UEe52C^Wulw!B_F%t}+R*Md<{A z5io=lPE-?@wOElmy=h5v&=EOOB zWg)#3HfoT@5D$G+TuB3gEg6E8d|2<~h_AdyxTOvGBp5w+GCp>_DU%*;n4y^gm z@MG*mazh_tvrHoC;47moK8#!76${|n6(q>9%KU0>f#ObYZGq}a3X=8yo&);}@6f9^P%kt3a^1T5YK^hInq!D2ubeiC5H6agxgRX>Bx#>h=-RH zaK%p>T8ZFLfL#<1qC{V6PiYhye;r43Qz87H<7oC#b?Mh#7*EC9s1!Hkz6c#SY_I^& zCH^J`OA4HLI8qQI8?h6!1Lm4(L)C!c5uWQ*uu&jH1M5^GeTcR|{)9i$gQ;uE31W(V zDAVx<#|aOLc~Mm)ujqsNa(^=SR3PC2bQ!ML;j6ZF?|HmAlVS&}gs$8I;u18+NrSE& z6#kd{PqE{rdz2^w{8oa#zQoR&HN%n44#e!A_0oodrUCok>@a=)kM}!|Hx600IF}}f znyw1-lZqfHxt%uW4s(`R;f9!olB6Pztt3-dEQE+3FZ#RiLH+!n^fTq@Va$2%9Mk7J zAIwCaml z(=b+{4KDHE7fw7mrGI6OF^yv&D~-5{&v>-fDfZzuiU;ifX@OB}Twe6*P?@ML{tY zL8D3HG$jm^6ERXzO+^t4K@b5+6#^s7aydI#6riOaYd^ms{xpwo(kV-#A9gsz%Mkc} z#VgH+AVYf^4xfNjbd?BhRvQi5k#>E(VTAn-#X0|#f+m=fDRB>(6WB&gbntxpi=R>n zQ82^=07q>H@%ui*N*cG1OZjRnc}X#*;u(q+MaxAA$5j~7KqQ|zR1Sq6|J3V`m+$aG zgx}bFyk18qLrT5E=|J^|{OKqnPl`^V1I`5T6rssehXcQemWUqAjDU6U8+ST3LFK?t zHY&eH55h=3(l}$S3WOjGBA(h*ljTbhJ7R#Q#Xy+!Z%@0#vGNseve3Ci1om=b!G1Vr z_2Hl8x2YaEj6y__AE^}bA|{BO_>xGbsC%*y{16<2{gesqj^R&O5AoydK%A87kZLCQ zs!2YC7lRPeo6RDm1$QPQhjPITP)P&7hO})Tiv%>_2S|YNU^|$Hfw?Gy76+S61QQex zbz&6}J4l75_B=?bxQEByQW#(_y@SA?YCh^HY(rKb7GaDbBfx?6;z*^SLMMk*A*B)h z2p}6gsynP6><4N{cNmUXK8zZKjDzGvI=-HJUkByJCO_1*5-ktuln>^@qsX911bBNg zla~PbP+nYcLPA1CY9@^NbBjMxK=px7DgF(M@N7^Yvud<{{ZArjnoq`T|A%P+nWX;f zVw#`{DjI5-h?J(9XbO@5s*C$56H7w19$8Ef{E}+@|Ixtg>n%i-)I<|PcN$6}V5qO^ zkEVOk$g9G6hK?!mIrd%#T)L zT#HQ`U)WrTn_$3Df=Nr1K&1lfMjME!`Gx6ISJ1fl_b*rvUmMx7`5yg2!`9VH)ZTmQ zfLHTi2zqcZMFm}I(y)L!kVQc;MN|NLP(nZyU3%&fHel8jssW6C5O+k6uUQ|8PeMkJ z(fqG2?r0ALZb7ybm+;VTMZa>gK?2bMRpf#QR6s(+5mgRx^ojvg4{ZQIQPhd~@C~Ts z(^{dmDhN@%KngTupg~rz7OhcK`@}Zoau0N+0CaVhzR-NQrSS(|o;Vs25b9AMtso)m z`<|B&R**h(el8RWiS4OWK1cu;*%d_t){2Ry2%ujB`fw^Fh-%uz;L<+`Pa+51@Pp*z z=?8vRdG-0@rBE6ms%*gpPT}mC$t|MdRmRoA2lph30A60DtqDQ^>**Arf%6ETG6D!G zyNUslAZSGd2*eOZKvPCJdSx4hLa2bKfu0H>o(emVLa?P3M1Bkq5ZcR2-p@pVX`0mM zsu8*%03Ww1Ap0c*_MY1+2PVB?G|&+ZAykn8Nu&%)PyrAnP!&l*NYceaK~z*g)JTFK zaufH-PxB=~1U~E!i|oKE2y&tbx*&whv1BzvVtCvXDj{^LeL&En`=C#~3Q!18g7E?f zRQcghJ@~S!zlJ}%Vx|jfF=RqL($b_wDM&QZQ502FghnJukV7#LlFBe?^TGyz2mRWTB=Gz}#PC{RQ&LX=R2Ds}qS<#m@!LYtN#2!{7M zkyIPFbci$rQba&HF%?lZA*XBP*-%3U4a5Whd8ZWuf~Q6ThzATn0SQ((o4nV#}PVRU`0N%yeAfO_eaddTd zEg%D}5CJiMAH~HL_(W8+P$el;P$e-^l%rqFgv0_w1XU6cKvJzVlr#k@fPIg#hwaD; z0)097TaBFyhtrthAY_#kkp@Ho3?^++Hq}AT-Pw(5zk2NFTpC2AVo{+eiAAJzg$9Mv zN3+MTj>_>A6ck`O^WR7GKF?<={8-vG);*UzkGq~#5nf&R=-k&^T#f{RN^(>pMWjk} znh`#kO+39fcRcr{!^E;jegp*Fhamw43?PW3vlRs%e$57b$~1jYh>P?I> zh}{51Zi;{$kkbtNdv*a{4paq@M+{1QQm@0}kn)p?fP7MYW(@Q1P~3_Vv!nvAZUA&z_CysDbCsEjc?FY1*D`XDFfC;Gz|et6w;KR5#yZC(YX`^ z&j97zpnQH(!2+OtFi-$cL<1}Y5O*NvVhXPY=6hR?mJv|b zUcRQ-Rhg(V5lEf6E6Du7}Vf|Q9`R)i{=8WNQV znyQ#asaArjfGH8ADJcXB6^XL-#RG*wfH^Aognmzx>3Q{;phSOKK3P#zG(=r6R2*_r zfDdj0B6i>)eb^st1rC)RDWh4SwMsA6`wkMmqCIb~!}TR-3InL6t*{QA0ZF6{4b~VR z-TS|Lmowkt_FA34(j##*&ayf>>vL@@pf~A10{oHWeLha|@~uq}gj)gz=M8I$AoAgk z{WS%5MiPM1YKj^bli!pPErk%oF2rcsridWtV<>U%N>x@e|2pdrZ>QP}7nHv2&*Glu*d`n6m9oyzW9 z;pOn3IVgjoPCzFhPsb?yAEE(3 z`t$Z7KzSVz&zSGsbnIa7ey-scP<75+c_Sp{S{7R%FuZg^9)T^9@qau`M0r$BtJ}A6jueZyfxw$h?lra1WoG zuS>e*c-xy-Y1DnV{#@^T!kj8l7^I3MG97~TrYNaHt5)mMM%*>L-uXMX)C@R_40EQ-O^&l z$(DipeD&kq&6l<~fPP6sUv{If$Iqi3JsG zTR*|quN68;0LKH36+zmEB!Zwfa%%(}`PRUzQdLkp(*dMHgro{e0+DIzG(@3D1W^Se zMJY=WNlH>Qkv$M;N(PLix~eHDp^~a%nj#viVn(P+78l2IL>2%vFA2nyD0%)Y{|*%o z*pWQ^d1H`7RRKygRVdL^P&5rd0YWPKKOO~B*Xr7QRnMOyd*M(h6ZNCnApXbbib9Z~ zy($dBU#$tDGbv3JQWSFP4oPo(2z#aa;(dCUyt}~ih<{(R_c(`CNy*VkK}(5H#Q3!p zJox-n^Q)-pLzB(c0e@mB0Zl5Q6G}2)0l^W~btynX&;Z*V6`=@IDM6^X#F0}m4VaxI z?vlJ+dqo}vbNJuD@5KKQgzualIQL8VJN#Oyq@tJ7G(4P<5N?(EW8}pc+Q9wqFbWBj zaT97N2KmWN5KG_yWHM7x5IgCn0nvz!&=;Gyp~(aUz9=EC!#8S>8$?~c&mO`Vuq1KC z`J(-zC1*)NTc}zyp-z@f;cZelj8P#XsQYlidcjoh9stfgrbtEp=b!5 z8yj{;gPGE?AxLP^Ol%mLEcAW5t>8~P9N~l>2qX}t z3QCenn58IEB}8HbsG?LuLYgQ7f~gbwvaauG-JShDq3mFaimD2!mLwtqhrv8=E%cW+ z%Z@-Nh(M>oRL>{IzRyqf~u3Z$UbaA2qrAyohxPFQAAKk(oiuVUs^qUc;|Z*dwKS{KC=p*AoRVE3+?!d!AhrcBlrCC`Qb!kC zV3>$z3MhOf-+kIS*;P{&0Te@Bi2UD2>ipdTkCcdgtG}ZFCWv!-6g>C?)M3}Bk)G6)9VnMf~0g>wtLlIpY6g+>s2 z-SY%?9-j!Td@qIVgh!})_&)9c2g?V~C?~`7zW42?#r+-jx#h#3r>;Hgk#nNzx+7R~ zQArp}6hs@pBgyNy?=LZvkyLpXl$>U$=G?CQbZ(Be=EWUHi06#;be>S>#6>n2k_LFu z6j*+GpVQgr@`g9x=8v4&(8X-xS|Ehhgo$b*EhCMcRTQDu;M`85OG!-5wGB>?+fj*v zrkSFShvpy;#oqg$oA)>Z^$GbP537ulq#iS#XXE7PV4%A&Lvwb3=5i2_WElt$t^K&x zS0OS*S2iHKL=bjjDG2ArxHwq|9J(50Y&e-}YvP;xg-92`q7q1{J)IfoxZk4c=g+)& z_m|D%J<#__I(f{&14Z;Up{R;Qr3yr$S_%pxS^%IHl)Uy}?_?m5%?|GSmE3{>D?<<2 zP~v6@0D(nCB%z9-DDtrsQc%Rz1XL6;G(|&A3`9*+R7FKZ($xh?Bwa?CBay18sF(_< zsE8sOS_znn3WzC)se%}ak|vs(j~kP>IBQ%y_X3LRRlHlrjB~Sf)=saz-&~%KI>le7 zItZwRS|SCYDISl19`1?s!KstJySN=adGme9_MQ0W;JPrqzNyJ5u&ZcuCnQ%q1BM#l zJEs)@50|ss!xzB6G4MXIw6v52K|#!Enxp9aJ@<3$6&x|?a%v$rAAEuL2?OKmQijd& zl&WY%T?x6u(yDIG3@I;~a9qR;kw-LE5CP?2QJ0314ekox<#63vF22=kcBNW`bU*`c zI=8K7p~iF5pG6gMhbJl!g!c3voQp?%`^Oq8hKrGBnA?@kR?m)5awsV%wUDG}N=ikg zC};zc8WNU(phDQtQ$@3)n5Y^k&}w3+qGG5TH0w^AYhZ|`gIaZ1j75!3w9yb!Rh)eM z<-(dGX#RgUJdY!O9=8a4;g64X&1W9g;JUtC}>J3 zX{u_NAc(43h$%>^s)#A1f}on3A{z8Ljy&eB8#<;bDjV#D&suZ;H`IIo+jML1G5w~ zKtiFt3W35mac^`)3B_E?Ai2TR4zb~bDyKs1(#jmVA_;(*&}a%0i*46h>#hXRRTWg1 z6Wx39k@E4BO~WJBFrfsL#5HOwoe&R0o|CUAi3V&fNMt`>)uMribfz z=%2L#?mW4===a7tAQh~Un5`JfLaQMmM%42Y zF`}qSs)=bBW?`Zzo7owtn2C~ts*{&@r(8EircV0jnH5Az31T8jB}q|rX)vOdQ4p?F zvXw}7a6x6qUG8&vV$CO?ioClzs5KR@fWRBp)HSXAw zit03>MHJ&yzHweEtIufH2khTp>yklDNkmCRRTUIX5fN2T#4}XT{xjhvK}^)a5Y$n* z1wm0p<>E965JXY9_G|On_xIwaf{~=4q0y>Ik}9Hxh8l(Oz@1=%lVhLt2OafP>gU=F%U`Sq~Fm-hb6S~St9u-F7 zbr05d42l$jA_P5o+8mUFQmAGWL4PWSEUM)gF-1e8zAYV>lEp|SxDH4Ghbqbh;93sO zfD*mwgovVwgb!1`xG&e0`Avn;HSV}TXykJhz8{b9aaYRk#uL^Lx$6A43ze?W%nA?- zDC6zBI;c`OrQeXkF%m|?1GzqnlXB8fA7VZIZxQHqUSW!{&S}wKRm#3ygW2}^ z;yTk?V9&|!>!2&v^YOa8`JQZ4R>roK3=|SDDtlX0BocDLo4i0#cjR;&*N{78Iv-8C z(Y5kMc_H`tj%-^wFPb?#ix~LhoZ2paiv4DuV=7GiAA2T8Uq>;>SxP}Hei^t9OVx~a|ma;A4XFmz$Mp5h3e?G*ZSIw)=%Al$aj*uIr8mDPs zaOB@PJfHUU=Ve@W4YLflq6bZ;mrN$fHT;y+AVU%Or}-|^n! zjiwNqR9`8OKOQq-KP|E-NdSxPxh8rC=;o8o4i8)fCDT_A zpk=@oIoC`s2iNSoux1SXmUk@E@^vVSWnV?HbO1}0#uc307`}}1A?*{JUaK6PN+bzB zr+varj^1XpHO86R`#|2XqO2!JtppECSO_2QPhg?H6>*Eu*@?8Yd&fLO(+44{I}!=6 zVAIVEaqaaWF)HQ&`ag7}bop!3^iU?wlktriko1?pV5^4LHd<=Lt4aQ}^) zYsNDd!d)14(BKJO=;lGkv2%UCIbiBYVvL+uYsY;}C!Ekdb_MPV6GF(i$u3EO|xVV%N7J7CY5b+=_G+kXAn2t;vuiCN|YMi ze$Q?x;nlU2mCyCe;-(sZg_QA*UksaZSE&P$=O6!_unV{c;9NlLCfc{$Fhwt-zaAWJ@u^E*V>_mU?>IpD?6seQvPYd?EEvg`E7 zW;Y}R2CIHEM-?XydHOwnSW4a7+l$zbk{%ZwPZ;eup(kh-ex^(?{64kn19;TwOO5=G z>dy9@Q;gP@Z{<>B5m)5aqxe5!jL((4seo{0t#M_w7PY>q>V;T7KlNO_{7aMJ;CtHo zS_C=+IV?i6#*ZMy!FW*`m7c4H#aC((_Pz$vF%`f3329A_-P&XAWkka&-&R+FvW6u2 z@54CvaBUA*;+&3yvZjl=+f>Bx?NwSCBAex$h0ILpJI zpS|7uK;7JJIe)Uc)5`p{o5)1@>3qrd*#P#H)s!XYhPj4&&v((+>y-lkTh zcfxdXm=${o3CBiv8()P0EOg$Xr;^6TJs(trx5wng zvSa{Uu83MtxWw8?iS}n^Gj%H|_ZCJZNd-f9L%ef=(NfYIomv95z=4(r{pP6s1Z~wb z#f`G37&hnaiyOS5agQ}%!?v}9)R<(5pDQU__Xza;G8uBxQnR+i8J{NUUn!qnXJ z>ur~Xrl<3elj<&RE*af+;AiH*2E+|x@bR)YZzkQ1E+=^Ix=g?OAl$9h-(exV6}9;@ zdh=^|mdg0H;uGDol|Rf|M90q<+x__YrS>zeI;EJKp0V3$#1%KrT^_eKCc8G6(k*bl z_rVFT0v+y^R2~eu^Y%di>p^9nUY=&)g~7MfWp6HE-jf+QmB@^3^WtP-wmD7bmk^4NVPn$o%d zq!;%+>$ZA+!h&t?DM*1Rx$JK}KW)~w^S5QXbn{!x%^W0|BLCK>nq%TuRqU1DqEz>+ zwpbQk$-`+T<>C-*P73t=_~;|W#bd2UFYLKPc~#lTlHDdl=rF;F^30YKsn}x8nxZmX z6}uQy<6D!;&yTCANR8!jYWex9SV-!LRR3C^VqXqN`QZ|p200#Z@TBs|YchSV3jDC0 z2N}2X?A|E%pZ9V31*_Tp>-FK+zvx<-8Xopoi{r_dtHpe?zxQH%B<4Wf5ZaE`yQmlALci7 zMapz86LIvD$_ zH~;+-zjpL!Uann#RcmQK@DV`C`QXle!y@*fY!L{83QEB2**A6;wCV5pY4V0k=V1rI9}m%Ms2^Jot4cSGA}x{=Vf%favOM3a4=OLDr! z;{g`f<1)H{@h&mtI!ipU16M2fSiGa|{fpJ17slm|%D~$UDlIUlO%s#{;zm50e&GX^ zdo-bYw16sILXlWhcoWzuAu+LI77kygTln_9RO^Y&|MBO^XzJIO6$~hnlzeAy;JF-4S zmnINYI#HgclLjW#{kL1tf)d0HRrdXa_ZhZ*c)R$V_+=r&YV4>T%fX3@uTw_gXauCd zp6X6Gtv~eXjU+#0^Yp0-&%MpB8zvp=L!Ua2U1evisgB4ly`WJnI3}Lz$6nV5JcEjj zMr5n1yB-X=?$8~Iy|5{K`eAgzl1ll&wTsQe0D6-$Nlni|kB+48$q7fa1>n&rffF1k zzu#|?-=FC={7QAD`%|yse=@7LEOlL^DOG9*aD^^QC%PQ}BaN>G0K3FpV+BG=kCPYwT=C zf8X)v@Uao)LNyx957~IH#M?dJf*hX9*H=6B$S#jVX?%GZFTgCji=OUD_^CrM zZgM>D^a?PhhfI1BOT3sVcPEeU$FwgFJiWOn`Q}%{;*AffJKLrvo9^O}Fk}Nu1f|m? zLX|%@5c72D*UjKBYkD`n3|<8ZKQPZom1LaGH*08({#GSi(D~N6W~nPjSt>fbEzzp& z($vB4gJSnedsuR`tw517Hz4{0e{UE;8wV>V2csF)U2fxW$0-_UVi7CnYPrN994!UY8d z-e2w;yh}ZFJ1<69q{E?Yy?UibRgvX1JihhGxT<=UHvNORb7nE@)#mq*6-IcqHTBz! ztC5^%d)xO`MV*Kshf@VG!)b%bKLt5E~)bxhQe$xKcvIaR`yBKB+k~&t#O2cA36!vFCsn{&s5X1WAc+9a zfTp74dM9i?u65GuKGer{?z$Q33LR?t zApw)@urNDC!8vc5?({^Q9V%`+7x8nOIN>^T=IeMEtNNjYOoQZ$`GrP95yJq9bg|%S z<9jQCiGTRWY(W%G)hATB|970Q#0j+bv{eUL_F9A2l)2-QLf-xo=LU(JH)fFx1*%%v-(}JWb5Oxi{?i{BBwue z+_2pGHa}<*LF`t_QEZ=}I4j#*6k#uR-qVCy;{YAj`IrT@yWaX@a6KH>Vc)iuQil(d ztjT(;8{1Q=*VFjuDX4eyB6Fvp@7HE={pM1zPjr2-=^vqpD4n<4&827iVwO*$0|cEh+?pP)chQaMUKGbl3Z(fmTF3`AM50KM1EFj9G4TO%-5%Y0o* z5HD{$Pz*k}fGFEtb)Nwwt2*~K{+PL5;+wB^$Ds<#ht@cD352V39~{>kSo``? zqsLrzC#*0PStIu5Q4^4O9NOZ9)jwOFe5jEL@ozYkMnS#&;PtG)D(Ae_;rigH_|`Ai z<%T*%)6pV6F2;lV8o7iffoo1ssLAkj^F1M!g$OS6;&V!M+4IutVCZ1kkbKYc5Rkb< zDBcq7#lCcI@y*~Z?nKGC(6pK0L_tI>1X{hgcpsON%xr2j;IAGCB1+G^5OiNfVi{iw z#6>19O!wvFx>@NO3#6K@u#^Zemc`F4h%@ zp3xH)3z>#4z$}PAT7O&e27E zkoFWpjiCAioqe}2@Dl19*MJ}*)HeD6_&GI>V=#;&l;zTsaS0CW6sD_Q$$pxcg$lfq z3C8LNoGN@AQzCuSSa@Dn6P;uiGE*EL_Xj$mnZ%Bp4{-AK=GZ?Mk9_t(Nw&@*V7}8Kk%*`!W#h zc#o8|%US&0-a-{x1*qKRD6_N9O288eq$Pr_Fw{ToK_e_#JwDc^d-HVSt7p71xG zlHUT@X2*qJah@;`%3n>Gx+B+CpHp@G*3XJb zKL2%Ut82-=>^@7lazJh0bHc2`fA5sy&B+&dge${XrR<~sOb3(EFk$OdWo!Pm9fQww9?XflRs&n zZyp=CJ!akxTn_zu=I7RygQSO6Ac6DGK+&2E$H1bfcsx=~P1+WkWKk@G@=&JYE$?(I z2LL!>P;n@S7gY(T?;XWb!@wk8RZZ5ObVDIAso#L>*)@L?)DsW$l+7=j*Id1!3L^dD z0>5b7b#Q#h-rMF=-Oz)n78pA;YJ0A^wxo0?r1ea6*kWWJB$99BfK&&6SwtVsB zL3xquru`Z}Ht6g)PmU($_pEId23=ctZuv#o(brv7J~dEl$5xdy#Yb1P|0?P!p0rk3 z&r=WpM*HTJ)Cl!^Ui7gm2ie~_QEpdqAYLA5BJ=^haK+}O1W6ezN7?RO_+k!!~rsr0`30P&q*e~JZks0XtwgdN|d@|AE*g2MvK8o(8 z9yqIiQ1jz_S7zYfs_`&hHPp|AAtJ-I)CeRtdUFafNlAXE9pwmu9}y)P0vje0aH3$n z@-44wJnQiJ6mhG2rsIQA51TbBMp7yE{Q^&>{&~d$t%l0wexLrc7U8yVi6C7p&1HVG zb_aA(VC2+N1$&46Y%(3LW~5@BnSe+_3nWna9s(jR@M2Wep#}%W? zOj7*Hg3%i8{Q?96AO(>d5pQZ*!#uN}I-(|&fvn<6LvaGKO+4cMpBqGIDHbIisukhk zI8?Zp0T71D9El0(Pn8IHE^J(!-hh>rqUGA&=8cUZL_P^}(0Oe#&TX(N&EiyF3{9aURx>4LM0)Vd^iCH*g7b|p@!%Y6Ay90aoC*e zI2dGZ3fw4*i1lTFLBAWZ^;tBt!q1(qh+>EHDuKgWoTbteS^>)?UBVJ=No~>~eZzR) z)SG(q%DIu~N^3tLlmgQ6uj#xIQfUl#uyY@U6ZS*L1hGK80Bs&B5^wg1ORDIwYW$9cr2gd_*!RtGz)`JZ%;ifV0VuaGbmUj#l3 zVa74sVKTTQuoo*P;9w$JDqBsN+8l3Vft&<`EDGr-t}X^yLu>)M-@GZ2^G@4cf1Bl* z#`f`c_$0HHCishx)_6iQ)2ZJV#QM5<%=KVl6V12up5@yfLKWv7o4NWU7?1?4#DL58 zSXFFcE>;RQ@NiBDt&$Wf6yilI4;0dOO7~Kqo(qj@pYOU1!x455vT#B)8n9KJvMBw= zvAWh8%PG!7vdhpF)C7hvwlMzpa1oF+)G8}{4AZ#yk;}(`-F2vTs6PWioo7XE_|pNz&W<3rQC zHuIjAjMUs^c%aG%ge3nxKLoSiU=f;%RmY|E7ng{{pAm2Ly@1dJPHdRUpLM$JK?-E( z3&7CQ_t0s}f$?0<=BpI-{Vnv5@(v}wA;?Yxu!_wA!vRHvpC`x#R0*n#^~rTd%MzWU z-qT3~fHYMkGEP)8a~M= zL9htBQg)28N(JAt$`NID^F!ujVizFHs2^!t1oc(W3aCoxyWZ(*XDzQ@#i?M4m?Vp6ab(MA`JE`ou@(85QO))Rnc>hH z#>P%JdnGCTRP?LO-b4R6^ zwq*Levy9$X5Ko#4i8XIt<}0K;$dI#iJ3ic-o}bQ&_rETyMFH^r-AuLbtib$4%i#}3 zyR|kzAh^wQC7(r{( z6C5@p7*%7(HW4S|;lqjX$avZid$V&6&GE@1m@McDEP#DE2!0$rKpL#gHBG5?Ii9~1 zcLQYqqU5em|1<5UA+L--0TMB(4p6$K3{!^!SA`ogrS#;=NzGFjA8-tsIZ3v?b(zaS zC&blaG&QjqM|Mpmjz2Tr&J>^UN@|iF>YFz78F7suG9bd4GrAhXMVvMt{#1$DVebBF zvQ{~*E~_UQD~ikG^ATb)dfm+7qRXHCFiF)(Djq|2z6%K-EZ}GqevbgUtEx=x#>X+T z?*j;x$P7G2VVQS|DjUWui2!!Ls?qlD8@>grCWt0URe$947F8D&V$=q5>?H7-4X6dF zJvc1@uBx0cmc{Z$z=_JLn}bl%GV&*uz@f?ITePSD3jSy>J`Vf$ek686u_;Xx8B3y3 z;7G8b_aNSPNSJq)^J!U3`tAjU2$QGks}8M61(#J@z;hMLh2es4N@~Z!z~RUw z4f!sr?A!&Z>{>1mqclTpXD^AM0;EfIWTijhr4WurM%v0ua?!h5&j1r_*bSmJ1A;@| z0Yegzn6Z%_G3)Rqikh-T9u-;L%oC+H*LE5a`a96iK@O^pD5MSZfDHx8sMKtBppKys zNC*we$YnNmGVptNg#{rN!G%+pd%v-i`xVwMbHA7dmjcw zv*|c|taYMh92Y&34R9cEE0wpO@|8JQ9)_;YWBbY!6Dpus41m9n?V(2Zy2HxlLWE@EE(IgYn1)XaxYRWxhmc~L2_OSNWudWhrtx5)bgN^ zjA{evO^C26qN>OwECm;WQ{89soGwoa&XlJ>?ZGyCY!;j#C5XAwUS-ea6<2H4ayY5h z_e%^`IeWrWGq(a-qlx45)9RE9ZyW2=9o+lI+~Y2=H%`s1u(|bQ5U@-N0LR{(OvNW! zVKT@!%|27v3sPtlGK)wjPo#;MPIYKNDF3{0V=8cJBdj@B(Lx9#B9JXs6s#E*0vp!i zyl@pn#8Htzo@<#w3Y?{BkV_|1RrSHTLK)5HbVQWz2+2BYpsoEl0=YOq1=JU($yNN9 zm1X~;g7dvt!b}2@0jLDSMY3@4*f)3(4QB_~&4AFf@;ht#@UdWg1@1QvH0X#L0npXi zhj`x)8EckmD(uS;;msI(MPx4BaF*hkxSvX~65jYP=EPtDxA=HPD*Dy9^|K3&1d*Oi zk;)N={s*KGJk+xKwuvn1h5mavA|MPvPPG&dZ`0i~D$j_)lu-}fW|Rb#84tk=?+B~X zb4O&o8;ki}jQ}s~%4GGxpiq9Cp@b%cU9sqwZTwDHLXMk=Zx)B>GwMXFkzg!Cli|Pw zfbn!&H2$&odx_K$c@_C*cz5)4*+(wVPfvC5o{s)&mBbe&=alQx+I=Gvr54yj@8Qh} z4?B~D$Qei^g5mq>g?ieh((axNu%rr&%mgc&byXGRT!&jWhRS&rb%mh`2pB@pyp)hz zQ%)k1iA2=iizY9c3!?0e_)*Td0i-=;@j%{6D#kj%GF@L&XwgxeA)`m_sc9T{umLTr zCyY+LdQtLDkY>~N;#{cES^H(2nsc=@yAo^ZL4Z1;%U7hxT%!MLGJ>1|?Igm<_ z9_%R|&SVfAEOP4@6kCcm9^XrZHwAKfZk2%jqtqWIx*ROvIuLC986~u z+#Z^a=D$)c<`?lf-ec%sWmv^=H!YYKnWCnqtzsXyGB)DF5f}16&k^eAg9LpWK^zbS z)C(3@kxCH>*}F)C?oJ7o9q)?M=<6rLhzwf>D9kxg!Zzds;8KQWNINuSVej1B4Y+zU zM5pnSXVi6f_8O?YToTGv!1~8~S!9h?Yx-bP1yWOTypY2bI2imO;ZEGv0exxi6e{w} zI8sBim+%mD_qv=vRR7694iH2S$2(A}?ovn-vetk~{O3il(WDx)%@?=))IJM7^ii%6 z@C5Pv%KQ^S-qzV3DSkL8F9j%6B@pX9P6;^IjKj;lEQx%WEHmd}0g(e0jUo^=gyah) z$-UyXB-=co{#bdhYHI3w5F>ZYh-_KBUmzO<5)QdkW$bMVQ7)`rEOCsbv(>Q#_#uJVGg0xX+E$Tg^<@?L35>j$E$M_D z{wuS#0n)uYXhqXB)N8tQvl_}e2^nSoP=Ju+3lU%|VlQ>}{rNLkOhaHzET<5XZ0Rf< zR@0kveD~xq>l`NLCjbtew0ij zBFKP*#yxrsiBGVD+-W5DG&x$s9W7wJ03wqd3%YG>L^%Lw;mZ*uk^_`UE{8M6P}DKV zq$nIEh8(iyk_gZB=($v)HeS>k46z|JC(v`H07w>Ll!8Oxl?h-X$X3chO)QV4kMG4h z;HeXN){Yc$HBlmyFg9V$pihdCdx4HDDKb$Er3L^Yo0z$_xs!0R0~6H6)OI8ylqpyi zaUmkle>pZnCzj_z#}Z5csIp}}d>J7#4_^nM3rokvVbJ@tgSyr8?-0%IUS?pVM*^EE zhAH?`8_PGZ#h)He16<3TIRK|He0x}`mc{-V54tGIW}lBCN^+WOHA#noWl+mJo+i6U zDAgiWnBeD!v*rloS37l#N#8D+%Og-iG5$4`SS-i4Dw|!Fs-9|* z;Ge^l#bIUDE%;*(kLQkjO2K#{s{a`*UT~})w?K-+p}BF4JQy9+JvQPjZ3P0IfZ_NG=30RAi`M;q%tW!_St_5`4e9I`sN`ysQ@AMxa2#_a+l}I z2LArM41Vn_n0aJUIV`g({&v{?kF=54*_?>3x%tr0J8oHwr$YqbS_0l2(tEsTyw1_E zE|9O8TFe)fHJ#QhWXGwRR2ObJ`Q#2Nz+nzd7QUBBaj;-8Sq3bmKGFf%lf%s0E4w2- zb|g5FSq8fdk*F;uml-z;C5$eFFIRnuj}YvZIO*{jYPs?ffz*C2oJ_xXd(L#-_Yl|B zNFeOhXLUwWs-rbBNnbvCNvb2PHF#O#c;AO%>89JhBR`A?OeWbeLRueqQm!P@p!O1q zIZnfOJ5n6Uu)>j@dxWis_B)5&LQK@+MoeJkx2>etk+xYx7L5=#tY+4Rnh;hsrL49Q z6{JteK`+2#4G}XBaCJ@coT^$EYg|;XnyhoEoclCgip?Fhp@Pu@%o+?GMBU>EQDPPP z%tVNknr#niFXe=M-^Pyah$5kkSPpt5O(Yy?hcIL^5l9uHErA(J1XIXJaa;TZg*-&U z58JY+^ydc#992h#v&7+`Y=^z^o8EU%9}OJKRlqBefy-l)8fp7_AKVY*Tx^%;N5&LXwM2vF0Pg$^jt8ox3uYH;uj?>RJEqZY7`|xahz8r zU)0GXVX}G*l)wjn#WKTzKWpC62{($dn=bC3dD3MMt+7z8>UZ|kX9WZC_v*Xk+LLeT z-`!C|-?*-zx|f|?j1cq^NG?h5*RoS4*@;6~j&NkB18H9b2yU%NKG^418l1Y-7lEs| ze!c&*tf7LLDh_~gyfbRYDt)N`V4MUiG||o|ph?%wNLU4*(sJNjWI+@<#UyR%8Jgg; zy5X1zq;CZV)~k=tT&dM6VE{H~LzpFiMYAOsN*l&fC0%UoA0eAwe4|KmyABdvycDiy zR;-LjJlO$q+b|9>_V9D8`!GX7n?v^HBD{5xNac3tkjZD(F#ji$?{vI`?X;I3UZ!%* zG)fL-5Jrv5UDPg_*ymb9Wf4{cWSKQPVnM+|LRs4G6$$HeE!hPaTe-K&mz~OsP;2j-BxR(?AP)f#uzN&=pg<*zckPpvD5<*414>M@QcZkA z{TS#_3qEDyYPe8Y)_UXM%tLG~rlNL#VD7}lSnH@VU!J};&ROsKv~zHX^sxN1!=cK` zJiI*26tB>$mUq}!;zPB_o(@_2Xi!Fl?<3`FlpRK6_UYG%*AdIlP)sA8?_vb}tr>fh zEh%g)CFOxWCXkbYIoJb2B66Ek3{Y?a)6S8Fr@@3xO3W^*$Hr$9NuBzy)`JCak?58_ zhY!C|J)5*tDm3|)AUkTwA!Jjel5rQaG(ioP985A7c;R-2unq4yb(yQT6O`Z!Tod5( zYc!KE2~UT1BI>VwJ!G;pKObLLD7iToxy@ZK4DrTNzX#VwgOUlQ@9&Glk=pXy^`YaO zJ4fGHZJ*e%P*flEGGdWe=bU6gdsUMuuFC-a7NJiI!tGue?3S9nOU<-gdeZ-=LuJIO z>Q((*zdT>4!K=E4^F0K%stn;oLNQUzcx^RgRSwNT@@EWiB~(r1;Zo0KqD{rGyS_(L zgdVNdhO0`YHfMq10Iq-^(-%YFaAP$w`)X?wDtZ4F5=S<&Cx9Q3r7vall{vMDK9|6h z;y8qlZ)$6v9f|pK=!+NoGQh-AcYkJSoV|^xl zGK$1AqRf4quz#~S<#-HwnP#9=_s4BzWJJ+$HnA#+Ez7z{1|M;e&0R!vK2`MMP3gao zI}++TaE{_Sy-2?ndb(!Bd!I6_QXtRthz1fQ=7QriRhnSb1X47gBN#}sbuRmAtaqyj zVhpJdMy6oD4V63-ESWZuZ_eYygYijhBmQ;!K%8j%Pr@QOSs>LKQcVc~v9IcWJx{;h zGO%HPV6N-6UKVI@RZW$e%lSDpUDL0Zy>bG1>N8CIF5WdD=FM24P)}HOKTC>2IoP%1 zeoSCQU(NPHAGDB@;?!PMT#@ql?6FMt7vo%>ccf8OTs*Okg`>R{smgAaXJcz4@KtJ) zA@3&iQvv=y%*!;4p zJx1@=%f^vlPLeVIqyu%=vys60tkudbQ}PP9*du7LwK@MH0#yK#s!n z#cWS{R7mf0DHBO7gJ{A$Vp9-Y9H0z}VEgfS)|H6-LLqm7gge!W!EiVYsflJm?Ch{T z!wIXF6? z99VFc>T?>C%sj+(P}+X~4XH;%X;Q2Wkxg*2?eGW!Mkj-aASM~C@1XCnmz6F`5Ftt~ zr2i6o+w4Kbl^g~D_Bmhs_GpiBcCb45@v^zfJ7_p`3pfR|-MiJcG#k)W=)!gE zsAusvC3MjyIO3C@mpApo90|q7z^4hjxB$=K>A!jfJLTL zKrAwof})LlkkabWm8CD)No+YHHrROgISt!hYc}B8>d;?R^YECP1<7SkXP-X!V%b~Q z9@>%daNcr(xYhONaGUA=ws86KTeEBEX<`(+2*c-E-xUsR{i7G!uJoRJ_|DNFWQXJ@ z`=r!90PTtkQ5A3mtF`vwLE;9;J^*Fk0TKR0A&2W7-o?aFQ~fEFe@+fkF91JuPtFwP z9dG>Qoo(6HGv>JeZFI;=W<`tZa%p{|?GZ|IbJAx}ED)VEsIt9~N= zr1zakE0Cr10XDK(kVUp&6cd;^F<&XK zWl;Oq2>v36n~@_o-hvj{S0M1&iHxh7oecv5zSP>YOHSh zZ~CAcK5o|#uiy!!{y3$ZlNHAf`VfR?yF}-|&j0rzCxvh|60InYlF-qlZ({0QVJG?6 z7|7#`5*+|HU>}#D*DG}!X5<;$B+A=ScnUp~6>-h-hKMV(s!KsYfyabu-S+5VeH+@?Mu2z59JeY7qc7MPAp^U4u!n0TZil0CJ z<8<7#=98&ErymVoRK5#n-^%O%e(-OaenB?V_EM520DVA4Ky9t?2Pn>moq`rYD<93< zEi#b*r>EsmoS9eM4H&wuN^C-jtverUne&M<8j8wc{y3V{l030-3_EVL?v-HDKVMyb z?bQ#$zhkot?NK|N=o?oRufR0_=7Y#YSdY_m{I{9k^; zcVTFRBv)VQoag+RsEOARG4}_S1O7TFZcW`9kx&dcKf5-5Gw)#BTbU0_0m@t7)@05i zZ>5y=|IxnJPDBg-G^;QFbBk=BUhcEeZ}=eXPxKFvvAcGaXZ*SPr-opM&O$KeqUE{0 znY3BA(#KY1%F@L3NB0hWZ@p3fc;<`Vub}^0f0w!|UH?<>^3+-5_aoY!gfSu_=MFI@2k80i5PuPC?@)4TzY~|3l%pV!xi(ISD z;ZA8QN}ZQent$E(EG}IHW-TcvFQTqRpN83N(Y3)s^XHtc-Wc|^xwX|!(#O1%I zZ=IU>rIEaKx=ifystdT}?$~SRCrY2m3F7BYS9t$^t|0sH?WeZS-(>J_Sn->P9!C<@ zfiQvx_A9745Z>>Mj_>yhXThxKzUsRj*We31a$XT0Mniv0p4^=nqsthAnWXd3P}VD* zq|3`mhLDu#uQ%q*Rds=y$w!Vv+l^0${bv@EM@>QFUxwLAXt(}&@9G8W;1qKK{F>)y zwk;eKuI<0~r{($9r3h6bivmKS?2x@+eIpTys2&T2a*X&n9YI7};_kz$(;+td^S6?p zANt-Ym-#ZIVh!jvd|~r=HWjWSO-?X*^e;-=P2_4vcb_yk{7=irua8#vNnHNs!cVub zFJaMVpC5oDk-7bLgoz$jRE8}D4o4C9lpTYftA4XeFt`^-qKH%WO1mABoTH}Vpr%X# zqZtk~$FH-ezSLM`4Bj2-eSIs!<%s-&xB>@_;wUlbfy|coUj^0|%#LoX)p0;DqG~o( z6@K3``qb(AXszr{6e#;}1vK~C_;M~424qPiJ%I$2Z}+QDtk$jlCQmknToZeLw$^u8 zcuj7Y$w$16d>OzXrlT#Yv1yN1P6+C#T%7g)+;-1oFg|-S^Q`{b)s$y~$#(+>IVZ*D zRXxT&MFoAV{PfM>Qmg5V`bTazO&Z4CW)VP*o8^Z$u&%M; zc#)P)#v{|1j=x1OpFS?_htU-Nq`F2GT@}x!iKxy$O18&6cY#i_%%1N49{ubdbJqHc z!h=f*N2cFc>`GXzo?Q#_->FMp6ZqSwGpP3*B>g+Fcb0G!p}X|_kr&2eS54ER(Hw z){ZnlH9w@elPjc=_Fp@Jdw06=kUPYFItTFE4zMyT?j$Yktj>A%@h=kk&pM~e_yD?7 z1Z?)QOi5rS!vDYpJ;T#9QHupPZ#i{Rti#N(zEjk6sDYkXdc;YyvoqHzxz__`wGJn2 z#gYw>V(|Z@?$OMS;^t=bS1ey6;*#Z@Hv1PNhdO?@)F`L#+ruD!nz{9Mb%44IXMu{) zS?8+@bt@5uKFFz_3l_KB-gIY5drMy#nwHu6$u=GqWV|qa+%$db6?-OXF*L}ylW^7r zq$Ks}g5A{?d`H8g7GOlYqh9Icv{SaidA*0HL6kJtmm@zunOO|?2%g#adSUY{t(Zct zu25O`3Y+nsoLP7-)~v8*Hhq|szV!2M?}F(nhZi|hq~EI5v4Z#hsF9_q8}nxU$o&Vi zlc{Z-pi#XTn2veMm1~gsn1wH211xB&NYywNCE3SHCS~`DR6=BI>~-u)19{dh5V(}G zfJmHbdWP?#PB=Uimp?58oqJ<=PrayLBZ;Q$pR%ai#m(Nl)SK*ka2TNG-Kp{FPVUJP zk&;zh{JERgHxI#*PC2NC`lpx_e}5pmJa_A+=#PnU9iuofq!|6~*( z86mM}BTjN3jDL~NQEDv5R0=E+#6rACjruc@)zMoa|2=&BV)38w{$;!Ox{nt(qK}&| zBi@_=hHb5hK23?5Q=HoC0fm(MO7(W%uF{tgcWcIN=*-u5uk8EqeM!?nN#KvNmrQoz z`OE+TS0}6suD7Laty6I)^!B@~b;BPsA)_y*Wv6{VGzDg|8V<<0Iv>2ZuJw1hLPHUx&-?19@upVwQ6HepGCs(n<^IyPyq748^6uLudl$bo_5P;8ib@n!3m~6pMPl zYcNB(vkg7fsC+BpgU6YN3d`(|5SJQ`h|A*n};iBZma(e9dz3F)`-Z~D2n4M zz0ApIndn!7#faD2Zk)OGk%x{nq31I2KPY(X0=@gKlA9O?9}7McSjDx zi2qVbr#BUvz8?1~5?p)hIv}sAezmqK?7!D*ml;QYKe505F4Xt!a^#=0%R!1VIfo8| z4swipWnkd}$V!|0CN@p1=7gr;&%X}ti;X;+KJU_o?oWR@^6o{;(X^ZwbGr}bcWv%W zm1%yx*|aDmeDCwBP7M&ZD_l_N934u!GOhS!TKCY@_DtB8d#-Mt%JTb@k@sN{!i5@1 z7mzy6kGJD3O)agviEc%T#ryO&**dny^Dyx%$}1`F?+xrcQatU);1>|RQ@ECx^ktCVF~BnYVI(vTKeSb_X}47av|pDL2S*NeqgVwk?sFe6?V7;a>A z>gplWYo9K~>@VNCyiv*`p1ydrbZRz+y%!6S{&IPB%|Gidw4(Ri*K#6y9wsjz1gqTG z{`K>6Ti*;%kL*w!5qF^~!TrxEHCOpem;uv7x#Ig9?f0itwT6>6vVLctzV~MJc5p=W zqq_NQtMLgF0<~u+EzGB`+nuk6nOi(^M%~+S?VZWSh|#0Wl=Q$)Jwt*5NlK9ZaI88xVk_XnZMGImN6 zngFB&u53|>yRo8P``?MDjn9Pd2F1857T!JBwt1cTsy}Dl4Kq4NK%{$09$$#wQw(u{@BT&)*8S zph8(Q)%)qJv(?Ni(bF?N!NJjgu9@7tsuv^Hst`0|rO5sVhQ%AUtZeJIwT00!B}P8` z-0L5126far(L2m-3Yk3s z?Uv!nI>y$eL^dC=Q-;6VZl8Zp4fotg;!S=LkVIeq%bd_s5}B$A9c=ds#VPN4fQY&3 zsIsRvVlq7GX+2_^Xw|Do)2#jXu3t7I@A7ly|M69`MqVHxxZfcj2aaCkDe2z#BmtLR zerY={95yEf6}{W{`pxpMr_yJ?Vox4@Ho0w5YF=K?H@@+>b%D049gvC{qX9s^b(3bm z*Oa&a(WNWE0i`krBu@ci!?d4i73E~K&#l*cWZ}kh0AU-a*TWu{ZPp)Y>E007?L7R* z49C{KJ*7~0_$8*Qr1o|6-_ELIaRHg{-7O--+revFjbFS1kCK|aPH5bT=?dI#lcsMU zvOJ{l^2TQD+3U^ESFie2$gi5!TXXf(I@{ax7~({yTg=ho^Q z%LhE3*~enMj?MY6@MFBV<8HN)uE46*;MUagSoYgn+=>vokhJ0Q0pn7koME>sHjIl2 z4Q3lX5CI^>=MW1LtCP{E)I8=An<-5a`Kz5Q<-8pDn?9!{sr{jK$-g0{?W$wC;B1d- z3TjvM#=nylCk%GPqH{?Bn?i)=hHD!9(^?TtK4ua3KkGoJzwAtOBM!?Lx1@)QPs|xC zd8;id-wRJ$ZLO7IKYuuXUQ`Wdw(7kYFa<27GtEnd$X($~>eYpHg| zORGabngQr-t)16J*YR*i+Sh{LGiip8#E#wx4y+Y&(o@t?Ss3J=8*B-y z{c-k^aXH>ABHA?w>YCIAhf5S_Xrmh>^O82)H+UK|K6|`}d7$(`kJn`gSQ8B^gONb=~51M-4wnU5aB|8Ty7N#Z-}jw}BIXF!<0ky}k& zC6o>_JpBN&scROmq@Wlzq=vyHC>o@YnIS0>ogow%WZw;zT*sr4{4TKxMjoUwbGq2( zvMgpwm%j27XI|CYulUYtvKb)^W1KN8EfE6@;TT9TE84Q))H!aLQi2~jI!-FsKdZ57 zFE%wb`xv|x_#6#9$G3$5ODp)L82dJhh^+CSaX1ohz})R~yIH<(4$u?>0tw&Aqsivy zO^NALWb5z&c5DIkwRK;7eszB%xzK;!;c{Z~+7TF&E17aT*}A0Gy~yafwa$-KMQP3U z_@frvn|$NHt%3FVe6Sv5p8j(slJRA2AU`lVvRqSz%j%&_OVsTS^zh+#rAC}AZCzwM zmLBqZh+v81!vvUmnzvmDIT3zCfghFM$%@*bH6hC779sLG6WJ`m!h!im=#kF6y~5qf zzN^eOFnJnEm}JWa9VMd-$!-jmxiZ``drLB+;&hbc0H#zxDS`x&D9EVP?tJTyf$a1P za6TTj*j#?+!Pt&!y{|F%{w<58^b0Otdpl+ufKqYZqkIK-+LX+~! zWWbceH)d6nkAQ|kJ6zst{j#r8kQnjbTDCUUGpvx#^r8W34LeOiz8kF6fd$4LrnLZ3KFACfnQEEeO`>afCPHdXFm?e|??KLzRzO?dc0?krx@qP-38GQLpY`VLl=# zkqXk=70V@CgJo`i7`x8$?p-;!<&USrDs8qy+Kqdo4F>`>m2*}7O?7;wgE zwB(#F;|Wf?26(w-Ln|T9JEfdCbhfZE#N2Jfy`|nZr)buGLNaysgXFM!CPulQ>QD`X z^OFudJs{zTVnfaqc*c?q0-k{@=4oVNXw>zbCkwxAfJG_q6ms{UyPW}zP7IEvjNO-v zFNwRJAkPa4L!0NVk>8HE(BBK=qk^>c4j$Jv&eajwliau|@;&GNwKQ#llwgb++kJIz z)eM%|wd`d_`+GoMr?kM)2f{XXa=73sb`8XpM!(T8eE7;U3LN#9iNrs?zG-yvk7LSX z45Q?RkV14pJ9@*b+j~IfG7&Ne&dUxH8=DrG$kwk%aC$MV)W6Pzmy*VPKPbRy-Ab1OosY*_HqC^BoOA{l!B_y8P@tkeLO-Cx@C$Tj z(c6x0%qK5~6Q=Igy^y8O*`-!yrhuZ!w9+UAmh@`wF#!?}Y8+O2!d{(RRP9!jr)Fd6 z%%TCnQ4o*`!?SBt8h2DTS}6H-;3y$aXdd&)?GMB3gD~YFlM&A66zmi$HpUVRx;tjI z#~MdFcW<|0?!DSL0}|LfTaLerGTn&_X!E;x0YTxA531lZv@IJ^MwfReu`lEBMJ0iTU5E}aO8D?`F*I~8(UjU16D`8{s}ni-!@(0~J)a~7+kfDFbWC&l zsJ3|tWjY6z=L0nK?NaUfs2k~6$DZY?#XaP*{cui$i#$Ol-rnC|dBOwtuJMxE1u2m=<#A_Nx~ zNB{sc56P0o?wZ61uk;^Z@T)b9g2X&iF%=a%=be|9NJ$xxFhKO3mM zj}&|<8BruqeEYAdkAZ{SgU3%TteqBfdSeI%7|b~Q)`qltqvZBH`Gmr9LbOt}NA5x} zJjK(C8i52YHsH!glAzb4Yk=qs#lwfkday!(jmo0_g9bsn^F2&IFC0s5+{}f@p@-?i zwAJJbI(-lNmjoZVSBUe@m#H!!ulKqF1}!yfUp|$E?+#K34l2(I4lJuki z7t7%(g@OU5nW;d=I@oP5fTui+Wv4oF;2B*y4&0iWF!b@)T+Lv;6ZdU;y+-1)2Jrz>2RfAtm&;iDA#r-@riXo00$Mlg$r7I|6F!!^uJgmdvx z;OJ0xv)t)$UQb`wo#!V;S=|@NM1czEQEnkHb^cd|+xza{E3NfE!U?D;NkFQKieiQ$ zD2iaJRDXni*SFHWo?d66n*X0?1SEo?VE_;T0J}N;67O2borMajpqMZkSLw$84YtFFN|#M+E6}y!(hzL!Y$Tr*GW{E z8-F90@{t%i^FdTMuO+GCg!j8Ux1OZtKMy(u^}edRelNqm#!Cej_O`JcE8km&#tZQa z1FeT!INY=|h{XpeyHXBtkyucKmKL!gaZch0?Fx>CKBUHkSf#1tUVjg&`8C(rn2XK=F74sq>U|_|Jl`eV zWAhArldSJ$xt$9_uSSn83@nQXSPJ|a*=vl}E;CSOMqnA4#MLDu5)`p9NexjIP_)!T zQBV{V(yINF@L?D@r9408&#;5frGhlr#xcg;do9B@~23QZ!Kz1W80u zG(|{JMNL6e6+tpo(NxS7RC@lK?0S1no{-93D9r|DmPIdmI~L79X||6*-Kp_>>DldkX!8hGpU?Y}z}p1cB1nuyOIFC&XqN2@B$WFsxX{85xI> znfVL(N#B39@I-Sx#q2S(#w>BI?Zx zEwGe!oT1C?S^9~qELByq*at5c!R6AW&VtLAZ{sZnPypk`KO{vcTq*iHeTzTNBYcA!38@a>;;>CS@?$XxWUa~h|IgaqaC~DRXI*SC0O6>>E?Aets@)GNfW5$-j&;A}0zAlS&~Hu`O9cC-&yW{>0uq zW?ZMM0t4vUek^j4-?|IsSN@-MoF2dMdIQvqq24lc4cdxyMUs> z4CXiEigx|ZmK(nlr^b1u+3hd0JvUm3dSt<^HIFBUv2Kp_SSx$0)AU(`S>3pOg8W#- z)P+o%42B;^5sfNX+*L|Yx_-XseRh6yJloqvg#&n45mv~3g9Jw~^Af#zlm(*`p5B;s zFs@TwATcQm%`}Xqm|01nx=&|9!|RsrHghA}L1uQ|Z0GLdGewaF?YB)9vLc|@J8T&V zE6d28OYSzVhZ;9#6T^J`-@QU@t0oYw4ulp-x~K!5%7R>m%`9nS)3~jlC3f$pK6$E~ zb!*svWxG8ZQtVk~pQ(&r+(to2-#5?pBKI}N3S&>B$%O}193$@-cIY2S(BEWrqvCP) zXDQpq0u&c0NUC^AL_$3Et;XgpLRZpQy`@do&2=pO@e`!C1oVT#)43ATTz_1xm2T~Mwoeh zB|5)jwJ~+A^0?vArk_V?^w*aRp{OvD*!agq{ff02Qth?1pxbK*1|0@W#F@F89VdNr zYM4C_i`?G+BZU-%?2d8Zzz=#;j*go)|2}#CR=-b*NXkV^P-jwnj2Db%W;F^^pqIwR z$%JSwP?TML-2HkCMTVDV<={(!6xEtoPL!aJ3Zo{TR{KO3pj7`Tvznjc4KF0W>I)GJE%0@K&Bk9iFiV>q^%L!ZvlI zB?ky5FPA=PfUv~R*oKL21E9JHN%Pt>{q@+qJCCZNOWypRL&L1yYkgLxZ}8>CNTv!m ztfndgEG=pP!GP=^i|kWnfj8Ht^MGsbX20Wrl0i1y&8JWDye&$~Q2{vU?Bozr31%^mvk94=(|UoJCT2g8y+EFq z0w<4EK?4nSd>8vFZ-v~g@Gt1fw*eaE@oU+n%B0jP{RMbYy;r<)|Kh*ky7;ousJ6zN znAO(seXB-;Ps2Jk=f%=+>OLI)kGCB}Kob;NGQPJ87#D9-ZgC|gLfDM~3YtIu=kuuw6(cAL&>-TE&|?ozS++P3gIm$}nb z9?8=Sls-d1rdt1UZ>GK1d^prr3;aIQA29cfdkrwJ{pt3INIovL>4|J z1ZW86YYF!N1&8w=#IaEsbY8@w5`ZIv0!BO_n;^k9Z&yV#Ao@N3Tm}6V14%+4QVl8v z0MesEfl4hX6e&$nL=i#D(VhUHzM;14#aiN(*pWhBQpn^|ZZXBqEdZ<1)5P{@OXSjHNNSes!*e7aL9o4;xr!1Xw{ zSGU$1CS6#f$;Yy43xeqORLIci6kA2@#8kB}chAgCm&wea_$xsO_i<~{#^G_HrG zdfnmc_ze2|PJy%_;HYp1wSf2tZBVejIr};*0lb{4O$p91!GaeR)T9IQ(BB5{pg2Fj z*%~VKeM^0>Q#Y&C_-)`gD~CY*vxvp)Q~Vs2n_v_c+kgA9>@ZI`khd$4~ULRIKzpN5Ng zJ8T9X3yff^x4IdR3Mc?_sMuEM1%=Le7E5H33}D#I10;cf49JYYRYX*fRV7sm`GN3F z`#(41gZckNP#~Uxx&uJaLTQBQqn2b?Jr|TB;bdBXHJ9T-8yeVG98b1EZj44SM+o-7 zqlxGMn=@QaJ_M3p8+jtq9*7ZpoG&Cee&}qw`~M{L9{SpkMybj^@y@zN9r^{)r11{) zz#L!1fYG(wBsHthP9=^KYH=7ypH&X7oK{F+Tt>N$`xp3}x$(U;{)boOSt!Xt07x(h zgf;izLxQ8@QG|qygdx%Wd+~h_QpeEq^PCrMfG;E&Bme+2FkmKyHMyTn+DM(bLl(WA zNa?jNxa$v{9W;&q=%%L!7QG#sLiSW5N>B(GG9nacnUrgtxa>fmS053qreEdm1-{SX zw(;q}f(|kyVG7yQ+1>B6jt38IpSw3i2?!H+IKG^Uw?h+F2tYxC0Hzt405IL#+D|7v zUn?O|#C6ebp*|=$RUIk)mZn_KP{>&ZO+H%&AGf`-wd`BghmzlW>t9>5!Di`aMH|ys z(8`*hccz@yoSg|zow#D!^-Z%5=y0^@VQauogqun8Zs*y0o}|ygbRqsjL3a8D3oB~xkHf1`Bf}> z6=~i`Yn^|sX%Q+S?M3qBOwM0B~PTTjonMnQd3E*=@bF$w4N}orzSmyhCzLc* z9qg154I@P;fKo95Kn(~|fl37mi9)o6G(;s;1pv~tF$_f2OqEqE5L1q(=V!L;^B+F= z6H)8mLF~OFx40B{u-;KtabhLOL{vmm6j4(}O*G3)RYXuyGDt|lgkc0^0voNa-%ow3 zwZ+?aJubhWhj))2#VBeH4Q}<~EhSWOM*C6REI2PENFtJCW<*9r4fv!wOWfY9lk#f2 zuGW9m<)}a_o)B6i!Wv3N0}vH25A5PVC}t zCv}S^GiW(gDedv{IJ*9H9XIe;5Xapy&^*1S3T?Z=#c=pmC19wj+;MsgL211bNH!hHs-S@bGe3<@pblyg3qg*%orIG-8pbl-Sw!s&W1I7 zcBsmvmwJ=O2|=^vuqqfbGX}jyjtT|pOYujbr+PzUgXhV+8)4wcjKP_anVH?d&JEUa z*=)N=63LRa;@HsB5(3XCSQL;xifF7~^VND*su$;&e>NUGFB;kF7}qo09yld>FVaBL zVd65xP!y=ITU13%P|;IRR8>(^NR0t9&`^X`FcM5jOifS`Op-?sEPm7&E(c`uflLEM zC@D1%r7058O%LtH;5_0%v@w_Ss1LHWxyQd^lBTwRm#0-TsP-gDX_gL_`B`y{kPnJwZ-pHQ%44LrTWt$Hh{=@{fE&l7uBqYQzU>Soj4EKuin1*;L z2n3>WmT>~avap1XuB-0k`+gS$Z`0o8>+sX~904L()mchCqnc`v)c1>LP-bd#I&>x0Dkth8aBX0-XPRh;y)3?h2k{POkm6z zkpN{D#rvid@{(+&>8@y!i@I?txur8g;rM}IL;6nG>toUOcYJ(b_S@xJIA`i;#H}da zmE8AVpUmg+#zF@xzfNs{WM*b$XB1S=2nBZ?CizQ$c=x zsz~$iJc5oJJhXZ2Qhv8E9M(vZiUK7tMgTBm05D{+$$=!tsi381+g7%RA%0H6?q*{& zeWwRAM$_BNv&+ZD$XnA_L(SJ@Vd&qYupp8N>Z-G;g3J`4RV1paC^l}ptCb;C5doMm zTmz~KMil|tjSR}P3>+~<3S;K>orhql6+v1%7ZxfQLkMMZfmzzY)*up!3w;(xE)Ez;}F9on>PZ98ir(3p$D?PSN;x%XR(tPd~P=qLr%W$=3B?Ib2T;B}`b#&|PM>-${_U;W0CL|_;*BOqWI12Zr;nY^U|!dpnCn|Wy0q{P(7q_8+M zgl1^Y@%awEf1Z+-{NC@w`lPVE0}9oJ-M{E<+Pdr4bI9(jo_D85 zUf%ww;Y3Ex0s)x81(0DRD$-OTQ_7*q@QrdgxOMIJm_~LZhK~rz5Swx` z7TQ}TAFTE%Q)kySr~?4Of(8s3gEH;BbmI+#_1xr4z+G^TZH!AEYw3!?H!Y~cX6I$l z>?&Gp!Bf+^SZ$c*KJ{>}HFS8;X1sJzL)c8rL-nAt;Vi|OK_Z^hxCNCQo^qg?kdf9< z;E-R^UTVkjSHKM@jN2I5@i^FCosP!faG5gey<5pf?zcjO2sI z%AmZXT_+MwqHkUzd^B2PE@&3^jrOMU;J6N>Q4?2mFASI&U@R6dg@PFB* z6IB$D1p^UekOn}KM3NZ!Q&w`l@#NPx4yPs9L!)(U;r?7a+w%VF;BC9Bs|GBihri<6 zFOw@*Rze{mB0+%&LaM2OXrNl9h#91q2q1z_l|DYpAolkd zO8Rlb-K+{`eIC@%Kkwtoxxo&6_BuR1cXw4nJx{TV)?mJDkx=RI=XFEIJ{OC7F}nF2 zMX>y8^QA&v*^{Imj{-8ne1#yz<=Sz(l=71l!+FUfFRh)Gc7?6lW7n_7uFU zYenJ5S!>EO#C93DL+PHW*yLPlIXf!w$u`bZHi-;$U ze6wBE^m$o8=>!?aCQykO+NII`M?2m*Fc11&ZC&oR-=xa?Bo5)=TU(el)Gw2?+@yBNvmK_mnW$cT){jKCt5F;w{VY6o`e zeO`>ZGfaP8`u<6_4jHjx6NHlhZI2Dlw~5BqRW=tI|oXg2i!Sp2Ji`}=<7k=lxO zC+frg8I}f!ntB^HJ5W6s2!F6aZi+4uAK-}ov|srk2i`;gFNw{n;ED+UVhRJQFO2{b zE&@GoUL5|uHE=CM8510|UW_Uwpdg4;SA!1Q_^HZ>A}4i;xc8Kj!Vo`KIsgE39x&x(5GT*N$)n`9xn!6#0WoU$ z(_&jJ=183X^cu=Pl=to1{|76vFZRv5mLs(tp>h-V@Gr%k{ZE`-pYeYyQ*8WMgNDcb zki)WUKs5iu@bF?u5SNR+WQ%|dHPO4|>E#-JyeK{_{T+%tICKn#T=?17n)l`;6;zTx zMgk|EY5OKFg5SIP_M$P8^#cJ z9lY{9sxqQTNC%IDn`@0JpJA-Z;T(36&Df;Qn$bNC?VADT?(5ynPav*D-CaSDgQ1(= z`&)x93Hy=4eONtjtJ5B-OjAS=1wfx*f&7wqBv}(_de9809P<7Sz#IE_9ep*P9T4tU zsqFl|{M1#pu^)iN)=;YZKU5duck+Yra1Xvr;5>#vV;w{y8^%S~G&)~_p;~%KY$EIW)5im3P>L?gUyTU?~r@RM)n!Yu*C5!c=1H_Sa zlAu&J^wL#G7uUy+b7Lw5dOnQTnCP{H;o^x7a@a@lX}s#%@JeS_i7fawBWaZBbI3OC zG$yy6l8n!X3^puI2wD~vplInY7|5-RfWk5l73@tAwC{?>aG{qALS~`5tNfV!O;wWYA`g}h-?7Cjv+s9*qoT$?|LXD!I^n zd|MV4r1JZ_SZ90M+r7Bc3=Q3mZR^#6bR5XKu{crkIo-Xv^0ny27I}2F6$5zyaKYj6 ztGn5!y)yVG1BbGNJEmVWvRU5)p9|oezS@ZwE48CY zn(^MpH;?VD&c#4vV)c;+%bu8>vhPEa3f^B+@!DVu{{rHcXsDhaT@OqP*Ml1XEB22Fz<>_$ z$71gpB;kxO_6WdOp0BB^g3g8uj;HN%=8nMKe&R+$qeIZK5d|5Y1IG3-Y-NIG*G^+G zT@Nyn=_D@ZTWtyM_?{DJHvY~S$EUpUCS&&w0glnV(Dn4vqXtflos;Q_pcbkV9Qp2p&hg0xG&%Q;$ zi6#q(Zp;`XwXbfH33!Vp1~K){1vCN3_P9yQJ>)S!%AMRlYedhn{n@E%criUVhOe0l zJ*;HwV2^ioNFd>lvd*K>n%kQD+w0^W?wy|gk1BJ`#?_4<9*;)_GsmFlz|{cxeX3K` zGaiMjtz8Z~hSSk24e}?NP8i7NC*w~pBd}$l zLO8|jzvJ2C{e_rk8OCQBYM4B?47{Kh#P2IVFgbE6@|ImFwdc%Pc$aqC-rrbv(Zo&5 zI^Mq4KJq%G+J)Y=CYM{x;@pQqA%8OMDh6$J(t!5P<;}Ll$ z#JPdGV51p*^R;>Lft356v$6Hqh)T{3vl(*52zn;?_Wlybh<09xW6=iaA8(q0nJE&C zs+kmv%YVm%pL&XEc`BoVuXQw3Niz(U?j)^ zkC||r+~f{4DoMZJKrPo1GMLOrT!w-03}&g#B>*5ozFxdlOfVRL3iBl(GGJRz8v;TU zSvhc_=IprRvIro{)u@2{{y~8}!3((0dLxMsx0av=NWxvLFQkR%E|*xJ@MWd3mYtso z-f|;cIuy>JGAC=Jq--~=lJ>Ak1s>HKPVp(I0c}R}2Q4wX(sY*rz4nJT)=R^FeF!q- z;F(}lWDAtoYBrzBLnC8W?1s;I_xnj^7AcjKxGLl^+xlUDU!&UVKH{9}sD7fNkOy_= zYqM+TaqebXTCvXC`1f?d^N`L3HG`F$n?|ZSP_@tFnMLp#0H%;CWteHSH6rqOp6dm} zf&p(F_3XR+zi3;J4(cJ2B4Ect^0?h^GVAAUeRmkE94EPyX~UPssSd&pSP_v$yFmoc zP2`8176Y$M0H8ua4rT*{!-?U+*gdek10oX)7pP^3U_!f#4! z7sG^N7E(Di)=1UMK~XTt7~-BWg4Wfe#Af#m)~i?sx3Z0q;kAj7OI{fjxrK$sz5SqA z2WCqcCCs|lNGS$pFpP2xyc-DB+q}3<(IjHo)muqxb%hugzBIR%nuRB@j@}OkwV;Pq zjof)y_qJCYJsETcK}W>TS>bd)*_E?F>_eL_Sb7j;7unTEMvmw4Z|Uf+I7bFenFt0e z83%0ll85LGnRE`Zh~`_9U|GlbmG(10%=#UYw*1JOZu6!#g<`)FOjJ2$F^qzYh0w~~ zNRn=sKu=R%*0WTdyBsF!p96Qm+HUxKHUrh&hw2{g3JT=c2flfBLdm#xk0LWk4rT7D zg z%dbcBiV^C;(;L5!n`!UZ&ttBQPE)BA5F!{fgaA0_gt7&%KO3CUaOJQDwIl+|G0O(i zz=7|W96O5Wt}B(EcP1lT9S1UU+(f%g^apQpbW_Tu7E(#Fb0RsMuY7uK>1|pAPB3VFk-or zvlm7!4zZ{n$79?2-Q`uzG3c}K855TX4-B}6o5bJ9n-&=3Gg;CL&@nA2rzI5(xdubD zclan70LEHS1u$`izm31qTIkD`Z%n2~QmKz)N9f%w7yN}iG{vYA< z46`m)i1~4OIg}bSQTdZW4fH)O!&5U}^WDJpGH2PS97(ktGs=th^X{Q^K+1!3N6VIu znALPnwu{ZKrA$h@f}0ULvBC$o_TPVGMYPN0U&wO7LN~AxV9T3_Vv7p}LQ+R4$YLBr z8cxv+@O2cPfv1vq`)=~@X9H~vGi*=N$4|t8bW^#2m?8nQ!>#I~q!IuyFi)Cg*)qB{ zj~`oNUl-ZF_gMtg1Qw6J_o)KycqUjCIhig$5+{}8Q}GUOF4d>=_xC44CxIOpc|D(@ zW5bG$9R{y&P&UjrfP(%`2Q?z>!|Swq(z&;4pAG^*T+0US7Ld?4I{zYcjb+1v2FBfX zx>SI2q=oaB$6K1|Jgy@pyqLbn!k{x&ttAG`Pd2m>!!RDjh&w;fu!3RQ=w29n8Ax=Zx!pQ2bL$lE?IB-+Na`>G9Tj)g*zj$s`-lpB zy?-I9B_8YZmuT?$W6p?#5)>2Oo~ouzs^s^H3; z`I0M!2BbG|9EX+3^Lo8){9d26#3jKEPhpdzvw8HrdLEVZIY?A7oX8}blbaj~?kamcC`;8WSP)%rrNSt)1UA5b;N;#!>x-`#)m=qu5N`}!6M^aIl zeIunXL_8SjPNZ(lq^J<_JYLf0+rym`#fMX_qy11ZN9X3{Z#kFd&1{GRGt@%I^;$x9 zRZo%ba{Ns#$Gk}$Jv+d+k=bMCDG_$)xcTTDd$c)t(ZmQ2%z{p_++otiak#t@{XatE z32$;zVTA}l|26A~C9OoWHS?AiO*4bjr2E*Z|B@=@`-w^O5741QW*}!E5=xV*C`*ua6@Dt7`O913|#0mxJeKtR$U27v;Vr2cG~f%r?W zJ&Jxm>0sqm7x8cGWI0n25|orB5g_{~-En9J^^gqB!nfaZX#9T_-?FS3&p7>MIRo3yvty z7Q=682~S(dP#oKw9eO+HzU=*Ze3ga+x6Zb>n=gY0yWvAj@YYp>9VbiDSF`yOr^YZZ zS90n4>!rZUVJY&{^9?_CeaciAKLnR=N6_Vk{Sd|MxN?AK!9S?UOa;ezx?lQmn)`z zi!YWyMfLqN+1Tx-R#X=AoWA?FiwTX&OBXHJfgV-RJ$TJBWly{LoubZObIXWR`(C@`%3a6( z8eLueOI%kG9Hpe-e{x{{YBf!m&ZMyw-@fMK^uHjZ2Vd%T#ts!NPP-cIA^QT8nBrP*bU-P@qFpl8OqMmaVtVwzNR; zBJ!f5bs&MVK=WX|)Dg4<+(3|5+r<@nu|;+usE6x8Pz&M%vlUY8`65HBvun7#uMgit ze)4~cffJtz5QG9iFp4`&pdds*2N=2x^9~C0e(gJQ5?OmKLL0PfX7yp`CU|t%Se1d6 zX32sIrV&I?JqVx*_>cqyKkF42p%p=UhzNth$J}`wPN$vQ;pMwe@E%{PJz9n{Dk68-4yepXM%aK1%52& z{Rz8lZ4gG%K=mgSgp~pNXevLI?(OiSrWU#RV)ngK0I56o|62?Z3Zn!pgA|D9 z11Say8RkinB#~Y&2i=qJrCyz|9#|`2=rb`At^aQpYpdoo_LQCnx=0&S8%x6(!E7zH z4PUX2Nu#6zJ~UTx@vsX9ajY7}Ka6%2=KbD}%dum?(9JLg$-;#7`Tnc$OYm0){$6h@ zHLy0rZ3eK3z$c~vA99+>LE9LHe3KwrrnVI-eYl>44>k+rZJiqfu%t(iwLoJOZjYZA zlME4ho0>y+99yiy$2Z2Hsr*sOkJR>5blc5UfXQ^s_?(S=G$AR_dmEzOay{%*QsrKX#Ug|Yq`OyQX)>7i1enX`>-phn; zUCc}RBd{9Co;qy}@@}W!c&=BSbb8aww^peE7+r{<3)?^djs;pP*KlCR6URq9%2|y5 zdOyq3M8oD4$VWK&Bo1u3x(Z25u|>HAxJc^n%0cC7lHs!({JB)Vio?RuBa5j`4whPs zodB7}dPo{8;3ROGF2AbU(`Lm9FTP?U&7|RNph%(+AG$IO+?t2aepZ6*SyMst|#I+PBB#x^9;7q~c=f*x+Bl$k?* zx`Jk#q0NG-+E;@s@y>AeV57Tl&^ZdblJAGBLx)cKgh28vNeH!%7Bvw|U4Y1ALoGzq z^kRJ!U2$f{RI%eAxmFOvl0k1vN7z0)$sm$0NXtl}lV${!Bo_7btHE#BCLC~`Cq!7s zJCzHH*TnplbXB4d2^m`SsDZw=d6Ng{{>N1%?-}kOP$_4WoI{f(5so%)M)HHBJu?W; z(g;Dh;mTe{Li7d_H{vd7H3H{hf>Na{&(#-jOaQ@4gHR-I8-5?rNT=Yd>`Yh;_Rv%u zPyr#)hoSI#6ie{J<5_7b7gtm`Ngr#XxLk}@v~Yx%2m`tyKZ8L)GGqn_wg_VZ07gA? zRa9riM19&aLzf>P9)_o8$*4yEPFE8IU5!GD((I)`hx+PHSlw(D*$i6Y~OTk$G0d$Oqo> z?gw3oMYS8pQAp-F!EZ@%zzz@uzQFI-Qzzsep^)+>APxn(2zkas7|AE6lQA=F-yI=v zwZ&9kX!NiIm5GTu!aA}~0Lz$tGlF1dzD6N|njP{JV|5T9BKJ?2NgA!^*GY6 zC2HGlg~5Yl^$w(fjN^IW(pg?%lN9CaALTGH^ZWOl?REJFod!z^EDfy*6XyF`4BrDR^VaF0 zSvtm9?h!tlw17BF1TO?2B+l-5+v!2mfMf5gw&>Sy%&l5G3i58{-&2QP@*v@k0xUxy zFpLcI0{0_DAP*ex5`v)aDe2`c2wsxZ8qLUJZ($s#@6(UZK#vw8Kzz7B7{=}l6#bJE zv_xTGS$uBoS(I7th2n=A$|RBC84WXlWFw$V@Tqgbo%LW|EHAZfYsOmBB?pJjNRg6* z)A3v}ae^f5`6qj3>g)QQ8inEc$9nKQ{j2-g<3k+oVVl|2K)94lg~5o8W>-;}bGk0Q z$%5m=qG=Gz z5+a2lu#3=}Ej-RNYHr$r*+eL4T)~3-bp~3pf$AT|>LSVUjBxMJ9TjFp`M0 zc$u`{SB0;d_=})qPsqzB{`pB9co*4Mv6t5|Y)Yz0b#1_b<$VoM6XLP(^%hLGy% z^;Kuz$qd$6nP|6ofeakOnthrsq{!vmV9&nO!+{ptvzc_vp@qIpdv84Y6>4LAceMPbS!8BH>cDztE}&pl+TfnMf97L*Q{ z<0A0EnHGga0Z$PbCLov>W>$hP7s{n zsowyI1|j%RLDWm&mnH>6<<6=F<=gAdxwAp&_2?)S$=}|fRF8M6q8rCB@G7FUsAV{Y zP2kW89A=O=sa?`ljRuLo8vdlFxYk@0+&Y@-BGEVI8Up9RN@ZorVY-`+l5k+I4?aQ) z;~fJw++u9DCN46;mbtu2ESU4ufp&Q3#*BjN(Uy`V7*Xz-F(w*9jJN@m4DQ+`Q`e~% zUdj>|U{o;UZ2(+rAc%($!3<*}Az)x>T=Z7C(?LreDYcad@ti;*jfk}IMpcy-m@x>- z1HssItS2`c&?{%p^EES^PI!Tc zcaTs8*}goZ3^@W*cPnOF@zO-G7Yr1Vk~Yb;bom-CLk1 zc>#RXMOZoNX)G;FUPC~$kxea`Xc!L?A)8eqZ$#}yJt*6k|GvTGkn7DeUV^G)04+O= zm5zCV1A%suwjD1|fT*VfFDWD+n1Fqen+-VWKyU-xN>I5l@lyTfXwHLieCajbi;ZL! z#58yhe%_Es#4&2+0m+OL;=M!S;32fRX+w^E)2nSZYKw~8l#Vje?UlY{5@hYD>4}QB z1wvD_J2gE1odEbydwdnbftW*X@+-@ZIf4gmFuSgfJGzxYXqX;+xE)^qC{^S(T11tT@;*oH#n=H?&@*l+?!p%||r04h>BV-LWP6uq5UD_k7_ zJwU?0(T6orlzFjDXgwH#ee_|$T^ZcYtTg6+%^a=Wcu>vd(Y@>rWuiSn2?i+~1!5#k zONXijT)^ZTo46?t9ivd~;_mAT9A)kwps5@Dlqw~3midRr0KshYZW10?L_-DIJmrw| z7|A!LL_-M3NH7Ko^~@|_3N?j&#RV99k-VFe%g#Xvr-jo&3o%hQcgsRUxCxA$epQ&7 z((5=Coilg5>wv(eSrHE63JZ#4(ZVu@ac>c}U;!Y=6d+(dw!Bh<{lriQQIrdZ83~6j zYG)Z>oGggY>fmr#NZLlJzj*N@jAdy_3z!zMcDm--7A+Isuz)F~WAPa}t#&(LTcbs%afPfb^jC!U|41ijdG{~fZFx(**$8>#G zZ8i3sHc)XplCKfg5rtb9kv=H8?}%PNGm2; z$UuN(RB<4rHgD{@NDsEq$^tWqPdV@z!uM0grJ8ADrZuP1+DhdT*A!CRC6M-xuP-2eT2T!#4^ki zJ2X-y11p1%F&CylW*q5AjQI`S*@@&V`^hvPbphFt3wz{TPv<(Yu@lY>;(E=Zfq@Ui zx?-F|Asr|z$5%-(=Owy^3Ia>$vT5qq&xHq2_KgQv%eG^(FxB5L?#>u zju^aHx7+?}X4W6DeBUh@Z;&+teqY7;sUQTeg?;H6G~hWdpOA&j5ECzU>GGB*uIWZhr^G=YROx3=tK~==D3D3c#LjY%B`LBng_xV=smr_;3Cn*!(OW|8mw}$ohv5 zjjK!2qR0SZ@3>D}xJ4EnAn^LH8UgLSCny9dYY`H3z2mqn;-FOk!6QnMIDufv>|zjo z4sNyFlx~444?x2DB3Yh(K^8{^0~lK@Nr&SMLL5(d?hDaSs-iVYqM;Q4oPfOU07czu zU|2Om1`Qh3>H)~+)-n}CLZ*|hL<{QL*R)EP2T{oyV7oCp>-{-z8D^$o*eSoE@ewK(1sJg8c|3W&J=O5+I*cjB#7sAnyNGht zIvp08j&AY3Pt}p<`u4I@c}@ij-{P+tD^@0E3|9fn4ci6>CM21$0t@I~8G3gz-~UFv z>-ZIJ!c>P){+kn3W~B~=x3gXO_7X{MoO=rXVSzu6W{L4Te8cKBtwRQc8X!-tp9%Hs zXc49Jq9j~BrB#3}q|Tc6p`O(hj(cYCQJ`}v{?*?0I;Y#d-3#6$#lkPLeCNP@Z`srT zviaLD$F*ChS10wVQ#dn=A-@#`>O@cm{6`V1sh3OpF5_1Bq0Z&mqL2OmKKTA^(79r6 zj~7m7@=1{NxiekHi3nXSdxzK8b#NDM%*ptFeuZ$?-H54$6hZ3kc-;QycSjw+@btVk zS9mK-lYrby5x#-M$EewcT}CPjU@GhAp=~?34K|N0!w;0u_&*P}*PV6m^v8)tNuGLP zLV1~s_ThT#!#wV~KsYNAXy%XC_`B9Eo?sjYE1rG|A7ZJfCx;5^r-rl1QDAtZOob&Y zo>acBK#2flnLkOmk46)8p*esqWbuQ{}17d&K;Ohu^tD|)mMt<;Qim$ht6Aj)qV_#toPuW5SPx) zuNJ0sApF#;>m199m^r+>?e*zHlh}4_)~x%J*~{QXWInK(l{!CSm=c+Ig$h|83!T(v zn(@$PZDigEZ(zrFjr=G}?(aWMR8^nvrrxjOxgOTT~n9GWH(D%qD3wc_~BzKk~WSC~qV{f=h0 zJR0MGY?xV|J!Dd=Z9T(gIoGZ>F>X60?WJ6HW{Pb1T`!l{UYX}Q4hOqBj)dT5=A7~G z!tEYrYCAboBMZspKWFj5#nMyAk{$FGi5f04WI=Z2$1=*Yr9CwFmZtTqwfk*3cBzR& zeQQ>f{^QGuDReY$8bgNfZ7TQGa=>$RqH`_Ky;c@pl)g-7{TG+cXuGCuHkd-bAXT&5>3#W@aPA35x(ytFm+S{U7WJUiB1 zUDZP{P_;U>A5d62+r6FVx%J&_#4NckUO%=S*A!kbzET7J+-s|Q+;%W-kxIR?Uad~q z48F5$)8^@(1WIXr*zl)*G-*P__%K=gN0`zSGISY@db^kg>7|!`%IkicTfE=Nth&RV z>|Ks8`kUOIbwYLfD^j6XMiX`M=IMq!*w=>sYxMLFryoz2l^9r%riBS$Tcf2c1`+z) zZyp5a0vlxJ{!K*5S{El|BzLAcZYQqq_TR4lF1lG({TCCZpkXPssU!wajE7M)hH}u{ z`^SfeTY(<5^or?tvzFfmlI!QNj|@s}b zCiO28nhg$ml?dFXim%|0@lIVe!ro`a{oQxc+jYuq#3!jWj0bV2zd^TwWpiw(=$>Z! zN-nj>+g-op73o{%P*~tZ_yqc;Q%Bs+f7rY1;FtbA!}TJrD2dy_IJ3B$YWcP|TC|$c zZ;Sa6x;mdiooRx4vv0#`|ppiW)~oS4)5oCJAgO8e@J?;43qUC zynr8@yr4g>{JO0&olnW2^B@Q}hxMbNdIl<)5FLa4EdSJx!}}Wz_z{3Aej$mi@Ui*- z6l!_D8`}@C?OqG7#}sN-<>lVSA9wZsW9~lJk%2s4etn}Q3q1WrJ$g&K^wX$S9K z<^DM05AFL9okq%!&HSH(V*WL6B29{_S@DdBmMpeFZ{+x*5b(?lZGZ!%vXzBd?r+(z zz=mIJVgNP>fWjax56#Wkwsz-r_6GB(#P1^$ff%lC(%7UYDJRk#|uhkeD5 z)YT7W%@_h`wia#12VXFu48sGBlqeKwf$eJxU+tZt3)e!2d zKxgRO$Vw}2zh9rDrnZh1S5Vx#uV}PBj0=u`CMc&+x!aoJd0=s(!V@jdGwfb%+R|NR zJLrC($BfQ^F<*iz=hTw@)@z$iP+A`2=$6EGK90U`?hnZiY-rKlYAj2U@RvM!!q@iBF1V2;%1GHiz$kIKBs&2P)v&d`%qjL7CB z$>sYxKUM}NfzS~+3V{H)H+|F2nFCG5ps$llsV^@=MlO%)XXs28^DL=rDt z?!>%tlQW&80zkF6ANs9MHb)yUZbq6J|0WCtMhX6 zz^miJoque%Qm&*j>hA98KD_PK#raa-y1lxwLSJre`O25N=MrC5Mr2iZ^zqWUWM)NkI18O&2!0(bF?w-{O)95Evb@cEe_@{Nq3o> zeI4i7zVCj1#@Dfjp>=80xweDNz_{JPhkUd-c+L?jzK8eH!KJ!$?(h3t&DryMZB4O@ ze}zY{F5e>Re65}SUoRH6MFwWvJgTtSj~+aBb5%yItGGRCF_Cs4aejsl zCpJXrIqr5bIW_+;jkMk*tVy1SPb-lj{#B^z^5Wff_4xVRfhCJH{(eUipY)p$6)g|& zb~G<)NrAJQy?@qf@?=YS6e=-b%Vvu7<}$t5^QSd z>+ot(bj6Br7v)-^_QW0cFNQ0u&qKiPKEHQ0un_?z|M9yV7kv0{zi2U8mg)&$Gx6M7S-1tZ0u*m$%DcyeXH(bctS&tpEzv2jA@#ulC) zU7YUb`|^~)|2wMSsZJA$CuunOf#Q($@zn}g?7HYH zpW5Ipe_{s-*IvHNR;hIx;pXO7oiXHxYuC=*)cbeg^Yy32Oi5T+U-$UlFZ^#p!sfY& z(nR}5pP;G!Iz6-7hs3Mue$SilI2xzs{(=a;GRVp?jD3l?*yDotI_ng08=1;feLZ&1 zqAGPgUN33B)ZPfg@shT3#CZh3;vT<4i5dhnB{_!fPGINUJdp>z;%D84+f;P+0L=F$ z>atV7^th{c;jiH|POuYb>xm~EHOU}39LkoJ0S5sHFgrK5n=A?_b~)Ycr`%2P2&?kv z3+TU>qZmh{$k9-_2&N*F@zL3rFd8P0c|kk?)zIOVEnbmbAi73F$D*tw^=lqa7;-R} zFKAttyRLz+#Ng+u==&_|E&VmGYvC->yLNoW=5sVWFSny{zHhC$y7wX%sqpN${&QO~ znUPkAY7jDf;u;cGIm76-pa2bWAhoV!L#SjwM9Nw`Key>D2nT|Zivmgq+&-tDBe&$N z_3S8Bo~a5v2$J*tIcy5>o$p6@J#g_J)vGC+K`rb138RdFx2+Zv4X*aZ4>4Fq_tf%5 z`&g2Oya!$2 zcK`l55~j>y?Mh9`%VI00o>pdJ?jf+6-Heqi{RGjS>mEZx^cQ#CZOgEyIgh`OGiq+w zi5c#vK0jvkUB2sIzIP%$1B{LXk9SJ8ix?Cr(4AS+(onz9BD z*WbMYWdGiC6vyq{%B}EfXsrj9*R`1{eVNX&Kfm?vgVu!;g$z6 zb^8~O(igyRpK2Fh?LE8p95plce||HrB;9{mRbq4EY%J}-i~JMQ_|aP@S*(ShM(e(Q zl+JqRBkDngc(XMY2|$T1Ek2`^h~FONhYLM*4Qo74;CoStJ!5HL^E%55TJdDczgp=k zp6mCVJHd6H)D3vPJ{uw$hWpS5t zVjHX(9mS%>t63>fUYz+*EQ@B2I(H{1JAxRsmdlreDx1ZYhW6tD`)aFN1*{mXv1R9H zXX+|U3zj(voGWTF>?zc=#~uk`rlLuMHLc4Nj6<(Z(BJo0Tp$pz%{sB?O)`CSP%zBG z$>kyuqt;CUb6kjO0~6BxdHw3sjkDSP>(lA8k^3J9_KO7D`I_CYMB>d~9n9Z+SmESQh=b^q|2UvdBD@Hqeyvg?|Bh#Q zm=`e5o>i+xt3xWqZ>U%JGPAU<0K*Ql0&?JiZZ!wvL|;)mYwRfO#9r$9BvSdY1uYdn z{eIndU4bTN zub|Tlq9-zY=(xwB7i$iEf~}%H@QPn!qyf0IG> zKWdfn$@XW3=FrHX8&KO#v9ndNL-6D0KUd5-J?n;bHMWKpjsxJL=6oC zO41g9HiU}!U#DN2lAmKA?ERWCK?hC>=|EGX9LS0vbp^|VGGH;Ft{=m;@&s<-SMe53n}Hny`oaIWfOq+?N{p?@`$N|FWxZqciS1`_${A zhq3wX$LQNL-Vpx3;GRE6e?#{v35F!ayZIyZx|!LZgFp0Of`<^TzP(SwS=DGEtXBVV z`{EMyN@vrT`<6+c*dFxE)L+nXO(A>oe?cQU@5s4V&%1k6C7E=#xe**fL>==|iWOMj zM&kHWgqvX;zgFN3s!O@GV%8`V6Bx@Q9`bOqSD9E@IvqT!cF<}=jRsY%s(RxAz!XLN zENr*0D>V+iNiPdpV6MZGwy_j_FKzYz>O-hP>6klD*332RP-bNUBzgK&o1(sRKLBq9 z#Z3tBq*REsA%j`m`@Gqry>PfI`VI-LJ6Q6nKPa(wr^Z?+b*xPP+U7k5@awBCNjQ!t zxzj+O2ZJtqXAT;U@fVS4{)OuQk(QqbHD0<4K6TzHix4ia?Z0G3m0qQ%XKv;sic_A; z)mm(nEx?P+muPVAf1ItX^6wb^saTp+t{13cO9U-Vx?evt^XbFH$ZO?P_Z3D4RGCQN zqXvKP&wEmc)qk1E!k7EjAT?mDg`H2-!OVjn*>`_rN1ql_M7q`O{cNtr+o`cMI76R< zFSsWlJmIpbH&^vD;k}9?#>Q_>gp9VI(_0$$OsgLhks@}p=&eJ*y`~?j)fpLFX?WdN zIT)_(bzEMHd$wlgQ=uJqVk}V;B`nFARm_e)xT$v$+}qI%HH0e`e(c#a=doHmiK08` z)NYr?RvXwZL#q1F^yF27~39UZPvay`34ICatZ3z=drBn6~CSZ0#OjMYfjW zLs31KN{puxqrlam6eB-`;og*lRhO5Yv&&8&f=h`Q9i-@A(M4-!jGbOUv(9k=Q^y0p{X*`RIn zuT?t-p3M#m@&0EzGHB0UZARhUY>JQXw4pe;<1t%S<;Tdq!K-PG>)c(G*)^GE=uOqG z#p^eaA}|_)dnN`c=nqZ5L#{d(+BxB@5EwxS9y4G90EH7E0JlT!NfqM|1X)Xk&V>L0 z7i+6LcV)>AaJC=rV^q0!eG2?bzZ`yE?mtIl+v$-(M%#;S92OwK0jiz&3}itLEi}9F zM~e~T=cyVGA1YAE-+fOh|E0pb?R}USD@tH6cOO-C|DU}#-}n6W|M%I+H(ggUu{%FH z_7fi~sm=V5dgcG`Ww!i=GoBmeo8Y?h+r`!UbX>fT@>%)chyHaIpC$nR@Z-?n{K#TB z*%790KWponn*Ro|iL{Y~0z@GIk^~k+gh&Xar755yMuMnHNG2g-MuMnl8d7BD-hSNA(2<7BJnyAbu)-pJFO2fV zPx{^cHoXhp|I?Sy>iy`SN9MXsX1Jt&>;ANMIx0Xp_ZgG8U#kh9nfCOu zu0|KnV>kFede&>*VZXpzw%z+&N*6v=3?Ju*dh)ZWKbxi2%h{QK{cyeK;KJBmBQKFGJla|7VTl(fWk1Omn)BB3#P)qGhYXwyNF!3cn)0oF2b7 zy%NL2lb(7(Dj~F{-UYbk+ zgktH#kBSYr&G~4?0~xR&=zps-cS8(hQfLA!;W{C>V03+7jJ$(@3D4AXmSk6#BnKzU z{}ulcZS1IesxC2?SA(Y&hqXo>|$J ze2`lY>JoZb5yi^5?HsWgSh5-8;ydE7J}Jm_WJBJ2CnH|C9i45G1K9>!KCbdg?Fby> zzh?c*pC!`$-nF53A>U-6eDl0A%bv1ISJ+k8BYPA8!2xfgf)=)@{7$roQW8OFE+82K zDIx@_6DJ@QLEHVhd&_&1(d~aUMDX}1eatd_Y-qIRAgAO-^c^odBUi6y>&U^&;W`44 z*Fgis{Onrm4im4!_Vc>A7iXt#5=Ex~X>(Lxb;n5{4I1L`-p$XQlbQ3q`r_E}e0~PR zT`g!JZEa^#P!w_ZXmU3 zbg_Bao#*u7Wj!Lv;df(atObtTUUHo$d|xaDS!MCD8K&DQ=TYOLRx&0}^4FrrM?f2{ zj12#{sIMWW==dujQlAPQ?~?09<3uIg_$jD|+KtSWxsLT;HGfvDAaUP#5T)wV^>;NGiC%w%2|u3$-& zF#Ov|$@~%qmpe)JGoU!egC_0kdBQ^1i_uCQN|2!RUe%?hiMVi?UvhK4a>AOf(;v>R zlGf+(@r4dN8_q0m`JQjubv5!a5-25%`*;c_3>KeV=zHdTz8jo&GMjaS$4G!B1Ut?@ zbI5?Ri^SwA7Z*h(0BXXQr5(!i!#R74CR>zZ!?;h0#zwivNQHF@?W_=TC&7TFVoh## zFvZ4Xo{y<(i9A=gn?eI_0e~KQ>Csdp4>()s5km|wV0Vz<=&pgj)0oiS{O%hnHNQKM zrFQ&`XB*X>G1)pR{$4c(^f4}T+k9@Bh?_jQsqs!0U>XxnUQPB zU&-L#$Z;9t^!?nP7CNDt_Tcbt(wDf4kK%xVCAKW9|8M1F_;`;ZKhRgoaNhqp33>1% z6#u)6s-8FW+9%sNdLOG%HZs>5I`@g+j)Ex(K`8is9=~ltxF{95C|-X;g1Uv)5zET; zErXK3js+kAX7TO0%EJ7J`7SEI;TMbC?{b=*|E@sh;1@6A^LF$=Y4?pBX!D#Kj~Lql zu6vq%%ZUH6NdyK>stA457SILn{{!?ac#!NOyXXjgm6;MOTBtf`>&<@ST0n2=q_$L{>4?a{2A{2e7xRe*W^u^ z>$FH!(A@lkFPCrwqDk}!KuUaA1_gB}Cahker@> zvC48fhsxC-E#mUGTYfHamWgHx2qu|X-hzm%%B4s>_yJn17EFZ-6i^fauZr+E*iMYx zg87dZ2DDVbh!atkO={eV2Vz@*)D=?AU?u?pQ7X6!kUJ#w;w0i$kSY?X z5Z}Kcnv14uipQPKmATNMC>vEFd+tN zGjGT&T#jPZURxDm%%Lbltg@j1nUow29F7z;BGW!6d!0Rk5wbI4{s+YH)SQBD+5V%K+bnxbM!30j>QdK3Z( zp(I(#upR{p_o)xo)CXqutsHg{Oo37)y zahPU|weq&+J-y9cN+1q=MHGDah@tIF5dcI&{q5&k(&OfE*-NC~K%%X6ceiA1uS^IXX#_9Xx&q(fasDw zzGWwWp+=5wd)nQ$;_vvlaNse;Mf2hU2f~N}=h}}BDeFZ=^kA=*6#()gFTMF^z|QWE z*0}wTOV!=tf!4njMpDi?5m$%X=-@k^_-~gJ!mx2TIjlPfgXa&{41y2djt`ae96Qtu zwmwgH_BFecy(*sC`Ab84CEqAnq~o6F-;@BsXb+HiM{Mboy*aq^8i(a;Hiq5gkk$R| zU$+n?o{_c)7HcM$Z1YSjeaxJ4-5uKy8CZRlyv+)3n)1Di%e?^IvwUW*zBhwXioRW! zQxo~O&}1=zyvU#ldal}?Rsa`uTE=}qh5=D`M%&Khyr%Rom!(PF(Nlig%hluh#%$U( z07)EY#3T!-jz4Y2<=$N@@gv=9N!QPpg(=;%N02D}$2XFoDgn&5CzQM|Y0-J;MYt1BlK`}t3asq|e3R`;M=&U@6fa`_j&s}RH^hML$; zZW!UZQx1;*7jlzBKY4&5VwuoMkkHK^Sp37*Bn0cLv;H_ zKHJyBi*wvU29$KAi&N;315(%DcKWE2I@KD5QUdqkh9cASxWB0Ux?XZW(uI@f$waI^ z)a5@oR1GlWE$CGGE~_rNpWQ`6_5GX&E}4NjAk`7jt4ABSYF3UL1pH#eq;LKR^FDjmZzINaCQlAf zWg%r|YEo;xp3OQTFr-qWxnr$M|A$9MABXfiOe`len1vG-_SdBmHz&;cjmv5yfRvAn zD%;E3eURY+!$HDO;T8YO4f(Y}Jc%~l@A12M@}1qQ+~@1fei!-FEpgkN|Lu0$9<#Uy z@uVf}q@*SkC-rW1*c_;%N~9jOZ0YSX_1PSkuK4ckJ&Z+W#nH(KGOZSdNWfEtw(60q}UNSYo#Oarbyu* z*OVaiJT_HqWMd8a?(UBVuTP1@o;mGCp5H$$YyazlXfwOfl$LFtT?Cnf2cOWatp$c{ z!~{u%u?($M0T~^1Rz3_hqxAc;q=XmlyM4}1ChPz(#4=*JQ2^$|RfI%jjEG19AQ^t^ zH-Lw&fiZ0QdV${jiKb;F%I$M~eQB+SOY-KA2V+N9M_b)(^ge!n1mysH_^7$SIe%V- z63|rlN5y~v(f$6Asu)b!O<(Kt?YseeR0o}oJe=D2!?%Km6h!Tnu75fOr;lFI>inDU zfz)E4dC?cNnh|+2uROZ%<8;X?5Ek@c9E2d$Bgjw>;6RNCzUc%%Y5+gtzz3ZW4txX; zp#%@N`gES!Dt~$aqpE0RsmNjeht&Bg1fqXVi51#Q8hP!6-sKpUR@bkVD>;q#r z@&Aa((=@j~MHkOyvi}0I>3z_V2~;MpZypQgM3Q>rhj6WUUIuua7y2IqcQAhSKl@%I z@i>6I7T|E?x{o3`|5La&-a_5efff5PmdBH4=+F74?aNMNcD+9QH#ND?&NRDP%|XO& ztAOU;>ZUvgh`ssHa1_*MgzN1h2hpDGUvm={v^)L!KY~!o5OGaHmXy`S_Idfv>4|E3tdh0zFzOL{TJiCLrI+W@k z_V66*zC4XS&kktnJ<=VOS0U;&Zf1=E$T=JNy*v(eu`}gzHTu2htEhP=br$CeD7mRM zq@!AA&z$VKfbF@!=Q2cfZO(Dp>zpss&T!T$q9SXl(FGPSI699NJwS7ZcQ(4?ChLzI zVzKFr)Ir%}Si9#tjvU4!<`TbVXgitY8>ra1qmK!zVy%sGF0gWVL!!BZ5_h@L=g#`; z9PY2>$1fr}jT6nF)dzX;*1ArP&bsTJR=L+X9Xnlb-k2#n)euFya^j}P13+RFa{S=E zs6lfsI<~5O!kj<2fb7biA{#fI#KHLjoR_KB>-Oe?_>I-V ztHHz0>#5rwR=b*X^#>#J^^}r2u8(%yJV5KsEdp|MKZD87{?&>oAStgn(Dx6OzC3jV zNy*S?Kp)+e&>WEUfbjvK3z@~XTyrnx_w~l{ogCYTG-A?jjaJFF zFWddzKJN726!{)j0}z^3dX(gV$gQA%1bMdB>uh*@d>>Ez9hRNg^C`s=t*>XH9LNpv zZ;A==Abp4^BXmGd?!^!{Q4k;AhzJz}u-nk4;v%R&6We)31*f|8Yx>jnjSL^fsy%e> zQe;fo!y3oCf9(H=AR~t0E@m0>FY4C%=UNC~>2?D5MZ1Uy}ty1E-)F*zxY>82Xa3 zT>9CklXT2lUgu6T0}#M^Vri6<1&=A9(T~4M8Fi>lLBX>hO7lq(y zS0#w2QrIBBKB^>w8U3q+@2|H^nmM08zP0hgh}q}Ye;pW`s6B)eUJE{jU&f(OrC_om zm|@7>&}7siXb7#oaiEl=D-9m&U=p&Ro*xFaoKz`4 z8t^0TqKT*J*TaDz`>bgI0qlr+F*{uDto*B(5)c*;fI@fFS0jCn-~bFzQ9}5FW8xMY zNtaw&hDU|%F~Zd%#zZ8>8gK{+VDHSKp3#Ah zdJV#Fmx8!u`W}nN4YVt-^_WieRn2CdHPEotXyK z(oL}|*^t1wvA6Pg5l{MRm-TB_@ZC(A7`9}7JUmrD9pQ&HoUFo9^@nIhHhm?KzUolg zgWuG#DxAh$dN!$<^dkLFzeSJdqwqX+)VJp*{v)`RN~W0hE6_Lf4T5FSIjZ)KHf(|I4?MVFQ%Y$^HRgIb^u6DMjL}U8^a=$I&20d z?&j5%bF_pAKq{2EIgymom|}E<+7*&L<*qM%8;hjdpFNn^J(-u}{*PvgKjW9r?fA<*WjHi)kTe$gltT0VDqup%Mb1m0f85MI)=El$Twsm+|UHs0DUY3QO zxa832SQjjz*VZ7cKx##Rog+63^rm@vI91<3mC*lHdC`b)2Af>-4;`N zji8n5$K<}cZdyOj@RF)|xVs&S%O67^ceUP58N|h{aW{}>GlNZB+v+oc1&}gyVK!2w z2jYPTQyi=%)keeZkr()sHyE7sw~-`nBhWQQ5EO>9=G*cSF~HDElQ z&`?o>>7(MX35XpMh0MsR!IiC{;2kBir}8U}z95p?(P0l$TAH+0RJjzX#$N+=kTtjKJ@scoTU11AEcxer3v z`$TCjm;cdTknnr5Es-zYb z3Q2WvWVn(87}!@AaC-z{D^ir|P}nF>Q_|sJ0WwgO^l#SM1$jYE#RN?u`6KDkz*2F6y+zqUy3vC;OXYFAAdw>Hm7DAy)(u^Bj(YF{o ztygkw_UWu7kP9pkVjqx))fVwM!iX4}Qp^AnWS~+N#x{9a8HZFu(r4(!tA(_Ks3D4()#T z{h#W+!VcaJgLYEqh*U$zEMfVMbK>~7EhXeUl9C`fpz*_DMLiI~k#5k=uSbA{qned@ zW~`1UK`g2zSJ}Gjb@6cJb67e(Vbc1^=LUnR9TZ9ss)|WCC{qzFfm{|Ev|#M2NJUag zE()870jowBTDK?>DM0C2gO@EEBtVeCQZND_WH5Y=R#^a0l~x%Waim2devAzB<75Ko zkn&qRai(jK*+7J77=Ph#<9Si`{~}OX?ZCpMVbKjtl|X7#Re;Q=VC7M(SVdaub{s!e zK1_Y-x?5Evm77%-3!@rUgSf)t8E~1m**YNv~{#dyTMD0MvGG&8O<2{S6qDy_v z$dBe)@v??zM%ziNSUgAV+~I1hb+~)>f3vV(Oq<3HR8>cfsPT-dq_?#U0-2E@+0@-E zy@&X}!ty)=6cH^E5|kwnND}{xXugg2u2RCHeeflI#HsFqOZYhj0zp+vqJrOy++?e| zlbHi!rB~s@wUvLT`rU=t_`@rd?h$Dou?|sZcznj~{OvPx`j&`yURE%AL>2r62QmX#W4ZuVx3W z2gl%}skRRKhZ4=cppJI9g*w=z^X61!PMkv|w2@p_);m^w>%1vo{mpc_;6JeiA5$7G@+zVEG43LGunqiBf2H_zJk8w(_6fJO zAa^hfo0?r1Y;9M2y1!^8O87_F{4D zaxHDuXqwa&IJvk_(!+kIZCt3Op@JqTpy%|dK=0zOUIz3rf!2bZXr5dP;*NQn44XZ@ zxNv#3N2?At)c`&`SMDR3oqh|nIrN*@s2m~A*rnR(&{vsH9QU_S>5g6MH|EXN4%7gm z0DYXjiwLh~Bh`YU8lVDyOYYDDpVozjI))P^g38zd7rmhhe8UJsLr}qLFmTSJofgCe zF~9Jdi)1WJ3`fe}r~r)U4tZfANuor2nCpNZ49{;I8tkeJ%r zxhVY^%3^?~Ile`YK|aI}fAcpsDDdQ>sf3iw+6p0RqiY2g_7m#D(1(m(zv1-p?>ak= zEvgBaI_C~f>*O~|A<&`F8$tgF=tS}8o`4=xNXSCU5_KW~ewco$id6S}pR>pC_Em7; zJe_?1;q5QQkEx2|sT2hrZVsl2XcS1w`c~64>Qn-+uf?xx%hAy__H%*K*vp=1{f#5$O$7@+#znGQ6+UtowieB3k>?7jCt;(uN5=TOJr zym2)?d(Jpa!F`Wra$7>%xy0#Vv= zQxf-df%;@;c&yY)xj_!9GSUK&)RM|*C_#WZ#S6MtU$pRFODh5pln6*bBtn1!hJ+Xr zpb~?wd7RWRSi3h(6w>0&pn;Wa-8s?(z!x;Dyug>mk=U_dAGxZA0;B~tTvfl1F=yf{ z<)*Tb3r0_u`EbHE)k7hvTH*04Uz&oP6F42!!JloaT!IN6Ixt}Z_hk3nVWZHYq+Q2t z@wWEiL(Qi~4x| zZU=nQA&eH<#G$q%icp{lt%^-EQc@+WIFKkYvR}g8Ql$+oG%X{@dggQ;MNl0k9h}kNtIn8bckpn}*waMo$8$9M|@wW*3xbK0~ch=wpNwRLWJT`M2=HbkN zRMA1yr6wv4L7^$?4J9E>S5cwoHz46PpuhgMI!2E@=jGV&MF+@10{|h3j|MeYW4bx% z|Cn^XIO<}8HHzzC{_6qDkkT-?IxurmsAClPuudq6ARAL$>2qn_<@lcWCF6_}>Ochd zfJ6o1h^cf$4)HoLdVR`EkA*}kq|GcXj+Kz~X1J>15FX(^RQv6p?_sMZ2jA~wmqNCy zDhOWqq4x_zTP}Ce4%;eMljop#9iRq)(m$iGT$2^Eg3N-c>>cQc(s#0n7vw`w&#l zYn_`GryY^j4(30T2sB(Tey`J)8`YHY3A`BHcIfR1Ne`IwmLsbl>9py9yrCDzE=A z6ZGJ4U@`y!3St4B3?Xw~rX$+TLT&a3>$IRP?p~=@PTAQW|fNQ_y*i&5(AklBmi4Lq`zvc(o6L6&>gz_94PrC=W56(Xq>383`6 z>VOT^mozR&KmT#RVD~xjdH2K`4}H!U#C6N)J(P95HqF_P*ckookA}RCYq8j38h(a3mdl&BLzYgEWuWFblCm%DZ%gR(! zBvbnkK*IzOJJ)6d2nS@3`~?s8zaj(|{^b6%#6p1N2M77@OFV$W)F6@fR`Lk2QUiZY z<)AJK#YOK90hJUXS=k57x&*xv?T!i>tyU-pbf5s78ms_TmB2V}%&Q>3|8;{{s18U_ zh%}Jpkrx(>1>{tt0YgBo6^D!Ed~l`E6wWJWfaP6OK%*#OL2uS^Y^K8pRszy*E*QwA z>dvCeFZ+xaH7^uQ+7qHHQ3)a}f$gI1erI1%BYlr{O zKq{4@<6GxQI4aPWIMUIHDx!tjTM~s?g(6u>I*{e|*)VD_oLZGpr5Tq73Ry^^E9m0I z^Eoq{QCjz^*6u9I)T*lD%>|)EygQ3bD>;~q&Ebw9)>KxPI2#b~HEP4lt?^-Wydx#b ziV+Wtx=BJDDw?{ivq7XRva3a0R$o<&&6@B8vZ6T8&$&>jp?*aSiU4T`LKh;2aK|x$ zSueP{=R}H#TG|x`8LK0r3lSY6X?8PY${K7|vomX@(&DXZ>Q7DIAAdQ_c-}kfd%I5S zuCo`25Xym!0b9|m6fhQ?NEZFB#<9@pwO}!T;mgm54XkCu_eiF9b#G zboBt(t8O<}SUJZano&n2g}0O*Uj)s{^<539aZ!yJ*QH>`OHzrAgPD{Jg{rDX_+&-N z3W(u$K1MS9ZAf5MO5{>Sb$G0W+1J^Pw)s74W|z?Gv0f3xFdANH8$UC9q9Ug$)~FyI zN|EAFkWJUDFg7${@#PwY@EDeax&#<=_@G^EarSYuPB>`_D)}z@$Z1_!rOUXr zUT+frGfAYFP@q_-+^w!Zy;Y{mY7Xn^Bg9)7?K6U1LMU9Zkx7%Fgwdcvl^n>M6#&d2 znnBbFq!7<`cKub5yESHo`68&MMFcDoVhDQ^W7lesN{FO@?SdEYCaypo;cm`aZJiiy zLp0Y_IwT0Hi3D?+BM}Yx>lbyICa9Gmxt(}v6MY$Xal!cT!%y3*%(v_W$X=sNBr!mv z2?iJd%)k>gBLKR2QF(Av!=AJbs}l%%a5)eK(&kbN#fmHRVZqFy6ccI!4>ACTq;BF; zXl3ywj!_RfRj3};0hJKN1sj>AJ9*!3Yq^LjU;YlrzM872)DO6pEJ_X~R64ceKdEmY z=9iAVT*ZxJHeD0eD_jKLNF2zLBZJ86%9pBO%K?f;0{n;|6WQx3XhLm?s#rDIWCR?3HBb3}C17mPFumZB z5aq#$MU+8e2l}E$CX#6O_o@@>pD?4ulVmk8Jp6Z?d*nj`fdH-Trsr$~r|OgxaR=~} zNkSnz82>?%1QH5_%>KvISlf!@QtHG)WI$Qq5XJxR6?dwF#0F}(7)TpTh(8;8hNB1q z)PLzuXX?zNQ9&Xrk!DXASRjkP(IfMIdWznabgMDh(+EHE#W|LYW5z&dc2zpGlS( z^nd3Az~j~e@9MigI=k#XJcAcrh1>kqhk)C!1vKN#NI{EJdCK6kds#RZ@8a#y zy|ydPmHGDO^_>aX&3wOn-Y|gIpp<}383F)rVk!kADy&JyQ%{5u-lqwL$XRb3xvt~D zJ6*lpZb(H@DI+4Qq5@w9!tEOyS29;R>JeA(|Ab`aZMFZbCCPc!`+w!N_@$^fwN{x` zc~>@~)Y?&LXukKUzT4{exRA_DK8tK-SVS}yD@_&OU2E~a=!Pf%YaZ9D*yG1aoV<3p zN`2O)aR*wa296!9TP$hlO_dZ+?Ve0+P+{@!gy?S5a^i3m+M9^6&y(Z)e>YcuAEE2M zKl}`jgR7^zxl`e@;9lxti$Hb)$3>6J1 zNN(Ueh#;fMh$2301b}lI1tblt<;Oh05|^N97&|BlFmg4L7dI!sSdCMHu}mx}AqT6g z3E62t0K%`#z9Sy}IT^^mqOs;Li;IP|m@H3oMLql|L=5ou-R{1LC0dF;-{^)5mg0~McQ*d7%E^$%hbty|d62{v^)V`Gl7T=aK5$}cM2LjP zWhnpz(m4@CNLHaj`sE~I1{l;J#>61U6=X~V02;!o1;Z^;87{cCYl5A3z%7z4dnu$l`&B7#+OR}kO`O96;SD3DN~guql{ zL`b%`AQA#y1VY?saKYV!*+^WOHMbU-T;WU|@WVA`4SHljiy3pIS%kt^e7+t!@4?T5 z``QylqGJ)UVEZr{3I(M=EhK=a&xj{9?{1xL!e4(%%t>Z4Yzi^7!$Q8&gw41*Pql5R z1riK`K*<8K=}iWNLqH1t7(>Qw*L^EI#GE1mMRm=K4e%C8sca<{vIK0lDEuSwRyVdn?`;9Ez9;}$qC^GAYZnYN` z`_8Ri-rcv?X@sB)R7iw?=`)yw$ueOP^b?hy>tnHRzqsO^t*spEhhtS91P+XL<`EBj z09t}F2=%%d>=;E8!-$U;0kt?GI+PZ2iI;}qI7dZ%fBCQ*kJp+{ra1?f%frFfxl!%_ zAVc{E0*nvV4E;9>7d34f$szA!gc5i0v&if=qnMEFpwTO8i#2QG~Vb!t-!2YXP+J-@pyJ!RtktS2%HQ69 z?_i@CZ5(>evPj7|s5d9Cjo>Gt6%76#&G=UjVhj4yV`o8CD9;;{o!I(%?l$55nE7!) z_%Ih#RSUiCbzqzD84QH+;EMQnP$sBb#`U^g8~FYO7K@ibvu!pm!xkTdv3)^l``$ME zS@)O^!uBc{`@Y>9X?o+i#%6-bYELzMoqV@Ht9@4=SwQ&V4;MWP*O;T``<~u|?@B)& z2Hp-yk`iT*v@yj4ofX=bV&>Z}0U(=bX?NMpgb5W_{BTwFQ8#mWAJ)T9jXRGzhX=vl z(PC^y{0x1p2){YY=N%h;7|e@?WgEj&)7R8l3st3VwNsqj%svZzc-BY&bhHt5Z{Yv0PmmDw zWhgojIwLREX8K>x|C!_JS+oF)rLI^|E!ZT~4AnJ)VDT%xs-=hhxm_Kf-g@YC8EPzd z<^l5%AP_F?)n9#I)A(OY>;E1bl%NB?+)VTj;G}1qwjGTPpY^AL?8I%V@qqhJvhqntr~j17c)I9J<`a!?!@8 zd0d}o-1jg(PsVHdplAKx@_!HO{!h>Uck5F1?r`0Do~|qZE3d(Em-#3Em;Iji_`6S6 zGJkULIUs~8H06i@AHY++&zKj9EIoQ5KB2Wx>NB~8z}T3AT|RCJRH z5hZ~kK&||d4uFv-0& zB>OT7euC%(K;}e86hVB*CBGrf%z=bONjXx2#|xc7BTCV9%I_ zQ_#lfdo6#v+e3`sd%NuWdU#*4`G4E>)geZQzLzT*| zzPj6Xxag_d5ji~BN(20H01y7Lf4BW@U)*F|Ld#NFNKrTjNH60yyqK?_t^+Kx2aaSu zLPM0LE>ubkvHd9;=E8tMm&yKQ{^#G?-2c1A`+q&8y&vfRum0fOeU;hhf4}T)zr}yr z@hduQ_WysA)VteT^ymHm)u)5PhFpHEJ)C~T1rB^c7xy5ldL3THl?v}s<3Viz5D%R^ zpC1|de+K*E=#bq9z4Pogoj4(aAtIpsAKCx!N9COj57+4aeS>{J3y8Q1Ku3@-_M93G zMuBMMiL!gH{`aWWQTa;sX#A?W6;KY-N`uab`g$y=puwPnI|1OunxSa^vO}amorQR=x#RxF$ z%aJX`l3kZSFn=T^s-dv6gd_Q0G5eiw->VfLg?HR8Qhy=hXl%E}=`KP!ko}V)7baJR zKt3G3)Cp=M2XdrV_Wy$=RDQEbX$a^8y|y<_pOVG;+_0Xpg%GD@2rxNjgG*lKa)cBw z_aLzr8+<(8t#oIeWqX~^?p9}OzTe{IY4%$eDYFp|xq_IYIdF2KhaLvBK*InYuWd>a zB0aPK^}d!~rQL=6Dywm-jKBT5cy9mupIW1Lm58|zc@YHLK|qi;B7vq3+~0|W4RFYP zic`*j{dggaMxs$rKah)%L#Mmow(I z%>J*d@BGO>`?mk5#jz-V^Y4Fu(9`Nkk@6>ghsSaDtFa&JJw)2+P+4RsfS6Q2nr?$5OwwFdH<=gmjCGDJ?{PyGc5ugn2dj${2_DG$%p^XRXptHEa$l`jfVs3 z8iWXwDanQSe;45TpWXEQ+@G_p^rrit-+TQZhIC!3vOf`vj&z+JHy>-}*KQ-01<^Vr z9-ILD$cy`NfAB;-c#5CLfQEPhQP_Z=s6i6@{hcY)zez1G__up`e81*)IN#g2!4}gp z3KjjjF81CzQ;{@N`8L7MXD#I04%G2XY742Z z)vYGdq0;!QF+fO^K$roNz%z^l|C!ZL_-FZ+eqSUyztFX}6gwZL@%xJ(qI92m-G#3h z*O5&WV+2U7gaVHz_rKfzEjn77L*8H)Xg}`Nmgtno(?+RX_LnZa$Qtm+hS!dO(mUo{ z#z;j2NEv|IxiaAB%w{sFlmjSwyzVTYvF<_VycBvUVBkwbxTw#Z{3q;1goai!3Q7tW zsr~=a?>6gNOYl~$ixpH<0~^%!3R)qpnwHw6 zD9gdRp!NgtFwf~zf<|_~{+W)Dgdz|?BqC&x7h%skK>`Xe5XgZckRPq4u?TlIMoFr5 z-|fW089lG-`T8_ShQ@zaQ+ubS5T2Mad;I_0iTsJPW5-A5oZH<7Ke1w)e1}LuS*VMY zpv@~mNXD!|Yx>LxqQd(ob>arnFYg1Qb`0AjT)3xEf9`t$o!FePdyMdV%=t`Buc@L2 zJ9WkK%WVr+W+6{rVYaW64F0N9{fj22v2%Mh*%4;l0NkjY@i~cI!`eDb+ws496ryl; z%rNpN``G^{SXgl~trwpS&Gnz*>?~I{PfuCyYTbN|H9Nl_{Q6jKH;Z;yrgYahw{NX% zh4_vhp7V{!+RP1%b<2*gu<~qyxq_K6QmMa&^bvgMiS^?8Azz_qk}j20C!ZSLkaz3{#ZNB?la00rQehQ1t_Lg_&qIF`7p zG|rmz6c8AdMU{vw!;7qKTxMt7pig0Bhel>UB@TmXcz)Cz6rkcj{@tIzOJ4eH$0nCM z&;B}u<{+49PIbvQmhe2fDptwjuSAxqBM53R2hK$78C1s*!3Wej_L5U1si4?kg+Zc~ zxDK8OqWLPT%q>@9p0mFRi{;#aEYEp9u0*E9ulIoTZy)NSXMEF@>&=V=fnj1=R~>uF z$Fs|^la!Sd;HlkP7^ z*GFBiUnuU$xShgkJeakuzt+;l_ePwZvth9E>gloCW>b-fuHN)iZK(j%c-HhqniBYq zpwxH%h&=DWkF4MLzxID!pL_o4uB9|%w^0)JPU8diF5|x^Hk=-gFpW+=CMmk>c@le| z3J<*klxQEl3CR|eeV=Q`m+0*hgdhSy@>T&o;5{xr=2jVyNDuZlKU$j50TD)Cf19(; z=#U`4P8?6?9)bEhK|E+3noDM?8G4wZKJQ_Mh6LvOf2o8gkZunNbE`YiH)pt$uoGU zshRz5*NrDigX`aC#-yuhQAusszu_*$ZcwD{Pq(f}r??FK;to?0L+pZ-0D|z5Dnf^fg~CL29*+l5usL+DjKb_xp_Lx3BI$sN}0H|e9d&;(-?Js%OKf57b zy|%AYk*lrS_qUoH{M$_eD&#rPR0op;v-M!}>>*kdpsxNLBdzSMWn3H|uXV~dMR!}& zXhzioNQEE`D9{B66st&$3Q&b01t5_N#d^S$0TPG$F|YgR6ZfP4EB$PqatDZ@prg4L zmmwMo4?O*z9Y-oDK0lB67sGacHs6of#6B{;U5Mt2$XASd_rC^M7>aU;!vs6_-%w*gZ4MF=nT zqK7sp8)3v!C|@G-C{Qv`At)dFaG6M9g+5GOQYha94Sqfy3$O+Xs3fR})Bpibr9~SR zMlhfw2T)Z}yHM@`tACST;Gy?d{hEuEyn(xgFY+f+f2--{*O)K<2mlokO`E9%hrpvq zVG=`af{wBRwRJs&4G5Y4=j3%Wv~`o1%)ShPp`u&`^cgN8@%c%Fv6YucA4I(lH`1jO zRS}Rv1|J9#1jG99^Xp^-q=7im!<|Zt&zV1-i+LAjmlZw6RcSy2p#i8GGxgwqJX8G; z&4_{6{CwxrIi2K}hx@+Q{rbP&!#?ys)An->oFDo?|HuB0AJ-QcPz{JS9Gtid2$+Q{ zmA3vx!c`o=UKhY&I-`FgckiZ<8cMG+=Z5ts@ zd<;WDtN1gfhYbU^K4d}PCWHe~j-fX8FNv8S^6LQq&9WQYN@KEUzS7z=p(Nc9X6HVlx-zk*3x`h#h zJ@ea*&ira2ZN9SC!xS#mb%Taalz*L4KC!G*sdH)o2CsnE5VoSPrz9jN7mscV6&3sm z&4>Cq^!3m%_oGGxkSRx=8{k)DfPAGs@4S9UdHF}+Z;haxm?$GjVVMeHlE;s6i=QQn zV4tr92iey`oIGzwnc==K>|Xc%c%A`lgR7Ed?gImi7!DE)8{hS^W~tyj5}!!)VE#wM z6bOYOt`KY)O18NPEduDNtf!E9_YG=*mBkNw;9!^`Od=r&kFNDZfgmAhP^_r~LB(ZD zi9lAx2laeTw$sz@{xb&Iefu|Npy!%x6aQvoYS=c{m9dd>2f)ga?bA9f0sM_KdKyEC z^PG&x6HHK9<(_vq<4ipW+T?d56se;|BmwFg7p9W`8K>u?HUH~exDQ~w5McX>aNH@1 z4MbcQs$;pKtM~=&_wK40LErxlQm($AD^!Dmqk`50p=jzIXsiugp=6b#Z)Qi=s1yo1|_Ic2vIE)0 z72`lfPN^TuhejrZ7ysV+)8{mK5r|jfly5^F$)W$9fj5k*KO3B%8R6SG4(>v%sNf``C>22#RDi_*fC;3QmQaKKKvIP+KfxP6 z;9FjT==H`LF0AaA1{lPp&}7sr{m6$OFrgxQ6oMcBIOg|RabJZoWd4W`FZGy#5hwlw zDW;sb7DhatNR+)~swxZxz?D_23siJu20#?bU?D(KNt;vC8B-uPFg|zj(g$o1Hq6H9 z_w_^w?{=O}Kl^lAR{%wniGnK&&5RZJ$;8J8bl5-H;NsDUIzkeP7fzT>0mX5}{qD!~ z_6=^%#PxTxScrzO!s}`^DXUgP9lZR*F7yH33NVfZ4vfv3 zcyx(w#%SQnL-Qtcku!Gx3+rTca?Mb(fvKJW4o#pqnXUw`XlyEmO1D)l?fA=kF7pfWh>;dP_sY>W>pxWpllYURzSy|#=H`Hf9 z>}nzZl*5ds+{yWTiSpc^FQo}>_Egh5{TAt_{(@?M|IA){Sq4d|r>1O=N3dE6N+7_%aV36%cq$-wssFjwp(tQ%67UZvil5ekgX8>pUHm(`yq+7H`gOSw zJJDXW6V*TD84&*5Ni8aZH(HRVR8?hHLExhEIId9H_qz27mSPNd1E9gOgAD9 zVh}%kf*=7vG19a~L4v!}`nml74OfvqdvUnP4=pAAi@)%xcMGEKbnmNOyZfDQ&-L*A zj1e?NhgNs}TKpc}cH`BxCo3&AYP6DUL4EWPC12G@x8m4K^|z87o0FDq&M}CTn@D6B zlx2qFX(Aizsg}K0OK0`llIlodEZYxUoE_9>cS) z1Q#v>{>1*YSI_FwKKJ+N*`N9SkBPYdW#$N3hCbPlaB(K-(oi4(fo_fm2fREOpRR`; z4v!Rzgg=v-%aT}hyDL#63Q-Z_0A!b#bP8aK%DDeXeHI@GadG@ihGI~FGk&Bheq9!c z2tmbP%Tfy#9&;HGVg&#sE4-T`HJM8y8G|-lU8+YloY(+n42TMUX#Z2{9T`1ciA;iQQ-CBQCW)u} zb^aUCWItWM@BJ(b{G!49H;~EzH|46>D*J5%A*!<&r9fiOnt9k|*~y3Z$eQBc<6+1C z4BG$yX4|**^{)BLJ4?_P>6A+Z*65aWpnf6o@JkNWfc)?`k@_FBkNR(zd1#lBC>{zY zW=O(#UM~Ug9yoJ{besdI>%(Gg(YPJm3Byt2oZsE`*IrkUaB zVhcx; zEzg2mciE@CKs@rJ`(p?M{5UUw5$W8p>x03Y;eqhKs)E zyHE`PHrqgyh%hm92Gp?4p#-j{VYG$9dLMQ+pfy+??+VhC-yA>j4OLdHPouQ%#KIq}@_)bWVMumRj0l{5f|T(W^CG^7-u$U_Py8H^ z|1a2_4F(4~%u%Ymu2`_fhLGD38?77x#nZ|9zdPdgI--1DwJJYO36=l{dJBZ`A=`?) z&!5b&S_vQzOJu{DkgLwV7-cmK7Y2E1F$KX`A(moW{+w_x1P8-!jeOfv_%dqzEf*Kx zJ)1jwAH@1s3IFr|b^uF@t`4~@;5pPLT zpTPh0f7`yy`}Bw z*>OmI<*XPCRfV5BCs0TZDJz2DKh!ROWA0Ux>sFPiXR-+hmO-v}N@xmLTwn#jxbjs6 z-O(@)YXJx#d$9u17{oz%AZaq;#&+9E+SRR;DkyTdR&;O)5~!lU`qudW=Z%KIS0P|2 zs{d{;UIx}w9k>Qyay)E`hX~r&BBm2*HK4(QAOPevB9V{Qmc>QlXDcl|mUep!lK4$JqTg-4GGR`ie?oY?r zcQ0Sd;0F(`y)y$71)xI=%|Imx|F#NR{?>tphxl7)!2JiKM~4(z!K}yX8p+^_CMW;% z|CjvwV=v`=IOAQdPxSm6e17NaTaPrdzvchEh2p@!e?><|BOtEsGChP=CL129O4*Ne(wHT@xFEc>a~eK)c|kCW%jNfKAjFo zo^(&S4mbgb!$yzVpzosYBID@RD5>+mN|P@_j{Ep&3rRoUj|OWIy@DqsN+6*ksh$8g zTjTtT|6=R#3WKWv*^2As(R?(bxg*4hbb*SbCoep1E`*g>9^%Snd@4;ba513CbzeWX zfVW391jM{V)POB<=>1MsblJJ$%u~jI)Om0RubMxXNIa+geO6)#MhI|q*bva3_!|ap`@QXGy{BJAP(P~1bYtm3TL0?9Gvx}Jf4I5tc zL2i%e%uq9DgF6kJs|N~qzq?DNsaBhKH%Cjvr6x<9M#L0?9V8%#5Sd5&>1tS@7avRh zNaB18C@Qgp0eZT!8E~Uh``@^xG^+o`=FOqqnM`)vN0Go&HTsQnAc*z6+q$v_fAtU_LB)SxbORDoMuFnUuEK93Ot~&U3 z#BUL!vY@f}m?-sTR|>G@Q_)PRnHdUz^lQ7xhquMt#7rXMlw*ksaYU0Y>qNx|1@x=+ zVma;R>-^Le>0MJ$Ls9*gW>PK1D=c%dT==I9IEH1IB}^!msCoS z0uV_c4cNU9W4Z+Jd2`1cTQMR62ais3-_fA-!Gr*y5>Q$ZKCkKYfS%vW-s-7-Gzfn|rIXJ&1sW5df+p5CkIZU=;wFFbvR( zi@SajK_WQ@xU*@-iVKTN^m!aYPb0qGlc|8l3ik7bchSit{9j|Czp;_mjI5TGDXON5 zGsSErt!ta_Y?D0{k}zca_hLt5aJc=~-!-y;q$(97EKqJC18z_ss0QRvq=864!I6~} zM5V?ufIEQWKDmU(M#T$92vI^Y1yBkQ<=iTRXl>jz&ae=P6<|mxK>(vwgN^cFb=Ye( zm7vD0VRZ@sASVAKx38zigNR6ZoZe;NFOqy$Ac)J~$&Or8La7x90RS;WYiK{){|#~) z4ftitAPKsKxW-&MEG#}XrC9%?_Cl@RoT{CWE z)HaOcJ9QU7CU&Wv0Fa^;L@KHyd-xs>xy-_W2U3b*$UDa4LUBVoWq9yYh@9NO8sKQj zvF_1no$0&*@XyGJ^eb`^tfj;~P^^y;ff@I!$b5JM^`>|Zs6GTLS^%BM2paubL$ru)XP>iLyM15sPuuLz@@=r& zKLu;s_HXu9p}u4?5>%j*F!x)U;9;$svRf7`R6+=Nzr9)=?^eqAv-@xXI9eIjMd8Ar zKH3h9bFfzq4_UsfJs#S|<|ha5smTEM(R27!=wPqL59Zu4ta8Qx84z_@~M zeTR|upi>h}bvxajo-5}voq^ss#y`-2j8`a$L6omGC;$2@mB9iWDTVj?`B8qK)%L;v z^mQSfQX~)s0F(~j*|kU3L20Fus6NHRYc62Q85br)7Z5QM5N(t%F+lzgh6Ymq>;HSf z5dCkP;eK5|(AuK26we?Qh7cmEP-6vPF=L-@5DQ958raxHRJJM`aHNqoA8JyydQyrS z1Btybzz_U(gFmT!FnG^8iB*bZXypV_DpHsVR-izVh(sg@im0Uiv{Rk;o@N&n`Bb>W zxzXwMZrOk*DhPckJ!k=5oBM@=;@5p!juhd`@JWNmF1N(1?XzC$;qp9$U%~6My*4~3 zgb2)8MO@hbpb3iuYyD(PSs4NVXE0^Bu^}NqlbM&}Yi5x-Ph^RfouIYcdTIR%i<57h zsR?WW#{4ou%V)YWUoXQ}T+=2S{;z3+Lp$1aw>V*ih=$BmH6>9T+V;DsLP#LqYKUJC^3`FEofYvjJ*|*Y<-yj6?d507OzJByO8b^Smy2~{ zY|GFxD{fDfh*epN7%x&MzCsYsYiP`anZSS+{cRRMIy~L~+P$k>YqbiU9_@E~t5V>z zKOTNRyy@7#gRk+wN))eqYq#KjCTuT*cobdyTaxRt4pvEy7$TRp3|p>M$Lkp=#K^bP zk)>`d);X&s`Ku@r*8(8sp!x`?OZrd$d8$bQ{}=yH@_1|h5B_z3_WT!tRm@OFiwpVY z5|Zq2J*2!cF7DPBAYAhV|1ajy9<(0^qfKV=Yn#7Ea}KQgxeM}juR+TPf-km;eOs{b zr({1)^T)?W)Wpdy1tLQYS!`d*SHP_b<9$UKIBUg7C_oS!+q)p*i)>ag1Pi8C83`D~ z>C!mw%fFrYIepf(Xk^dTH3=af?U@CO(2CzT{VJu1e|+Q>{){{jV?0Yg9>rr|BDLQ! zUY~%5&!Uyd?VJA?4?x&h*n`+VgX+~UeB@So8MFI0yK^%t-B1bsJee%6O#SbBdt?Xn zW-!XFeidiKwaqp3*l4(MRno$w$cygycMoiTgOG&W78(RbzlX?bh? zPv!dTzIf4=d_ES@p>feYOfLUTBlodQF>=IY6;Bc(njk=j0gweF1fv73_77OF|IK3< z8Zw;^fa^V+)eeh#Zyfh;eu4+>q(PST=9(Bo*?|_(lTVN1B}+7; z)9U9MPPJ$*Z_l|KVJq9WSk;n4R8ZsF`1ow^=h2!bQY?}rjD=N55)6UI3eU5t?Sz9Q z4T(@;2^heYPTo#61V22X->1LS;34q(uU}yuV-ZC%f01a=XlZDoD2_ox*VFJfdG+|M z8(=mlP#`Lm8#>GLdQ^gXZImmB2qX+Ifez{x6Paruwdtb9PHb<4lVC-N$cTY_h4}k_ z!NCe8s&I!nig&ZBb11tz`Y@ume9s|oB zv5SYt^Lu&qA?<_@sgq%z=_uHVN8*Wczd|VGuVvwC#Tm-*@-(+NW)3 zT7eUch=)a~pM3~`4KUt2_8gs41ghMo`7awWeed%=jHEE?oldEV$1)HpRA^b6gRr6h zYT00Fh#dwVW_TG6|otv>skARxC?!IDP)j;!yzn}E*dFS4i$eU zDM(5P}aRQa`j6Kf!oP6HBcH3Ial@TD~-Kff<&NKNCr>_hO6LHjR4fuxC5zIs+F$lyss2MN^`5I~e26aY-oqb5z0 zjWPmu(r8G2R@X?o~SJd<~fId&*Jp`&ts{1)>>yh8j61$v+3`!PDBG2b}B1=tHySK!gvk*Iep& z`0{-Dhp$|9@@&Ub=?ESOPQ9L;o%o=W=Ddzx0rm+2#OJ9UcjwO+i7K<#pEc7SAoTAm z(uDZDWikdPl8za++RH5(ker$l=@`h)d!0>&vA&mC&g8pFW<@gPwh>x)WV< zo_?GY+wA8blx+b*eCWc2PM=0kK|*vy(xIWlKrSWlQO1)%OIW)l)`LaWgmUBs0oAth z@CG^lhVgZJA?2SG(Et!sw5oX*E>azK_CrbEeT7t%zUb9>k-hv5Cyy)3b=QrEJ)3(m z)nWuO!t7w<2O)c4m=~0;7zU_ejTzx+_Z!l@xSWdF)Sf4S+1EH*)17kbR7|i~P>FzJjhhchXp>X<_-1NMFlM8?V$q_a!@4Zg7-Hb5t{X1;W78_eT z_F90U!3B^&i4Yygu7XH{_c$oewU;A_8s*bJ>pXydk@n77kOj$+K{5b6FayQ?iV6~A zpU`0&CO%Q+bscg$ocy=~X#q~Fq&otD+=W7Hs%li>`fK+gbl|J)k}bM_mqGFF^zBTC zmpZWY<=Wp|-|1Zg*!c(CS zRo@nk8!anA*)noN1OBT2_jM4v?3qbaRU8_`dx%5cB2q&D!A7?h@}?~y-d;PiXs7Pj z0E_sf#t9-BvF8-{htBZ*ujljd-TnNY1Ye;jS^`=apd|s?AxMKVOfZ_gSIS;qwbBp-Jj{|68Kc^&P%PKK;Y zJgI19n-lyH*oSXofXn^93l9nKl{#jgF^%rc^%}HU}BkWzY(PdGF3-v#V&n}QKmFdxE?F8wT;2hGaQUk z@uElO+}KXZ;B}d8q##Y?B*O5Km0~~l)!xRhm#>x`7TCjt*2*|AQ?X+!C~9Qx=Nx_N z27-1TBF2RUNKgc=Sp`ovoeBFBv7HPCg`KQ-Skh62*_YTo#r1<`miga9u#0bjLxx%p z`1{lc(vI%`KHhxTNalmb55bn(WT#*mrJYHiMUJG{eQ6-c&Z-$ZN52s__)B5%DmFMM6r2sNGi2qSxoXix|;ab6LLH_a#e& ze$-V<0%8OMpx2?a*twplY znjwa30d8Hatz~J^sOP#Ed1%9m<7>3xPwrV`;u4MI3DxlOwphlbT{Fb|hT8WxIno=%TJH(`B7 zX_II*T^L~Qv5Pe{cNfX70D?*yxm4xGT{z%qhFIl;*xjHngR>a`z7E8w3J{zgZXhE9 zj-ye!SUD~pU(g_2`V5_n*`@7M{U2xA`wkB%b zrA#*?3u^S>N)4@p2d&dcPP2|ou%&?GP_$Z<1+-f}va(v2V^ypMi0O%F?Ojcv8Deh3 zY6mJJMhYlE&nRUijtquDhXIZy=_x!)iH52ySfL_DB{S_6sA4v*!x$>6r%M9iK2Vf>@jJE`ehBbh;Nf;ZB$d zATb1~>L52ZBr?Mw&GwFzr3RgSDM1svi>HV-VsNq18-d4Yq>^1hC|3-Q5CV{cA|lxa zF}Y4L11v0pojKYyKB==JHu|OylnY{p)kuhukvUXnh?LZ#XsbsCO0_s(bYoT!D7W9q z7(p9adU+&B>vZUVO+zwERo2SeReT1B3ZWr}MlDpwc90n9IYjrE(Yp6%J0RFF2S&`Z zO({`I@v#o@jy$+BmqvAm;!Nt_wH;*`pp+bJct~hL8=QijtRlZ7`N%IE>$A-U_Yntf zy^ox-0)rncjC~K%Z$RPQ>o=FP^kLws`jS5DtFPTvAdp}m`pr|QBProZ!_=)MP-^Sb zo*k%B{)#!I4k#Njdh_i>&F9wSI3ij;bVv1O6HM1F3_Y(Guh{306vl+5kYP9K?9|ak zUvuo^ufxCL!Qo(&_cl9?EfF4ixt}OMFWBpy__y5sebik?|Ewy1T?&7i2-z6$pl>-4{M&##NM3blFBxqPMYMNx zH^WnRMWdxZo9R}z&O0uL60vr)rdA3j@`qX^hrfvKarPzqyUXPI)F$V_g7hf#ybb+_ z97a^CZYS1eZM^?6>Avh42qYg?>_jolfFZM4_>kuKWEj8(`ZNdaTFi{dUCxe(MTpQ8 z+v={FETlnBP%~YpVPXRihvQU$P<;4ZZ=bc|8!q)k%%B-%xv}8TaKC1n@K%ILIGK`| zkb#v^G1)-d$!WdfR1^dv;xJ_5+18_krx-N&4dZYDbJ;?O(4c(c9EK-m@W7kn{p~9p zyNC$YzKCgf%|?~clK@dbuD^_+-HhR~T$xd&Q?t-e zxX5oG@PCOu|49my0KumM3|PiBA@xjsZvqD!n4R7<8;08ERNJId&)>&AH$WVKuDQVc zKHmA~`rcea%d8#V_g|S@#x^%Nlqg8!UjdY-;3^FXW@1_seQ0JtVz~$Y<)-Jp`L;by z&PyPD$O)*5nxcD9s44KE2g!i^xCn7YWclB<>s!O+l+Nz`SUhRW$Kq=5vB{15)Q^L8 z4qP*#AOZZ5UPRTZU&Dxx`~QfM{(*r(K@ax9ftQsOQ3b%<+%d&Ds#pC>r<(^@xF-{q zz`RA_0l+wwjYc8ig8>^IbB9Mp=L3U^=OcmzJj;$tM>dNYOu*pEDO_ZR0sx9af+)YU z#-u0kUwdmpA%!B1AT$7+jwJ$L2>|Tj#*6ttFfGi82c`J&=`e!bL667F60Z8Y;LA5rBNIxb%WKWR`qmTMF zZM6f=-r}$U&*q0e@xS`e2S-BepN8iMG-xkRM$aj2C$SCS#X4haDpAXWoq4m?anpe?bP=MFI({(Nxe!Gfr99dQ90Y6D0W z68j`MCs=eHa$O01T=r9vtKB8Z5prUogbsIg!pgB3!R z0HEQ4%hj)zo!T1fPSmzgyAgMAe;YUNJ@;q#pUwZr)VTV--}ojFulI?Q7E(g;ng~=Q zR8#ceT4#2D_1k&;8~?ADnqtc>25FM2re&q9!!(h>5+PBHLWcw}38-r$mieZBn4B>$QUg<{&Pyryw3@xJ+ad7~{ui#|1S0j`{z!zmBn9qBeM)@0% zOdcXZ-;4V`)5DzM0xP>)i;;#PzM&QdAV9)mUGX-wu0w6C5RrKl4x?CdOzjB8mNj8U zlB^)uKsdpNa<|TeZvN*7`0+j8f2V5jO2wVuf8r)*JNoy$%qtxPBu0uZ`}lI@$YLFS zt|P~RgPcfQsMemP7_d-sqP&l*KwxgpH4sG@{ z2xv#&Lr2Q3Su9&wS5PKzN_}0zAw<^pUA6_l4STlGP3I|aFC0_$7vx#p}YG`Xwg^)44{$Dp(RoM z?{`K}T6?aa(qQ|%NJ68brO6@ugZ-@@!^z6l*i}d^vjg`Sn&Wn>tm-rUC;GLdo`s%l zMa$u5{s%HCL5dHpgp|`EynS=1%-6p!^)4rG0C*i+$>%T~IyWdyqkXr(&q;O%i2D5T$SFc`mMM^sWTUWTtF_bU7_x4D2|3xzH0LTr-on+`_kVE!vWzw$7(gdT zK@3=k%`z89*FA#-=0z0`vk(C6;G%jFP;%#W6?{3dcZVIE7m6rfKgo6PxcgVwiwLXU zZ-3kQ_ZQ4$HKfTwMF_$KzECoQrz#x#GHCYCzW;|eJ!H6N& zE`huIbQff;zudVI@;W;5+$cTt_qXLOn`-6l@m}wg^bH-eK(7CAQNX|$q;5L@IlV@J z(}px6A_QNlJhc83#1vbJc^T8@W4%Y{theuBvHP@{sBOBqaO}|=;b2{G(P!>z+qYLd z5CDJ>41fSjg`App%)z@ywBhVX6uQ;Evf*{9Xjk)Zp0La#L?y5oZas#EUHi-Q^(p>% z-|*?whZp>H^Cq8TUgkdPT{;jyw-4;&>mORS(Lp6MFi_H(F*|04w#%5HllC&dZ=;|B z4~O}@P+#)ki~7+73`zSHiIyCYes+vg+J;$-Khe$N=UwzVUh>cA&wC$xt^M6?OV51) z|1=Hc-9vK&2HsGI;Y9^CV2F?T=Wi4Zv^|8%h-Jh5F@5LqQa-dt1QpSn#fqa)-FTh7 zzv~(K)bpX?Kpua0LOBy8i7A2)`pyBWNTP^}sz`{cVydDjlB#L~B?>5tVkn4-q9}@@ zhzN>mDkPY%;&gkW-dpGBdnoY^1~g~-e_Q%#SZW}1f-yo!EAJRtC9j}`FX>vbML|p< zYktn3{#*>amq~0GsA}+j758Y#_Ln8wxh*XV9*rtQ^9#gT51zDXjHQs*D6odZ057O& znDA_4wf1HT6~A5T65f=~4>9Z?#PiQb>3oG*3a5f@BuGr&yJ?oQ+8jd)kt1=HMtiuu zwYN62o;y<03Q7lgY^ybEdTmS1b6y~3Duc4&rT6FX{fpK&&OyX{B_ZOB7z)k_Ni8`6 zK3l}z&qZVH#a=jQz2G9tXy;{-nu|%yD!NS%w=-Pt|&bpS4GWw8HE@u*OD8bkH3dA#P-?8B#1lN7^eMbZy2@X{?Rxo0KH`O+z* zI7$&r8N@VZEA-5|ykrmvhuebT7fkuEL+RbEIDVGa8$Dma{0~s^`q+lt6M_TkO~$46 z1MQ*-ucgr9WYOH^0rjF;fF{&NLF~8pWn50u)(++gp%bmcm0{^s6P-wNBW}khD&pwn z#82P+-_NiKw)}(Jec?@4&kIhmIbOOuZ=*%G$HgNHarCp{fi5m7-w zj=cB>2u0!Oo)bafIyL1TNm36CbY4;?6U#?^@C(O$1n%*8=IrmJ=U4-UY;geb6>OL< zOOosa#e}1=gMh@i&ESFy!%d*rguTk8aD0guA=GSVC39Cqs}}N|2jE;Z+Ui}Qt{?F%~8bf z`kf#0y92)%@OPi@XGC|K?R>RSK|}~cVyft0eSc~-dqa2Y{eP>H{g`BLBW|~!0_z$^ zcp9+@YjSf{@e`G*)1jEANusENsw$FZy2+AZG#H2??sUr%S2m)Lj&~zPqcl=hj6o$t zUFQ_k#A`nsHg0Kc03Z?xmkP}FONFy&9Gtaru zayh&X6hnstC=8j%3^l{IqF$UzKxB=uV0x@=g61NQCN#B?)49%a3v74J98gznMhh6h zbAgM2RZiK~jMm1Qi%lkBr2ztj0~W0oEf%WF7&B_t+ZJ)K#x-rWi8YE#V%i|6+cwl{ z)Mrtwaxu$Mwlo`R#Z5XhuwbG#(@!HoR1xp9Gti(R+(7`O0YGiSM?j)V1EZ*Pig6^; z3WW$zh^q+5lguH41O@}VnpL6f1P-wW_P@nI6DsA$qOEi~udU^nJIBmBhk-Tj&4eSbw_&!6aX}>K<<&>VJR4#QS`l zfi2%T3(aT3HPO&bQ z+0xdEr?`-KbNP`r0qwdf? zcRStZk1Kyfc}3CvZ9nPM)YH)au?-y@@uQwU>Hcy4fBol=CqMU#*u?m8bN)&{Sov9s zNFEc>%0^F+p0a^wldC^w0rww&LD|9k$gW-<_q({~K)OG10W)ydfI!HFWOejlfc>+UZb8?Ei^P48>L^ENz}{g=f?giWSA3y&+R-KYDj z@#0&r_N4U;*sq_c^C6eJ%C=`R4qn>*m%m5#nL~gL{jq&7Z zp5d5mIUOD+i22?BcjgbKT0i9)MWf2m_s6z9zq!^gs#(7ue5iQdE8g@ESJqOLY3O!D3|96j z-T~l)kK?<=2$bhe`%BA>rmGj^*Z^t6L_SAP&k5A^ymLQG1xw9T)>p0~8ZRasDMKA$xuoFW*hikGWX?;0Yp*$#Jjb=-qX4ZALY2U z*lYpuqTrB`)|qw;-|*dK-;yUc<=th6B&Pgqe3|!e0{B{}Q>O$vV8v{qUgQ#&c-xtn z72*AbS(Nyd=bbMEg)#p0paO^N;bMr0|1>!WdUeHR3WLKR^S~TpFYj;CW{PXZh0_E! zVlH^7Nocr4b-*$}9xXJUth=1`3Fx<$#ze>w0K7iBvf1b>C#lpP?@@q=fR<5l`Rhg` zw=0L8lJCC5gNvWLcJ^y$c~D0936d$@frLldieO{`39^u5^*+D#azC?o{=L8K`i=n< zS4e@CF-t6jijjD5R+TR9LuJZ7&lPNJU0`*@Z_8^c9LNZHQ3GZp{pg2{_h}F2PniHu z%Yufeiy%9^6bfqI=LJzHN}q|uJdU94eAFZP{uAybl5iFXMu+@Z64PDjA@=$Hpw{Z(;cGa@2>SqR{tDY;}MK?%}Z6-6SrZslrj?A?ws+74Bddjh`$VzO$YhzBY-L=JDM zLZ$Fbu{IzExW8jfdN2))X^=qu*d`G~_dm<&PlG#3SW*8YLElXS-9DL;=45otBCJ2~ ztJIeQYlWw_tdk1KIdHD?A^KPSD`R0QEQ!+Nt3VSm5FXS|CITZ|b?H$Z$vZFVI&;f} z9bd*n7KroKL0sM&hLpB3@!M2XB4Z|lw=%W4egC|dA-cd)YQ-_fZvi{s7K02A_s%&V@SqR9XT7AdlGphx&g$;Qn84T4sNx)?7`-e|B91 zHjp?le7K!H(!AK8tpRmnp^_-)n;mkJ*Yd1h@JiHkyHS+02q$QP1$`zwB|c zu19SXPH9OCx)r0^)@0O=QXRy1mo=v=m1kz{LrRl*nM6Reegr^XE=7uog^H-lFCR5B z9LHJ0RWb5YWd$f11lph>C=Lj@+SHsFZr}mFn4<2YoEQiDvgpgf*zk34-OY*j3~bXa ze#Y;4)N)K}rn`Mt@%-t(Ka2eAo6CAtFZ0i8lAz-%|CPaW_Hc0G!Ohub*@bLEz5S_c zNA92g`TE-zn0~*ZL-(zO4h#B(hu;t0M`qwVp zP;r5-4(tQPgTIAc;M2)A4xdY3k5^yADrpFKoK%-V^`aFWn8=!<9Zo7&>p?Gwr+PaO z5BKMPuM7C^wC87)3CA7ix8q#c^y9~xuz`e^v{QrCUpC9o# zTia#EJTdM-&~5m-{1LRq)pG=QyqIp^=iRA{d)=)WW1;fz`zsDWBp@XO^B^a-g8tN0 zbj&)s3w1oY)dPNaFsF7s*ks+ck2~#Q^6n6G#a^G`cPyP)1GNNt5ZH-mbG9gJc8_1> zQ11Fwu`+m7v9~}k(STjAbH&-OX9U`_S49J}6$d31W=f6nF7m1P|M`If0$j(CZPKC1 z(OeM%W$Z$LDLLfp*7Vl`0*cHjg#>NJz=%;40(!j}mhNSvw;D z0uWI9o14&n`SSkFsOUaX0}=-SBZ&JX0nA~cn|WNp3_{anHC%w0#5F|&#)I)P(s0bN zf4TTnrSU@VN`N4)L;yH|of2HRuh4KCWDVIcRnxIU9zHC926^pA=o`BaU1W7)1@J-C zk2)@gmx9QeQ9sy5q6V#dTlIG*hpNCHtY2{hekGt9V!u`oLv-HX!+KM-(ioyN9hFFP zKRc!3fSweWO^2*|=ct6pxdx#?8Nv)*2a$uipp9vKED%m$QUj2&MOHxv>#zNG`i$D@ z?e{WwFa`VQNl^G+`h$uhc<=z?-=q!KZ@V*CsV8Y3R0IgQ8j%Lxjn`-9lor~)1-lb` zBM?OYg-ApMOqI}$jVcr9$H9nb&Ac^YTDdkhpkEjEV`+i|?SaHJ5hu|GiW&-j#Ag4G z3RDBV1H;+=pH}7UkThrHvNng(!k)YXDY3C{h0d%h{6<}+yQMHh@A7|UGJ?K=!m{~j zf9CHxZ!$jx?1=7YIeYQUJR%`p@F>B>bd3x{1d-6lufi60@&va%%cZMm?hEQC;^ zo<9S&nFskQB3+uhkUZt_U{v9MKk0sJ5a{mIt3DibedZXg^WQ38{{CCWHgOSY%+g=- zZ+m~Gkn^DR*N6QbMPnC7a@m(?a|6iQ&I_l=> zzj(QOyPW#$|AI}l{uYP(?_+shZ9z-((VbgMFh(&N3K&tSesw?D(}y zJdQgaYp)$Y70n!n*grPf`h91o_J8_?5!LP2{PdhGwd{tzaq_=amx1TaxE_Vq9d7i; zK64rW+kYGWG5_-<4-MNtSp(yL7Aia!3xEFoTCRTvvjBYW?fTI4pWc6h=IV#u%mu;D z27^QobF{Eg9}4PO^yo3hORB+7bK3Ykua|&iGEWM_R;r1=(LcH2Mz+|Ph?at2C=Yat zsz5XmG#s96>tMvpYb_K~Ql})K(wM0t(jT7uU+#aO{}0o@Bi5gfZA`n34~^*UUB`;& z=#>nqKpy|F%`GIK!x6u+^X0a-s~O6~t4f%%b37`@I15^EHhxv@l&Q zV8s#s8Q{QCe_!w@RPiCB=6}ceydA%N<9dTD(BmwBkLGv>EyzdcFLW@GtNXs6g+J2^ zJ0n6s!2dcw8RL;ZxJUKb$&;1HoBtI;Be#? zxLJ+R%9GWp51)%ay8JzJnU|M|ljUDqGx$G#j&wEjLy=~KPJ>(nPMP)WTE%ge`rppn z>vz9T9|z<7FT&BJ($pOY&_wGSIT0;ihSnkkR_=qthd z9wPK*fBo_NpNGGE?(5m|-18_NJp>pT2Hqi!RBoh){R{+|7I}j)<-w8}tM_GUNimGKjlY}_&D+0Ij}0!ivLb1 zEv9#ujnXb#?4>dQ@cR3PpDOIW);Tp&hpb!gEL=kE%1JkHzTeCFczHM+4!*$HgMpik z)aH7A2XgoS(RVZso`w886np<)#r)NyiPK1bkjDWi1$@Xt5#vRHO$3QhJ#W+dYj2{C zxjr0DPv=w2fwc#V5=Z<{I{sZDj#{hbz^F+hS(GY&UJ1EV`fNZ=D#MRcOexl^kI(#i8GMfa z+0av&t-W?W%~c1+0GM{B00IesL^KQ#A}rc5kKcff*dUa>wHRQW$PiCQeWKF>5kT2K zHnNlbtQGfzwuN5#&P23q@iTC$*Tz=l_A>fVm{)|*;CEjfu3Fa&BA{Ua)uw?AYKx%% zE?v!`Q$OTal>l=fB7SuJq96u1yB&Z0vn_}8umI`6{{&4RW2t%R@)rZw`Yf1raIg_tX4R-_}4WXud$quc(ohCn9~%8Bew#aZ=rGXND2qSM;S%w ze_cPJJ#BAANrT3(h*1rxQ)jHx{Gh_ zdkyRq>mTBtaxktRwD~a0^Scl<8m#g1FfGnl8+kzzrQWcR3P0?*dc#3*;Eb3{goJ|u zP)1Tg0hjIhIb1>Y{%^baz0G^wVD2C!DUcDR7+sgk( z@Gu5(*wN7V6mD$o)!i^S42#M>)jUsp)457&lrC)3kYm9aM8^S@MnV#NysmRpB-t%w zKzc?CAa5jjx=eZXj6F!XsLQ60_TW>0GVA<>{mfE+5&~D$QMimskfk{pL<8l0E2eq> zq;!KXLtkRA^D?(I>(6CY1&vC_W+bO#dAt}qb~v{DZ`D}bIAVzp(%r?Te7Y<=s1*N+8|GsKZ1lT!e3>zweYWQ^q-xoA>Eg~P9Vyw!wrVD%MWYx9 zc{DpTnyzE5Ub{3ct+L^M+3Sst*D64%U7o`a_{)@(>mIk+P%cxbfP|oiz=I9n-_-sU zW<_8!*o_~FR#@fDd+RuG;-U)k9u-&&-Z2`IUr_%Uub=HypFiU6yZOtZaOSEwvWnd2 zenGMckGalwOTWvSmIOLzdZuo3^tOiFxb`5kgV=Peu-2bA%aF>WFNR>EuE%9yhz@j0 za~N*y|6euc;g1E4J6dYSlNbQ~h@*^ehFRZqXdURG-(z)@KG!zC+|*(8?oibdLC*(7 z^0erOJKXSV)5n(+zF+gTV?p#G(f6olXv&SbpF=7>p@1|WW0^ATpqdP=V)Fpeq^(m3 z86)Gt%MijFmcQM7&#TqM}$of_wK=a(@c|$P<`Q z#H$K-+Kx4+e54x{Duknu%cUEn^Ar{$3LpGSMp!j>3~N;hU}6j)9k$+x?ixtvYQmy+ zyQAOZ%WcfDS|h3`B5}emWd_if0w@_^0xd~BiuOM;yaLS9XkJS~w~dMxySGC04`%D*Q(-$Q%6|R% zutd=JO{j>8sR;+qDUqg|8&?^@ARx7&*;Bezox?D(qq>pBFy#3ikJV+)W0#DO;~0Ax z1W536{f7^8Y!k(#I@#2X(}yY{ATD%N=s_Lq0pV}!sRT7lHBv#}M}NM*oL$!2leW0> zU>R_F5f3tpQ}G*-nI2bD(4hl@0VoC~NUEz5qo@w*Qm8Jq&V~ej5`uqUDD@y8OusCTAr9d^Bu`02>H>uOyxqpv*4&Sla)<3h+=L^MQ;T>Fn95KgR4XYs3!`Z84v(M>_mBv*b2ams*(H@rg%0N@0J%Jkeb~&=RTNqZXFO>JtUt>O z<7NfWDT>6X^&GfkE#1JRnyy^tQzqvmq9oQ>gdzbppQY57@$^q|EIc%>>c$?x6$Q9anIIB4e@2n1X?4fM+@y1Orrg z>;)(sk#u+Q{M!0Wx%RED1bBwNefXwVomlvp9g;8X#C76j!zqyVcC$3oMr5dDK!6dB zBvL^jh?0oM`g7|$c22M%C>2QT!NXUOneR{Egqx|Gs)y5)UrZ)`TUp5@qGWy{+Gq?H+!0e2TGcVC~KP@7l*Gu z%b9G_zWxUQlL+* z1ORFPL@mBovmM*i^2BaRg;~Ohbm0|0iBi{RtIVyx$VgjapY2LeV3;X4507OUenSw2 z`o_5|3XQ(vBoK{><8Dzbc-!YygJ@s_o?xrJ1c9ALH>$UtCIwaM-*Glqug&?+5g_)} zM&c8np+W_7e&eVrs;tsk!qQVOB&Vk#OMpL4q4xnoweA;6f*n*YozB7{BH7-cGh!s)FjK>gLX!^XQeopvWAEJVgQlsX#UH zdLMISA`RA3-N}N5aJI#V{G`%ke%c4H8d`dkz0=$`wobWDYGONf5W{+Ci<(^?>`d2)f>WPtsOp-3A4dWXDJMdLFbg182&? z8sSz%5L;^|Sc*?U5Ct5rY%1xD2Q2X7b6|;#12h5X!_9&II2)1E1g$hYGiD5eZN&#P zHcTg9YUitY@#brruQ#n}V^D~SoAu1BFOQ=hlCKC>0wNhEC83D^&JV#LuOWWK2i=9^2n7mF^d0nEcUSTC zl!fqy6?1DXY_GjQsO_}>C5Fpez`>C;ppLW z+?=Qu?EHJ~+})wO3$2WIElBNlfkgao`Y8Yr!38;U;B3Bga`)jHnkq0E_C9vD5@>`d zDML7~XpH*pT7hDyp2F7S+k*2y;)7z8zYEjDe{U4C?v{B zSFAM^kPWJoDIlCMza5S1ldZUCw#)*Jh>wG-F*KB3)(~yM4ZvzpCouy;i1eVLTWyeK z?{GHN{$s6N1pb*%^vq!4DF#g#4@`$wvWVFzw>icgoc`}ehaJ}>`BI>8 z+=Mcf3cEgH^E@W+D%6BTk`u~M_F`+_pfqcvmkkhm(5N0nFM^KrP^-LaS_|qb3kb3b zAe_2=_TSN(S}%WzZpo7UJZNMjWP7j3`nE*`x;a~lL^S5aPvnRW2~Y-w2m8rgDu{W& zs(#c6uOR}J6`4>9L{GgSeUFExxDQab#S33Uu>+5zy=4vDlphY%79!L`^(CTOM|U6b zr_6)*c1j-5L_2{5`_S;8gqVgP3a8ySGx}vZzmY%4kq&-LpU#d!NiqTyN0h2iA?E=9 zA^``l5hPEw52J_5_xkrT{63ad0JJ&h6b6n?mxj#Cs4tF)GBNlho%zX!!x1VqDZ!Z3|?le(FD@v4sMIzJzK;2;t6Nu6ji9?gh_Jr`~)tI59$>`GPO zMGrc75hOi_$hCmU5X+*DoB%tid=&HM#$MG>4R)P$uVqg=S6IOdr_KpWE%RCF8WNz|L14Ch9kBwR%i zjYs&WWm(HUCrBUYHMh@E;K3@B2%uU7s7;DFQun(eTKMM5_UE3-1WK4EWJm^TK{D-u zRAl~5DmP-Q3KGw`lMFJ|+_@KI2m`7rfWZ|IGX?{Q&iQTlDqjT}lz3Sv2Gf~s>M`M6 zIus|{J#?VKs0WU0`ZFj}w9t2%5XX7|s!?e1C;f6Mb^nNMJ);Xlwp$Ex!ISu z5CFv_1R)KniaZ;sHr}NkY!|SHyMSPbnBtr$CjWy+Hu@&IX1X>aeOMlcE|Km{8{C#Q zeR@XeB6xe0n;jU2l42-N&j@uV#SiUI&!^GeA+CS*(wsV2pczp)(teeZ5Eq~u{SF}GGE{QbHvYUzl zN(5$6bx**p-n}`*0A@r*Ed>zJ`XWNW4{N!! zg|EL1jy?5v`#JfStYSlCimIVk*-*ZUst`gK9o$DoiiYk%AZUSJga45hvV8?&mT~M4 z@)UdWpaOD6Ohtr(o-7ko(F*0z{3x6f=_01uGdhKRQ=i{VjJ*>tpjfl6Aw#na^i-h;5jzJR zLWKD~%78z3q?Io_+U7vw!^P6uXI3lumnBKnjS>)ygvmt;aYDJYxgfnMU_Q^U```23 zkSBxc)=RU82IXkFuY8HKI(YawuE}{vpLHIj05U`ajKq=!C*gDX{Q8t^^jS{LWshYC zFRtuI?wjNLIjR2Z>*R%mB%Tvj2sKfWN`;o!1OU`U9!hw~aKp=izInG>c>k;p`f#3oX2aY&2P5}tLEATM$x7~`{RZ_kFkJNRnE%A_5FBQm zZ;Xo?lkuOUP2vT_lsh#XBQLtj3l8-^)}TXiq6qOJ>S-L6PqR|`%MC4OHnsOF-CN`L zkatWisr~~MuhvmbvXmW$EiyG2BnCyG$q*sUfg{Z!@>(o=t~iU7{u!bFyL*1I{&x=> zQ-;_(JwMpvv5vbNS#NKF$Jd~-EhM_s+KMy_j@7J>^di3^2wEsZ0;&)Q3BASO|3ciX z0~<=#Cb$-`WzB{6-y_I}odE;dhyZlp0_?<3){mV41JsE8s0tXSlqvxowGy%%;CSGOCeVLM78=0AjVo@)Y1RV&4_eQOsOchThbelrSpBaS94 zJ(`mj`#g`lu+tXSr5MD8KZbuU^{eO($NicVF80qMyv)jDPOa#(#a(nQhCYTw9PWR> zQ;!5ewZ~%5(w?4*9dH~zog94+Q={~~TEO9fy@ds*G978ri*z~>(FFed0P|zD95$;& zP1sAuuZg2aA1@ByXUOqBHe+uZXFh^!t=)SQRy;l?i)Vl)Sc)*1n9R<*TWM{%i2v0^ zDJl^~%XU6SNaR}_ifP%m8{v|L;V{Xe)l?&j1)=9)pk`4hK%smU1Y+rvgboRXqeKtL zfkcMgXwycGid#fAz|%a;wa$Q-K>kcX*_QS#wWjaB(T;knq@CD4_6hkv$e|869B^G7 zs(B(OBn^nGqA2k|ZjT%UPaY!S0RtEU;~tNLMW9H#Ty>j{kq*$J{3t33=)_Rd6&C0b zHe@F*iW=CZKI97c+xu|1G!Gl?R+Kl8Lr-3dMAEu|*M*>(klu*&`BZ`I-wI5S12j#* z__=B7_4&9$H8`7pQ@t-upayx!X@0bb{zyf}*L; zSqKa%xHxcm%?C9DZP6DulORy9ao3a15NZ_PchX-9<)W9vLde(EtiwsizR3n73 z9ykS|Xd7_=9s)BU4Yg6Jd#;M1hCz@iPn1B{y;hn4Ht^ogvH3^%W9fS;{Vg;IAL@lE zL;ilpmr>||xF3eHxVB}cty*4L&ZOmp2NW$OtHtqoefQ6hk<13+&G5P5tX@722dM^Q zB?hss3%?o+35j+JXrzxQFXLJfT~#q5E$#U&vEtp#nLNytCzyUi{a70Uu0NE2ZW2cmN9KNT zP5db8;1r|xV`|nhHvQGEA-$&6;B+50qxJrdY9|fSf6by>^yh{d%}40y)S)rQbrH?8 zYjvTmFo*f+6&D>V{))w1HnCf%TW!X?*E9hCGKW;b?4KP08_0z{c(RDCfyK_Xv+Vts zrewgxOYEGFWYET0@hWWkd8({hymx7qXfI&`j>LDrHcbgEGr>4;oBYPs0k?Fz&{hwB z`?ln&iG=K_Un7oh)B9BKyB~2{@jGrN)7P?y1Thl=*)ExOTjMqShWJ)sDg@+# zR^^q{Oxf9*uvyWZ1VDb=|G>6QnJ@jL$64C|UpPlIM_S=35>EO80N%cj2}P!Pr+JHR zyw-XclUAM7y>he2sSsVqbv|AuTG;I~Gm4LoRX?Bg|81zH?!Ju<9W~c7>w7mAG~z(> z466hRDw!!_2ap)!(mR|IsATxKvu>Mz2HQZf8DsBj5v5k7dG(4o*`8kG<*!2zE*dhC zI4DLa6CwkTeP^CdRQ(8%+oGqwRA#*dOY@fWmq5W!J1SM#*Hf^ihW0moyf(Xev+AQ9 ze_3C*{11G(VIX1HyF0p;$DH*cpP~CX6wvSD{bX5~uNBng=5k2nnf%P5-{z?H3wyft z|L;^so0B4j@*aJx*@vXl+>nEq)PUq7cF0co?i34V5cpiH2cm|-V`1(pv1amkq!rz)reCzg^M{^F11dVY`od*fPd-+vo`28k80L{=K!N z>TBH5j(wJHG?(6wIrJ8hYTZeMS>x!i)2$0RFGHd#D< zX4-IW@?J|JsKdzrH0eoqp4N=e*}ZvTsX_FlbD>}PsplBXn>iuWNc^chOJ~ekYR&l~ zf&2$(_Ht*OSi(VzRYy01ZA3pM6@zkFVprPjm4afJ{L9TW#*y6q1?+B6iNrwTDci&@ zOA561^@>$h%`>p<*w$;J!>J~+xyK;8gnlWeD zT^8~EX0^^UoZr{Sw@)u!#q2{^8h?N8kFly)N1$skJZn%*S_@C7?XI?bHizoT(aOrf z%z9#)n~9Wa_*6e^8o~A!f^Qirp(kPuXN5;{sX0fZvb>Xo{6<)ebd09kL6-Ee&?$!}YQyP2$dmd@$fz=vpc=9!KEHiM zle03VoiF{{TRQ`{$rE3Wsthi4v&qc54_I$59Ti5DL-J=K`7|xJp&-hife^jp^chmS zMl0DsXG!Pjp=z6wlvv&8Aw1`lm0`c$MluQ<+Wfc9%Je3UddH zuY`Me&{$w8!2q39R2PgJhhtJ=Tn2Ud;eB?u_8hF6vu`fFh;(L821}Q&%oHh;wh?xp z)231L@-&Red!NwLcR{no3xlqc#R-Ny%Me<8hqKD13|ls<=M+!kzYGr?c>c25>*PFj zUAQPmI=^v;kcNvkFK>E(t3Gj_$|%8@prPl( zOx2_Or*;~`Wx?OFf}=v|>YmY$a}vELs$C6Eh2t#4oeN>55+-u<1Zrt0r+;&@ke-i@ z8$|ZQ8bZSvJ9M#Sd4Ok|b7fl3mBUh|EwBDx5uuhpEEWYfpazo z=L(dpP8$;}&0eydeteZ(q1NQ*_bz26p!^<(0-|$fN>ig!Yk9Yf^>MGeCJo<}kH+j( zy_aKB&Li}?A*WFmB_KaAa?q-JX$mD3{PdL5#x)%w6bB+Wz zVtgf}9aQzJD-zo--r@SrRNQr%cC&TRulBs;r-opmM7>H~Rc8?k506Tm8^-(0T^-f3 z0>&a5BE}7`q_WG|vWZ3>9ZQr2;GtvW*_fBTDNri3zf>@Y+IzcdhGH8kiElb)dyEu#UZB9-92ccUjPPT(U z(4Z#0>q!odF_D7?42+1t6=0`tGIN+ zu}KvSIGTo>^3p>8R^=jfQ$+eT<|ta<-Y4Z{U$CgmVma@6ZWwl8p2LYcI?koLl%cG~ zr%PR>*=K9LL1$+2MI@6{YE`Z>mrm@&!8|scq<+?N20r7&yU|1oXWH&&`Yh0ioZ}jN zl)D;eCk$74P8_#QbK|(JH0dwih1jSlGy?`jE5|~{G))F$he%;ddi~jB^n-srPWCuKGM| zC|WL^C70$V-`oVZdcL~XA}&`ksYWplcA72gK%A!_;Ie?}n^f@+c*G2D8y%lV#Aly= zrF}DbiqBDs3`}h3%}DD|=V|CETYGx?N&+dH=ZSU2c%S+?K)k0G%{@iloNU~6I9lq9 z37Bq?Zjz13a*!?OV8eW{a~T|CqmYN3R9UA7GSNJ%RtkyJUzqlq*A$eMLwyPN*vDM} zPe8E0j0w3{UwvUP+jEIsQatSi<@@b1@PFM9?JgZ+%kr(G2A`?wcn3l@cQsO*xJ|j1 zFX6klhnBs;i6@A%9Gz@;4X86E9x=0T6=;i8^(7`rkL2rlZB2E->FG3PIp-(Y{S=6m z(YtzqNV@n@wR?-djn+$8n8~vi|0=H1@|JRz7jIQT-Zhg;BN`;(|P0oTNBCwAYQBxTzH5HlO+@HkpHy{{I~e?Se|x;8}9gw`jRcXJYCg=>CKjDHd(32ud$fu--lbP=olt0~m+$BGF?fv*yyz zhW5NT{4?%TI=V(Tv`3Gf%{@=E+F#)Ox3R1VK#BNJF}$Qhd&!caX#$J25e_tiBu-`i^s8JUv~p3tpB zdwDgO&|;4TXw0n8c4|u6g%jS&^+lZ0p^>=@_zyhj&q*Ypg1pNLiH2{XWSu`B!kKIM z-X`B3RxXT6$&W@0VT@)Ru3cLUyRW?6Y!ggELJ-0$#H8&{fsaAz0=(`jVBf}{5_f@( z59R43J(PfRUdKym9<+|{W#HHg-hprzvvN8BxCK>O|m3RYF zIIQ1gXJG4aMApeQLa~trf%htJ=O;(ROQCK}#h;Oqj(@^tN>08SEWsDMr)QxnY1=ibhCp+3El1 zripN(7{wevN6v10E`}{N+x<3qOKOLIJ^@`A?b#j;GS?o<=$nYWe=XSI z@hP5*=9rC2hXqXv`P8VZht-um{h6O%T{Caip|93J^Xh1hE|gowVm%u8=+}*|=PGP; z-lnsX#VOhRPsiNmbqCHoH`Cr-JArlaZqsuHhj^;Ubmve|(iSoDuNu%S#vaNAE`hp} zu`t`z{5}5`IJ#0Fx_D7k+0Khy1KB?`;hi>dZYwd4Qwuw&F4$1XI9_LgE;cir%%x>1 zW7LN3O^FNZWhHe6;wpN4DVZvP#Ff-=!Amj6G6aA|VFnNlGQvW55f;Lk=jf=b9fm_q z;QK4mgM*stay!G=xmiGx5rq7ZUf%D9EX_A7>!1`}; zdyt!D*1Hs3u_2!a&AG1OZ}D6+gzg&t!M?{sGbcyL`II`{#<(*#e5If)}p|%hE!bsA3f&04c;iv^IHN|?dS1%p5;DYpBMA9 z^9<=##@Q#yk3Wg`YLmmS^PCro^r2`33>`@&A%5wz)yXwT&A!HGxK^pBW5Py^PG`qE zj9PFrlVq}qh<+5w(a`0VEa5yu{(-q)_I*dIN#dry!UU+*j!BhlMcg}$Su&ruGSKR; z^;{s&M8gj!WvG)fH|*2+F%Fl9e(vMU;LYZZ)OFM;%9wl_cb-jZY1SFUn(rA%QR_)V zn7lPoQo&<%`q{;mf_Ml4b0d+t#7dR*u2LgMKo*X#Bh*|jqpWG-TrfVH&ZWd=O{ADv zF|Cvp^-#BA17P?@!Rc3t<=G%{J!7KkI;79`m(QUsC@Mh*K!S$z7M5*w^9E>R-*y+$aA26gcH!bRJp0>i_FyeBK>?RF6D-tjtyk4<)&KR;>JIOq z_3^t1hdJBVFR$4}Yt}j2NX9nfqJ}SA?)$5F+FA|z5B0wX>rD#JleH4?eYze#k?hj$ zmlrwZ=lVVW2iTsbzAo0EV`jTLzO~nIN1w!9&bp+8ghoLmJ4cz`%BPdaxtW3bQuA)l z9qNteOV&u+^lp*@;0)i01I68%ph2U~V)7OA1|SjpHE z%{z?QX1qyrN2~I1+Wr36`~Cac1|(uaj>UQ;eD86no=ay}?Sh^;Y(*lLcFf3ZX=~P0 zD+OoSjqPOi!A<5GFVz}i?zXE2Ox8q zpOdlM?nvP@HzSzldf4dIErPQYi-lFWuz7_UbYzF|{@>%IKWhKr=I1b0f;H>3GT5 zFgG~5o0)sMsfM~wVrVGRp}=;j7B(c1_?N?Pt9MJUJo1@C+u@;2od>}9h#qXA3dN@x zkt}yqt(LTO7l$=8aI#=+$=Fu$>Rr}R*P4z-a`&m3#<@pY#rc&L{TQIa=CyBvgDBAV zRAx{Jp%b&ziQcs$#0z<~#pAA`d_hKKKlYXxEva0{IIT+06ViVUiw}FSt(;G^{1ng5 zU&b-6DGR&n8K&IC@u=|1oTgCSj1@LUm7dw=(0Q1r!8a7y@pCpYU#2A8s|6Atrs7flz+&vt6?(el7Kux2EqI< zS{k=p-_Y8ExM)oNWjD^xVKp5)`dX8xw0!I+*LF78W@yc7iR^k1f1hTT_;bxI^MQsL zW7p&&z70{gAjsXrT~_YXTb<;&Ny7y_ncaEi5lpRs*Z03m3Zns*^qs!4hSTVu-~D`& zbIMl5MGaq@>gQm`@l7OeK9TMzeuD2mDzA~Hc)w$5HR8gzQRL`G(d9*Oc)@A4F6qZ5 z8)~ZY+PO^^s$m_itYhH*&T;dfYeEljQ}NU~v-xku2HB}f`ZcLrILA*PMSxMojbFLw zW@{Lh1mpJExrW1LFw{r#v2yik$+c`T9MNNzob{(fVn{)u3+u#yvhgz^xLLg2l#}`S zFfG#gh5_zOoNm@@9R*s&hE)9d=1}h)rj%*1)IFoU5P~jP5%L_kbw4H4I$S7Ssqm&h zeTaz088GDz(!4lp|FaWQQ;x)|8V%mM81AY`RK!JROFnVy#5@Yr+9OhwdG{EqOfD(l zK2fQCHTutove&m}<>0){wHc4sYN^HOEU)U;t;VeV)mzxcHoK74e03_?*n`ADS;OdL zz*`}B&QNN!G=*CEsfOLAM1`ChEJ0GVBvMV2iCuu1YWq?YPM7J)P>5`qTw%*kn0oKH za&e+A=4G6PJ${K_@P{#Ni)JlNpNZ;3b9Q%UXmWr5BC+8ePO1XtB}ITPX&t;;)F!qR z`N;XN7LIPs&tM}Bn8~(tyq*b*=O}3!$%J9@G_#Z*#d&h5Yan{_v8<|We_=M4GC+BH#2m-8DBdQMwi@Np_VKMVc8b(4G<@Rb1I8|c~h zEiGT_#Y=FZ$m89~VlmY=vQM0CtB+j)bJAJ_DT`XfN9LHh)3E(krY?DP=Ho34*@D7( zr%GkwBjBp)%*?L4%@;pH^+6_N_``Fq)ZVltz+S%(a_g$?*z-(UxbtRZ*S(VWDeLZ| zt1z6#QL?_ieKU-a|#vnkI!85 z&7Hk;oO;W9_B*^SY=xX~?KoWqvaFiekGb;MMJh>a)j-z<^g5?9fLKf!$PuNi_Pz@7 zl!@jy<;+au9OD70rJZ6Iz`Wyd`iZ^htU;3}=IJY?t3)KlZmKMLI~K}@w#uxC_Rr&| z_0Ls4RMo%s_jc`1h3!pA)DZ#$KAdiuX!W)CpFfYJ$r+R5ZN-C3cZ$41wLU(IN%_pr zy1a_;E?ed-XWgVO&1q`TV(k%DBo#W66%)gYr3gdZZy?$nvt z=DV$hldOI*fkbXs!l?k>G00*E)qbhu$m8cI55_S*JCyt87P+0;KYLe=x9C2%Od*L( zT!$}7EbHQLm1>(G(AK5!u_Mr795YZgrRAn6_8F$}$t==qB@5J-$WDv+TTkut*mxh^ zy8@NI6ZK^3PsyF{Fd-eiSr1~yfq!C4O}cq02cQ(6;cTN>`5BqD@1x~`<^u*6(W zJxixdao~DS`%5y9H6@R z$7@|*q)NQR54|KavN)%`k1#E}(@}2&@Xxr!nH&@=u>{69&aXEg~&t`U2)g zGSCJEIG#;ghfET5(Yj;FiqWKr(T(k0q<|G2O~L4R%L!#qLzz=~;v!9{e0?b%wE*}^ zBM(0n9ZyZow!h}`{S{Sm6^04tmUcFcQ29!Ij91~`aUbI4~avIWez@b={ghmtYW;mOMjIDkHCvtOJhhM z?lp8W)lMl*F-q7fjNX6QDF`P}n;^W~i+B{WbzcCUh*- zT}JT80u*Y)ZPirSa1w2(DNq+AL?+-~Z+cvTKoi;^Q^{|{I2p&>Z&ZsE1NWf$+^tp^ zMAZ%me5u`l7ew!%?d?*$=$VWmkOhiD!$+OWVYm%4UUiS#J{JKD?)mG^bOV#S#^ZD^ z=x4Er{K~cieQtlRd6$4OQ2U4RsCo1AqT>*mdbU3D7MyW8&{IrDqp%xvC1%Z+0_EG~ z=DG#SAW!dL#*PTZI+x$ooue@mjhj}0so%%RUrJax{{z!k99fU>;GuljbV zNvkNBB2<1%%z>2;3V1x~O=m*|g3~(aV7d}UE~x#g@Hq2iDGIAaQCCr^0?5!?HDQh` z230%#)s6s}<#-WJ_ZxsP!mY4oF#D9khMG9Y^t#HvlZ5&$GvF%KOxbn)hk3$S(QZ&c zDn@{a5EPCIkG0VQVc@W4?G(NWfa!}RJ&0kM2p1w0!zxx4VO2m5L@EHV9RH-k#V{06 zXA3?o7#8JSj^wu4amxcTfg~)Eus8!MDmtP>wV+fE@K-bvsO%RVaLN!h3hYZW9YT^2 z2XY8<$I4OtE9TVx&<>UOIfp8sQp{#;EO z`Bph0L`*n$>=;AMs$H@vo_naF@1dq7hX3VPM;&%M5Zl}+IaKNZMZf31~5d}t(cx@Amc*Il%*YP05{1mDP^V#hfMXD~6JzC4Bp?B(Tb@%Ynoi zeRBY^m?H(8g$m~0S_=k)?oqs z^IYFSK?gR5F1Ld8OevdOV*a;HHy%le4aR2b$+LYMs9zUv5EoZ(M%Nb2gnf9pqP>^M zl=GsQ;+r!}M$8681D*U7p~C~&=W+XNawtAPXp%+>6}TmCypmO2Lu`g}PY}0V_C^IM ziUtsVwhbp3G&atkpw4SReVz4ZvtIUD%|||I7l(Bj-%U~_xyxZ~`8<^>Z@$##Caz{v&X_r~(Ak-p zj?QGVqyTW>g*Y-VMDZW3>T-N)FQ-%X{Vg0AnX;tGl7;4AYk~o}bw=pCHmsBg+6K6# zZWQK-VY2Z^Jf#9wAh+ayeu$m$Q!l@cW)_4~o+zhjQ`;yPlMC!5DB7nrAT+Oc+mf(B zf$kU{b`mdJL294hI6H{3Hgu4#g9sgc^YMK6<0DX@3WAKDuUA=|W%AZ(0g!dG3+fCdl%=R*$Fdz_ot~nv^geJQ6x<0Z9r2-d}_M=n_lPUX>8o7>T7cBk{i*wUwa z2Rf>Nw@tPO1W;|J7IE%)|Q8uALB*wR=9|fO1qv28f=a zjYcd!Lmn$p#x1XW$@)ffp^liyZkUm>xTtd6wLbh_L9QdjVvFmY_j;6haqPNA$~sCU z_YigrS&Q>j{{IZI&e;3Xix2DkX_OXdIXIVyS~2M1L0c@u;~B8-o8rt~_~saKfPw@| z7$P$*`%~A&YJiFJX8~>su&b7X73q;(A2DlzHLB0U1Xo6Hb{ry%8cc_RCYLSBe3Dtn=89BoiI}Ez( zd2$`*N)zH<{S>rF-g+XagGec}T6vHamkTIHaHRy2wraLV`cOFL0pl)l5?p+}+0;{p zq#&$uvfjqsSFuM8{tI(cMM&c`K%_!pMJOt5MMo)gy;m-}`&8yYE1qj(%6uCFN#Gy_ zb}8wtWZSxrz{hf{wF#%$m`arX)X}zUm8|7_C^ww{=<9y+!!e*heEd~w_}Vn(M%M{0 z#T{-?7^XsTP!IrYPNU-0i65e57{R2F_{e&SijeHZ#6&)SJ+515d!bLXHFI4xKy zPgf6T-iCo(5a#ULL(EJ7u@Hx3$`cq7(<-4zdX}MbA=dftqNtUW8&lHL(AN!E;~#n; zHEkHduB>)-F0HFS4l7atc23s&0hnSV=o30a>)%vRUDwHph>{vkpk1i}+AcXT?&CQhJn_ z{WR*Xl@-5FuBoz9#;}`Biqj{!qRL^0TW9f)6qAd%fI0bVY*|g|JRR(7gwx(X4cwI}6-1q&@o3%+pxL_fQd?dwgC3f3D zJQgp;fO*|SX@_s9B8V@2tU3K+i=)ku!qzyW&`H-pY=6EHd<%s*(2}TXd2n9%9 zrERsqj9?6Z@StI1I`1&l6)|TgNmnL4Q}8kLN9u3-n8Hwj##f`Q4k$JiP3%uK7o+N(RZUet5XQ+FxE-P|Yz-;1;J+roZ+z8f=zAdoyo z6KCJz--1G5M&boYuTLfHchZCosJjosGDZX^@Q4dAvkou``_EPeLt>9%SR9}fpkFzt zJgi@s)ykP|tXdCT_;k<>!VAD`w2jsfyIcGv0t3By4I)U0jEm`s6O4#|)WAc0#*Y$T zK96F9&+1SRA9$qN|2rEv*e#g+G zeFciXAFX!{)%NQ{9DUL9y8E6bi*{bAiA1#&4$w3)3aq9*UFNz#V%F1-F;+R9xC z08V6G2!XsN&vUD`;dPh|_LUCtw#EFif+$pEPcdJks-XICuz3n6kGiAw4a}70ZjzvD z(6xeBPUvo8G2WdKYHlfg+h*_{esXr-XpJ`KUjJPECi+JEvFjE3ioMO)SO@Ff7wz~u zyxf@x5dh5J%xb|9$xcjZHydv#fP9@@f1lAK{ip47s?hdD7fiz0Iw86?`95!ZDLZrY zV3vphdQgpPP}}VAtT6$BP(BT>7h}H+QSy-|zuuXQ$`SFQc-Zwe)S_2 z56&j8x|_c)>5pC-8<{5W^p%OXW|tRtlcXGtoJbGcL{oJDpW~(Q;sL|X*~fQX$+s^3 z_d}(WZe>>fpG7Tn;OCXvf_PoZ9WV{~%z}3yjxZB;0%^NHQ17wThJ1kMBEBRLx&j>t zgCZOl09zV`BR~)g28@9IBOnSBH7lxy4-X@2VRW&W;g3rjj!>xdT6&Z|YJ~0aZdkP` zl&SWyK0R$p(sh_@!D^3-M|X1QLg*Nw%>4QU(f_p$tY!`#&i_c96DxxPM(sU2V3SRX zR|L|3^;Wkv;p#u$Fhn-;A!BDbKoA(uQILndUu`I7dMo6`IkEV#d;TrUH_Y%g6MnC< z;V;j}Ereb;AmyvD_3ds7!bw6!2nNI2V5b_)x+y+If*7Pwr(nRB>$@DzR`Va$L2Zfxf~?6Ltb_Sk#Q&kipx}&t#V+dO1fqoHaH!S< z2IklQOAqo0ME#-zkV5kS4oworENE#G8 z3K1~WejGpDQ69lXBgSk+Yu0HtDGJ(@4sucBATc9i#U=zoC=WmoZ4Dy;->J%pURt7W zy{}@m1p{N={6+oS#&45`1^Tg67LWNJP^T`9;A_4cH8jUeWEvxfF7AO=7` z6s8|RK&c;vE@NuW4ompWx~LJ$B^IU&86i*Fkj6_9$i76y7Qa>SSPN7q2l#?J8r@6;B3T3KmZI~tj{~Ym=i}st!))`wO`6H4_@v_ zVM8f(qO7P?2f8x2q4}SeR(kcax>)-}a}8Dqn>}wM78zjxSXMF6RwL3`r~JJ;-5!d# z;kbVeHBjJ1_dPDTxBI?*HhLO1=G;xyEPHrWz|_peAwqr$WXFtPoqq!9@olj~5>-U=u*DnO&FMfPnfd#=)XUlx8Wg2dlTymVRgc5Oh;6*q z-Ne9BGN-rt$^xFUT+>8!M@hvAQEXglSOrYJjNNrdnF&&wV+%39V2sHch(AyJXdch! z*JR(Bo!0)KjmNfNhK8lm%7V-Ft_V;ys7^@7_hJ6@k2?8+AnqHKk|7{-28dZJ7{GvI z^vNQ@hTbO395BK|tSY0D5h}YH$7H*>yZ7=PTPjXOAO26eV11qB9Y3eT#m_cVj2#hapeFvB-oO*h|0&=i7{Axe(7;FyVA! zP1I__RH#>W8lW76P=J<%)Y#y!n^`fcj15L6#0+y(1{4#4up;+d$U*!f6P$q+QR#I* zBcQj#_#2Ba%RG4eLsU^|E4S8rBr4l14)V)MQ-W=uM{R{0kj|59A%q(d}sCBl?qaFs|kS823wiN)dWgd8ZPH z2T3TAgi-|1fC9kB-;#SKEixfCxn_!&?5=(aEHjJ^53&iTa}mMWrKnC*)UH{8F3JAU zQs#|9$T$uIgi<=PJk+{<>D4AP-_bNAjRjLFZ*cl;AyJV2nSd-1K;IR77vARRQN@QM z1ANY{dnKS>1p`hTsP1@mzKfn2o`jUPpaEx?{e6_n7qFKi;h27KH{zs);k%r`hYC@z zc+xZ(R&Cn;&Yc;W?W$B7V?lxajY+EH#iY<8F$n2EhMi^vPZ6)RO}i#iTiR7ixYQ$XxYz(PS{7SnU2D&Cca=4=aGSfVKyFj9mLk^p>YJ9;V% z1RI6Ws}ZJmhcq+DT?PbP+@fB}5D4H_p$fLSXK-r@A=VtxGsHgfCU~6qY4}yaBm=KM zVr;UI*HD1rOYA5V*`ExENFJkB=StE0x_ zWohwFJ6wd=x~0<&`rM4Wt%yFf6M;ME8)znsPmLaCo~^s26Jmk?sg5uDTUGfzF7Krd z$D5+RvoJbFy%0M81|=TCidf1v96Vh0yambsJ* z?&gG4*@7ph@TjP#MmeM6?(pv2YOKCSHrU+ua(`y?LoLv!O!6WM#dn6K%q$M6y)ptj zyo8Q9iTz%RMKvP`!_(G+hlU4O$f|sq`g0U6fN@}QG`ovEv=He|Onb-%W@pW@)_TA2 z>1SW!O)?!iYf5pZIW-Ds>dO%!7d&j`a#`(B)uRkkw<+Ls z=A*G{uxn~r#qP|EL3Jz?v~GnFK;B}C)p06@6=XqnaO4G1gf{O` z3ym?)CmE2p3udC#k%Fn(!sG$KL3WJFK>%kWv^0dV{KZ&Nu-%`|D&Xn~K**YZzN5+N zzpK4;+ScxGZ<|Xu(CaW^Vu*}F@XI%_yqQ0_=;1+6jM+6Q(aXS)Mv?!Rqmi67Ysk(e zd3=9USJspO@NaHTp+O*Y4P}ZeXSKx5zO2%w@4_4=a8DvK$Y~`YtlQ+Lo}hsYkc;sD zoiXE=1l#7V1wr8gzg8MC6sSl#P$UCbZw;GQ#KTVBDYbtHFPS}kn~CDhTo_J?4zFas zI~41oJz%2t=~u#_xsFehR=6|s73W-ra~Bi@@JNvazl} z$N>cPd049 z<%Gb5spAp(WLxTgu_zqSjUS_XUx2ahTV*Fc_RpyLni9O)t-D?C zEKqD<<;p>zxZT3mi@p@Y@yYX-JA40$w01{(miF!!pIvbBx!ud0W>!=}LtS0Uk^+*u zm0-Cfkjj|7Vc|^YMth1bMb@fr#V_?=(KHno3!WS*H6q2Ya3P_#9`MHDkV0xVc&S$$vA<@f1Q1S)je-Mc^WL%9tp~yUkG7ykK8(+*wcDj-4RFz)p=(X(*SVtj@XNY1f^&Wj4U7&Z z2&OlfzvLhQV>O?IU?KWmXPHX*%}e{~yx4NbzT0O&%q!qn>=<$FQ`}Z_ryY$7T5|Zx z6D>cIV*yDdMEk7INTs*VE#9*?PAmF#P@mpA!A#B|>II<_f8p!F)^Jc_kQ zYh`O#-RIcBaYTgjr`p=PIuhcO=t|vCeOq;GQ4@QcZri~tdjvz?q#gPC)j+5FvRVi5 zQq`?vA5vV}RUfy^!YaOIEaA_M%g)k0dGNSaBfYL{TX)3`aPORgZfiN?0p-V2i-O4V z&dTCgs<8^N%`p=JVF(?UV2<|y9i`Qx@;d9Nrs&_u>v-+*yuYlsZ;iIY z=gr8tVm3OD9O1<8n6BfTI_Dte*5$`@BfIM8>xXit)W3E}s!segRJ?PxsQC_=fCI@p|?3Va0#`UVY2vz}v!CTg|nO^rNTR_Aa=O z9pTqb=L>9bVT?HwrHKeZ!q@h@2-3u0d8^ur%7YZEv31(MRvis4UNb5BGqKR`?B~wk zky#_aP5B}MaOjKzkJEDIwG>~O#=rSzBvVH$bsXkj^|mXiH890PGkYvh*C!A~dE*?; zREum?F@VL9qrrZDZ}p;k>jBTulZ&SpdhjL@4J;OJ08%1IG!RosRF%{eQPzB9VDK0? zulh0M2bS{6_qyZ0p$j~jKfirrk;;BW>dt*4XYr0R}gIm`vvUJklBR_qIj;5zO(dl@`}4jaDCfbzT9 zF5Yfqy>P@&I-L;ib)Ix{Yo_9Nxz_T|c#vKU^PS#0>oYM)DJ&s@Xh&yLETNz)0|ARe zhKC`65E)&SUC4zHtdL-&s>85hBp6i$24csd*Fb4Hxy!C@ce&l%9Op64uIH-VbDb_b z=I^b}_gS*;b-?8v9yoU#j^lP$PNrF*1UorZM?xATI1&qxrAmiXf+U5Lop;R!AnUHV z-!fu*qslj0-S?b$yDnZk=;`XwT)1<0xx(H!^J9g!<*9q`aE-iNt#);^oku&|IcW}f z!Y@)w)Nn zwK)&8ys)+~P=C+3B5@)DhygkkY6x_;DNkn{xDFC=?#{!bc8)QpB{ToC!ok#Uj!)B2CP|?jgu4k^;^ZgAvPbM5Y!nBmkcV0k#|u5)!*=I1%cxS9@NrvD_ zZ0g2LMkKc7T6qNhj<|51i<^cnXs%+#F0rl6jcgd_GVJq#$tmP=+%crlws$s*TZ~c7 zbB$@J+hG*vkmP;q(YcPxjX|pa$Nu~O54G%qDqi)0XkP`Cc?+N^RpYoE*`PVhM={9Y>~QyN zPdYAeHpUtddCP*7CnJmqPyv&p9djKor$wxVRoUo#`1kM)%SWw&mq>8{f-U}8!=pEZY?1 zfw3JArlErdVt-K^Mh0P-7zRKX`C)y&gGm(^L^>U1^x3C?CeF)nccGbn!_t`f&&Tq3 z->XrWE$JD7OD=11bg6G~kUEo;LjL;Am#-IVUd6RV%>)iRjvY4ubT@QyTuodlJ7IDo>3#4EVwtC`)sKrJhnHSA62)0A$O0uAnyPa9fo#p1dK%s%7 zf6!PF0dj8x<|k>s{FJXVhb6G?lv+Lv0oMT=Nd^u4t8_Ec7~t@5(igzXkAFL;uG@Et z-m?eqC%K#rvDM4%Z@X~Ek2Hq6t1 z#LQglv6l`c0^U~X>)p?s5j@D5pn5QVzxY0tlf(G9pXbf9CuO7i`6#+hTsnAnFz>M; zRn`2vVxbZERcH>|RopO6f6~RAdOKX(R3&6uWSz zr|!^bbM)58c8%jMS&;@yWdR0zO^an@rV;-6tB7Lg_%Yc|B4W!6UamJXPyh)xrbHR{ zmPo?5;;1Vm^B`R%<9(O*T!b^@Cf!W#Pl!da_A$RX zwbLjGd6vhXOano?!$JUg1`APun&VLBMMC+znzv#)@z~ZEcm+^cwI~KagiX{pzT>UF z=yKuw(`G5snK4+jtc4yOa4d#wydZtL2Koy_3|tTO)`Nr06zJGAu&an)0|#KVPXBS> zqwD5U(jF3j8Dy|K)NSWCA&_e-gV!jJf!e|WHdPIf_DTkP$ZCWk#zqR%1#O#ET6%Ov zslNPmeD6PAAD4bZlz1?W0GPsIrz^8@t75AQ>iJeS;++U5j|&kb zQztU3E9~)ec>jfixcm%Gyyn5=Lp~0qkdVv1foA!1vuGMj7$~r<__pnu379NA9(-W<8RpAeq5!#*W_Y3q?nNds&k9=^xR0*>Fa2r5)@)jWyAa zVN!|2`MdhJFqqBVyRF(>w)gT<#aBit-G=WDiAu<{6+a$~Ug zBqrnJ*Brm{9`WJORPM>FU%x?3LG$YoRv=M7fV5ZprdO1(3%8#i9;_Ej2AT6mklXtZ zX{~w>dfmScpLaPR;qA28=S9DMjhkreZrJc<07zq0NC!8*qpKQ#bfAZ;5}=&#uSY5d zUAmK#Yd;O#Gh-fk7B6g@Y^X34^J4K7QRI1Yj!m1$3srOZ#UWG!soxylXFvU>m4V1Dn(S^!6Ow=gZ`$2*Y zN&w5cyBO5FaYru&CZ9%@(HWUQyv#Y4E+Kj9+QL84wk@-9QbuLy?AzF1nfjXe{l8*i z%A5K{AODb4_|R5rRmaTzdkZ+|v=RHDq$hny5732MBzM$EFe;b>3c6Ckk@*!6bAMa= zv8c=JcK@yDPk4_zF)fm)C$}&E5!Kdz^`APG&Qq=Cb>b|G&j;1}=tMGVlBZfzY*eUm zd5eXFl$kidS%}fc@$}~z(%)%Nd4;uKQ}O*a8M=?Ie_pGB8$DI#Z)RK<=WZ;NZ6lIM z9S0cWoI_yCMFEYEwXsC615nh{!m?6;gmQzVpwNU5v6yCmTlux)asLUR;Ox&RjxH74 z-GM;kG9vqOT<^gA&wt5fH07+29)86-;FgFMgRUphiTFeX|2#aH_Ep7)a~&(PRN zQ{wuya#Nl0t9ZnycR|Hbkn0!#dWWkGN&!YBWDLlMILNISKm(29K|(z}%4G|e?qnQU5u%1p~N>_W$(>TSI4?M;J(@jjI*86Cfpp-<@iTpR}7F7);l zS9L3XeH61MkE=WRjkkx1BX<1XwaDj)H)fwfgAtG-QGx>A!^+!@VXLVgv>~hK$r; z*>&WwbA%fp#5a#Fgen{+2uy&GtGdVCC2&a@RU3*O<^luyMnFcc3TxH+Hn=csH7Z%( zwb1hy&sSosl9Rl7zi~`wn?=l8k&5N`*(Kv!NekmLfdv_M41ZI9t*vA_c+<1|%Nefv zxZA68V>k`UQy~LCR)be+osHT9KQ2O|*Q%vFF};k3$JaC%sc?dJWc%@;LUxNoi-);MLbsV+`pb06IBsAdXVjQT+I;NiySnS`>S(SEFm@B}V}S zGW^uVRU?Cdk5(e>;5dl07?m{BlXmr)uYXCyIplWdJ@LqA-QS4A25&M_9sl=_g*O&s z4a|<5^H<_O^3#z5W9s&E)chVL3@5p3_@X>f{-K<%!<>%SPqF7d7-{OYZJj(vAqDzA zXZRObkFw|QHI|xdYI!@dSGAaVO4*3>{CO&y$;aWNEkm~dPMvI(*ocP=qhlVbZq3@) zZ{oJS2JrUP4=?>Iy<7RLIuWEHi4)xGIe8}Q#x0Jf&|l-7GQ9>!r+cKz7CFt?jc#9< z;;jAt$Ki7H`Jp@K($Y`K-`3u&ha=_KY&bsLpPT*Pg}MRhBNJj<;bH20J-B((v(;FE zlT3`rkjRL6>~-E2%^&H1@EFl7y-fHv9cvUjn>_VsJB+;NboaX8&pa|b6Fn^Z%w`<) z*)p?T3SYwA@@0b>XO?7F`)$_#S3~=M{jlKSdH+7{W&JxUb&}9wpV?Rgs}#_Xinl<~ zl8C5$ebeaN{bT36JP!XK56OFBj`xr#J$GLzFYy)tmmG`R9+hbI@q14_cbD9w2F1Of zRa&d7N1KJ)lQw$#_~!bTI1xKhK&T1Ul@UL74`L1QGetr`)e&&&*TUhyjky5AT>`eM z;u|M1;!fk`**v4D(7)kFQONsT^yiNMU!Bv{CS{B6@9d7u1EmAYk71!nj(&gH{sO_X zI2xJTTg&&qt%KcNoO%_}$42TqZS@9a^z*SRP;j`FKY&Ls6@Fb2IDBPS0KV<983MdjL@p*5W2Z?9 z5wWY69x^bODgU?cTqKj=?l!eb?+P1;LV;unnx(CI2$}S{1_VDBv#xd%b&Cw*=iX^$ zY^Pcgkr*W?%HE&b0V_Ix;&D!Jp9(3)V%|L<({4XgwH44fjbKElGv(@PZn?VXe95C7 z$pb8dAn~J$DdtW z`yR(TE?hO_Ez`O6s*_?KtbONpjhJhSA;keL5bvM|Q=dp3eH!im?mOR=4zF{iY;fws zdxEb(h!n4=}aTVE$9*j`M6;pkuM-CJp&qYo& z-*sAAfrP5z$_>FUPaQ``Sq~eQZV>(J&-Onj0PoB3`Jyeb1R{bk-B>^?0L!uKLze-{ zDI8ut>tT)x*%bA6^?XdeJR=F^9Q<|Ay~7VtgdQiyMGvKop;RIpCPYJGmZHoMWu;(b z&mO8t?b$Ty^CE*X?Ow1x&K0Or=b)g$M4$<&&whLS*{kphpmQY@^5zr<*O!Uji;=ne zyea_CI$gJ?sFUos2MNLNsthLI z}5fbgef54ZP_02E-N=*p;n{@?tma~Bi@+lWgzI5ina zKZ0^xSF3E=78lJXWj~dp^tjS)cFcLB&Y%A;Zxo80CYW=5{s(^P>}Rb@Gsg*(zB2%_ z5H#@e?4s~s{|tWmZzCbx@ayoQiW^zmn`qeB($B-XdQ2*Gvc~ytWgOUJ17;U)MErZU zX4nk~Ymk;BMO^IC0eW<1&O#*?PqJ`mWknX)sFflJs;ZKrl+%qHGKw=)_sHI7yr_cM z3##B>{P@ipUN?Qx}+a|Ysxu}Aj3duvl-)=;D)0Z4+!os@zQ@druQ(b?n| zj{$ex^Zm@$^T#^L^Fc)-d~&D-=)_pMoG@hOjxskmR4_IM4L}ak5snmXNH4*H|F%&p zWE6wM!Uz?zro!ML8)?c|I^@iJ9@yK1t#eBYPQr)@6aYC}>9>)TlPW4!YSz5QQ5z5p zkPa-==Y_3Kc={70GbD7wMl}}?#f&87btZ&-U%h$&=Pm>IY@SFRej(0F0=e#>7czeN z(0V`xFoFPohGIM4YwvCReak=5@l3e-7+_>dqc14I@@ey1vfMl}~#>Q{BwW41Z? z5~q#0@^<*|*PW!^!{BSyk217j1dY@!?x2)nNst$Zt?TV1U^V0DVGK=10AWs&h{?2# zXr+yCUa9ziG5T;U9&~LTHZ${)s?LeVZLt77&tZPrcrHFmDBW=pB71i^McLVOk^r78 z+{HfX4?bd*&)CN{YbFZHDbs>$KtB>+IFL1Uy6sDoLLlcsPJG#emDpABbBVyOkgR47 zL|>Ny90C!Iw^YQl6&K2AyEFNfALO)ZSfJdP=~Lk-Zb2l0#7Rag8m}?hdFw%9j#!PQ zTdT<8q&&p%(FKo9F?84G`4+W(19nvM`#fT`j&%luq+%utMqNiDXn{|_ruYlgh;Krs zXmz2C?DaS-cXIUX2xGx^45!ug!~Ss(*9wVc3N$0hO0z>21ki<*9{~nb5jEp{9;^`F zO*je}(R&$v*fmnU?#+upL6JRnr<`?=2mKgv0!M&%yd16+fli_a^+@T%64RVFS)qWE z5D&fFU(_SaBB{hxNCT9*-MtK(m>%(jEWT-EvW1n2eC{Y)&UQ*F@!i{pHRrgwyQK*s zom%)1v6vrQhd-6vWs_%7Pt!uxOH=|K%|)Ew#mmlFJj;ah7)_K}GD+@SBBb;VT&_LO z+jh9FJd|<{r;d!7C}Zj)PHH0NM3HH-`H510B|sLlDU67K{GP6ELD)_10xHZF{ESNJ zx$R2#lto;5SOXl}%EWN&kKRS!cC@Zs47fSaJdBmpf?o$RemhD9>6uG6#{k1ed}1Mo zk#qUEOE@ujWB=p-Ncn9fHAEpjulO5R*m_lI9hc@`bntP)N))IZqB2lW&%R8$m^2iE>m=`hl)5o}#vAS`l9 zf@WJeSAfSK7mJB)^O~n<8IeJzsynH1&L&`eK4K?d&&KgJZb~Ad+17$z;7nbA%E`?{ z`w71c8RBwhcd^5trx*fn8L3658iI_6STOMhEM?xJT0RA~@V57f&$?&X;>swXe25wh zYDMb)&0HEHkYRP)+>BsG(a4~0H2Bs~fCxR66`k-P?#k49^ND@Y@bBv29*_?qPE-fJ z>lzQM9p%KzQ@%x6LuIkFA_(nivvfoQ8YmcJwT7VBr3H9h`VIsQPa$R&WEML{xT_5Y zl)KjJKtLr+l?k0&ts*9*D;PB$4Iq>r%*eu&Yn;fjHc+HuK?IXqegboA+uo`*;WV*t64(K%;M#(%{L>k z7Gtgg2MiPiMg_H|ghw67gDIvKL%0J~QVMKzeB3)$Yli?F-1Y&%%9y7L{NII`V_chr zfuRF`E<4vt9#6eu6UtFMQFc2h_gBnd#ssygQiuIzDcA}65pOk-gJugQ46 zDifEv!+rL>H9^!=jf5u~M&(8*iM|IOm&#C3rI5pzZI29tHBg&G?qJ0#WkrZ&LhRJQ za>}?#xK||1E(+~(5)F#Fhaga*ZYhL|wFsyo&ZQKl{aKK7>NH^_40-Wn6)sRgL^%&6 z@dXLp?t{2}V@88_g!6~W4xlQjeBsjjqYwoT%`Z}VPsvzewji#fhhE3}c&5H81_%&h zZK=nSbhf>f5C9Q+#y|oAg59Mm+o)V7tF3TgaPv%JEQgJlOob!ZbjmZHbvE;JsTba& zv_Xz8#lg(;5mJMBL;wxzI=UhuW*8-mww_Gqd+Dh&sxZ^@w>j{K45OSIjH1}>t2m{PF`|iU?e_$yo}^QvYR5X$GTGMNW?F4( z7Ti@q%Ei%bRXDmgOhiI5fYa&Sgw6Yi=#K4F!WgqCU?>Q==QdPfcGVbuajD<5 z`eqpKqb_L%*aU_KmG`HJ*2hW@S_+pH{7clN!#bx6O*YQW?QV~XVbn&)BOqv%9v8Fp zw6=1n9nh>XsP)M2nss{&8#PHnXyCa*eJdOhVyX_&>|Voe7hLeUx$G1NHSSxhsB*g> zI=BzTf_%CopB;Ix#@GwxP`7PEZZ^YM?#B_u&N*uqk;DF1ZL3w!w);QVyK@Dk3|2pv zsxMLt?!|CQk2WpMh-R%Zpu(Qb%A(k8Xl+MU1AOL=CN+dkWxYk#qG|0VkJKF&KG;m$ z(}fBq$$k!8)2>-GVxqb;(`thR%A6>+&wE4I_5lhIt?IT_*G@QHUJ!zV;g1KsZb0J0 zMjS61@h(>UJ!R;sP}_-d{PTb4?aHBW_bBbhk+cG7W7*ejYBYzp8pu=)Zqb5nQmWn# z2sE-Qt8$gN*`rMRa1~^DrYp9d1BvOUQsM|-ZZqOs zug;2Tf%)(Y;)gytWP-oFp}dbio-BV6T!H)qac#J`e+?B6kJI;m;Pz{sKvI<<08Y># zA`N4cl5?;8n=E}b^D8IvE0y1wB+A*eJrd2@_*O1^nr3~|g z%-0KF+0yMez8SKj8~`Ir_!1Dyu=)MOXYBvS`5$fYqjS*o^HkFlXW`r?0L0g@p(Igo zG7cR>VY3;*3gN>>8)X167w#lb>V<+>v6C6udtbr$ji1Fv1x80Yf$U=`auL~=+U@i1 zt*6Ci>BE_H8L?||NeVO=QH7YC?FDqGf0aZSjh$5Q;*Hxa#EF10Idu_UhbN*7Tsn^5j(`(eiCst=@X`?P|A1x`OeN2&6Wx(z0kh zMuJ=lt`tw~GxK+SWhPg;redL#En0e6*KXnsGrdioDv||!E%!@H^C{|)jLOW$v0_IB zcpvJ~_*{+dw-t*~^Zgu({~i5@8nC+!s!I)jv|thXTm(vtxQ|*c3Xn!bKe&IY^|e}= z1|Qz1L4-(+(@rfxdgz^Rmq~OTtR5zn$oSDr649vtehNbc`K{>e?fJ#Tk`E(qv!FDO z6a3N~jS3}*Bm~ib4xAHK7K`ya3P8ago)*}uGcQ86B5kKxHbC_siCrM)x_MrTHu!w# zv&C=x=jHR6AZIdCgZ;NCK7-Xb%j~wWA@_`XYoL8#=^u0t@c@FBVvXu19Eb?GFpD=X zt6IZiyZT9PpH)5Aki!eqr}dw(x7utcF%zFpOn?0?{pxyR2yF%-Vk}h_Dz-w?n-Bk| z?u|o+|5-=&IeJ<%Wvi;6H15KNT*eBc1RoUIV8MwI+pGC>rxGIZ z67+XWfB-)^1HMc`qBMTIa{IP1fJ^9nU}*dqX0XQWwwcKc;;G zJb%Al_`E9le)tw=oW*j2;5PP>>TP7EG__`#kJ~tsXy9gAHkMFJJW5 zoOV~ahNvoM-&}hYg7OxI@xN*lV=>5BD@_r-6lwmqbH-I&K}!Sqx_|9f1$R{@Iq79+ zLBLzvo4`zbq|22r#KuC`>-Hi>c-`QD;?DEsvE(M>O`a7O-M$X}CxrzSr_-fBEkJUN#@{OM4Ukn~*#o!wx4C z{mm9nCfI^X+SvwvnU~bfACQ2zGIG;m7$j zzF%=c|4-iAZ%5l+1|oo8==|gKH&_`;5%mp-9nWI)b}jiqa;HnZJ71kweqzi%a8J+l zwKIsu?iT|QW#@^^1{IYP@Fm`oZEYZ;2wt2lrD5Tl2;>-Cz+B0~5b?9xNCCevgdzNs zc>g=aO7e6@JwLre|E`RlZ%6rGKh>5Q{s%OFl%NT8eAs{1-{P33h5f%Zu{-@=XZS+P zIjAOyVmIuZU*DDNWcv4)aN&VU$bG*uoBy>PqJd5fF*EbtFozWBez%_njDNeyss3ZJ zZt*H{{+mZ*(xK@2o}}5Oc<&?a3G2BRgZM4#doJMr%lNH_)dyRXFX)n3)-%)@g3Zt7(At&Us&mQ8xXPZ;#_-Gc#Is`lv_&vOdLPq{F>KmNt=4^*5mYHp(yzEk^Xu5 z*{s4{Xx7Mx)b!ZGr}pr#RRFl8ztw}EZ}jrFxmj^s=6$N|+ryv8$MwI>%;xXe7O)35 ze~@SEWqxF*r#A(W8x($Pp47$3Ab0>Y8sn3^Kjq%i>oQ;Z{LRarpKmX%YpH45#{SA< ztAFEp>+p3gH$PX0TZkXmXnn^BMsWmzNOn*b5e1%Bm-8+`atnd_+_=v!*ZwYsdoL^8 zx)>n*uQt!t>bsrJKOj}=sxCcvR~$i8->Ow{85fyk;I$cdVw!PCyC| z_<>~q;SX{sPzrgptlccvZubDb7S`TannIO@;ab};C$l#So$Avyg)rv+p*IhFODGH_ zRPWxfFCeZv%@hi)+_{SF;P-9^P$nTMw0&GtOOW-N?g5BR4}Ip`B-%KXVCpS)vFluEJru|k%)fhV$IH966{FR zOXu@9Oz zL}~tZo-lfHUSQ0dwO;_UptxH7onHBuEJxwgEY>* zU5ONdfqzHPP^=t2ty4d*FT(Lbu$HA*skz-xdA>d9o*+Oy5|6jv9(S`1-Z!3+@HuKT z%+9jfbHnJ+3uyXB(CP8={NFQtb-G$3Tl`w^>iH1TI{r*czIR`d#a++nRN~5&5P9tc z)jEezDjZJ~cP6?bFCJkMfPqlC90dd7@XeOm`nhS&3#nB7dqt!~umO5x14iAzhKvgG z5~TnKgGw~ff!T#1h;;?E>K$EO0Wx3|z!A@}8o@nyIU-XtI zlMdDM1Na)_dr<}4wmXz1$w;N*&(*LzZ;_RQ)|B@0^2UAVALTKaKTI5KmdDKu z)IH&WG_^u4d|YTcPq@rf)u8w1AMZUB)+X$byc`IKdFD2zD5j$Pb5?iL=T z3_oTWf13LdJ*@F`&`~v?#`Nd#H?oEthTWIFnDl7l(9Fqen-3!q>&uhXvc@}+jSbDC z4Oj&5aL^ahoujns=!)++m=AO!V~4im(w#ZOZgNA}JGNb(F{S>MzdUMi9aZJadQTsQ zp7-Eo)N=?P-q>#n(}l^;`1*Y4wOwIP6X&;?qKig;W(JsU>hkbPXGn!El|$O|tt%N| zb@;k~L!GzMaRSA{B@Q>1d(tzrty^z4JdPe7s*46dxtv97m%ASsIXd@Pu0|NnoUFj- zoc)VC*cWne2Fn=tHXoXtg}OMYTLSSjF$1E7Z7L|k#NGLh1QKr6pduA7#rkLjVRFW9 zJ?Ohl-PD-9euYh!1xPs<1m+aijLLg%BnVhx_>Mq67j9vCHv11PD_k}p1y&3G^!J;0 zAs9@ch)kD7S)tr%GH6=KZu=W8{NV%xtnU*7)Ca(8;;q!&t$hDt<>og0%%{K=qD$zX zYm(#z?{6|=Q2!=9n|Y}1+5#*St;A@PM1R`#KPMgLy>O-j zmb$1*$2^FX-$-^Yn7U(^KaA|Mc+WH$BEEySp9UW@X^<+^+det((YQna^MS1P zbr%e?E1I~N>FV+oO#aqQ<@CB1&alPvi~8?7HnL(7L8=jjbG3U3MtY>CBs6@>dDNp@npS_|Wf#Cv|Q9vPaF*7`acwKu@_^4JSaU z*Lz(7T8AV`RG>@YK7f)g6t!f`Qs!|ByP4mtCQ z)H^Agc>UZKn||aDh8izx;U+Hf2HWeLwQ~6x4mlqNBt|%SSJ+6e8QBoP;vhY8Hj+R} zyruM;q1m*x&|2X5cP^G_ceFC}S{c|}Oc82d z>xesb@Nz>SfW{+)co;Pug)2zVEe^~J$`&`WWVyMxikQxaH)Zlx=Dj+9>7IdBfsCf7phiNL?w*m~&UfF{fk-3f^M^q3pd1)s zhRhB8UAza99~uI1V?rK$Y6~HJIUolsH3;+|KXCvtKn=WIv_O1_C?*)8)QSgd6v>`| zUW8HbVj}WIRM8MY!YQUGf(5E%L_mNd1T##C44FMWia8(ck13hi5~*?m@j0B_#LCdZ z<@90|6V>3GLtVZ8e>VG9XMZAq)Xv4Yd5UkK9f9>`JZt0?KD+4forhU?zY6@w#e`Kn zmfhF`!j7b|>d?W;8zxk$&KVwR_HOhwb;QBXEc;m!Or)1ihXMXBuA3Rtf{dj_nnbrN zD^#+zc_*t!p(>C9d4U1+@Q`m{QWbNB#5S=c*WwlA$)pA3@Z%0MBI7UZ&2NDydFlEV zs0JbYj*@~g(e6>^+gle>Wf`WPAzBg^| z)$;kvDS+OJd1Z&VCtnWj&D&PM-B9B88(VJ6*@LO-uPhvOE0W-MJlx?K#pE{zC)r}Z zG!WPn9zCIABV(XsGC>8?Z?{JZR=4`%W`h+mXZGj-@DOvQ~{70{uY2T)~i z2{XjN+Rabjr#FE@KoIjF>YT@Y%_t+~i5oB|%Xf~gD%wtH7x z3;;V10eC>Oz@@{Fi{TY+?(sV9kZdWsd{KAb;ahk&-;MJuan}&ATD?D0lVWevHHu{D9qy5#aaBVw$JT)~(zaWAg! zFYfppZqH+b5O`tmG6H|VbT zEaw-)R0&T-xr>@SF@I~}@|%-7`g9qhCIps1eBW*MFQMb}x%aenhhq$1Ie%sVq&>tN z-`j(I6ECaf$Kj3W{?Fg==5x$)zb7-EenXCU?sF2;!um;5TQ>zL`*>9sVD)t~974zYEn!jpS*KJ+A_Ko4c(1%@w$;Y}N2% z?QLLQW^%Cd#ih?Lw`*q>D|wvXGg3oE5MX<7^~JBs@;Z+${Ej1>%x!lb7CPRmEMj?o zBWbwt`1~D>-G0SeO?_Wym!sO=d@j6~`<&~}Rf(Ha+pjsyI6g#1MvqS=1F@njkGtn} zfoHMT9hJQeZMR>QAtvN$U%wWEDK zJm9R4X!(tGo3w|Vp1L0k*08l74`YWK#*eXMOBNmUHw$>{D`TUjjM(CC+Tt&hmpP7gu)pYb_-=(M z{$<6ttETZI_dM&)n>`ia-fQpzmFG-p?eCxK%h=cB4^Bwf*_rdNdEPY*<}o-s*Lc0H ze9jI&PH5_T74NaWd<%`~XnTqyj=-|_tNyl>Q$X?rx9xrn9pOwkPtJay=?DeiTZ*Mle zS9Y(T;dZ>9U$cXWs7?XJE^Z~o{qVKo_O31n%C6oYuVTO!URmK+(%oN$@Vl=%*Aqs@ zTb_$OmpnQ)lbu4+t)W6>4#DJhuAF<(Jn0l=!s5Px8eCsO$fxL{=fiV@3TpD)yAy6G zae6c-6~~>$PB&LV_;xknU>^0(2EH-f^3O}D)M59L>2RiSr*UM;nbw=w8_O-En{HL- z10DXZTrn8$p+n}}Sj+dVQ+ttOuH;Nfo|-c1XlZZ4_4>{gMrTU`u*AXe`rYp{hrNBN z#n6wDZwT@jvo|}1&+BYojIVzli;V9N7gwd_-ejc(;6`R|1?=gQeeLblQk8_A9`dsE z`ER`cu@84^1Be&B!1p3wYgkb|i7mKoNoP2L1lT4SyLZslwWc z1B0zU`7~YR=1tn-Xl9<2>N2-xBg^Dzx=yDHLBbtZub<~`Xh)D@2I6w}uQ*c1w5?sq zLgrn2cx>^lcRJPl9y@cOR&c9q*c^F&8`@aVpjRE6O3d7)Pr8j)&&#^Gwu;A|a%BEz z1`X#|J+`K_>j$~O-_yIucw>bltupb`H7>*ps;_wbCA|JS{~HgB-G=5i-(%y%f8%1o z>SaCV^mnuHJc@ALwX1VSu@GWm(X8_U%vGe|Ze{!_-ok}by?t*0wCcN_9p*xVMpY|i zwJjG}yZ03vwdp#SHQg4t?p4#N+TvN}CmRj?4F5s)W9PYIPZx^;52@w*8;O@^l+BA; zf%FOLu^LsYH24%a+$Y!yzK*x8;N{0Vcuy+R&bW7Dqggb4%*iUumgalUIr9#Aa^Z0! z+V^P7Z&mK8f@*$jN*MFu+__3?{SL+NyHc*MD&fbs55mkWSwF4iTfFMp`T7vKqXUD+ zacvKSDZEJ7fcduGKAaQu-V8}@w!FMJUL>Ng$KhF4rS!P@qHc(Jd+#Kb)=W}kcH(1yPQ&aj@! z2Nws4Y^n0&@%>HDIQe&|pISe&7l#B7sSwFpt^8lomGPxoeG0Ee7MFf^*tM~uk47Vm zNAUYuobIoGkL-6qp5?)6@?r%jbMO7zw5l@bCx$=`h+6x!1lHPQs&(V07lRfGndw%{ zpXi^L3t|!rXTW6>$i7s4oXIP2eGSOF)|$?}A(l?o1`)R>Egr_5wn8}kl%KPDUg0N! z?HYjiTMwRJH-?m4a>>M_O3DN^utoDIJNo@k|=@`sKEA$l2s6RGNgtY2pX0!Yxvte22& zbXoMCpb4fx2~7Y+Q=x=BUsHkT*P`1N1G4w$&5AjND{AfyDpqBpZmap?oo^fQW$Xw2 zXC)rZ*6mHz$9iwDIat#2SalFR15zWpfomHJZ+PynxC(ILx9u)_U59)Wx7@<3kTNyA zvU>I#0tVb@pCsej{FoA(iWzuyFBg`x}!T*t%s1M?#Q%>Hl(}@bh;eBxurFSmiZxnLDn!Cc2X3U9B2Id9qMq}DqJFje1@xG3g zKwnRkzGX$T%GcNBi&4T@Y&@4@V}-yQob1j3*1*%9J=IzlO}%XBRERUGQADAe^QTm| zpYk-7LKUsvjlv29`7zj`JJzcG{kgn~-c*Z$Tt4LCy~5{5eZ_^fZqHAtsxx#hCfYlE zIke=kupFFbEpzV${}y|?q|TnKH)7J$8JAK;-ShhcB|7vhTj^WkToTyQ^lOe+uNE_h z4McgD!=EUvCDp#A({WMlK3Ji%!gU52yqe^vKo8-$bHJT0i~zZCi{=v(3n z_i2rhHj!f1xZ7Kokv>hbBcAK$m(eWm+Y32&^P{_=k#g^TRQWEm=g`5RE;nje*?|A2 znSB|<%i8OM3og8@Zl#v4gt*u^b>}MNQDWzUi4-6+&{kZzQD-g8#g%n^cM0`rHfEgv@T<{ z-Mp2V1^BvVm~m(+FgiNiJ*&*FL+6e~mtqW>_fhAySFFB->DKlAYndB#*_Ru>+$xah zv(k_#=u(dNMb{28UU7AHxH#DV6BF0X>T74`e`Av z4tyyd8m-s9$nAL4IGh)^uaNOsSK5;v)<%wZI-EI}vqRjC-V(1Hn=O}at}WVt^B~?d zGWBL7b^g36Qg)|4u>KpExDS;+O)d8DCE35$d1&R4T?X8;?JU1q4-$RrP-M`)saB?>*e~^8%Y^3PeWa))K1*v4 zF;RzBFyUQlFXMD1yCJ5$JiIxhPFlElGC5tFh*Bg~$D4=7%h_qkj1*y7S-BJ)1@U8! zE86#OdO6d)8<*IcR`>QFJLaJUw!S0Nl~((mo3YMzC&zLG!?jDYbnJ{4^yavwJ`M~X ztm}-t|MSCItArEjXR3Ro~4E6Tn@1xm&F&DJZFl~ zzykhP9~KRay3jA?5uR%PL*S9zzT|ltHG8x@T<$SZCZtkHnE-{8I`;TwiF zXd}^?(7S2h-Q*3*Z5e}E0S1ErAdDG``vh4L zF7lvmTYuX=Apvv?q#$#dtzBz%{+18F%tN)0Xaq)wbS z-o-o(9!2)|=GdaTj8k-mk0o?~*QZl(yIa&hGl-u-$G7#A`Qv6KOT6XjjwQI_Vr9>E z3W^JqEpL~R0`Hx=Fcxmch7POXwb^cj2{;`+?dt)*e5^jYBj&edMp+sZsK%a6GSemD_Vv!mF~9k^3SG++Y;I}Op6 zBF@Q?1r>#E;3ufqAem2_^9GGPXk(#IGCCB}s5&VrE<7`u2YK3L2F z#WE^}0ExRhgn+1=8Qe>|SYT{yijGTbj0Dih@;j5Gk!{F=LP3$3A~PZdOFyK~N6EEd zX$G*#A@&S0TP8d>4AM-0qQWo+;2QwBL(AD-#b@A%#3d_pw6Z+oZV=-yh-PHbtL81B@g24KLH15{>aEO!{#383os z=0eQO$+YNMc3c7(cYs;=&X*cptlV(wqTz|?)i@YfQWchAEa@>w)*&#&<1S&2n6&fm z1O|NVx88qWS2BbG0kQ=T8sJ($DVbgrjcwk5(#~^6UIGA+( z-fxX{{7)K>7VQE7(2Cq1zL$^6xsUxHFQVyp9bTsAXAgJyJY6UI8zSR(K8L49zW!L6 zSw1!IM{jD|(PXvqt*2U_frV+U6{5enLYqSdESUiVcb=D^xQ;Vl0nfq#=nSYb;b+}l z`}OnMAV6_C9-avs?Z8vU+}<%Lt=UNb#c(y%UFy1;BRa&!z{nP-?6hBu5(Ffaz<8Gg5$ zx(DiNdCwq*SIgTz_n+K94`Blci6+!9-S{U$<6qj;crq6{`BkC`;{I>w{$W%WfuZ`%T`w;`%)vD&U{MWCW3uo7nY-ui?87S)Ie&<2>z(a!Tt=nYTzB zk4ua4H5G-@;qYVoe{n<0ZyHZt^0DwHj@;;UuMrqAHWX0ia|Lln8q$Ws!cno+ zXe1<1z)24v>6+IZyR6H#k#C8iaSh-rmquU{MlAlm{Hu@;3mP~c<88P%Ms9)t7M9sX zLVcd~tRwYZ()3*M7s2-b!p>d8`~SS}BGx8G%Wf$wqoYUIYL71u=+|nQldDrjZ4?Uz z@1sH$x1uz3Fzv38;DKBpp~FCS{%W$8g_?oPFp~j;EIkX`&Nn{$PDkIv<@wI%46cLV z(uQtruX4`C*MniejAT1p^c0f>>fZyyZY{@8AJ=Nkgb@;gq2=+GwYWs59@fpe!x~xu zcwkV~(mb$-BSzy*0sU?p{yi=y4~LYl8m#(Ha{29z7~V{DW1tLs%K69XaTn5gZDqXc zRxkQ!dSC-FaT|vD(jl%zuPe`SS6`vKRp#khxOcTT9_GvY8IXJ)rnQQT23R@!p|pvn zIU`FxeQM4U>YV1lDYR+i%IH zzoUAFh2pa+B*6>DCj5we3>TFfpkn^#gQA9^E-~_^QP*{p$x$B zy?&}np_A`E1oaA@7oCJNXuK>m5E43Y(}M5C%*b1Lqi~w09oi$O;F6cpcIT)T;fAt) z8m-^ih1`08&zwYt1lGU=Gz zXAu~Z{f;T}@Svn*?4hglT5CGAU2GOpartLL@44bOB!lIdVz7?)iW-%{H?LN6&8-8O z+h{NH5fjjN8-7~%I9UlBC;fM5FV4!H{C(MdU;53%kbKUPJF+gv7lHbAjBJ^MSlK&! znrjO=i{ax#^^F z$Fk-xx1H4Bxh}LpfItL?kqLXZeBUsc4<>NWo0}_;bXLknq&%US4}$Z}F#PzY*@4jE zJMs9kc852(c1#1w_@K@YZ9=iM%uW-;V!Ifi|AL+W+FGtZy{(GSe$vwS%5{5${2Kl|=zrdN=9MJW82@Mab1s7`;Qp}ZZoPLrRyzO2RaxB) zNI{$CgLv=7H~mpsf;?oR-qY^6d>w`f`hE+92&!JL@I6EJ7Z(2s=_ijL~RAkMZwNnDEVF zgg8*rDoZaBSM%*@sfX&_`%S>?I-dozlDO#|ZL!+DGPVZ?zZ*4|Y>G5C)5|ocoBQ+A zNyYaTu@?Y&K!(2xt@|vb>0j(3gbn^bm-Rp4V4;zf^0`8#D-6*TQWKq(cS)YPmO!E9 zPZdYsNKcTWA>OQvC~sDc}3l|+VL&t3`>o|jH}n8M^@ z3YIe@7PzMpi7ufIysY_?BN{SxQ?0w?sx*B}i{~810K|{y{2DBpY}li*FvxAXofE-Y zV-PAkt?+K;n;K($SqDroBzfwtDBQlP_ByA8hlbBbpZPSlvuuBp7PIeHmqjew5AV#l z)7V$8dl>&aB&y%OUT@rkJl6-IDTpX|$}Zza+2eGj2jH&PhF+N$a4gVIKlU(@)7#l| z4@YS_NK6-b8&C4`@ZbIq@!w%fB^75u^K-79HDAFgw3O$;r+&WI z>Oe%g5fOy#iGkAPZ|bZetaOb>h06#AcZ<`y^BP=8{&?XvE;W9dgRn+NmJG~0Fu}q) z&Skknnds7z1Tp~ja%q;&hRAf`w;|nqbGefb2`p0uUQGa77m**YeWn)xVHBa1_sxW4 zSazDD5bpe*4y#MAHc#WSx~cOM27c2z7SAK-Xs_qhKZb6Q!a4tHc5|Nlt!6F6?037j zx>pxuoW`y`318VFm^)~rpz9Z|-mj=rShQM}p+@&O-yOy~hb$RERiN=wH zxHBj(!iT(&U&@e&%jf=`=Ehyt{5IeeM^2JXiDKJ~I;%2Z&68YZaFVr4M+sKX10m)_ z_iozJaWw9;I?h(8^`Y|KICD1b5Lk>2gMHS1?f)WRPn+;$lL>z6!+e5fPTl!yQIyS zVp0euE47N@G#Efl+*Bg=f%3B`J+-~u^SIr)=i0_=lLDLt#@?T5)(Si<{?7cQC)V77 zftmRmp__YX*oU4vb1{vov9`Tz&VE`Z*~f`CCP->aj63j856IqS*tAU&AZtOsYc`l0 z_ljJ7$#Oy^=+Y2_<=YQ>0j!qXV3K5Bn(gvUCm+#VyF;nw>byX(5p@j@N zVyB_rj`Mu;24tZm5Q)BetiFAp8v1vIrQei~Bdec1)OgBqmePa#*MgrJL`xZg;W`iT z9^J?#;8r=+{TFXP$BlLVC(8!!WXyt3T4Jy9 zbkfWQS;a6cPhxY-#2ue7j z83!U5Lx;G&y7vrZ@F4XLY==sO-+O6WbhT71enuu`%AME3g^ligor?Y|M?MqR@A+2U z@4SGj{saJBhNxwmVZ(W@=unde^p4{o!&$`J($i1j>NJ=>=jEzZn9W!8sQcVksaD7S z7co<#lTqO1a(aDcek{pfrv1BVJ(iSlCZQ@qLGh_<-!)F1G%g$=RSxybkutI}ROO|X zCH5ja;wWWlp|>&PLC6emG{-}=&;h=2P@0JEDDDumrYPb}T9sH0Ry(0U z@S0x6BbzdUEd*1*&IVV%Z~wW9T#0{U?kjzJR_I`p5aGX#?tkE`dQQ+&h=dDj{dWM7 z(|{bLHbgJ4@2FvN7s9^R;yC-o`q`@-iRj7t#t=q|Gxao`)LBE5um#s58Uk0D0W1#e z{3r&-sKgn*n-uiO;V=i`P=Il zwx;zj;)m+aRm8otP9q1Ama1M4Tix^E8Vq())EYp3bPYN^<@~vG7$}itr3N4v!=Hqm zs>lNbS{)&Tci3 zlUVL8^k`6Lf?;}3AsnZR2a_H&h1Hq(Kb9k#$dK{r^(Mc!mz!g9tRIUGz`>59;NJiD z*xwzVg1Oo^9D9FL4W)Ok#w5O2v*lmiu8&*ha_|h5`{(G$c&lD;t-C9WFDV)H6e|M**( z9mtUC+e+?Lk8RR`p!4(`r_PGQ_tW{9ZbPTBwsBuiDzi402-r*z3i9Aptm_!@%O z7fltLuHwQ%##3<#6^FN3D|7w7|4;8+#{Av~a#Huao|S*4=KbGS_aN&@ML(!+hiOCz z0W84c+2HyV1|VEf`{z6Rsep_H)&k9RT*6x1=~i9mOx+UdLKCcRf7IjguY8w}{#5AV)1OH|sCNf(wBoc)CpLTh8zHbc-PUle$A`O zvKvE=$D$baN}#`e%!_U^I*gSAT#a}j2YBy4R&(zHVve|NndX#6=&-ZN_L zc0FFi<)3SJv#>p-$1V49B2w_ICSmOw=W!L&m~pVGFCz2tUHsD`vw}Z*i2axK@Ghh< zv}h&^mSXCQjtn*e;{k4g46Z>I1aGP-0X(BKcJ^VLoV2oH^S>w(05lnxX(W%_$J-Pg zSQ(MXeUL-UcN%47pIZ%#%%_>LPn?qNWKi|wdMB}?KcJ_azj?JY%5s{9sL zwL;O473+w$wwvoE5-r#|CQx>}Z7do)ej>4>+H-a|cV{4Q_>PHr7<5NCvF^x=_FX>< zhRi+zy+n)d#u@~OVY33f{P(p5)nW`D@tsTqqsfH$Fc6A$YS0uP$sr(G)mH_{aSRob zAY{zat!V9H+*>f*;L%ml4%0`j{2mvgr!(0nE836=4yHQ0+S=p*TJAeO4aKq|kRiz; z6oCPkB2kG0l)k`Fp$!INJ&TeAgR3`{;o|wckd8k19%sftTHga=+oGLbAe*fow->f3 z$)~n}S=i0xheLX>vqpf!84oIU;6D0Mg4{8cGp%uYm#56t%U=q{aA7DjzToz_i44p} zE+-{rDOguIg4lUn46hl^>%C4kegLuG&F9lA+r5+&*ZYp-%U_^YvD}V=#5BBX@nCZX z>fnovgkWyblZ|qj%7(b$EeR7`s}cN;VSn3dHV8a& zk}QIOjWPUxXF&0EA8)oR#flz!Z0L6A_Z`n)$R78%fs8z3yFl^vQH_z*O(T z!-0xI&3ofD$woyA!s4GR$*slPm$_rqUzh736oW(*XmD@%xtln<`wPXwy_1*Id4any z$b38jJhKHMrC>8~yS&KP{O#}}!pPPK;>af#85a&T?w5RgHCq4x?B)T~x0+c)2DXNX z98LPRDki)OPsz%>>^j~mEO}{YUbtIDR{`uXpt#$>8OUDaSl?jpI^!(vyQv-ZsW*hR zH*51bd?_8yLx(SJt*&FL!b5!+sJa2hN$?I2lbpz>+RQEW3^bs?CCzaX(cp8oKs{rO zAZMZR-Qr+$<6H^#X#t>1KCXFq-I@_~m|=kfiQSQQwAV__0dBLc!snx*B6c&ve1WZU zLtLPUP;9i>0l*xI%F5#akWM)r3X8D7n)3MvYhutMdc?qWPQiymyX%Usx7nruE=CY(xwtW$ZHi)WO{ zK6E&fUvXZ_cJcq3?1-OPd@@|Y%NkBH){G36weQ6mV4{dZLKSf!L*wvhg0~G2xspYm zC+GqdIX8lDH8BVb$+}c3#v;?g&k`ENB1D4UMPW+>>ebFcGDM+UK!T>viKITCl(KZ_ z^jd*86sKt}-O30!+GXz9-ej)Hx@lt-BEkrbuU?*Kj5mr>mS zWq=-4z1zw>CSptxU~4mHF|b29xfA-BYzh|3ClX}u4LqP*QY|qgh_C6~M7poL)5p-K zcHTXJ{(LYXP1Y;R-7YqXRMlUkewONM%-p@PV+ayJZk+PA^x@y{eRET4!InxnreSJHy6TE`~7ySM{z6+O&t6hwx94t9_qPcr+KE< zd$>cTuXC#l6EIvkXic}_u%I0%@H_oYXAj-hJb8PI=?)cfP;Yc0kb-J~+_lrT0eV@v znYR1mLLkt#+~&9e;%D9%8cZx@!~yP^-XIMyTfw)~bWoxwZRp1MYq4He@NkDj*9t=LJWTlQS~ZPXgw=Fd7n5R%r*J-Kh(8 z@f9|etZBHiqshI$AE;%w?jU&Z-D;DmL9vAHXjc4h%u}yMp!HxXxactq|Z2KE1v?98m=lvtIbK!)!c%U8wM(%40Q8HMqIL zJZjGpn6~xDcn6{I)KU!sP1LIqEQ}=H#>(JpM~f|}yWQt3Tlu^StZBoILKK6~v??#S z@ZXEb_4cY?+bwwAa(3puv@YCTqsYtOJ4$`+$f7;9)}k=d{WjUBMuzVCoWr#-cwJJ? zJ=+~^X|=w6&c8duuM|?&Z{LqIg*!Tx1`bPAoURfC%Id18;edJ?E^XuD2Q`D4QX$cQ zVOShiaon?WkcqKq^CqF*l_=4=ahqmw+eCxY@-&>{y>=$M!~d2YTSbo|+djzNgyM6H z>rSniN0TNEdq-B8lW-tbZi81iIF1~Iq`|qlbq*}6dHb;@ zSUK4HPF=crJ2v8OED0?!X{_Z_Yw#~?N0Q6EoxfrI^%@rl>)C|km-;+x26*E~UxSZ* zUC#z>Y)JC_#qkM=?nC3&)BV`?&fjl6*1kH2u|s9;_hR++-h+&ZYQVnLd>eDe!}K%o zCmr7fC)0xB8_5s$<}%&&IP+g=NpSDcPJ8S7%6pqts!!&xA-9R#dNKq(2h=clR`L%K zUJ5kW_}ZHg!ogfU-IHjuY!mi2lK4YzQBs{%@{#X zv(cocgpLfpa?&D0Z0qA@v3SN)3E05V1Oywji>YAaKeJ+dNLS3Rn@6!|%)(*-9&}}? zBa?AHg{7v4AFd|3hr;pw72ZSC;`gXv#ofo3LT+0zq+sPOJ1rN|?YD02@a^y}pEl-2 ze2n{xt4+&Su9~`EHU`c7D_6{eP)Cks=FATMWpGEA4THFb$gupKQeO;ZQW6^hc8d}t)yd4 zZoqGKE_;1T5wUAV4Ea8US)qhv%3?hCP-DCGYghNx<3vN~JNH$uF(gxZ&o_kq({~+4 zVqPyt8nb)u@^>3f1Y3!;D`{Nr!5DRRuypwl)^PHga#d>iLKWsjM$N+fS(mU?e{sfX zrd3*tr7p#Mi)b9L8QU~Bs=opxm!5*5;YF+B_AYWJv5NlMtcSCxKzUti@ci?oN8|Mz z*nb>5fyTn?YRp;-VW_n-oJf-e#r~VGH2iU*4YzgXShc!xYoaJ{0MRp3{6DM{O37v1&iLav?=DJU4#EbJXT`VXFk@?$)=m z?cHIAD>~)QPt31{Wcw6x*0-AQJ`&gEo z9Kq~f`V(sL1XQwoyCTqd%s{BC-S3=40}`cGRZG-8l43z$OM|&T1HkJ!RkC}1EP9pR zeBM*Nu9(mqa$Zmk84(BWa2{|tA88@)p|!$hLv2RE>2x`5nvW1I5-QGtHy zea5Zbv;7~$Q>&cJg;pa}OfR)Yy;Wb35>!tA-)=Y_A2wYx;)CMJI#f zHj*gw5KRjak(Kz7=GjKhGrjAIi=mB}sS1djMLytHCLFIJ$%+viIg2B5J@j|?FGs&U zI@K3md(L-*{P3zx_F8INZaY|!u;I@yIN?&0=(BwRr_@^OFzfJ37xx(sf_=UIlP}1J zzwRqFd8q9$d`6{wGDV$9kw{=S_soM%nPd1Yi(ZD9(E`^0tg;F}uzbBw+gI*;*s$&H z&(e6Tes=#8m|hj`>t_BohC4ov|MHd1drzc-Th?{8P8jzbtx*+^Z_kY0?5{s-@Nm%$=(p1LdnQD}E4Z_HAYA_XsE)U(Ohlq zs#)3Jr&e=TT|Re{5z(C5Xo0((8BUy>s9U?3v|l=IR4H}vSiNd)6A|Ek7kaF|{k9>r zf_3{SSAh=u)h#w{b*0_p{A;buD!&ivPogev&~gznnaViR?$_vSZn?J@x%qZX=wyN2 zL^)6l{q`&ubE=_Q6tgzCcZv$?1p&6dexVZ=S;B2t#hc3Vk!`07Xl5BuyXN!d2c5f$ z5Z@qY7$(dkk$9O>h}|V7Kr;dQTp5`l#^K|LSUY>sMg+V!GvX;F0x|@D#g};w0o%(DrZgeBVc5rv*Ut6ciW>dt9aX7{lOL>Abx+dzQ6ZoY_OX_9yWz955OT6A#|(p-yz=kn%V-p6rd-f%c0u&D&0&g0To_ z>(aj60B?03uUiDSrhEn!^(MV-%!?X3hcdZ#n}3nq{|Zma8GD78l7v+Xxz{*i={<{M zO?^&sq-70IrE_^755P3@borT<+%|g_P|aQ%lmhXkXWW+$vEACn;u%gEGN>r%GdwWR zsCH5hL0<>=pEVH^oydl4c|ln@?q7*U+K3!Pbo2U6vg&5(beaWj#X1wXIh^c$?b~Ll z-wK2*txr!Yr$*r;mFot_peW_a!E_y2ye-8z~a9y#25cqpOu43;VNOKd6zk$vOv zp>t&b`@0-VtU-}s=E;s|IC3dHamsY^Q-fF(3U4m zrP^U9l7>5Q%W97L7cbt^zek3|I>jE2H@6i1hwG?!o9YFOJniSk;g-7%=3y}?YxqkX z*4WaHwJ7Xkq{R*DP?W#?8>D{)?Hlkr3@eM)N^5trj}u_$7eM}u+YCVK_v)bcKE4xLD8`e(p5e()e`l> zvOcy(S54)bC$~2(X75-|-X1pu)I7-jB#%_&jIxuaWR9fko*z0whx3c>t z0CiG5gCfLFCZRn%5*?ssK*U7B#Oqq$RrF{g&+q&Dl)xCIdrMbb`W_xL5{4UBIBISx z>7ZBZHAhb~@_9Fz#6Glymik+>+grG@s^TzV3`s~{!Vj#cY%U7~|5>{r5nf=8yf;)S zVjp9(a!0oz+Zbf6#)&9kW*xZs>@Ga_1TCw}nd%+!GT4xXs9=E5!JQ&-?1X!e2I7 zud_Lk5m13OuGek%&sgux#rj*^f6ith12uvIF597N=57hR3ZJg!BJ6(su zK=^EHTPmgO>y2E87bf>rKh&AWDOB015mp=x;}gy6iHR$e*M_2@@RjYTQ^!Q3G1i=& zFWgl;n`eInPrybwX2^$+t5)E8>0I(9&2)sq^W*v(BH}CAP=?XEh7cP^u)o>EMbghq zMe+FYeN|1rdf`=lyKFaZcJ=kPHU1~vigZ!nJ#!xbC@$2o9j9rC7t6$z`d_uzc})Z} z8Gah);y}(mfsa{0A@q2BYgu`;!IbnMoM~fvJ3U&$-`Vz*B)??ISt(N}7`9r%^OL8M zW?(eUaQ}`GF4fj*i%ze`SZ z;R`}*zRrsTJViZ2`W>VZGZ2b-221JINTZ=jo#}7Z%>K*N(2GvCUUOD^itXlGJFw zGT9CLnD5NeDzDtk&CYEPDkY~$W^ekN~ttH-e&l<>iKTh^ej}~leZw+dc5XW5dC^< z^;nZbrH)d;JQ^lF3%R(xdYV@}&o9hc#rK$_^aE=N|?*ckkx34fM)ceU9 zd>q*Dt&a<(R_VI?+I|If$ZI_Igrej*jf@#3?3m}D>CWW{Sfl+tKdsAin|uloH;5!~7(_?twac=} zHnW?Q5m2~8^cOvKSyv0r%=;Wy;z(#4C+nl;-f9PMVLx$eI}-2@Yi}`mqJS2araDdv z&Yop7<}?J|xXFO-wu4+Odmma8w!N-(H&|V~+;`Pr#_3!)Gd1%u8h%t{#c8uo1($9b zxbo*^=DB>vbKsjV5RIt1@$o4s^eowxlVjeWHk5W}DO7Z7u}P!(_Ug|?H@*T1tYQ9T zrJ|2Q4VU71o&+wtMWFsh=fam$ zFwJfKMJ!L{YPO(t?#*Ty-TvL2cGqJF;3ezk;6(v*B3^8&_mPvy$oX@5K80uR&SS>y zE%YN=F7`~^cMB{p=OLWdzGW&4w>1SdWL?)o-=%T>-iq2aCPBN1yV}WrH76l^Wwb9C zV)gQ`8v>}#&*)=s7gn0h$!E~r_j)Yqy4@8zVI|qu1ysj1dqXzx7(aQx| z6_HeqJNg4h%!Dl0DrG7EwL|efOMJYaqql(6B;@nELd!jOP_aoee));9Th9oT)eh!ya zf=5%(0>Ua*Y>LW#qj~vwzv_cLB;@6as4>HehH$8O>KqXFIvt@KEmmXds*)3#ho&>_ z@Jm|Q%&pmBclJn$lb9tL8GvSHOc<9b5N6uSj=Gy(luq$fuH+Dv~#l#G)k8FvmV>ho~@CGj+5F)xbHzVA+x>Q{RPotk&ID>riCJFFz& zOR~wNa@%;i_t&}a*AVriyc1+qaw5!K;-=)^fl_B``%^?v+*AHh=-xpe79P@}$D2!Q zZcI`RYUbKRt-H#+HW3_fS9|guQlAD$a~af`n$g>=1qkurvkAkiL^Ja%^2RNzB7}X5 zR|?1WeaF(93_@__Fib4qGJisigw7ZCv6{@1mn;mch}U=CTL2_bX=dLfn!b7XIdFQn z-DG4h$)1i^2F2cR=`s9!1x9qHS-lRBtn+d!CMFx7%gKr^cq>e&e4lPeMBm7rdCf(o z%66BF!I@;&$e&kX`!)O_bxEG1%7!NouQ#|N!QJ!v{N=R>-5PNQkFXsYqsoe7KYn zi$Q-Qm89;Y`6igq+&(gm?yWhFk)n6=-xi)T!5);bhTb94tr$w9)KvIuHYA0;O^OL# z410wRJ;%@Y84+yNkg>jSp;A0iA;SU{oEP@&se0|M_NvbegQG1h#1L5AV`Gg;6fS9U zY&@#ln0AjS?cJvBTs1$RG6jpDiUcUoUNbPD+@p&jneOGP5z{5hW*gn-&)M6Q5*etr z%`)oS;wx^;FKIPDTK*U(`@XnZ*ZDFr7xwuX|9Q`oVeO#2j&R>$+ALSJr5Yy**wNT= zd7~sK(qU-L(tFQ-x>&_+?joib0@j809ZiqjcKhm|9lvx@|S05pJZ{j*Ug<^Y}8`EC=_jYa{%H}MB(l@F4*AIqHq9X{+BH#O4 z3MqPw-9y;;?(d^TbEDV5o)qfuh?Z1}lwc$|wtp)C1LAGm`e zl^A}6^NCZFX#JRcB_wI0X@V<5#DXBunr&gMv*br_-jRmK$L*kNR+g8hPJbxR=5*@Y7xvx=NAy7GPedDu;>4Bhh*eZo0H1Z-qfFv29VNLoLES{86h#bH3{N zo-B6bHWU!sm)QE*C({oW94+Dh4$XBqhg$S2Oqn%mq{WAeHIZNA#RZ6x*_vgI6<;-b z6o*f*QfL@uenGiO-0Z}j@p`PNTlAr>q?-s{s^ba%3PDTUoG1^if1S~z3h__&zwy5~KB zD1o7b1}4Naj91d-fZi78JE5BEg!}vDF!nWs;^A>{q1cA{+4VjI;M{3qKbxb^X(Ahf}Hhs7f$}y3Dl*k6=AOskCYxeY3zE#r<6a~=) z=oy$fZK<+)QhlOG?#o2rK%oCgFDOk_x$9q0jBeXdx5Dt2Te?{oHHkLzYeOVfh zUdYvvMc{3T%>Kg;9%@)lM4cp5(x;-=eJZ?Fd{<@OTg>_XA7{rktpf+Pv9UV(IFe*S z!KmZZW{5rGa)F9-_y#`nt~{;goMV3jpdYy|8xe}FR6W#qiflr7GMy2Vf>v5QDZkmq z+U6QOi$5|6t_}I=F^5HENsv*Qk=I+bd70WgGY`&_TU@8?-0E51kCDIdS-T{Mc*m7^ zxwG?Iy7w7lJSJ7kE7*iZ{|4)pTSf|VA`mPdizVnqFPFTF=Qoq`dkp3D5Q3J5%ak{?jhAsMjo(fgT^~jpKiT8WCuQaIczAkx zu|g&4rFP;~7`;h`#h99teV=LpjN0Y1inYe|{r1O#-jZHvR{u}dsVjZ9cq>n$6D;y@t;Wdlkg$5=U$^ib1#DIC40?C+y^->yquyNO)IxNM~!VZ zeWRz`buEKMP?D@p>dTp324712O88;a;P{MgzjFPP@{gF_)JRj@H7@1pRv3I#uag1a zA(sHnfQNL{l+SYS2OB$q@GkgW-JCaUReY)BeFZ&kBK5^n2vb&8I_QRLCKkQ=(pTuw zCD>51q;gR^9Z5VbETp+?jf+TcS7_>=qR-drFtZ-)%tu^Hc-OG5wV-gasKvc}hvsxD z+NX?Ur*AHLa{$ZIh9+R43m$v#{J>MTu9Wjt>+7fPpz7SL#-|qBeDuxj_nDHS#Ul^f zUC~;U#Nj?m@u*)k-N;cV1vR$+Gvu_shQ_K^R@*e!EPSge>SSb~_+JOiI&R(eYKgZL zH?8t1+tcyoM^UqU1=JV( zM{!Jy1knG3RHDCiWn+C2by{@!1EVEDgZn=A$A68ST}kQ7(AcA!4h&NVV3ORSZ(!4j zDcVWTMDY1$rR+()c++ep zXSn538sW#js-?^rn2seiK2&jJorkODZm|+JrNL=wxmw4bOZ>rjuJ{r8*^zn0V zwwA9L5x2FejEb#7m(&ZIu-Rh7fSTD4&J&x2U(r}bl2UlpHHT2F`BRb z>en6px3ID=TSs2yv{~<)v0@&nv_duUS5<5E-^t5Chx#^I@#1ORsBWb#4TdRK>d6K^PLwm7mm^FBOxRA(uWoo7_UVn$`;9f~>>z5Y!VPXiDSoGnVU{Ik@l*tU)< z8nden??;zbXH>3^w1~81(_g!*La%x|pG}p0eygal9%UmZ7`VV$;243%PFTpGK8)zv zE6H05_@{5zLYV6F)9oI8i`5gzfF*rk#qT2VS4U%YT>UuZMtGc``#n#drU>&p9NVB! z5!ou)R501@zJeYt-&KF!Da7YzScC3JDO|iiC(5p8ee=Elvh|JUG3y{W3|V)jas$Dt ztxmLE@{JTU&S<(*OlILEC5lXRwZTWpC$ABQ(Rz*=VzLWBPcykmKgqewnGF3kK||x8 zp9BC-V^e1(Pm;^?D391=Fmq+#DMThT_W4r(8yTNX6)~FNzW+k=d?=cUcVBr+KAzz- z&+2XcbP2A!Br0Gm$N0y%=371R>p^OA0KJNqw<%J=ipv& zJ5A7~viPJ>jYOJu@Ys{95o^3};rV)exSnJBN0&#Kx-gaw?v3%Ksmu}NEh=U-HAQ8& zbynDpxXIXA;@KZA@pml~Ftc1X8M)Jsn?b!tSPAo|LQM<{r)S>J&wR_(aG@o_+3m)? zQ;vN3Jb7_%v-;k%6tSHSPi6mp!q^814=Sj;zdkB)Sqq@7q)XsGO5|Wfr+V1bG7l`U z6XqHR^T1sr*#A?x&Q@z$oFwr1;_rc1USv^=iIJeWc~kJ@`bHwFJ#5%?SAf}uGjF-X zln}OyS=?54ngl*yE*w&n$#j|-OnmR{P}SM?l&PM-LRDfN_Pat|$gG(uXoWqscRkNn zn=XtwtX$D&9WBXENxo-0VlVP3gS_ktG%pLNt7kj`4}o` zQ4N?kr!simCM?W2(p@y=$?%i|oOu;aJ6f}KZ`cf(?v?F%-N?TI-gKDCbt!l*UOE*0 zCn~dZD5-2Y{@O~5(x7TG+Bpf63FPNitFj;){hK-x@1hHLHYPaC<;a}+@x$lx(z5jC zuW3>EZkwlDGfk|{%AspBDg666D=Rng(vV!4xLG;p@F~#5 zrl{&1=grfrM@Ctr8<3}pFR8%QdLKn;m8=XgDK6?f&?GP#s zUC&ZrMaZ1mG9o8JrMoIM$5Pn|-6`qqGKgq#E}SKoRIOxV9R>0CqBIhI3uG@Q3=y)SMfS#HMOoI~ciNXy z7`Cb)9S06ye-q`?Y|VOOgD2>1ZkZDnUCQ#l-LgDt8-6~lx5el0@o-PQ`P^^tD5U2O zsq)$o5JH2nkZI&~?+AE7Xt1&~=y$E1b>T=@>mc_xW}N3>s+8XKVyYO=+aKInv{I7zod9s+gxhRIyPssJDr?*kO10KPIWfM_L`dRuMl`;bfAy|G` zZ&TM;QD%C4ewX|h-Q}1LFs}-q8m~i6U7mDkT+QtvDV5NkT+3(>I#1X$L*)2f{%WF7 z_hYY_;7r@Jz{xQ6IELD* z^R}5{bsk(EM&%I&y761%!6)sQ)44jo zkaU^o+mr`iLcPnWXunQ3?4H#frg|EBj9w&>s&Qm~uG06g(($WCnJ>U}(o#`5Nc=BR zi-RmSHB7<69B3bc3XO^CrIzqYPhXVfa+sL<=D(Nz|1Dz|U#)$@7-Y=Mo`i-h|MYuQ z@9%h7;09wXlq)#}B9@~p$fnwiS-WfIJ%3F6_vf0dlRBNGY2{BH$gw%ttg;ek|BoIq z_s(y|o@mRH>^dfP*A8p*BnC*>glXN)?wi*UR~hCTH{!}k><6<_@rGv{(>ghD{0jJ?M~2t%AaBs^;#9zpx}v4+kLv7 zU~R31sI&3=VK*?goykuZHTAZZ`*wWkhsQZZ-18n4$RUj|?7);tXF?`_4BBH)>`I>v zfmY$)SP8JduCZehKi2<0bKCz%fA4*ccVp51BL1(<;SXWISK9tR%a;R`9)B*_{{)hX ze_Zj7R)q`evb7s*T-}Rfx<>-lg~Rn0!~L`12Pjy(UTrJ*pNB@*6vQR8RhxgG z>wf;n56j4OX52o0`BQ~&U;TL9+_|?re8+J^++p-bhln341>&F>>#niT!?w0yXr-h0 zH1gwk!{*H9DLRO>pW&JwdSBE1II z|ML_Tl4UiVp@p-c9+&6;|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0-UWsB zo+){#+LQ%QQ#F79G|&K000BxWB%44&fKQ`J1wnHgUzy?Z{ zk9U3D-scJ=3I$LA01jJXg>{Mqua z2%-|yZf!BE)b6mwh#6Y5#EMy4t=2Az*50Z{)hgQ3>iYd%*YkhgJ=gWT`kr&0^WvQE zi}U&1_xarS?NyGC0??0DN|@#Yi}P_VE^f2PgnWJ=MUoaA>QzF8STQvB?aMH<-3*IvBu-tKa}7 zks}hT@&EUwuU3kZ|2>Jg+OPj}NL^fwLJ&~DP@hHr_uBt^(JQYmpEWJ6-mgrpK8EF$ zp@GFnG@3Xb@tox1)-R0D4@VyqmkpgOYgyc~@D3}RJ1wxpsc=CyB_q-;Zkd)jq4J;2 zMI@HvR?=W*v^JUrM!{%=kU*oU=f=b4wM%b-|ANo8st#1D55mgEg-Z{# zAP2&d{P~r0vsB0{(}l!?SZT8ME%S3aazt_29CNxk9eEUXE?ilhjb;(XRF%V4(26jC zBH;PowzK;KAR3-h`>kgsl_oV4qMY=vco$=Qa#hB}{do?$lWtsB*JO!mr0i=rm15Iz z1TKqHR5^T{C3Du^UbAHk@E+{gzgWwU?d;4aXVtZ^K4%M1QO#vH$j#!+%qo!3VJmn? z=Mx5AIHAd)$Kx53Bovlmmh`bu0xwPpJp6ujN{oAMfdAkl=Ms5=GYX!hN6fd)1-k=j zO{G%91olOrNnKL5s7|$EvZBBZyI3utNw$}Ge-_8XQD@-%T zD3geqcB?gag!l^;u4?+)dM@Z%Z zIgF+GoMkCcSG=4wzWZvAgJ&h`4bDaGs2*&hx zT5a5vD7={%kd{w7LJE8KO-XOC<)fA|V15t}FKWF2evUvqV#Xluu zklips!$IxVbFiH<=mZ6~ZyQ5pym`2>A>U%Bc$bDf{XSOk5Sn&rmjRP*WIE@UU!Wt* z-adLIij2c)DoXXX@A&p-Gc~Z{%~$X`9xp)?lZmR2i^`prpOt~y3q?a=>4a7#MyND| z!iRyELYEq4kmR(rtT4>WV)rS5Cv$0*iG%L|TamVKP~R@gT`i<99lhjxR^je!ENw#W z>u_*&P%uGVr#-^^Z5+3M`j}=nPm5M2&&L%N<@Gl8%J@(HtYSq{X}E%}^IR)yrAo8p zSV;Bgg{@S0g2)xl<|(Ra?LAgKdFz7HZ>1i5OsTh(?*jG8Ej}CHt*~mX$p#XKz#xtm zMN`Gw8X48sC5g9KX3f&MBe3LnOiqf$Q+01Tm44rnk+vM7g&(~;kA$G3_s6U61xJW% z76=1YJDlPN=M*l;)N+^=ekx?w#n@@6-~!nl%*UP)gt1EQDz}oHiCh^6?U7l}s64x0$*Pr^PkcVnHkeB4EqsE$(#2a}y-9HT zP6_f82^!E^2l0j`H7`S{q2{BpZ34gi@m|MbRrwxDz#u6JyWs>) z;$@`2V%RVRA^U^G8aqpVFGk3ub0)RVt-mXtxdZe&K7M{vD#rLvhw|R-)Ar5|^UAey z?(xnaTJmqJjLXS^=RcJX^jTmTXX<@zcynng+RX!Jt{mo}u^a|8nf#bmmpWIQYc+nX zK75*nXnS^hAG_o?Hat=Ji?&uyjE9D-?;BP+mCz=FN7xnW&Lx`)|K{;|1g4!QQ-&K} z`CtId=msts#I`X)3w%e-K7HElWKTAzZo7`G9FF(%Cc#ZJl%VZnCidud8U%)~eO6(m zUlbf~><015J*qHGFiLPE53~E#8;ief$gAbH&2y>n9ucx&rD?wpt!Tqeb7BIGn_TmN zjv*<*xoz7c{2Z*Rwoac&QqFfN#|_loZS2+kZ45BMYD4aN?mdN4>x+ShQKIWX*LX3! ztg>}39>zQkjaPm-kv9wiN!k$OX`zseT$U=w@YOMH;h@f@JWi>e0FMLyfx2X6m9hNk zg`txUwH|No0h>G%14}ZUe{F$9zKsm&HaR&aciGO)52D=SBey;E*>n~pR{_}29BkwD z@o2`^QW$+&C&*aYj9llA4eI)mB!8Ic&~r!D*Ik?d1rMUxv#r(g)K9ISP0Vg+Rz7nx zS@xzc`Cf57kH{;|#AL#2D8+5#ZM9&o%PnIGwWzN%@@(n66@S>9Qeqf#JsQ8Z;|y~V z&ywujIHs&CW@!|*U0Luj5Ynp|4g@qd=y$&zlHsA@5bhS{&rY=gkIzmEAZv4J^~ucn z<{M^WEOu|IZtys0-TtJ!Uz^WlJv87()W{gcamJS-qXlYtG{giK<>(8I^NH-`VR0-U zxx!iCM3Pu5c?X;)N?SULt^hzSsk}G>&8fjwT;_lpp_e>7R-ZFy;8d^Hp)MZ;_XinO zi%>OLleiNa3ZC#$@JRxg(YIilL)l1|J8E=kh!_uXHNu!Gfj#EDlfm^_!*v2UqlFO* z(`3cge3oQRT$~pJPrSHTISKU4#VPyIo-6&Jicp>eCYewU%O6Q4S|))~yLOdcf}?~x+v*I3~DBJoR>3U*;^`~+Q8Qv(O#V#HI3wF`djVDf?~sZ!6f?Wy%^ zyOkd=LNKC3c^N1!gR8om<)@$+XU_pd08}Ukj9roOlH#L4h$JP6vETCK<82~fz0%gm zAS+nUs@()9A?IhZxmqEflj#$FCBHJSZ*3z<80Th-0vB_(ka%VVDdtS1oT!qEOk8o; zs(^=81jc)Q5;@Fc9cm|ALW7MH%Ipv1UPjT+mB!M<)zu};&mod|cxC09A!^rzR=U%6 z9*`Rz?1u(Ne2IxV{nFjgupX-6ChduJsK31!;2c9JLs3(@MJv+7sfnW*9tGmMAZL(Q zX|xgx@Z9dqE~0CAd_~zF*v30eZn`~;8>Tly5TYNFXaXd}xBwYn7ztiS{lvaARb&TlT1#^D#iyR33I=KhMkhuH57^GijzP>k)0l#Rlb5~xHfM}37?Gn#!6{1Z%+sbUo5h>r z+g1y!XfpP(_p-9Am5R0U1FoWLrhO~xHk;@P;3U(+wmNAPU1}(2AV4InZ^Oud6u+vRs zo#KRf)#f~lp$3hB=ADDiIUS#};FKL1ddvAnIeLupW$Bs5`n`~?I zoA=`-w4v&y?6Q(qM&_&t_P`V#0;!GA2FFV18?_SajEx~snmX@QV3R>DkO0gY%@n_C zvW;hE;1b4^-rh?o*!E#lZ+`ch97a2S@!J_hDHN8-Q^83~s~1&5iC{okaqzJIXuZJD zARK?_AyG6OY|yoa?%Lq7YK%kYetlnKXG)SW#zVJ)-SN8Jv8?mjv0(oWD)>$Imf2HB zzRIi(f~M0743|e5^!0-A@Nnl6_;T)$53La!?rtYST|NWl`UQ=RQkCr$(Of74JxK(v zm2a9eqpx)w?Y0u5h;POK^;~de27H622K18F;YUz6~rv?P`dq{^`KrZr@o01 zhDVaaKu+?0U%{l7?>wb%dAqC!3SFATsFEV^;dk#{@$--o0)el7J@SsD=Ev@gd=tqm zTPAR2lDMsbJEYnz4+Yk<%!3#kW&09|F?Q%?-|})>DcQzm1D<9sW7w?aWK2@7B67Jr zD@$L+DBl)klqXebD_JI)C+>x60vcmXvnH$2%mm^X1dFvS;6xFZD=f2oAcP7?YIzgZ zsJbG{H_O;L+wy; zG}LWrg~=6{SDuw5UVkR!KSH4J0?S>?ogcO(HCeXH><512bIAjxeaS|`-p{x&KD}OE zo^7Y1XMAVRKNs7K@!+soo$6M>O^k?7`|(S0LRd-$m6MH=l6hsL@v=`}eKDWTGIfu` zy=R~uMdi1;*`9^m!k9Qy z#*y!Cz>{aNS7ZVdrypx{T4pdswZgx3mM1ZaMx(Hb?wm;4OfbxCqMs7$#1Q9VYS5HA zfuYT+#L!+DNntopAg~;pr%$Pq8!#T~Y9VSGJJse;Dyb+2WE|$i@WzbLFDTZOlc6|l z9E4Gc7v%+RqM?9Nptsux1VKH^Skkq031qUXKb17jfMC6iACQA4r$ppmaYF zl^M%|6Ykno0;*(XDoc?@M!ofcb{Rvm321~@g`#MA`Lc&I4~g}vY90V)Hfn)3pm|OF^zr6clNAR(8(x8B!_0#_icSsMQYTZe?O|S%|=4#{%V*T zl%M7ys8%?&Y-wAUK{lx(KMOV~fs--D4>y&R5P%s=Ni6)?ltZpcm@;m59&Vdq{%9k< zF}!gyp*`E&P8ozRL#_(g5WH39tPI-7by7T1tp(yxVccu*)z`AV6A_jPtlAB3%l2U*uw;?2f*97tHj!${=IKjdfm7g_^^JsEt$O&r; zHf9{eWpZJFvR@i(jmj65CpBw!{0Ex+Xn2sr`V1%zGI7j@8z&MUi8_8I8Nmjwn(T(TkTSi?IbJa0vxzijMSEz3wuw8HrnrKI3M z1r!&AeoT~z#hLc$>Epg|mbo_Sfp1*6VND%ASJ11>ft5; ziefYsS3;rUmZ**CV}XpOg9t1LisE$TaEZAy{%!6A6Y*2WO7Yq8rkm#-`YaiKtXnb+ zQ)ZyU`6KCC&Y`1q){O3p=<^s~*IS<5CrMcR{FmBf?xzZ)Rb4VsB5hhmO}gCw3K{95 z!#SSo<4KV10Ww`zCcBC#j#|Gw^NxpMZ9HH4eo-5~7yQL_kXG!8GmrERgXbToy!p+#;;#XP?$CxXCUj|3WzJ5o zPS!%Wk7`U{StC3{^YDf`dhu$&BKM*jjSHQrEQ@n=&TVIkrKk&ID$xtd3r0<F??tUfHbkXU_HwXRdihsj=Ay zgs`tL1?Q5<6$QV?7n6I4HWZ8eD*#T2#hPc%C`Kqy9Xy&Qb5kjfi86^@EV)#gu~4D_ z$PUon`IbYWElUo4k1tO2<*G!4_0T&xTZAV1$yaLDKF|1EK5HyQEVe8OVh8e>X|Nx- zvpCRzOAB>n`|-bCo(m4LP_+HJf*5o;CpHwFU39^7YvI-``~dp9sI*(j*=LvHe8nRw z8N1Smt_7j7sg^h!hY)y@b-MgYVzuNZ*E{}OLXqDlPuC3J?li{9YvfWhXk4zP@Nzds zd;H|@A(SOZss?y}A>+pXe`aPLk&nk4x zMj2(QU4N?D3L6$KZ2En_Fs>#m@VCS14&lJHjA3pgb7EqDyq1YIQvI{qV&1Voj_$;n zRgSYX*d_T9ZUHXzhvJJnd?IIiNF8O>-+#MI1VS-CZY}y>T4*n2WElHN>0els|8yX8 za8vGGuj~cGY-um~V&N~>U?3J=E{=jSE0LQe$M^Q0xNqXlUK;l9zZ)}*ec%DsspR=j zlHQzZx;^+)Sm>@2dnCT?z3%>lmppqxR>IP+N8_Jn%!uWWzaN)zLPd|l3nWpZAx6=% zdUO7zG?d;hp2r&3qwlFzm^F4z+=@%Q(*;D-w9r$v&= zP1^f@@3Y#R_1)aRw9n02EQCg~!crH4bT$`k@MDL$2ka4&fjg2;4S|J9>)?YO=cp(E zUkS6lyWFqbU{PQKNe+-#rjr_SBzKJimnA#{KX8XnKJ^r>;xjHhsx3~`*RG>uVfgsw zS6_H@WtY%&a*}x{*?g^pLiAvoI_=-Dvr~3afmd2CViAjJ44_2L(Y0mJqI{$acpq>) zWxTC%zvX3|2H<|F6eHtKBunp3E52mEIK#%VJm%%OC# zQu&%}`;Q3~G_xaRmmK!su7`8B5 z|6ktvONYI-6Gdf&#A4*#Jtu*Hkd|@4ZpOC~4BX)MK7zox;(6^QvZt!?N!!Mv;QM7E zMZ|XBZon~H#!2!WEvA!*OZ2Ke*Vq#Q!PQ#=8vazZuD{V^ireuPBMTkz$JXKhvO@}J z1h(}nJ}Zd6CVn8YNk)S9dhe1eZmMi^&!|_#cPM|qI~TF8{1Z=-Fv6G8@%g7yok|gte(>$Xsc@K)ej-4-G9lmvm ztMDxzSNgl5wybI^1O#44Q(QKEsV98jo!Dxs&sfv_;P*%H>3y-2?NCDHkP5mxyEPo9n7vU5uZp3c&>H1jUrcR5^Yn9V4~ps)0MMCraI!#kckO! zfx@u?^H5@rG^OBhBUvQyVf&*n14_Lj-NMyI!;C1#{q)RYJvqkPtBct~9dS9T$WJ0q zH_FW~VD-Txr`fOsH{?4DC*{8fsGOW1;+U?n$e(d_6y#2B2H-sR?YYDIOBR=Yn z_?Cm~G((Bme=%M9JxfkbtW!m5PnRHoZ?)HW2x#GV;^#)W@b`>>*7al9FLX&|5XkLN zNZT{yjOXD3K%?8>9as4EpmQS3hr)MUu#6NvdSOYetLST^r8hiG?pmF86`fB}w|cgj z$UJ-59TL?oTvh&(v3F?VmE&NOh@xSLyk>7;Dh~Ms_MyOLhPrK}bn59o!Y{ZjVYf<0nMS`c@mcHru@7QUbWnuHt9XM8mFS@ z>nBo4p!_|ju8>C3zpdo5m=QhQuRqs+z?2wc-FbAG7(Tn&_I$tQ$9*!dR_6Sxz6TF! z>%6R?9n3!MIc~oBamsZy**)R-+gk>69EDN8IqaECaGzGnix9c=mu;y}c9a;gTlb3S zb7{lmwEupo?#trHJ5XtR`SfKRE!(xQKq$rA+EQTda8FW~ zHAZ$pM)x@Vb24Qb#qZI$@H-TZ<@Os`A5^O^UA5j~j@^Uvz`8YKaQfSe&fu=9l!wqk zVdWG>e$DWo9qDBXkD65xOk{2Q+@L=c4)==4$sfCg$$7 zeizZz!(5Fb4En2I5EbR?;c0|c{`nt!k^brz^89(OQq@n~I~d$O?|hh@degFdH2qj( ztcva0dR@qW_i6(bS3aF)Sj6Rx4(bW1-2CvLK!m=_hgZ$f1~wsNHooq7zj%4qvE^y- zCy!Mrk#DO3A6DkrK`;M4OCaAg*vo*~5|}{m)ZRE=+a~m1tVHa+a1H8T^e*Vb%IW;l zdAuWVu+jXaqGKXJYfCF+e*AZEPXZ}OAx|e~NAi_rhy?d@gEk$x?01rflZ&qyxGYciIt_nX zP}bo!jH0?czl{XoQxYZ7o5Cb8;+G+~z25{uxQuBq!_s%C@d zNkMU}nAaQwI!R`oXq6hPLabWVuax(eZB==g-(O?em0z~g1zGC(;7#telDg6d`ZRv- za@Ys6E04z}`x!8FWd81{u@ec!U6x)JP%>Zc(KHsiVwi#-O)5y2hh5wKOccm(Sk?ab zv~iU6WPIRTh%acnZ{RpY>$ce}D*89%p?Y{qGodioO@qj0h;UOo5HV&(_v^v0xkQC) z4>M@p&rObP(9d_|3V%aL^aIjQ4;0j;=g7MyHMC;)-L%g-1_~v;_DpCGt9PJq_4;p= zK}|2#oY}DG2mchmo$fW%Sg*e;i~j0X?0@I&m~KZXTlnL44Go1$(|2-WXJWhGZFHCg zCzn6G6`GK)5HVxbd+=HSfUDTpS@~fz_1`@G(?CkkGV4-Sat>yp^aMt&D5yUGhGoCq z-&SHq0DTV9%7K%wxr(M3PWJ9=KKF}bh0<;4-OnYlUF(F|Uaw4?hY>qX-FaL4(?&T!=)d~JpiqT-&4FDGlxic?Lm`U)DK+h#eSFkF^9RqSG(AmDCyaom z=S4*lqNWtJzufOgel*N&YMIIKNgG?(rwj?5@s4381&o^*ujqJ<2j3pDUH@!7y`kx# zWC`B#*FSb0jdd-HWfA?Madb$wv$l4qT$zZyG9db@EP9WQ{P!+UKQjDDF#Yip|BJ}Y zqg$phjbx~Zqq?Tq+L^+?RsbhJ3V=<<0I1^-I>}YLS^n?3*Bc2B?`JpJ=TIhze3`Bi zE7TEc`l3Pj>G(cI3KFKy$~r1;Q=yW`DHeslQU7UQAG5Dx{q))9lkVl`uV`6Pxg>2e zUxIV)9mE>Lf%Wb+HPubUknP3znz( z*x2u?-)J5&YGeOPurymX;gqi`C`*1^;r}m^Cs|pFRv}*Ju3(5NqW~qtive2q*4*d& zN-l;0gtw^$T*qn8a@xuQ-I()!(wBbn@?7zDM}}2to?CXf{98))c*FPiDou{l;iCM$ zEcagM26ebh!2deJ$wDsw%y-=@jVuU8?A0bZ#|OdAgg?cpn%)-kbm4_A4R2L#s~ozBi;X;`R6KB+5n)z}e_AlbTv( zRH4G2WMp%$LwjP>DgB8M{}{W5hd!=-%1@e5v;WifUiav1n%UkO*?VjshhMm%^lT>R z-E&g2Q?ZI?%EMUq=U+&xo3_wDSfgcJZtj_Of{%_NQ~eXS)HiGe$I62hs~QoWpW9@v z(4s$UPb%v$_GQ91WoE4p7Hp3wzWd9lWL5R0{<#z}5wfIpOGtkByM4;_&mZP{U+GRq zLo&}r4?Z50`OqdfPW-Vs3DJ=6aDH~C5>~P{4rbf28eS{S$9^&3pyZAMg5}f#WVzh8 z7|iuERIe!y0)PG~V7JXE*6+RH#=!X}=+bb532uAyZz^}Z0=2{$T)2x}>BrQfvl_$K z?H7wqo4*C6nGwqM?U#aTh z8uHiG=q8@qp;^9FyUN2+efpicIgsiKb9oh+q)<^1d+L2%_#Ni{XAd8`{>+uwN0Nk) z-E^M9Za@arR}vI?@*`A5*%`#sU9%TlfZdZ*ja{y3_zq>nCD+l%J8M-HtLyoT&3`=R zo1-H0ZZaO_ghsRCS<@V8FTVyQx4FNGe2DwLm{G(Tt=G;6E2+aXaGJ6|w4_#(E2%2@ z6lS}|@D8W>=90y?a~N z4dyDR52ML{w!M{41a7<*-B9W_{pau5*?!X=+ry{l=Y-CrgItf{>v&NW+*D<4aK@fz%zs>jTj)!i2*%iRzGi#ZT1X;hW5+X& z`tNoAkV!mwWOmv1jaWyQS@rzx#0Kk%N$uIkkY|m}y0^BsYjZZnZ}(QxUi+%i=C`Fi z?bPvS)dyje|?;UATjgaABX73?J%eT7UaehzLqd9YkXpJldI_jyVsx6vXHN&KMmbcDLy z+tLr;OIMxa_LqgV(pRo`qPvYvb>QgZc2kM@&;v30VI3KKyx-d>0sX#&2xmDPpBJsx z3q`_C%DO5~-K`r+n||I5`4v{?BLV91tC3lcMy4?Fcp^N+@8wT!zhrTrhg$id89TR> z?cLehoV}QTe=YOWpl2Om6YZn@{`aJo3tD$1W5_=kA?n~cPj^$Y1rSGB7zg|t7?nAq za#~ht*yu*)X_+O*GG8PYIVJw%D=rzA0~yKYQLP;n1Xq99e|XVU8Y5QULm1VooK#rz z^@`f^HL8hVp&obEOfIA6gQ*ek#qi|z-TN%is*I6VEzTKhza!qUu^X}RMO*6e^&*j^ zt)lBVo!itEtgpV+8d3GHVr%T{4y8Q`mbnE;#rty{tB0P(^S0MavnoK5~Vzr;3!uF)5n@Hwz{_>D*R4g3go2?T3epqZt(f9LbW-=Xi7yS zZHVdstWGTs(9SzI))QgXuzirAajx9K;@dm>^4j?0DU0+j&VqvOtJql-eH#)Fx&9z0 z40`Y5_e%Qn(a5)v{2{e!mq_!1WuC?0ROoB(lHX~-T(UbwZPpJSj|lP2c$tsH&j_8TFf`Mt%9N-{1iyCB-Fwuw{wYSf zZr8w_R3RuGPv_y@8QrZEQia^px|_dshNww;VE23@u_V#zMIkdx^!{*~-j82Dvq!9~ z3qoo-dvD}E6!;aTIjkA4ocGfh)V&jWIW9KC;j>L`#_XhjKb*={zTUm+Ss%Flf%0>+ z;N8)o?bpbMae5-eKYT*sEpxv@?^-#vzoeJBPD#QwJZC=CH#&G{xFfX4#;JMn@b!Ms zq5|*xFCUybEGKtDleKLgS?>E-2ELP_)JbuKIO_)(kM^B&7~) z)Um&emSAbb>~lOCkO{(@Eu(Ekw5*i|Eyc8K|C?g=^4ee+g3oN04q~~d#$WKTejI$= z-@moB646`N`Q!C^NN1^yAt%H!nc`d|psi?F?{9EUhrFQd&(Jx`7cc*_cgETaVP$LX z9ho}cs^fSl`jGZ~GtXm$12?t6AqU4itS{$4shPA-mfWOj-#Jryfj(;KXm1>gED3DC z9j0n+cx^yBl%?5TmE7PRV$WjDiFieO$7*48;&zSa%`fpqJ^%a@ow~Q`9Mg{gOg6qJw@ta0>w;~l9yiK(sIcXl~?{c==HsJ(zgY3+M`o-WAH@9 zpU7;q<%V@L;`RBv;kM&%a{*HFucbbH%YE+_&;Xka6Nr6(Fy+N-<(?PLX(pV!+~1fbb_K#Z1iuGA z>HV)*+UxQDE_CR0a>qdJw(5bw%M7dgk~{RByQf&)2;Mt<=t{bqNp)eY*>sBZUss}A z%hpU)V0r%y4$0QhRw1vZKLopruiau1z7+a)^X8lRm+BveKMhsZ zY%Bjoq`q&d8(od)@jYxSdw+6C+#0&yvqz<|GLimXmO71d%Ku~{NG8aC`4>U;Z+OI$6FD^6Ba zlB|M?K6(ov-tAaE+>x4CMT?nbzP^S8bBZ=@?AUc$_EoO&&DM8^TNm^r)q`N2!=Il9^zC-r9! z%(Zfs`us#S!cQe0#=cHm{VL>-jL~k$rmLoXCFG#Faha}x*7EVERcX0d@Fv0WtcH$^ zxabX%RLf|Gstsk*JDcvC-T#db0Z$uaxzc_6{|DpK1`Om8$Ox8ua$7_Zk0e|mC$2|nEZEUbuEv^xTLaXAmKj)}4)#Q4@ zBQk4>Vk8kRBbmc$B$Xl7B!>74OaFCCsqZB-+(OymU{JCb`UkGe?vc3q#t8kB_PX)N z3aPH{gu9g_8(R3G(P<{~5jO9$#%+Yt9Gp1KU9QoS6$Kq)AQ9M=UVA6s6cBNp9rzaX z=g0Abu=`>)=`yprvwA;IgPVK4R}losbh4yiBO|?7K13;^TJ5Xwan7}np6%ycKe}#w zY})ygd&EG^oEnj?`w@Xwzr(>q+1U)!p&5U~$a_X|yT|eyV!4j_FeGEpRgIr-q{Aiw zEZjm~M^v$|vJ4iJ6h;IeLqf{Y-d3{oR3S({p$aF*mo=MD(C+kG#0*g;N>O^l?I&vD z>5>dVLP?l70`XXW<$>z`m&2?rH{-+kRd}B-JBv8`XI5ulFK8)e<)G-4(v5%|+zh?1 zJ<@lwMzkabg<{g$#uzW#`zV*-Y>ly-?`mR4dcdq7juh12a2AXmJF{I;m8YAMQ*q@O zA-;IsuHs-PvuN&JcINFWuPg)gM4W-5ZvX0w7#-fQ9Uf*m^eEnRIyo{>;* zwZF!R)atEp|K^C#k3W5rNQm{DbDA^!IWl0jaM666)*UrAnW6bv-&&aM_qK}qTYa!d zHCv?Ck!PeS?TnFYlxU!`Om-ooRxSKdxulor;TDPYrhx?jPQ?K%gPR-mA8uO-mcMv{ z3kg$N)zW#_+efGC>u;vQ{%uuOU?|^lkFDpA14BoT*VI7ZnmqgZ?wQzh|CYnhV@rlS z(O`u_hwn{;qeVT!2=&GX3SS@UaO^tmln84ew!|)>eCeNw8W5IGs<=BQCh?Dsd?eTf zCrW3$yL>Z#e(mw182@}KR+5HXvSX>bkx{HgOe@yexD$lG;FoWpGSn_)Xd$k?x!>?c zA&&BDoCzMqqKn|)E*A$!dxvH#s;c?P_LvEuIeLfJ@yadxfB*9B!=59DnC;JXNl8JS zR~H*v^{o?qh;DWocB`FOBjqwW;tfsZk)GZR1c#oeBwOvgW%iAbRk@2tKQ6h|>WY>Y z+L{7#{2jgs0UkvZyl!#))}_E*Mu!RL-#&GMC)0LNu4$7|w#eS+l-l0n2!`J8rnFef z2k>j3W8+fUTL%5Mx(qd2I?|(sHYUD4m(YQU+@#v6(C?Ohll{4bt5Y;1O84sTIKD|i zYPxOC`^$#0X#>~GZBK29*DF7Z{`sX}6vQ=k1c_Wu|9QqACg50?arQ$&R^!h};o~Y$ z?9E^23NPL{AE@y}SO_?f>Js)mKHb}N8!|HL)a}XbKw}vWG{qVQvOG6&Qs(ifbfBjBZ`3rSAI0;;Ne?#m0Ql{pSCOV+zrhZj&iCi6gELK1rzDt|M)*_g zy!W7PrOb~TtQ+AcAFMs`aMp#$W_Q)3em0JgEDxGFKwYs^)P2$W(bJ<(pzy=DtVQaL z6NA4s^f;F=uLj(&$FU} z04o+8_bW^Xd1P|LEt4RONHMRN9P{7oOj%`=H|{a44Roth%@ITMt_@^-cb$q`9&3+= zAdcG9;MxywddlE7b$Ytgx&NATT~c$JpDD0vok9C-=@!RWx#HqBxe7POMr0*f1COW^ zVU9SXdK`vc(BS-Qg5`U`^S@q3~xbX@I#>m1cG* z3o1b-jncd;4re*aUT#?f;34RWmEH$j_g`2~6TU2!cxu5XCoCZj@Q`g+cgJj5 zt{Ce_O&lr{6rnk43lMNB?M!2zdWfi?9@BNI+_>uiLWv;*LK&RMjyI<~WB33EePwE` z&LmqxS;-W;0IZl@ORiRx%3gaPeRS7JOFO8es3hAZyr85zYqnI^tDk#GH#CkeSC`hf z6fT3?7{)a3h=Xy;Y+@Fy2|M6bBrnXyx;Z_S}gs)wZoeI=%q!0_@@zeks`ea`KI4{Qc z2JG%WHFIB4C%3lSzpoK15VmSBtAWo?-5AadRVCjzca`uk{JSxh;$Hi%NsRN zSvVW}s9;Q$By6XGb8>;^z|NAecQ@jvtr0(>16rg8^Q&-fr1zFgJ zdIa7CSn@sj|EXL5+r0CHEe1}RHhJ-T`Ln99%#RGJ63FU$@#W`-8%^LiHyb}9Jqjr17XUyj4PSwl z*zS8z>z9eviKC&#(vMoMuZMPpg!6xNk1$ zX!*(IoFy&gxvis$h{JgS`|0l9`GZ~K9ex0S4>3&*fL}c&M-(M)Xu1pybAx#$;c4@L zUfE~`P(EN*l;Rc;b|q2uQY?8-836)b-3#Cr$3;cuLETtr;fgG@QM94}AVX48B>)LZ z$TQ`l9wSgFBp}5jZW%u&0I0Y};PXme%39v8Xr%Hj?u%qs9J-pyLKgr6paG`T0Mn#s z1+*ex%@g8CRr#oQgtvC}fs6E&ycKc~p?qr^w#3EJW^rz~uT^~vIe2VXUGuoQ1yWhH zUxvxAI(Qz9oY#hp-?NOwm0uA5nnxs@YfGNfR?^2%;;3f_ zl~t;;ue84U5aOy>wRX*8dap8MJ~Y1o@(El#uQE4Hig5E1;O8>Uh>#dy*#8XK0?jbT zSj-@dKxo>0?(YNvP<7RT1e-b6TmKriA5G9e*On<2^@h|ZR}X`!ryO2{uxjx-JzK!eZ~ zH&NQK{#)Z-_+i+}^Aco!0WuF(obRrZ56#PB1JAOcVPjiRZ!ZGLn^tv<|m`*jW0*T6Xb*UD)n)GUUuSm zh!F|=Go1StYO{iuOfDYh!2G??A&~Kx5Zhi1nwAJa0(bG_SZ8|eLCL;8G zpyfZZVCZ6rU#odGeAQ*RmQwjx0FhUshmL`TY>vsRPXOiau)I-LFQApIw~=H?Vu^t# z3ga*iGWX4<03(N@FZx^&1DaN#HszN$ z`3}uay_V10Qic)bwoQ|RZE{cF>wo+aa6A3vo@0uAC`%|OMf=>e=A=|` ze?#DDOCUtyGv}YTWvTDDf29g^*y@)Oy7?LJN_?)wJ40<*96($X_dmWhqcaTjIM8T4 zc06_&YpPuaNO;x+M1kb`^PC%@ULPDpf+g*v80Tuu)@ZJB@0lJOOg#X zaiuZn(xyiC8GTDe0*}FOd_*zzS*K!`aXf&;SkxNWw@6CK|(U6IVi}Tq5 zbEg}us%I@ILsihzJH!c<_dj{X*4KBIlR~>AUcn+X~3zV1H5aCUh-orRrG+0>~dXZ$B{XLyqOnnHBptiE3 zzfW5)iWfU0pEYs5+?!BT@!kKyWL5XqqM0W_uv>qSE908phiLu@?`U=$_uAF`UE5z7 ziP)PiFM%_2IKG^Gbkh7gqU-bYwTSc zd=E0z62k*7uAm5V97!d1SJ1&BjZB!q+TCH6)~mFb5D6iHM(QY-VnL9%V?Ltx^5)dy z1h}XeBY?WSIGq{e+4r3y%@Z7`heTuR>FRplee*}u@qRua02utqqn=h^{P_d%a<^T2 z=&wQ@q0x?E0JFg*yeP(aU|BYD$oklK3o!ZDM~CLW&!zMK;3GQ|Sgar7@X!2gUz44m zKjJ83`qTK13WSE&@CQWjN7Gv3Y&FK5Fpf*Nh)K9RiY%Ht?$1f{@^-m*=%D;#KOs$L zn0WdetC)Zf=_p7Qz$ofSI666sOcI@df+}O?phl(FbUZ0$2z{9CB6C$f+3U}g$`B|z zs(YMbBLJRek;h`O0@%-)5MolLC2BqJ` z>rND1R2_5b*2`NtL38R{070i76vwsKMY_v9^rNn6?3_2s=!RwR$d-3A0hYM=$nxWh zpnxcj_jyV1Z$+;@7LY(FSvZK2Zr$iC9pw&E<~asaPiUGXC zqLQB+&1}CSLz9HY3~e>D`#Fj&?e1miQgd5tib`vogj<9i_kj%`nGndI9Gt6)Dz(KU z%X^9|a&$cW-RErlB<>8&yQ({W+XsrGw3V^y6<5o$F4kqE*T}Sp8|gDI!fMl&9^X-+EwjV9+? z?YgrxXL?tOxmac;5K$>r)o1RNexrL1W1*)34u8o};63W`zUvU&pk~#os;V-; zc@G`5;^^Mq2%^;ni58L4b1Q#-qpArAS-J)uucQf(m^zXt=h;(A?&)cj>}M-T<%Yo#!kP-P2|z6_h}m6lj30oA4lmXjulQaMD6E%``;UV zYH7P7*J}!j4LAH(R&U4%CK@^KPmwYpqtIflX$T|oY!RlER)tGwj$Xh8k;T7W&qJpQP`MVGHaI5FqoE&~`Kb<e!$P1_gl_X*p68JddNvk;e?jCv#2R*URgVVS-z%hgF^3C)+}CFH`;xSaS2H zdHenYsh${h*FmbSYa_ULJMgL4wUdv;fgOXWKy;Ec33MERI*YZZfu7xlK@D1{M3gp^ zkr-M&x)q-Zv{XP^#{O&3vEl|qKtQOVG6X;rl1Wc(2tbM2wUhNn>glK7(;kf`-ca06 ze4xlx3lxe5OE1Krsu=t5O;tnCi% znbDXdGI0TK()l|(P2>CWlUh0|d-Yu(vkx}l{mA>S8RowyR|WZ+a!zj&%iV1eGX3qG zZxi9m>ZUcegKves7835^9CHUP4~q{SW?}t`EA!u%J$;qWTGsaI=CBRO`aBpClL4^O z5I}+&`)rf|iPx8t`Lu`vH2}h;7*PGMxQI6l@2XRt&+7Eh-%)f9ns#M{hG)~)g9jez z3zm&=`(x*x+7;Ozms8HlWxw`sgL-jpb7yAFR><`>{a%hD;HN`O%@G$H3BZE6HtKVm z=TM{vCwgnZfC&Cu+LMw@tH-RkyPS|fn%hJ1TVw39a*M3i7~g(c0Z5qrnFyep5k>lk zBEqp+mcs89U-s;Klk(yW3hxgGW)q6|1yI zsq?^|t8|swb4b4abNeJSDMe2m*Iuu3`?WmaQ1ux0r~TPm=-v{g1V8Pu8Rv#U7FC{x zAYpldbqQdy#UcU(ReqR9ymg-Q=&pbhvLbxwhRjAW%98W-k0e~tFp$X~iIu!CyU;(4 zBYbAJE&BUk;&d%3($4&#Fl>T@L?VXR#3E+QOdN>2)XC`pfD5GBKy9#0+(AgPT&f{l zsh6ZNr3Orj4JJKn%9y)r)>d9(pIW7DTFHmbBUM>aOl@=OuuOWy=a0TZkV6wrBr_?9 z9N>uWFlxTA(VAZQtd<1soSgeyVe}bD0xN8nZh>;HAx@iNmBHYlyld34UP$8Z zAT2Bvq@*chVA_(KI%g+ZZ%%RNd7ABN)nsExU+9$X4Gxs;E$SW8BPD-q$LnRNXSh-x z{a!8s+x_luY@F@)ZeH-GmiMgY#~AB2^rW<`1RG|OM`F-}reYWMF(i*MfD7|512wRK zLQ=~>a$byjS!hWjn@CIbKRwm<`n<(nU%)wFUD=+s;3pu)XuU7(cydm8@lbP-y{|K` zRmxR1cv;>=f3vOIGUdFgm*yGdG^-Ij6(<;x(@LC>1c(|BN|4R;FO~49fHoqW(d;ER z&SQHr^QK1QobebUc|m6doZcLAu^XZx8;ZzJpDjXyL4&pEJMM$IQU7`=eXog-a-;tChc?3tp2=rXb*#Mi`Ma>3NOgq&ZZng;g;%^+80J}c6dzFovs_C z#l=q;zmV5sab}Pz4-gfM$S}ouqa}x5Vv$j{ffai zn!|liJCQ;l@Bo&I`=g^H?*P(ti@v zCYv*3Uo;0=Tv=qaw*~c47q;2BFeLyrZNzDk`e(0ad{~XQByJ;gJ)mZyn8Lj#&~ejqo9bAVC}%03vdegrwvJ z5e6iAN!}c4b*~8#x^&QpcD~r(q=XuYxh9mu3K#EHakUw4e=k}+Of_)Ie9p^KDjf!A zQQ6B2%89N!;mfMx;~nQg+`(1I2AyHQj`;&VPMk%|%gXPSMI)~*XS|L43~JK?r7|n7 z{BBJ&R7!k1;#$<_lu-NKD^-xRufWSFLYv zm>a}zp+TC!)l$OP_bPgwbv+7Xo2gR?z9lT$GzJ$ zkiKJ~tbv<`P|mr5Xwa1{AuBEh5Kh&jig5#?JbhnJe?H0VoQb_OEwmFH#bzTI#2AYs zbIV#CV;~wFyUhk_rs=MLs%q886D4#`^3rPIGepmbd0v0sk^A}|FT?(a@}h{Mj~43O zvXPW%c%Qm+Hz&)p`w?u4uL!WjTi_7?B&^He3Q#6;vedGH+2CgD4Eo+|JBynFz+gez@Zsmq}} z;0_|p_db{a@j###!2n~~%zQf&S^TAC212bWmxouSzB=gqSP1NLJy|2Rc)3hgk>q74 zmuDS|G&yTxli>!TW2{>gvlA0jsiEGk=DTAk7Kj*m$MJZ>VLkvy=sAiPZJ-O&24Jz%HXq&p=@0MIkec>95(t+Np61wCS7C5giKjOo=>ak zYm}af)Zf?U^?IrK@1W4~3~(TVN|XpI_2^arWw0P10zoIGjVH*!iinay`|KNYmC6jF zw(rw0Jci?si7{ac=5)4dUgu*$dETky+UH$$ zy=3hg<~mY}MpZQgG+EJ%FaZ$E_omuXIE;785m4pW;ysKQ?ZB{f!ErySyRU>*FKJ(c z<7Os7X^9KVa`%u!-9d8Pik1jMIfTSbiCnQD zsNtmzCd#UC89c)hN*hTPn-kF6kl3v$tu3z^FakOC?Gv;Bk{dXKm|T6fz0=KV@WCZI zn|I|2&$6C;fC;~A()i#18E*nLvW!3aFEwK6JuQU&lZWUXW-zIPp=#ku4#p@qch20g z0J)Z}jJ$bq(}SmfZW;C@0S<8Gz&+rUq#u*dZLG<-)q5{`GTiN5A<~#>+AAgoXz>rG zvTrn`;-RGp&V+YTi@8FD9mh%q?tcd2HP0C7?%!uPVyfjEGd@tYQ=^qof6Ki@ruPAP zPf3y+`HTBzC^gM>njGooC1>)gP~lZ}zEu=m0?$C!C?O65j@xJK&ANL zO4KwDpoCJyu3ZebsR<%zQj~c_tIgd88UbjaS}Lh+Du|Q<)^wCrrB;Y#3bxX!(5q^z zRG`XGY#OD|K!_Pii5YN>41{$7HA`g0k*aSOZ7PB$Qwes%?4cK9_`+LQl{mX^{5$`) ze^z>l_0z77hi%w$Z`=lJO4hbk)ih>Tb-(YZV{^N%HLTq4QvB>ai?Kb762DRd}#t{>U zfjB&Yi-2;;BvBuELTvUCIt$=&J};9!;OselKCkj@w;$`r^Zw*ImLv$ncaD_;F_yS< zQ*_0YjQf}ItPByrR-=cgt<)d7=0)w?i34=D;SBx+I%(pt=@pSB@<)RZ5l%1t}6wINJWq!<49|q*)t4d2AC|m&De=*nl0w?|&7qfxREM@^Pz1ezSpOy;E8 z(-#<+(atbq7ihI@(6Yx+r}m+#xs~Pj!QHa z$T&twimgcsVWhOUO2$EUYC23zSfe(nyJl?A<59onxZQp$NPtiJPK&D-g>$&LQ?Nch zoxMEiD|}l@xpf3_uhhtzZ}G11ZBqGM7dkRYEwr>PuI$ydZDS0LRpJw)AsWD0N;2v= z1WOSY$s>j9={@Yva@fCKNUdd}y=vr?x|*PxVJz7o4kMjF*-zeHIU>t;>=H#fBo~XH zVBvV<&p17)Wj(7CN?b#-^Fg$@h;KvtQo^EYyog>R_sWsO3Z0Cpy=iFPT4IrPmD2wR zi>N)zhv_Vs=j_}>z$`%ol%rVrM80frq%Lx6XC(KguT6PTZ3l5kvYLh#wV!OHQ2w5x z%|Exx3|rsZ%a`cZ}y-qLAbw4U4tinC-uuEr zYH=(99T%nqliWHC?Da(!s;g2J?H;<1yLf}U*Xgfbz(sNv4rGn46|361PVsjce2tiy zoGhSN4ph#JLT#xeAmy|xqBdf~A3=^9Lo7^l8aueHuov3DMa+u0;>=2MRObFtxD$4V zGBI{W?eQzX5R)r$9o3{=aHY_rKMAT7HqLQ%NhQ~`RSDY`HTG!+L{V63Xr8k776}ED z-jOCCyLF&5e=+B3@ZvGSteS(6+toD|Mh|3JoJ-}_WL`v#T?it(G%#ukC0p)qsiT1rye&GxRV4~yvJIi)iP+2zv z1?Qd#H|WBv3vd=U(7EzZ$1%OyD6epnlI6RGK1885VB$HDwaTL~E+k8mZT9nF2Buxc z=Q(vTu~?}#wcn#pR~hNfJ#lFV&m$hyEKkvH{hLFZp^NOQ(t*|r2Y%+gjNDC97BgTn zL7)!W4Xg;zB|!kntxV@W$2j!$f)^-FQBHvh^g_Hlw{WU*3YTWez`^;kQKH=;v3jdA zLM3>LPJIz*snN`}62OxD6l6+v+}~`~H6Vo*n%na>R}ru%Zs7|uwh(cG%xxNQ0t1XI z1lA;Mme(<7a1;`URTOeekC!%7)n~|1vU}&xJqxClxTiT&c8n?AL6+63)VO-0atlJd zvsAxnR|pvM2%BQluO`4FA0W<#!zO+m?j<2$$L??ADWa~!pzv2}S9T(+n3Q~`s>zDY ztSQr|&2wW^NV9Qeb6Pc!H7e8CRqQ%{huD7my^B{Y+S}$@BMZum;||*!Ego)VS9jT2 z*X?RsRnuO|KLMSBGqAI$dnuxn(Jle5Dx`Yu5SSKwOJ@dlCuEL2J_c4(v zW_cf4{sP#n<8b4CR^yvnVv5@wRJat$mb^tV#JM7sG_mlMGblRfWFaX9NsKhL^ILKl zN^^|V32r2hqSeM@xI=7Vn^eZv5R_6=R-lQR#Q=yzN3L;!#k0y1LoK52&dL!{^rF%E zy*nZJhC|qlme+05XSbihRvRTNcIv$Kn*!gY=I z604&+VAvqS*bopk`N$U`oUlM?axa;T^vNj-n6idp(|qhLl=PU>JVao8g4se7ap&ve zuL44X6=C)v5fy96jWov*A_(0}M83K@lr<)S+K!14QF?^b)sq_8(awVnD{tvnXCrrN zt#Fsd&4*_YOfraEB)y*kkilg^ErN;*gh(t2evwh~AmSQyJoPIF)CN%*%CfCe6j;Xd zA@q=f0y&XrApC1hP#!%bc*+>(Ih0f?A$}6dJ2f%tMg?L_eZfjG{7g{@1_Ll}Mq%Ki+ z5tz{>c`I~mt^bqlQJV{Gpqh@f214=3;v`IsEaAW^buK?MdQLKo*UMf`MwrOa!f_GW zI1~#EVazjN+Dl#2veEbwMjiyJ=ZZL`ts;sF;)OPCCr8W$Wh6&pylL*Uma3!%RD+s| z(4s?Fk^orOb(iW{LIJ8qA&NC(Ya207L~x8`d@PFbV~Pl1tadM~%RBPSip5Gd$C+s( zl#tRZkxw1FQKLup(HlWpvoEF+Y6~qjLT9W}39@R+5GOpq7pTl&J|-pv^|ntg~W!fBanOA5JRt|X;6x4io~ierctE;Xml;n)JpIb)*MtI5W=#m zvDPWvtrT_Yp$DGS>@h0D{mi%VzcH67FHj-oD?=2QgrNpO&KH%5Lx7}`wN)6(mo_FV z&&?QWMYP3|He=UcI!-5sbx$4p4WKR~tXAk)%fX7rR!u=s_lO=4c+|4(>F(Y)^jg$l z8qGzbxvG_LVt{#DlLw27#_%E)=36I=a3bS2i#ciQ^4S!C)(hqJ&?SLs#8HY#OHnEf z2sAZVxRM$34NB)XIfp5&D@iW3fyPsu*vv>OF(FuB0nO$T0;e~3uZiAlG4#Ct_e0y* z>(m#K*X!G5)$n-!9?vhY><-UY`(TsYa`6FtS#)Jj#;YgKtyy1_$XoHa*!=RrT@zGG zZC|@(`|p3EDSS^J8DF_pS0k-+-Cb%ud$pLo|4Ll9m5GL^e!I(MzchHBS-5=Z8O(C* zS(0Ij45n?pf7OcBjM-~qew%aG&8(Ri%*O&FY3$o}<{5>{xi_pVMJ{VnUBuOC=(RXC zHZJ^Y_O8cfi<$7*TI6-9eG5`~V#S7XjlO2mp)?sJrRGKX9A^e)3Ps8d!2^)@= zm_*YurW?3Khz&rJICNge-FMFkMb)3YFid&x%!)Thg8shwTb%cAG|KMyo(bXJ9UVP& zowc|9OJ`(-tse_p_rzfB-C49+dd*n}J48dcy?sHuf*$HG`#I+ck<-S5z~^fng_GoY zk;}fJ``)_uhC;)k17SOB$cD7oQ{{4+5&iwkPVduJ5e|`z+}14cKmi!q;HtwY_0I68 zP1nBa-<0Q;7!F4f_0#%qiu{F)3qN7pZ=d@flH0*q?}6s_x^l6)y}PQm8;m*t{)fC$ z?$?&rv_)+@kLoD5C+3L6K!`?LxASwe<+kPT2Xhyk>>$9#6B(>`X8;4=8B@^q6h!sq zz8%$Ehbll_hKa)UMLhEO#w*%>QIyJ*h5v%04!MGf&EU#|ej=~ z5sJ}9f(Ye4P1J8Yx4!*6D|yL(M9uIPd46>c$aFw4;z(U*rn}_Q@hN)=XhG2c11cLD zs3e}(+B6CY{JV6m(t)zJ2D$@5|304E+}oDczK+V|NqsB$Z6uI}#0d?8$ojPxMXNy% zpuVVqxxaO#Al5ZCFxA^-#lbF*xU>!0OWFhp06pk<{9)PnO!zIgd`JrU3QjMtkGyR050p6}gtM>KLX*a`Pa}?yTZ* zeO^J3SnG8b*LC5xGi%l*K1&>SC{FfVXKdYt?0GhUZ%EY zlqUyg5~SvOtY1gCgvw~I8k0NI3?tBTQKJIFgtV3UXxUwfxZE5y6k%=rs35@hV_vTx zr?WwFl1EeS2mv-A-L?WWlzVn7Nd`X;2NGGwVmJGJj*`X>mc32~6Q<$r5Ole{5rFZu zS{&b9Dnwy%BoyoDl0Z*S8A=Uj-Q-FZLP9{dXq&5d51X~0JqFE(fT}!6bhPi~vr2h+ z9p4!etL<+X`y)Fo!~+v=vwuYB7<+X(J4=SCZCAa`sfXspIL_!FPr=Jtwwu>`x^;e3 zykxg0fQkE>G{qqTjqZ%-xOo#E19A`(%h)%j!yk7c0ThaCxu#35DMX7sdIx>!m4prz zVuE4aMKr#&3faE(R8%5sef9m;3X42ta3r@72p;sLqzXW+fZ_L#CCuX5Ig96KoCbr5 zJzQkWgHuU8WWa(q@KVqbqPa*!0cxY|?k-T00SPF>E=Q=u!g;o{iA;q?q=iPS3`#26 zsM94x6bQzV6}S;Rt3_iF!YODeQ7WTKT2x9)bZd(yAi8 zId`{oP+C=Nad3h}x9QR)7(}NM0iznAwzO!s2z#9X#nLovgrfCfwvNo-&jt94JY)~$ShyUA5NI@LDj8j*KL zVzD(ec}~Bs!{S4W4IN#Rua{ihn=gyO&A?pan&os_o1axQxo}fuiGigdh)W?Pl6px* z$NchR1b_)xYs+>bUUdrQ3G7U4wA5Jbt(C z`x0EYnN7bE=A3gULiS#DJicC1-cl-NJ{x)#*9(F!kCdmgC*CV73brQ)CllJ9AK@Dz zw*SS>=MHM>0eY1wG68=MpUXz;*y_Yx^-8cXB*83OvCaefw>A3tic&N+X&Gb9{-C=2lgoFS(2o=7`xZ{;iwzq-A z0!sKnaEKpwb+#AlgF_Lb~63HnbAmHVN(#(Bs#5&M#o`WrtB-S;Q$fBy8v;;i8M^}D1+0PsGW*fiPBeeFG-PBG8<+wjA_R_g z7~sgc(fPVbGc|$&IxlzF`|Namv54p>Y-C1cBT;zAYLZ+Edz zaW+vI6SunGYpU@%v7uj{_empzlXt9GDG*NUWy@v{Q(U1{5g3F}i-B2bN_W7iE(&w1 zCqV$`qEO@TOiX!YYg+lQBkCgc8Wd&{#uU?)0&U1BX7f=GOQ7+-7GHLPqKNffxioFUR`ndW;|##*7Gs48AZR2T@dazh;0|vI|)B-E7ww>Uw)q9lhHo z{_$_ivTQQDdf>dOAR+G%;Utt@RV-+Y?fQlbmW51hrNxM*!{1x`1TU>)k4{zd&S?pv ztsx9X+rV>wY(-^u&l{*&1F7NWW*)_%xAiqKYPI1>%N-)|NH_cowfu!PIu_3=zAH4 zpZp)Gt;QZW8CqcJi+4JOu}e@K1DbOTif6jz*=K1vJs(rj_8~NppwXx~-GNU{ztiPb zHyLyvKNdBFb>6oxx!q8FP;a!Wj8N;~?MN4}~x{E+7G+bzq@VkRFhx z1@ZZHzp8Zu5N#ZgUZY({&g6IKA1XOFfDTuJ`IHIkF29VToVt~^NdsGPMGBD9iy%;( z7K(#}mRw(Y!ABo0^DCm6xav?(ogn0K`(U^8GXfU!1AWi1iR(v_DWgK%>@Xyo zhD4}{m)7BVG=a5R#)yfVNZLsE>+V+fX_f2lxBAMo)*A0-XH~Vf`Pj9@ zT<(`&J2Nx5BNKAbU*xe}jYYz~MS0Y3JmQxp!P5xn}915f=HA5Z|rNDlkR$YCDJAiVRYQQ-5e8;lU)R*{d<+sph+z zyx@J(NiPyc#6?iWxtW=ck(q{>nsFyJ*Ji*Wg@}$NT-nrztcKijXzw-B;Io-4E5)#? z0za}3aWu+iv~!6Bp%SZ@m95>SUkK55|APrRmUdU-?wg2TA?j;iWGn(_^#|?ju^+4k0aK!wf=|SyZ*Ph&loj4Et~1WU^t$~ zKd`<7Y*AwS*w*U1{lGRAl6@i!g9?ZedyxzOwQyiDNRc6SIm)zQQ#E!k*JWI`2J!{0JFy@>JJe}^8S$QbnR{RDZ;Gulk`oLjhMq-s)F(y&3rXi)g++>@|~T`nKGHDO7=963h{Z?*`_-LTD-f@P@grP1Sh zlRZs)o%I&0OTU0!DuYCdl~U#H@a11Rs9Ol-H4+Wil1+b0W5BciTXhzpQqu zD5(cIma1@L0ss+=Kp>2ahYv8++?de}7Q-4>S^7BJzgF<%-`Xt;6T_y>7NxICrzw7H zXMelfdUPYT@wB@Cl9dk-{ZSmrN&PF07&nETY2iVbI&tX9jI;+#BNR=m4gK9WgQtzv zx-_>mlK-MGIO}tY(o#laTC=-5!dY9NM?TMCQ%{U`X>oHXM&`#&#ZG0Q4p5h}YYb=N zOf2Z!sTttpuV4w0G=K^~R3srp>^#o@vq2X!kUeCw;u_&*dgRBK(t$uVfJ!hDfrSAJ z6=h6G1!6}Y?*dHi@ZVflhz5sv{#uSNQ*iVBx0ye;*3#Z(>wfVsbA&9P=34unSK``F z-0`1#+g=fITDCpKp6t6K_57Zz**!B$K6X#$iIFXBvo*ENdzG>cA2QNdlgaO@T&?VUaqDe^b2=|V-|3?dGZT&w%q1E&&FZLBK5eeN0(iZp}fW z{xl8pxG_9lr?Dm5_#m%bfixLbXyHJ>NP>VA?J#EnzFcA#NQ|H%RBMjUBO{Y4hIT-IcV-{Ib+Dg!e zlXrKUhvm7%%F25kO?`UOb@q3)9f55BzMQ`A@cbnr(@P_Hea&mv>L-cudSdsynntk{IWuh+b!UPhn&~UbXOkafOU@T7%5xzK4vpAL^9O-?(UM|nY z*4I)g?gEVvA~3gZb|V^+Nlhl3D+!u*klz-o$4m$WATGyZU=SP)pGd*igZH|=CkdNQ zgWpx}%XIzct3FOZy1*<#XXmeh)|2*TL|oz~-(Yv!w!@M^_jUjVYT+q`R~CnG$3UMa zAmDfK!(cTyOlLc8qA__xPtF})1HyPyJRgkMeO?_-^ZdIW$^c9tZT}H4FfTKh0aVdI z%*8hRsl?zeM$oqVw5j7kc~bdcA>w>;FOvH{kf4r`b=l)pcXsAZ0bB6O5_G{0v_+ zk#gjqs-5$^a7Qu%4q@e6%|!7|f+Xd^l!QR^3SkIGEZHu*kNP1at*qCtvDe{y`<+II zB^b;e34xw_4Ys1+;ySxoexwzA3a0hN+fzV*nkwN1(mlU3Qe#~jeBC4n1OOlacB4wK zg#~u~!*cq+vkBw^@sPu$Wl#rWQk3%Ta~|Ypj&D;qveZf53u2S_-(ivTvu-M7kv)l% z3cQ&jYE(sfu)9lOZ)TB8@K}%g<7a0UgeADW@uUj|tR4?V;VCP(wQK0rgsZr@aBcEc zr!XXRmzhbazj!^%M^x)J%CX;ccYlU;0Bz#fl2Uv@c79oYk{nzc{oj5wK%I&54;}Fq z(S#ZiDi*jXJ-%RazNGpNu^?a)YpWn{a`W@--DSI+(tuw;6&b2jqyv~khHdI4BhSKZ zCiL^f8w!b!?VTQQ6yXBx1TA%Es+@(r(&T4*U0>Z@?KA4e4TZ$F3xVKEtH=ZFCB4-( zV?QxHW@hFDBI7}e(Q_Z^-cMD|2Q@2TurAF5&v1=?eW#Vj2AEOi96KMAR=8L$^@u2QmYM7X>Tz98UX9J6V)$;Nc(ziL6l+UrNXg58#6%-EZ zW*&QVG?&&-=;yC=l{UT0O!xn@?QONRjdi76ou|QTb<45Xd-^`#$xHC1p-oMB_GCU; zVUI+HApimW?Y&)OM`I2q$|GxEcYN>2bA@4E=Kea`=FcYT_u!eCG`02)O&$~*6Y#Mh zX0VvZz3J**x_g1Cg`IbYMJ<_$smGR$#;$WyNikV;`R`tj0~*GY75+Xv-X#t$K8fY6 zt~=W7OiXI~`iv|q+`Np8JcsxcTClLUtH#r5r!RNu>Di2B(sv&{DIblWpOTc^F|iEY zL4V=n{_@7Ftte002AZfq=X@bdAe1fb&b?%4wV84ytPJ6?Ch_tMWDPs3g}ulo1G ze|}ODjowEun~$A;y}-DkZ|(Z-mSAOFe^MT&POfwN3ElP9z}}&s@3%aV}`iSpD}VdE~IFMJ=PMSRxA-yMDUTr6GR^ZcithlbC{{%>Ba{sx-&mi)&2 z{okD4qW2yB{zqZ;I3L~m>ib(EEuX$i{C-?Z>bt9qtuO0+x$pQJjNi{$nZwS*e;25Q z()RRuKenrD`hLH*Ro%nQKK(8B#_w-$TCS5#bi&7v@^3Gxu6MWFY;6{&g!a#aY2?jO z$z*3+YhKG@W8i6Tr6Y4SIhnLNxa{2L7Y^s9@YxkwEJQqo9QAc36Gsx!lX8N|-y2_? zV012b_exqQsya-bN{SXCoxep%A1w(>OGz_$mHx>|Q%P3fqv`PP_quFtJ<6K_k-}Ef z&QVNGOH*X(GSOK4oz|xdSyNk8q^rp~;j)!@I$MBl^O$_5R(__gva+sfyE#KylC8tm z=4R+GbCbB*?B;fhJ2y#7ldixx2S1S$FQs>K%C(-gdT|&XU2bHimn=T zr+UhA4yV|enaH}${`LU4b9cq#3&KyL2rM(#8^Z`Z;EiB7UOIAFNuMi zV0^X+u*v(4w2d1NNA^G-XS{{=8NhbWZ*gZ|FuSQtm3W2q{b@)W{`OMJ?9pFk0lxMl zFQDkVyn2aI?8Y5nC=U}ek!SgPXy)FQs+0=9vafD&yrVCZMHEUTAIwd7HvMZ6MB)R- zm7j~x<`2RUeuoi+2#AWS_rkF=GKkQ@AZ9b0(d%LsYO3KZ<2<>q&eb;!Fa&7;d9U7% zPpi+*^7K-u9kl^;tQTm_ZmeRmmv!s4*)kLlmMN4%ro{^(TV$xV7D+X*v9M?OStQ*R z<+Uo8@M&7ZA^Zyv!9opqabN`jmMIIWDSjaZOR3BO_Kq;z?Zb{7b~mvf&#UlcFACbz zK&^qbMAKB|FjkyfkO|Jz?g@a4dkBIp()u6V>I!OBS7lOVSMXU~)b1{6^Oof{**gz2 z{N=OOuh2H@y0UW+-Z*6`YpGee8hZ2+MSPu2IM!Fv*DFf7Gf*BiL{Ne?>}(NXVAc!) zpi}cjlhb*JJPu1nMHS6mDnR3uf-E*>%;PnA^I;HZIXQKU0B)im`1 zlb4qsQw{`5#V}qoSM{ z#6>9HD+&-&h&#T(%s{+8OsblHmy?>7In%k>`H9)9`}x_~`T78%ciqo6#+va>6MI8@ zqzw5Gp9~Fr+utfi08kT<>6q+59&4=cZZqB}Yw|C_9XxJZ+xjoTd#LQwWNd-{@bORN zz%Q}_A@7mqRz{pWRs|Yms z)(x#EXrL=&`(8FC{50GN<8ydWj418+Vf<8SAt{+iMd1l%m4Z0}T5#jvgon&+qa7kp zLtyYqs6xtqf992cku_#1@bwk3eZKJE017&CFH|j5^4ThvhWX5S`pk%}y~@EgEvLI!Hhx1b`RTr6*Iq>-Ak;UXM$)@JE*LJ$XF0y7gD$erq-P z`KpvHjYS(vOCa3B0pUO+C#iu;$l>lh$+W1Q?cKf$&meCAa(%z?+w|5^%N-<~om+u_ z_h7?(S@~;MNZpKf>JaH_X=y1@b2*PAGH z1czJnsn4m07HVUij?_xyRvb%+-~(}qDp3q? zH6uH59z~7eLp7P^b+4<_oI}x29$FL!mY;rrWq|+?P-RAdpQ{<-{J13LxGHolhJ1+s z7jmi#zGmOiaa)kW9czL7G9^q12tz}VASPpX)Q*UJ{7R8%MxuJfP1f>+KRc~=)x}AD z7II;Le(2OUH2ck09p?#>nI$FyTotJR7h~-L9UVdruy_B{8Zl(JBw6P;?Ia5*Fa4S& z-52v@$VuWbaX!9ai9caT6QFQp9Bp7KJd0|s_(TcUKEIO8ZjD(&HE3=S4jJHd+e>Mb zK?4aw4z=oU;+PW%NClehc|NZ@$JuHZB}qnLcP_-!+ya0YK#fG4zVY3;z|pGJXFsAo z5D2SB-L3t2geu)ar1j;LA;!{RbmSky0C6Na*-;^Trtqwy--o6CuFv$#^H|m?h>L+Y zAjs9HfD6niLWhm2`6fTKQqtPHehwfh856NCLU*+>atvj_lQ`+b`JNLV07d8+2uH|2 z>}_v?33qkQ$UQ|(9b&vhfifk6(&G#Z{~=+gQ6o}4S|wL>N)xMmE63g z-sanp;B~ELD^IfBrPpL_vf8Jo5=YuK{8@Z?y|8E+!t_K4QeKA_jw#D-EINCzi=y+g zQd+l8PgUt)J9f`L94h`}Nrc?xcTT*XB3v#_lH(;!@}S~0g4zoT4ZKSpE4jjR=msBjg^^Lx z%u7Oz)FgDYkAZeM;?=KeLuam+-RCw-LcHXTyjqFEzzw?bGIYKpPnY<#Xrbgf+KK`N z){CFZfYea=(BxB44FS~|P6?(&e~;qE=nSx_sp%rWQ-hzSaFrHm4}7~K08yhNMB)?` z(|Vj=8<&y=Ymn?B++NVcq2vhQ!?6`f+j{K{2MaCzv*$AWsl3S|bAD|nu1kUcs64w} z=C0BQM6;O#^&PigkKgw4Ic`te`$!szPO>;u{GP^wSBf3fnt)l{7^oGx=sd>eogu@{ zUyQu;Kg>7F-x+#ZG9h-3+f9IamyimD`D_mG!67PBiAh=3W9#7f(hf`vA(JzQp(2mM z%jg1jYQkf>#PRQCgt$=Uw0wWB^<4YEaWBWU+40zG>>j7LT1jVohjaTwsJ3_DcvvJm zveWkrZ(@68*v!EDcs>s#@ZR-QTp2z0=V@il#p~_jKo21^g#t+L_qwQV(}n0#i~I0_ zb;JNp-*d8i!A|W`UVv~!SGw2b*I`)DY+}ssl#x=a(n5r|GOanJP5~h&FUJtaWlKqj zKflWdX14U3PRMN<@rBe5Xha0z0+^WTWz`AZtSoqYtJd+N2A=_bV~cQ!<0}?l7qivM z?`Ly~xT({$=XCo|SC#EDU%Og-??YDg_~Y(5IFHUjUoWft!LITbIJ+2ao|B~ojKY5D zn7;RQ^|0S>!tN6ik7mva%Y$Re`lRzL-E&aEagp&kNp(6+Yy(|=&v=f)0WdJn&t}V_ zyxRLy zBcm&#jZyAY56lZ}X*&lUq0lf8!T~lQnjl`SR@q>Bf(R0$r#rX~czVh8m6ScMN>tcA z(5y^x6)<}&L1rfW8BHP!=MeS8Wr5*$B&z*f!oTQ?zf*Dlf6lf0T)_;WiFDiO&H zBI8(SjWBKytir)3!3&JKY~zzFi(&3uS$A7`X{TUcb4UhIbOf<%Wh5N;-_4(+AZB4l z!GwlJ?<3A?k1I{UwOfoG^gtMbBn`lbhVTfhhH=2>*wBA+GM;T>oF4yQ)S~jeSn0og ztlb0@#%^a-r2QS5{Itfg6r=qyNv`5Y*Kj?r>QW^RwDDpN&Erb%AW=Y|FCapcD_{&^ zDB`(A+3JcLU$0Xg)0Kmx;`&josK=-O-*o|s-205~9-w-BPt2Hm&a2#2b~1z?;ZbUf z(2256qOoECohTg$&YR{7ERAzzy`=)Vvzbz5XFB+|*!V82^FStPqwvmS;kKSn%Nlbi zr@~Y=Chz8Es4ksHYc{oig^q8)d=t!cEE@OQ;_4S4iV#U&75#efieM5L{g2Bz1;x#$ zrlX{w{-0vx7ZfQB+^-D_Srl!oUbSapERmw3DMRI*gP}|dIf&*WGcL*7TZvNBZ{5+$ zb+1XE@!};_>Ix^cgF7{ITw~{gBIi6FAVBDIpmWqzD6Rk-(g2771Xd8U>+{6R$KQ!{ zAWozzwL{+jgF7{FoJ7J{4HRv@!#vkNL;T{}*@BSJs64_=<#nOh!=|zH8f$5 zK3e(COR$@nuG-W|mvcT@bXD>ua%V{akIZ;uU+uWrv{PZLHdlelgv}iTYyEcEv}%n+ z?Z?jxc}MrZ!w$iAsz~A5cS=l}1Gw^OUN?q7QG4U}>i6p8Q+OGdnb^N3E7K>Lo8tiV z$3ZyD2j^vbBT?58LA|O$u!&<^`|Gh|h0F0-_u*Y$+H4|{J9|tUIgq!oN|_FhRQ}~T z%@akcJ1|}ErIXGs0jJvK034C!3ry2VfEo-&Yx5Fpvxhx(affKbW&-ci|-(vQvti%ZF@=$R3V zm8hojw&=EXz2JP>rEOi^%hWIT!CX>d9{^P_Wje~Y^OipLxVAHwwl#;Z0DzPQEdFpp zX$cHGS#IyprL>PJTNR|0McyXG!)UFK0kSVB%D4iF7M~&)!=t_ z3bvXqKR>O5Wv$wYO!{G@5HGp6S4Qc~To^*OqP)#tpSl-bM6(Vq4 zR!*;nvs1f4V7}h770j_HUD7UWbCh3F07d=4r1;HvECpc0u(^qPl8ZO{8D3EXW3}OR z-4@5r?`r@Loz~-GTjLDy&0kb8ij8Cc>qF=>NXt<7YnbY=epl$fT8hU>Pme1FP39j) z9}NX|N|kgsWgrNv<0(R{@)8_Df7c?2LQFa$Lg5vH5{tD^+vkKe4?c1Ov354Ro&SHJ z{TBQ@-(sIb@Ox_TVHzp-F;g6ws9~=h`>2H0Zu;gIvr3BAYk>^#GV{& zA65MPHtn6gZRrAw~7@aUY4rfVq*MT6P^Nvg=F0^q0`Zq>{aFVfwO% zA4G%)nEYl7X>SMCV81!Dv-UkG`k$v$Uy8P8Z^>{rpe~{y)T+$8-HXe zuTaW8(@S^$zx67jF0XA_-&A@kQUHZm-wa3hOJnH|*nQoq6E;)M z8#>PaKTELAC!lZ3LFtyg$dmvB(lR<8s3f5suC2YiNj zm71XuxtD4gfsq-O()D+|y^4{cq9RJFimIxrs;a7rs;G*ps*0+t%&5%9tjvsztZJ&t z%&M%$XsV{Fu}q9O*n(20%9yAW@j&U=JVT7%D+UqOKfQ6AG6QgloTAdE#FbYUmTrIuK(wwSA1&~N$M~8r@ z12i59&p?O@BmhAU42G_5p3_t32^XLAY~$bFau5aY)m_-()gs{i8A{>}dc=uMv6bRl z9C;oL=ZtIy@#SC`aic^67>HuGlfP8@Fviv`< zww(Q>W_)(G?>Z?I!K)I42k2NxB9T~ST0xg#z0c=vuC0z%oC7s8G-o+pkn$)+!z(a` zjFU*<0r{I7Xlx&G1PvtGI>xGY9%oOR$fG%T_cv4LxjS&VC+dF`9M+;L9K%5aY1(7E zb2Ijwu66Ms@NmjcHpXkOAz*#i?%Bud*6~v;t+vdN3dIBn5(TWxJKu13*-IzxF4(7^e=WdSno!_*X(E!brKhjS1b8+X&?jQ1W${;N%CV*QciZ5L<2rf7!;-lwSLzN7PJ`VV05bde)IGD#g&Z-6swSM>Q`lBB{d~wVpuyI%t5LdqPYg&;-OCuv%6fzIs z1#!&t*$pb&qcnIOKJ|vfVJLSm-uF+nuBaK1zM)!ti{}19=<_O)?d%mwt0R2uA}F4< z$O=OP>UJ^-&xM-7!qg|&5-}9t<@?8j3imV`v59yhx~QRsCJ-di#xijcA`rTcQa}lN z4FIHv`rjY{ccg2#=|{WnKkLKD%sLG=qf1+1P`n=a!V)aN+}O@z6nlEGb~<&%#!=lW z03x7O&^UpJ;^hyg zBFy0QEaF(VGpi(PVqI+2<5j!}Rw@BinW7g_iSk5I@_qDDiCf|qZ878+fC~{WdyV;m zFI(oD(-gpPML>p-4zd3WkJAk~U~z_1-Km_;_ZrOw!L+Jjl2}r<+D5JW?Y&Z7ssIpS z>-kLofl{w2ul!rj%|%ZBDb!5|X6Dj)tK12){(|^)e!stx+39yx`z(HQZ`J5-9kV_J z3Qoo!_+qMdR)O1Q3=SdRlZWT6{%x>dFV-ru)5Fx+``It;yZO4vrlv-?bShmo*QF{Bu} zT!zZak@Vm^42wrMpo#^w?JMv5IP$kTcmrF%*ZmKZbFk!W0f5DIo`uiEVdf(Srx&hx zI62o=q2qf{eLokw<(vri3JY}c$J>Z)GMpudE4cWS{#oEEC0ooHa3Ebdc;0zrx2;-= zZGjA`=*gRdUsmh?E)W3IL2W)4;Hi3#Xh5@7n=aqPD} ztMxjpfm8>&Ft0D2vtBI#2=}mi65c}_-mkV}E2$Dof70BeeD3hqn z=sU> z6)fuIOZ7i!ydna~LB{WWGBRljj0r)ZNa@aBP8zlTKMnpz2&b1@<+)&GeIEzrHcw`v zK+YGX-E+DAm*xB9BBtQRH-spsaBg_Ao%wS&Pb|xj_aOs}!t?g-9psbyG3A%p7>DDf zI(_fr=pX=8>9#=}KB(%bbMHv|Oo%{$^jP1aL#w0PzHa5e$iELttS`Hnk0h2bNd{4X z(pISS7PHh{fpA#Iscd7jCr`uI!k>prSMFXsd_&jr1Hwg5LCg=Lzut|`iuKQWxQvB# zcw!rU2$ymOR)aV_IS=1n*h=f<-qn+a~rm zs!=L>VyC~FfU5~qEf6j9wRQv%T(zF!wz`o{#kNQ$PM2P`@;X<}dc`^+5u4uhdk{3B zjEnb~La7}Wn&t120FM?Yta$OMJ@s%_CXN{9V2i(K~Gx9l?Xk~1g_eS zJ@+CE_e5gPNYa!>_9z|;Go#5NgJwqx4&`5QHr-H6m@gqfu$<#5&_sg`arY(EIX40~ zds25i9hNqb4%sS&t|ZPND8qfWg%nj(@XkB;L*Vh>ITK{~Z^YU?a(-7UwAnyf>pj); zt@K{AT{_i}`{*4W4mF zOKYM`$Q<@ebN5W$$jOMC1k*LVMlbuoucl#tnp3^)hZXxU z4~1Z0LA4swg6dYTr~;`FLVlZB+?~1H`cb$$N%D!46!N(Gmt}~eIfv8781xCl{P*4i zD9-<;T}BcaR;m^`T$mGw_~BDuk2D2bfFwS?#sq2+od^K06-ZKSlG$&nLHzTi;>~1T7&c^COw$vRIxe$0_ z_efWhn3kJ(XdFfPXCi#OAeciA-CexEF?yJW8r-4=L_yXSr!9&4v0LR=3uBn*;g5Yc zfq!+1TfUk*f#)A_Rr)5W0xn6z3+A{^T5X=Qh@%w#Yq;Ttid$K4qtksaYZC7Nk^kEI zOWHlFX`)cL2zwYf_-ya$5o`?Q<_G(dL!|%VvZ7RGqBI`v;vkc9%k1y*@^W4m26&i|;QTtt7X;rxkdPxq z;1mdG!h})<=S59`U`3uaSh+YZRKMrJ{gpkkogT8F762Bp~T1utjJ@_b~9 zsl-M!#y|6w{^cjF&1@6!<`YXr4t#H%bTaF;>{tzNu2gJsC@ z2p=C`e@dVZe2=R`7wq&JcRc^RF2R@C3iV@1D4kUt41Vf7<$pUw?8^<*87K!r=ZaK& zU_p+KwTReCR6Ad=nU`3v_59CQXb&S6ft^T0z#wU|$a60c;hjC1qqvv>SUo|Rx2;>O zPYQj}6Bs0@bsS@kNep2t3?vRq7!Vrr7P^DXwRya0u=ZOXov*F(oV2yy={P+v4PLIp zyU*c&ogJN288MBZTU>0MweL34J$6%tjmS=`Ik>6VU<)w}|KH@D7<^?7nV;@tJKPpu z703)_w&D(BeX%lhl7;}8jd`4Qe0p^~bLV5DhDTA?h{=iYWQ!ipA->~<<)-J&?Io$h zT;jrhNcNDzA^a}Q&O3}Yq85)3e+dr(_T;fpF8a|K99|wW*7Vzx?n8&Z%l_AD1O74o zUaxVGei+^!vV~2mBy$7CP*3BYU&XnJti#cpaks{o7IV1WUO1KRc9PdPyapOw_a?@y zF1c#&j9vN&zwa8I;D{g5C;>toq$mp9;>g(xj||5}4)JP$G<^o|7kHg7c>U(tx7g|^ z@8$Ea%yVY?o6l9SOB`UoIj;v)LP53D{3Yf2-5m0GLr0>8=*jR}yxrC2a22#}+d;CvSzZvdB461L)&X=o|s! zL>C@S!?#T8B~r_pi1r%BO6pR0-_()mX+Jr>&fBxb4Lkh&J?G%KCiPmTB;P5yFd_7m zqY5DyARq5_Bp1_%gNVea``MbTn%?@#jOzv!GZOe`46_@CFE7f}b|O^4m$Y6E92PJJ z6x>8V`BSK};$B`xLcG*b7OfW1_I?Z>#O}rX3Y)F!QE+7~*Pm@NK&Dk|Zc_D^C_+cwu% zQFZv!)K|L26?HXPhX$fLav#^nDgdrmK;xo>0HX*X0x;HPn+UF|60v!eIDC3qvSGs3X4KphkpwS_qyj|XCG8OTz7tIS2z1)QXh`m?%quIDZxhGhH z>KFZ@kFg1l6R+sdZK||asMV|dpx(~ef+#Nm;?$f#ni~xF0BGDohn_dT2|-ZDh-VL6 zLNH@>ZR5EUb3r9W_=hcRX@9Jt>8Q@fwc+hvR{A=g;V7v6eP=wB_C-AZg-I*>HHnmHpY*O86D?X2D0CsxDt9*8k(J0AMXVx<;==~@q4_8LmSiXt$nB$%h zc`!<{#IrLz5%cMv@nO(Q98`5V3aQI2Y(qhVCv z!%pv)bDpIjXV&G&9o=1_`c#q-m_t$mv6DZ`;y*ergdI#rTJ7$8e{f7`BJd)$weY{P$1x> z1$ToVKSYFzq5H#Eex2NPz1vI1oL?HE#t(@lCX@8I45>PW;k8}HijDEVRDkcG2wcI7 zhh_84o)3$~th#|PfG4DKB72ntoePZ%hH`D4UN;P;i$7Bp6zZ-cfBke+qCsOr#bcd} zmirg*%_vU-`tc-np3?EZ#qyw5@)%xyUFoxfkS^kcgX= zwz2YZ4u|Ns$#CD|#n5`E)+iEk^`F_>dF{Fvfe-Igvdj z?)e}Dl=HzFEPD@|n3ICWtI1GxGb$b7&>o_R}Go^9kvyxO~uA{PP7{DI&&@fZgl`~T##~~6*u7JZUmqmITBR; zT^n=LSSSkMAcLEZ1>+r8I;Z$;vsf)!((b8;sSpAn6p)56PH@TtiU@UQ~%cfyWc_-hxL&n8=UNMd&+Bcf|(1;`)gE8ATLuwH!OBMq5_*|0 z!p%Im-rB6MudlZpjRqE<1^+s*D1Cr9?caY+;`MdwI0LwF(@PkyA}5qS!T=5e9Ik)I zP4hg2f_i#N`kNs)z23gL+?;*^qtUH&JzvyL8cBCjOG~!ib-L;x96YHXi}XLBxQiuqqHMfmDpJ3FoeUTuJbK9Dj2=O8RQks#_Ej25RJ$>%+p9DoWxE>lP5kxA z0pLJ6XcP(A0ca34VRiCn8t6B@J(Vpd8cn?=KDQX#QMtvz^{*41JdDS(A0p=6@3hv` zM3BHiW?VrwLD!KGI99Q2ndfKEPOZeEjqo#X{NFQox;h@t^u8A!8UaytUaQl@H>D$6 z2#uaANU7GYyp07xMOUBA{9wMW{)*+ylEe`3X5@BiR}GMp(g84urE?8oLStpE4AMy>NR>93TJvd@@6!t*LiI>(Sskwa&4z=ac2m=^j1+dP-Ni&Kg z|AY#GY2C>u28NHH0+CxK@rdpi|H+CiHjcWc1HwZ{;$)+z5|Af=K%I z+0B6hhLU&UyR}JuRu5`xyKQprwDaltLhhJKLWm9Gc(sXtebbryij(RTb1H7;B{Xy@ zgM@GFzir52TGjAUH#nA*_o?gvNTc&Zv>*$eY$>$Jbo<#j)v~TjiINZc$1%=i;Ux;# z+AY2t=SPZC*`^Sc%4_VQF5U9_7reN>&uGVe z1HyWdBcZr5tjYc{+{r|`yXpZt@A$S2t6`!1!2U%USRe<5<73xYjBq1vvTS~&)I_6##F`Y$iqx{s8c)p zy=0d`ft;g0k#q$RpJIC$3RsL}69;STI7Vji5B(srMks5!i2nH_?Btu_%Mq_YO=Q2w zM%s*}ZEIr92Im&s{2)Kb)u&6RIO$z9Bvv2YAj@3iq@rl_|8y@2=PA%xHk3|=$h@(y$yo4#(ZgXw)W_qbvIpNtS<)IoTAD$J@ho%KqU*niBFIu{4H8H|>p z^}j~#;n8@P2n)AC07iN=TwF+BPX6%L;cK!0!b__ z?317P0ZE-xf1Y$rZ=21WmqrPIHDyVtx;*H-J0SE*@KVa`*sldp?wuBf!sRa?E=Io! z0kOXL$2BMTP`u3|U~0anFgCK?dXT)>E|%Fv;~5Mo*fu-;s`wCfBFF} zjZ$E$^CcJo3USQL+aR&J({Q@R(B*r&nVk<0%74cHf=c8Yet}R*Suz~2nDjcloqdGY z8a~Bq4mii4U^*TA>LNKtyECT+3+=+%sz!f7LD%3nbL!|m(z?|xx%k4HwmOWZ%Ubc) z*s}J$c|`mSNn>~g2hbebB^wk{3B27o1jGa0W}pS=Nf`IM7&^Ck#b-O>NufaFD#6?-cV413?n!4M?NQbuqRaXZ4MP$4~WX!#KG zdJ3Q!nf(U_KH;rH4b83G=~e}Xc^@j4aIcx_;FaxZK2++}C@a_U8uS?o9vUb>VPnhu(Q~B`#sbfQO5HW1=;0OyDCg%MMT}f^Dpr)&lK)y>2o}$ zO$Y*G!bMCpjF2O%K31M$f~8QjGVWBtI9w=KnGs*? zeVXC=MBoS8%kqU%nk9=cxMpXWPp8griqPQvSVmOR#qf`6%e)gzg0pOe<;h|nJkP-` ztKYA`KL?|#Lr2ra!3(@6Jap#;7Z^&6-f>-N?JY8UFY-s-MuI`*Vbw7_vmCW~lLuQk zy6v)9nYg_1H1;C;3i|}LPDUm?ESj}5hqYZT?!ut=oks%4ynd~J0qv|T_0#>8zYpkd zD$IV{h2fUfkFVcwqpMGuU|nphWnyHhChK6kB}lP^h^$nhSyiu|EuJVof!2t^DR=K` z<#RPwYj&r>2A~+W`n;Wd`y{mI-pA1WOx0_(MzdSUQsp5?0NYv?Om%rnh*VeI?kdqp z9>_In-_${8%C#Lz;Eyol#4PLyisz->7riHtJ;qcWCsc*5+s?hL(AlD*IK-9m4eZkb;OB+UW<0z5j*lotx~MvlU+8|rf1U(+XfYeCEe z5fT6{i5gXu?X-IWWPu>e0u>q{Og&$VSBK7oMg`xadIQM3s6k+br-S_F+<+8-I?9j} zTy0QJ&~wsL!w=F*2v0Sx12x7y096-)S<{TN0cheIyo28gY`DTKEYtAO|QYgJIpPVxnbqs<*HeX4* zPY|JO$B*CqWQ3QV>I7sw;s>rti8B`!t7u^Q$d0Y8Z5hwB1}ozeyBND`|1;^+I)kii zsZ&A#z6uNO`#fcL{Q1xQ#Obj%w~uYHgudgs;re{My=ljdaS9u%dCx_H&VC~P|ABN| z;d^JDc%{mRTwNcMgXq6vKfchGFqUL|Bw%3M)@eG2V7_SRSv*J4nA`eQ3ORCd>m(CK`UlFBFqSO?&*aAq*ocR|+iFj_Zu z_%?6$rIFzlwO zP}rYjYLjjriC-eF1v0PlspXQ^)xHwH7b2zc2FaY64y^(-|0Bg~c3(c^$q+@z9@uqx zw)~;3?3-)SA|NS-N!K*`he)JBUD+sem(n03!yG`HVe#TlX>|LT>F*$*8-L))#JZ9= z?rR32c}3aiUfxf$?fsoydLHQq+2p5;sQTa^VVWkPp#7{#PN&O%JA8=TG_#{uf1Xk- z-5&-ylo83l9oA~jVWNEBA4NCR7}KYVf`m9KFGWlz3uwrIiqQ)2PT&V>l;synvr~X(vOUaSBmknNYV%>`hH0@(d`D&BlXh38qXxkFlH39Dk_tz*2wk_k zqd$4N70J^I7%wZn&r8Qvicf%MjNqc%>i)@U+}Zp^fl`HSml~T@dfEsNj+@hE{_^=Q z4@}W;k4zHIh%!81No1KNE>})!7|}1NT^pVZe%xWE7R;dZ=wF)QY_(nv<0hR5;Y9@r z_>1?XLbCXK@(M#_y}V40JaGtvmuVJBSUlg$pW)}O5vsX1l7wwWpPxryVl8*Ei*G_7 zm2OaBN8I?&ptxu(Di1>Q@Tsa5Vg&7x$@#eG!hW4JGwabN&G~C26ta;&&)odpTb{?0 zCC}w^uCUubniTlXKi<~)_K#?~@nLa$hmN0{DQW$G`u|`uxD8tJ{J&A{Y%Mici*L}4 zl7P(T^-eTr+=j`1QGfHydd0B^657hgl%Ly;Nviy~uI$p;`+tc-!5yKkO~HofB7jcp z(G4%Ovx*#_beYgy_@uJ3&i0!;DB>CM^0*?h!)pd*oHi`P zy)w4HeXe%pU9A0f-E{je11-02kEi5q`xl=t2i0q{Q{5acX2RZV@N$LFYV0yu1W~hj zHY7kf!a)9gia?f0S2kCgYDPaMH_+XS#K(Riv}&%u82%*7dltzgZLu!xUaob&nvz=* zrgu{|uYJvLOqBgjg+I}=I&4;rg(~LUzJC^PPd#%&`1|aVqVxMB88)2@qg(ocw8OOQ zFg~0DUM4XaZD#T3iR{^HJdV0Xo*QoR!;H~;(b;`AZyoDw$^F;$CpvgZ zE`~?j?&&VE*l5zjAeT{f=*vO!-ZLI-F+brWu!l_hxt9m1*jfA!=)`{#9xj~;>{XXB z1*tO4wUZ&EsxA7u@ zlDMn1@$XvaZ-cIetH)_k<*&H*JpRt6jjBoySjPvH&_KdUaqQdeZhhycE}YYi)79uR zMUi*Sy@MYPB^YL^{w+!10Pr9WTT-+xLW+i0d`h@#R~e=col`GfVp`k#CqFsW-)?jG z%H6*wNW-TFz8qSK{?!~ur^IS{3y36XEn>}zf zI|@u1nv7I_56rFuyWY!Pr#1g(OuF7uhRNyuT1F3c%Fy5D1vZylejYPbswg9ME^o5j zanmyY4jTKLg+r4sStqm}6nD4c#aXPlOL2FX;!c6$THM{;-5m;r%KiTCdvD(V z?lZ~cnVd7p$z(E_%qJtMC9NPU3+4Px4EWzlT!#Pu3;+Q4KSt63CdehB&!DAWdpryW z5K8{?@Bg9O|B3AptdZ0bg!!H`4m-6UJRywZGKyI2(x0qVl|L8qN_GOgcnugoUqQjL z*F`j>QQ#7$Hx)i+5!8OZsHKPe@WU$U6jS3OFgYChkX^~`NGqG4e58hEo4NoabyTUOELkFjp;Q%OV!{;yP)f1Y z0RYr;0Cf@`n5m*TR|`7fYg=9(I+w$3TL?wYf&G^RLk>a^2LNCQ;+X$;$pCQW@+u5z zgG?6{X?M#Ae42q(=NCC2w3fzWY;)`!Npoz^CWxuuBFb~M;Ke@4GKUIM*t%S0RYhdDuOFS5Qk$(OMxi_jRS*neB*L#gSlaIb91D& z<+4Fz$g)BIO#}ciG5^b)FINEo=3tg^g_ro=NFsceiBP3xc} z1wN6~T``2pb+hCr2|Cx#61A}4syG~1tE@-?!jwX0LdXmXadE$cm5(|eY6y6%b*Iev+)Letam^Akw0-K!$h~v9 z&ztK-7B6$(zxVU`durp)jZrHsq$&LlA??MUF|%vN`rjY0b78*BT+z$Fj&EKzcN#~q z?VBHn47PR8HBTsv^UUqkJBmI9J0#oNx#_Q``+NV6M2J0V@n(cA!MGt=J5#lBiH#GP zssm!n1ODU}R9v_0b4Tq0i)`Jw9Z=)NpY&Ab)9(W7e%)AyKtc0>u0oR$HqqZ4*}vba z1%Aw6p} z*Ecg|*_x-0ZCT`{TaxTUyQ$yV+yYg4S|yFeZfR-JlRv=c1vSx#*o|ARO)p5lD>`0x zvs+|xk{lW3HkO|06%1am?G+F>9z{Eq`PR(Dn z^VG0XL%NwxQFD$2KXT;um>k_t4VFOmU}P<%4hpu>M;9&eiR9sLG%fHLWcSP1`e*G! zKx6Qh=WE@b#Ot8yQ*mw!|?6Gtt=o|4ukG{DdBKTil05}}*}*4&o@qng7% zbZ>K;vnuaU$G_6WzVmBP=iYlZYPhhR2~uV>n)I%Fc;|(v{E}!_E(7(djIl5pJ@;$m z=qv6~x1}nzC76zXrG@-(aw&^N9UCE2i5KqA)0R3{$W6W-1}DXdIpN*)&X~tyEUiZO-iimLj7rEq2-vV>_qVbf{Sh*6j4HOKT z=_UHO5W~QQ60`$|T!`3N7WiSZa;1!gjf3h;TFFDdqs^2NX#uI9ioUe34v@c8mbVb; z!5B+;dxd`L27d2$b1F#?>OsCOH2Jn>{!J0h@7QASnV9ll$-c|dT!r1UDG(9F!u5b< zhCsh1*-5||IoDvV>8zij3|1V#7!y#pEdG{NYVJenGRa@>Ba#0^i*Jmy0dhR4uf`4NaamfXW7 z!atIN;q8L}EESN#In*ecFF{i@UUg{C(UP7H?(}ZM>nHTXdbnDS^rVRyZUjkUf>Wg9 zE>R#spUz0+#HpFD@KEX5BIXc^3!AtDi3C;iYy9&a9w=QL6X`v!y?U3 z;=>~}fNOjh8e7Y1ltVmoByEcxs1Vc`L{`4sc@L(rOk?kyMoh<4NrSVzk$F3oS^}HbFtk>Z$?C1(*{H+%>fbAR^0FZ8dgJ5-0{mL6N!hv)9 z+1p6Pvvza4nsoan#d2x@1$df29H3(zL-(=%PUP-Gaf8VPe$la3vY2VP_n{f4v>Sd# z(vgnh_Z02-?bi1o6rM|pR|&p0C8^>ceq_g3D@hYVU6J+WtWf!201;`S(m?hD${Gk! zdU-8Z`t7>*3*3Pg2I7EXmhf9{b?~R_(R3pKLCwD}b>+{R2&m;{rQA6jRZrM`iX33W@Z@s z&w`)3_B$(*=T@9|AGIUn-9h(Xw-IWK7I=6B!cwf|ecT1HpS?Zp7bu$M&JW0u?a+bc zk(QDse=Oh6^MHlFBFLR)OE+=ciE*7U7!mV}lCG~7ZAx}2$R+R*Ta_tsT47)J56g;` z^?{&C0*+_#R}13JrK*ya#$4aQ<(LLO^^h@1a6i5tP7(zFV-R*UD~N zDyFA9-ZrOIn2eqm{8~cKnid@~wnmpRKK9R*Yag@1ylksx_w~oJ5^9w`L_pXd(r|xDz7)VJNVPG4{}EiQ z?DD@t{T)he9>ah~}(_HZ~eP zgp7RLXv&H~hi&zcOJ}2AWu8*Q3IQDt5thk$n-;SgQP^LmD%UQ>X!u?fIT$tEk`@u9I;(N$*m zDh4TdhjSqdbXj1ny|S92M#>(om-j;l)X6PjoVK*B7i*pK%qb_-l;dtHQ=A`gl6S<) z$Qqh}U^tQDhvS&_tP zdW4)m>v9p%eb~i@uMJr! z??+xuT!JQtAJh5jt*}HBy|p2Ssj)3vk6*s3!^7F4LKR7E%2b%5P#MHWR?n>`Xz?M+ zDP#|yJ1fu&6rg5~R<(sLB3KCt&$aXOsWQ|C8Oz8U=se~GoGjA#(R2)!>^I9>>U1)Q zG4j^muPj|LThakSh*KS2SW0o-&wo9+lXQF3cSQ@ik-HKLG@|13s<&UH(}sF4-6 z$M_1bjB!RM+BPPpkF{TOEA_yYs5;V6^h!?ljck8`l=B)161-3HFB3lFOAqh8(Cw>4 z%X@%Pfhyz;6t5%YBldHpy!BNHnlF!IusE%u^(5X1^5IB9UoD1Jmiph6k_eD-4O#IzS= z!5%~dmPSR4T?m2$hzBXz2!k}V?*)YY;C)wZq20Y=;eTcP8@rw#uktvyyH!QI4R0I# zD0d2q*uK1Qd9nY~`broujEP7k^u6TwzSS@R7M2$+PHo-EzW)yuB&25HEA|cc)ymuP zy1Fj~jf)D$9L?XHnt3Pdowuh0aP$BJj%cW< zfv7kDIv~mSAFIC((%I;{gJQA-eA=d0X|8>oZ@nF^`(1al!%tnxpR@=px;o5;w2XeR zto}jC>~{L=^tp|*R3$1nNE!gPyiH@F?xVLn1`RSxjT=C$V9X)E6a5~%>OOEHdnz=V zGV#Wz?FovZB<>Z$IPB)OGu1+bGxO+RYMB?YQ z_p0`-H+te{6GQ#UANS1utW!2BlRKY#KIYCg3>j^uYyV`ycmfO~{?)4 zU|y#nNti;+f+Ry}!;s~X;ri)O!MYT2b+zxMGc((2p1bw6=5OpDF7mO=OeaGi1Wo z$Z86`MNc&L-oF%Isk&TWA7m}zl7ojjduB~n(1lkna1TFmS#An)y%LJKSRGro{mbxd z!nCzoZk-aXG`R6AU%WVkda+l`l7Cd0v3aU*DSM=4-C5+xKJUO% zYTR!lQ>rn_{{$gpKc|xfe#Mty|4QF((7(m5$CSc!`@$Nxo^3Vlyh~HtSi0IYkEM|r znA4u(sMMI-6z&H3bE^<|iy7+n83+_aTZq{QUf_r~ zYRlNKG@obFa!^bu6E`Wy-wvcQ-R@9s`m=jm=Dj_;_(V!}tJHAOY22j4>sgM1JA_#n zBdwURT&I7|J;_R}t2jj?NG+{Si1tlDI88r#;Mb0Q#J%6=1i$IypT7d-+ArlD2v8Jl z6S$nq7T_2?<+KhUGPjeRc*QUc$01D(7D-YB=;|vhmvG^LQ@O}M)yx}%3uQwJdvw=q zlzfIAobjce-hbkF2Vf4q*)5NX{puGww>GVu529&qsReCVkDvFvEN7k zf*a-%r$dHLZ_zMO*Hn0J<(EJGWx3scVVSd4R*(*pv2i|hh-{_x`TP7CLN}K_=9)c; zJ9R00=kP)l{q<|uc%9-tw#Vsp*s9M9&5%447>UG;q+Vr<)cGl2&bF{g}KCxUvyorQ5-c{x=M}J+fTe2?< zN;@yf|81S~2Fcrv{G#6boOcAI_Bn4d;Gq3!|MKs!blGUd%%}l>O;SQAscX}BIm!R0 zeJZE2VWvL!Y;nTy=0xSy1MJ6%wG{vh#d^LF#Kg_VL(WUq9m$rO3ru374ZGdW?)#3b zc*Fj2g1oNbObFrBhJ&ZWsW(=p&%(tL%=H|DA>O*p!ERACs97xC1wR%uQ*`v8x;JJ) z9e)0*-Pgb-uB@bvsw&9uDn_pLl_cywDY`kO zOQF#N(XJ%iNRZ*y%`LU+B6uuaL)^2vb@Pg3 zT5-w2vqKC~Z1BZQuq)^Neq;A8)vd%ApbZig=Ti4_pX*;kRR&if;qf7!AF}m5 zXn!IW5L@gCp8X(!5 zhbt!4ld)*BvY|0KhE%iITS%@eE;mRhE;Csa#SvSPL6*KL>7mn17%I*e_m#M|b8Z^w zHnxTs3v)IR)RH!*OL*f6U3&_KF;w3>P8I~h z`7Km;OUkL`r`EF}D-%sGsj{ISblO+hcS87?nvBbwoDP?hMhoTNK5=cyC+oiVTUoJV zGV>#v_$M;bww#gye7NDhlAp|5&fzBLR^a`|raO@k!Ru;UeO7u<#h6uz=JlbtD?QTa0P(B2 zARy*2ctCNE7=I7}hOJKPRC+y~(TSod{UU@?;X*XUibD7+@BZP-x^ghLLc6kvq)_}$ zqK32tUT*NN#EY{50YWMpCe4`%yKmT7B&0xrHagk!P!)p)*&GHh9%VQkz=c7hmyb9& z|HE@;A`=e+`XFbLJAaTA?Xb1o*XO?Xb;C5?DS7b+I*iNfhcfo2j87DHG*Wp@u!%y* zMv-g|v?hNc(2==1C(p%EJDkD(iJ7zQ5u`rQ5Y4K9iefH)_#UOrEa?=N@Mi+LH(4sA zi;-eQ%4S|mflp(Qw_5oD^N6m;)o(F_RGB&omMyyZO2F*^#8Mu zCzgFvK&SZsgM9p-(82r~>9Jg@N5-IR6is4XihEiPkZ5ppyUAhW1pYeHAUBq+$w zsci<%P}rjlYv4K_a9$|5iFm9Hm*SCDYkM>6CW=diRpFrXbp7?VP6(o{7WCQ4sU3Q0 zO$zs2nLtJwrL8!$`~I$Ndgi)|5czsBJS}sV_9E<^ccIOA@S}|+P%?O{O#}Y!m+!J9 zDXF&zQEL)VvsxRr^-O&oyZ&9!*okhsMpLUMMKJ`W6aEp%>o@WfYh|r3S_15IX43eR z1MU5K6EI&Mqam^XloIJe6O$ybKlgO`UGQvYqjPYD%HjG9;j)mDQ;7HgQQQ=sRFqda z9zGLb9irgTj*bTR^7U9_Z>QrNIBc?3kmeaR5pG>oIxF;u#oftC?RuyH} zIH7UdH92m*^dyJ^v$bBD8qUh4b>y?)bFjB+$XID<-ZjivSoJ)%YF)97YDEr$LKPIx zpDuEn>Za7sbM_Zc%MIF^> zPpt2Ut4#|yJx}jb=(AlHsxRFKe%o;mXqS6Ab@_+0)^gsJu_K8Vy3xa(hc`y;3n zK>m{3<=Az-6LBw+>o22@@*OiXz!5EtqX+2J#%Ijo``$gZ zYrXSQqSE8H@eo>|=G9$`=;X`vVl%no)xEdx*D{-#=(xSB=GSky^CHSM;br6QWbi(7 zS}2zP<`~dFn?K@r8xZSvy#sV}^R5m>=`J8b+jTn`!a~?JJ9)ej!5Ut!tb|MTS?!v-h|P(Kt%uApSBF4w%lu4}An?!K?i5k2 z8k4%qk=otoF9ADGdp~_VpoWIsVy|!4!U?$DFSCA5Iixo)yKYMXG#7??q*RXBa-x^Z zIiwR)eLGoRGCLjDf}?(2t4zKD-NxS?R!&Bi=WZqzxks+3O#CG0CH2l8zgsOWZLj|n zoiDG|+VU8x0?!oq946O{b~mEwoz1uwv^MjO&;E3&6Wnt> z>W0$OI%f zGz1-rggrY(zA!os<|J)80^BI5l|n;lG=a7}d{C7*CMXE?zf=bC|2n+?>EJC9#1Z?l zNl5{Cc*>y?sUU`232qV+&ae&Jv}tI5R3Dm;L}3sJ9u5jnfzH8OLXm^u%}9a4+!)y; z7>eEuC5sHQ=^y6#a!3%g|KK5)GgL%haA`fL@TJ}J@>NtMQ!?9BF8xt|im&ul(;DzC zToji;L`9tgA)4EULGkdUHy9A-(?2j|VS-`VmA)0Ff1nGnT&;0R6=;sHO0=ldFh)c? zCaG_tLJKJrHnu1wk;;p$Q79@-Nr!7oLk$QDq7lUtALmp??Z?2A!u8Eh+X?@my;i=6 zZ+p%K^_5adrJGjMh?O&4*VYrM%9!Dv@(b6Y3zU++aao|nd8n_bMyfJ`x+SS^5CFKhNh zL=M8C3qza(+^x zSJhDxAh5lF$R37H;0I~o=nLxq2xyvmNJ>JyZi16|L8_UEa3Tbs5zwe!3L(pZ%V=lt z-_0rLsT`l9S;fsUU-#^R20@SR;r^U2qR(vbiGNu$Y?s%8@7y@-fJdMSLO_}yz$8*hHO{k`xjJRsD& z!90Tcg_T`14ur2RqpTQ$aT8EL6&RWq(?$?QE#(2HvYNgAtKjqxQ%hzf#^)%0T8jo$ zR0#Z?Cw&{f8pRTwktIFuyqOI4x0zjjWgkO}W%6Ad3$iX#;)0$eMiJ}vd;&4s`2z9w z<&tVNE=YBG6q*a;+cNCsPKbc%M6$?bqUC@rp_8Y1g`c^ZgED%J^DebKz*CyxsKk7=M_EK!AI(jNX+qjCL4>|623^^W`FXq!&vP(@h zh|0;bB&P3l;FV%IG_V!{E6+D(Ted+t#a^gs7iYYT-ja{P5aVWFn+5fdlTI;@D9+9Q zY=<6aG_F&zB|2^4?Ctlp&mNO;i(bqE_8Qf*w(F{XOn4OO2tpzig9Qg1Nj6TlQQvIW z3Uz^)DlhP6J5_g9xyiRk;5ocJ)Mx@0oU{ zTpb!A6MFL&9eVq1Af-U6&wtJ{kuljmx9YIz(&+Onf3JW0o9A{{exN-*K7qm=XZ~cx zb{WRhaPz;xf0VWXvvWXvUNH9tm|<3SDDFvhY^=JgUL8OUd2=nGZvW~Jfyv~uyL2Q zOD{BSDZ>SiETcWKkV&^lPg0&eTwc(lZaJu!Vn=aB*Rn>a4DBn47|%t3gj&>)WsJwe z^}g@4eOuz}mKTm`HOA#fB(0&R&peL-6#-;xCL&>&3#SDFv>ED2Lh;%lQxCmQjEnt- z681Q<_g-a28;$}$c3>O$8HR>%o7gcnMYStDM8nzF+)wBreI;)XT)BCS{15?LFIAhy z5rBDzgw=dQW)iOkCfZ$a7>?hT2R@{3BK>ZN!Zf+YWucV}MP9px9gvD+~5 z?!2}hHX4qsN>s3cc+1(C`4aY&L!=m6D~E-!@b~0T1^jtGzMCG!;$w(AKFnyX5?>%H zdcK)cL~-u_x^_&jDyy$9wtsm^ap$BU>wLW-=2lFb>L_#(`s^x-a{Xoe{rZKRTu{J; z>0>Z;0ji3AYZalzOsX-1VeP)NndtVXy4)_aE{!XNA)g4A%80$ZrNrMYA2B@6X{4mM zY@|-T$Id>y!kjT?SZWN-{B6}zMh^%ROhtD_c9IW*V1JFsj(l>~-(>@!79T5)wg3MY*`4nnL}<8-_&E zWn$3BL73tB^Ue@YV&>h?o_m1}P6F5ZxNIFha;2UZ?##bGfKT1i-7^NZikpxTIbXc&ee+5(Zo@}0~ua72fKUsI~^Ry^KSYCFAl zk|qiU7M;auIzQ>3$qGBqL^_PD))y)wCymd~O(M0WRj#?!*)N<#ACx#QY9Uwsj_@Z@ z$Vadq8Q(dsb)%#*5ou29gFbzbpUJ3h29}5wc>s~sLUD$B0#mPH0%2)sGM!}myNH;y z>_$SF3{7f_>{>`R#CP6cYsn2d3h%#WV{)k7pW8WT{C-%vYoAZF-gW!Z3pZL-yc7nB zk>2t~!cb5%6<1$p(IsrDK2v1)G0CvWBVk^Soa69yEd7|;KDuL6z&=ys=;e>Hv%A`^ zeLWvt`hL}&KV#APcl$5FmzQhL%ZQJ56&^eKl!q9E5hfxJ2GkgeX+l=$eM@?D{M<`GJn*1nSrL;l*T>Q^w8KT64xsc3$e9Qq#NE1p`?{x(hz%ZBLuH z%9WvbVQemY&1QlNgA_4yxP#%&hCR4dbet6)_U%Q#%T9Rl+fk{vA#~=G;qoA%Zevnf zQv=Mtu21{0hvmi~@T*$h#2he0On;YgL2cj7VcHQeC>s%<7d!ZNAB0}S$?1mx2_PEM z${M)bN+LrbuIzdf@dlW}XZ>A>bs^%nN#PTbkfk}(@h}Y*8E#`SR8JMI4sWJ6Wxc($ zMy-d+icZr{aW~+gg>`eqVg#oLi#~a|}T}!vK;iRdtdcK8c#)cf8 zoT}gIAoqZIi!A6-T*+tSxFPQ!#z4pFIeRfe>4j}IhH(y< z2|7)qNPS!LOOwOQxlZGEd_;k`Y;^+#j(m%!jwSYVW@{`C_gF8gVh#_>%hon|ogRdS zb{CllEqjBKgKCtxzB~ttG9lh>`TozH0@tW05+)ro#^#Eml8F=#Ru{!0|DY#m1h)0LP|m@#^DJBbWM- zNLYgiu{tXz`37HUi6QQXm$OZNU((+s;J+kqzjtjS5ppX;M0VX5B4#81`-M#OSKqiO zM2u?DEtc%*i_YkX+FuNlG4e8`9r900w^9>K*Dfd%N^k7y~zldMcts#1S z>b#V%17mP-a#k3nN$w3=PPaJNi>0V28FQG1zj4T&BOjpOdo@hwvv&2&53i)bzd}Xv zdSB1n3aT*+S5V^8zQAXJIGVn{#ey$D86B-D(crSk4i6&0|LzdvBTVEJ| z|4`-lA~kT+Es+gTrn~(&spvz&ojLrlp#VVDp_czr2B9ieCk!hM@TN+YwnG#&(^A(g zui1^V)rKIN0UtlPVg`~2(wCKyxbgEyvA-}Q;VOnoqt0t;mjjtK1X8=t{;hBMiOd@I z*(`p5|3F!?m3ZcjQnHYrSmE^A%PFQEu8H)qqLXY9iZ!=PFKp0aDX67QPl+XN)!&Fa zFZ0T0Bq67XtU12>O7zMO?iRqDyY~3|jW?0`Z^Or7`7fko*3rvU zxaluE#HFrj(gZpr#8J%~lkoq4USjGv7Ij$icH)FRi{UpaBbDdeyHe8sA%Mrc-p|0o+4ptUg#KL7Gm zij8qPH|I899k`9fwD-w7SqE;Xt5>0d_=SEdc?BNr-=n55rcV9D-a3Ra5=qxe{&;xY z+_E>5{ht1b?8xlFN-Y$*@HZ7)4pS;_-f15Y|HlMj7M;S}HFn_)T1xmkg zib;Q*36BP19BWL+QDr7jQrU}wU zCerY(o2+dtJ<9K*YH=G9B4|h5lbo}?LKLPGmLg>q?`380Q?mvq7(5q~_qNo}?xPHO zScgjJsP^VneB`fvHWo|lz3{2*4SuF#yb{UjdOZDloodrJ^5qnpma=$&;boJf(5eI# zB@<;2Z#gPK0^r>HR3Ht1eTUWr4W4lNJ9fWIuEzm&D62^!LAFLZ6@o`ce)@U}k|C6m zhXSnY^`^;;<0OV-8$XVr4voUiKApd!JXTuX>3(fGB0Zht>%IM3j1obymqJhf?&h^t zSFpZ7$=~wf?nC0!?=K7ofi9=!{kZ3ihMsnP>VGS*0-e_9KbHIY>3#b1&|xSu%{2}5 zFr6$-J}2Sw0DU}k?slwi*Pk6-cW*!qyzY@&d0*5KR_C*qCs^++8(t3llsdOIdYCx+ zt2q$n|J#p>*cM?Q6ZqkH&O5VnCl zVq8%y$P3Z8v#ugicfDc$=M+D7L6>VP%_qA~LY2A_d@kzx(kDcXL?>#YKh>Jf9bdNs zRynjm^ETLVIzy-6eel@FG^|q12s4iYQM2E1{&{pRnSXwlihyzMG@>`5_+%%njG#|8 z*G*S`*iSD&SWhvqSWM0=dt!U%o9&yektEIBO=I2Lo2nISMG)@LtUn0x6TSUWoUUGd zN~EJ@QDRaA-VY%`mC`ho?3$13Y!O(L&5k_QG8*yq#4QE`3c4&d;j$y{cmy85vOyg6 zTK@<)ikc3BPF~0h{vfx`bbcW8oY?m9dlD2C9sY_k`B6|{(YKpydzIZ_5-qZ}#>1jQ zIO1}3JE+1<$m@>6CipvQw-Eb69VPrxIVXKggN_ZbDk@tJX&YuTrQ!BX4~dy3ot=kg z)X0j-_JlY^FCxANhZwP>Jkk4E?sR5+=5bQO2WwDW33EwAz*&%DLk6$mGtlzLG`To< zO`f2Msn(-TrsoJkSB>pDR<@UZaCJ64F+qE4DHx6>NdxjuDg{{&eONTfm9vT|nkm@d zR5=L2r;hl67?1X}dgOOU+~omA7|{?xV=CE@4B-_SsT(6vTx&p{_&y!9-|HToy^*G8 zn3uP#kdU()abjP!D(!=HLi`^LQV{ep<3bKQ^HQ<2*28TWAs{_y*ETrRk!FdqK(vd~ zw$38%@QC0+mc<9rhl}eePRh4Nn`>^U*D4xp5^kV|+tur9AO+PIHtJNDFiDpvwl1{J z)nvpBIkq^Kg}2f`k_^mS1xcs0(PCq@Ilg?6=>)b?_WV$z2=}u%wTnk8;M)>$}XVoa#V!e>m(F{&g4R zO%wLaj8t}Gke3~%=o(70D_m}qigqcJ5mQ-Hvc?Pz!=Zy*!=#X0rliTfb4TYv7dZqtuO6&$mnKVP$t!Io=c;?R z2VK|>8IPr=SO|7E^V1^6SR1WyLKZL(1Yl~OH3f2!jFxI2sjxcODgFkSO*A0s)f>n{ z(yh^8B$YArMNBj~RXN4xc*6`h(gZ+h8EtJY0u||G1Ubj8lYYLCZu~Gd`Q&t>23kAu}p$mf-DyaWmdV zU9~uAc;m|`>MMJK0>T$3M1!A2(;&bPp$RF$Sq_(pp(TjvMu?yWf*T0=P9yxv@}z4efiI1){LK*b<+%Cf(o(~p-R{eU!SR}eN1Ad$>UYv|ouv4l!4^_ls# z3FdD+O1(4B`6|$V`C;arq_MGD`4}HpTWg^=VpTL4?uwg&8LUT)H~h*VSrOadraA7d zUbt?S0&JPw-&tfFaXh*;ZOu@KJm}+D@kuoi%w|-cyF?KOD}HtQM3W5~I1XRfI6n&NE*Ot(fK7q~M1)UGEl8vke3q*j^$}DR;osmh z7n(_kr7tQ?3X7uV*EKW8zkY8hobwU6pq=r7@9V92a(Z~mfCNzMDqb!I2<}j4?vb>0 zo|$am;Npkt>9^D2b0eG1yi?nUn~#)W1f-K3$q6u1I{Lryb0z3UN0b<&WC>U-S4Z6O z$wK(-j;FNZ8mkvh*q7$h4CZt#F+1 z@Bk)2R=E}{-fCh8XG#(hA_tGpr$5$9sgs2!4-r3*90(3U28vMZ2XLmMr8X|JhjG&I zRd`U(5U5dhKlQy-MBC;$^%X7CcVH9!SnbFcRhqv(jaFRU%)D?9-oF$1>S7w>**qIL zT8i;dw0qUI^zJpPyX#GIeILVuglHzCse>S|-jAz-sx_DiMZ#HD*PS62WUY*a8O@St z(>C&CEIdm#OCz2#&72%i9E$vgrk7hVh5Bk#$(ZTeTe21pq!d@uB86*strjMTHhPH) z92Qr@m+V|;F^52^^KvTdFU6ZW_q#2Xo6EbjpuV?X_}$tme;kx&`t6YJwN`7S>PM9u z7@yZc5vtbL#fkFj>mfx5L})rvDU^Lov5%GjRJzeDY%btbCHQME8*5?8SB};is+jVo z)_s9bRzbvR=Qt(jpYu3Z;O?j_TxrOEs}b_fNusFw6JdDq8rj$Kw?M@r1aOeEm|!V{ zdAWwj=|4+X_(+r~l!G#EF`TOu2Au<1TI*PJpaVA!1N82yoOc{YT-rsFB#6kk@?qrB zX}bn0>e`k}z0Sgt2qZ0-+dOj+S74LNQ{G{2jP3Q>s^cC(!DK=9LKkA;7TMWU|3?H& zFT5<9q3r(3^)f(&iZJ^(=5ljJ^@-lMbB&MiDxJ%6rp$`l+TaitS>uq?O_|NKR%dZ) zxlbW&pGHT4ifz>wVHZ)3B5L;j<%hc%C_aObJt_PYAg1tLy>t>NjqX2szw-uOP8;bq z{yTB|qvA_aigBcCd3YITxAE5v{A$Spq7Q__auTuV!f61V_NG#d4_Wjgg}6BKN~Hdr z=Z^|IsWXBho(?arg@ci-g;e`%^fEa=_k!6aqc|{r9~RgxcT5Vq#&h%2xnX0hsKGUO zq9@X*VG6x_*>Ryk$pmH6&jZx$!>Z9cC_$yM$MsQyAiC0M3Vsqt9gohr)pp4Yppdh6 zZSbO(ndA%Qg72wubFa>gRtP7xl-2yWBt|lg^lGNcY2zj4U2(l0Bt`})g;kOj7Y(f= z_&Zf*y!Nw;)U5lb%1U{p>4q%MVttPk4diOGV-Fw9SgY_v#*aS;nM=bj4VpZc;3VFk zN%1cMc~H`lps+C4Z*XQt3O3%yS0~?vR*Y8}!%`C@=+@3#u(3a8#-QU|86HO~OG}5u zXwa*tg+kR!fhy7$r|o9y{rS;#EFz>`VL~)$VG^=4GQr?*I;$)$MDQ}lECJRkizoFT z$2n0}gp4NR1MqGrny&v*A?ST#)Vs$kPToIG(qB7h$mAp@4=Jtwos*=j*En(ARb2|i;*{#9G>OFy|29RY`^|hqIGyC%s7^?#EmB8XhIBy%P)8L z#4~{Rbi~Bp^K&trwTIIF3VGxA@8d(moEi;F@k1fvtEzb@&fz_jgVuZY=x`Bw5Gz^C zp}o#bx_V_0aU+Rz$}^iD)Gm_lRI21#Ax4v!Xn?4fb|l6~o+UZmxOOo$n$B3Ya%deA zwPDB;g?~{!zhM0-WDOw@Q@Ujm1?kRDG@>~;fWz@c*UIRjoW59i&v+58dbsea8bxDK z#Y2A?522=NEQP^V&R>G?>LxPekLJ|mALy@3&Xeewg73>bUvjVt~V8y9;j%I=NT zOsO85yhj(gvb)J};onc@QQp6HD($%4n+Zj)Me_flblH;ApsF~q{sfYw?9oTOux3Eg z!_h$Silvz(R%?;`*yty0Buy7b5rV9Xo+7Mom+&i=!h{is%<)kv_>gE^ow$r+NA&-XVOwwqQcrvE@2Jt5NTw;dU_?vdD6{nS!_f!=FPkQTR^1Jp0-|@SFx;PJ_@% zKZT@UpY>!2z@uWSTLfZDD7&)*ej0^{d$`RGkQ$6}a)8egY-)GfK z3qn=@QISwU) z2_Fv~96K{Bq$-Wi^Z7Q+&~q4-ynDa@!~S4+{Dr8dV(As|roWlC5j>#-JqV%{W0qUH zwd)?LC}JO?(S}P_z%cr&dHtx$K~w-ludZg`q}kxTZ~Hq^yIAKsGPSV<@3WV{6`H*? z&@6CTZ9&W}U>(JkXXKP9@{1lZVo{Pqw!1qI{TmPBfsB;%Zdd#SlhXSd6rvGj zIbFs(GdoDy);}AuQ7@+oPG{IJhEU`%Vkqrn$An^#xFjoFdIrM`bzVA5*HtuDRIH${ zGd4}LfBg2sjqqRZd&S+~f4ek(XY%iPIqBN|IXlN3hDv7^atxB(q1aQwPogeuZ6!;* zUuuUI#(V#jUWA@XqaUc*s3#({J&m%4_{@&v808wg#OTe zUptv$nCTU0_t$CGCB+IF+|_#!0-t0_DAKEgcREdYO#Wv6{M7fwE>LRr_>0ls-)3(K z)W1KBPQ|dQYxjwoU+Q1sDB{C+W^?{7w8r-Q4}^wdpxJ#!F7FmQPvALq#Lw@3Jw(JdUXL^{ay~TIJ@ZXzY?P>nN9^;gR&K^TCd?4>;rXBwaL_oX0unZZX8Uhf52(ziJN2K~B2?4YL zG))7js_ucz1_pZ?XFxb9Y=Vj+x)2Z$0_SH@Xg5q@uCja{acvnkxuE`sg1dWr)G`7n zsRqC*f(SnMUzbe|)F1Ug%K!%(L-;xRhgpf4$O7}cv;2`2 z0M5>}vpZln49EfPnL%4{Es~6-{SXrazt6Ls${cZead+T*TB7u?PMPna zm#hG2DtA;F0FwBu&g0>LL9-c%Ytjs4d7gxCH#N904m_ym*P|X-I$b!NSf(pb1XBap z1{3BHUWKr{^}YZ82wq{Ee1Jf4Ar-X4=o39m1aB7;+}7Ii@jspX3!!z^>kyg?FMOw| zHKS3(16RfMV<18YAk{fwI`EF!@T}TYF0~u|pd=!3b6e+69{gx)5Dnd3sP|T^Y<_p0 zxo>NixENeNaWt`K4<1O|O=Yja-^h;&gAO<)k#RO}F`EP-<$m3x5 zSPwjnwtF)L9qsl6z&XC?08nS*<7S~yTUidThLe05rC_?sMyxd^Z|=2VGvf9m;uvf$ zmiO5)fcF9#&uuOEc1u%=(xvid)qRBNrJ=2b*U_Lk(}%`A%vhxTYOpcJHT!3M)PE1J z=;*{_nx;&W32V5vXh5!A7qFsh9Zu%w64`mln;cY#K{Q2}2f1cxe52 zL4nt{TvYvP<9 z&zXn4qCtp=MZ~eJm7s-c2?Zev&|1nG^!XRP_uM`A@u$OZ5N$*%N z`Fq!OOXuQkF|`0BjWi{-+41HDhR0Q}OG99nm~dhtP4}F&hR+WH_`sbK7=`@nrUa~# zvVU^=z1t@mA67Tc>!*Mtko9~Q!4IA1A*rpzNkRen>cx=}JT>N>eO%8#?DBg+b?m(n z*KPVNi|?$S<}1W(Dm@|J-k{M zkk37HH#2O6owz~9M)Uz_xW<6aC2@hf%p^ZIg5cu@H(%xDU+O4gUA!wHmRZtYUSRpk+`mFa2O5_fL|)GasvL(p+F+zCLE3V0oU+(r&Z-={v(_Q#}cx$b!EISuF=1o}FY7#jwSgouM z7Yy5B_gUo8RqhXPrh8Bn2bj*H#CZi28{q&S!6<@B(9q_iemUhP#3@9ME;}4d_NH0;6;8ZH1xvs&&vjKtt2$+4qtn~E2L?%*@`KdJzfO+7?rxetJ|M4LJk;SDG|5I;4c6YcdMXsw%f^YSawO12OYQ0mBNpX(^gz zN925t(i-3EJ&}h1s6=00X6_jSLkM8{Id+<=r-gw@LIeQ8gD^Uxsh!hjR@%z!qi*7_ zIIzstaEM6t+C!IfX`dFh<7-D!n(K+(8p7t>YE~#=bC{HDHZZ533&o&N5Ht_3&1@#O z>leq5NA9+`01|@iSYi7geD&Iqws9x2b|9BNu6afQ))udbh(B;gABM!SKWUfk>zlTyHWEAe=q=D?hwI^X+cVU!fg>Q+_-m$%zU%Xq)225NbBRJ)>5FTnHGB^iZSeqFM=v5nLkrQ zfP*Cd&hF?olZbWWMiQ9QMy+U+gPH{m;KQPt_Mrr@Gz;a3ypWl=b@wu#PdS02kCU&) ziY#?*&zuWPk9!OqP?`hm2=M{V){ueOW~tj>3Tj&vP!OQR;BIM)k@zjuR`QjEDun39 zV?ro7uj+xPiMH$@lM*qeYOJcb8@nT%#riHNkvPTioifeFGk^>M9RbiGz2G%8z=(^C z0o{*t0iyJdFPOiZlglPl(mj*i+#QAcc=#YZd&~}7STSi1U$CUlhwJ0TCc42O`zI04 zaoo(EQd8*}@b8Wx^SQwBlhz?veAbqjCX4l+!h8>#BIg*^l$#sgYs{^S zT5fI_w*W&ZEY5-}cq((Ur3yis7dtq76>I6C8`%Vbaz>S~Y#p|&wy%MrjL=z746_cV z*)Z(DX9--+?PfaP9kt2M`;yVGG(x)~;N(x|SVUi4v8d7!uu)Bf+VBJ=%(Ataqq6(D zK!Pw=f~$m*xS;|#sNJTMW>sq)on&NM26I-8HqQ&liO4l*+*BJt*IiYxMubpWSi~W? zJ2FhoG`l4#k_o5U&oLJTv$|7i2nvaP79PgnF$C+S?zULBO;c8kWh?>>wNyqREQOom zqRBRrN~H4>OOAt>QZ)keOsf0TtE0w02kcq-kB5&By6>f{7mxp+mG<{~e2%AZ!zSd< zp_t>l5C{=|TshXK2)sxc*fIzdwi4TDFRr!#P3?At1XeXpJ_a`yq|zeU>b;Q!a3VnG z4yH3Vz^SZpqOL`C0m4AYMw&{h5DK9dkSHlA2a1I_7|9`2Q$FUO`&ri&xH2{Y#XQqy`7BxWcx^HS!Ri;AXO z>|US&;z^b4?2LY&3bk&eyI?>C@xWac!Mn&GX9Gsuq5^vm3hW%43&z%Ubz*mwB~`EX1046h96hgx-+mqc5AysE1Qik< zF}BG8N|8#|tYwl4NK4InovH$m3O{N3Zt#Aa^dCRaK9W7s6;#07b7mz%kH_wQ*6qk{ z&YnOy+@49B4Y<=M&g2N#SavgCyvJn(BCk_xh^HO*jlqZMC4&n0M)RDmhX;q`)5NMZ z_t%^&&FAv#(mnt7zFq#<^-oGhDjK9{A&Mq~k>EPL+|!UbRUuTt9|AJfv^oeVnj%`N z9M``2@{$+p{-3;kfN@aTcgM*fS>p1POB}@tjxZ@2@O?As3-5bXikceUUnGR%YmkA< z9k^6y&&kIz#zII)em1Gv?Q%7}gO$b>>Zv4;<>K&6#yl|`m)I+X)jr@KbU=IJ5$Mk)mc3_8-y%5`#03b+65R(}Co+B=M1h-wl@nwze?2+wtHrcVdd^mX;Okl{%cLrKr@wmu2>f1k|E?R#n?lalUx zUcTEER;gu;AdqL(GxopEHL`tU?!gv4lBx<4Vk9F_kYuS_pHlvJDajTtm!`P58IAeH z-94du%aJ5R?jEs%EFE7>0p&9XkaA2YwdnB*@}-Eb5iN(}IVML^GM1oME3;zY$th^t z+jaFo)+by!0pfZuYhPT_(&`;fI62eF^tQ2M0ImmC<19;|e$>-|?(L)QdDMc~BynD>%wQ%y`os4_^1WC)y`qN+Tae>JVJ z%5qv#lA2mIs~1GXv<)QYA2@yQ)%|Cb)bhwaUAo3DU~?e|LC+4~R|hscq)hqkzp2>n zQGF=P!I0=<%jb*VbOH_5SIaH*ctLoGG7`{)992xr;Oc>?V}P6wCLg~8$Q?1$5jegG zeX|yT>VPw{NNVx7NC{xbkYNVU*6C(cw;#Q~EYWt1;4bTM=bek_w4Zq$!8p(Zkrdp3Cvg-xX{i-IXo2&i z=Xn^=4nFAvo5ZR1^wzm5URKk?x!_CPe3V$|AWdbzASzQGK3|{H9PrbXZA6X1@pU^C zh4&_eVd?--BOx3!95XLif=Tt5s*)}3c)g&uD-yAv;Z6Oz>v?P3DmINk+|{GwzJDx2 zzt1A1F13uRlYO<=y*)-Y(N-^&#UQs}fpM)ZmpgH5EByoygo2cYOjQuE%wPx((z%w= zjRHs@s(4+7b7Tw#CW8gp;m29g@KhxN77P>v>T!}b-uTH1V6)4w8c%`hPd8soQ^#MH`qMNs-Y%x1B_I-JDH_A7L9G zAC@070GE65qxnNd%mFt<0YQrE6pFcNL=Rnh(^YqfU6f&l$`dJ|LVjrkgi=623**T8 zSZqcy`Fk_EQiHx*!WWHE)$JkPY&$VKW1oRQNE&wn<7{#3P8;q?vabD%B4S}%^_{ad zB9Xe(1X718ib4(d^U&@+L)=;=9Xf16tpNM z2}f1?RXURU=}l=;eux~7?Dp~X4mw4KE4>=FNG3C1ra(%cQ@|@=xxwm4Mqy5%yotFx zhK-mfq=%PxV&JGG60jBHzGU+#B{e+{XJ3O;Z+yR8xu(}gC>3ySsvselO+ zQ&in&tvzcRl^0GpS~e8>h=oBs{&L~hNgu&WRif1dRgdid8%Fk_pK$2Zk|B)>2DPFh zg)s-U(aiV0?wn`FMXJo@&ZHZtxRnIK1l$ohQFmP_TffG_nLuAYW%5*mU{F?~84_PR zJ*_PbEE2>{fko=oVFxx*X=&i_;2m~JTrTe^BGu7zPm${jG9LnSOk%Wa)RTEq9`J9PU-f* z17W`#bGL;1QUGyC=f|D`fHn2sj)rd=hVJnU`L*JPy^#V0))BBr=5OG7v2PW}c|axJ zrf0L3s5^|F=~@$pNn6O?74<9NwO4MEE?Z%~$*~UmLNzW%2|;itW?F-ii=yH1(6GU0 zcDgpXCxmr>2=f#~6AzZ$AZ&fAZN7jq{>s@r zs`qy3C4LwUGsNs^H8Iwjt3cy_BQx@Ze!`(^UUUS#%AHM+wzJNcUr?VR2aS(e*_T^o z*+U|RY$|WDwOYNRuEVg%%T^J2oR31*2|=C$))O~|zHA=DmrZhdbu;Thy$eR%C>QNv z79<7WJIkxVK*(yjMom)+IzquND>pJ5i01z#ShZGkU|oOf_$AZZk*y@YV6@| zOV${bD%*B&nE>5XHm9^h@54YBG)?*949*( zd6mQ~<(6$29R}_hjA&($voYOjDyvKjPSblY8p(f@H^@7s5B@H1-NDT;6KtW_8k|+^m16tNx<6ausCn7$p zoN{r*G>h+r!-gETiWPk)N;ti@=N6Urff75oET-Vwos}KU3U|08I^8-512Pvi(J-AH z<6+#|-ZW{}YC@|36h{B7M z0hby&3>1NN9SbGVR^w5O%EjnpDM<3UvWB=^Xro99*gUCv{p-=I>t~ zZ?L;Z*c-V92R?-22kG}jwrDBsU%jX(-GJ@%?w`m}kwqByXE zNFLmU@w7gb{zjMKcqLBXINZo{oIcp- zTVTbKL*;vZzVK!~k2J+3#&6HCoWq-@>UxmfHW^Es*t*%XS@af&Ee&vD8TUxToNo5N zaa8R2JZvOp^jh#W-9H~&esAQDyJ=fB!KT1hUyy!>d|q+AL+k2rvl^d-8hkI8hn14p zqm5MxGjj=P_$WZrT4g{cJaLp6BiK4OrtQ#SEC?s*qFRe8_#~!@WOjz}*AAp{h zq9XY+2gs6=pKXM(BZ^uUA?|E-oJj-=;Npe^2?xrTTLU(~OW6C<-0hf%5;Aw39|+5{ zP?C#llpK?`aJW+!KhMQ$tGelxB#CaRg|ouYJV>^xM~4iWbiXQJntmU(*?CAEGXU{9 z_fCAQt0PM67+B~MaRLOPd}t5p^hJ!^*!Q#wQwRcqf;R?vr7{R%k%;1P2I&ek-th01 z3@K?E@_47R2drR4Yd@>STBLhW4^4$BW(AwhpvxJ2*V*G8B4TJknFAX{F^pq~azJV-5MsN0((W^} zh%zrC^m=%q@g3+s3q;T`cg5e#!DE z1e4410k#xl?zU^FX+m>6Ly0}FYC9)^0pw$$@Sr*sc}(#3L!q zOy+R|y>lY(j4nlY^XG~V_lGmPyK(L<{P?#ca-Nxc@Y9>Ol^KyYQt>sky!Knkx0Z|0eu01Xa?He9?h2317q7GZBmG{=PKpTV%EoN&>GSl2M+ZCDVYW?hSmT7z?I zm7}z-_OZ1^Im*G8-{_bh!q->dE;1KDEk{i{YryHJEp7$dOeGM5AfuF0gy5pToO0Qw z_^wivt+^v8mp%=28rT3kmsKF;@DR9RM^5h~hmv57z+^>10Sn$|fDNfd;ngY#g2ciA z05G*rRm>S?srx1?vEmtxBV|&9TWem*?Ig?)0|rCUjR)AuIfG_vih~T;##1@TY`PWS zGX?L4p#(5qX_>JQb=ad=WA@b_s7>>Bhm}Jc&gNdsFh*-StCXR&Nd(?R9Oye_uile` z9kNKrV|KH)>xF&=JE2IGhO;OGB?MVqf=GBZhLf`bu$RAB9Z))?&XhT4R5fjAK|_5< z2(SY(0hB?DQc5S3AB)ju>h1A4(cXpNDIuT?CmO0i^jMM|q=Y-y5Aj98&MP8>0OFfeN(K=cy` z?48Ll?x8}~JV-Ab_v3KYB^#RI__eMj0QK!qbfp@m>(ss>Dh91$$ng;Fep}P3i6=@0 zBeK0<$xmlfeRCKMDzTZ%DxNAsJ5LPrH<(Z-9r;xNvkQ{K+?rq4(2qH3-EyAhnA36{ z4x>|cP+<5u66n0tMw+QA-2*dgFjCLPuDFAtNG(rU4l2g8HwuDRd}O1ts`f@G>GI%! z+->2EBPhF-Yc5V-&_4wn$km=MHq}sdIaT#Lfl@7n8<1vHe>scJxxv#Esg$;)oC1iy zM)vvdHZQ8vAZEGnxok)!Szi(&xWEiHBEfVo>!YqmMwTpt)F=jowyBP}v*y#YTHopD zGd01k>;EH|iSIaewH;X{m)`V9>t^}&a!Do%hnrqHSu6RY+!&U95TYS=Lkya zkRujZk_42Lw6L(%ZO>K@b`3QWppwsI9*DIWX|p}q+VfRC+FXklB+OpMRuoL+=GGVL zf}x0FEZFmIbF2YSI`tLg(b*xs)SGfQgU6j|f^DUa>8LHVbPlZe8O_H7*DxZuZ=|Rq zsc9@0#k6(Y6h{D=T{L;x3wg*4dIA8jV`Xj9%{b12g~>`MjkR=Q1GGRWGL5~NfZ}&7 z^h*naRxu4?86k&-b65>jTU5gv2tppcUEhwH&Q0${jQr~UFPZkd_wje~+x+=6f>2}t zLGzNzafOZNQs`cp?!`3Q+6x0Q*O-Y5&Q5ECnci1V$qbYO5ljv7qF0|VsnC){)O>h&&xzdg5Jjyy+B zS`U*hf_nbbAE+E!;MUcTdFE1fh5CM`HRrYNhVEONInRlItZ9gcw>JEA#zXf8**N_ouz>w9{T<2DcC!zW*MH5=m7?jg3<7cYb(MPYRJc*sVllM(=O%{O$lA{Zn!S^-L7S z{uyZTwTS3n6E^@!`_I4ZKebDloU;fsAY&}!NHK64Bf#!$V7u8NXn= z5rIiC89t6*rg3jFR4VeCpGO|a7Oo5sI$A-oWz-J;TJ`e1IZxk5<^UcyE0r8mneyK! zvic}%{GQE(>%Yqn=?$v4_7ZAjVPhgt<2>8^|>htyNNzB<3dU6AIX3K&h zZL^CP%nm>#hT*jJGtTqBAFF&04s49r*oP0hH1*&u;$+Mv!#fUCwiC}u7w)oAPI?Q))zvS_`9$^%E`cG#5MhP^ zn5WY_Y-~_Ltobk{_V=_799cr;LQ@Fj{O%@QIZgBj%Q<=ow;Xn%gu4dDr^`oAeRhih zs^!goq4oN{G(hV~7)MuwrEB>yEw4G^32q6AkE_#(NRKERpx{74K_DKTL%8Uw8SuHN z<*i44>)V7bJ=hgQ1jSyYVy@Vh{LhdFc!L5z%hm8-4~x1G&nrAVbi=F)O31NF@@0a8 zkofJ*(;TId*+QuGHQ)T6Ew>5ao1p%iYI4lqW*3_C0H zI@YcMv~J<-5{CiK(mg31Gu`Gu>wz{@ZIqyk5rK3Tw%+B3ui5G5b?e-nsY=tg9oo=q zENyhyGTFtV;|-=-3T4%M-5T{=-}iy9y_2D7kL`W!W>LplIvS(CQT)R36||39PaRFbEoy9?12MX=cxJ^~3_P@fg}x$M*qdC`;- z4luex<<75_7`7S)sAEmL*PEoS7g|&1*kHkw1AyED6l=e4Gx&5!7mzu&P;#FZd-KH! z3}`mvuZ8=D=3Gi(Q&5fu-Nfev$^gEm_8Y!D zocVRPGG7`{24I8C12F_aAZ%d8gLGc(I8UR1<8_h#{W}LnQP&H^pKcc+fysv9hKhi} z$W&qwWTiuQVS=jW`!Z*fU1y5xz&6Fssg=Ul*pZqLcyWdY#UJsXX4aYau+Z|PnN;CU z3DO4+6&;vuHKne4);O9pWq+MoApmeFVKT2H5wPV1#^Fn5^e9S(6vG)Fz~wL{Z{*T^ zaD7B--F;lT*RP`Hlphp@wyH=}qKU|c1kf?hYWXu&-wYWuajB`wnV%DFPpQ4?BTmE7 zf(R17L_{%KMKI@A#fL=~o zB5R}s#5<;wy3!2KerPi=3Amd+gNxyIyUwm9al}gOY;f++@$6WKC*0Wp(+nlIa)9BE zso-P~wA*VM(NwByi9TFEK{mWd`Y|nZB0_#~aF|2vd27ot%`0_t_l^;Ws4eC1#DdR)H;kN!pcM?_c!a)MIhsa@NI?}dB1vTN z3K1lP9}%!S#9>(ELj1AsaUqpX7B$TJo8IbjxFd8Cm@x<%QX%X}(8RAQm~nyeFrn~r zxf*@X%B^x9UfvZEeT0Q(#0jjy6JQpf=IIvYG2=G^5umE znx)+?4Ew(A>@3=uv=XlelEd-DAAW%}NGol2m|J_pFL)w5f%ZM`cEle;VGOY2tr7`Fc0_pV4~0 z*Y-X?+kZFvj}OE3caNiBr1hpGQ`TrHqf;{&w88;{G@2V1-qe1_WWoq|d3zlJ!nlVg zyK946WCC30Ajha(mMMXmtGiW*>%;gEfa%Hh#@9E4R#gp7hZ_^XFpI^fB4ZgO0Y!i` z3*lBjXUON_;hR4BQ`d8Jz6Zt5|6ziX?y!I8JL))Vl?OE3qMaRGDgi{#IY5z)W% zxX39{7jwFS<(>Bbu$RuV)W;b`lZ%a~bK*nT)7Uw_=KeQ{rW;co^xkp9ixCbyT9pLH z=}zPJOe;xGt@<9FLKPq6xl%jI$%!@)VUJdq1(L&otrBv<8-bM{zbbaMpLQA$1+@C# zE!s|m47mpw)^qz-ZW+zd0OC;&MvX6V10bQs)dWER%5JQ3BtUTX&v4HR) zfyyAszKdbO4itTdxpc|tZg|+Mbbc8Dxa=g1oI#Z&Ff7_ixf-|&ueqlqKctw^qXh;O6srr9MT%G#2Ib0PfM*IU z9>P*uMWO0d`ETFFM<3DU9sNZTlgLFR$64@%cg9}NU}3y!$_{Hacwqt3ZqsRW@>?Dg z9*2!IwQ~u?kZ7idu!zyd!9emjR)C1`7Q{PFJ9l!K(+R2h?d8TqW-7C>9O>U7BB-@$ z0RTK16s;v8m>p}Hz2;y52ce2ZOx4Lx_ZSrZzgTQ_8|t|kc_JV%v8{=o)H}yDvAMww%v-Q9kBV zwaTuswB2NL35JF|#fB9uQ1mxZS&U2x5p4}DECR%06=dCj3I#*zQnfXs zP8Z56^Ni^iI{*&=nBki+-7#DN7d8sW^Jc@iR)EIWxuCVSA!TIBsWfcmtjnQLK_(j3 zykgLdw#-1wM9nTeTGD1KyQ@kTlvX*p>K9Cu-n@=(DN;umXm#F0uoEvLAw!jSSklY` zxe!?JiQ$HcBKB-ySc}w)608+lQw0t?6wz6#-CD8}c+pB=WZIco)WeCHHP5|FI$a$o zubrACQmXzUuQPZN`RFRp`~ID-B18(tpZ5N3j?YuTi4jbOpZVjmM7sLW$)()26vV~{IIkxMyH z+<4#Ao_$c+l)|Y5t+R!k@LQ3E3x39uAB7?ac(EA0r#cP|Y#_+xYcSOcanY&Whgntx zDix&>bFrz?g<_i;#v=Wle<*VT%5>-O=(@)GFH}S?331bY8e%V}C%q1MY`fq@L4l)7 zk~hW!ZC*?#PB^Ky&8fh(R}TRKRLmn7AtEJ*7J8Xn zYNAUgPBeTm1X^7X-5_c(Qohw9i_Yk!DXi^X*UL=JJzpZBKEeY`&{hkrR%vJ*>&0cJ z0?7P(bn;=#BlhoE37c-ZIW9nt7K?mAWdu=B^bU?47}0^jL_)(W9ZecHAze?IM!P1q z)U)Eun=~b{A!j;W#%wNiv_Wy(z8wdTG%3PHtDsRkmjt5$hFuhGx{KDDK)#IHPEOLJ zLvk8cXxbY@Sf|U*`i7+K&Wsa}9jaGvgHHXFKCx#!_-o^32Y4F0oGZLXB8d;fN@eAh zp@IOD!Z>`N8vW1hyp8zG^k+q9qWH%#?Sm-Mk=ZgQjJaGSB)}F;(dVf<#`+V798ccO z-UZ=#ZgsnZa{uxnaKV)h3zMKM4R@LYz3mAQw;nQE_vwYDZ>8Z z+aEr4d(*KJv|y$u?~AWwNhRJsi-KyBYK#SL6b_2)ADq46!3Ce0tCpf?ubQoW#`ng| zR1N|M$%&c2UIC9*<^AX>TZjeKJnyTW*@wV0GADt6)XJOynN1W=mJ;u=h>VvX`Xe^9+xBbxC<-xt zW_#V|l%J(yCX1*RJKR53SEk^gJHJbnW6`_seLpbzKPxAny|8*-9-rSxer)OaN0kR8 zpoo@;DMprG)`)9W)e*eXVOMurxK~;zYS5)yu7xA`8=>>|%?3%S;18R2r=iMt2A$XN zr0kw2yHz8PHxd%qO2Jx}V~gZT78A>IzmYQPOUh3sK<)LXJ~>fTLb?PJl4 z+It5R^c-d_5YA>OqNIWtMNp;`4wW_g=jfYL@4Mn|{zrY^%I-i9C8K@797oX;8%83S z%E1HEb?1EHtP@z_Z`}MgcAVcfDKmbw`;S35y+S8IHRD7CnfE>l#%fA! zXr_anEo{tH0N$xm8#j1W(>k@`7^OXhu+AOaJ-}GE0-C+J-D1Oz z&R|z5Z9S>P1+`LGh$xmJ2^duZ!wNVSAQLoGL{_%L-CI_rxv*;a1+wQ~aTBSCxP(E6 z(shQ&$#Gy=d&UH2h_#A>;TSWg0ey5fv79Ja_XL_FJc9PHQgTPUfmkjEI!;eYv84k% zxgw`T2GWP=>I5<(S{?^`wLZq67x-VRk+U>v(7lYo#>Q0BUE*f6hWc@I4p31=zGX9) z2pFOuNux(34S?}SavLR7}$CzjyA9wvY?ad5h>^BsXT{gJsW9c zWIAJH;P!bg^6iZU)A!#gn0LZrh`zkzF=4UQZ0Rnj83{XjaKZU5ZoEu%G;uxb@&;vJ zbzCeROyO+LS$F~^Vc!b*A_*$+?`BsnnPsaQhG~wgtGnC=6hhu^851_H(B$_#GC|Je zQzIMG=fppB0q1haVl=)#P~gyrIsHz}eO*-jPcZIPjePb4(fXdlNx?0kZVhG-#(f%$ zlRm_0=hdJip0G0yM}s{)E>;wiRxcVtF%Z=O%Mgk{@|8Vijafz|Xxxp8XhUa4$s0az zv}R^nAxKtFh{FhGWVHfBR8d|5nvACctW5@2nrz~bf&76LMX2%=jF87~#Eu+ZsT>F5PVSYql%XG8|!v60Ro-JV(~kh`M4rgH?c%6Bw=} zngkg9ZHPgzoi1^p!C=~AG8HYm1+YkH29IQ@oXtHodsCC*=~>rXolV6OHcq*nW|82O zYNMq&;2z1PNOkh4F~gLPfUZ+6t?;7!CvE{&k(#P0$hNU}jvb@S^L;BK%K|->$4mNT ztDJRLRVmzhD^AsF+>{Gt$mfD@caCuIph!6)1&m;*>zEvLrisQED1?|g-*nvTMW-7r zjmV8dLTHUhwx0ZYpjQ1gyBTNZ>9;l0*np+FD_ECVFCK_j1o78X$XZ(*xoDxYL_Sep zhMWPqe2#{{h>D3Hz?T`aifWc92M%EbOn`5_M9X)lR)T7+jTI!h)?BsS4#XA)Q z#RXy5Qb?g1F$=}6Wy0TUy)+2w*FC;AJCeCLeCd#JRL%o z+X&o9!C_j9JQ^fInY=#7d846w=V*9BNTmw5&YA+%Ox*b@Ylc{TPZqu{Bxpu=y=>{c z<3$O%R-|5(M%&fsohf4@e#8brY{Ey3e8Yrcg9HZcR0u{RK7!_=MsSVD9WIF8m$%t- z3Cyb1gva1>nm~NfOF_ZN4514|Ny^IQ`-#KXX|?y;oTgrjtXnIm@zalafN~N%np;rd1 z*pY-{405u&Q>@X2sY)8}}UWZkl zU!H!K23%b|_bT4W-Ms$W--{p~1cV4rRu&+!h>|HH07!{CbTtrraq8Vz68Kd`L(=ba z-EEiQLd!AdozP+^$bNp?L@M*so7N)6@CbPDqv|XqXKnO#`FxG`-%bWubQ`IgIP-%X zWC%LI6?4XsuP>{MXL4XXt%21EGR z1lf|WS$^ry)Qyubtf8pAE>E#_nT~bIEI1v^TCA;sn1w*~fH!MmWq1M_3XV=3Szro8H}#*qQFQ5c3} z;6^;k5iCJ46f3ipY5a|;(2t2)^%y8Dx0;@Rpb+^2pi-CVj&{f`o*W}habj-q;~-St zW|QYd@JLtX^DAqJ&a+33&;x{su-3+dupI*;yV}#q&z}f}n6D9=Fuij0>;w@!ln5^? z+Qs@^Ok9X%6&7n4m|+K%jyYN;S(RC_n>#OnzA@EXJL1NVL2 zRElMjWaoz};jYSI(T;gsI`zj~%*7;;%+|^eHW~>Wpm9jSkYU5!V#+)8gQ@*pBW#8+ zt8i#C;T15wt-?BWN^t5aAb>#8gEUI=)GW%rDcJWUa+Q!*P&#>3M!C#MJEbuE(?<_Q zmt}J}dWPqnQF>Z*XmYZG)eYJbX4B0m3+%P?$f)qp49|HS`GUTi;yq z;T>`}*LQ{|pu0}1W~|Be@S0ujt_Ys2D#s0i*HtZ!2{EoR#V4ffmnRvT;cpf--t07$ z_Jxbo?`JvQ;~ZspMBfV=KsL`2iyVowd7Z|Nky%l`GaLgrvarCmlT-*KlY@hvmSFcl z2LZJx#19Q7c~cZDPNT-Y5vBIk6>`OkT^B*J(S*9dStXX?(Yjw(Y17u2b#)z$b!0%} zPO!<#l7JssYqK`5uP1FP&IxDHK{{cv4z)qe%3Djoy;TJ*qTuXO-TFo#3TDfp&@(Yt zu!wTW3c<3aLq!n!)9q>4)R$WxEVh8fqa^OO8rHXD7PBmP>4qGOEs&QvvT07$rD&5g z5;@u5a{i$Bm%|!}c#qgP7}9h*B*AR|8LGeA%;3u zVXLQwp%PDnBafyaufLvx1ksTfd}5$pXroC3?3`haHYrmQvOvKD{PwKJU!D58KcbsO zjEI=-GbZ^4Wvw+3c-r)PD_`C^k4uJL7dZ)Ms(drD{3toty?ZzzsONw${L)VoJ`Cjz za{|6svY$M{#2>PfAuOT-xL}MppdP2oQMlk}|1AQTUtu~2KU5(vKto0K6Yu1z<8Ti} zFk3{*$>P+8k-B4?42AzZER-4yWE^NDIC|>f*+Bpq8?{M2NvSXBr;cf!&HhWMcsrLI zf@a~vi$N@Gpl~4KnlFH3(Jh8hW+kDQghRQEx6ELy`izOovg)xEC-Y*azg|i12)&t1 z#jPvt>ZK-n^pCgux1kQT+u_gmzs~f&`2%(WX3CDYPv`r;KzC&$w zE2;wCx=w7h2qlOD86lW^R599+E@*v>5&xOC&!GB}&b>X~FNVEnZ|t;sy3??;=RiK` z0t+OD10aDd&#G+NX@uXjgzl@0s=+>DS`uCNI-x;Gd|4N!xVuPmSeTH03m6MbmY&n5T4=(cZ*)m ztn1n}x0Q+VktysjBmxNmL`kSfWF_1jcwqespWKaaP@ir-P_+SXfy2+HOTGtb^ey6XjDd zgLfvG@U8)?5vI~-5wogVcvEjY<__q?HuQ{f8!WRveb$KY8U!8TZd8sXr=ubCEsA)K z_Fk%7l(|5ZXoPZzfTcjwIYm45-;#H~dc$lt#~pX4V*IJYpI6ApnIVc;k2x9~`)VYJ zBlYWs9=XqlTw)Q38-iHARO|gfokITrox{F&Hd7s~w*Fc42Nsjf!YzI9*@RLzIc2 zz_}D?ru57Lg5*PP{>Qi66U4pIrO>xskQjv}$_jF+2-QKePLvC=<;t~5xu^ltAo}ZE zz6J+exP`*TYPeq0j0KtM2qYU`D4W~ENY%w@R1}o1W zZ|nCTaq#$drtTZq^5bSQEKJ6I14(EO<9NX$slqPLqO2|zkZqhCJHoqpwQx<&>f!`9 zqs4~#mj>CEkTmb$`{6mUv?Qwg6T9}GN@DyO**`d(^YPKx<^r*);7#xF0S?Q zxuUP0WM*K@zy=J6z~s@pHv^g{DyHzetAOO=(>K5dyGIWW)^dn+!;5Clc-3!HmrnMp zY|mZQQM@w}L$tSxGUZnu_}<>d2q!(ddIowrJ#5?0)b~XAv~yD#Vp!&$r7c!2TY{s= zlHg(cF&cQV2w<-U7+|+K zrZC&j#j?@4xOxvY29uU$Jz{vuix0qd*t%vyTC82U3DbgS1`=Mqr(IFku@0*8s?D=Z z&jYuHn_SU_yqkG2sp@5)PV;PTzCJxSJaNz^x0&%(4Gy<9Ylk-`RW=xW(M}D;NgCuybrK4UAdQ|qh~=2^XAkSM!PN)G9k#D`|DBe zA^?t62sCnE!UKjC7=E1Trqt1)XB`e(GjHkTzihYnz~kAU8y{ZG+el|!{lTL+0L*5V zdTR!)-_rsvVHKu}Y!DqT;Mc3n@gWrfV@qTbSxtcy5+w^kzX z6bnnTs-?(9Weumbg%PY)+g`I;S_ulbHfFaA=S$-vS4t6S7Y6p%!M)Aq;b=D3c#{Sa zgc-bH25h#{L9Zct$RVY|*wc5&->&mHo82fY`e+}j4TBNPi>_n>&PC*N!^oS)bI5I~ z3oOa4sNnvT^xcir<-qZn?ayb3(Swx(Lxp=((neC>R zee&x!sa|1kYmDCmYF{kxV3`?@9A&;0b!_hBfP^h6kgLh8FW?+V+gt%p+(bzX(6{n(rMCpnT^dB=-Zp$5b>z6ZrWGXKf0wl`cJjEh z;0r_L9@Xd*=E^+I@rdIT$QhExUU8ea7$!j-Tha_2^_24$y@Bf|0sXp7w)o~_@0DKr z;sFz$3T;%xAGq=eA3OIPHS+%N^bg4$_G~7uv6qDRW?q~z-BNW6sI%(#_qZ8bxNjA> zCUyN9TsB`}EH?e!w|t!jt615InQ&WsLHBV3kxb^eyNqWVZDqZ#;``^8md5h3aZtI} zN5zhXW{=uIz~eVoFo>ND4@29L3BVt=e_?91C=^ZDqt@qxvFK(j)J2|m%xK3MQfX4C zHEo&hrt$?fv^ThuavKM+nz6Z6Vbsf84!e!zRn?eNIv=3+yC;M(Ow}?u1fL!y^3~15 z1a-T9XXb(eTbtC=xl`62!=2USJL)0IJPL0|i8ELLU@ z1By3f=tZuam?*N7T%DkeMashPEt7MZUrvvA+SQ}k)VDa;8X7MfR$pWq)E8L=t-I*) zl{&E5ls+yjMjAtIbkX9GT`tXfRljF&d%f-P__#FoVMCno<0D{Dkz1SDmq^zNEj5|~ z)%xzZOMS-(n+0%}Z$s2Iz*H;SAS}0lVAjI9=Xo#8z*MO=;K<19gxD#TmR8V^ElG#t zSgMYcYFO5S3$;Z@Ti%M`lkj5LWpyIfrWG=?vFva{z^bt^h4s9%9vCtx=~|J6>#um? z);U$salQuf#&OB`nB9(FNYi2zoCf=)N`(R!j}^q{A>3Jsm<|a##psmEj5WGoXVvY- zH8-stnRy&&AxoB-nT;w63QhPfvTu$b*P75@QD?Ws4Noycl@6x}oqS zlwSR9_}DVCObCa++U$)jYvta>82YrO?FAEj6g8SSYAOVpi%Sm*u1`-MQ zwZ2+|$C(f=64~@mv)psi3LrYXnGFmrmCK#2Nh}8|>^lcv%)9a1(K3I8Amr7X8?@F< z9RqZLGLCl}ucTpp>DoT|`jB*b5>^Zb(1ak&Q7);0>u!6x`x5>54fQek`N5xVYaACz z2Nv*Qwy*|3$y-o0V=^+>EfXO(x1M*(>^-@BH80FV-U9$|nv&6h89U+RdHB8tGA>f^ z^mme8zu+J(D^Ig>?1Ssmnk6M?PHWRKmu%!9(zY92v?X>?y5LqZsbn@r{(8U9x9HC+ z2Ow~;>PKCo1?I9g z+S-4?`ds~g;C?S+`!n2zhdp_&MqDj{jln3yFo_c=J&nP!HX{hIMG`|8i$C_=o;uCN zv$Q~jAQT{xIw+6cJ2jjwoprF5V&%3WvNH}8rHg&eZ}{{rrV%Vhh%%6I_h(75->-PRihNBPjURysmVkO zNs_QoKR_MrrEfVIPb#`R`C!RtOx{?0l*i<rH5 z*UCxN&kF;;5z7j>QF_;9yAH+k<4<+uu{?AeuytX;uUXn`dh^2%i#IuL0<^hD_b0XM zY7Je_6AV}mMmESP{G`4x%|QJ65b=$5Ng~|iL6TXYJS?(mm6g?3yQ#)O2uct_Apz%Q zBsqOauTS2oa1_%&6fPp&|Z#XCj*oqxli)@I|7}wWofZv}_*zd(<{Ui9e z{fRFIS%|EUXN?hehjyThN224;j=m8D?e3u^iwn%m@@Kmh z*Uie}?|2>1BG2InotK~4puILbduX8GM(zasMl*9gJK^4|z+icGrCGj>B}1S(q}2BCl81W_ zHfA|*1h4`PTP7bc4XRJh(jInc} zfX^)Ht-AP3-AuP(ermT+r3pebz5eR`4r}jq+Yh7Tud(o4&bJiz@^?ejEg!-Cgf^>3 z#8;mf8?lCso;Yi+Y_dqT3(AS;u<{yIIHY^9u|cDZ1DAZ)S|&b+oD-*xyp3`98K%r-(1Xnc6gSZp$xtkQ;n;hEFbs;M`85JQ> z?W-xW$iQIpVw`Sis>j%Y(71MGo6%QWg(}IJMuiJY*EEfDl*dLDm2v{Q(Q6DGIdQe$ ztj!&D$ChV4xbDh2oJ$QYV;0;c#m%AkhKR0ZNTRRSuKe(_#$3l}N~9=}HyuuvZAowT zCM?>Q(@fZ5lwN>(b%h#Vdg`fwVV@PO#0>IUg*|%-cy7DF4b?JQp_p+XswDy_ghfCZ z;nR$!tP~;<9}6WBNjR{U@3$Sd;6bvE8VhPm-wwFbEazgduG?B6$%b8W9~NQ-Pm2SX ziWaviL{xwfLxNOjYgz?Atz59z*zfu9DqO-$84>)5IU0Ho{A(y@uFKqwwl|^qaVEoc zP9WJvZ!acx%dBpE2<5!vMq1lfJdV!yJW*#)f}%+d5(zW8Rd=cOMfNSzIyksF3oZHE z8iE;|Bc)i1ca6*1T#Jv+>)_-0AvAV$-n>5a9?U+EYW0*Ma`K`AAT4*UB!=+httxaAAVhUfMkCV8>K`oG9 zOPGsBsr@<|OG7b%Tx5{CpKc&^jR*0L_hk*45bv0t%I2^m;y8eW;_G_NXN?o-{Ohdr zHv3GfAZhvkA+5w!)?_5~=x@$(n5oEZrE+Md1{|-;7uMDnebuf*;Jxeebo{H{hRdbz zrrJ1vx^6i&Cs!ETU&n0P)Xs5%&Ho3Wsn2Q-prjB91Q2v#Z`3;V^OrJ~us52QeA}=9 zh>y+#AOnrAXYBx-uE1m!x{Cv$O^PTe2?UO^iO!Zc-|yWmI)0O*4TzrO(|J4>`#dJ% zs_~>|L#pcV4Q5Vb4A}YrD~7hP5#_qcdL$}{EhIRLVR~fQ&OU|pz5t%Tyb^n}$L(@l zI^$>%+%<>EU0uSy^L~Ht@`*L3q4>6Rt+wB6u=JT{Kk}$97fzjTQO=Z;g|R`g0_7@K zA=E2jg9&B|SiHEqD}@xwkmu*CSyXS3*yea$M3jbp8$ca|B@hLU?5T5%Douh$Lb0mBItP!!O)jv;MeOsO_h7uQN z35l;#`=r=vj#y&o#yE_h$ix=id)Or1Xsn|Vf@hvu9}c3R!zx9;RlCXg_uQCwJ{FdMj*etp`PWC-Ex zD(8#<#j(*9R{mqu?f~m6*SwB05lf|0()@9y^u9I;*Ze0q>Iq8StTMwww`JMea?Z{# zmG(~05SlPJyvHdNP;bICaHg}cya>)Wl??9)YYmrt>^#aj)7GTJg^t6g!~OY+oE zy}H=osCHPVmQEbLPuPaBB8Wm#SPzB2H|{z7)4de@|D1s$EiE6R&SR>5Rx+*Gaau(D zrK8~tn(b(UZ-|hbLR^yQ5n=4%}0hLY4QEorMyA=;2aV4ulxzJo3j*8mO_x_PYs^uwMJmyw$+x_}Rn3WD7nlWoC4?2VVN>cOnM! z^o~4q8uRf2k21U+^jF~eeM5n3CrIUO1s~g~7ww1JZODMQc`y_b9Ng4$; zx-3Y@VWTxw&nu30wu0Tn^!|TdPZ7)U@uCeKS8?0cdZu8D*B9KPKs~G4((k^logX{j zom%@9W9QPgFUvbHb=~U&cbK2YRn}uSc_QmKYXSwJF9T3*!^X(~*9Fg-GWwJg}b@Nx5}itBa>pQ9cIl;D5$L|&lf7*l$fUvU!Vv*&tspvhalDR_7kT<uoc0_Mh;>MfWl_37WID4X67)FQa+b< zcv-fNF!$7*y{Usl%(%T3$O4r~18DYrBt%-*J)RZzkYt~sEeoPbT%hkX+QD01`D@Hu zf`GQz-&4HaD|Wesw40R)fg?wD6zlFDV>epjKti+FN5|;t!6g{N;oneN6tQ~)(LQ`; zI5zEOt$ra92`kxZz+#@iniuW->1V&e@7wH8e$Q=};Pd6`J&{Q<(s=mDHkk4al9eEy z`$YV`m&dQ{xW5_J`-1wtXza45XkrMmS)Fv9RXt+FP|fXnHEpXfVHg#g^J-m3Em6X~&bQkp4C6)l5WV-RcI!@e zb0~B^l{+JK^Ar{+$H1<~cT|VNc=vt0%g(+#4H`z=Mu@ysiI#+FfGPx}DJfQfnm2hD z3b!1IZu_;7kXWLqMT{gAjL`AC36b?($Si_@piiToe)G8DG`LyW zPa_p9PWCPck&Zi_h4rM#>3(DpUb?W{#EUM$m~YN;#z(fFfJcxt4K5u8)&4TPb%8))Jd3USxFs%sjj>Pf15zTP~6&B^Uzn!hB z3XWN9?!7Jn-qGo;$(b@UGFKB0+>AgOjWz}6X=srj^B=|HPOq#%7`QlUHI+>qKDVIZ zZ0bwx@!PiD|E}9Fb`zn>_5AOh3%z#&KPo;l3~{!vJW>YGzoQu!yQ2%UrGc-=O?vJ0 zVF$5g@^GOu!sI34w-M2S=^!D-1C){T?*^HG%8``@cKYcVg9D4Rn90*y+P;k*^gKR% zTWS;5M~WwwDHqSbQ=tm#P+_n#Y)}L;!sBad;th^hDnAWoWcv8z@xuoVd&$XSiqCPx zLF*Tn1f1BsM;H{^--qM9PY|i|3@O^zp!+>;9BHL5kI9t91Eeb+zWc!i?RV;_1fAA^ z%PY^0!n$Dm2m{TH+3htbm0PiH;^r zUbKZ1VpLN$@W+#Hjy6cdLc#GXs;uBOvUg!iL?vXWVhh5y^fXY%Brsz@j4v*EMzZkI z@Hj2x%}Fp$yLh4uZLRL@rZzU22NWnWRxG0D0}TCbjDU=>siLcAQ^pecR>}5>EZBC8 zOCff9k0=ch!KJku`Ba0IZks#GH(6Y_1$XJt@I1AhsLQCZM$;`2+|O6e<}U* zj-O3{2mlMwbbGaw)GShiVS2GfkM+TDNu3>~NQ;aaP{T9biJMQ4$6yXx9{L#GyVltx zbN&rjlI!ODmc&%^osTc9oL$sy@Gfm19?HsWY`_S!jSI-5Chn9vb(Y@3fwfFb$;Xy6 zOh)FTzF_{8>EK!Fym*uTWV%elwdiOUd6ij{*`p2`y z1^nv0e|E}s1ZgBQNjk5uIa4b675?5vVR^j{P|TadG40#9@-K~qQAjAV3Mf#-v{JMb zBt;8JNHm~OEeKGEJR;+*apdEHXf}bM3&xt=v&A>Yve$v+{gXuOSwnCd5J@P-$Uhd3 z#t@+T=W96P``NwUMxqZcdG6D8-nb+!KFbw9foKsyPT=U!>h8uEka}UKq;apdEsGqD zIDWyI=Hhzv| zb(&V3UDKht(U*;W9Ryq3Z>an`JpAPL+nRTNBbX*DYcUyM{1vKoEs0aqyx+9Y%4Wj` z2x_X@;}(vlTR{wXk!LZ(UY)ldJuTX`*27IjrKlO}886nj#a3Q;$9le%A+sD^^_^vF z%@!s1()`u#_YZ9ur;$5qCpG}3dbRp&lJkYRT2gmx|oUXPQo(#??j;gh4t>_r8R~rrDL+~cYx4tVq0Y3&iCB?>d?(Va` z`f=6KxQS6jwv4ALu@KgMF!#Q#b0+z!Oc;8-alO^9MG`pOXxHCl-gPn*6qC}3$JOMf zeU7rEkaYuCsYO|+!{1dJ+M5;(kFZ^1g}3B2-oo0A1|H9+E_;Wqtmc8|4Mw(XTxiNM zwsj#Z(9qt-`q%9W!r7pQI(m^&u6yuTNwcm0H8X-?lD2pJ7J^IR8K z-+`UaV_Qt}#%i%{IT6S>YpwIokh|k*VXqa}NLfJpJi4UA;h}kE_L92X*r?DhBwOC- zKU461pSrcaUF<$TIxbfkwq{>I)Q{sX-cI3xeF*#3 zxEMGz7A?rj~r}?DXtY=U{~hQF9Ze8dyzC zE#*iF1RyWX#o4{aVU%ZPwRWWT6(sQDMbuF_tsw|$3Bw` zLo=@LbAbvvg{h4Fj5Vh(PUW&Ju8_8-sv{=BApaSbKhOJh?!{lLGZvdG?|654vJ-_hvgit*<|W6Slp%f zsE&&LY5?ph)=9|;%#1vq#}0dnpjX=C%@V!ss zo>MF^Uo{t}I;uA`s5u%zY0w^hYEk%#WwrR-WFnlug^tp2w~LMIw=!_^^mM&_<}9yD z7B?5=WJHg-n1CX!CjCW{`;^gzx`y6v{TWvrc(=VAT6$}gGXyqKg@@qJ&WT1Z17YCX zju=hFmSF;sk?J8Vp23Y|w8lD?XJG=Vi_mYr_By`mG1}rzaRcnu_TMg+;fC`1SP#5+ z7O1_Qq(H>;IUPd*r$Kuha6kfNj#I3WVxZF5hXIIgYf3roaTK|+c=O4&!0=6ccF!XF(>~u$`||h}4D#f+i)fMBV@#c< z-Ak5T?zYfl4|oOJ6g&Swyzx-7W>^U~WqZ4W`h?==_%!o=G%&#Hd*=Us5M zt5;|N*%ze+OmvHv>(d-LX%z>9G`QkOUC5Ee3MS0boDw98uC+~}N}?bDA{^57T29>Z zzTJL(yL%}g$D87J7nC(w3$ooqTF&tyhJ5h;4fM$mM1iyJ$tbJiz!70@k^{96dd7fn zAbTy}EEVgxhR%JQBK3A_yK^6?*9~up$?imY6miGs!~>1lK<7tAD+L`}|D;#{+U0OUdf|CV#iWPr4zZ?xHdcGrsaS4TZ+ zr12q0+hA>e4p#mfe|h@ZJV)95Rxsy%jIUZ?;Gu5DRK{77`=a8FmBoU?7#Ml(V`V_b z;D8BG%fekQc0CY@CrV*Yf5))?J~EUsYw^Q<9f6=Cn|rcJ^3T)}R9DwiPaF;;h+5&G|DW2o|QYS(0-@o;9}lt94YF2Lp^*z59UjnqTFd=SS0Cs9aMObN{MP?FY4&glz(E`DMzFRhSZcfEZv`C+Tk{9t0_6s_cI`MxhsSPuolj7jIx#|37lu_=A52yg>rN0 z_-u0Uk_HL7pl%ADVvX0Fx$Ls&IM{E&;{p#1ji;4OX_aA_g|%r!Y|gpw@m2Yb9B1g8 z>DcbqyOV?Z%3d=)AaN_ZonD%_x})$d3iQNz%<>Gov?M|FYPB7hP>=(|kcZcgr?s!Q z>v~NMt-R#d@t&J1wJBA-`ECrDe93q#lH>@09`w-x+;!(pWi27H9NRodKNn*+aCdbM z@ud^Uv&iRNt$y6mNqLfd4TmI2=U)|4erg;I5n6HC!r9?kT5DN#6O|t%u#8vYLV7c< zPBl#{;^oapymX6cuPw(8GM`hb5h71AtfiExU{ygtLp5uw+&FU9mtAno{MX|%8DdD0 zKxQsvu(;mfA5)UWR6_$Fw%%I5mA5`zs0P=A4z6mLAW=#@%Igacsj=a~hY|x6f?-1I zXR9{iihQ4pb!GZ|9N=-XvZ`EM|C}F>lO&A3gIpm3_28(D~%2b;0EbYgktrDbfa+8alTIWacsLT9jVGf~F@W3CizmdCdbq)m5R~ zt&ql4tt&b3CnXFF>7o;_zt>~f4Ylw{sdH_8xU`_bXk#T0-_2d+B3ZhQcdgd!b1|_t zToiK;u^Kleea?1zL>W>lc}?f|xl38a2r>{&9YfpeC*I{B2p#Vg?@{7T;5^owoo|6U zoz$b=jSI^AjRwC*? zi;Bcg(=$Qz>a2?3L`^-dnu^zmwr=cr!D zjB^8MICt~1rg{#c+{{LWeRo#H3MUiRci#33(nQx@i%LZ6Sao>nwav~ny^c0nFjuKl zPQ4+)y|wyYq@@0LPh@*M?AogfzLEepYY%7AnE_KGV0Y|^7O~pEVq7MClo1sUJ1eCx zOkI6%HtM1_t&hPGe3;g~nAvV}rqA@CbTZ&z?Y~MKVZrSB3r+%S0m#Kc5aSO$?_T*3 z9KF^Ydnl-nZYciuaU5Z5ZE$i#Gsy8uEGNR`Z4NmLW$encw*>-NTjw1e)t1NS7zchZ z+0$#NwBlUIgzE+Rd8!er-!8_`uve=B5!hQUK*Wb({ukaD!@}bw1_%sOu!ZF;h6c3# z&ZQ`Y6yK2iTR=az{J-uycKd7cI)k?kEu=pcQsfBPM@x0G3OCV3!2!quhm7dXO&xw- z9C(RPG{DU$aOl>dfE21nLWrag5r<*ljr2OW&{@{gqwi(Xai7i1=;2Ooy|A-o<5t&= zv1ihCoKm|#mEHQeuBOAK^=RQC_vyv(covELEJsO}tK5zRb#v06lBBl=TfVgqRNXwh z${-0~gC6gBOVIeLLPS(C6KgS&Vp8=RMysrk&Z7jLepM z*l?f2hM%7pwYB;Dy|a)`#?59_JI)(N3u7?nB~)3VULd>f&Bv2Ha{4#=6UQ0Qh2a`R zLA?DOMzvgwam0?xB=Q7xI_;R%hGglx)ZpJ8(|@Uq*LX-g2u9%w_7tt8X+2ycecLkR zlGz|sJnGzoK5t_8+WyY}%UVU(pS2k`dpPhwCTWr8`wfp|!!Mc$5*f_jsei26UGPP1 zyoJV4#zV)Yhe8^pc6fZ={1#wALIedKMmUDlN5|GQ7Q>I4D#vm!4?(99kcDecDx- z3}mjdS#G+8dBIYn0;wP~Gh(0&6LOd)or(}$S)%_&$k2<+_CEvX(#d;uU2W|SSdtq> z9Ejsel0q@u(Ey%>+vi!jyL7RL>2aE=8klW2w$@_1kI|S8#Jy=~Wa$|>9iE%Yfj1#zYMb#RmiBt( zHt{Y4XxvY8IS-|c;7O&>6wiZrHDY)7j_+AKeg;t9TVaG4Hsz55BFK^ej>?ZuW82b^ zgxlP{ellF($Kp%m_t@U*>G%8s_VM<9h*Etjh)%|n8gbE5@~MMlH89Zqei6=NkHgBe zP8s-(`Ir^Ge#1Lm#9x8x)4<|xmFg--wS#$^M0<}9bN%B!qM&gIj8P*-5AfA8oAG-> zs{1x!s}`2ni8(jvvZbgTu0?Nv#O9d!{uQ!gC1w{??-~_nz;esCHq%I<$_|L^FARHT zrZg%eg@?luBCZR4e$)VDxk}`BAZ14?)r+(~ls0(3WXo38c6}qbOp7*bDl1zPRe8M`dlJWf+%$;JI6kCFOzBKYAv}mz zRQw`{5O66NHaZR)>)pP(?rJev7&OuP?o+YGPaXnwNQ#ZyhLYRQR-Byarj0m^ah^M@ z4Q~49U=(X(iz?i`5nKJAlLKfArAoY--&|~cVZy^(IDVr-m=W(N9!Ecxq+QD053}J> z(Vrsq5<1v)0Dp*Ug|bjN^bs3#sS#Bhw=*X)!q4HhVj#!ekB^@^6RC`!2HgG68qEev z{%^*wfW6_!yk60#0PdaoSPEU`mv$-_bSZ;CE7N-?;;l5b@+jO<)82jixC<`}!#*Qy zt#5TLTaSjbN-IYeO9Nv~X;|SJ8*zrtZsgvi<>hxFMlta-7n{~Erj~}PnO2SR66$CL`~c6bC4Tvtrk{%z1LUR!NgFeLfL%))I%&s|mK%G}U%z zXod7B;KT1U8FjHOTy4mA8{hrSkuK+A7P9vS#f|ITy0XpWykvc8#pX=Y*7vC zO{qd-loR&YG?2`=MB5H3G z3XOC?Wzbks`FPI`Tx?k11$TyR?VLW+LlmFL283m$IL2BQ4P?|fDF>4w?4 zU(md|#fs=xxvcf=L|NTVw<|!;7WF9R=izoJ4cyX0X{S}MYq*h6BRzn3d$6sGZ}nBh%v(H zX^xt(B`kQ9!>>XTt1PRdoZ=+Pg=uCzwoz}!S>Nj4py4B`TU{V*Cu;`>xj^8Xo1v|% zFIwH1=T;bC6!qDH4J+VpF`-bl&U_w)6$Z$M9{qBW9Ea%Wq`b-SqxfMrbDEaC%j_vi z44AUk-(d%EBQk?2XP$t!jH)yi3@dt!>X6-1_#$p~F*4nX$p@EPvdWvzcJ)auqS~oN z?nPwDx*&5rOgS0WBXq>F`(ZCBqFqb=uZ$slBrNgRj-{IXo;o#qfp%jg_sElCNZ~&}pG3H;a{zz23vcy$LZ*PzDpz8xlM^PG`6?Ve>y4Ionn?Bl?#VQj8N(|fRFAMO;c_K6+xGjj_Y!*Z8B;QfUQdhR>yT{OXaOC9T=2%;S zuIfd@5FziAJmC5r{fYb6SiLgjKCdfR=6y`r&|A!vE5VCCN1|fHAoI6vwRm&wTW!i# zt=(P_jDF@4KlsKyevC4&fLRgefzFIiI`}bS&Y?%#W?wX=E4niu+Tp z%}SE55)Jial~pJxkJ`>bm?~yN9~UyTtX{dA=9fdDwEPFJje^*nb~DYk9dRlaa3Pxs zn&e^{RtRNv;Ydf)%AHaz1FQ}S6u4v<`F?jUSw8Cgp57-l_fA7{3!_kCv8o)UwEFuY z0L(ipC^dD3eBd3(|5=mXlD=oYUN7c+{P>E&`O=rMZDgub>~4Si|WUg$qlC(KYE+lmhj9I)DYF_8U-V`J8 zR~PwM#&dmj7Fc&im+UzY<$rh z)@5zK=VA&5SHD=FjUArm+5TTURW_m_ zk7ksRLv8>7dskJvt^^1SwZ+ejGcl8LL^F8^5gR7kd*80z>?U(zW%CK!Kq3r|Jag|n zK5}d?5-jZpF8r2w9v~7HVT3Sint4gbyl>-T^qJXdes)oypP2{{0V$!hjMeDJykrIN zsSY1wgDQ}k0B&|-GVi(K zyYAo!LLQf4#K5hiL$;}Q`>O{^)mP0Y8sT7VkFLf>PZpyy2sY_Q#TnDajJ2sSw%z84SdDx4*nqf*J<(Ry z$+8g$)&_2)=#cj~?(=OrBaz}Y+cQ&p&^;(~u_Fd>AnyEYv{(+XgVQ}heqYP=J>N4$ zZAS+=G8U1%O4AI@c>gO1s0ni-fEe1;mKfzxT(;uC-&2IR0Pdr!Cjf1T=nGP$7T~;H zdZxC2&2__%*sFTc8f_iNVUDr{#TsCsKElwN-cE4vTn`iHzD;XxON z3&0**&BGD%X5Sm=ErlwgeM&w-c*^R+qqRMoh8;zuL{A6O70E}R^o<|)6UYm3vaJ=|2gqk#FOH~#COmE}Y z4VgfhX!pFbDiT}D8l2zI^`dDNad`9kI&zUX&W79E#qHe!v#^(75rgv)K9Pi_+D30P zrP2p-cdpHrY?)jm;AXR7yBJ0U1YFB5*IvCs6(6%ZKNc=19Z^Dh+OmqIl-;&uJIi=Y z-ub|lo_)E|ahh3M_r|gK4uxAoCL(nfHzs}$&FN;MsFH2h#|H!+I1Tv#^qz=V^lq>) zpi6mImVYnwF#$JB9R8R*`KAD$);nf7x~1*8XvoV(k;x>ER}5$8ogZ5y*OsWeC1n(b zs+c@3`*z$J5N7s3j$WFQv%*%>$htqE%~)7U%+pILt?qYB*v>HbaORB^rWt5{OTB7@ zq7*Ny>+_?Y7VM6vxu3rS)Y+-%JH)PwFD0@f^s);3=L-I)C1w4Mu5pCXe@hdTP5O*X zTAjcx!t`#~fDFUwZLwD-pdpp{XL<>)WBON1p<8xOQ&YBnwf^d_d^=m;6p_9ni7l+z z@evkHz4)}R_UGmI&(zvg@{8r_OBC5YZ^txh@63PVE2NjLe9Xuh$+DGZDsH%p{=1x8 zqq@s4s((qr+Zq<2x{UdFyE}lD?Ynwx4(T6S}kokodrm^u1o*nWOlwSOwf3!7U zc1=Vq`I#HhJ1jEpt_O4;dEKm%*`%9Wulcp3JYRd=(cb@!1R^M^5ZV8&1$}?*;{j~6 z=8w%h94VBdN;~jzDE41?T~d_pDP-bmr1s9N(62>FrQnCNRlf_*@)X~z)+0gn>^J1O z2Fs%kN(UXmZ`|_jbH3{&`oAKI(~B+*a)LJKu}I%D(SR*PV^kMba;;AJpHX!oQb54|3agt$*wYdzd+U zmr9}Ue>3a$=bl4}BfMTKs~!6_|AnSs0|)i)^F4~Y=9W+!RYPsrzdnO2`|B)=LqQ~j{Q2wKUiJJ~7g&dWSER3=>)%)Z za@GFPoe=Vx7AiZlmh}wA)40PF{rq`=TJ{!c>0{xetA|pVaZGf zu$w!Y>f%~ze zIi0!GboH?VdCmVMH(r`#9UOqMmYvaeS9kaER;yR{oYm^^60HBJog&7cj_Vm^RC4G4 z7qU?QX?$&k?D&+I{mu#ZRl5`21VIaqZT=pG&c=vnXF6d3ba4#4ACHL5sjJ;kdgDA>tEvfZxg)yYfJ>m2s>N}S z`WD@63VWhH9*M+(e4P}f+((O>rYoI61l-{7mCVRHF7P=cWmar7Gu{*)F|(E{ECSr= zJgkKxp3Z@O3*PCFXWDm^nGAn>h&=_3B`TYDMatZI!Sq{nf=!up<>m*4dNR?dRKxde z59F)!3&VKH5D3*o<1nCcDYQc`>B`&WNRvm0osKfLnmO<+K(z}dev|z4`T!uKFM0m3 z97p>nfF=8|0Htl7-pu=O%;S$i;*GX4xB5c0s$T;qw_cW?3xs@TCQ_s~=O*;u>*>dh zYJ|sioYkHQgzwHlD8}~QYqIEP=iFWW!RF;N_0(9xLi9OGm)=!Cv1XvXFFT+5a;Auz zC$0+fSN#Z1`S|Y)MPZU~57X81Q1Hz{0w~$}=;B144ku4!Of4mx^A-Qr~jFx3_ zuFt4FbA2kO*lZ{L@~*X=`=Z2PI86O(Wnlf^*5Ls%n}XD?h>Rq+=r{W5_k*TLZ<(*S zZ>oIEH^%TntWiA{FE<)XD;=rymX*d!wi_wakjY$>N4cKh+>CjZ@KEqJar{oquH-GN zJtO}6CKL_3VKUMGD^G*XD>)5tddZFT0Xm3QvaXGOvY!hXo@RY9G z!Z!~v5qJIF^QNe^B-NQsRX>O~eq{T!ppxM>%PHT{>$2d8{jyvD&E6JBG22`ZygOz!VK z-4sqtJx@dK6yQESJihjegh|i6x2ERjLvKg3lFe4fA1WP(Cl+=^Y1@7njHFEdW#tm! z8%*&nII>G^&dT+?eji-kco=m7h6TM=`xOs4C}@WYc*}#1c5)03ItMx*6#e5<|NIFb zVoXKjJ&(T5ebS|_C@;>{SuyG8{xRM1@s)OBLE~Gtn^pyvfuCuDYfa}D87ur1hlIN! zA`sp@dQfb0I*h`|1=eN#N{j}qYFhM6ZaU%5X$OTMgXjEHRIkpUKD~5zYBGmk>61h~ zw_%~Av`C6AHie4&(@{$anamy(_0Vrdc1vTKqR-mml-?_pRhtREKC@?=n&I~U^wfs> zpW+M2hwcg$TlQCc{?Y46`hVoLjSp<@uvZ{3Pp1L{-H9+X!JWFFQpYJ$hM6EQstbO_}8Y<$z?p}ZN~_feY}3P!WBU4EI*c4 zp(Gl$vQO%ckl%Tkey67z_&c?J3BTkRekQP%&8D0+pJDj)9*9Y8v-;nCy9+P=_K=_H zp`ti1g?tMG@SFxn=8W(w!hS?eYoRyijnF!`a6_aQcuo#iPp zd~U7y(4$eDGCYywe)vsBS7YPm%AV`9(TVIkrU>U zl?2Lvl?ZJaK5ce*{DdVzoqL*lQBWkgT|=DMv4Kyf~n>`o#FIXHrTVigEw`x#Pw5nba zMMue;S9F8RBi5ur@y{q_)tZG6F}?0oG>hOFSs|cQ=ZCzl+RBrp$yFbu|3R>JbeO}m za=*&6+YQIZ;R?Vu+pSmYnD9BCfr`axyk=oaO(a#jg#4Jj0_4KKq5&qP%=-4D+M7dYK z>f19_?Xt|c-JkyClG}DM@rL>%AXz)zm)*PhGLeUAPB}v>s@of10tN!GU%^48yPc9O zgs=XVa=e>Ntu@bkKhE~BR4#UTzq$8V{;%81P?zuIU0jc-ioU73nWDC$7d}9!Ra)c2 zPK+`0;=a6zvU=w4=Zz-Wp>CfdrZT_kIh-c2sU@OPziYP1Y!CXI2+?teo`!ICuxhbN zy-w~E7CABmY^hINTTTiuss5CwwKA!nr82yyNYyz!r1Dp?}v9%A0u&?)lo3H;7i{lO-7 z%j%6N_-9MMB59IMg7ry6PhC#ULL4Ds>G6yEg`-7EV1dZ+9s!r*?y}>-#CHq_deQ7( z1svz;xSyZ_E9zT3E`?)h@;cEz&Mg}~rV8|xhE zy@a6K%2a2;r!U^S-)B*+u%g*bV!0r>VonE&>4fuSJ%vh8hl{MrMyDc-u#<$5UaInQ z+s(FL&X2P->S8p6cJyb(95w+X43oZaZ7142<1bXbv{M0E0!8gLe6BpGy|h8S94b`M zrddn2e>ux2ML*3df=TF~Fhsi$qIcYHe=nA5gv2Hv2^hEC8>Ozw9% z+@ZG)H>wf|*%?i3g#=5bX>h1YZVxb+r!5Gy#+OP-E-qG6{Cw?w6vZ;41-s`!Z~#w% z%FuKgg6ULR{57-_4F4LyCtW__D|DaI<*isaN^8M68OhEUB#rl?Uy$q+nnTos#=ohO z=jlT@0!fc$0YmiP^piA1Y+&I@*&>u#DPj)b9lWQpuov#MphX(yZU4V}V9_NyVH11y zbK#pa`OP;|P^wQWOs!CAVC;f0y>6sR?^?PMc>%6Uy4yobrW%m2gM1%M&>O<}(q-tJ zX#F7Tg?-4ty|}&^N%F)6A}8q4j4dokPC;E^Hnmk>gq9I;xskhUCpsmat8j8JS0gj5c8Zhl1Y%|?D3hoP_ zOJUdcBV>c~{pkH*j4fDaIzJ+-6cJn+z=iR6rqljUL)%xZQP&I(d7g9|&G+Sv$-T7x z@hYjll$upfk0(1l&`2;uC~{a$4Beup%e+aO1*?b@i2XUNGwyyjTXL8_!%i=9n09DC zH-+I1OSfl{|EkL;m0uyZn_p?cZ7ps0_!OfiaMFQhgX8SC%!C?*^phf@O1i$R>TL=61qudD*0 zTsapw+tKlFW-tYXPkuJxDlqPihbsolatJ0+b-tshczg3+Zt+A6qA?uP0>+X12|)1- z8{L5x(l@zPF#nPkoE^YQ<(dW;hy=XLM*6cf3XWZ;e~E5;F%;+22nLq9b+M7DLEj-n zIQ367fq(!A9R1p7X878t{fduEr$>S)$_7GA&Eu59GAKy5^CK%*;zUR!r_rwB;s;!; zrOw&*X~VXwvA+zs5+=+A8vs!$(N1M&3LjQS!@@)RxIRAJeb_kpKgx~mixmvg#pQi+Tw@${;N|cZ?Uez#FiP@H$x8at9vi|$<@p;2? zN$bJ{v4tM-LxIjxQWf5ci?#ii>qk0x#tX$NjwRW$x<(=A3Cs;bPtPK|sW}__Jwtwz z?EMyY)OiuqD2LMYemVpfBrCtB`f-3kK{e5oVK(JA zQM?V5e`Rl3_E>pil2z=YxKad z4Xq-GEGrw1-{Obq^4i0f#(X=5ra_$s6&Alf33S;|4=MEJr;D$^T4*hb;ko;I_mdyX zvt6vW1qIi~&~eS=sCJ@<{C?ajyA$g_qnaVs7IC_DTbD=oe3;3-1{IcATmxhcv&6`W zO-6s1^~U_<8BNc!V-WnY@%zEl?!&~WYx`Zg1)R0=C?=g{kR~9_nQd@DF0$3*Mg-!NMBY!wsfaL?hMSLT!FIj>YRjU#{Gu7n3h=zBVH1;Y;p%)R~` z-PaNw;7J{ZTty;`kQFp|MI`IXn|mkz3>Q-Z$aed_VyZgL+so?=Lz?pK-q4@FY$H0b z(|6wKOWad6alNN_0;z~nr_VyM=A*K~cUCc^J+sNyekv?NnC3^&?TGu&)P=*fr60ep zxLtbrSCfKOhwyUvUcT9Y4&>!F`&U$ypYK(eGNe@*_NO=1_WtimWW^5~pjn&7x4$%9E(p5quKQMij%6|(pH6@pb{fye zs+Qb#g-Pd5*9!l>NPBs&U{YU~Oll~4PF7c=p z!PM`k30nIv|59Av?PN@W`9oiRfa5Vyf6fH=FoM7CR}FnqYVv9W-Per2@*g7#G}Ghc zDf0R$^Z8(N{L!xG$J8GFlp&Guy&*twjli9=t3g1#W~~WFb%b^fnf1-_gy3(ca`qYg zvzTmF_V4#-$MgP|?n-mim;6YUsvmc@ngWjGY=QCq^cGl2UYktbNDwaVYxtlT#BLJD zHO+zr=ARE0aqhy9OoG9SRq&8 zN|M9;)UwO1EdPD=x2ycNHw)?M&L+enew?hwfx(GmZ^}gDqO$3)r``lj!n9t71kn^A zJAn+xe~1(>*Ke1yU4P&LLoSgeRjgs627mt=KN%Rdr)x#ixyM{8;^+=+AF{M$$6Y=j zQG2;OXtwD^EgKZJ9cB&vQ9B4bU*(^hbt2wP`uRpC4#sQL-$GLo_VB7n+pt^2F0BCa z%sO0`2O*Lge;mQY&v3*>7gD&4{F_1^I?*~QJkU#k9S@e4=5wU6#zWXO1|`sa0&R49 z_viUN7F1|%jDhdH=Kz# z@3Uah!{ri8g<J&{&U$w? zzDvE4N{VPbzsPHKyf{KBHJ zK(q7xJ2#3w#Ll>!&<2gi{N$8pSaH9N?wz>pZ{FzhwW<8zn~Plu>!=w37yEx`N-sR^ z2#;Vpc4}|(1=FXsP2gel9z-~fTS*sK>?j<;K(*_<;`?%Yz9!qwL@vC9?2)4&jQBFVob^9z3sw7)B`99iaaKE$^S^Fq>|g%b$#kX~Rk4 zYv_ZR{`&GG>5hZskF%aop?TcJrom&^@|PW~^Mj#T&|{DMm6u{|iD5iH<2BgzC-3Ke zb19uVYgnA#81DUtSfEr4?P8nwO2k&&g1pP$pSl<)vSj}>4K#8XmAe4j&2{|!FW9lK z^pl`?c0?MJmaDHRVTPYz^y8r)pXVNZ7(L)zSZxb%ClOahXGkQycBEjxPJz5rjJ^sB zQWp$fH6En?syLN45#vxQ99?9HW7ybvJ|8OFmt*UjtH00rO7N|yv&tyB=+h0M$~NR_ z{qywapPT8!7a@g{w8alIRD)#^@Kb{_o-_dUUw4L+wfiFMv3}5 znkGNLq!e>DQ0zYhAnwoX>ZWqN;op1pwy`))TW2`kh888?H;UU`$q4wihJ!5^JDeH? z5!6dzqoCM5Tr|Pl+pxZjj!5KOHAj|4NUr)!|1W(h>9Y50!qcNnFnKr|(^RLaJJAQ3PaD13wi zQ5+kze+IF!e1Ms~)v#NV1(7LK$yR6Zai3n3JXRe*53oqi08N;e_MLUHq3LlX2nDqJjk4F>$@vHE2d~vm znvO1==y*Pil;-Wd*FRd%qy1`UNXD{gh?hRoRJ8GRE+{YB2~4vrV@eg7{kY&Pf74lY z^`Qty83a}2%#lPH2}H-@iU`1xf2?^jNc@JNI6Q!KyEg<37-4P{P6yYq?ZtaEE%xFB zFps$dR7B4?Z)3zNZQbXV)|-v$z(P z%7_{807F6*Y*nJ7KLOqKuy|-Z7E6!UN-@E;I+(I+3~3|<$!ujJ7J}~X?nT1Y^WeI) zP7;*o+9@C+g@zrL`uEFi)Kpr87BC>d4rgYt33%|@TaLnbGn_*JilEuUsjaEcD+1q1 zRg`LgD*~Tl1D^x1>LTp&k6wcRSmPY0`=MP%eTpSJ(~xi;0#Hjd&6y*b$fKLaLn4t} zaYb|i8@5K%IopXgQ4tpW9>TWB1oV?bfkn7 ziLnErwt5&daz~M(z$Y0(ncA9#1AZI4gMS$LgT#(G#Icbnl*3`ZA$HWorjvIR`T8Vq zK_aM!q$q&|XK)|qTeTtj9%mko@FHie%pswah6G*9tTZ&bL6Pi4&jVQ2(T6picJ(C> zF+-7z(^O?}YK@eFY)D+5`ge9G08&$)l1H#W&70z;xq3+20UTvh-2%`SW(`g#Ltr;m zTCcu&GB4U%#!W&1XGPQj+g$=>YZ9z3Yo}XsLfT`1$gdx=;Kt;78mL9m5}5xRNaqV4 zSK|CmK!V7#8Gvk67vZg?hMoAC)A6=Lpr$s1EQd5K#d?CiOtmFUM7DxtAciG3M;<`E z2Kbt`>dhw^g$oiMCi2`!Vdwu=aLY`KYkN?vnoo*?Q&11r6RYsL)ubI-AlJ(16fc9Cq9Q{}l0gq1uB8vs7q>9Rk zf>7rQ9*#0v9MGJ>1y4wFHUNwB3Hkylpg@p4NgQebvBHqZ-*Nh{mHK)2M*5RoD~mEQA=hFmw`95odXq6k%zgQPZc0@MI7Pz={MVrP6{ z079ijp@aq|e6V(&;G;$S%&5IR!ZR zo`0+{v^W+>D*9Yk?qpyJUCF{A)Mnq`^Jpt;+u{zMK%B)aB}oXoo1W^WuQT0s3g&U};BioA7dBNe^-O!9saZzRT(ye;ulYHHc5Ptw+JmNi8d%FIul&WV|mEXWU9Gvm8)hoh>0*! z$aD;Tsd|2(TWLObplPF&yW}(A*b7?T3Jy}4XY$?Oe(zeI`?kr3-1|Y*y-*lSV`>{u zLpc-=eF{oPs83AcPW``(HxI7PaPb$%PGCCKL#&`J1`yTfz}Ja3Uxh09$qHN`OI=XB zXgrJ2Fr6hGk(#s9SIMkZK2E9}Xj)LNODqT{Ihe<**0Kn*o;gq`iPr`k&>Thdst2O! zX)VIvzpu1IU8`TjLTyyY)u7X!*yzQExGcq)X*Nz&1G9t?1{>TQ*`t$$08N0RM5Ix0 zomn(`k`sZ@M^I~_8_;RYs9J+AQ#c}?9cJ0CCCyZ%OqeSYW`=>%q?rL)AP}7U2%81r zT?apGXJr}3g;vuh+iw#Yso3p-a(4N@v*pzDW$eh|(JwR(((eYI*?4|MfmW6*JDKg! zAI7WHTnYy~qhCF0SevcaXRK5sM?fhJa5XwN4S^z##$~2NEi)~U9H?PJni4euBEwKo zlvpVkI&zE)IZ+InP>f<1>fnUtKnW6MlsN`GzyfG^1xGo$tXxF6EDKubmNLUiKV)!2 z0xjX<0xc2GVSYLv`*Gdvg^_j3mmZ=_NbsW7dHmX1n9aOY24sT?`bxRJ?$qLJCc3Od zQj$V$LdyFgx{B)hR^#k@kc8bs82KZO6!`f^tgu8Bjjs85_WOln&EpQ^58Ka}yuU1Vf{#;&ffBsgId#vnZ0IsZ7D%++MZDuagoR2_E$8mEb zIQg_+m#7`&e~LUmS)PNe#>)l(qaNx4R+uX)9?1UdiE682WEGojcAJfuo`|~TcuAQd z?RlM2RIIFr1@VlKtbAa!=!f#mTo`mSZ8KeoLEBVX(hSZHGGzwMK0u(*=&7nmF!ljQ zLllEyT4X#JT`godiwr7dC@U}Kp^T2Qry(n+8?z5QqSYnF77<`1JN%rAtPUnw1X+Y1 ztbr6N&Hc3Nxcl+Let6%(^N^EqWsg>#5Ubvj7Pa+>n$M*Mwj1^?ZQw7Gl?dnRS*7&3 zw@kv8+)&v*15-E*g`U9Ft$Vn^bf}EXZbbv&2w{X0TEvXHTw2l8e4)2{Y0)K|?nRI= zW;cW00!MMmb|pxo)~wzFrsH=HXMH4=Q^VwHyq$o$v7o$YkRg;NWhp6q-!$n^tR2ib z#AO{xWq|bPGF8Z#q0goAPb2}mj9I|U1vJ$m8B&(D7LUvXbZ!-?k&;&(&u5aB8del*wUhu>-+18u}Sy`#TB zdw_)A-RZHfUp1B==eanoo2 zDhU!C0A;8d$M@yT=ETZtX0MNxR7;rXXyohc4thXv-WS?jzRGv>+QrmKem<$aDM=v{ z5}0Wg?nmQZ)f$#6LB^I$`n?cHRM53dsp2j!cJ-cRiCUe^j)8VJ`U*ZQ|Sw7~i zv_vuKcSwbozC!oW5luv4%ObUFx7B!@vJ+Wyorwb&iDt)Wrgu_PhT3_{Im;9;4#6Jd z3uwW{!uoEBxOlv}Ln7tfkJR(8_9LnS*^oYtFTibs_KE)$22FR94kuTzr>o zyqFbcyj<`Q@9OD*h0M2sYe64RjC}1ve-xZN7iwU7&?f6wx)gp#FW1QA##6{Te+u`i zcZ@{ZM5FG-N6KkqJd#n+riRqHH8r?ZB9!cfu6+{>?w}Ir7Qqge;5_3*znRiTBdNPxEHf zQ%jLJ5f(6;)<=J?Kav+bhyqRbQv4m)jQ@>oaZ$)AzkbT9> z>^f7RC~+fD8nZZD9;M>uK4O(ufhq|H>f(h*B%BN7x(Hd&0wPc=hv=L`i4*4`R7fO= zmt+fyQdWI4uLC)xqh^HFHPj8mWIwc(7RoI<8tpYlAwbD+3_jVm)*M&?CHAf1ZcE(S zcuAXZRNv^@tbkDHW>&31P|t-c4;tt^*VgmW)BF_s1csjlA z?WdrTG)?`5o zp6`zXzsgiOX0gTkQlt_}o{HF{C+kz+y~(m6$<5f1H!etKX_s8O94@6jrKzexGBqERkEUg>J~4LJSJ(F+M8Ov|tJ1#~x^2|IZ*0%k z%m{xQ?ePBIiS8}ux+%Vi(ynaWRLi5IXpFJqN zH!I#5u$jpBw(TFx@U{OiK481auMguy%gGKx$m#UuZrmbSK)ks{KTMpCK4zBV(y{NS z#+pm%DDw%55w2^fx+w#X$jsIY*B9@~$l)~dPoZ#`tOhfSN~fPNfAB5fHyl@&nnL{f<}j>rIU5;Y)KKu82)cDXYVnn5TiC{uOI71#{<%-`yN zyw|asB=>JCl?@$#S=0=TCrSa6&eWF1YKjs-${#;R)k<03(3UOdD7o8`V_lFFFWxqQ zX*eC#m8_j8%#_D;C-qeW5kJDEil7c znj_v&;~=KMUTr#h7AHjw$4h~9A))*vdns%+rp(l0fpTWiG8P*L88LQo%MMsw^UN|g zZ19)^5zG3SxWvuVxgKP)!!bSuxU=RzJf*m_khQymKfocIhez8J>RRYL{tuGyQWm4g z0R38Ji?XB9iGbDE*8HR|sT2A;QKvH>O!x&x?<;9=z^>Tvk#}i%2ier~$aq$}H97uo z?-H2x+>b;PxvE%o?y-hZ($a@aTnZ9iEG@Z-Sr_FCq?*ygX0-qPcj=hM1&ySBSrzzF zC|$<|D7Ph7CP=cI>N>>8`tdZB`zajEsYJ8sUd^?O7jrRzRk@ANzTGN%t?oATb>d68 z;Ci@PowlkJm0wwq2(!V3jg584s}x+*bJv#FqXGvTY0F!EWm0iieSs!#oW0xYANv5?H8Fb5(fu?3!+#lUg?2B)qylw12uq_ls~Dv- zHNBWM`>1Hp847hN5cwB6RA`ZEIp||swLi_Cz2S*1Ut4|R#uApX5c2ap|FD4Yu%J{b zc|)V-ov`4Lc-w6^9dvtYU1*@x|D1b55GJDJk{8gR!3@g+pjJ8F8EOwID9D2T%S(8> zvl>uQpeP1R5dwfjM?n!If2F}I;gw&RTQmOHoxd>0 z*+!J!*?da|#bTDB{k}VdDQW9#`PAbG@V$nWA!d6)iuZxbA76Cr--j<aDeSWzj~~ zQen`jSp?k36lSU*XT*V3U{~TGQkK;ci4mPQMa$e&r=Pz#QSyJSS~35+XV^))ZEnJP zYsJPR8D>yXUO+5MDFBw?NCgR;NmEG}og)XdDbmm^qY#9jNvE+Ok?N#c$?;g0OB(r< z3wpPX-r4;+<1*pnHmZnl~5QmzL` zz2^quqjp9J#roNbdNrmwy|*kboB6V(+59;8QUuGA+SbZEflgn-w|ADF&!8zj$GHNJ z7n3jZKRCF!bW`;4lPDl+dN(cwt-XCqTYb$z(GltJ z!NEU5-jc<{4>^p((rWkfcR+TBuzOzFD8Eo|IUE6cun^<2w`-|wVxDKQw^MEDM@0{) zZ`zaj+@5eCz@&3)G>i|k%Mr_+=-PX@Xcg%SPRPyX-8c;AKrB#k94T2!)-Nzi#zTD z7F1}j<0uIOg@a5_qosxZd4`UuYL{x6HI$=l)!9Q0mfEDtS<||}n!Kc|TeIq)P3~Zh zwWWCl7RGM048;*^EFMFkNB&M_;tAcW%6*3115v3=R2&5AGde9>- zY*j@SJC;z9;J$3eSXip9``o?EV=Mm$JLkV4dZmrfxrU_@)xgxWjTg^!mfNbCavLPo zl;psq%_tgdmLl^lA*6#TY))C<2#{pTfdVzm>BCW4pSkGC>@)y_DhUJ;ifE`aVjvWK z#DG94Bxobd45`Rs{!}|fkuhabm>s)Rwt^#`?Wk!LM1p!|G5ypb6>`BDq>1P$E6G4j~ zjojv|-HZ~v6!DNc!Ge6TY`gwwXrR(C8N>@K;d^we& zMFo=?f=YX5B+^Z8v}UMP)-Z?1xhPfwU&dWd5;q`~;0fhsC`T+Og11<~qC|lcIVq22 z)PJC5io`ORQ5jJVFsUjvg*^IoobPG)gc@=Tb9)?NPh+GXX)`YE*(bMAp7J{SV_vGK zQ2*({{HrVB^7DGa^>?Wli1B9m*uc7!hnQsQ z>Kkss2`(w~HO#q@K?GLl3cIr8R8mndfB%rEHmTWhUqF8~k8>_sF|Arr)|5kxQNo3y z7?5;!<{aWD#jsULFCq6>O8vTM#Y;ns-ebP|)ZrWF;ME<6{c@^JhyB!%!5WPO9=7k) zcH2E{--yB&sjaLU&Hib_h7Lq2{1COq_fzku_XN+b#rOk`_WXzeM)d{aKq%o3(^P2P zN4+@2P2;Hn;e?_gJ<;SDJ3+X2pZudYV0Sa!rD^3xkM}&Dg}yRv%DdB|H`Tlqjyx@W ze_H2Il{F(CkA6H*pZHNxfLptT0^v)e-X27M3HCo^3tJr9AI&Ve`qFr}2-W9J_lT_W zOqVi-$JvpLLiGV7P3;0zw`VSbMX@l%y-}tF7Q48$Fw8TTx^}@dG(d`!6%4#T^WiBK8I9MO8GQj}=nl z2DHxR2J%~Z>}BMkK(AfthP%^KLBIZ(b(WTEsO?&hggR+Kphd@XDmL4HnQg9AK!ypQJjfE5QJQIhs>eUOKq%)xXl`=KCjUF%(_x;IT-!Wvu@kYn(l-q!m$`sn z<;Pv$x)5WvV3VTLeFnXVi?F&q=@PUJS7d%M(=>w~4i_ujIO z95k%s8IC>pd*at5*u$jzqWzI)!E3ba+E1!FgS|pA391b(aFu8B!G)a`dkr<7HQ!lc->ejV~Ef}g+&*`HP zAYlt;X~q`#U_rP6JC7EQ-If}~e41Fc!?bU~S$*UwDP6lT_$me~vRqSU7( zT59(nL+>h&GBVb{S%bM0$+aBCTC{nr*g^&B-EdzmxkZhUmH-48uK50qu~KW*qNRrp zD*VVHa!r^ST$66VE+$;^zyPZR162zdxPp41l(>JS<$n$gd7~(~nB7I9oZgDk~H;8~0DcJ#RQJh#1!bqo_EtyJNYpy{oJJ@sE4gI&i`(=|-BwG@F~>GC|Z!aOuY zaS&j#)9b(Yg5LsvTT)GGSpKVg>F>C!PrqCgUW@$R5v%sasnY#qgx)xU!p$`qGXtHV zH`GakpefR5C{b}R5It7HtbB4#_IrJ4)a$?FL#mrK6BSgGC&T@B5r$v$!k#@&%|T$< zLzf&Bgn{aN+sSaEE4xR%@toiYr8{yFPpzqnDW*+^@_=}(v@()Jk@h@Xtm!|^v+xz= z8+!xwD+55^-$S>!7p|ynA8bH?(feF?uaQ_(jEpfyg$|b`jfLyHN}F@3GVNF4 zR70k(qYobBv}{!=&)CI_3s4u&GU*B}oCG|yk1!4%QZtTWp#n9u&*$+gE6yBM-do5S zj`{SQbt z@QeNuzij4N*gQ>k`m!(`z8|9++GuQZ)M8^3KTx2Ztg5a$uRS;9uW#WqHGUV7l@i_&<~ChhxAXA9Vz5P>oA`xjvJtvJrvZ^xm{wkLD}6Z7QNzWq#V1e34c$M2 z@~KkwkN(ZnwfaCp@s9R@XwpfrgNg$hKy5x8PILikL!-N7m8C69a7XEJ;@*L>uf>#8 z4SdR$njSYCMeg+rydGr*9o4_PQFiY}Y|T5o%`EwC%l_T4{6u;z7LsFrOtn{b2S7#) zL#g{lTcn^#BN5Pm^?bgwR6;_}j)C9!;qfd!M?hfA{C#J<#D@e6^kFDyMm;5uBkxy9 zFSe7GlN;iw_dc||MElj}0K06&!J1Q+F+GQMQy^|b(u2@IA&2)XWzkRR!lOziMkuRlT{A_1QYhy8S0e40|==Pn3|B3uPP9s)H<)l9G()z#aiu*ExKkr^$c1GyF zt8Dd3<%t8+dnW1ax`7a|!zjSZvM7K*kf4Zbn*b!S<}vPRHy&>Y%_p0*WPTm?pm^;F z_$sPhwlR&rH_EWZV0KJ_i@mwG$#rQ5V*!HV>APih3>Bq8vj)^!0O|r_;YTcuc2bH6 zHN!s_w!O{*4OWR>W8(e>%z^wx;MIwv35anioSpNh1DMn{1!8rFgkdV8i>cMDN+_W4 zEVjWSRFMGyU=AsdEVtKzy`=tQZeKw`cS2@k9<0!koq%%`DT0_Gm_jWQgtyoL1+p1pz@wjR+J9g(9dFfl8$#N;DuF zMhX!LDiF*R)e|&HO)NB}0}|5Gg&{zVB1EMKG=&W+QA<-14RWb z1vDuTK@>El6D0u@l2kMlM9D-H)Kw8Q$x2cTQXr)$k}N?$f{dX65R{7$Kot!sD$0tX z1q36@3aF>Lg*KH(cmmLjBLPIx87ioOxP=wntOON)JfazR;K+{#3Y;~cNg}R+VHjgh z8C5JzB%*PYRRSU*R1T=rDk$<2(5jdz2v*}EvoixM7^#pzQQa!i&<4a65mHGIL>6L! zwLwS#F2V}P5SU2CRZ(UbMUsUyO)9RX6eNTdN=2m&swhl^K^+SKQxiZ^prGWGiim0j zO{j@-G9d)np#nw>sw%2O2Vo>tLa5qmAcCx>6J@1PicmmgDxeVA1SAY_C8Q}ZjB-k~ zkYcD1#1a6d3PnPo2`nze)DaXVA&VG}$`k@b!h?tEu~7qQ0S&+b2uMRp3b<7fQAE&E z6e5ec7Jz9OhJd7LN=k%f(Ns_vG8mZ=fDP|#%LhDH%oD~dq55?MIFMmS0a&?qEsak5-O zu&@@SZIy#+RIEk}gn@zZKb@pNchN0GL<6u1l3%; zK}tl0Dn!!6(G>_#Q9%+FMJZGb0YNnYREiXof)s@S1q4Dwr3DKpG%-Te0TB^mnFN$b z%Z(I?NTAXJ(zK#gC=`g4m}HRwMFByMOjQ&J#1d2uB2W$lMJWhKomE=fYYK%>Kv`gs zgeEmi*R22m0lf%G3L=kO2*(5H@6W2}Lxx$q_O^ z6x9StN;H%dG*L|`js#?lCIMvR8jv*$GL!~GLzv>mVr7$%E2wph;Y5oHsgXp5{)GYl)(T{lmkM8N>YtdjVnM4NYEfOD?%znGyqbiF;!H=)d^8l zK|@s}6j4A-j8PK=KvNJ>B{YFR(lmogw23qcO(-;~ts*lc6qQ3mQzcPCQB*_}k_yeU zk_}=xV`XEMqVI%5bdKUog`+DGDgz-T4FYZ>^6vlu#6;Y&6w-u3!U7V746p)Hln6?I zlp-IUE!NkuC^@nEO?m&w{;q)5zn99U)6G)2q#-3tfV`5F3rf<^v>`y!DL|l&C``=N zR8>S&kuX&(RMRp85-_v~DMdk4RLYVnbwP#EQVKf^M2e1@2q;jcA_N`O6jK5jC^=A? z9Fqx%G~_a(>_r~i3W)T9g&TwAy4!P5jF~1psEO@S)rt+ zR#+WV0t5j~07WV3N-ZjHMUa9vMB*87SIeB1lvXBoa`Nul{nWw$M_BR8fRr*%K)k5ke74A-tlLmr?`qAWgL71&9Vk zQU*+*2Eq|kQVtr-lmXu=f#A--(yHW&ks(tcks`z(Q4$42s*xi~lz~78$CX@>L7@r| zrBJQG21=9-C{{_5R6144-rLXiLg^Pn`eM6jM16J=x? z00~H>6gZ&JL$(8etFAyRAZ&$MNlH*Q5lTf$5=6oZQJ__bStzK9r70+ALL!zKL8Kw6 z0+pbpXebIO5TGbx2%#yM29;`}7@!$uN>(7Lpb#jgl%#4PNgNuZ#n zN*0+wD2WtIK!^e)NC9z`LLmwV!3IhJK}{j-&JZ*Vz${5ZjUhtP6eS5sFib}7 zG?bxKh|rA+Q9`XPD3sDIDl|cFlO+O`C@Dao1*B4xXetSy%X9`XkwC0%2~epPC^m#q z5LGKu3mI(^vW;b8Dxj=np{Lq~7e#Hcs{)B4rWr8>)FhNg0HqAYS4tEW5HQYi~g(^6T67!v}f ziYsiAiph$is3@FqBncuSAya7+W^l#8RMMuyn*jinq6VQuAV4;Pvkb~38P+x`fKZ7@ zpwKZzFjUhO1d%Nj1wjo&(2$T#0EsLJP&6u~3^f%L6D&fKfhz1nO#su0DQpsnu~QT( zHBhQ(U`H5=M=X*JI2e`&K{1turKPc@1%i>aD``TYqSZ25kr2!rh@zQ>5{g0u9I$Z2 z(IOGCla@|R6qE>1B`FzE0)~JnO^IAZ(IU(O60NpTVk5MM#by|7f?N=|+@)Z~NEkrS zz>6fQqKk8W8!14FP_A+Zvr1rlu#YY;@C7-=@DHbKygL?N@4FsZh|1X-~H8$g&~p^lOugeVn+ zg*8MDoDhhJ8XB0!GJ-}TNGg^{%vMx1yCBdRP=zYcPQ;^7&=iFOKu|DjnN?F+n2i-n zj?g4KCPf2|z@~tb9EFKM#i2F|siCVRG8l$nF;Lh-*sL=tGBFoqih`DwgsMVjK-n`a zh#euISYXf$o_vM~MM#t_B{T&=`v~1iPzAdniVQ?CZGfnRqLPM~hEQpwA_#?|n$X0B zC}JorkSYO*6jA|-f>EBA&~?FO3;+4qFP!PP?74)pr|T>ilw3; zKnlYI!4nvmDk_3N(11`-P$N_n08q_9#+ibOLqQNkQAPs77J`+aDH4RBreY9fq9%l) zluC(AfKyaZQiUxj(M3ZfqA;^G5I|8>#U)T6vzQ?%RK+0Z2z}nM0NH6#3P=YSVpT8# zlxh%gpkYi@SVsb&D2P{NDAf~7CP0KPn58Y)V-Z9|BcdRnDHN1&sh|^TB!^ivfhcH* zU=2;KT{$EL5-!NXuEazGp)i7$Du}6qsi0{UFoLFugeeM23MpbiC4@B%C~nMgqR5Js zX-$9=X$l&osuqd@DxjsRhzdd?h@=6@MTIs-jikdR9hu2?RR)3-a3CoHk#H$gfpCfh z$w8zLFq{cYs+0;qM<~=FVvdlg1omO4{OJ2npRMX`C{YBZ5X1~b!~~EaQUs>4Bn^oaJhJ~jQ3Ga# zkWtuZ2oS@KQ$)Z}k|qFXc!-r^0%<5nRWe0Dpc1A?fJ#86KnfIT6)6x3iUx`mst}SI zYJZ~`5`|1IrTa)+h;DpG1r3N7)Pz$YH7G*_%LYzVK?Ko5H53#qMF~RWh!~1AsFbt} zfEfx&D3k>N(2BGmwM9%dDoIro$_5mrQqYh%6p$Gw@xGrCG^0Sm2EzjKqR_RXgm6=p z+7ehWLm)nmT$Kg)l7Wzlfw@NC3ZbGzR4S#FLWtib6jjxu&4J!fZ!;P~R88o@YgmZ% zLy`-lCj&7u8ZsIZg|bXZQNcrE6as__KpGSlieaFNCP4*=3xbA8K@u5&fwZ!1IfhGs z29P!~7z5Lg3RK8=TjSrtE5a#=3IZVEAxI$R#4$k*3TcWdN&y-{DF_H4hF}^Rppq#f zX^J8yRwb8s83qrA17d**b|6im5!^dNI4Erhr7DR~v;>7407!jB5YlYY0Xs-Uv7WY^ zk)h+FJu9_K$acz$-5XNMNt>dlvgAXr3Qpl zqyUsDLY0GkhQ;!4TV;f zStrZiK?9Z*B8(0b6KorAlR?WWT62YQd+1Dkwut`b;gue?7~=wb*cI9rgdp+ zZzTSrQA73+2l67FxC$CyLX+)47g{1E(G}E-rbQT!n}@ZdC1^Xchl&LF&>|nc<-=$| z{tb{Id?^t7SX03WzbXKIw4#TOQ9jQ7tR7F?U>_P)_i(=tkF%sho%9xi6lgk1l#?t zwU2_otbDDn(c1c85datg!y_XyFaeHF$Mk%8-e!|Nf2>2(znekleW^{;=A zpl&(wu%qK3A}1<#Y^otB0VJU!gunBCHPkY2aba)_A|dp#o13%)!iW+Ct`loCg9c=A z988l({WS9|9wBjm4(BU0LG4#ja7WnvPa*Bs#$Rn{S+VWq;1Crs^BBfh3^N1!QM^21 zdyb!*WBthilH_3E83** zKz|dL!)6}(8~j`D4>x%4_n9PNz}JI2Juo;s0q>x!fwAa&k%l*K(b5UvM*z%T%m%hE z3JrXysQ(`a@7?^JN5k}Zgvh8OF1h(5UO@O98QzDKLT+AE;OKiFG|Y^QjI)Qk)oiu_ zgA6i9qx7B<2zM%t{#!wzF2l?W#{~8fs(0U-BW!fTJiYdo{aeaDa_F{w_$SHCH;r+< z^rfNfM;*B#$y|8xLg1HsSzW#ZlmRd=GXT~-@EmYuwX2?%N%a$e!dfLLI2&IK%#}Ys zrig8bW;w)Z#CH6|6@jKIqnKC-To6#{C_G|0u!pFPbFs3WLGv^{8s1%By^J%Qxv|2- z>F?m{!fF=RejkT;RR~c4frBD2U>E}e7MtFhGz23NNYWRRTWg27^}+fz7h{Uj6a)~E zMO6_HQ8al{hi3?Yz%YB5m*foRMqWV!-CMs-pC0c9sTA2-kQnh7hK4*C^Tt{E-Zv&x zVvXYDPLSCG4jGW-%ElL>G$>ETWw#|WGL=KBL1DghTI|GnhzcE^*Ko1x+0a7?^} z@Dl@r0DJSDZ+Z}YuhIniQ2ZmEVhaeOFRS9*IAE_pI$n8V@9OFuQYjW3+ZHIGon<|z zb4Q2o8HxP03x9~&K2g*6r{@(ZTu9(>c!G$72C0xhBZ6@dh=@Sh&&cL+M$xbO{JeYc z+VyuhT7nb#wL^NV-)r6jlyDT|G*Jl#KpkFA&Ac8Cwrh3f`-8#^{_o8nTZnw_1_&@V zD$3t$kK6qzWLcrnVl;7`9qtm`)c2FzuW9}va5!KH>y8*v4iGG01I7Z$Fu4yuCd`cN z<2+VC1LdrbzYuDkF>hK;v%J0(GLgc7U>+j;PJC%R949$if^h&XsoDb;FB)Mjz@wF+ z)oc(Z&b-m!yit!ws(BDlq}wCI=RWFAROE6k5p@hi%UoyO>me6p<(xJfa?N89Yo=9=|Gz z#6?z&bYEkQ8l#cJ@;1?y?%{%tYY#Zc4Dq=xrkdqu@qU+=lmiGxW5(q4L&bbqqZr5@ z-|6LSV?uy5ng40E-Xs583gI#Mk}EyLi~Oqv6qaH|HHjd>p8{eFsR0F2=byyHSLIk7 zJNY{qc{d+}_!p1B&+)Eij1b(Jtr+8N(Yi3aylOFm{zG2wEFn9|D0d@q-r>{Avo8>g zTmXcE?|LOzw&r5gyk%gWeqTN>ViOYTmj4ENIGxq~ZtHNkT{s4;|kyl_08BV;u?xma|LVR^n)j2Pvm(+vv- z0iAi;n1owu0|(N|7OCfSqhRZ4O|T(>{m7&-w6AQ4x>ziBlpk}LwGB^?RXW~DI`3T^|T#UlL2ZUha1@7X()1{=)(cymIdRcM${3MM1Up_{M zR};UBs4wV~4AZC6tzw>~07BN*X?dczcXsk77F2z}3$o4Cf4H1MJ*JPW5I?8;7F$6rY}vNxbQ?FMG-!q}q!K1WZHFNW-K!Ol$D6 zG!^J(`dOE+L*#NDib8mr9Lo>oAXLE!?8LvKIC%y2`T3*BrA3|WPPSfyEemud7ZTLr z!OMZ4X7qjtZeqjV$U)ix9~faI3YiQ*2i+|nJKy>R-4ewB9=PX(xkQY&6QSw(J#)*a z=Cko!PVcci;|`RpMhW`-=;6ywFcMimBP0$hKi!1c1hHG}v^u!erPYRmLS8I>m||qu zg1YK($N>?@mSu(9b(NTGWur$5`Gz4Dv4qy>QQLxyF(#Gd-eosp}M|#vG=>xCM%7@~OOJ%O#3i|&1b8onq zo_>Z#fv?-?|C4c~I8m(t}$y zIQp3qFC!Vkijl=vuW`R=(Ews!-_PNVj4KkiJpn5Sc=KoM<5u^wmKc|)D&oonAS#8n z4j;X+!4UmV#BBW^udQ!?x!+B8+$tyT?N&d+Y9fj*`Lh*LWAJp=3FE)J0VEU*V&tT2 z-S;Z^_qI2S_%0%b2Q8c^)|QYTyN|mo;;EZHs{A|7YUCs>+M5syat4I{Jj{G3gT3Ut z+~3oBQ*%PrDtQWvCmyUhR`yDQ1WI66T5=S$6X=>k;RST(#?z4C(R4tA88vv-GpCGs zlq_a-wQN_dXX3o?y8WMX@BMB`%;JR|nOrpFoDN27(%@u5K#W3*?yVFB4GBd=R8s^g zOh^aLH8e;AJ!S$(HVq~!(ub$NuYM7WE{O2?5#sN&AZA^&a?)@iAVSWxFdpBuLFCN6 zFE0XYA(aj6j)XDYu7$Cogc&*3ZMmZ|To{G$M;56cJ3McGJ;FTQLE6 z{K|EQ9y}i-vu?gcSzW39ZKY%ftg)bLVsnL7^BUOWK>6H=tM%h-+Tqix0i9l|LJtwo zN*XpDrwl1dspj^fakiSH0^o=*bLwvdg#b`cG$ji_Qk0aSP|`Gk6;KqaG$<5N7%&C` z!WpH27$ljX#D2aaNkG?Ye{(}x_RX+YdbT4-oli~#i#8bGYPAk^?(NL%Bt_HIVnyVt z{eu}IVl<(ZTQ!AmjfkHya$ZNb4c|mf%kKJ=cs?#n&F)t#pEf*b+80eD5Y_{LmDHt@ z5}C+BnS`JBC0|j>X<3iGv4J$p0+j-&us~LCAxa#B+-VD)Qwc`bru6Z1 z2PLbWJT?crjXq?(yP3Jq6sd@XMv?B`3a8-PGtKhu=aNMCkH4$6;R~m*}bO_ z3@H4(KL&i)$MQ%*-9^)R-F_|{c56`)3K^N1nGuaNRH>7|sV&?L9-bBDnK!LShX`h6 zEmZ4H-ST`CwQ```&G`>W#EDFfDU?)pl9Lg4y)H96S6A)Gy5(0$g?G^5(Q_AF#-#qe z8F$OIS_{%l%mdHtsuptTxxK~IN`GQST98&pks-)WEpjxjy$=L#`)J2NaME%Tlh%h~ z5ZS3KkiG0aR{?C=uj`Z_G}0nx1Yp5()bA80+i(WL0OFq|Ax|R;{yXxvI5OFN#17wc zi|DV&TS)IOp|~Smcp%7fdMhoSutoHmlqFwA*mDKUSRn%W3ubH5z~_M0|yFEI7sOaQ=eL_Q9VM2K0Nrap{Qc_uFD;rrmx@Ylxw zAFY0^pB1yu)983JHLmf+RCz64jLH+fzvcKK!^C86zdD>m)Lr`WHYJUn!f-g?1|rSbO{`3=TO_f`Wr}VuLKJq9TKy}rbphiFb1YHt#h}{&U{}eBlgO8l z(1IH)66AGhF4ab!Y=H%&_*>grBI;yjVd4CV(%PCo(nNke;A zL14B&(n>=JKnOBHkpy5LMRs-Ep}t0Hwfc__1oIy{cQt}~5W2=7WM)U;T^vmTx=$rr zx|WwjI<}aHT?bzZdD;6`6Z0`)U^eR3N{C8nO!2hQDjq)f zx3C(}e4-}zZ;R5XpS3^Nuk$Sa7Wq@v7^OfR5R!@ND>4Njun!wf|M6)ykkj|fzIJu} zoraYTAJ22T_qfP^o#OOBU&8ae2@e+jRV79YS@>vuXCCgkf%?c51yupp*Zcg}>_5V@ z%dOi4NA=otKwt zjgB}I8EO~}Iz|aj2`-{IGUAyS{>~p~8qYX!aw`oVkfoVfrev| z<09D)k`2P?+PLQCTohLygBp;FTb-02Lrl>vt zZ0(uOAy9X-Bza2_0{F2lUk&pzQ-*KiyIfxwDC8RvHcVHJaI}EqedA)?P2Pl9d zDqyYrOW~nkpa#b)P>68`65jg(rPa4A`#rZZrw$^g%kH^{tSEil5Eo|~-TmR<34BF< z7rTln-jV3NgzcWd4?p4Yz0Wxm$+h8Pe&?&;^DxO>Q2^^7Qhf9P&tpe3xdX+4K0BaA zPSivJhJ89qM34wT1F`pB?mki4`zhM(>+|{EEg*Y;edWjDak?DlIOD>8-#>`Y;&*5G zq4s9>;k-A$A>2OS3xBiHz=p_l`%HvRIc|A~PmH<>TIea>U&}7%dBx)XEYyz7w-WKN z*8jhP!vDh+7|?Eqj$CRU7G>k-ukxm&K{xyl6KkK&S($1P(vA0%Q}9gbFO+bqB9YDO zdk+69&lEY;f{zA#O=@xt{>xONhz##EGY7h#uh*$-SUsqMO71|ldTHO2*32^XVAX}T z0wC*AY*^J&sxt#g6fq4}iU48)k_!mM14;o%G>8ofBub?yP=z2NNQ%V}6ond*Fp@z) zK|l!v6t%JRym)v2hufZeiTMcSefSc8b>sg!@mPT(BcDRFl?uyYVe46TfXr$%V= z8UUdvB3eYIkZ4K}prMEwaal4AX)x4KBSRFlH335mL`0E*bNG9X9vmbDo=M1E3SF8O zS6=CiGYrQV-Zm~0{Ft#!%!|DVYN)@Gnq7Lnwj&nk!BeZA|xHC(W?$o=%+=I8J`C_E{X^fW_V@ZKtH zm(pDWCdN}m{4Vbu-#eH{iwJ~?AT-cYkj+I?PzxngM39mrK9|SPd`ldBEgAG?2D0M; zR2YRcfk2x;VKfFwgfu8psK5mvp`@rNEDAb0-71_RSjEpFWx-VzBCHq4+4&xyU)AOG zdw$c;^gaD2z(iK4vl$3Fy*i`notKlw& zb|gr%2JibdD{EnXQ?Fm4AJp`&fAco5b-r(3nZVuR@cDtng9yk%W}=DBg-$)2OH>6i z%A*xjV?N{bJ-j}>A8YvcU)A?sLUubqV5(vUf`*z(N=h24swt8JCL(~On53bq7?`M_ zX_#V~Nur_{iJ3)Wq95?Q{JZ{tcRpj$0=CziQBSlQ#I%))R5XFvC3J`XZrwdUdF{^! z)%sVDwEKU?*1v7)T-Om=V93FlFT1{-QPeT?mg}0**Mm1Jhuv$pnNK}DhW>J-kJi0i zLR6~^=E*5UW_BjDG|%H^%E0*xN1MF&kgVgoQ~ed;R*7;l04b)jTq3B7iY6jcGYANb zj2IwhK(qQj4^Neqp6oa#&8YPZ%#1N)bSnt*KfJ0j)FU z+Lf0=T>#4?`Vs}1vyL*jcj$nGAz=L_kd#-kT6FgOcptCN)t01fhpc&W*hqk5 zA+5EY$)!O-mY)Q~*GwH87n$nzoc5V!R%5!?=6}{RZ3W_a+V3WD1a%;)RUa0_m(t%E<#MTt&$S zY{qdS*7FM~OC8@uFfHL6SPe{)G;BtHcw(*^H>;qnNt`ftl0UIau`#H$QobDrY(-Wh z@sRVoa0e%Lvhs+?%_itD3uOTPN?u*81ie9rK27e+vSFN{gn&$#BL)vomZ`OPH52o@ zE#1+IJ63iWpI65iF8{%lBJS^~q2ZpdH2ppIf$x~^Z(w>MtlTZA4Eh|jNnZ5u-4b%u zXR)Wt{?3lu4jhZw*mEX61f&3iG6aJn1d74{cf#*&ce~w=j~{$yVF@G&bpY!7-j>I4 z@)q?vS(qX9f!wVMHHz2@DHCm8yChDIU>Sh6l!#Be#cXZ2QHA{ewrSN3ukA;B8_DKI zWI)a1_HCkJ$A<3cQIY;XTm33*6(p>&Bx^N{l&F? zpaf4qZY1R11fs9XU^EOwWp)@wS>;2j%@YzVWq-0e-ufN&jRdK@Qsm7P{J4E+52+8M zAIP7D52Zdteec=%)-y-%VfPrQPLJ5*`)KNu)RW{&gWZGcSxu!9lh4fXLH#HqCOE1G zLI4ZkZ*Q53>}B1oT)X_PO=HKUlWnz!4ZQFE_5|w|-b#Xr#sx(_JfeP%L;&@nRW_9O zG64E=BD#eU)~Wy#!-@&PiXr-c&HMkg_BcLe-Alf+fO>pNB7L5 zw-4m17;Jb4WiZ9y((lbAl)`+p9nih%@aMF>1M~cNdrFe)ks^hM;)H<$C2tE8yx*69 zf_kTS?CX{fbhx( z@?lm#tMTC`AB{l2r2e0O(|7o$>5bF-uDjl(?@xOw{%yOrx$^y=9jXZW?8-tCf@Vkv z^KN)HV|gsb!Mo~O$ecKgoU>SM*F100Wk;@ZzhK!xM#ru}Fm^x!b!Xj5kDfWT$}bby=S z#|dai#0^QMl@@psVL;Y2;NzKPP55SB@KH^-T4gwK)xh6+mI9oL+E{Oy$MV-;F&}v0 zw#-4@YPX{Me2)^3sj|xGc5En4MMUyXN6vd`S@zoiVHr;|UXKqy1U%kZ(5zFew zHs?DBkJ6(TQQpWmWU3;_IvrGB*fj30k%A(qQr!Ib;b&ak10u4q({GG%Q z9lexjnE56a+$Pf5q{z=51aK=GipbK`rAu50mSt!@w+boqT|cxa;r zJHlTYe7{`W_j&U6Nroym>fuM`= z`Z@X!%(p4EELK(V;QmIRXp>WYx8G)l_<@L?2@81>npmUSK`@^V>KJ)*!}8mg6dp;d zZz}K9_0L}44|9)bWYUvFZnY1=p85mmz%Pv2!;vf(b?S+KT>!gE8{|&y>Q2_g8moEm zS$Avlw|c&&4dlbSrq3(1SlEc+=@o>LwC^;_u+zZOxiLQE`5OivaS8u%l%MH^>*lYX zjr>jBr}k8WfltbSqcYk;qa~33$|g*D(yA| z(OVM&ci)0luBz0J6rA#0>wDTWGmre;T{v*NM&{3_f0yX`TTR5fAGf&n@Gyve$BkIy zAe*tNl4<1A$>ZOpJLUAgTAcJTSchNjaM8X_Ts9$JSO@C%%{PdS7aoMJr+i6lo{ zTJSAHj)1x&B@0{FH^+TkuBsWliPgIALYteG|>59S9xbJ(q4piF1 zz%1)ehr{~VT|7t)9~wjY#F)>zLcaI!GK=E0(n5ae4Q)Sh`%9<^tF=NW$hM0m=FBV3 z@(eEcFw4lr?j-Eqw!6r+pse;WPpA2Rj{P1R<`2^Hvm`+gf0|>TLlx>9b zc(us--bXVT`WdrhZX+0JRY_=l+}U-+EHuIj3EBpu1zf>7m!F5v(&#nH&+apN#!jmo z<)^f}m^soi#4dc}aTj#ii=PQBTEJk=`>UwFfKN%MndP$8ARrlwrul+|`pOthr0%+Z ztH}|`j5$X#t&Xo0x7%9QYu>1mFm$}zD=xVpfvyc6#=5Cy8hv{vEl?n*vuz-4I z5#KgaPbI#Z9WbyZLe@JE-%(!rYp&xdK#n_Cx z;1+;`vYf7C8O9a;M$VH9Z0RCJiF&Yqwe}zy`kC9vFjvNMbOjOyLWYV#AZG*x(hx?3 zUtXYn(^&npKr$X|HdG6jdv!`T0t!Z}X@_fx5n{~CcLw$XRJmh0`>BWB-dJaWJG|Fp zxjmTMtw~O&(XPGy#k?#<rR|Qo<*lY+X_okQbtEqAQ_hi3VY#x z$#73^?$~<%v?Bmdvdj3I#^>ZC-zogV2i&tVpGpF2_O8C~E34Y8&A!&5W%J?=NM!E_ zSH`BH==W7(_+Kp_D4Fd$Qy0l9`>9P?`_J9eLTVGQ=^sZ7 z``URj`Lie?yNa70s`IjaNO$(~0uX}+d-ELrW=o#8T^YO`_{SLwMvZc63O$@<*XH1) znD8=$=OBYgfMF(ol7X^$6tDv9tb-s7^3Z}mD?7=t5HF50(VL%;n}?rz{x!42Uz$7% zf=?9)BkH12$Z0DodLY(PNXtP8E-)|z6ryr6@Jkx9J6M-YwT*ohpMkHN|6DZzDEbF2 z$g*V7kMpK!HlcNfi6%Of0xKQc3#RbN7lwmT%1A&JBetR!y66FTW;V|6zOrcuZ6UL@ zHTQP;yb=dS6*c>7>u}X5q4q6Lxw4+osLvZifI4LSANY53%lP@pqd*7-1=!P+IETnx z0Nq6H-PC#d)Vdpb#zB)H3k1iMego_qn}8#0?Q*SXa`xM5EHcf;!~9v_hfB_9VtB7g z{L|h(nS_~`Sis_$Y8X(IVlEOCK<_b)1-?EmAC#ClO$^)od_R}SjqJG41~`zCokpJL z>8jpgD>U(T5Tis5KPAiA@iaR2!1FG3_ufc<$}oYFmJ*QfAER$48>G#>&(-z6JLzL? z>SKRyPg!sxW#hqwey0HiD24%^H04NLI+D2sKE3>VE$?5I_ZMG%B1k4W#cJ-Nl>in7 zbic6CKHI}wXzoldT>nLW=`cn=3;P_*zvgZ%u{pMCbBTvqPb${FN-7cClxRAZu?z6< zx~FmMPPNa|>Dc<4{hwOozJKGr0w|(!Ohz)2pG8ZUv|)cr!bo58PzhnpKtXCc5}i7= zRc;bIqXR{op)PaKN*gBHOyC(u21*YgkS!q{9262|($LqpshvS-wbOcLX5KRb^Obp9jC^XljBKJ3R)=-7x2W>02n9DZrcU?A3OaXtNe%h8B?F^ zzS~(lvp4xE^?&kc&QO40%n13ZNSmmAn#Q$!N_x!NU9;Arkd&GM+%PH_5R8?w#qxp- zl@Sv(eg_maSj)&#Oo6>QATVGkd^|)X%*o*TQeei6vxts&7mDm2)fuO=5%<}r%2ab$ z&vJL1srFX^Ab1-!2(^%zkOhPyxP#Ja|S$sh+S4D$+<%(?nwn8P@4>XWiBCczDejepQc8-Q8*BK&~Xa zE!G5)cHvbBO<1e1s}Y}JKOgDn&!yjgKUcB%Zb_u>-k@bhv>EqE@Seli`qDIgF4}mf zOGfJMh$c2nX3IfxjD%1ppb{;Zopx6%(0P(307eg@pcq|l8w(5r3P=d?QA<8+H(0aV zqK-{k0e31I2Y=M_{Hs!1e|P6ASNU?JNfQ7#Fo@=a4wB`6UH^2{c#iw~$Q*~EwXUgsM7 z_F9Hx4YX-^$Vl$zOQA0sH=4|AE&!Fy1`NpeY(2wQB9;xeWQgRDBM&xYeF^1_U7u%BgK# zBIm^1m4PE!fi>npXGAVY(4kpmA5r}8sQ%B_=>1piK2uHH!AI+>2Tsb$kZ3x&;S*}C z5)OpqxAm5;po0cOB*+p75mn1Y+3O^jFzQw3Dn9!Sb$N(K2?Q%7Hk)zXSk#5gkT^Ym z!O^c<_N>7q=+Fo8()<>ppCh@Sq=hr?f5ZLPs;Gt-&OX9xnnh_<3QCrvx|kd#npaAa zlF9=K2nJ+WhKQ&@L6MmPBp>s`NK^lv#b78Hdp>WSndxpjHtI8jfd+jX^e3>>%iZv6 zU#Iz$!**5T!1#TX(F6CqP~lTg^(py3x!lBKl*=2jZ&P7m5}K2h=!X>HtY!g<=0p^b zlcLFfQ_$8QsP%pSmHW+exAaWI)kD>n@90W>x(ny%Yaj4Zr+@MAG3Dm*Sz(afzGYQ@ ziLiFe0D>T9V9dax5F+FFApE8tO9}lRt=_7jKbXwG!$vQ{}?`yFfyJ zH7Va{y19~5>HjIYKM93`!JQs5iT%fg{v5eud_tO`<#I5-=w*XKLPAgCMvo({W6i(%!xs(>qnuZe`DcKbsPbZxu7S;_cd%|^L?tN&f&;!1`h3$vgcK%^&x#`f@TZ@ zGBV7BJBpv)@1p%Or#4h#N#UXvK|*+VnTijEX@|7hyzMY7|GA|_SpdU4$qLJT zWUdB}$wl{3Vo3$_5E4ct56FAgTG6byeB!A9oBVY9e>cub=4D9O&O(nQ^JS?fV8M#W zLrB#~ALIXR#hE&E)_Gq-vdcX_|GDT~-^=LO%XLX5RRwk@2lVd#ZU|mq-ztM6)!J3L zJta@`7*4^HjmE_Jmu&lftLS&fl~9i|d+SD7yA=?^=C%b7%=8#3%CCWw;BCwt_+3>j zVbmD(veXj^WCbJavFPE35ya)E;B=|CI)X(HN)C9V;zQxZHhtIY+tA(q)W!EY@s_m? zFg-)@JHn&qx4)+%J=;Uj>!o^}pWMps2E=g0Z3!Q!akaJz#`g9$k>VN1_a}VcVmfujO|qf%wGS*7%GHkD$fX%kYTkPb>20ZKFhNPB2Zl7f-t zg9sRaqK1&9YiJP2X%L_y6`-N22B|`nL)d_Dfm&imBrs$LQW#cPO_^3WB`qr3Mvbzk z!p(a13T$Z{>}YUAp=NELT!z(ss3q#`cS5$Xhw#SGYkqsfeKU_7K0%& z$;dsls2Y(@xKm~{OAr|a!u}S8&J#&R9+$%JXT*Z@udOcgeB(pfW(HgCJeGcM}}<<^b*`dNcR38W@4yDpgt~y}{yQP5eNK*n)Y8y2TUZ7`kPR_XQk0O; z*@Br;5gD1DzoY2uwhwbI@+5Dsa9sB1FZVjxQY9Iit(ph`?xLZ~yPdlHd`{@202o35 zD3h6A0e+xhB^wu9c8x@=!|Gn)@S`-Pb zK&BL)G}%dNGg{4Y@al4Dsv9kCHTniRT(~a9{B@Nc%m?{QpQW6pPDi6&ryD|bzsP1k zMG+vW^c->7N5+0ryO02?TS9F zf46bC$o*M&d!BF3`S+cT`!^oQ(xF)FNyW$jDi4Ao=`@BlvYGX3}bfKJO>c&up?4=&4;Eo%$qGvgBvOIpCQ*qwbmtxx$4kNNYOWy@?C2tf zH^80y@wJprNk#j96)HZS5W`c8quS8{|NQygckFb-&i3~P zpUm`EC)kPnRwj<_&qpxuyNNe^7qnJ55x;qzS}KK`1*7jBU6>SuD}{NS-=O{so^oJ1 zk`?WrKI@CFhn11M&{z0aFL%|OWBpB$z*08n{hdB{NKDT8miI7yWA#ILb%bYkPT1YD z2>f4{t-X7yw{bt)p5N;9w0mdWuksdEs*Z#>9teLKgO`rGXCO|_M7dvnZVf`wHvdTH zN1`H2pfjt$|4vwXq3?&^4^5_c%0DUCS6yUV%C+ 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

    9ejY2o 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

    : [p Pokémon Red and Blue has a fully functional map tracker that supports auto-tracking. -1. Download [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest) and [PopTracker](https://github.com/black-sliver/PopTracker/releases). +1. Download [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/coveleski/rb_tracker/releases/latest) and [PopTracker](https://github.com/black-sliver/PopTracker/releases). 2. Open PopTracker, and load the Pokémon Red and Blue pack. 3. Click on the "AP" symbol at the top. 4. Enter the AP address, slot name and password. -The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It +The rest should take care of itself! Items and checks will be marked automatically, and it even knows your options - It will hide checks & adjust logic accordingly. diff --git a/worlds/pokemon_rb/docs/setup_es.md b/worlds/pokemon_rb/docs/setup_es.md index a6a6aa6ce793..6499c9501263 100644 --- a/worlds/pokemon_rb/docs/setup_es.md +++ b/worlds/pokemon_rb/docs/setup_es.md @@ -17,7 +17,7 @@ Al usar BizHawk, esta guía solo es aplicable en los sistemas de Windows y Linux ## Software Opcional -- [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/j-imbo/pkmnrb_jim/releases/latest), para usar con [PopTracker](https://github.com/black-sliver/PopTracker/releases) +- [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/coveleski/rb_tracker/releases/latest), para usar con [PopTracker](https://github.com/black-sliver/PopTracker/releases) ## Configurando BizHawk @@ -51,7 +51,7 @@ opciones. ### ¿Dónde puedo obtener un archivo YAML? -Puedes generar un archivo YAML or descargar su plantilla en la [página de configuración de jugador de Pokémon Red and Blue](/games/Pokemon%20Red%20and%20Blue/player-settings) +Puedes generar un archivo YAML or descargar su plantilla en la [página de configuración de jugador de Pokémon Red and Blue](/games/Pokemon%20Red%20and%20Blue/player-options) Es importante tener en cuenta que la opción `game_version` determina el ROM que será parcheado. Tanto el jugador como la persona que genera (si está generando localmente) necesitarán el archivo del ROM @@ -101,7 +101,7 @@ Ahora ya estás listo para tu aventura en Kanto. Pokémon Red and Blue tiene un mapa completamente funcional que soporta seguimiento automático. -1. Descarga el [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/j-imbo/pkmnrb_jim/releases/latest) y [PopTracker](https://github.com/black-sliver/PopTracker/releases). +1. Descarga el [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/coveleski/rb_tracker/releases/latest) y [PopTracker](https://github.com/black-sliver/PopTracker/releases). 2. Abre PopTracker, y carga el pack de Pokémon Red and Blue. 3. Haz clic en el símbolo "AP" en la parte superior. 4. Ingresa la dirección de AP, nombre del slot y contraseña (si es que hay). diff --git a/worlds/pokemon_rb/encounters.py b/worlds/pokemon_rb/encounters.py index a426374c2e6e..6d1762b0ca71 100644 --- a/worlds/pokemon_rb/encounters.py +++ b/worlds/pokemon_rb/encounters.py @@ -197,7 +197,6 @@ def process_pokemon_locations(self): mon = randomize_pokemon(self, original_mon, mons_list, 2, self.multiworld.random) placed_mons[mon] += 1 location.item = self.create_item(mon) - location.event = True location.locked = True location.item.location = location locations.append(location) @@ -269,7 +268,6 @@ def process_pokemon_locations(self): for slot in encounter_slots: location = self.multiworld.get_location(slot.name, self.player) location.item = self.create_item(slot.original_item) - location.event = True location.locked = True location.item.location = location placed_mons[location.item.name] += 1 \ No newline at end of file diff --git a/worlds/pokemon_rb/items.py b/worlds/pokemon_rb/items.py index 24cad13252b1..de29f341c6df 100644 --- a/worlds/pokemon_rb/items.py +++ b/worlds/pokemon_rb/items.py @@ -119,11 +119,11 @@ def __init__(self, item_id, classification, groups): "Card Key 11F": ItemData(109, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]), "Progressive Card Key": ItemData(110, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]), "Sleep Trap": ItemData(111, ItemClassification.trap, ["Traps"]), - "HM01 Cut": ItemData(196, ItemClassification.progression, ["Unique", "HMs", "Key Items"]), - "HM02 Fly": ItemData(197, ItemClassification.progression, ["Unique", "HMs", "Key Items"]), - "HM03 Surf": ItemData(198, ItemClassification.progression, ["Unique", "HMs", "Key Items"]), - "HM04 Strength": ItemData(199, ItemClassification.progression, ["Unique", "HMs", "Key Items"]), - "HM05 Flash": ItemData(200, ItemClassification.progression, ["Unique", "HMs", "Key Items"]), + "HM01 Cut": ItemData(196, ItemClassification.progression, ["Unique", "HMs", "HM01", "Key Items"]), + "HM02 Fly": ItemData(197, ItemClassification.progression, ["Unique", "HMs", "HM02", "Key Items"]), + "HM03 Surf": ItemData(198, ItemClassification.progression, ["Unique", "HMs", "HM03", "Key Items"]), + "HM04 Strength": ItemData(199, ItemClassification.progression, ["Unique", "HMs", "HM04", "Key Items"]), + "HM05 Flash": ItemData(200, ItemClassification.progression, ["Unique", "HMs", "HM05", "Key Items"]), "TM01 Mega Punch": ItemData(201, ItemClassification.useful, ["Unique", "TMs"]), "TM02 Razor Wind": ItemData(202, ItemClassification.filler, ["Unique", "TMs"]), "TM03 Swords Dance": ItemData(203, ItemClassification.useful, ["Unique", "TMs"]), diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index abaa58fcf901..6aee25df2637 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -175,7 +175,7 @@ def __init__(self, flag): LocationData("Route 2-SE", "South Item", "Moon Stone", rom_addresses["Missable_Route_2_Item_1"], Missable(25)), LocationData("Route 2-SE", "North Item", "HP Up", rom_addresses["Missable_Route_2_Item_2"], Missable(26)), - LocationData("Route 4-E", "Item", "TM04 Whirlwind", rom_addresses["Missable_Route_4_Item"], Missable(27)), + LocationData("Route 4-C", "Item", "TM04 Whirlwind", rom_addresses["Missable_Route_4_Item"], Missable(27)), LocationData("Route 9", "Item", "TM30 Teleport", rom_addresses["Missable_Route_9_Item"], Missable(28)), LocationData("Route 12-N", "Island Item", "TM16 Pay Day", rom_addresses["Missable_Route_12_Item_1"], Missable(30)), LocationData("Route 12-Grass", "Item Behind Cuttable Tree", "Iron", rom_addresses["Missable_Route_12_Item_2"], Missable(31)), @@ -427,7 +427,7 @@ def __init__(self, flag): LocationData("Seafoam Islands B3F", "Hidden Item Rock", "Max Elixir", rom_addresses['Hidden_Item_Seafoam_Islands_B3F'], Hidden(50), inclusion=hidden_items), LocationData("Vermilion City", "Hidden Item In Water Near Fan Club", "Max Ether", rom_addresses['Hidden_Item_Vermilion_City'], Hidden(51), inclusion=hidden_items), LocationData("Cerulean City-Badge House Backyard", "Hidden Item Gym Badge Guy's Backyard", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_City'], Hidden(52), inclusion=hidden_items), - LocationData("Route 4-E", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items), + LocationData("Route 4-C", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items), LocationData("Oak's Lab", "Oak's Parcel Reward", "Pokedex", rom_addresses["Event_Pokedex"], EventFlag(0x38)), @@ -636,7 +636,7 @@ def __init__(self, flag): LocationData("Rock Tunnel B1F-W", "PokeManiac 3", None, rom_addresses["Trainersanity_EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_2_ITEM"], EventFlag(11), inclusion=trainersanity), LocationData("Route 10-N", "Jr. Trainer F 1", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_3_ITEM"], EventFlag(308), inclusion=trainersanity), LocationData("Route 10-C", "PokeManiac 1", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_0_ITEM"], EventFlag(311), inclusion=trainersanity), - LocationData("Route 10-S", "J.r Trainer F 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_5_ITEM"], EventFlag(306), inclusion=trainersanity), + LocationData("Route 10-S", "Jr. Trainer F 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_5_ITEM"], EventFlag(306), inclusion=trainersanity), LocationData("Route 10-S", "Hiker 1", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_1_ITEM"], EventFlag(310), inclusion=trainersanity), LocationData("Route 10-S", "Hiker 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_4_ITEM"], EventFlag(307), inclusion=trainersanity), LocationData("Route 10-S", "PokeManiac 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_2_ITEM"], EventFlag(309), inclusion=trainersanity), diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index bd6515913aca..54d486a6cf9f 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -1,4 +1,4 @@ -from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink +from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink, ItemsAccessibility class GameVersion(Choice): @@ -287,7 +287,7 @@ class AllPokemonSeen(Toggle): class DexSanity(NamedRange): """Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to - have checks added. If Accessibility is set to locations, this will be the percentage of all logically reachable + have checks added. If Accessibility is set to full, this will be the percentage of all logically reachable Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage of all 151 Pokemon. If Pokedex is required, the items for Pokemon acquired before acquiring the Pokedex can be found by talking to @@ -861,6 +861,7 @@ class RandomizePokemonPalettes(Choice): pokemon_rb_options = { + "accessibility": ItemsAccessibility, "game_version": GameVersion, "trainer_name": TrainerName, "rival_name": RivalName, diff --git a/worlds/pokemon_rb/pokemon.py b/worlds/pokemon_rb/pokemon.py index 267f424ca89a..28098a2c53fe 100644 --- a/worlds/pokemon_rb/pokemon.py +++ b/worlds/pokemon_rb/pokemon.py @@ -1,5 +1,5 @@ from copy import deepcopy -from . import poke_data +from . import poke_data, logic from .rom_addresses import rom_addresses @@ -135,7 +135,6 @@ def process_pokemon_data(self): learnsets = deepcopy(poke_data.learnsets) tms_hms = self.local_tms + poke_data.hm_moves - compat_hms = set() for mon, mon_data in local_poke_data.items(): @@ -323,19 +322,20 @@ def roll_tm_compat(roll_move): mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8)) hm_verify = ["Surf", "Strength"] - if self.multiworld.accessibility[self.player] == "locations" or ((not + if self.multiworld.accessibility[self.player] != "minimal" or ((not self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_badges_condition[self.player], self.multiworld.route_22_gate_condition[self.player], self.multiworld.victory_road_condition[self.player]) > 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")): hm_verify += ["Cut"] - if self.multiworld.accessibility[self.player] == "locations" or (not + if self.multiworld.accessibility[self.player] != "minimal" or (not self.multiworld.dark_rock_tunnel_logic[self.player]) and ((self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player]) or self.multiworld.door_shuffle[self.player]): hm_verify += ["Flash"] - # Fly does not need to be verified. Full/Insanity door shuffle connects reachable regions to unreachable regions, - # so if Fly is available and can be learned, the towns you can fly to would be reachable, but if no Pokémon can - # learn it this simply would not occur + # Fly does not need to be verified. Full/Insanity/Decoupled door shuffle connects reachable regions to unreachable + # regions, so if Fly is available and can be learned, the towns you can fly to would be considered reachable for + # door shuffle purposes, but if no Pokémon can learn it, that connection would just be out of logic and it would + # ensure connections to those towns. for hm_move in hm_verify: if hm_move not in compat_hms: @@ -346,3 +346,90 @@ def roll_tm_compat(roll_move): self.local_poke_data = local_poke_data self.learnsets = learnsets + + +def verify_hm_moves(multiworld, world, player): + def intervene(move, test_state): + move_bit = pow(2, poke_data.hm_moves.index(move) + 2) + viable_mons = [mon for mon in world.local_poke_data if world.local_poke_data[mon]["tms"][6] & move_bit] + if multiworld.randomize_wild_pokemon[player] and viable_mons: + accessible_slots = [loc for loc in multiworld.get_reachable_locations(test_state, player) if + loc.type == "Wild Encounter"] + + def number_of_zones(mon): + zones = set() + for loc in [slot for slot in accessible_slots if slot.item.name == mon]: + zones.add(loc.name.split(" - ")[0]) + return len(zones) + + placed_mons = [slot.item.name for slot in accessible_slots] + + if multiworld.area_1_to_1_mapping[player]: + placed_mons.sort(key=lambda i: number_of_zones(i)) + else: + # this sort method doesn't work if you reference the same list being sorted in the lambda + placed_mons_copy = placed_mons.copy() + placed_mons.sort(key=lambda i: placed_mons_copy.count(i)) + + placed_mon = placed_mons.pop() + replace_mon = multiworld.random.choice(viable_mons) + replace_slot = multiworld.random.choice([slot for slot in accessible_slots if slot.item.name + == placed_mon]) + if multiworld.area_1_to_1_mapping[player]: + zone = " - ".join(replace_slot.name.split(" - ")[:-1]) + replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name + == placed_mon] + for replace_slot in replace_slots: + replace_slot.item = world.create_item(replace_mon) + else: + replace_slot.item = world.create_item(replace_mon) + else: + tms_hms = world.local_tms + poke_data.hm_moves + flag = tms_hms.index(move) + mon_list = [mon for mon in poke_data.pokemon_data.keys() if test_state.has(mon, player)] + multiworld.random.shuffle(mon_list) + mon_list.sort(key=lambda mon: world.local_move_data[move]["type"] not in + [world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]]) + for mon in mon_list: + if test_state.has(mon, player): + world.local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8) + break + + last_intervene = None + while True: + intervene_move = None + test_state = multiworld.get_all_state(False) + if not logic.can_learn_hm(test_state, "Surf", player): + intervene_move = "Surf" + elif not logic.can_learn_hm(test_state, "Strength", player): + intervene_move = "Strength" + # cut may not be needed if accessibility is minimal, unless you need all 8 badges and badgesanity is off, + # as you will require cut to access celadon gyn + elif ((not logic.can_learn_hm(test_state, "Cut", player)) and + (multiworld.accessibility[player] != "minimal" or ((not + multiworld.badgesanity[player]) and max( + multiworld.elite_four_badges_condition[player], + multiworld.route_22_gate_condition[player], + multiworld.victory_road_condition[player]) + > 7) or (multiworld.door_shuffle[player] not in ("off", "simple")))): + intervene_move = "Cut" + elif ((not logic.can_learn_hm(test_state, "Flash", player)) + and multiworld.dark_rock_tunnel_logic[player] + and (multiworld.accessibility[player] != "minimal" + or multiworld.door_shuffle[player])): + intervene_move = "Flash" + # If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps + # as reachable, and if on no door shuffle or simple, fly is simply never necessary. + # We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been + # considered in door shuffle. + elif ((not logic.can_learn_hm(test_state, "Fly", player)) + and multiworld.door_shuffle[player] not in + ("off", "simple") and [world.fly_map, world.town_map_fly_map] != ["Pallet Town", "Pallet Town"]): + intervene_move = "Fly" + if intervene_move: + if intervene_move == last_intervene: + raise Exception(f"Caught in infinite loop attempting to ensure {intervene_move} is available to player {player}") + intervene(intervene_move, test_state) + last_intervene = intervene_move + else: + break \ No newline at end of file diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index afeb301c9b94..a9206fe66753 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -1540,7 +1540,6 @@ def create_regions(self): item = self.create_filler() elif location.original_item == "Pokedex": if self.multiworld.randomize_pokedex[self.player] == "vanilla": - location_object.event = True event = True item = self.create_item("Pokedex") elif location.original_item == "Moon Stone" and self.multiworld.stonesanity[self.player]: @@ -1561,7 +1560,7 @@ def create_regions(self): <= self.multiworld.trap_percentage[self.player].value and combined_traps != 0): item = self.create_item(self.select_trap()) - if self.multiworld.key_items_only[self.player] and (not location.event) and (not item.advancement): + if self.multiworld.key_items_only[self.player] and (not location.event) and (not item.advancement) and location.original_item != "Exp. All": continue if item.name in start_inventory and start_inventory[item.name] > 0 and \ @@ -1948,7 +1947,7 @@ def create_regions(self): for entrance in reversed(region.exits): if isinstance(entrance, PokemonRBWarp): region.exits.remove(entrance) - multiworld.regions.entrance_cache[self.player] = cache + multiworld.regions.entrance_cache[self.player] = cache.copy() if badge_locs: for loc in badge_locs: loc.item = None diff --git a/worlds/pokemon_rb/rules.py b/worlds/pokemon_rb/rules.py index 21dceb75e8df..1d68f3148963 100644 --- a/worlds/pokemon_rb/rules.py +++ b/worlds/pokemon_rb/rules.py @@ -22,7 +22,7 @@ def prize_rule(i): item_rules["Celadon Prize Corner - Item Prize 2"] = prize_rule item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule - if multiworld.accessibility[player] != "locations": + if multiworld.accessibility[player] != "full": multiworld.get_location("Cerulean Bicycle Shop", player).always_allow = (lambda state, item: item.name == "Bike Voucher" and item.player == player) diff --git a/worlds/raft/Options.py b/worlds/raft/Options.py index b9d0a2298a07..efe460b50353 100644 --- a/worlds/raft/Options.py +++ b/worlds/raft/Options.py @@ -1,4 +1,5 @@ -from Options import Range, Toggle, DefaultOnToggle, Choice, DeathLink +from dataclasses import dataclass +from Options import Range, Toggle, DefaultOnToggle, Choice, DeathLink, PerGameCommonOptions class MinimumResourcePackAmount(Range): """The minimum amount of resources available in a resource pack""" @@ -32,7 +33,13 @@ class FillerItemTypes(Choice): option_both = 2 class IslandFrequencyLocations(Choice): - """Sets where frequencies for story islands are located.""" + """Sets where frequencies for story islands are located. + Vanilla will keep frequencies in their vanilla, non-randomized locations. + Random On Island will randomize each frequency within its vanilla island, but will preserve island order. + Random Island Order will change the order you visit islands, but will preserve the vanilla location of each frequency unlock. + Random On Island Random Order will randomize the location containing the frequency on each island and randomize the order. + Progressive will randomize the frequencies to anywhere, but will always unlock the frequencies in vanilla order as the frequency items are received. + Anywhere will randomize the frequencies to anywhere, and frequencies will be received in any order.""" display_name = "Frequency locations" option_vanilla = 0 option_random_on_island = 1 @@ -41,6 +48,8 @@ class IslandFrequencyLocations(Choice): option_progressive = 4 option_anywhere = 5 default = 2 + def is_filling_frequencies_in_world(self): + return self.value <= self.option_random_on_island_random_order class IslandGenerationDistance(Choice): """Sets how far away islands spawn from you when you input their coordinates into the Receiver.""" @@ -53,7 +62,8 @@ class IslandGenerationDistance(Choice): default = 8 class ExpensiveResearch(Toggle): - """Makes unlocking items in the Crafting Table consume the researched items.""" + """If No is selected, researching items and unlocking items in the Crafting Table works the same as vanilla Raft. + If Yes is selected, each unlock in the Crafting Table will require its own set of researched items in order to unlock it.""" display_name = "Expensive research" class ProgressiveItems(DefaultOnToggle): @@ -66,20 +76,19 @@ class BigIslandEarlyCrafting(Toggle): display_name = "Early recipes behind big islands" class PaddleboardMode(Toggle): - """Sets later story islands to in logic without an Engine or Steering Wheel. May require lots of paddling. Not - recommended.""" + """Sets later story islands to be in logic without an Engine or Steering Wheel. May require lots of paddling.""" display_name = "Paddleboard Mode" -raft_options = { - "minimum_resource_pack_amount": MinimumResourcePackAmount, - "maximum_resource_pack_amount": MaximumResourcePackAmount, - "duplicate_items": DuplicateItems, - "filler_item_types": FillerItemTypes, - "island_frequency_locations": IslandFrequencyLocations, - "island_generation_distance": IslandGenerationDistance, - "expensive_research": ExpensiveResearch, - "progressive_items": ProgressiveItems, - "big_island_early_crafting": BigIslandEarlyCrafting, - "paddleboard_mode": PaddleboardMode, - "death_link": DeathLink -} +@dataclass +class RaftOptions(PerGameCommonOptions): + minimum_resource_pack_amount: MinimumResourcePackAmount + maximum_resource_pack_amount: MaximumResourcePackAmount + duplicate_items: DuplicateItems + filler_item_types: FillerItemTypes + island_frequency_locations: IslandFrequencyLocations + island_generation_distance: IslandGenerationDistance + expensive_research: ExpensiveResearch + progressive_items: ProgressiveItems + big_island_early_crafting: BigIslandEarlyCrafting + paddleboard_mode: PaddleboardMode + death_link: DeathLink diff --git a/worlds/raft/Rules.py b/worlds/raft/Rules.py index e84068a6f584..b6bd49c187cd 100644 --- a/worlds/raft/Rules.py +++ b/worlds/raft/Rules.py @@ -5,10 +5,10 @@ class RaftLogic(LogicMixin): def raft_paddleboard_mode_enabled(self, player): - return self.multiworld.paddleboard_mode[player].value + return bool(self.multiworld.worlds[player].options.paddleboard_mode) def raft_big_islands_available(self, player): - return self.multiworld.big_island_early_crafting[player].value or self.raft_can_access_radio_tower(player) + return bool(self.multiworld.worlds[player].options.big_island_early_crafting) or self.raft_can_access_radio_tower(player) def raft_can_smelt_items(self, player): return self.has("Smelter", player) diff --git a/worlds/raft/__init__.py b/worlds/raft/__init__.py index 8e4eda09e10f..71d5d1c7e44b 100644 --- a/worlds/raft/__init__.py +++ b/worlds/raft/__init__.py @@ -6,7 +6,7 @@ from .Regions import create_regions, getConnectionName from .Rules import set_rules -from .Options import raft_options +from .Options import RaftOptions from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, Tutorial from ..AutoWorld import World, WebWorld @@ -37,17 +37,17 @@ class RaftWorld(World): lastItemId = max(filter(lambda val: val is not None, item_name_to_id.values())) location_name_to_id = locations_lookup_name_to_id - option_definitions = raft_options + options_dataclass = RaftOptions + options: RaftOptions - data_version = 2 required_client_version = (0, 3, 4) def create_items(self): - minRPSpecified = self.multiworld.minimum_resource_pack_amount[self.player].value - maxRPSpecified = self.multiworld.maximum_resource_pack_amount[self.player].value + minRPSpecified = self.options.minimum_resource_pack_amount.value + maxRPSpecified = self.options.maximum_resource_pack_amount.value minimumResourcePackAmount = min(minRPSpecified, maxRPSpecified) maximumResourcePackAmount = max(minRPSpecified, maxRPSpecified) - isFillingFrequencies = self.multiworld.island_frequency_locations[self.player].value <= 3 + isFillingFrequencies = self.options.island_frequency_locations.is_filling_frequencies_in_world() # Generate item pool pool = [] frequencyItems = [] @@ -65,20 +65,20 @@ def create_items(self): extraItemNamePool = [] extras = len(location_table) - len(item_table) - 1 # Victory takes up 1 unaccounted-for slot if extras > 0: - if (self.multiworld.filler_item_types[self.player].value != 1): # Use resource packs + if (self.options.filler_item_types != self.options.filler_item_types.option_duplicates): # Use resource packs for packItem in resourcePackItems: for i in range(minimumResourcePackAmount, maximumResourcePackAmount + 1): extraItemNamePool.append(createResourcePackName(i, packItem)) - if self.multiworld.filler_item_types[self.player].value != 0: # Use duplicate items + if self.options.filler_item_types != self.options.filler_item_types.option_resource_packs: # Use duplicate items dupeItemPool = item_table.copy() # Remove frequencies if necessary - if self.multiworld.island_frequency_locations[self.player].value != 5: # Not completely random locations + if self.options.island_frequency_locations != self.options.island_frequency_locations.option_anywhere: # Not completely random locations # If we let frequencies stay in with progressive-frequencies, the progressive-frequency item # will be included 7 times. This is a massive flood of progressive-frequency items, so we # instead add progressive-frequency as its own item a smaller amount of times to prevent # flooding the duplicate item pool with them. - if self.multiworld.island_frequency_locations[self.player].value == 4: + if self.options.island_frequency_locations == self.options.island_frequency_locations.option_progressive: for _ in range(2): # Progressives are not in item_pool, need to create faux item for duplicate item pool # This can still be filtered out later by duplicate_items setting @@ -87,9 +87,9 @@ def create_items(self): dupeItemPool = (itm for itm in dupeItemPool if "Frequency" not in itm["name"]) # Remove progression or non-progression items if necessary - if (self.multiworld.duplicate_items[self.player].value == 0): # Progression only + if (self.options.duplicate_items == self.options.duplicate_items.option_progression): # Progression only dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == True) - elif (self.multiworld.duplicate_items[self.player].value == 1): # Non-progression only + elif (self.options.duplicate_items == self.options.duplicate_items.option_non_progression): # Non-progression only dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == False) dupeItemPool = list(dupeItemPool) @@ -116,14 +116,14 @@ def create_regions(self): create_regions(self.multiworld, self.player) def get_pre_fill_items(self): - if self.multiworld.island_frequency_locations[self.player] in [0, 1, 2, 3]: + if self.options.island_frequency_locations.is_filling_frequencies_in_world(): return [loc.item for loc in self.multiworld.get_filled_locations()] return [] def create_item_replaceAsNecessary(self, name: str) -> Item: isFrequency = "Frequency" in name - shouldUseProgressive = ((isFrequency and self.multiworld.island_frequency_locations[self.player].value == 4) - or (not isFrequency and self.multiworld.progressive_items[self.player].value)) + shouldUseProgressive = bool((isFrequency and self.options.island_frequency_locations == self.options.island_frequency_locations.option_progressive) + or (not isFrequency and self.options.progressive_items)) if shouldUseProgressive and name in progressive_table: name = progressive_table[name] return self.create_item(name) @@ -153,7 +153,7 @@ def collect_item(self, state, item, remove=False): return super(RaftWorld, self).collect_item(state, item, remove) def pre_fill(self): - if self.multiworld.island_frequency_locations[self.player] == 0: # Vanilla + if self.options.island_frequency_locations == self.options.island_frequency_locations.option_vanilla: self.setLocationItem("Radio Tower Frequency to Vasagatan", "Vasagatan Frequency") self.setLocationItem("Vasagatan Frequency to Balboa", "Balboa Island Frequency") self.setLocationItem("Relay Station quest", "Caravan Island Frequency") @@ -161,7 +161,7 @@ def pre_fill(self): self.setLocationItem("Tangaroa Frequency to Varuna Point", "Varuna Point Frequency") self.setLocationItem("Varuna Point Frequency to Temperance", "Temperance Frequency") self.setLocationItem("Temperance Frequency to Utopia", "Utopia Frequency") - elif self.multiworld.island_frequency_locations[self.player] == 1: # Random on island + elif self.options.island_frequency_locations == self.options.island_frequency_locations.option_random_on_island: self.setLocationItemFromRegion("RadioTower", "Vasagatan Frequency") self.setLocationItemFromRegion("Vasagatan", "Balboa Island Frequency") self.setLocationItemFromRegion("BalboaIsland", "Caravan Island Frequency") @@ -169,7 +169,10 @@ def pre_fill(self): self.setLocationItemFromRegion("Tangaroa", "Varuna Point Frequency") self.setLocationItemFromRegion("Varuna Point", "Temperance Frequency") self.setLocationItemFromRegion("Temperance", "Utopia Frequency") - elif self.multiworld.island_frequency_locations[self.player] in [2, 3]: + elif self.options.island_frequency_locations in [ + self.options.island_frequency_locations.option_random_island_order, + self.options.island_frequency_locations.option_random_on_island_random_order + ]: locationToFrequencyItemMap = { "Vasagatan": "Vasagatan Frequency", "BalboaIsland": "Balboa Island Frequency", @@ -197,9 +200,9 @@ def pre_fill(self): else: currentLocation = availableLocationList[0] # Utopia (only one left in list) availableLocationList.remove(currentLocation) - if self.multiworld.island_frequency_locations[self.player] == 2: # Random island order + if self.options.island_frequency_locations == self.options.island_frequency_locations.option_random_island_order: self.setLocationItem(locationToVanillaFrequencyLocationMap[previousLocation], locationToFrequencyItemMap[currentLocation]) - elif self.multiworld.island_frequency_locations[self.player] == 3: # Random on island random order + elif self.options.island_frequency_locations == self.options.island_frequency_locations.option_random_on_island_random_order: self.setLocationItemFromRegion(previousLocation, locationToFrequencyItemMap[currentLocation]) previousLocation = currentLocation @@ -216,9 +219,9 @@ def setLocationItemFromRegion(self, region: str, itemName: str): def fill_slot_data(self): return { - "IslandGenerationDistance": self.multiworld.island_generation_distance[self.player].value, - "ExpensiveResearch": bool(self.multiworld.expensive_research[self.player].value), - "DeathLink": bool(self.multiworld.death_link[self.player].value) + "IslandGenerationDistance": self.options.island_generation_distance.value, + "ExpensiveResearch": bool(self.options.expensive_research), + "DeathLink": bool(self.options.death_link) } def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): diff --git a/worlds/raft/docs/en_Raft.md b/worlds/raft/docs/en_Raft.md index 385377d45608..0c68e23d0019 100644 --- a/worlds/raft/docs/en_Raft.md +++ b/worlds/raft/docs/en_Raft.md @@ -1,7 +1,7 @@ # Raft -## Where is the settings page? -The player settings page for this game is located here. It contains all the options +## Where is the options page? +The player options page for this game is located here. It contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/raft/docs/setup_en.md b/worlds/raft/docs/setup_en.md index 236bb8d8acf2..16e7883776c3 100644 --- a/worlds/raft/docs/setup_en.md +++ b/worlds/raft/docs/setup_en.md @@ -17,7 +17,7 @@ 4. Open RML and click Play. If you've already installed it, the executable that was used to install RML ("RMLLauncher.exe" unless renamed) should be used to run RML. Raft should start after clicking Play. -5. Open the RML menu. This should open automatically when Raft first loads. If it does not, and you see RML information in the top center of the Raft main menu, press F9 to open it. +5. Open the RML menu. This should open automatically when Raft first loads. If it does not, and you see RML information in the top center of the Raft main menu, press F9 to open it. If you do not see RML information at the top, close Raft+RML, go back to Step 4 and run RML as administrator. 6. Navigate to the "Mod manager" tab in the left-hand menu. diff --git a/worlds/rogue_legacy/__init__.py b/worlds/rogue_legacy/__init__.py index c5a8d71b5d63..eb657699540f 100644 --- a/worlds/rogue_legacy/__init__.py +++ b/worlds/rogue_legacy/__init__.py @@ -35,7 +35,6 @@ class RLWorld(World): game = "Rogue Legacy" option_definitions = rl_options topology_present = True - data_version = 4 required_client_version = (0, 3, 5) web = RLWeb() diff --git a/worlds/rogue_legacy/docs/en_Rogue Legacy.md b/worlds/rogue_legacy/docs/en_Rogue Legacy.md index c91dc0de6f7a..dd203c73ac26 100644 --- a/worlds/rogue_legacy/docs/en_Rogue Legacy.md +++ b/worlds/rogue_legacy/docs/en_Rogue Legacy.md @@ -1,9 +1,9 @@ # Rogue Legacy (PC) -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains most of the options you need to -configure and export a config file. Some settings can only be made in YAML, but an explanation can be found in the +The [player options page for this game](../player-options) contains most of the options you need to +configure and export a config file. Some options can only be made in YAML, but an explanation can be found in the [template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml). ## What does randomization do to this game? diff --git a/worlds/rogue_legacy/docs/rogue-legacy_en.md b/worlds/rogue_legacy/docs/rogue-legacy_en.md index e513d0f0ca18..fc9f6920178d 100644 --- a/worlds/rogue_legacy/docs/rogue-legacy_en.md +++ b/worlds/rogue_legacy/docs/rogue-legacy_en.md @@ -21,7 +21,7 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -you can customize your settings by visiting the [Rogue Legacy Settings Page](/games/Rogue%20Legacy/player-settings). +you can customize your options by visiting the [Rogue Legacy Options Page](/games/Rogue%20Legacy/player-options). ### Connect to the MultiServer diff --git a/worlds/rogue_legacy/test/TestUnique.py b/worlds/rogue_legacy/test/TestUnique.py index dbe35dd94fc0..1ae9968d5519 100644 --- a/worlds/rogue_legacy/test/TestUnique.py +++ b/worlds/rogue_legacy/test/TestUnique.py @@ -1,8 +1,8 @@ from typing import Dict from . import RLTestBase -from worlds.rogue_legacy.Items import RLItemData, item_table -from worlds.rogue_legacy.Locations import RLLocationData, location_table +from ..Items import item_table +from ..Locations import location_table class UniqueTest(RLTestBase): diff --git a/worlds/ror2/__init__.py b/worlds/ror2/__init__.py index 6574a176dc2d..7873ae54bbba 100644 --- a/worlds/ror2/__init__.py +++ b/worlds/ror2/__init__.py @@ -7,7 +7,7 @@ environment_sotv_orderedstages_table, environment_sotv_table, collapse_dict_list_vertical, shift_by_offset from BaseClasses import Item, ItemClassification, Tutorial -from .options import ItemWeights, ROR2Options +from .options import ItemWeights, ROR2Options, ror2_option_groups from worlds.AutoWorld import World, WebWorld from .regions import create_explore_regions, create_classic_regions from typing import List, Dict, Any @@ -23,6 +23,8 @@ class RiskOfWeb(WebWorld): ["Ijwu", "Kindasneaki"] )] + option_groups = ror2_option_groups + class RiskOfRainWorld(World): """ @@ -44,8 +46,7 @@ class RiskOfRainWorld(World): } location_name_to_id = item_pickups - data_version = 8 - required_client_version = (0, 4, 4) + required_client_version = (0, 5, 0) web = RiskOfWeb() total_revivals: int @@ -91,6 +92,17 @@ def create_items(self) -> None: # only mess with the environments if they are set as items if self.options.goal == "explore": + # check to see if the user doesn't want to use stages, and to figure out what type of stages are being used. + if not self.options.require_stages: + if not self.options.progressive_stages: + self.multiworld.push_precollected(self.multiworld.create_item("Stage 1", self.player)) + self.multiworld.push_precollected(self.multiworld.create_item("Stage 2", self.player)) + self.multiworld.push_precollected(self.multiworld.create_item("Stage 3", self.player)) + self.multiworld.push_precollected(self.multiworld.create_item("Stage 4", self.player)) + else: + for _ in range(4): + self.multiworld.push_precollected(self.multiworld.create_item("Progressive Stage", self.player)) + # figure out all available ordered stages for each tier environment_available_orderedstages_table = environment_vanilla_orderedstages_table if self.options.dlc_sotv: @@ -121,8 +133,12 @@ def create_items(self) -> None: total_locations = self.options.total_locations.value else: # explore mode - # Add Stage items for logic gates - itempool += ["Stage 1", "Stage 2", "Stage 3", "Stage 4"] + + # Add Stage items to the pool + if self.options.require_stages: + itempool += ["Stage 1", "Stage 2", "Stage 3", "Stage 4"] if not self.options.progressive_stages else \ + ["Progressive Stage"] * 4 + total_locations = len( get_locations( chests=self.options.chests_per_stage.value, @@ -206,8 +222,8 @@ def fill_slot_data(self) -> Dict[str, Any]: options_dict = self.options.as_dict("item_pickup_step", "shrine_use_step", "goal", "victory", "total_locations", "chests_per_stage", "shrines_per_stage", "scavengers_per_stage", "scanner_per_stage", "altars_per_stage", "total_revivals", - "start_with_revive", "final_stage_death", "death_link", - casing="camel") + "start_with_revive", "final_stage_death", "death_link", "require_stages", + "progressive_stages", casing="camel") return { **options_dict, "seed": "".join(self.random.choice(string.digits) for _ in range(16)), diff --git a/worlds/ror2/docs/en_Risk of Rain 2.md b/worlds/ror2/docs/en_Risk of Rain 2.md index d30edf888944..651c89a33923 100644 --- a/worlds/ror2/docs/en_Risk of Rain 2.md +++ b/worlds/ror2/docs/en_Risk of Rain 2.md @@ -1,8 +1,8 @@ # Risk of Rain 2 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -23,7 +23,7 @@ Explore Mode: - Chests will continue to work as they did in Classic Mode, the difference being that each environment will have a set amount of items that can be sent out. In addition, shrines, radio scanners, newt altars, - and scavenger bags will need to be checked, depending on your settings. + and scavenger bags will need to be checked, depending on your options. This mode also makes each environment an item. In order to access a particular stage, you'll need it to be sent in the multiworld. @@ -32,7 +32,7 @@ Explore Mode: Just like in the original game, any way to "beat the game" counts as a win. This means beating one of the bosses on Commencement, The Planetarium, or A Moment, Whole. Alternatively, if you are new to the game and aren't very confident in being able to "beat the game", you can set **Final Stage Death is Win** to true -(You can turn this on in your player settings.) This will make it so dying on either Commencement or The Planetarium, +(You can turn this on in your player options.) This will make it so dying on either Commencement or The Planetarium, or **obliterating yourself in A Moment, Fractured** will count as your goal. **You do not need to complete all the location checks** to win; any item you don't collect may be released if the server options allow. @@ -48,16 +48,15 @@ then finish a normal mode run while keeping the items you received via the multi ## Can you play multiplayer? Yes! You can have a single multiplayer instance as one world in the multiworld. All the players involved need to have -the Archipelago mod, but only the host needs to configure the Archipelago settings. When someone finds an item for your +the Archipelago mod, but only the host needs to configure the Archipelago options. When someone finds an item for your world, all the connected players will receive a copy of the item, and the location check bar will increase whenever any player finds an item in Risk of Rain. You cannot have players with different player slots in the same co-op game instance. Only the host's Archipelago -settings apply, so each Risk of Rain 2 player slot in the multiworld needs to be a separate game instance. You could, +options apply, so each Risk of Rain 2 player slot in the multiworld needs to be a separate game instance. You could, for example, have two players trade off hosting and making progress on each other's player slot, but a single co-op instance can't make progress towards multiple player slots in the multiworld. -Explore mode is untested in multiplayer and will likely not work until a later release. ## What Risk of Rain items can appear in other players' worlds? @@ -69,7 +68,7 @@ The Risk of Rain items are: * `Legendary Item` (Red items) * `Lunar Item` (Blue items) * `Equipment` (Orange items) -* `Dio's Best Friend` (Used if you set the YAML setting `total_revives_available` above `0`) +* `Dio's Best Friend` (Used if you set the YAML option `total_revives_available` above `0`) * `Void Item` (Purple items) (needs dlc_sotv: enabled) Each item grants you a random in-game item from the category it belongs to. @@ -127,7 +126,7 @@ what item you sent out. If the message does not appear, this likely means that a ## What is the item pickup step? -The item pickup step is a setting in the YAML which allows you to set how many items you need to spawn before the _next_ item +The item pickup step is an option in the YAML which allows you to set how many items you need to spawn before the _next_ item that is spawned disappears (in a poof of smoke) and goes out to the multiworld. For instance, an item step of **1** means that every other chest will send an item to the multiworld. An item step of **2** means that every third chest sends out an item just as an item step of **0** would send an item on **each chest.** diff --git a/worlds/ror2/docs/setup_en.md b/worlds/ror2/docs/setup_en.md index 0fa99c071b9c..6acf2654a8b2 100644 --- a/worlds/ror2/docs/setup_en.md +++ b/worlds/ror2/docs/setup_en.md @@ -29,7 +29,7 @@ You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) h about why Archipelago uses YAML files and what they're for. ### Where do I get a YAML? -You can use the [game settings page](/games/Risk%20of%20Rain%202/player-settings) here on the Archipelago +You can use the [game options page](/games/Risk%20of%20Rain%202/player-options) here on the Archipelago website to generate a YAML using a graphical interface. diff --git a/worlds/ror2/items.py b/worlds/ror2/items.py index 449686d04bf0..3586030816e9 100644 --- a/worlds/ror2/items.py +++ b/worlds/ror2/items.py @@ -59,7 +59,7 @@ class RiskOfRainItemData(NamedTuple): "Stage 2": RiskOfRainItemData("Stage", 2 + stage_offset, ItemClassification.progression), "Stage 3": RiskOfRainItemData("Stage", 3 + stage_offset, ItemClassification.progression), "Stage 4": RiskOfRainItemData("Stage", 4 + stage_offset, ItemClassification.progression), - + "Progressive Stage": RiskOfRainItemData("Stage", 5 + stage_offset, ItemClassification.progression), } item_table = {**upgrade_table, **other_table, **filler_table, **trap_table, **stage_table} diff --git a/worlds/ror2/options.py b/worlds/ror2/options.py index abb8e91da25e..381c5942b07b 100644 --- a/worlds/ror2/options.py +++ b/worlds/ror2/options.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCommonOptions +from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCommonOptions, OptionGroup # NOTE be aware that since the range of item ids that RoR2 uses is based off of the maximums of checks @@ -151,6 +151,17 @@ class DLC_SOTV(Toggle): display_name = "Enable DLC - SOTV" +class RequireStages(DefaultOnToggle): + """Add Stage items to the pool to block access to the next set of environments.""" + display_name = "Require Stages" + + +class ProgressiveStages(DefaultOnToggle): + """This will convert Stage items to be a progressive item. For example instead of "Stage 2" it would be + "Progressive Stage" """ + display_name = "Progressive Stages" + + class GreenScrap(Range): """Weight of Green Scraps in the item pool. @@ -339,7 +350,7 @@ class ItemPoolPresetToggle(Toggle): class ItemWeights(Choice): - """Set item_pool_presets to true if you want to use one of these presets. + """Set Use Item Weight Presets to yes if you want to use one of these presets. Preset choices for determining the weights of the item pool. - New is a test for a potential adjustment to the default weights. - Uncommon puts a large number of uncommon items in the pool. @@ -364,6 +375,44 @@ class ItemWeights(Choice): option_void = 9 +ror2_option_groups = [ + OptionGroup("Explore Mode Options", [ + ChestsPerEnvironment, + ShrinesPerEnvironment, + ScavengersPerEnvironment, + ScannersPerEnvironment, + AltarsPerEnvironment, + RequireStages, + ProgressiveStages, + ]), + OptionGroup("Classic Mode Options", [ + TotalLocations, + ], start_collapsed=True), + OptionGroup("Weighted Choices", [ + ItemWeights, + ItemPoolPresetToggle, + WhiteScrap, + GreenScrap, + YellowScrap, + RedScrap, + CommonItem, + UncommonItem, + LegendaryItem, + BossItem, + LunarItem, + VoidItem, + Equipment, + Money, + LunarCoin, + Experience, + MountainTrap, + TimeWarpTrap, + CombatTrap, + TeleportTrap, + ]), +] + + @dataclass class ROR2Options(PerGameCommonOptions): goal: Goal @@ -378,6 +427,8 @@ class ROR2Options(PerGameCommonOptions): start_with_revive: StartWithRevive final_stage_death: FinalStageDeath dlc_sotv: DLC_SOTV + require_stages: RequireStages + progressive_stages: ProgressiveStages death_link: DeathLink item_pickup_step: ItemPickupStep shrine_use_step: ShrineUseStep @@ -386,10 +437,10 @@ class ROR2Options(PerGameCommonOptions): item_weights: ItemWeights item_pool_presets: ItemPoolPresetToggle # define the weights of the generated item pool. + white_scrap: WhiteScrap green_scrap: GreenScrap - red_scrap: RedScrap yellow_scrap: YellowScrap - white_scrap: WhiteScrap + red_scrap: RedScrap common_item: CommonItem uncommon_item: UncommonItem legendary_item: LegendaryItem diff --git a/worlds/ror2/regions.py b/worlds/ror2/regions.py index 199fdccf80e8..def29b47286b 100644 --- a/worlds/ror2/regions.py +++ b/worlds/ror2/regions.py @@ -19,11 +19,13 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None: # Default Locations non_dlc_regions: Dict[str, RoRRegionData] = { "Menu": RoRRegionData(None, ["Distant Roost", "Distant Roost (2)", - "Titanic Plains", "Titanic Plains (2)"]), + "Titanic Plains", "Titanic Plains (2)", + "Verdant Falls"]), "Distant Roost": RoRRegionData([], ["OrderedStage_1"]), "Distant Roost (2)": RoRRegionData([], ["OrderedStage_1"]), "Titanic Plains": RoRRegionData([], ["OrderedStage_1"]), "Titanic Plains (2)": RoRRegionData([], ["OrderedStage_1"]), + "Verdant Falls": RoRRegionData([], ["OrderedStage_1"]), "Abandoned Aqueduct": RoRRegionData([], ["OrderedStage_2"]), "Wetland Aspect": RoRRegionData([], ["OrderedStage_2"]), "Rallypoint Delta": RoRRegionData([], ["OrderedStage_3"]), diff --git a/worlds/ror2/ror2environments.py b/worlds/ror2/ror2environments.py index d821763ef40c..61707b336241 100644 --- a/worlds/ror2/ror2environments.py +++ b/worlds/ror2/ror2environments.py @@ -7,6 +7,7 @@ "Distant Roost (2)": 8, # blackbeach2 "Titanic Plains": 15, # golemplains "Titanic Plains (2)": 16, # golemplains2 + "Verdant Falls": 28, # lakes } environment_vanilla_orderedstage_2_table: Dict[str, int] = { "Abandoned Aqueduct": 17, # goolake diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py index b4d5fe68b82e..2e6b018f42fb 100644 --- a/worlds/ror2/rules.py +++ b/worlds/ror2/rules.py @@ -15,6 +15,13 @@ def has_entrance_access_rule(multiworld: MultiWorld, stage: str, region: str, pl entrance.access_rule = rule +def has_stage_access_rule(multiworld: MultiWorld, stage: str, amount: int, region: str, player: int) -> None: + rule = lambda state: state.has(region, player) and \ + (state.has(stage, player) or state.count("Progressive Stage", player) >= amount) + for entrance in multiworld.get_region(region, player).entrances: + entrance.access_rule = rule + + def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: int) -> None: rule = lambda state: state.has_all(items, player) and state.has(region, player) for entrance in multiworld.get_region(region, player).entrances: @@ -43,15 +50,6 @@ def check_location(state, environment: str, player: int, item_number: int, item_ return state.can_reach(f"{environment}: {item_name} {item_number - 1}", "Location", player) -# unlock event to next set of stages -def get_stage_event(multiworld: MultiWorld, player: int, stage_number: int) -> None: - if stage_number == 4: - return - rule = lambda state: state.has(f"Stage {stage_number + 1}", player) - for entrance in multiworld.get_region(f"OrderedStage_{stage_number + 1}", player).entrances: - entrance.access_rule = rule - - def set_rules(ror2_world: "RiskOfRainWorld") -> None: player = ror2_world.player multiworld = ror2_world.multiworld @@ -124,8 +122,7 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None: for newt in range(1, newts + 1): has_location_access_rule(multiworld, environment_name, player, newt, "Newt Altar") if i > 0: - has_entrance_access_rule(multiworld, f"Stage {i}", environment_name, player) - get_stage_event(multiworld, player, i) + has_stage_access_rule(multiworld, f"Stage {i}", i, environment_name, player) if ror2_options.dlc_sotv: for i in range(len(environment_sotv_orderedstages_table)): @@ -143,10 +140,10 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None: for newt in range(1, newts + 1): has_location_access_rule(multiworld, environment_name, player, newt, "Newt Altar") if i > 0: - has_entrance_access_rule(multiworld, f"Stage {i}", environment_name, player) + has_stage_access_rule(multiworld, f"Stage {i}", i, environment_name, player) has_entrance_access_rule(multiworld, "Hidden Realm: A Moment, Fractured", "Hidden Realm: A Moment, Whole", player) - has_entrance_access_rule(multiworld, "Stage 1", "Hidden Realm: Bazaar Between Time", player) + has_stage_access_rule(multiworld, "Stage 1", 1, "Hidden Realm: Bazaar Between Time", player) has_entrance_access_rule(multiworld, "Hidden Realm: Bazaar Between Time", "Void Fields", player) has_entrance_access_rule(multiworld, "Stage 5", "Commencement", player) has_entrance_access_rule(multiworld, "Stage 5", "Hidden Realm: A Moment, Fractured", player) diff --git a/worlds/ror2/test/test_mithrix_goal.py b/worlds/ror2/test/test_mithrix_goal.py index 03b82311783c..a52301bef5eb 100644 --- a/worlds/ror2/test/test_mithrix_goal.py +++ b/worlds/ror2/test/test_mithrix_goal.py @@ -3,7 +3,9 @@ class MithrixGoalTest(RoR2TestBase): options = { - "victory": "mithrix" + "victory": "mithrix", + "require_stages": "true", + "progressive_stages": "false" } def test_mithrix(self) -> None: diff --git a/worlds/sa2b/CHANGELOG.md b/worlds/sa2b/CHANGELOG.md new file mode 100644 index 000000000000..af6c4afd229f --- /dev/null +++ b/worlds/sa2b/CHANGELOG.md @@ -0,0 +1,247 @@ +# Sonic Adventure 2 Battle - Changelog + + +## v2.3 - The Chao Update + +### Features: + +- New goal + - Chaos Chao + - Raise a Chaos Chao to win! +- New optional Location Checks + - Chao Animal Parts + - Each body part from each type of animal is a location + - Chao Stats + - 0-99 levels of each of the 7 Chao stats can be locations + - The frequency of Chao Stat locations can be set (every level, every 2nd level, etc) + - Kindergartensanity + - Classroom lessons are locations + - Either all lessons or any one of each category can be set as locations + - Shopsanity + - A specified number of locations can be placed in the Chao Black Market + - These locations are unlocked by acquiring Chao Coins + - Ring costs for these items can be adjusted + - Chao Karate can now be set to one location per fight, instead of one per tournament +- New Items + - If any Chao locations are active, the following will be in the item pool: + - Chao Eggs + - Garden Seeds + - Garden Fruit + - Chao Hats + - Chaos Drives + - New Trap + - Reverse Trap +- The starting eggs in the garden can be a random color +- Chao World entrances can be shuffled +- Chao are given default names + +### Quality of Life: + +- Chao Save Data is now separate per-slot in addition to per-seed + - This allows a single player to have multiple slots in the same seed, each having separate Chao progress +- Chao Race/Karate progress is now displayed on Stage Select (when hovering over Chao World) +- All Chao can now enter the Hero and Dark races +- Chao Karate difficulty can be set separately from Chao Race difficulty +- Chao Aging can be sped up at will, up to 15× +- New mod config option to fine-tune Chao Stat multiplication + - Note: This does not mix well with the Mod Manager "Chao Stat Multiplier" code +- Pong Traps can now activate in Chao World +- Maximum range for possible number of Emblems is now 1000 +- General APWorld cleanup and optimization +- Option access has moved to the new options system +- An item group now exists for trap items + +### Bug Fixes: + +- Dry Lagoon now has all 11 Animals +- `Eternal Engine - 2` (Standard and Hard Logic) now requires only `Tails - Booster` +- `Lost Colony - 2` (Hard Logic) now requires no upgrades +- `Lost Colony - Animal 9` (Hard Logic) now requires either `Eggman - Jet Engine` or `Eggman - Large Cannon` + + +## v2.2 + +### Features: + +- New goals + - Boss Rush + - Complete the Boss Rush to win! + - Cannon's Core Boss Rush + - Beat Cannon's Core, then complete the Boss Rush + - Boss Rush Chaos Emerald Hunt + - Collect the seven Chaos Emeralds, then complete the Boss Rush +- Boss Rush Shuffle option +- New optional Location Checks + - Animalsanity + - Collect numbers of animals per stage +- Ring Link option + - Any ring amounts gained and lost by a linked player will be instantly shared with all other active linked players +- Voice line shuffle + - None + - Shuffled + - Rude + - Chao + - Singularity +- New Traps + - Ice Trap + - Slow Trap + - Cutscene Trap + +### Quality of Life: + +- Maximum possible number of Emblems in item pool is now a player-facing option, in the range of 50-500 +- A cause is now included for sent DeathLinks +- Death Cause messages are now displayed in-game +- WSS connections are now supported + +### Bug Fixes: + +- Two rare softlock scenarios related to the Chaos Control Trap should no longer occur +- Tracking of location checks while disconnected from the server should be more consistent +- DeathLinks can now be sent and received between two players connected to the same slot +- 2P mode should no longer be accessible +- Boss Stages no longer display erroneous location tracking icons +- Boss Stages are no longer subject to mission shuffle oddities +- Fix Logic Errors + - Eternal Engine - Pipe 1 (Standard and Hard Logic) now requires no upgrades + - Egg Quarters - 5 (Standard Logic) now requires Rouge - Iron Boots + + +## v2.1 + +### Features: + +- New goal + - Grand Prix + - Complete all of the Kart Races to win! +- New optional Location Checks + - Omosanity (Activating Omochao) + - Kart Race Mode +- Ring Loss option + - Classic - lose all rings on hit + - Modern - lose 20 rings on hit + - OHKO - instantly die on hit, regardless of ring count (shields still protect you) +- New Trap + - Pong Trap + +### Quality of Life: + +- SA2B is now distributed as an `.apworld` +- Maximum possible number of Emblems in item pool is increased from 180 to 250 +- An indicator now shows on the Stage Select screen when Cannon's Core is available +- Certain traps (Exposition and Pong) are now possible to receive on Route 101 and Route 280 +- Certain traps (Confusion, Chaos Control, Exposition and Pong) are now possible to receive on FinalHazard + +### Bug Fixes: + +- Actually swap Intermediate and Expert Chao Races correctly +- Don't always grant double score for killing Gold Beetles anymore +- Ensure upgrades are applied properly, even when received while dying +- Fix the Message Queue getting disordered when receiving many messages in quick succession +- Fix Logic errors + - `City Escape - 3` (Hard Logic) now requires no upgrades + - `Mission Street - Pipe 2` (Hard Logic) now requires no upgrades + - `Crazy Gadget - Pipe 3` (Hard Logic) now requires no upgrades + - `Egg Quarters - 3` (Hard Logic) now requires only `Rouge - Mystic Melody` + - `Mad Space - 5` (Hard Logic) now requires no upgrades + + +## v2.0 + +### Features: + +- Completely reworked mission progression system + - Control of which mission types can be active per-gameplay-style + - Control of how many missions are active per-gameplay-style + - Mission order shuffle +- Two new Chaos Emerald Hunt goals + - Chaos Emerald Hunt involves finding the seven Chaos Emeralds and beating Green Hill + - FinalHazard Chaos Emerald Hunt is the same, but with the FinalHazard fight at the end of Green Hill +- New optional Location Checks + - Keysanity (Chao Containers) + - Whistlesanity (Animal Pipes and hidden whistle spots) + - Beetlesanity (Destroying Gold Beetles) +- Option to require clearing all active Cannon's Core Missions for access to the Biolizard fight in Biolizard goal +- Hard Logic option +- More Music Options + - Option to use SADX music + - New Singularity music shuffle option +- Option to choose the Narrator theme +- New Traps + - Tiny Trap is now permanent within a level + - Gravity Trap + - Exposition Trap + +### Quality of Life: + +- Significant revamp to Stage Select screen information conveyance + - Icons are displayed for: + - Relevant character's upgrades + - Which location checks are active/checked + - Chaos Emeralds found (if relevant) + - Gate and Cannon's Core emblem costs + - The above stage-specific info can also be viewed when paused in-level + - The current mission is also displayed when paused +- Emblem Symbol on Mission Select subscreen now only displays if a high enough rank has been gotten on that mission to send the location check +- Hints including SA2B locations will now specify which Gate that level is located in +- Save file now stores slot name to help prevent false location checks in the case of one player having multiple SA2B slots in the same seed +- Chao Intermediate and Expert race sets are now swapped, per player feedback + - Intermediate now includes Beginner + Challenge + Hero + Dark + - Expert now includes Beginner + Challenge + Hero + Dark + Jewel +- New mod config option for the color of the Message Queue text + +### Bug Fixes: + +- Fixed bug where game stops properly tracking items after 127 have been received. +- Several logic fixes +- Game now refers to `Knuckles - Shovel Claws` correctly +- Minor AP World code cleanup + + +## v1.1 + +### Features: + +- Unlocking each gate of levels requires beating a random boss +- Chao Races and Karate are now available as an option for checks +- Junk items can now be put into the itempool + - Five Rings + - Ten Rings + - Twenty Rings + - Extra Life + - Shield + - Magnetic Shield + - Invincibility +- Traps can now be put into the itempool + - Omotrap + - Chaos Control Trap + - Confusion Trap + - Tiny Trap +- The Credits now display a few stats about the run +- An Option for the minimum required rank for mission checks is now available +- An Option for influencing the costs of level gates is now available + +### Bug Fixes: + +- A message will display if the game loses connection to Archipelago +- The game will gracefully reconnect to Archipelago +- Kart Race mode is now properly hidden +- Minor logic fixes + + +## v1.0 - First Stable Release + +### Features: + +- Goal is to beat Cannon's Core and defeat the Biolizard +- Locations included: + - Upgrade Pickups + - Mission Clears +- Items included: + - Character Upgrades + - Emblems +- Levels are unlocked by certain amounts of emblems + - An option exists to specify how many missions to include +- Cannon's Core is unlocked by a certain percentage of existent emblems, depending on an option +- Music Shuffle is supported +- DeathLink is supported diff --git a/worlds/sa2b/Options.py b/worlds/sa2b/Options.py index be001572849c..438e59de5e16 100644 --- a/worlds/sa2b/Options.py +++ b/worlds/sa2b/Options.py @@ -1,18 +1,26 @@ -import typing +from dataclasses import dataclass -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList +from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions class Goal(Choice): """ Determines the goal of the seed + Biolizard: Finish Cannon's Core and defeat the Biolizard and Finalhazard + Chaos Emerald Hunt: Find the Seven Chaos Emeralds and reach Green Hill Zone + Finalhazard Chaos Emerald Hunt: Find the Seven Chaos Emeralds and reach Green Hill Zone, then defeat Finalhazard + Grand Prix: Win every race in Kart Race Mode (all standard levels are disabled) + Boss Rush: Beat all of the bosses in the Boss Rush, ending with Finalhazard + Cannon's Core Boss Rush: Beat Cannon's Core, then beat all of the bosses in the Boss Rush, ending with Finalhazard + Boss Rush Chaos Emerald Hunt: Find the Seven Chaos Emeralds, then beat all of the bosses in the Boss Rush, ending with Finalhazard + Chaos Chao: Raise a Chaos Chao to win """ display_name = "Goal" @@ -46,9 +54,13 @@ class MissionShuffle(Toggle): class BossRushShuffle(Choice): """ Determines how bosses in Boss Rush Mode are shuffled + Vanilla: Bosses appear in the Vanilla ordering + Shuffled: The same bosses appear, but in a random order + Chaos: Each boss is randomly chosen separately (one will always be King Boom Boo) + Singularity: One boss is chosen and placed in every slot (one will always be replaced with King Boom Boo) """ display_name = "Boss Rush Shuffle" @@ -196,9 +208,13 @@ class Keysanity(Toggle): class Whistlesanity(Choice): """ Determines whether whistling at various spots grants checks + None: No Whistle Spots grant checks + Pipes: Whistling at Pipes grants checks (97 Locations) + Hidden: Whistling at Hidden Whistle Spots grants checks (32 Locations) + Both: Whistling at both Pipes and Hidden Whistle Spots grants checks (129 Locations) """ display_name = "Whistlesanity" @@ -227,8 +243,10 @@ class Omosanity(Toggle): class Animalsanity(Toggle): """ - Determines whether picking up counted small animals grants checks + Determines whether unique counts of animals grant checks. (421 Locations) + + ALL animals must be collected in a single run of a mission to get all checks. """ display_name = "Animalsanity" @@ -236,8 +254,11 @@ class Animalsanity(Toggle): class KartRaceChecks(Choice): """ Determines whether Kart Race Mode grants checks + None: No Kart Races grant checks + Mini: Each Kart Race difficulty must be beaten only once + Full: Every Character must separately beat each Kart Race difficulty """ display_name = "Kart Race Checks" @@ -270,8 +291,11 @@ class NumberOfLevelGates(Range): class LevelGateDistribution(Choice): """ Determines how levels are distributed between level gate regions + Early: Earlier regions will have more levels than later regions + Even: Levels will be evenly distributed between all regions + Late: Later regions will have more levels than earlier regions """ display_name = "Level Gate Distribution" @@ -295,7 +319,9 @@ class LevelGateCosts(Choice): class MaximumEmblemCap(Range): """ Determines the maximum number of emblems that can be in the item pool. + If fewer available locations exist in the pool than this number, the number of available locations will be used instead. + Gate and Cannon's Core costs will be calculated based off of that number. """ display_name = "Max Emblem Cap" @@ -320,9 +346,13 @@ class RequiredRank(Choice): class ChaoRaceDifficulty(Choice): """ Determines the number of Chao Race difficulty levels included. Easier difficulty settings means fewer Chao Race checks + None: No Chao Races have checks + Beginner: Beginner Races + Intermediate: Beginner, Challenge, Hero, and Dark Races + Expert: Beginner, Challenge, Hero, Dark and Jewel Races """ display_name = "Chao Race Difficulty" @@ -349,9 +379,10 @@ class ChaoKarateDifficulty(Choice): class ChaoStadiumChecks(Choice): """ Determines which Chao Stadium activities grant checks + All: Each individual race and karate fight grants a check - Prize: Only the races which grant Chao Toys grant checks (final race of each Beginner and Jewel cup, 4th, 8th, and - 12th Challenge Races, 2nd and 4th Hero and Dark Races, final fight of each Karate difficulty) + + Prize: Only the races which grant Chao Toys grant checks (final race of each Beginner and Jewel cup, 4th, 8th, and 12th Challenge Races, 2nd and 4th Hero and Dark Races, final fight of each Karate difficulty) """ display_name = "Chao Stadium Checks" option_all = 0 @@ -373,6 +404,7 @@ class ChaoStats(Range): class ChaoStatsFrequency(Range): """ Determines how many levels in each Chao Stat grant checks (up to the maximum set in the `chao_stats` option) + `1` means every level is included, `2` means every other level is included, `3` means every third, and so on """ display_name = "Chao Stats Frequency" @@ -407,8 +439,11 @@ class ChaoKindergarten(Choice): """ Determines whether learning the lessons from the Kindergarten Classroom grants checks (WARNING: VERY SLOW) + None: No Kindergarten classes have checks + Basics: One class from each category (Drawing, Dance, Song, and Instrument) is a check (4 Locations) + Full: Every class is a check (23 Locations) """ display_name = "Chao Kindergarten Checks" @@ -442,8 +477,8 @@ class BlackMarketUnlockCosts(Choice): class BlackMarketPriceMultiplier(Range): """ Determines how many rings the Black Market items cost - The base ring costs of items in the Black Market range from 50-100, - and are then multiplied by this value + + The base ring costs of items in the Black Market range from 50-100, and are then multiplied by this value """ display_name = "Black Market Price Multiplier" range_start = 0 @@ -468,7 +503,9 @@ class ChaoEntranceRandomization(Toggle): class RequiredCannonsCoreMissions(Choice): """ Determines how many Cannon's Core missions must be completed (for Biolizard or Cannon's Core goals) + First: Only the first mission must be completed + All Active: All active Cannon's Core missions must be completed """ display_name = "Required Cannon's Core Missions" @@ -664,8 +701,11 @@ class CannonsCoreMission5(DefaultOnToggle): class RingLoss(Choice): """ How taking damage is handled + Classic: You lose all of your rings when hit + Modern: You lose 20 rings when hit + OHKO: You die immediately when hit (NOTE: Some Hard Logic tricks may require damage boosts!) """ display_name = "Ring Loss" @@ -692,9 +732,13 @@ class RingLink(Toggle): class SADXMusic(Choice): """ Whether the randomizer will include Sonic Adventure DX Music in the music pool + SA2B: Only SA2B music will be played + SADX: Only SADX music will be played + Both: Both SA2B and SADX music will be played + NOTE: This option requires the player to own a PC copy of SADX and to follow the addition steps in the setup guide. """ display_name = "SADX Music" @@ -714,9 +758,13 @@ def get_option_name(cls, value) -> str: class MusicShuffle(Choice): """ What type of Music Shuffle is used + None: No music is shuffled. + Levels: Level music is shuffled. + Full: Level, Menu, and Additional music is shuffled. + Singularity: Level, Menu, and Additional music is all replaced with a single random song. """ display_name = "Music Shuffle Type" @@ -730,10 +778,15 @@ class MusicShuffle(Choice): class VoiceShuffle(Choice): """ What type of Voice Shuffle is used + None: No voices are shuffled. + Shuffled: Voices are shuffled. + Rude: Voices are shuffled, but some are replaced with rude words. + Chao: All voices are replaced with chao sounds. + Singularity: All voices are replaced with a single random voice. """ display_name = "Voice Shuffle Type" @@ -767,7 +820,9 @@ class Narrator(Choice): class LogicDifficulty(Choice): """ What set of Upgrade Requirement logic to use + Standard: The logic assumes the "intended" usage of Upgrades to progress through levels + Hard: Some simple skips or sequence breaks may be required """ display_name = "Logic Difficulty" @@ -776,96 +831,195 @@ class LogicDifficulty(Choice): default = 0 -sa2b_options: typing.Dict[str, type(Option)] = { - "goal": Goal, - - "mission_shuffle": MissionShuffle, - "boss_rush_shuffle": BossRushShuffle, - - "keysanity": Keysanity, - "whistlesanity": Whistlesanity, - "beetlesanity": Beetlesanity, - "omosanity": Omosanity, - "animalsanity": Animalsanity, - "kart_race_checks": KartRaceChecks, - - "logic_difficulty": LogicDifficulty, - "required_rank": RequiredRank, - "required_cannons_core_missions": RequiredCannonsCoreMissions, - - "emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore, - "number_of_level_gates": NumberOfLevelGates, - "level_gate_distribution": LevelGateDistribution, - "level_gate_costs": LevelGateCosts, - "max_emblem_cap": MaximumEmblemCap, - - "chao_race_difficulty": ChaoRaceDifficulty, - "chao_karate_difficulty": ChaoKarateDifficulty, - "chao_stadium_checks": ChaoStadiumChecks, - "chao_stats": ChaoStats, - "chao_stats_frequency": ChaoStatsFrequency, - "chao_stats_stamina": ChaoStatsStamina, - "chao_stats_hidden": ChaoStatsHidden, - "chao_animal_parts": ChaoAnimalParts, - "chao_kindergarten": ChaoKindergarten, - "black_market_slots": BlackMarketSlots, - "black_market_unlock_costs": BlackMarketUnlockCosts, - "black_market_price_multiplier": BlackMarketPriceMultiplier, - "shuffle_starting_chao_eggs": ShuffleStartingChaoEggs, - "chao_entrance_randomization": ChaoEntranceRandomization, - - "junk_fill_percentage": JunkFillPercentage, - "trap_fill_percentage": TrapFillPercentage, - "omochao_trap_weight": OmochaoTrapWeight, - "timestop_trap_weight": TimestopTrapWeight, - "confusion_trap_weight": ConfusionTrapWeight, - "tiny_trap_weight": TinyTrapWeight, - "gravity_trap_weight": GravityTrapWeight, - "exposition_trap_weight": ExpositionTrapWeight, - #"darkness_trap_weight": DarknessTrapWeight, - "ice_trap_weight": IceTrapWeight, - "slow_trap_weight": SlowTrapWeight, - "cutscene_trap_weight": CutsceneTrapWeight, - "reverse_trap_weight": ReverseTrapWeight, - "pong_trap_weight": PongTrapWeight, - "minigame_trap_difficulty": MinigameTrapDifficulty, - - "sadx_music": SADXMusic, - "music_shuffle": MusicShuffle, - "voice_shuffle": VoiceShuffle, - "narrator": Narrator, - "ring_loss": RingLoss, - - "speed_mission_count": SpeedMissionCount, - "speed_mission_2": SpeedMission2, - "speed_mission_3": SpeedMission3, - "speed_mission_4": SpeedMission4, - "speed_mission_5": SpeedMission5, - - "mech_mission_count": MechMissionCount, - "mech_mission_2": MechMission2, - "mech_mission_3": MechMission3, - "mech_mission_4": MechMission4, - "mech_mission_5": MechMission5, - - "hunt_mission_count": HuntMissionCount, - "hunt_mission_2": HuntMission2, - "hunt_mission_3": HuntMission3, - "hunt_mission_4": HuntMission4, - "hunt_mission_5": HuntMission5, - - "kart_mission_count": KartMissionCount, - "kart_mission_2": KartMission2, - "kart_mission_3": KartMission3, - "kart_mission_4": KartMission4, - "kart_mission_5": KartMission5, - - "cannons_core_mission_count": CannonsCoreMissionCount, - "cannons_core_mission_2": CannonsCoreMission2, - "cannons_core_mission_3": CannonsCoreMission3, - "cannons_core_mission_4": CannonsCoreMission4, - "cannons_core_mission_5": CannonsCoreMission5, - - "ring_link": RingLink, - "death_link": DeathLink, -} +sa2b_option_groups = [ + OptionGroup("General Options", [ + Goal, + BossRushShuffle, + LogicDifficulty, + RequiredRank, + MaximumEmblemCap, + RingLoss, + ]), + OptionGroup("Stages", [ + MissionShuffle, + EmblemPercentageForCannonsCore, + RequiredCannonsCoreMissions, + NumberOfLevelGates, + LevelGateCosts, + LevelGateDistribution, + ]), + OptionGroup("Sanity Options", [ + Keysanity, + Whistlesanity, + Beetlesanity, + Omosanity, + Animalsanity, + KartRaceChecks, + ]), + OptionGroup("Chao", [ + BlackMarketSlots, + BlackMarketUnlockCosts, + BlackMarketPriceMultiplier, + ChaoRaceDifficulty, + ChaoKarateDifficulty, + ChaoStadiumChecks, + ChaoAnimalParts, + ChaoStats, + ChaoStatsFrequency, + ChaoStatsStamina, + ChaoStatsHidden, + ChaoKindergarten, + ShuffleStartingChaoEggs, + ChaoEntranceRandomization, + ]), + OptionGroup("Junk and Traps", [ + JunkFillPercentage, + TrapFillPercentage, + OmochaoTrapWeight, + TimestopTrapWeight, + ConfusionTrapWeight, + TinyTrapWeight, + GravityTrapWeight, + ExpositionTrapWeight, + IceTrapWeight, + SlowTrapWeight, + CutsceneTrapWeight, + ReverseTrapWeight, + PongTrapWeight, + MinigameTrapDifficulty, + ]), + OptionGroup("Speed Missions", [ + SpeedMissionCount, + SpeedMission2, + SpeedMission3, + SpeedMission4, + SpeedMission5, + ]), + OptionGroup("Mech Missions", [ + MechMissionCount, + MechMission2, + MechMission3, + MechMission4, + MechMission5, + ]), + OptionGroup("Hunt Missions", [ + HuntMissionCount, + HuntMission2, + HuntMission3, + HuntMission4, + HuntMission5, + ]), + OptionGroup("Kart Missions", [ + KartMissionCount, + KartMission2, + KartMission3, + KartMission4, + KartMission5, + ]), + OptionGroup("Cannon's Core Missions", [ + CannonsCoreMissionCount, + CannonsCoreMission2, + CannonsCoreMission3, + CannonsCoreMission4, + CannonsCoreMission5, + ]), + OptionGroup("Aesthetics", [ + SADXMusic, + MusicShuffle, + VoiceShuffle, + Narrator, + ]), +] + + +@dataclass +class SA2BOptions(PerGameCommonOptions): + goal: Goal + boss_rush_shuffle: BossRushShuffle + logic_difficulty: LogicDifficulty + required_rank: RequiredRank + max_emblem_cap: MaximumEmblemCap + ring_loss: RingLoss + + mission_shuffle: MissionShuffle + required_cannons_core_missions: RequiredCannonsCoreMissions + emblem_percentage_for_cannons_core: EmblemPercentageForCannonsCore + number_of_level_gates: NumberOfLevelGates + level_gate_distribution: LevelGateDistribution + level_gate_costs: LevelGateCosts + + keysanity: Keysanity + whistlesanity: Whistlesanity + beetlesanity: Beetlesanity + omosanity: Omosanity + animalsanity: Animalsanity + kart_race_checks: KartRaceChecks + + black_market_slots: BlackMarketSlots + black_market_unlock_costs: BlackMarketUnlockCosts + black_market_price_multiplier: BlackMarketPriceMultiplier + chao_race_difficulty: ChaoRaceDifficulty + chao_karate_difficulty: ChaoKarateDifficulty + chao_stadium_checks: ChaoStadiumChecks + chao_animal_parts: ChaoAnimalParts + chao_stats: ChaoStats + chao_stats_frequency: ChaoStatsFrequency + chao_stats_stamina: ChaoStatsStamina + chao_stats_hidden: ChaoStatsHidden + chao_kindergarten: ChaoKindergarten + shuffle_starting_chao_eggs: ShuffleStartingChaoEggs + chao_entrance_randomization: ChaoEntranceRandomization + + junk_fill_percentage: JunkFillPercentage + trap_fill_percentage: TrapFillPercentage + omochao_trap_weight: OmochaoTrapWeight + timestop_trap_weight: TimestopTrapWeight + confusion_trap_weight: ConfusionTrapWeight + tiny_trap_weight: TinyTrapWeight + gravity_trap_weight: GravityTrapWeight + exposition_trap_weight: ExpositionTrapWeight + #darkness_trap_weight: DarknessTrapWeight + ice_trap_weight: IceTrapWeight + slow_trap_weight: SlowTrapWeight + cutscene_trap_weight: CutsceneTrapWeight + reverse_trap_weight: ReverseTrapWeight + pong_trap_weight: PongTrapWeight + minigame_trap_difficulty: MinigameTrapDifficulty + + sadx_music: SADXMusic + music_shuffle: MusicShuffle + voice_shuffle: VoiceShuffle + narrator: Narrator + + speed_mission_count: SpeedMissionCount + speed_mission_2: SpeedMission2 + speed_mission_3: SpeedMission3 + speed_mission_4: SpeedMission4 + speed_mission_5: SpeedMission5 + + mech_mission_count: MechMissionCount + mech_mission_2: MechMission2 + mech_mission_3: MechMission3 + mech_mission_4: MechMission4 + mech_mission_5: MechMission5 + + hunt_mission_count: HuntMissionCount + hunt_mission_2: HuntMission2 + hunt_mission_3: HuntMission3 + hunt_mission_4: HuntMission4 + hunt_mission_5: HuntMission5 + + kart_mission_count: KartMissionCount + kart_mission_2: KartMission2 + kart_mission_3: KartMission3 + kart_mission_4: KartMission4 + kart_mission_5: KartMission5 + + cannons_core_mission_count: CannonsCoreMissionCount + cannons_core_mission_2: CannonsCoreMission2 + cannons_core_mission_3: CannonsCoreMission3 + cannons_core_mission_4: CannonsCoreMission4 + cannons_core_mission_5: CannonsCoreMission5 + + ring_link: RingLink + death_link: DeathLink diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py index 7d77aebc4caf..f7d1ca72b09f 100644 --- a/worlds/sa2b/__init__.py +++ b/worlds/sa2b/__init__.py @@ -3,20 +3,20 @@ import logging from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from worlds.AutoWorld import WebWorld, World + +from .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \ + all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits +from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name from .Items import SA2BItem, ItemData, item_table, upgrades_table, emeralds_table, junk_table, trap_table, item_groups, \ eggs_table, fruits_table, seeds_table, hats_table, animals_table, chaos_drives_table from .Locations import SA2BLocation, all_locations, setup_locations, chao_animal_event_location_table, black_market_location_table -from .Options import sa2b_options +from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions +from .Names import ItemName, LocationName +from .Options import SA2BOptions, sa2b_option_groups from .Regions import create_regions, shuffleable_regions, connect_regions, LevelGate, gate_0_whitelist_regions, \ gate_0_blacklist_regions from .Rules import set_rules -from .Names import ItemName, LocationName -from .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \ - all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits -from worlds.AutoWorld import WebWorld, World -from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name -from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions -import Patch class SA2BWeb(WebWorld): @@ -30,8 +30,9 @@ class SA2BWeb(WebWorld): "setup/en", ["RaspberrySpaceJam", "PoryGone", "Entiss"] ) - + tutorials = [setup_en] + option_groups = sa2b_option_groups def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range: int, multiworld: MultiWorld): @@ -54,9 +55,9 @@ class SA2BWorld(World): Sonic Adventure 2 Battle is an action platforming game. Play as Sonic, Tails, Knuckles, Shadow, Rouge, and Eggman across 31 stages and prevent the destruction of the earth. """ game: str = "Sonic Adventure 2 Battle" - option_definitions = sa2b_options + options_dataclass = SA2BOptions + options: SA2BOptions topology_present = False - data_version = 7 item_name_groups = item_groups item_name_to_id = {name: data.code for name, data in item_table.items()} diff --git a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md index 12ccc50ccd2c..e2f732ffe585 100644 --- a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md +++ b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md @@ -1,8 +1,8 @@ # Sonic Adventure 2: Battle -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/sa2b/docs/setup_en.md b/worlds/sa2b/docs/setup_en.md index 2ac00a3fb834..f32001a67827 100644 --- a/worlds/sa2b/docs/setup_en.md +++ b/worlds/sa2b/docs/setup_en.md @@ -4,8 +4,8 @@ - Sonic Adventure 2: Battle from: [Sonic Adventure 2: Battle Steam Store Page](https://store.steampowered.com/app/213610/Sonic_Adventure_2/) - The Battle DLC is required if you choose to add Chao Karate locations to the randomizer -- Sonic Adventure 2 Mod Loader from: [Sonic Retro Mod Loader Page](http://info.sonicretro.org/SA2_Mod_Loader) -- Microsoft Visual C++ 2013 from: [Microsoft Visual C++ 2013 Redistributable Page](https://www.microsoft.com/en-us/download/details.aspx?id=40784) +- SA Mod Manager from: [SA Mod Manager GitHub Releases Page](https://github.com/X-Hax/SA-Mod-Manager/releases) +- .NET Desktop Runtime 7.0 from: [.NET Desktop Runtime 7.0 Download Page](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.9-windows-x64-installer) - Archipelago Mod for Sonic Adventure 2: Battle from: [Sonic Adventure 2: Battle Archipelago Randomizer Mod Releases Page](https://github.com/PoryGone/SA2B_Archipelago/releases/) @@ -15,6 +15,8 @@ - Sonic Adventure 2: Battle Archipelago PopTracker pack from: [SA2B AP Tracker Releases Page](https://github.com/PoryGone/SA2B_AP_Tracker/releases/) - Quality of life mods - SA2 Volume Controls from: [SA2 Volume Controls Release Page] (https://gamebanana.com/mods/381193) +- Sonic Adventure DX from: [Sonic Adventure DX Steam Store Page](https://store.steampowered.com/app/71250/Sonic_Adventure_DX/) + - For setting up the `SADX Music` option (See Additional Options for instructions). ## Installation Procedures (Windows) @@ -22,15 +24,13 @@ 2. Launch the game at least once without mods. -3. Install Sonic Adventure 2 Mod Loader as per its instructions. +3. Install SA Mod Manager as per [its instructions](https://github.com/X-Hax/SA-Mod-Manager/tree/master?tab=readme-ov-file). -4. The folder you installed the Sonic Adventure 2 Mod Loader into will now have a `/mods` directory. +4. Unpack the Archipelago Mod into the `/mods` directory in the folder into which you installed Sonic Adventure 2: Battle, so that `/mods/SA2B_Archipelago` is a valid path. -5. Unpack the Archipelago Mod into this folder, so that `/mods/SA2B_Archipelago` is a valid path. +5. In the SA2B_Archipelago folder, run the `CopyAPCppDLL.bat` script (a window will very quickly pop up and go away). -6. In the SA2B_Archipelago folder, run the `CopyAPCppDLL.bat` script (a window will very quickly pop up and go away). - -7. Launch the `SA2ModManager.exe` and make sure the SA2B_Archipelago mod is listed and enabled. +6. Launch the `SAModManager.exe` and make sure the SA2B_Archipelago mod is listed and enabled. ## Installation Procedures (Linux and Steam Deck) @@ -40,21 +40,29 @@ 3. Launch the game at least once without mods. -4. Install Sonic Adventure 2 Mod Loader as per its instructions. To launch it, add ``SA2ModManager.exe`` as a non-Steam game. In the properties on Steam for Sonic Adventure 2 Mod Loader, set it to use Proton as the compatibility tool. +4. Create both a `/mods` directory and a `/SAManager` directory in the folder into which you installed Sonic Adventure 2: Battle. + +5. Install SA Mod Manager as per [its instructions](https://github.com/X-Hax/SA-Mod-Manager/tree/master?tab=readme-ov-file). Specifically, extract SAModManager.exe file to the folder that Sonic Adventure 2: Battle is installed to. To launch it, add ``SAModManager.exe`` as a non-Steam game. In the properties on Steam for SA Mod Manager, set it to use Proton as the compatibility tool. + +6. Run SAModManager.exe from Steam once. It should produce an error popup for a missing dependency, close the error. + +7. Install protontricks, on the Steam Deck this can be done via the Discover store, on other distros instructions vary, [see its github page](https://github.com/Matoking/protontricks). + +8. Download the [.NET 7 Desktop Runtime for x64 Windows](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.17-windows-x64-installer). If this link does not work, the download can be found on [this page](https://dotnet.microsoft.com/en-us/download/dotnet/7.0). -5. The folder you installed the Sonic Adventure 2 Mod Loader into will now have a `/mods` directory. +9. Right click the .NET 7 Desktop Runtime exe, and assuming protontricks was installed correctly, the option to "Open with Protontricks Launcher" should be available. Click that, and in the popup window that opens, select SAModManager.exe. Follow the prompts after this to install the .NET 7 Desktop Runtime for SAModManager. Once it is done, you should be able to successfully launch SAModManager to steam. 6. Unpack the Archipelago Mod into this folder, so that `/mods/SA2B_Archipelago` is a valid path. -7. In the SA2B_Archipelago folder, copy the `APCpp.dll` file and paste it in the Sonic Adventure 2 install folder (where `SA2ModManager.exe` is). +7. In the SA2B_Archipelago folder, copy the `APCpp.dll` file and paste it in the Sonic Adventure 2 install folder (where `sonic2app.exe` is). -8. Launch the `SA2ModManager.exe` from Steam and make sure the SA2B_Archipelago mod is listed and enabled. +8. Launch `SAModManager.exe` from Steam and make sure the SA2B_Archipelago mod is listed and enabled. -Note: Ensure that you launch Sonic Adventure 2 from Steam directly on Linux, rather than launching using the `Save & Play` button in Sonic Adventure 2 Mod Loader. +Note: Ensure that you launch Sonic Adventure 2 from Steam directly on Linux, rather than launching using the `Save & Play` button in SA Mod Manager. ## Joining a MultiWorld Game -1. Before launching the game, run the `SA2ModManager.exe`, select the SA2B_Archipelago mod, and hit the `Configure...` button. +1. Before launching the game, run the `SAModManager.exe`, select the SA2B_Archipelago mod, and hit the `Configure Mod` button. 2. For the `Server IP` field under `AP Settings`, enter the address of the server, such as archipelago.gg:38281, your server host should be able to tell you this. @@ -68,7 +76,7 @@ Note: Ensure that you launch Sonic Adventure 2 from Steam directly on Linux, rat ## Additional Options -Some additional settings related to the Archipelago messages in game can be adjusted in the SA2ModManager if you select `Configure...` on the SA2B_Archipelago mod. This settings will be under a `General Settings` tab. +Some additional settings related to the Archipelago messages in game can be adjusted in the SAModManager if you select `Configure Mod` on the SA2B_Archipelago mod. This settings will be under a `General Settings` tab. - Message Display Count: This is the maximum number of Archipelago messages that can be displayed on screen at any given time. - Message Display Duration: This dictates how long Archipelago messages are displayed on screen (in seconds). @@ -92,9 +100,9 @@ If you wish to use the `SADX Music` option of the Randomizer, you must own a cop - Game is running too fast (Like Sonic). - Limit framerate using the mod manager: - 1. Launch `SA2ModManager.exe`. - 2. Select the `Graphics` tab. - 3. Check the `Lock framerate` box under the Visuals section. + 1. Launch `SAModManager.exe`. + 2. Select the `Game Config` tab, then select the `Patches` subtab. + 3. Check the `Lock framerate` box under the Patches section. 4. Press the `Save` button. - If using an NVidia graphics card: 1. Open the NVIDIA Control Panel. @@ -105,7 +113,7 @@ If you wish to use the `SADX Music` option of the Randomizer, you must own a cop 6. Choose the `On` radial option and in the input box next to the slide enter a value of 60 (or 59 if 60 causes the game to crash). - Controller input is not working. - 1. Run the Launcher.exe which should be in the same folder as the SA2ModManager. + 1. Run the Launcher.exe which should be in the same folder as the your Sonic Adventure 2: Battle install. 2. Select the `Player` tab and reselect the controller for the player 1 input method. 3. Click the `Save settings and launch SONIC ADVENTURE 2` button. (Any mod manager settings will apply even if the game is launched this way rather than through the mod manager) @@ -125,7 +133,7 @@ If you wish to use the `SADX Music` option of the Randomizer, you must own a cop - If you enabled an `SADX Music` option, then most likely the music data was not copied properly into the mod folder (See Additional Options for instructions). - Mission 1 is missing a texture in the stage select UI. - - Most likely another mod is conflicting and overwriting the texture pack. It is recommeded to have the SA2B Archipelago mod load last in the mod loader. + - Most likely another mod is conflicting and overwriting the texture pack. It is recommeded to have the SA2B Archipelago mod load last in the mod manager. ## Save File Safeguard (Advanced Option) diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py new file mode 100644 index 000000000000..bb325ba1da45 --- /dev/null +++ b/worlds/sc2/Client.py @@ -0,0 +1,1630 @@ +from __future__ import annotations + +import asyncio +import copy +import ctypes +import enum +import inspect +import logging +import multiprocessing +import os.path +import re +import sys +import tempfile +import typing +import queue +import zipfile +import io +import random +import concurrent.futures +from pathlib import Path + +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser +from Utils import init_logging, is_windows, async_start +from . import ItemNames, Options +from .ItemGroups import item_name_groups +from .Options import ( + MissionOrder, KerriganPrimalStatus, kerrigan_unit_available, KerriganPresence, + GameSpeed, GenericUpgradeItems, GenericUpgradeResearch, ColorChoice, GenericUpgradeMissions, + LocationInclusion, ExtraLocations, MasteryLocations, ChallengeLocations, VanillaLocations, + DisableForcedCamera, SkipCutscenes, GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, RequiredTactics, + SpearOfAdunPresence, SpearOfAdunPresentInNoBuild, SpearOfAdunAutonomouslyCastAbilityPresence, + SpearOfAdunAutonomouslyCastPresentInNoBuild +) + + +if __name__ == "__main__": + init_logging("SC2Client", exception_logger="Client") + +logger = logging.getLogger("Client") +sc2_logger = logging.getLogger("Starcraft2") + +import nest_asyncio +from worlds._sc2common import bot +from worlds._sc2common.bot.data import Race +from worlds._sc2common.bot.main import run_game +from worlds._sc2common.bot.player import Bot +from .Items import (lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, + upgrade_numbers_all) +from .Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET +from .MissionTables import (lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, + lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race) +from .Regions import MissionInfo + +import colorama +from Options import Option +from NetUtils import ClientStatus, NetworkItem, JSONtoTextParser, JSONMessagePart, add_json_item, add_json_location, add_json_text, JSONTypes +from MultiServer import mark_raw + +pool = concurrent.futures.ThreadPoolExecutor(1) +loop = asyncio.get_event_loop_policy().new_event_loop() +nest_asyncio.apply(loop) +MAX_BONUS: int = 28 +VICTORY_MODULO: int = 100 + +# GitHub repo where the Map/mod data is hosted for /download_data command +DATA_REPO_OWNER = "Ziktofel" +DATA_REPO_NAME = "Archipelago-SC2-data" +DATA_API_VERSION = "API3" + +# Bot controller +CONTROLLER_HEALTH: int = 38281 +CONTROLLER2_HEALTH: int = 38282 + +# Games +STARCRAFT2 = "Starcraft 2" +STARCRAFT2_WOL = "Starcraft 2 Wings of Liberty" + + +# Data version file path. +# This file is used to tell if the downloaded data are outdated +# Associated with /download_data command +def get_metadata_file() -> str: + return os.environ["SC2PATH"] + os.sep + "ArchipelagoSC2Metadata.txt" + + +class ConfigurableOptionType(enum.Enum): + INTEGER = enum.auto() + ENUM = enum.auto() + +class ConfigurableOptionInfo(typing.NamedTuple): + name: str + variable_name: str + option_class: typing.Type[Option] + option_type: ConfigurableOptionType = ConfigurableOptionType.ENUM + can_break_logic: bool = False + + +class ColouredMessage: + def __init__(self, text: str = '') -> None: + self.parts: typing.List[dict] = [] + if text: + self(text) + def __call__(self, text: str) -> 'ColouredMessage': + add_json_text(self.parts, text) + return self + def coloured(self, text: str, colour: str) -> 'ColouredMessage': + add_json_text(self.parts, text, type="color", color=colour) + return self + def location(self, location_id: int, player_id: int) -> 'ColouredMessage': + add_json_location(self.parts, location_id, player_id) + return self + def item(self, item_id: int, player_id: int, flags: int = 0) -> 'ColouredMessage': + add_json_item(self.parts, item_id, player_id, flags) + return self + def player(self, player_id: int) -> 'ColouredMessage': + add_json_text(self.parts, str(player_id), type=JSONTypes.player_id) + return self + def send(self, ctx: SC2Context) -> None: + ctx.on_print_json({"data": self.parts, "cmd": "PrintJSON"}) + + +class StarcraftClientProcessor(ClientCommandProcessor): + ctx: SC2Context + + def formatted_print(self, text: str) -> None: + """Prints with kivy formatting to the GUI, and also prints to command-line and to all logs""" + # Note(mm): Bold/underline can help readability, but unfortunately the CommonClient does not filter bold tags from command-line output. + # Regardless, using `on_print_json` to get formatted text in the GUI and output in the command-line and in the logs, + # without having to branch code from CommonClient + self.ctx.on_print_json({"data": [{"text": text}]}) + + def _cmd_difficulty(self, difficulty: str = "") -> bool: + """Overrides the current difficulty set for the world. Takes the argument casual, normal, hard, or brutal""" + options = difficulty.split() + num_options = len(options) + + if num_options > 0: + difficulty_choice = options[0].lower() + if difficulty_choice == "casual": + self.ctx.difficulty_override = 0 + elif difficulty_choice == "normal": + self.ctx.difficulty_override = 1 + elif difficulty_choice == "hard": + self.ctx.difficulty_override = 2 + elif difficulty_choice == "brutal": + self.ctx.difficulty_override = 3 + else: + self.output("Unable to parse difficulty '" + options[0] + "'") + return False + + self.output("Difficulty set to " + options[0]) + return True + + else: + if self.ctx.difficulty == -1: + self.output("Please connect to a seed before checking difficulty.") + else: + current_difficulty = self.ctx.difficulty + if self.ctx.difficulty_override >= 0: + current_difficulty = self.ctx.difficulty_override + self.output("Current difficulty: " + ["Casual", "Normal", "Hard", "Brutal"][current_difficulty]) + self.output("To change the difficulty, add the name of the difficulty after the command.") + return False + + + def _cmd_game_speed(self, game_speed: str = "") -> bool: + """Overrides the current game speed for the world. + Takes the arguments default, slower, slow, normal, fast, faster""" + options = game_speed.split() + num_options = len(options) + + if num_options > 0: + speed_choice = options[0].lower() + if speed_choice == "default": + self.ctx.game_speed_override = 0 + elif speed_choice == "slower": + self.ctx.game_speed_override = 1 + elif speed_choice == "slow": + self.ctx.game_speed_override = 2 + elif speed_choice == "normal": + self.ctx.game_speed_override = 3 + elif speed_choice == "fast": + self.ctx.game_speed_override = 4 + elif speed_choice == "faster": + self.ctx.game_speed_override = 5 + else: + self.output("Unable to parse game speed '" + options[0] + "'") + return False + + self.output("Game speed set to " + options[0]) + return True + + else: + if self.ctx.game_speed == -1: + self.output("Please connect to a seed before checking game speed.") + else: + current_speed = self.ctx.game_speed + if self.ctx.game_speed_override >= 0: + current_speed = self.ctx.game_speed_override + self.output("Current game speed: " + + ["Default", "Slower", "Slow", "Normal", "Fast", "Faster"][current_speed]) + self.output("To change the game speed, add the name of the speed after the command," + " or Default to select based on difficulty.") + return False + + @mark_raw + def _cmd_received(self, filter_search: str = "") -> bool: + """List received items. + Pass in a parameter to filter the search by partial item name or exact item group.""" + # Groups must be matched case-sensitively, so we properly capitalize the search term + # eg. "Spear of Adun" over "Spear Of Adun" or "spear of adun" + # This fails a lot of item name matches, but those should be found by partial name match + formatted_filter_search = " ".join([(part.lower() if len(part) <= 3 else part.lower().capitalize()) for part in filter_search.split()]) + + def item_matches_filter(item_name: str) -> bool: + # The filter can be an exact group name or a partial item name + # Partial item name can be matched case-insensitively + if filter_search.lower() in item_name.lower(): + return True + # The search term should already be formatted as a group name + if formatted_filter_search in item_name_groups and item_name in item_name_groups[formatted_filter_search]: + return True + return False + + items = get_full_item_list() + categorized_items: typing.Dict[SC2Race, typing.List[int]] = {} + parent_to_child: typing.Dict[int, typing.List[int]] = {} + items_received: typing.Dict[int, typing.List[NetworkItem]] = {} + filter_match_count = 0 + for item in self.ctx.items_received: + items_received.setdefault(item.item, []).append(item) + items_received_set = set(items_received) + for item_data in items.values(): + if item_data.parent_item: + parent_to_child.setdefault(items[item_data.parent_item].code, []).append(item_data.code) + else: + categorized_items.setdefault(item_data.race, []).append(item_data.code) + for faction in SC2Race: + has_printed_faction_title = False + def print_faction_title(): + if not has_printed_faction_title: + self.formatted_print(f" [u]{faction.name}[/u] ") + + for item_id in categorized_items[faction]: + item_name = self.ctx.item_names.lookup_in_game(item_id) + received_child_items = items_received_set.intersection(parent_to_child.get(item_id, [])) + matching_children = [child for child in received_child_items + if item_matches_filter(self.ctx.item_names.lookup_in_game(child))] + received_items_of_this_type = items_received.get(item_id, []) + item_is_match = item_matches_filter(item_name) + if item_is_match or len(matching_children) > 0: + # Print found item if it or its children match the filter + if item_is_match: + filter_match_count += len(received_items_of_this_type) + for item in received_items_of_this_type: + print_faction_title() + has_printed_faction_title = True + (ColouredMessage('* ').item(item.item, self.ctx.slot, flags=item.flags) + (" from ").location(item.location, self.ctx.slot) + (" by ").player(item.player) + ).send(self.ctx) + + if received_child_items: + # We have this item's children + if len(matching_children) == 0: + # ...but none of them match the filter + continue + + if not received_items_of_this_type: + # We didn't receive the item itself + print_faction_title() + has_printed_faction_title = True + ColouredMessage("- ").coloured(item_name, "black")(" - not obtained").send(self.ctx) + + for child_item in matching_children: + received_items_of_this_type = items_received.get(child_item, []) + for item in received_items_of_this_type: + filter_match_count += len(received_items_of_this_type) + (ColouredMessage(' * ').item(item.item, self.ctx.slot, flags=item.flags) + (" from ").location(item.location, self.ctx.slot) + (" by ").player(item.player) + ).send(self.ctx) + + non_matching_children = len(received_child_items) - len(matching_children) + if non_matching_children > 0: + self.formatted_print(f" + {non_matching_children} child items that don't match the filter") + if filter_search == "": + self.formatted_print(f"[b]Obtained: {len(self.ctx.items_received)} items[/b]") + else: + self.formatted_print(f"[b]Filter \"{filter_search}\" found {filter_match_count} out of {len(self.ctx.items_received)} obtained items[/b]") + return True + + def _cmd_option(self, option_name: str = "", option_value: str = "") -> None: + """Sets a Starcraft game option that can be changed after generation. Use "/option list" to see all options.""" + + LOGIC_WARNING = f" *Note changing this may result in logically unbeatable games*\n" + + options = ( + ConfigurableOptionInfo('kerrigan_presence', 'kerrigan_presence', Options.KerriganPresence, can_break_logic=True), + ConfigurableOptionInfo('soa_presence', 'spear_of_adun_presence', Options.SpearOfAdunPresence, can_break_logic=True), + ConfigurableOptionInfo('soa_in_nobuilds', 'spear_of_adun_present_in_no_build', Options.SpearOfAdunPresentInNoBuild, can_break_logic=True), + ConfigurableOptionInfo('control_ally', 'take_over_ai_allies', Options.TakeOverAIAllies, can_break_logic=True), + ConfigurableOptionInfo('minerals_per_item', 'minerals_per_item', Options.MineralsPerItem, ConfigurableOptionType.INTEGER), + ConfigurableOptionInfo('gas_per_item', 'vespene_per_item', Options.VespenePerItem, ConfigurableOptionType.INTEGER), + ConfigurableOptionInfo('supply_per_item', 'starting_supply_per_item', Options.StartingSupplyPerItem, ConfigurableOptionType.INTEGER), + ConfigurableOptionInfo('no_forced_camera', 'disable_forced_camera', Options.DisableForcedCamera), + ConfigurableOptionInfo('skip_cutscenes', 'skip_cutscenes', Options.SkipCutscenes), + ) + + WARNING_COLOUR = "salmon" + CMD_COLOUR = "slateblue" + boolean_option_map = { + 'y': 'true', 'yes': 'true', 'n': 'false', 'no': 'false', + } + + help_message = ColouredMessage(inspect.cleandoc(""" + Options + -------------------- + """))('\n') + for option in options: + option_help_text = inspect.cleandoc(option.option_class.__doc__ or "No description provided.").split('\n', 1)[0] + help_message.coloured(option.name, CMD_COLOUR)(": " + " | ".join(option.option_class.options) + + f" -- {option_help_text}\n") + if option.can_break_logic: + help_message.coloured(LOGIC_WARNING, WARNING_COLOUR) + help_message("--------------------\nEnter an option without arguments to see its current value.\n") + + if not option_name or option_name == 'list' or option_name == 'help': + help_message.send(self.ctx) + return + for option in options: + if option_name == option.name: + option_value = boolean_option_map.get(option_value, option_value) + if not option_value: + pass + elif option.option_type == ConfigurableOptionType.ENUM and option_value in option.option_class.options: + self.ctx.__dict__[option.variable_name] = option.option_class.options[option_value] + elif option.option_type == ConfigurableOptionType.INTEGER: + try: + self.ctx.__dict__[option.variable_name] = int(option_value, base=0) + except: + self.output(f"{option_value} is not a valid integer") + else: + self.output(f"Unknown option value '{option_value}'") + ColouredMessage(f"{option.name} is '{option.option_class.get_option_name(self.ctx.__dict__[option.variable_name])}'").send(self.ctx) + break + else: + self.output(f"Unknown option '{option_name}'") + help_message.send(self.ctx) + + def _cmd_color(self, faction: str = "", color: str = "") -> None: + """Changes the player color for a given faction.""" + player_colors = [ + "White", "Red", "Blue", "Teal", + "Purple", "Yellow", "Orange", "Green", + "LightPink", "Violet", "LightGrey", "DarkGreen", + "Brown", "LightGreen", "DarkGrey", "Pink", + "Rainbow", "Random", "Default" + ] + var_names = { + 'raynor': 'player_color_raynor', + 'kerrigan': 'player_color_zerg', + 'primal': 'player_color_zerg_primal', + 'protoss': 'player_color_protoss', + 'nova': 'player_color_nova', + } + faction = faction.lower() + if not faction: + for faction_name, key in var_names.items(): + self.output(f"Current player color for {faction_name}: {player_colors[self.ctx.__dict__[key]]}") + self.output("To change your color, add the faction name and color after the command.") + self.output("Available factions: " + ', '.join(var_names)) + self.output("Available colors: " + ', '.join(player_colors)) + return + elif faction not in var_names: + self.output(f"Unknown faction '{faction}'.") + self.output("Available factions: " + ', '.join(var_names)) + return + match_colors = [player_color.lower() for player_color in player_colors] + if not color: + self.output(f"Current player color for {faction}: {player_colors[self.ctx.__dict__[var_names[faction]]]}") + self.output("To change this faction's colors, add the name of the color after the command.") + self.output("Available colors: " + ', '.join(player_colors)) + else: + if color.lower() not in match_colors: + self.output(color + " is not a valid color. Available colors: " + ', '.join(player_colors)) + return + if color.lower() == "random": + color = random.choice(player_colors[:16]) + self.ctx.__dict__[var_names[faction]] = match_colors.index(color.lower()) + self.ctx.pending_color_update = True + self.output(f"Color for {faction} set to " + player_colors[self.ctx.__dict__[var_names[faction]]]) + + def _cmd_disable_mission_check(self) -> bool: + """Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play + the next mission in a chain the other player is doing.""" + self.ctx.missions_unlocked = True + sc2_logger.info("Mission check has been disabled") + return True + + def _cmd_play(self, mission_id: str = "") -> bool: + """Start a Starcraft 2 mission""" + + options = mission_id.split() + num_options = len(options) + + if num_options > 0: + mission_number = int(options[0]) + + self.ctx.play_mission(mission_number) + + else: + sc2_logger.info( + "Mission ID needs to be specified. Use /unfinished or /available to view ids for available missions.") + return False + + return True + + def _cmd_available(self) -> bool: + """Get what missions are currently available to play""" + + request_available_missions(self.ctx) + return True + + def _cmd_unfinished(self) -> bool: + """Get what missions are currently available to play and have not had all locations checked""" + + request_unfinished_missions(self.ctx) + return True + + @mark_raw + def _cmd_set_path(self, path: str = '') -> bool: + """Manually set the SC2 install directory (if the automatic detection fails).""" + if path: + os.environ["SC2PATH"] = path + is_mod_installed_correctly() + return True + else: + sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.") + return False + + def _cmd_download_data(self) -> bool: + """Download the most recent release of the necessary files for playing SC2 with + Archipelago. Will overwrite existing files.""" + pool.submit(self._download_data) + return True + + @staticmethod + def _download_data() -> bool: + if "SC2PATH" not in os.environ: + check_game_install_path() + + if os.path.exists(get_metadata_file()): + with open(get_metadata_file(), "r") as f: + metadata = f.read() + else: + metadata = None + + tempzip, metadata = download_latest_release_zip( + DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, metadata=metadata, force_download=True) + + if tempzip: + try: + zipfile.ZipFile(tempzip).extractall(path=os.environ["SC2PATH"]) + sc2_logger.info(f"Download complete. Package installed.") + if metadata is not None: + with open(get_metadata_file(), "w") as f: + f.write(metadata) + finally: + os.remove(tempzip) + else: + sc2_logger.warning("Download aborted/failed. Read the log for more information.") + return False + return True + + +class SC2JSONtoTextParser(JSONtoTextParser): + def __init__(self, ctx) -> None: + self.handlers = { + "ItemSend": self._handle_color, + "ItemCheat": self._handle_color, + "Hint": self._handle_color, + } + super().__init__(ctx) + + def _handle_color(self, node: JSONMessagePart) -> str: + codes = node["color"].split(";") + buffer = "".join(self.color_code(code) for code in codes if code in self.color_codes) + return buffer + self._handle_text(node) + '' + + def color_code(self, code: str) -> str: + return '' + + +class SC2Context(CommonContext): + command_processor = StarcraftClientProcessor + game = STARCRAFT2 + items_handling = 0b111 + + def __init__(self, *args, **kwargs) -> None: + super(SC2Context, self).__init__(*args, **kwargs) + self.raw_text_parser = SC2JSONtoTextParser(self) + + self.difficulty = -1 + self.game_speed = -1 + self.disable_forced_camera = 0 + self.skip_cutscenes = 0 + self.all_in_choice = 0 + self.mission_order = 0 + self.player_color_raynor = ColorChoice.option_blue + self.player_color_zerg = ColorChoice.option_orange + self.player_color_zerg_primal = ColorChoice.option_purple + self.player_color_protoss = ColorChoice.option_blue + self.player_color_nova = ColorChoice.option_dark_grey + self.pending_color_update = False + self.kerrigan_presence = 0 + self.kerrigan_primal_status = 0 + self.levels_per_check = 0 + self.checks_per_level = 1 + self.mission_req_table: typing.Dict[SC2Campaign, typing.Dict[str, MissionInfo]] = {} + self.final_mission: int = 29 + self.announcements: queue.Queue = queue.Queue() + self.sc2_run_task: typing.Optional[asyncio.Task] = None + self.missions_unlocked: bool = False # allow launching missions ignoring requirements + self.generic_upgrade_missions = 0 + self.generic_upgrade_research = 0 + self.generic_upgrade_items = 0 + self.location_inclusions: typing.Dict[LocationType, int] = {} + self.plando_locations: typing.List[str] = [] + self.current_tooltip = None + self.last_loc_list = None + self.difficulty_override = -1 + self.game_speed_override = -1 + self.mission_id_to_location_ids: typing.Dict[int, typing.List[int]] = {} + self.last_bot: typing.Optional[ArchipelagoBot] = None + self.slot_data_version = 2 + self.grant_story_tech = 0 + self.required_tactics = RequiredTactics.option_standard + self.take_over_ai_allies = TakeOverAIAllies.option_false + self.spear_of_adun_presence = SpearOfAdunPresence.option_not_present + self.spear_of_adun_present_in_no_build = SpearOfAdunPresentInNoBuild.option_false + self.spear_of_adun_autonomously_cast_ability_presence = SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present + self.spear_of_adun_autonomously_cast_present_in_no_build = SpearOfAdunAutonomouslyCastPresentInNoBuild.option_false + self.minerals_per_item = 15 + self.vespene_per_item = 15 + self.starting_supply_per_item = 2 + self.nova_covert_ops_only = False + self.kerrigan_levels_per_mission_completed = 0 + + async def server_auth(self, password_requested: bool = False) -> None: + self.game = STARCRAFT2 + if password_requested and not self.password: + await super(SC2Context, self).server_auth(password_requested) + await self.get_username() + await self.send_connect() + if self.ui: + self.ui.first_check = True + + def is_legacy_game(self): + return self.game == STARCRAFT2_WOL + + def event_invalid_game(self): + if self.is_legacy_game(): + self.game = STARCRAFT2 + super().event_invalid_game() + else: + self.game = STARCRAFT2_WOL + async_start(self.send_connect()) + + def on_package(self, cmd: str, args: dict) -> None: + if cmd == "Connected": + self.difficulty = args["slot_data"]["game_difficulty"] + self.game_speed = args["slot_data"].get("game_speed", GameSpeed.option_default) + self.disable_forced_camera = args["slot_data"].get("disable_forced_camera", DisableForcedCamera.default) + self.skip_cutscenes = args["slot_data"].get("skip_cutscenes", SkipCutscenes.default) + self.all_in_choice = args["slot_data"]["all_in_map"] + self.slot_data_version = args["slot_data"].get("version", 2) + slot_req_table: dict = args["slot_data"]["mission_req"] + + first_item = list(slot_req_table.keys())[0] + # Maintaining backwards compatibility with older slot data + if first_item in [str(campaign.id) for campaign in SC2Campaign]: + # Multi-campaign + self.mission_req_table = {} + for campaign_id in slot_req_table: + campaign = lookup_id_to_campaign[int(campaign_id)] + self.mission_req_table[campaign] = { + mission: self.parse_mission_info(mission_info) + for mission, mission_info in slot_req_table[campaign_id].items() + } + else: + # Old format + self.mission_req_table = {SC2Campaign.GLOBAL: { + mission: self.parse_mission_info(mission_info) + for mission, mission_info in slot_req_table.items() + } + } + + self.mission_order = args["slot_data"].get("mission_order", MissionOrder.option_vanilla) + self.final_mission = args["slot_data"].get("final_mission", SC2Mission.ALL_IN.id) + self.player_color_raynor = args["slot_data"].get("player_color_terran_raynor", ColorChoice.option_blue) + self.player_color_zerg = args["slot_data"].get("player_color_zerg", ColorChoice.option_orange) + self.player_color_zerg_primal = args["slot_data"].get("player_color_zerg_primal", ColorChoice.option_purple) + self.player_color_protoss = args["slot_data"].get("player_color_protoss", ColorChoice.option_blue) + self.player_color_nova = args["slot_data"].get("player_color_nova", ColorChoice.option_dark_grey) + self.generic_upgrade_missions = args["slot_data"].get("generic_upgrade_missions", GenericUpgradeMissions.default) + self.generic_upgrade_items = args["slot_data"].get("generic_upgrade_items", GenericUpgradeItems.option_individual_items) + self.generic_upgrade_research = args["slot_data"].get("generic_upgrade_research", GenericUpgradeResearch.option_vanilla) + self.kerrigan_presence = args["slot_data"].get("kerrigan_presence", KerriganPresence.option_vanilla) + self.kerrigan_primal_status = args["slot_data"].get("kerrigan_primal_status", KerriganPrimalStatus.option_vanilla) + self.kerrigan_levels_per_mission_completed = args["slot_data"].get("kerrigan_levels_per_mission_completed", 0) + self.kerrigan_levels_per_mission_completed_cap = args["slot_data"].get("kerrigan_levels_per_mission_completed_cap", -1) + self.kerrigan_total_level_cap = args["slot_data"].get("kerrigan_total_level_cap", -1) + self.grant_story_tech = args["slot_data"].get("grant_story_tech", GrantStoryTech.option_false) + self.grant_story_levels = args["slot_data"].get("grant_story_levels", GrantStoryLevels.option_additive) + self.required_tactics = args["slot_data"].get("required_tactics", RequiredTactics.option_standard) + self.take_over_ai_allies = args["slot_data"].get("take_over_ai_allies", TakeOverAIAllies.option_false) + self.spear_of_adun_presence = args["slot_data"].get("spear_of_adun_presence", SpearOfAdunPresence.option_not_present) + self.spear_of_adun_present_in_no_build = args["slot_data"].get("spear_of_adun_present_in_no_build", SpearOfAdunPresentInNoBuild.option_false) + self.spear_of_adun_autonomously_cast_ability_presence = args["slot_data"].get("spear_of_adun_autonomously_cast_ability_presence", SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present) + self.spear_of_adun_autonomously_cast_present_in_no_build = args["slot_data"].get("spear_of_adun_autonomously_cast_present_in_no_build", SpearOfAdunAutonomouslyCastPresentInNoBuild.option_false) + self.minerals_per_item = args["slot_data"].get("minerals_per_item", 15) + self.vespene_per_item = args["slot_data"].get("vespene_per_item", 15) + self.starting_supply_per_item = args["slot_data"].get("starting_supply_per_item", 2) + self.nova_covert_ops_only = args["slot_data"].get("nova_covert_ops_only", False) + + if self.required_tactics == RequiredTactics.option_no_logic: + # Locking Grant Story Tech/Levels if no logic + self.grant_story_tech = GrantStoryTech.option_true + self.grant_story_levels = GrantStoryLevels.option_minimum + + self.location_inclusions = { + LocationType.VICTORY: LocationInclusion.option_enabled, # Victory checks are always enabled + LocationType.VANILLA: args["slot_data"].get("vanilla_locations", VanillaLocations.default), + LocationType.EXTRA: args["slot_data"].get("extra_locations", ExtraLocations.default), + LocationType.CHALLENGE: args["slot_data"].get("challenge_locations", ChallengeLocations.default), + LocationType.MASTERY: args["slot_data"].get("mastery_locations", MasteryLocations.default), + } + self.plando_locations = args["slot_data"].get("plando_locations", []) + + self.build_location_to_mission_mapping() + + # Looks for the required maps and mods for SC2. Runs check_game_install_path. + maps_present = is_mod_installed_correctly() + if os.path.exists(get_metadata_file()): + with open(get_metadata_file(), "r") as f: + current_ver = f.read() + sc2_logger.debug(f"Current version: {current_ver}") + if is_mod_update_available(DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, current_ver): + sc2_logger.info("NOTICE: Update for required files found. Run /download_data to install.") + elif maps_present: + sc2_logger.warning("NOTICE: Your map files may be outdated (version number not found). " + "Run /download_data to update them.") + + @staticmethod + def parse_mission_info(mission_info: dict[str, typing.Any]) -> MissionInfo: + if mission_info.get("id") is not None: + mission_info["mission"] = lookup_id_to_mission[mission_info["id"]] + elif isinstance(mission_info["mission"], int): + mission_info["mission"] = lookup_id_to_mission[mission_info["mission"]] + + return MissionInfo( + **{field: value for field, value in mission_info.items() if field in MissionInfo._fields} + ) + + def find_campaign(self, mission_name: str) -> SC2Campaign: + data = self.mission_req_table + for campaign in data.keys(): + if mission_name in data[campaign].keys(): + return campaign + sc2_logger.info(f"Attempted to find campaign of unknown mission '{mission_name}'; defaulting to GLOBAL") + return SC2Campaign.GLOBAL + + + + def on_print_json(self, args: dict) -> None: + # goes to this world + if "receiving" in args and self.slot_concerns_self(args["receiving"]): + relevant = True + # found in this world + elif "item" in args and self.slot_concerns_self(args["item"].player): + relevant = True + # not related + else: + relevant = False + + if relevant: + self.announcements.put(self.raw_text_parser(copy.deepcopy(args["data"]))) + + super(SC2Context, self).on_print_json(args) + + def run_gui(self) -> None: + from .ClientGui import start_gui + start_gui(self) + + + async def shutdown(self) -> None: + await super(SC2Context, self).shutdown() + if self.last_bot: + self.last_bot.want_close = True + if self.sc2_run_task: + self.sc2_run_task.cancel() + + def play_mission(self, mission_id: int) -> bool: + if self.missions_unlocked or is_mission_available(self, mission_id): + if self.sc2_run_task: + if not self.sc2_run_task.done(): + sc2_logger.warning("Starcraft 2 Client is still running!") + self.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task + if self.slot is None: + sc2_logger.warning("Launching Mission without Archipelago authentication, " + "checks will not be registered to server.") + self.sc2_run_task = asyncio.create_task(starcraft_launch(self, mission_id), + name="Starcraft 2 Launch") + return True + else: + sc2_logger.info( + f"{lookup_id_to_mission[mission_id].mission_name} is not currently unlocked. " + f"Use /unfinished or /available to see what is available.") + return False + + def build_location_to_mission_mapping(self) -> None: + mission_id_to_location_ids: typing.Dict[int, typing.Set[int]] = { + mission_info.mission.id: set() for campaign_mission in self.mission_req_table.values() for mission_info in campaign_mission.values() + } + + for loc in self.server_locations: + offset = SC2WOL_LOC_ID_OFFSET if loc < SC2HOTS_LOC_ID_OFFSET \ + else (SC2HOTS_LOC_ID_OFFSET - SC2Mission.ALL_IN.id * VICTORY_MODULO) + mission_id, objective = divmod(loc - offset, VICTORY_MODULO) + mission_id_to_location_ids[mission_id].add(objective) + self.mission_id_to_location_ids = {mission_id: sorted(objectives) for mission_id, objectives in + mission_id_to_location_ids.items()} + + def locations_for_mission(self, mission_name: str): + mission = lookup_name_to_mission[mission_name] + mission_id: int = mission.id + objectives = self.mission_id_to_location_ids[mission_id] + for objective in objectives: + yield get_location_offset(mission_id) + mission_id * VICTORY_MODULO + objective + + +class CompatItemHolder(typing.NamedTuple): + name: str + quantity: int = 1 + + +async def main(): + multiprocessing.freeze_support() + parser = get_base_parser() + parser.add_argument('--name', default=None, help="Slot Name to connect as.") + args = parser.parse_args() + + ctx = SC2Context(args.connect, args.password) + ctx.auth = args.name + if ctx.server_task is None: + ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") + + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + + await ctx.exit_event.wait() + + await ctx.shutdown() + +# These items must be given to the player if the game is generated on version 2 +API2_TO_API3_COMPAT_ITEMS: typing.Set[CompatItemHolder] = { + CompatItemHolder(ItemNames.PHOTON_CANNON), + CompatItemHolder(ItemNames.OBSERVER), + CompatItemHolder(ItemNames.WARP_HARMONIZATION), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_SHIELDS, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE, 3) +} + + +def compat_item_to_network_items(compat_item: CompatItemHolder) -> typing.List[NetworkItem]: + item_id = get_full_item_list()[compat_item.name].code + network_item = NetworkItem(item_id, 0, 0, 0) + return compat_item.quantity * [network_item] + + +def calculate_items(ctx: SC2Context) -> typing.Dict[SC2Race, typing.List[int]]: + items = ctx.items_received.copy() + # Items unlocked in API2 by default (Prophecy default items) + if ctx.slot_data_version < 3: + for compat_item in API2_TO_API3_COMPAT_ITEMS: + items.extend(compat_item_to_network_items(compat_item)) + + network_item: NetworkItem + accumulators: typing.Dict[SC2Race, typing.List[int]] = {race: [0 for _ in type_flaggroups[race]] for race in SC2Race} + + # Protoss Shield grouped item specific logic + shields_from_ground_upgrade: int = 0 + shields_from_air_upgrade: int = 0 + + item_list = get_full_item_list() + for network_item in items: + name: str = lookup_id_to_name[network_item.item] + item_data: ItemData = item_list[name] + + # exists exactly once + if item_data.quantity == 1: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] |= 1 << item_data.number + + # exists multiple times + elif item_data.type in ["Upgrade", "Progressive Upgrade","Progressive Upgrade 2"]: + flaggroup = type_flaggroups[item_data.race][item_data.type] + + # Generic upgrades apply only to Weapon / Armor upgrades + if item_data.type != "Upgrade" or ctx.generic_upgrade_items == 0: + accumulators[item_data.race][flaggroup] += 1 << item_data.number + else: + if name == ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: + shields_from_ground_upgrade += 1 + if name == ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: + shields_from_air_upgrade += 1 + for bundled_number in upgrade_numbers[item_data.number]: + accumulators[item_data.race][flaggroup] += 1 << bundled_number + + # Regen bio-steel nerf with API3 - undo for older games + if ctx.slot_data_version < 3 and name == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: + current_level = (accumulators[item_data.race][flaggroup] >> item_data.number) % 4 + if current_level == 2: + # Switch from level 2 to level 3 for compatibility + accumulators[item_data.race][flaggroup] += 1 << item_data.number + # sum + else: + if name == ItemNames.STARTING_MINERALS: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.minerals_per_item + elif name == ItemNames.STARTING_VESPENE: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.vespene_per_item + elif name == ItemNames.STARTING_SUPPLY: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.starting_supply_per_item + else: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += item_data.number + + # Fix Shields from generic upgrades by unit class (Maximum of ground/air upgrades) + if shields_from_ground_upgrade > 0 or shields_from_air_upgrade > 0: + shield_upgrade_level = max(shields_from_ground_upgrade, shields_from_air_upgrade) + shield_upgrade_item = item_list[ItemNames.PROGRESSIVE_PROTOSS_SHIELDS] + for _ in range(0, shield_upgrade_level): + accumulators[shield_upgrade_item.race][type_flaggroups[shield_upgrade_item.race][shield_upgrade_item.type]] += 1 << shield_upgrade_item.number + + # Kerrigan levels per check + accumulators[SC2Race.ZERG][type_flaggroups[SC2Race.ZERG]["Level"]] += (len(ctx.checked_locations) // ctx.checks_per_level) * ctx.levels_per_check + + # Upgrades from completed missions + if ctx.generic_upgrade_missions > 0: + total_missions = sum(len(ctx.mission_req_table[campaign]) for campaign in ctx.mission_req_table) + for race in SC2Race: + if "Upgrade" not in type_flaggroups[race]: + continue + upgrade_flaggroup = type_flaggroups[race]["Upgrade"] + num_missions = ctx.generic_upgrade_missions * total_missions + amounts = [ + num_missions // 100, + 2 * num_missions // 100, + 3 * num_missions // 100 + ] + upgrade_count = 0 + completed = len([id for id in ctx.mission_id_to_location_ids if get_location_offset(id) + VICTORY_MODULO * id in ctx.checked_locations]) + for amount in amounts: + if completed >= amount: + upgrade_count += 1 + # Equivalent to "Progressive Weapon/Armor Upgrade" item + for bundled_number in upgrade_numbers[upgrade_numbers_all[race]]: + accumulators[race][upgrade_flaggroup] += upgrade_count << bundled_number + + return accumulators + + +def calc_difficulty(difficulty: int): + if difficulty == 0: + return 'C' + elif difficulty == 1: + return 'N' + elif difficulty == 2: + return 'H' + elif difficulty == 3: + return 'B' + + return 'X' + + +def get_kerrigan_level(ctx: SC2Context, items: typing.Dict[SC2Race, typing.List[int]], missions_beaten: int) -> int: + item_value = items[SC2Race.ZERG][type_flaggroups[SC2Race.ZERG]["Level"]] + mission_value = missions_beaten * ctx.kerrigan_levels_per_mission_completed + if ctx.kerrigan_levels_per_mission_completed_cap != -1: + mission_value = min(mission_value, ctx.kerrigan_levels_per_mission_completed_cap) + total_value = item_value + mission_value + if ctx.kerrigan_total_level_cap != -1: + total_value = min(total_value, ctx.kerrigan_total_level_cap) + return total_value + + +def calculate_kerrigan_options(ctx: SC2Context) -> int: + options = 0 + + # Bits 0, 1 + # Kerrigan unit available + if ctx.kerrigan_presence in kerrigan_unit_available: + options |= 1 << 0 + + # Bit 2 + # Kerrigan primal status by map + if ctx.kerrigan_primal_status == KerriganPrimalStatus.option_vanilla: + options |= 1 << 2 + + return options + + +def caclulate_soa_options(ctx: SC2Context) -> int: + options = 0 + + # Bits 0, 1 + # SoA Calldowns available + soa_presence_value = 0 + if ctx.spear_of_adun_presence == SpearOfAdunPresence.option_not_present: + soa_presence_value = 0 + elif ctx.spear_of_adun_presence == SpearOfAdunPresence.option_lotv_protoss: + soa_presence_value = 1 + elif ctx.spear_of_adun_presence == SpearOfAdunPresence.option_protoss: + soa_presence_value = 2 + elif ctx.spear_of_adun_presence == SpearOfAdunPresence.option_everywhere: + soa_presence_value = 3 + options |= soa_presence_value << 0 + + # Bit 2 + # SoA Calldowns for no-builds + if ctx.spear_of_adun_present_in_no_build == SpearOfAdunPresentInNoBuild.option_true: + options |= 1 << 2 + + # Bits 3,4 + # Autocasts + soa_autocasts_presence_value = 0 + if ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: + soa_autocasts_presence_value = 0 + elif ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_lotv_protoss: + soa_autocasts_presence_value = 1 + elif ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_protoss: + soa_autocasts_presence_value = 2 + elif ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere: + soa_autocasts_presence_value = 3 + options |= soa_autocasts_presence_value << 3 + + # Bit 5 + # Autocasts in no-builds + if ctx.spear_of_adun_autonomously_cast_present_in_no_build == SpearOfAdunAutonomouslyCastPresentInNoBuild.option_true: + options |= 1 << 5 + + return options + +def kerrigan_primal(ctx: SC2Context, kerrigan_level: int) -> bool: + if ctx.kerrigan_primal_status == KerriganPrimalStatus.option_always_zerg: + return True + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_always_human: + return False + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_level_35: + return kerrigan_level >= 35 + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_half_completion: + total_missions = len(ctx.mission_id_to_location_ids) + completed = sum((mission_id * VICTORY_MODULO + get_location_offset(mission_id)) in ctx.checked_locations + for mission_id in ctx.mission_id_to_location_ids) + return completed >= (total_missions / 2) + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_item: + codes = [item.item for item in ctx.items_received] + return get_full_item_list()[ItemNames.KERRIGAN_PRIMAL_FORM].code in codes + return False + +async def starcraft_launch(ctx: SC2Context, mission_id: int): + sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id].mission_name}. If game does not launch check log file for errors.") + + with DllDirectory(None): + run_game(bot.maps.get(lookup_id_to_mission[mission_id].map_file), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id), + name="Archipelago", fullscreen=True)], realtime=True) + + +class ArchipelagoBot(bot.bot_ai.BotAI): + __slots__ = [ + 'game_running', + 'mission_completed', + 'boni', + 'setup_done', + 'ctx', + 'mission_id', + 'want_close', + 'can_read_game', + 'last_received_update', + ] + + def __init__(self, ctx: SC2Context, mission_id: int): + self.game_running = False + self.mission_completed = False + self.want_close = False + self.can_read_game = False + self.last_received_update: int = 0 + self.setup_done = False + self.ctx = ctx + self.ctx.last_bot = self + self.mission_id = mission_id + self.boni = [False for _ in range(MAX_BONUS)] + + super(ArchipelagoBot, self).__init__() + + async def on_step(self, iteration: int): + if self.want_close: + self.want_close = False + await self._client.leave() + return + game_state = 0 + if not self.setup_done: + self.setup_done = True + start_items = calculate_items(self.ctx) + missions_beaten = self.missions_beaten_count() + kerrigan_level = get_kerrigan_level(self.ctx, start_items, missions_beaten) + kerrigan_options = calculate_kerrigan_options(self.ctx) + soa_options = caclulate_soa_options(self.ctx) + if self.ctx.difficulty_override >= 0: + difficulty = calc_difficulty(self.ctx.difficulty_override) + else: + difficulty = calc_difficulty(self.ctx.difficulty) + if self.ctx.game_speed_override >= 0: + game_speed = self.ctx.game_speed_override + else: + game_speed = self.ctx.game_speed + await self.chat_send("?SetOptions {} {} {} {} {} {} {} {} {} {} {} {} {}".format( + difficulty, + self.ctx.generic_upgrade_research, + self.ctx.all_in_choice, + game_speed, + self.ctx.disable_forced_camera, + self.ctx.skip_cutscenes, + kerrigan_options, + self.ctx.grant_story_tech, + self.ctx.take_over_ai_allies, + soa_options, + self.ctx.mission_order, + 1 if self.ctx.nova_covert_ops_only else 0, + self.ctx.grant_story_levels + )) + await self.chat_send("?GiveResources {} {} {}".format( + start_items[SC2Race.ANY][0], + start_items[SC2Race.ANY][1], + start_items[SC2Race.ANY][2] + )) + await self.updateTerranTech(start_items) + await self.updateZergTech(start_items, kerrigan_level) + await self.updateProtossTech(start_items) + await self.updateColors() + await self.chat_send("?LoadFinished") + self.last_received_update = len(self.ctx.items_received) + + else: + if self.ctx.pending_color_update: + await self.updateColors() + + if not self.ctx.announcements.empty(): + message = self.ctx.announcements.get(timeout=1) + await self.chat_send("?SendMessage " + message) + self.ctx.announcements.task_done() + + # Archipelago reads the health + controller1_state = 0 + controller2_state = 0 + for unit in self.all_own_units(): + if unit.health_max == CONTROLLER_HEALTH: + controller1_state = int(CONTROLLER_HEALTH - unit.health) + self.can_read_game = True + elif unit.health_max == CONTROLLER2_HEALTH: + controller2_state = int(CONTROLLER2_HEALTH - unit.health) + self.can_read_game = True + game_state = controller1_state + (controller2_state << 15) + + if iteration == 160 and not game_state & 1: + await self.chat_send("?SendMessage Warning: Archipelago unable to connect or has lost connection to " + + "Starcraft 2 (This is likely a map issue)") + + if self.last_received_update < len(self.ctx.items_received): + current_items = calculate_items(self.ctx) + missions_beaten = self.missions_beaten_count() + kerrigan_level = get_kerrigan_level(self.ctx, current_items, missions_beaten) + await self.updateTerranTech(current_items) + await self.updateZergTech(current_items, kerrigan_level) + await self.updateProtossTech(current_items) + self.last_received_update = len(self.ctx.items_received) + + if game_state & 1: + if not self.game_running: + print("Archipelago Connected") + self.game_running = True + + if self.can_read_game: + if game_state & (1 << 1) and not self.mission_completed: + if self.mission_id != self.ctx.final_mission: + print("Mission Completed") + await self.ctx.send_msgs( + [{"cmd": 'LocationChecks', + "locations": [get_location_offset(self.mission_id) + VICTORY_MODULO * self.mission_id]}]) + self.mission_completed = True + else: + print("Game Complete") + await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}]) + self.mission_completed = True + + for x, completed in enumerate(self.boni): + if not completed and game_state & (1 << (x + 2)): + await self.ctx.send_msgs( + [{"cmd": 'LocationChecks', + "locations": [get_location_offset(self.mission_id) + VICTORY_MODULO * self.mission_id + x + 1]}]) + self.boni[x] = True + else: + await self.chat_send("?SendMessage LostConnection - Lost connection to game.") + + def missions_beaten_count(self): + return len([location for location in self.ctx.checked_locations if location % VICTORY_MODULO == 0]) + + async def updateColors(self): + await self.chat_send("?SetColor rr " + str(self.ctx.player_color_raynor)) + await self.chat_send("?SetColor ks " + str(self.ctx.player_color_zerg)) + await self.chat_send("?SetColor pz " + str(self.ctx.player_color_zerg_primal)) + await self.chat_send("?SetColor da " + str(self.ctx.player_color_protoss)) + await self.chat_send("?SetColor nova " + str(self.ctx.player_color_nova)) + self.ctx.pending_color_update = False + + async def updateTerranTech(self, current_items): + terran_items = current_items[SC2Race.TERRAN] + await self.chat_send("?GiveTerranTech {} {} {} {} {} {} {} {} {} {} {} {} {} {}".format( + terran_items[0], terran_items[1], terran_items[2], terran_items[3], terran_items[4], + terran_items[5], terran_items[6], terran_items[7], terran_items[8], terran_items[9], terran_items[10], + terran_items[11], terran_items[12], terran_items[13])) + + async def updateZergTech(self, current_items, kerrigan_level): + zerg_items = current_items[SC2Race.ZERG] + kerrigan_primal_by_items = kerrigan_primal(self.ctx, kerrigan_level) + kerrigan_primal_bot_value = 1 if kerrigan_primal_by_items else 0 + await self.chat_send("?GiveZergTech {} {} {} {} {} {} {} {} {} {} {} {}".format( + kerrigan_level, kerrigan_primal_bot_value, zerg_items[0], zerg_items[1], zerg_items[2], + zerg_items[3], zerg_items[4], zerg_items[5], zerg_items[6], zerg_items[9], zerg_items[10], zerg_items[11] + )) + + async def updateProtossTech(self, current_items): + protoss_items = current_items[SC2Race.PROTOSS] + await self.chat_send("?GiveProtossTech {} {} {} {} {} {} {} {} {} {}".format( + protoss_items[0], protoss_items[1], protoss_items[2], protoss_items[3], protoss_items[4], + protoss_items[5], protoss_items[6], protoss_items[7], protoss_items[8], protoss_items[9] + )) + + +def request_unfinished_missions(ctx: SC2Context) -> None: + if ctx.mission_req_table: + message = "Unfinished Missions: " + unlocks = initialize_blank_mission_dict(ctx.mission_req_table) + unfinished_locations: typing.Dict[SC2Mission, typing.List[str]] = {} + + _, unfinished_missions = calc_unfinished_missions(ctx, unlocks=unlocks) + + for mission in unfinished_missions: + objectives = set(ctx.locations_for_mission(mission)) + if objectives: + remaining_objectives = objectives.difference(ctx.checked_locations) + unfinished_locations[mission] = [ctx.location_names.lookup_in_game(location_id) for location_id in remaining_objectives] + else: + unfinished_locations[mission] = [] + + # Removing All-In from location pool + final_mission = lookup_id_to_mission[ctx.final_mission] + if final_mission in unfinished_missions.keys(): + message = f"Final Mission Available: {final_mission}[{ctx.final_mission}]\n" + message + if unfinished_missions[final_mission] == -1: + unfinished_missions.pop(final_mission) + + message += ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}[{ctx.mission_req_table[ctx.find_campaign(mission)][mission].mission.id}] " + + mark_up_objectives( + f"[{len(unfinished_missions[mission])}/" + f"{sum(1 for _ in ctx.locations_for_mission(mission))}]", + ctx, unfinished_locations, mission) + for mission in unfinished_missions) + + if ctx.ui: + ctx.ui.log_panels['All'].on_message_markup(message) + ctx.ui.log_panels['Starcraft2'].on_message_markup(message) + else: + sc2_logger.info(message) + else: + sc2_logger.warning("No mission table found, you are likely not connected to a server.") + + +def calc_unfinished_missions(ctx: SC2Context, unlocks: typing.Optional[typing.Dict] = None): + unfinished_missions: typing.List[str] = [] + locations_completed: typing.List[typing.Union[typing.Set[int], typing.Literal[-1]]] = [] + + if not unlocks: + unlocks = initialize_blank_mission_dict(ctx.mission_req_table) + + available_missions = calc_available_missions(ctx, unlocks) + + for name in available_missions: + objectives = set(ctx.locations_for_mission(name)) + if objectives: + objectives_completed = ctx.checked_locations & objectives + if len(objectives_completed) < len(objectives): + unfinished_missions.append(name) + locations_completed.append(objectives_completed) + + else: # infer that this is the final mission as it has no objectives + unfinished_missions.append(name) + locations_completed.append(-1) + + return available_missions, dict(zip(unfinished_missions, locations_completed)) + + +def is_mission_available(ctx: SC2Context, mission_id_to_check: int) -> bool: + unfinished_missions = calc_available_missions(ctx) + + return any(mission_id_to_check == ctx.mission_req_table[ctx.find_campaign(mission)][mission].mission.id for mission in unfinished_missions) + + +def mark_up_mission_name(ctx: SC2Context, mission_name: str, unlock_table: typing.Dict) -> str: + """Checks if the mission is required for game completion and adds '*' to the name to mark that.""" + + campaign = ctx.find_campaign(mission_name) + mission_info = ctx.mission_req_table[campaign][mission_name] + if mission_info.completion_critical: + if ctx.ui: + message = "[color=AF99EF]" + mission_name + "[/color]" + else: + message = "*" + mission_name + "*" + else: + message = mission_name + + if ctx.ui: + campaign_missions = list(ctx.mission_req_table[campaign].keys()) + unlocks: typing.List[str] + index = campaign_missions.index(mission_name) + if index in unlock_table[campaign]: + unlocks = unlock_table[campaign][index] + else: + unlocks = [] + + if len(unlocks) > 0: + pre_message = f"[ref={mission_info.mission.id}|Unlocks: " + pre_message += ", ".join(f"{unlock}({ctx.mission_req_table[ctx.find_campaign(unlock)][unlock].mission.id})" for unlock in unlocks) + pre_message += f"]" + message = pre_message + message + "[/ref]" + + return message + + +def mark_up_objectives(message, ctx, unfinished_locations, mission): + formatted_message = message + + if ctx.ui: + locations = unfinished_locations[mission] + campaign = ctx.find_campaign(mission) + + pre_message = f"[ref={list(ctx.mission_req_table[campaign]).index(mission) + 30}|" + pre_message += "
    ".join(location for location in locations) + pre_message += f"]" + formatted_message = pre_message + message + "[/ref]" + + return formatted_message + + +def request_available_missions(ctx: SC2Context): + if ctx.mission_req_table: + message = "Available Missions: " + + # Initialize mission unlock table + unlocks = initialize_blank_mission_dict(ctx.mission_req_table) + + missions = calc_available_missions(ctx, unlocks) + message += \ + ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}" + f"[{ctx.mission_req_table[ctx.find_campaign(mission)][mission].mission.id}]" + for mission in missions) + + if ctx.ui: + ctx.ui.log_panels['All'].on_message_markup(message) + ctx.ui.log_panels['Starcraft2'].on_message_markup(message) + else: + sc2_logger.info(message) + else: + sc2_logger.warning("No mission table found, you are likely not connected to a server.") + + +def calc_available_missions(ctx: SC2Context, unlocks: typing.Optional[dict] = None) -> typing.List[str]: + available_missions: typing.List[str] = [] + missions_complete = 0 + + # Get number of missions completed + for loc in ctx.checked_locations: + if loc % VICTORY_MODULO == 0: + missions_complete += 1 + + for campaign in ctx.mission_req_table: + # Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips + for mission_name in ctx.mission_req_table[campaign]: + if unlocks: + for unlock in ctx.mission_req_table[campaign][mission_name].required_world: + parsed_unlock = parse_unlock(unlock) + # TODO prophecy-only wants to connect to WoL here + index = parsed_unlock.connect_to - 1 + unlock_mission = list(ctx.mission_req_table[parsed_unlock.campaign])[index] + unlock_campaign = ctx.find_campaign(unlock_mission) + if unlock_campaign in unlocks: + if index not in unlocks[unlock_campaign]: + unlocks[unlock_campaign][index] = list() + unlocks[unlock_campaign][index].append(mission_name) + + if mission_reqs_completed(ctx, mission_name, missions_complete): + available_missions.append(mission_name) + + return available_missions + + +def parse_unlock(unlock: typing.Union[typing.Dict[typing.Literal["connect_to", "campaign"], int], MissionConnection, int]) -> MissionConnection: + if isinstance(unlock, int): + # Legacy + return MissionConnection(unlock) + elif isinstance(unlock, MissionConnection): + return unlock + else: + # Multi-campaign + return MissionConnection(unlock["connect_to"], lookup_id_to_campaign[unlock["campaign"]]) + + +def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete: int) -> bool: + """Returns a bool signifying if the mission has all requirements complete and can be done + + Arguments: + ctx -- instance of SC2Context + locations_to_check -- the mission string name to check + missions_complete -- an int of how many missions have been completed + mission_path -- a list of missions that have already been checked + """ + campaign = ctx.find_campaign(mission_name) + + if len(ctx.mission_req_table[campaign][mission_name].required_world) >= 1: + # A check for when the requirements are being or'd + or_success = False + + # Loop through required missions + for req_mission in ctx.mission_req_table[campaign][mission_name].required_world: + req_success = True + parsed_req_mission = parse_unlock(req_mission) + + # Check if required mission has been completed + mission_id = ctx.mission_req_table[parsed_req_mission.campaign][ + list(ctx.mission_req_table[parsed_req_mission.campaign])[parsed_req_mission.connect_to - 1]].mission.id + if not (mission_id * VICTORY_MODULO + get_location_offset(mission_id)) in ctx.checked_locations: + if not ctx.mission_req_table[campaign][mission_name].or_requirements: + return False + else: + req_success = False + + # Grid-specific logic (to avoid long path checks and infinite recursion) + if ctx.mission_order in (MissionOrder.option_grid, MissionOrder.option_mini_grid, MissionOrder.option_medium_grid): + if req_success: + return True + else: + if parsed_req_mission == ctx.mission_req_table[campaign][mission_name].required_world[-1]: + return False + else: + continue + + # Recursively check required mission to see if it's requirements are met, in case !collect has been done + # Skipping recursive check on Grid settings to speed up checks and avoid infinite recursion + if not mission_reqs_completed(ctx, list(ctx.mission_req_table[parsed_req_mission.campaign])[parsed_req_mission.connect_to - 1], missions_complete): + if not ctx.mission_req_table[campaign][mission_name].or_requirements: + return False + else: + req_success = False + + # If requirement check succeeded mark or as satisfied + if ctx.mission_req_table[campaign][mission_name].or_requirements and req_success: + or_success = True + + if ctx.mission_req_table[campaign][mission_name].or_requirements: + # Return false if or requirements not met + if not or_success: + return False + + # Check number of missions + if missions_complete >= ctx.mission_req_table[campaign][mission_name].number: + return True + else: + return False + else: + return True + + +def initialize_blank_mission_dict(location_table: typing.Dict[SC2Campaign, typing.Dict[str, MissionInfo]]): + unlocks: typing.Dict[SC2Campaign, typing.Dict] = {} + + for mission in list(location_table): + unlocks[mission] = {} + + return unlocks + + +def check_game_install_path() -> bool: + # First thing: go to the default location for ExecuteInfo. + # An exception for Windows is included because it's very difficult to find ~\Documents if the user moved it. + if is_windows: + # The next five lines of utterly inscrutable code are brought to you by copy-paste from Stack Overflow. + # https://stackoverflow.com/questions/6227590/finding-the-users-my-documents-path/30924555# + import ctypes.wintypes + CSIDL_PERSONAL = 5 # My Documents + SHGFP_TYPE_CURRENT = 0 # Get current, not default value + + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) + documentspath: str = buf.value + einfo = str(documentspath / Path("StarCraft II\\ExecuteInfo.txt")) + else: + einfo = str(bot.paths.get_home() / Path(bot.paths.USERPATH[bot.paths.PF])) + + # Check if the file exists. + if os.path.isfile(einfo): + + # Open the file and read it, picking out the latest executable's path. + with open(einfo) as f: + content = f.read() + if content: + search_result = re.search(r" = (.*)Versions", content) + if not search_result: + sc2_logger.warning(f"Found {einfo}, but it was empty. Run SC2 through the Blizzard launcher, " + "then try again.") + return False + base = search_result.group(1) + + if os.path.exists(base): + executable = bot.paths.latest_executeble(Path(base).expanduser() / "Versions") + + # Finally, check the path for an actual executable. + # If we find one, great. Set up the SC2PATH. + if os.path.isfile(executable): + sc2_logger.info(f"Found an SC2 install at {base}!") + sc2_logger.debug(f"Latest executable at {executable}.") + os.environ["SC2PATH"] = base + sc2_logger.debug(f"SC2PATH set to {base}.") + return True + else: + sc2_logger.warning(f"We may have found an SC2 install at {base}, but couldn't find {executable}.") + else: + sc2_logger.warning(f"{einfo} pointed to {base}, but we could not find an SC2 install there.") + else: + sc2_logger.warning(f"Couldn't find {einfo}. Run SC2 through the Blizzard launcher, then try again. " + f"If that fails, please run /set_path with your SC2 install directory.") + return False + + +def is_mod_installed_correctly() -> bool: + """Searches for all required files.""" + if "SC2PATH" not in os.environ: + check_game_install_path() + sc2_path: str = os.environ["SC2PATH"] + mapdir = sc2_path / Path('Maps/ArchipelagoCampaign') + mods = ["ArchipelagoCore", "ArchipelagoPlayer", "ArchipelagoPlayerSuper", "ArchipelagoPatches", + "ArchipelagoTriggers", "ArchipelagoPlayerWoL", "ArchipelagoPlayerHotS", + "ArchipelagoPlayerLotV", "ArchipelagoPlayerLotVPrologue", "ArchipelagoPlayerNCO"] + modfiles = [sc2_path / Path("Mods/" + mod + ".SC2Mod") for mod in mods] + wol_required_maps: typing.List[str] = ["WoL" + os.sep + mission.map_file + ".SC2Map" for mission in SC2Mission + if mission.campaign in (SC2Campaign.WOL, SC2Campaign.PROPHECY)] + hots_required_maps: typing.List[str] = ["HotS" + os.sep + mission.map_file + ".SC2Map" for mission in campaign_mission_table[SC2Campaign.HOTS]] + lotv_required_maps: typing.List[str] = ["LotV" + os.sep + mission.map_file + ".SC2Map" for mission in SC2Mission + if mission.campaign in (SC2Campaign.LOTV, SC2Campaign.PROLOGUE, SC2Campaign.EPILOGUE)] + nco_required_maps: typing.List[str] = ["NCO" + os.sep + mission.map_file + ".SC2Map" for mission in campaign_mission_table[SC2Campaign.NCO]] + required_maps = wol_required_maps + hots_required_maps + lotv_required_maps + nco_required_maps + needs_files = False + + # Check for maps. + missing_maps: typing.List[str] = [] + for mapfile in required_maps: + if not os.path.isfile(mapdir / mapfile): + missing_maps.append(mapfile) + if len(missing_maps) >= 19: + sc2_logger.warning(f"All map files missing from {mapdir}.") + needs_files = True + elif len(missing_maps) > 0: + for map in missing_maps: + sc2_logger.debug(f"Missing {map} from {mapdir}.") + sc2_logger.warning(f"Missing {len(missing_maps)} map files.") + needs_files = True + else: # Must be no maps missing + sc2_logger.info(f"All maps found in {mapdir}.") + + # Check for mods. + for modfile in modfiles: + if os.path.isfile(modfile) or os.path.isdir(modfile): + sc2_logger.info(f"Archipelago mod found at {modfile}.") + else: + sc2_logger.warning(f"Archipelago mod could not be found at {modfile}.") + needs_files = True + + # Final verdict. + if needs_files: + sc2_logger.warning(f"Required files are missing. Run /download_data to acquire them.") + return False + else: + sc2_logger.debug(f"All map/mod files are properly installed.") + return True + + +class DllDirectory: + # Credit to Black Sliver for this code. + # More info: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw + _old: typing.Optional[str] = None + _new: typing.Optional[str] = None + + def __init__(self, new: typing.Optional[str]): + self._new = new + + def __enter__(self): + old = self.get() + if self.set(self._new): + self._old = old + + def __exit__(self, *args): + if self._old is not None: + self.set(self._old) + + @staticmethod + def get() -> typing.Optional[str]: + if sys.platform == "win32": + n = ctypes.windll.kernel32.GetDllDirectoryW(0, None) + buf = ctypes.create_unicode_buffer(n) + ctypes.windll.kernel32.GetDllDirectoryW(n, buf) + return buf.value + # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific + return None + + @staticmethod + def set(s: typing.Optional[str]) -> bool: + if sys.platform == "win32": + return ctypes.windll.kernel32.SetDllDirectoryW(s) != 0 + # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific + return False + + +def download_latest_release_zip( + owner: str, + repo: str, + api_version: str, + metadata: typing.Optional[str] = None, + force_download=False +) -> typing.Tuple[str, typing.Optional[str]]: + """Downloads the latest release of a GitHub repo to the current directory as a .zip file.""" + import requests + + headers = {"Accept": 'application/vnd.github.v3+json'} + url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" + + r1 = requests.get(url, headers=headers) + if r1.status_code == 200: + latest_metadata = r1.json() + cleanup_downloaded_metadata(latest_metadata) + latest_metadata = str(latest_metadata) + # sc2_logger.info(f"Latest version: {latest_metadata}.") + else: + sc2_logger.warning(f"Status code: {r1.status_code}") + sc2_logger.warning(f"Failed to reach GitHub. Could not find download link.") + sc2_logger.warning(f"text: {r1.text}") + return "", metadata + + if (force_download is False) and (metadata == latest_metadata): + sc2_logger.info("Latest version already installed.") + return "", metadata + + sc2_logger.info(f"Attempting to download latest version of API version {api_version} of {repo}.") + download_url = r1.json()["assets"][0]["browser_download_url"] + + r2 = requests.get(download_url, headers=headers) + if r2.status_code == 200 and zipfile.is_zipfile(io.BytesIO(r2.content)): + tempdir = tempfile.gettempdir() + file = tempdir + os.sep + f"{repo}.zip" + with open(file, "wb") as fh: + fh.write(r2.content) + sc2_logger.info(f"Successfully downloaded {repo}.zip.") + return file, latest_metadata + else: + sc2_logger.warning(f"Status code: {r2.status_code}") + sc2_logger.warning("Download failed.") + sc2_logger.warning(f"text: {r2.text}") + return "", metadata + + +def cleanup_downloaded_metadata(medatada_json: dict) -> None: + for asset in medatada_json['assets']: + del asset['download_count'] + + +def is_mod_update_available(owner: str, repo: str, api_version: str, metadata: str) -> bool: + import requests + + headers = {"Accept": 'application/vnd.github.v3+json'} + url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" + + r1 = requests.get(url, headers=headers) + if r1.status_code == 200: + latest_metadata = r1.json() + cleanup_downloaded_metadata(latest_metadata) + latest_metadata = str(latest_metadata) + if metadata != latest_metadata: + return True + else: + return False + + else: + sc2_logger.warning(f"Failed to reach GitHub while checking for updates.") + sc2_logger.warning(f"Status code: {r1.status_code}") + sc2_logger.warning(f"text: {r1.text}") + return False + + +def get_location_offset(mission_id): + return SC2WOL_LOC_ID_OFFSET if mission_id <= SC2Mission.ALL_IN.id \ + else (SC2HOTS_LOC_ID_OFFSET - SC2Mission.ALL_IN.id * VICTORY_MODULO) + + +def launch(): + colorama.init() + asyncio.run(main()) + colorama.deinit() diff --git a/worlds/sc2/ClientGui.py b/worlds/sc2/ClientGui.py new file mode 100644 index 000000000000..22e444efe7c9 --- /dev/null +++ b/worlds/sc2/ClientGui.py @@ -0,0 +1,304 @@ +from typing import * +import asyncio + +from kvui import GameManager, HoverBehavior, ServerToolTip +from kivy.app import App +from kivy.clock import Clock +from kivy.uix.tabbedpanel import TabbedPanelItem +from kivy.uix.gridlayout import GridLayout +from kivy.lang import Builder +from kivy.uix.label import Label +from kivy.uix.button import Button +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.scrollview import ScrollView +from kivy.properties import StringProperty + +from .Client import SC2Context, calc_unfinished_missions, parse_unlock +from .MissionTables import (lookup_id_to_mission, lookup_name_to_mission, campaign_race_exceptions, SC2Mission, SC2Race, + SC2Campaign) +from .Locations import LocationType, lookup_location_id_to_type +from .Options import LocationInclusion +from . import SC2World, get_first_mission + + +class HoverableButton(HoverBehavior, Button): + pass + + +class MissionButton(HoverableButton): + tooltip_text = StringProperty("Test") + + def __init__(self, *args, **kwargs): + super(HoverableButton, self).__init__(*args, **kwargs) + self.layout = FloatLayout() + self.popuplabel = ServerToolTip(text=self.text, markup=True) + self.popuplabel.padding = [5, 2, 5, 2] + self.layout.add_widget(self.popuplabel) + + def on_enter(self): + self.popuplabel.text = self.tooltip_text + + if self.ctx.current_tooltip: + App.get_running_app().root.remove_widget(self.ctx.current_tooltip) + + if self.tooltip_text == "": + self.ctx.current_tooltip = None + else: + App.get_running_app().root.add_widget(self.layout) + self.ctx.current_tooltip = self.layout + + def on_leave(self): + self.ctx.ui.clear_tooltip() + + @property + def ctx(self) -> SC2Context: + return App.get_running_app().ctx + +class CampaignScroll(ScrollView): + pass + +class MultiCampaignLayout(GridLayout): + pass + +class CampaignLayout(GridLayout): + pass + +class MissionLayout(GridLayout): + pass + +class MissionCategory(GridLayout): + pass + +class SC2Manager(GameManager): + logging_pairs = [ + ("Client", "Archipelago"), + ("Starcraft2", "Starcraft2"), + ] + base_title = "Archipelago Starcraft 2 Client" + + campaign_panel: Optional[CampaignLayout] = None + last_checked_locations: Set[int] = set() + mission_id_to_button: Dict[int, MissionButton] = {} + launching: Union[bool, int] = False # if int -> mission ID + refresh_from_launching = True + first_check = True + first_mission = "" + ctx: SC2Context + + def __init__(self, ctx) -> None: + super().__init__(ctx) + + def clear_tooltip(self) -> None: + if self.ctx.current_tooltip: + App.get_running_app().root.remove_widget(self.ctx.current_tooltip) + + self.ctx.current_tooltip = None + + def build(self): + container = super().build() + + panel = TabbedPanelItem(text="Starcraft 2 Launcher") + panel.content = CampaignScroll() + self.campaign_panel = MultiCampaignLayout() + panel.content.add_widget(self.campaign_panel) + + self.tabs.add_widget(panel) + + Clock.schedule_interval(self.build_mission_table, 0.5) + + return container + + def build_mission_table(self, dt) -> None: + if (not self.launching and (not self.last_checked_locations == self.ctx.checked_locations or + not self.refresh_from_launching)) or self.first_check: + assert self.campaign_panel is not None + self.refresh_from_launching = True + + self.campaign_panel.clear_widgets() + if self.ctx.mission_req_table: + self.last_checked_locations = self.ctx.checked_locations.copy() + self.first_check = False + self.first_mission = get_first_mission(self.ctx.mission_req_table) + + self.mission_id_to_button = {} + + available_missions, unfinished_missions = calc_unfinished_missions(self.ctx) + + multi_campaign_layout_height = 0 + + for campaign, missions in sorted(self.ctx.mission_req_table.items(), key=lambda item: item[0].id): + categories: Dict[str, List[str]] = {} + + # separate missions into categories + for mission_index in missions: + mission_info = self.ctx.mission_req_table[campaign][mission_index] + if mission_info.category not in categories: + categories[mission_info.category] = [] + + categories[mission_info.category].append(mission_index) + + max_mission_count = max(len(categories[category]) for category in categories) + if max_mission_count == 1: + campaign_layout_height = 115 + else: + campaign_layout_height = (max_mission_count + 2) * 50 + multi_campaign_layout_height += campaign_layout_height + campaign_layout = CampaignLayout(size_hint_y=None, height=campaign_layout_height) + if campaign != SC2Campaign.GLOBAL: + campaign_layout.add_widget( + Label(text=campaign.campaign_name, size_hint_y=None, height=25, outline_width=1) + ) + mission_layout = MissionLayout() + + for category in categories: + category_name_height = 0 + category_spacing = 3 + if category.startswith('_'): + category_display_name = '' + else: + category_display_name = category + category_name_height += 25 + category_spacing = 10 + category_panel = MissionCategory(padding=[category_spacing,6,category_spacing,6]) + category_panel.add_widget( + Label(text=category_display_name, size_hint_y=None, height=category_name_height, outline_width=1)) + + for mission in categories[category]: + text: str = mission + tooltip: str = "" + mission_obj: SC2Mission = lookup_name_to_mission[mission] + mission_id: int = mission_obj.id + mission_data = self.ctx.mission_req_table[campaign][mission] + remaining_locations, plando_locations, remaining_count = self.sort_unfinished_locations(mission) + # Map has uncollected locations + if mission in unfinished_missions: + if self.any_valuable_locations(remaining_locations): + text = f"[color=6495ED]{text}[/color]" + else: + text = f"[color=A0BEF4]{text}[/color]" + elif mission in available_missions: + text = f"[color=FFFFFF]{text}[/color]" + # Map requirements not met + else: + text = f"[color=a9a9a9]{text}[/color]" + tooltip = f"Requires: " + if mission_data.required_world: + tooltip += ", ".join(list(self.ctx.mission_req_table[parse_unlock(req_mission).campaign])[parse_unlock(req_mission).connect_to - 1] for + req_mission in + mission_data.required_world) + + if mission_data.number: + tooltip += " and " + if mission_data.number: + tooltip += f"{self.ctx.mission_req_table[campaign][mission].number} missions completed" + + if mission_id == self.ctx.final_mission: + if mission in available_missions: + text = f"[color=FFBC95]{mission}[/color]" + else: + text = f"[color=D0C0BE]{mission}[/color]" + if tooltip: + tooltip += "\n" + tooltip += "Final Mission" + + if remaining_count > 0: + if tooltip: + tooltip += "\n\n" + tooltip += f"-- Uncollected locations --" + for loctype in LocationType: + if len(remaining_locations[loctype]) > 0: + if loctype == LocationType.VICTORY: + tooltip += f"\n- {remaining_locations[loctype][0]}" + else: + tooltip += f"\n{self.get_location_type_title(loctype)}:\n- " + tooltip += "\n- ".join(remaining_locations[loctype]) + if len(plando_locations) > 0: + tooltip += f"\nPlando:\n- " + tooltip += "\n- ".join(plando_locations) + + MISSION_BUTTON_HEIGHT = 50 + for pad in range(mission_data.ui_vertical_padding): + column_spacer = Label(text='', size_hint_y=None, height=MISSION_BUTTON_HEIGHT) + category_panel.add_widget(column_spacer) + mission_button = MissionButton(text=text, size_hint_y=None, height=MISSION_BUTTON_HEIGHT) + mission_race = mission_obj.race + if mission_race == SC2Race.ANY: + mission_race = mission_obj.campaign.race + race = campaign_race_exceptions.get(mission_obj, mission_race) + racial_colors = { + SC2Race.TERRAN: (0.24, 0.84, 0.68), + SC2Race.ZERG: (1, 0.65, 0.37), + SC2Race.PROTOSS: (0.55, 0.7, 1) + } + if race in racial_colors: + mission_button.background_color = racial_colors[race] + mission_button.tooltip_text = tooltip + mission_button.bind(on_press=self.mission_callback) + self.mission_id_to_button[mission_id] = mission_button + category_panel.add_widget(mission_button) + + category_panel.add_widget(Label(text="")) + mission_layout.add_widget(category_panel) + campaign_layout.add_widget(mission_layout) + self.campaign_panel.add_widget(campaign_layout) + self.campaign_panel.height = multi_campaign_layout_height + + elif self.launching: + assert self.campaign_panel is not None + self.refresh_from_launching = False + + self.campaign_panel.clear_widgets() + self.campaign_panel.add_widget(Label(text="Launching Mission: " + + lookup_id_to_mission[self.launching].mission_name)) + if self.ctx.ui: + self.ctx.ui.clear_tooltip() + + def mission_callback(self, button: MissionButton) -> None: + if not self.launching: + mission_id: int = next(k for k, v in self.mission_id_to_button.items() if v == button) + if self.ctx.play_mission(mission_id): + self.launching = mission_id + Clock.schedule_once(self.finish_launching, 10) + + def finish_launching(self, dt): + self.launching = False + + def sort_unfinished_locations(self, mission_name: str) -> Tuple[Dict[LocationType, List[str]], List[str], int]: + locations: Dict[LocationType, List[str]] = {loctype: [] for loctype in LocationType} + count = 0 + for loc in self.ctx.locations_for_mission(mission_name): + if loc in self.ctx.missing_locations: + count += 1 + locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_game(loc)) + + plando_locations = [] + for plando_loc in self.ctx.plando_locations: + for loctype in LocationType: + if plando_loc in locations[loctype]: + locations[loctype].remove(plando_loc) + plando_locations.append(plando_loc) + + return locations, plando_locations, count + + def any_valuable_locations(self, locations: Dict[LocationType, List[str]]) -> bool: + for loctype in LocationType: + if len(locations[loctype]) > 0 and self.ctx.location_inclusions[loctype] == LocationInclusion.option_enabled: + return True + return False + + def get_location_type_title(self, location_type: LocationType) -> str: + title = location_type.name.title().replace("_", " ") + if self.ctx.location_inclusions[location_type] == LocationInclusion.option_disabled: + title += " (Nothing)" + elif self.ctx.location_inclusions[location_type] == LocationInclusion.option_resources: + title += " (Resources)" + else: + title += "" + return title + +def start_gui(context: SC2Context): + context.ui = SC2Manager(context) + context.ui_task = asyncio.create_task(context.ui.async_run(), name="UI") + import pkgutil + data = pkgutil.get_data(SC2World.__module__, "Starcraft2.kv").decode() + Builder.load_string(data) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py new file mode 100644 index 000000000000..3a3733044579 --- /dev/null +++ b/worlds/sc2/ItemGroups.py @@ -0,0 +1,100 @@ +import typing +from . import Items, ItemNames +from .MissionTables import campaign_mission_table, SC2Campaign, SC2Mission + +""" +Item name groups, given to Archipelago and used in YAMLs and /received filtering. +For non-developers the following will be useful: +* Items with a bracket get groups named after the unbracketed part + * eg. "Advanced Healing AI (Medivac)" is accessible as "Advanced Healing AI" + * The exception to this are item names that would be ambiguous (eg. "Resource Efficiency") +* Item flaggroups get unique groups as well as combined groups for numbered flaggroups + * eg. "Unit" contains all units, "Armory" contains "Armory 1" through "Armory 6" + * The best place to look these up is at the bottom of Items.py +* Items that have a parent are grouped together + * eg. "Zergling Items" contains all items that have "Zergling" as a parent + * These groups do NOT contain the parent item + * This currently does not include items with multiple potential parents, like some LotV unit upgrades +* All items are grouped by their race ("Terran", "Protoss", "Zerg", "Any") +* Hand-crafted item groups can be found at the bottom of this file +""" + +item_name_groups: typing.Dict[str, typing.List[str]] = {} + +# Groups for use in world logic +item_name_groups["Missions"] = ["Beat " + mission.mission_name for mission in SC2Mission] +item_name_groups["WoL Missions"] = ["Beat " + mission.mission_name for mission in campaign_mission_table[SC2Campaign.WOL]] + \ + ["Beat " + mission.mission_name for mission in campaign_mission_table[SC2Campaign.PROPHECY]] + +# These item name groups should not show up in documentation +unlisted_item_name_groups = { + "Missions", "WoL Missions" +} + +# Some item names only differ in bracketed parts +# These items are ambiguous for short-hand name groups +bracketless_duplicates: typing.Set[str] +# This is a list of names in ItemNames with bracketed parts removed, for internal use +_shortened_names = [(name[:name.find(' (')] if '(' in name else name) + for name in [ItemNames.__dict__[name] for name in ItemNames.__dir__() if not name.startswith('_')]] +# Remove the first instance of every short-name from the full item list +bracketless_duplicates = set(_shortened_names) +for name in bracketless_duplicates: + _shortened_names.remove(name) +# The remaining short-names are the duplicates +bracketless_duplicates = set(_shortened_names) +del _shortened_names + +# All items get sorted into their data type +for item, data in Items.get_full_item_list().items(): + # Items get assigned to their flaggroup's type + item_name_groups.setdefault(data.type, []).append(item) + # Numbered flaggroups get sorted into an unnumbered group + # Currently supports numbers of one or two digits + if data.type[-2:].strip().isnumeric(): + type_group = data.type[:-2].strip() + item_name_groups.setdefault(type_group, []).append(item) + # Flaggroups with numbers are unlisted + unlisted_item_name_groups.add(data.type) + # Items with a bracket get a short-hand name group for ease of use in YAMLs + if '(' in item: + short_name = item[:item.find(' (')] + # Ambiguous short-names are dropped + if short_name not in bracketless_duplicates: + item_name_groups[short_name] = [item] + # Short-name groups are unlisted + unlisted_item_name_groups.add(short_name) + # Items with a parent get assigned to their parent's group + if data.parent_item: + # The parent groups need a special name, otherwise they are ambiguous with the parent + parent_group = f"{data.parent_item} Items" + item_name_groups.setdefault(parent_group, []).append(item) + # Parent groups are unlisted + unlisted_item_name_groups.add(parent_group) + # All items get assigned to their race's group + race_group = data.race.name.capitalize() + item_name_groups.setdefault(race_group, []).append(item) + + +# Hand-made groups +item_name_groups["Aiur"] = [ + ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, + ItemNames.IMMORTAL, ItemNames.REAVER, + ItemNames.PHOENIX, ItemNames.SCOUT, ItemNames.ARBITER, ItemNames.CARRIER, +] +item_name_groups["Nerazim"] = [ + ItemNames.CENTURION, ItemNames.STALKER, ItemNames.DARK_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.DARK_ARCHON, + ItemNames.ANNIHILATOR, + ItemNames.CORSAIR, ItemNames.ORACLE, ItemNames.VOID_RAY, +] +item_name_groups["Tal'Darim"] = [ + ItemNames.SUPPLICANT, ItemNames.SLAYER, ItemNames.HAVOC, ItemNames.BLOOD_HUNTER, ItemNames.ASCENDANT, + ItemNames.VANGUARD, ItemNames.WRATHWALKER, + ItemNames.DESTROYER, ItemNames.MOTHERSHIP, + ItemNames.WARP_PRISM_PHASE_BLASTER, +] +item_name_groups["Purifier"] = [ + ItemNames.SENTINEL, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.ENERGIZER, + ItemNames.COLOSSUS, ItemNames.DISRUPTOR, + ItemNames.MIRAGE, ItemNames.TEMPEST, +] \ No newline at end of file diff --git a/worlds/sc2/ItemNames.py b/worlds/sc2/ItemNames.py new file mode 100644 index 000000000000..10c713910311 --- /dev/null +++ b/worlds/sc2/ItemNames.py @@ -0,0 +1,661 @@ +""" +A complete collection of Starcraft 2 item names as strings. +Users of this data may make some assumptions about the structure of a name: +* The upgrade for a unit will end with the unit's name in parentheses +* Weapon / armor upgrades may be grouped by a common prefix specified within this file +""" + +# Terran Units +MARINE = "Marine" +MEDIC = "Medic" +FIREBAT = "Firebat" +MARAUDER = "Marauder" +REAPER = "Reaper" +HELLION = "Hellion" +VULTURE = "Vulture" +GOLIATH = "Goliath" +DIAMONDBACK = "Diamondback" +SIEGE_TANK = "Siege Tank" +MEDIVAC = "Medivac" +WRAITH = "Wraith" +VIKING = "Viking" +BANSHEE = "Banshee" +BATTLECRUISER = "Battlecruiser" +GHOST = "Ghost" +SPECTRE = "Spectre" +THOR = "Thor" +RAVEN = "Raven" +SCIENCE_VESSEL = "Science Vessel" +PREDATOR = "Predator" +HERCULES = "Hercules" +# Extended units +LIBERATOR = "Liberator" +VALKYRIE = "Valkyrie" +WIDOW_MINE = "Widow Mine" +CYCLONE = "Cyclone" +HERC = "HERC" +WARHOUND = "Warhound" + +# Terran Buildings +BUNKER = "Bunker" +MISSILE_TURRET = "Missile Turret" +SENSOR_TOWER = "Sensor Tower" +PLANETARY_FORTRESS = "Planetary Fortress" +PERDITION_TURRET = "Perdition Turret" +HIVE_MIND_EMULATOR = "Hive Mind Emulator" +PSI_DISRUPTER = "Psi Disrupter" + +# Terran Weapon / Armor Upgrades +TERRAN_UPGRADE_PREFIX = "Progressive Terran" +TERRAN_INFANTRY_UPGRADE_PREFIX = f"{TERRAN_UPGRADE_PREFIX} Infantry" +TERRAN_VEHICLE_UPGRADE_PREFIX = f"{TERRAN_UPGRADE_PREFIX} Vehicle" +TERRAN_SHIP_UPGRADE_PREFIX = f"{TERRAN_UPGRADE_PREFIX} Ship" + +PROGRESSIVE_TERRAN_INFANTRY_WEAPON = f"{TERRAN_INFANTRY_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_TERRAN_INFANTRY_ARMOR = f"{TERRAN_INFANTRY_UPGRADE_PREFIX} Armor" +PROGRESSIVE_TERRAN_VEHICLE_WEAPON = f"{TERRAN_VEHICLE_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_TERRAN_VEHICLE_ARMOR = f"{TERRAN_VEHICLE_UPGRADE_PREFIX} Armor" +PROGRESSIVE_TERRAN_SHIP_WEAPON = f"{TERRAN_SHIP_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_TERRAN_SHIP_ARMOR = f"{TERRAN_SHIP_UPGRADE_PREFIX} Armor" +PROGRESSIVE_TERRAN_WEAPON_UPGRADE = f"{TERRAN_UPGRADE_PREFIX} Weapon Upgrade" +PROGRESSIVE_TERRAN_ARMOR_UPGRADE = f"{TERRAN_UPGRADE_PREFIX} Armor Upgrade" +PROGRESSIVE_TERRAN_INFANTRY_UPGRADE = f"{TERRAN_INFANTRY_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_TERRAN_VEHICLE_UPGRADE = f"{TERRAN_VEHICLE_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_TERRAN_SHIP_UPGRADE = f"{TERRAN_SHIP_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE = f"{TERRAN_UPGRADE_PREFIX} Weapon/Armor Upgrade" + +# Mercenaries +WAR_PIGS = "War Pigs" +DEVIL_DOGS = "Devil Dogs" +HAMMER_SECURITIES = "Hammer Securities" +SPARTAN_COMPANY = "Spartan Company" +SIEGE_BREAKERS = "Siege Breakers" +HELS_ANGELS = "Hel's Angels" +DUSK_WINGS = "Dusk Wings" +JACKSONS_REVENGE = "Jackson's Revenge" +SKIBIS_ANGELS = "Skibi's Angels" +DEATH_HEADS = "Death Heads" +WINGED_NIGHTMARES = "Winged Nightmares" +MIDNIGHT_RIDERS = "Midnight Riders" +BRYNHILDS = "Brynhilds" +JOTUN = "Jotun" + +# Lab / Global +ULTRA_CAPACITORS = "Ultra-Capacitors" +VANADIUM_PLATING = "Vanadium Plating" +ORBITAL_DEPOTS = "Orbital Depots" +MICRO_FILTERING = "Micro-Filtering" +AUTOMATED_REFINERY = "Automated Refinery" +COMMAND_CENTER_REACTOR = "Command Center Reactor" +TECH_REACTOR = "Tech Reactor" +ORBITAL_STRIKE = "Orbital Strike" +CELLULAR_REACTOR = "Cellular Reactor" +PROGRESSIVE_REGENERATIVE_BIO_STEEL = "Progressive Regenerative Bio-Steel" +PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM = "Progressive Fire-Suppression System" +PROGRESSIVE_ORBITAL_COMMAND = "Progressive Orbital Command" +STRUCTURE_ARMOR = "Structure Armor" +HI_SEC_AUTO_TRACKING = "Hi-Sec Auto Tracking" +ADVANCED_OPTICS = "Advanced Optics" +ROGUE_FORCES = "Rogue Forces" + +# Terran Unit Upgrades +BANSHEE_HYPERFLIGHT_ROTORS = "Hyperflight Rotors (Banshee)" +BANSHEE_INTERNAL_TECH_MODULE = "Internal Tech Module (Banshee)" +BANSHEE_LASER_TARGETING_SYSTEM = "Laser Targeting System (Banshee)" +BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS = "Progressive Cross-Spectrum Dampeners (Banshee)" +BANSHEE_SHOCKWAVE_MISSILE_BATTERY = "Shockwave Missile Battery (Banshee)" +BANSHEE_SHAPED_HULL = "Shaped Hull (Banshee)" +BANSHEE_ADVANCED_TARGETING_OPTICS = "Advanced Targeting Optics (Banshee)" +BANSHEE_DISTORTION_BLASTERS = "Distortion Blasters (Banshee)" +BANSHEE_ROCKET_BARRAGE = "Rocket Barrage (Banshee)" +BATTLECRUISER_ATX_LASER_BATTERY = "ATX Laser Battery (Battlecruiser)" +BATTLECRUISER_CLOAK = "Cloak (Battlecruiser)" +BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX = "Progressive Defensive Matrix (Battlecruiser)" +BATTLECRUISER_INTERNAL_TECH_MODULE = "Internal Tech Module (Battlecruiser)" +BATTLECRUISER_PROGRESSIVE_MISSILE_PODS = "Progressive Missile Pods (Battlecruiser)" +BATTLECRUISER_OPTIMIZED_LOGISTICS = "Optimized Logistics (Battlecruiser)" +BATTLECRUISER_TACTICAL_JUMP = "Tactical Jump (Battlecruiser)" +BATTLECRUISER_BEHEMOTH_PLATING = "Behemoth Plating (Battlecruiser)" +BATTLECRUISER_COVERT_OPS_ENGINES = "Covert Ops Engines (Battlecruiser)" +BUNKER_NEOSTEEL_BUNKER = "Neosteel Bunker (Bunker)" +BUNKER_PROJECTILE_ACCELERATOR = "Projectile Accelerator (Bunker)" +BUNKER_SHRIKE_TURRET = "Shrike Turret (Bunker)" +BUNKER_FORTIFIED_BUNKER = "Fortified Bunker (Bunker)" +CYCLONE_MAG_FIELD_ACCELERATORS = "Mag-Field Accelerators (Cyclone)" +CYCLONE_MAG_FIELD_LAUNCHERS = "Mag-Field Launchers (Cyclone)" +CYCLONE_RAPID_FIRE_LAUNCHERS = "Rapid Fire Launchers (Cyclone)" +CYCLONE_TARGETING_OPTICS = "Targeting Optics (Cyclone)" +CYCLONE_RESOURCE_EFFICIENCY = "Resource Efficiency (Cyclone)" +CYCLONE_INTERNAL_TECH_MODULE = "Internal Tech Module (Cyclone)" +DIAMONDBACK_BURST_CAPACITORS = "Burst Capacitors (Diamondback)" +DIAMONDBACK_HYPERFLUXOR = "Hyperfluxor (Diamondback)" +DIAMONDBACK_RESOURCE_EFFICIENCY = "Resource Efficiency (Diamondback)" +DIAMONDBACK_SHAPED_HULL = "Shaped Hull (Diamondback)" +DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL = "Progressive Tri-Lithium Power Cell (Diamondback)" +DIAMONDBACK_ION_THRUSTERS = "Ion Thrusters (Diamondback)" +FIREBAT_INCINERATOR_GAUNTLETS = "Incinerator Gauntlets (Firebat)" +FIREBAT_JUGGERNAUT_PLATING = "Juggernaut Plating (Firebat)" +FIREBAT_RESOURCE_EFFICIENCY = "Resource Efficiency (Firebat)" +FIREBAT_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Firebat)" +FIREBAT_INFERNAL_PRE_IGNITER = "Infernal Pre-Igniter (Firebat)" +FIREBAT_KINETIC_FOAM = "Kinetic Foam (Firebat)" +FIREBAT_NANO_PROJECTORS = "Nano Projectors (Firebat)" +GHOST_CRIUS_SUIT = "Crius Suit (Ghost)" +GHOST_EMP_ROUNDS = "EMP Rounds (Ghost)" +GHOST_LOCKDOWN = "Lockdown (Ghost)" +GHOST_OCULAR_IMPLANTS = "Ocular Implants (Ghost)" +GHOST_RESOURCE_EFFICIENCY = "Resource Efficiency (Ghost)" +GOLIATH_ARES_CLASS_TARGETING_SYSTEM = "Ares-Class Targeting System (Goliath)" +GOLIATH_JUMP_JETS = "Jump Jets (Goliath)" +GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM = "Multi-Lock Weapons System (Goliath)" +GOLIATH_OPTIMIZED_LOGISTICS = "Optimized Logistics (Goliath)" +GOLIATH_SHAPED_HULL = "Shaped Hull (Goliath)" +GOLIATH_RESOURCE_EFFICIENCY = "Resource Efficiency (Goliath)" +GOLIATH_INTERNAL_TECH_MODULE = "Internal Tech Module (Goliath)" +HELLION_HELLBAT_ASPECT = "Hellbat Aspect (Hellion)" +HELLION_JUMP_JETS = "Jump Jets (Hellion)" +HELLION_OPTIMIZED_LOGISTICS = "Optimized Logistics (Hellion)" +HELLION_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Hellion)" +HELLION_SMART_SERVOS = "Smart Servos (Hellion)" +HELLION_THERMITE_FILAMENTS = "Thermite Filaments (Hellion)" +HELLION_TWIN_LINKED_FLAMETHROWER = "Twin-Linked Flamethrower (Hellion)" +HELLION_INFERNAL_PLATING = "Infernal Plating (Hellion)" +HERC_JUGGERNAUT_PLATING = "Juggernaut Plating (HERC)" +HERC_KINETIC_FOAM = "Kinetic Foam (HERC)" +HERC_RESOURCE_EFFICIENCY = "Resource Efficiency (HERC)" +HERCULES_INTERNAL_FUSION_MODULE = "Internal Fusion Module (Hercules)" +HERCULES_TACTICAL_JUMP = "Tactical Jump (Hercules)" +LIBERATOR_ADVANCED_BALLISTICS = "Advanced Ballistics (Liberator)" +LIBERATOR_CLOAK = "Cloak (Liberator)" +LIBERATOR_LASER_TARGETING_SYSTEM = "Laser Targeting System (Liberator)" +LIBERATOR_OPTIMIZED_LOGISTICS = "Optimized Logistics (Liberator)" +LIBERATOR_RAID_ARTILLERY = "Raid Artillery (Liberator)" +LIBERATOR_SMART_SERVOS = "Smart Servos (Liberator)" +LIBERATOR_RESOURCE_EFFICIENCY = "Resource Efficiency (Liberator)" +MARAUDER_CONCUSSIVE_SHELLS = "Concussive Shells (Marauder)" +MARAUDER_INTERNAL_TECH_MODULE = "Internal Tech Module (Marauder)" +MARAUDER_KINETIC_FOAM = "Kinetic Foam (Marauder)" +MARAUDER_LASER_TARGETING_SYSTEM = "Laser Targeting System (Marauder)" +MARAUDER_MAGRAIL_MUNITIONS = "Magrail Munitions (Marauder)" +MARAUDER_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Marauder)" +MARAUDER_JUGGERNAUT_PLATING = "Juggernaut Plating (Marauder)" +MARINE_COMBAT_SHIELD = "Combat Shield (Marine)" +MARINE_LASER_TARGETING_SYSTEM = "Laser Targeting System (Marine)" +MARINE_MAGRAIL_MUNITIONS = "Magrail Munitions (Marine)" +MARINE_OPTIMIZED_LOGISTICS = "Optimized Logistics (Marine)" +MARINE_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Marine)" +MEDIC_ADVANCED_MEDIC_FACILITIES = "Advanced Medic Facilities (Medic)" +MEDIC_OPTICAL_FLARE = "Optical Flare (Medic)" +MEDIC_RESOURCE_EFFICIENCY = "Resource Efficiency (Medic)" +MEDIC_RESTORATION = "Restoration (Medic)" +MEDIC_STABILIZER_MEDPACKS = "Stabilizer Medpacks (Medic)" +MEDIC_ADAPTIVE_MEDPACKS = "Adaptive Medpacks (Medic)" +MEDIC_NANO_PROJECTOR = "Nano Projector (Medic)" +MEDIVAC_ADVANCED_HEALING_AI = "Advanced Healing AI (Medivac)" +MEDIVAC_AFTERBURNERS = "Afterburners (Medivac)" +MEDIVAC_EXPANDED_HULL = "Expanded Hull (Medivac)" +MEDIVAC_RAPID_DEPLOYMENT_TUBE = "Rapid Deployment Tube (Medivac)" +MEDIVAC_SCATTER_VEIL = "Scatter Veil (Medivac)" +MEDIVAC_ADVANCED_CLOAKING_FIELD = "Advanced Cloaking Field (Medivac)" +MISSILE_TURRET_HELLSTORM_BATTERIES = "Hellstorm Batteries (Missile Turret)" +MISSILE_TURRET_TITANIUM_HOUSING = "Titanium Housing (Missile Turret)" +PLANETARY_FORTRESS_PROGRESSIVE_AUGMENTED_THRUSTERS = "Progressive Augmented Thrusters (Planetary Fortress)" +PLANETARY_FORTRESS_ADVANCED_TARGETING = "Advanced Targeting (Planetary Fortress)" +PREDATOR_RESOURCE_EFFICIENCY = "Resource Efficiency (Predator)" +PREDATOR_CLOAK = "Cloak (Predator)" +PREDATOR_CHARGE = "Charge (Predator)" +PREDATOR_PREDATOR_S_FURY = "Predator's Fury (Predator)" +RAVEN_ANTI_ARMOR_MISSILE = "Anti-Armor Missile (Raven)" +RAVEN_BIO_MECHANICAL_REPAIR_DRONE = "Bio Mechanical Repair Drone (Raven)" +RAVEN_HUNTER_SEEKER_WEAPON = "Hunter-Seeker Weapon (Raven)" +RAVEN_INTERFERENCE_MATRIX = "Interference Matrix (Raven)" +RAVEN_INTERNAL_TECH_MODULE = "Internal Tech Module (Raven)" +RAVEN_RAILGUN_TURRET = "Railgun Turret (Raven)" +RAVEN_SPIDER_MINES = "Spider Mines (Raven)" +RAVEN_RESOURCE_EFFICIENCY = "Resource Efficiency (Raven)" +RAVEN_DURABLE_MATERIALS = "Durable Materials (Raven)" +REAPER_ADVANCED_CLOAKING_FIELD = "Advanced Cloaking Field (Reaper)" +REAPER_COMBAT_DRUGS = "Combat Drugs (Reaper)" +REAPER_G4_CLUSTERBOMB = "G-4 Clusterbomb (Reaper)" +REAPER_LASER_TARGETING_SYSTEM = "Laser Targeting System (Reaper)" +REAPER_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Reaper)" +REAPER_SPIDER_MINES = "Spider Mines (Reaper)" +REAPER_U238_ROUNDS = "U-238 Rounds (Reaper)" +REAPER_JET_PACK_OVERDRIVE = "Jet Pack Overdrive (Reaper)" +SCIENCE_VESSEL_DEFENSIVE_MATRIX = "Defensive Matrix (Science Vessel)" +SCIENCE_VESSEL_EMP_SHOCKWAVE = "EMP Shockwave (Science Vessel)" +SCIENCE_VESSEL_IMPROVED_NANO_REPAIR = "Improved Nano-Repair (Science Vessel)" +SCIENCE_VESSEL_ADVANCED_AI_SYSTEMS = "Advanced AI Systems (Science Vessel)" +SCV_ADVANCED_CONSTRUCTION = "Advanced Construction (SCV)" +SCV_DUAL_FUSION_WELDERS = "Dual-Fusion Welders (SCV)" +SCV_HOSTILE_ENVIRONMENT_ADAPTATION = "Hostile Environment Adaptation (SCV)" +SIEGE_TANK_ADVANCED_SIEGE_TECH = "Advanced Siege Tech (Siege Tank)" +SIEGE_TANK_GRADUATING_RANGE = "Graduating Range (Siege Tank)" +SIEGE_TANK_INTERNAL_TECH_MODULE = "Internal Tech Module (Siege Tank)" +SIEGE_TANK_JUMP_JETS = "Jump Jets (Siege Tank)" +SIEGE_TANK_LASER_TARGETING_SYSTEM = "Laser Targeting System (Siege Tank)" +SIEGE_TANK_MAELSTROM_ROUNDS = "Maelstrom Rounds (Siege Tank)" +SIEGE_TANK_SHAPED_BLAST = "Shaped Blast (Siege Tank)" +SIEGE_TANK_SMART_SERVOS = "Smart Servos (Siege Tank)" +SIEGE_TANK_SPIDER_MINES = "Spider Mines (Siege Tank)" +SIEGE_TANK_SHAPED_HULL = "Shaped Hull (Siege Tank)" +SIEGE_TANK_RESOURCE_EFFICIENCY = "Resource Efficiency (Siege Tank)" +SPECTRE_IMPALER_ROUNDS = "Impaler Rounds (Spectre)" +SPECTRE_NYX_CLASS_CLOAKING_MODULE = "Nyx-Class Cloaking Module (Spectre)" +SPECTRE_PSIONIC_LASH = "Psionic Lash (Spectre)" +SPECTRE_RESOURCE_EFFICIENCY = "Resource Efficiency (Spectre)" +SPIDER_MINE_CERBERUS_MINE = "Cerberus Mine (Spider Mine)" +SPIDER_MINE_HIGH_EXPLOSIVE_MUNITION = "High Explosive Munition (Spider Mine)" +THOR_330MM_BARRAGE_CANNON = "330mm Barrage Cannon (Thor)" +THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL = "Progressive Immortality Protocol (Thor)" +THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD = "Progressive High Impact Payload (Thor)" +THOR_BUTTON_WITH_A_SKULL_ON_IT = "Button With a Skull on It (Thor)" +THOR_LASER_TARGETING_SYSTEM = "Laser Targeting System (Thor)" +THOR_LARGE_SCALE_FIELD_CONSTRUCTION = "Large Scale Field Construction (Thor)" +VALKYRIE_AFTERBURNERS = "Afterburners (Valkyrie)" +VALKYRIE_FLECHETTE_MISSILES = "Flechette Missiles (Valkyrie)" +VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS = "Enhanced Cluster Launchers (Valkyrie)" +VALKYRIE_SHAPED_HULL = "Shaped Hull (Valkyrie)" +VALKYRIE_LAUNCHING_VECTOR_COMPENSATOR = "Launching Vector Compensator (Valkyrie)" +VALKYRIE_RESOURCE_EFFICIENCY = "Resource Efficiency (Valkyrie)" +VIKING_ANTI_MECHANICAL_MUNITION = "Anti-Mechanical Munition (Viking)" +VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM = "Phobos-Class Weapons System (Viking)" +VIKING_RIPWAVE_MISSILES = "Ripwave Missiles (Viking)" +VIKING_SMART_SERVOS = "Smart Servos (Viking)" +VIKING_SHREDDER_ROUNDS = "Shredder Rounds (Viking)" +VIKING_WILD_MISSILES = "W.I.L.D. Missiles (Viking)" +VULTURE_AUTO_LAUNCHERS = "Auto Launchers (Vulture)" +VULTURE_ION_THRUSTERS = "Ion Thrusters (Vulture)" +VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE = "Progressive Replenishable Magazine (Vulture)" +VULTURE_AUTO_REPAIR = "Auto-Repair (Vulture)" +WARHOUND_RESOURCE_EFFICIENCY = "Resource Efficiency (Warhound)" +WARHOUND_REINFORCED_PLATING = "Reinforced Plating (Warhound)" +WIDOW_MINE_BLACK_MARKET_LAUNCHERS = "Black Market Launchers (Widow Mine)" +WIDOW_MINE_CONCEALMENT = "Concealment (Widow Mine)" +WIDOW_MINE_DRILLING_CLAWS = "Drilling Claws (Widow Mine)" +WIDOW_MINE_EXECUTIONER_MISSILES = "Executioner Missiles (Widow Mine)" +WRAITH_ADVANCED_LASER_TECHNOLOGY = "Advanced Laser Technology (Wraith)" +WRAITH_DISPLACEMENT_FIELD = "Displacement Field (Wraith)" +WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS = "Progressive Tomahawk Power Cells (Wraith)" +WRAITH_TRIGGER_OVERRIDE = "Trigger Override (Wraith)" +WRAITH_INTERNAL_TECH_MODULE = "Internal Tech Module (Wraith)" +WRAITH_RESOURCE_EFFICIENCY = "Resource Efficiency (Wraith)" + +# Nova +NOVA_GHOST_VISOR = "Ghost Visor (Nova Equipment)" +NOVA_RANGEFINDER_OCULUS = "Rangefinder Oculus (Nova Equipment)" +NOVA_DOMINATION = "Domination (Nova Ability)" +NOVA_BLINK = "Blink (Nova Ability)" +NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE = "Progressive Stealth Suit Module (Nova Suit Module)" +NOVA_ENERGY_SUIT_MODULE = "Energy Suit Module (Nova Suit Module)" +NOVA_ARMORED_SUIT_MODULE = "Armored Suit Module (Nova Suit Module)" +NOVA_JUMP_SUIT_MODULE = "Jump Suit Module (Nova Suit Module)" +NOVA_C20A_CANISTER_RIFLE = "C20A Canister Rifle (Nova Weapon)" +NOVA_HELLFIRE_SHOTGUN = "Hellfire Shotgun (Nova Weapon)" +NOVA_PLASMA_RIFLE = "Plasma Rifle (Nova Weapon)" +NOVA_MONOMOLECULAR_BLADE = "Monomolecular Blade (Nova Weapon)" +NOVA_BLAZEFIRE_GUNBLADE = "Blazefire Gunblade (Nova Weapon)" +NOVA_STIM_INFUSION = "Stim Infusion (Nova Gadget)" +NOVA_PULSE_GRENADES = "Pulse Grenades (Nova Gadget)" +NOVA_FLASHBANG_GRENADES = "Flashbang Grenades (Nova Gadget)" +NOVA_IONIC_FORCE_FIELD = "Ionic Force Field (Nova Gadget)" +NOVA_HOLO_DECOY = "Holo Decoy (Nova Gadget)" +NOVA_NUKE = "Tac Nuke Strike (Nova Ability)" + +# Zerg Units +ZERGLING = "Zergling" +SWARM_QUEEN = "Swarm Queen" +ROACH = "Roach" +HYDRALISK = "Hydralisk" +ABERRATION = "Aberration" +MUTALISK = "Mutalisk" +SWARM_HOST = "Swarm Host" +INFESTOR = "Infestor" +ULTRALISK = "Ultralisk" +CORRUPTOR = "Corruptor" +SCOURGE = "Scourge" +BROOD_QUEEN = "Brood Queen" +DEFILER = "Defiler" + +# Zerg Buildings +SPORE_CRAWLER = "Spore Crawler" +SPINE_CRAWLER = "Spine Crawler" + +# Zerg Weapon / Armor Upgrades +ZERG_UPGRADE_PREFIX = "Progressive Zerg" +ZERG_FLYER_UPGRADE_PREFIX = f"{ZERG_UPGRADE_PREFIX} Flyer" + +PROGRESSIVE_ZERG_MELEE_ATTACK = f"{ZERG_UPGRADE_PREFIX} Melee Attack" +PROGRESSIVE_ZERG_MISSILE_ATTACK = f"{ZERG_UPGRADE_PREFIX} Missile Attack" +PROGRESSIVE_ZERG_GROUND_CARAPACE = f"{ZERG_UPGRADE_PREFIX} Ground Carapace" +PROGRESSIVE_ZERG_FLYER_ATTACK = f"{ZERG_FLYER_UPGRADE_PREFIX} Attack" +PROGRESSIVE_ZERG_FLYER_CARAPACE = f"{ZERG_FLYER_UPGRADE_PREFIX} Carapace" +PROGRESSIVE_ZERG_WEAPON_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Weapon Upgrade" +PROGRESSIVE_ZERG_ARMOR_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Armor Upgrade" +PROGRESSIVE_ZERG_GROUND_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Ground Upgrade" +PROGRESSIVE_ZERG_FLYER_UPGRADE = f"{ZERG_FLYER_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Weapon/Armor Upgrade" + +# Zerg Unit Upgrades +ZERGLING_HARDENED_CARAPACE = "Hardened Carapace (Zergling)" +ZERGLING_ADRENAL_OVERLOAD = "Adrenal Overload (Zergling)" +ZERGLING_METABOLIC_BOOST = "Metabolic Boost (Zergling)" +ZERGLING_SHREDDING_CLAWS = "Shredding Claws (Zergling)" +ROACH_HYDRIODIC_BILE = "Hydriodic Bile (Roach)" +ROACH_ADAPTIVE_PLATING = "Adaptive Plating (Roach)" +ROACH_TUNNELING_CLAWS = "Tunneling Claws (Roach)" +ROACH_GLIAL_RECONSTITUTION = "Glial Reconstitution (Roach)" +ROACH_ORGANIC_CARAPACE = "Organic Carapace (Roach)" +HYDRALISK_FRENZY = "Frenzy (Hydralisk)" +HYDRALISK_ANCILLARY_CARAPACE = "Ancillary Carapace (Hydralisk)" +HYDRALISK_GROOVED_SPINES = "Grooved Spines (Hydralisk)" +HYDRALISK_MUSCULAR_AUGMENTS = "Muscular Augments (Hydralisk)" +HYDRALISK_RESOURCE_EFFICIENCY = "Resource Efficiency (Hydralisk)" +BANELING_CORROSIVE_ACID = "Corrosive Acid (Baneling)" +BANELING_RUPTURE = "Rupture (Baneling)" +BANELING_REGENERATIVE_ACID = "Regenerative Acid (Baneling)" +BANELING_CENTRIFUGAL_HOOKS = "Centrifugal Hooks (Baneling)" +BANELING_TUNNELING_JAWS = "Tunneling Jaws (Baneling)" +BANELING_RAPID_METAMORPH = "Rapid Metamorph (Baneling)" +MUTALISK_VICIOUS_GLAIVE = "Vicious Glaive (Mutalisk)" +MUTALISK_RAPID_REGENERATION = "Rapid Regeneration (Mutalisk)" +MUTALISK_SUNDERING_GLAIVE = "Sundering Glaive (Mutalisk)" +MUTALISK_SEVERING_GLAIVE = "Severing Glaive (Mutalisk)" +MUTALISK_AERODYNAMIC_GLAIVE_SHAPE = "Aerodynamic Glaive Shape (Mutalisk)" +SWARM_HOST_BURROW = "Burrow (Swarm Host)" +SWARM_HOST_RAPID_INCUBATION = "Rapid Incubation (Swarm Host)" +SWARM_HOST_PRESSURIZED_GLANDS = "Pressurized Glands (Swarm Host)" +SWARM_HOST_LOCUST_METABOLIC_BOOST = "Locust Metabolic Boost (Swarm Host)" +SWARM_HOST_ENDURING_LOCUSTS = "Enduring Locusts (Swarm Host)" +SWARM_HOST_ORGANIC_CARAPACE = "Organic Carapace (Swarm Host)" +SWARM_HOST_RESOURCE_EFFICIENCY = "Resource Efficiency (Swarm Host)" +ULTRALISK_BURROW_CHARGE = "Burrow Charge (Ultralisk)" +ULTRALISK_TISSUE_ASSIMILATION = "Tissue Assimilation (Ultralisk)" +ULTRALISK_MONARCH_BLADES = "Monarch Blades (Ultralisk)" +ULTRALISK_ANABOLIC_SYNTHESIS = "Anabolic Synthesis (Ultralisk)" +ULTRALISK_CHITINOUS_PLATING = "Chitinous Plating (Ultralisk)" +ULTRALISK_ORGANIC_CARAPACE = "Organic Carapace (Ultralisk)" +ULTRALISK_RESOURCE_EFFICIENCY = "Resource Efficiency (Ultralisk)" +CORRUPTOR_CORRUPTION = "Corruption (Corruptor)" +CORRUPTOR_CAUSTIC_SPRAY = "Caustic Spray (Corruptor)" +SCOURGE_VIRULENT_SPORES = "Virulent Spores (Scourge)" +SCOURGE_RESOURCE_EFFICIENCY = "Resource Efficiency (Scourge)" +SCOURGE_SWARM_SCOURGE = "Swarm Scourge (Scourge)" +DEVOURER_CORROSIVE_SPRAY = "Corrosive Spray (Devourer)" +DEVOURER_GAPING_MAW = "Gaping Maw (Devourer)" +DEVOURER_IMPROVED_OSMOSIS = "Improved Osmosis (Devourer)" +DEVOURER_PRESCIENT_SPORES = "Prescient Spores (Devourer)" +GUARDIAN_PROLONGED_DISPERSION = "Prolonged Dispersion (Guardian)" +GUARDIAN_PRIMAL_ADAPTATION = "Primal Adaptation (Guardian)" +GUARDIAN_SORONAN_ACID = "Soronan Acid (Guardian)" +IMPALER_ADAPTIVE_TALONS = "Adaptive Talons (Impaler)" +IMPALER_SECRETION_GLANDS = "Secretion Glands (Impaler)" +IMPALER_HARDENED_TENTACLE_SPINES = "Hardened Tentacle Spines (Impaler)" +LURKER_SEISMIC_SPINES = "Seismic Spines (Lurker)" +LURKER_ADAPTED_SPINES = "Adapted Spines (Lurker)" +RAVAGER_POTENT_BILE = "Potent Bile (Ravager)" +RAVAGER_BLOATED_BILE_DUCTS = "Bloated Bile Ducts (Ravager)" +RAVAGER_DEEP_TUNNEL = "Deep Tunnel (Ravager)" +VIPER_PARASITIC_BOMB = "Parasitic Bomb (Viper)" +VIPER_PARALYTIC_BARBS = "Paralytic Barbs (Viper)" +VIPER_VIRULENT_MICROBES = "Virulent Microbes (Viper)" +BROOD_LORD_POROUS_CARTILAGE = "Porous Cartilage (Brood Lord)" +BROOD_LORD_EVOLVED_CARAPACE = "Evolved Carapace (Brood Lord)" +BROOD_LORD_SPLITTER_MITOSIS = "Splitter Mitosis (Brood Lord)" +BROOD_LORD_RESOURCE_EFFICIENCY = "Resource Efficiency (Brood Lord)" +INFESTOR_INFESTED_TERRAN = "Infested Terran (Infestor)" +INFESTOR_MICROBIAL_SHROUD = "Microbial Shroud (Infestor)" +SWARM_QUEEN_SPAWN_LARVAE = "Spawn Larvae (Swarm Queen)" +SWARM_QUEEN_DEEP_TUNNEL = "Deep Tunnel (Swarm Queen)" +SWARM_QUEEN_ORGANIC_CARAPACE = "Organic Carapace (Swarm Queen)" +SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION = "Bio-Mechanical Transfusion (Swarm Queen)" +SWARM_QUEEN_RESOURCE_EFFICIENCY = "Resource Efficiency (Swarm Queen)" +SWARM_QUEEN_INCUBATOR_CHAMBER = "Incubator Chamber (Swarm Queen)" +BROOD_QUEEN_FUNGAL_GROWTH = "Fungal Growth (Brood Queen)" +BROOD_QUEEN_ENSNARE = "Ensnare (Brood Queen)" +BROOD_QUEEN_ENHANCED_MITOCHONDRIA = "Enhanced Mitochondria (Brood Queen)" + +# Zerg Strains +ZERGLING_RAPTOR_STRAIN = "Raptor Strain (Zergling)" +ZERGLING_SWARMLING_STRAIN = "Swarmling Strain (Zergling)" +ROACH_VILE_STRAIN = "Vile Strain (Roach)" +ROACH_CORPSER_STRAIN = "Corpser Strain (Roach)" +BANELING_SPLITTER_STRAIN = "Splitter Strain (Baneling)" +BANELING_HUNTER_STRAIN = "Hunter Strain (Baneling)" +SWARM_HOST_CARRION_STRAIN = "Carrion Strain (Swarm Host)" +SWARM_HOST_CREEPER_STRAIN = "Creeper Strain (Swarm Host)" +ULTRALISK_NOXIOUS_STRAIN = "Noxious Strain (Ultralisk)" +ULTRALISK_TORRASQUE_STRAIN = "Torrasque Strain (Ultralisk)" + +# Morphs +ZERGLING_BANELING_ASPECT = "Baneling Aspect (Zergling)" +HYDRALISK_IMPALER_ASPECT = "Impaler Aspect (Hydralisk)" +HYDRALISK_LURKER_ASPECT = "Lurker Aspect (Hydralisk)" +MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT = "Brood Lord Aspect (Mutalisk/Corruptor)" +MUTALISK_CORRUPTOR_VIPER_ASPECT = "Viper Aspect (Mutalisk/Corruptor)" +MUTALISK_CORRUPTOR_GUARDIAN_ASPECT = "Guardian Aspect (Mutalisk/Corruptor)" +MUTALISK_CORRUPTOR_DEVOURER_ASPECT = "Devourer Aspect (Mutalisk/Corruptor)" +ROACH_RAVAGER_ASPECT = "Ravager Aspect (Roach)" + +# Zerg Mercs +INFESTED_MEDICS = "Infested Medics" +INFESTED_SIEGE_TANKS = "Infested Siege Tanks" +INFESTED_BANSHEES = "Infested Banshees" + +# Kerrigan Upgrades +KERRIGAN_KINETIC_BLAST = "Kinetic Blast (Kerrigan Tier 1)" +KERRIGAN_HEROIC_FORTITUDE = "Heroic Fortitude (Kerrigan Tier 1)" +KERRIGAN_LEAPING_STRIKE = "Leaping Strike (Kerrigan Tier 1)" +KERRIGAN_CRUSHING_GRIP = "Crushing Grip (Kerrigan Tier 2)" +KERRIGAN_CHAIN_REACTION = "Chain Reaction (Kerrigan Tier 2)" +KERRIGAN_PSIONIC_SHIFT = "Psionic Shift (Kerrigan Tier 2)" +KERRIGAN_WILD_MUTATION = "Wild Mutation (Kerrigan Tier 4)" +KERRIGAN_SPAWN_BANELINGS = "Spawn Banelings (Kerrigan Tier 4)" +KERRIGAN_MEND = "Mend (Kerrigan Tier 4)" +KERRIGAN_INFEST_BROODLINGS = "Infest Broodlings (Kerrigan Tier 6)" +KERRIGAN_FURY = "Fury (Kerrigan Tier 6)" +KERRIGAN_ABILITY_EFFICIENCY = "Ability Efficiency (Kerrigan Tier 6)" +KERRIGAN_APOCALYPSE = "Apocalypse (Kerrigan Tier 7)" +KERRIGAN_SPAWN_LEVIATHAN = "Spawn Leviathan (Kerrigan Tier 7)" +KERRIGAN_DROP_PODS = "Drop-Pods (Kerrigan Tier 7)" +KERRIGAN_PRIMAL_FORM = "Primal Form (Kerrigan)" + +# Misc Upgrades +KERRIGAN_ZERGLING_RECONSTITUTION = "Zergling Reconstitution (Kerrigan Tier 3)" +KERRIGAN_IMPROVED_OVERLORDS = "Improved Overlords (Kerrigan Tier 3)" +KERRIGAN_AUTOMATED_EXTRACTORS = "Automated Extractors (Kerrigan Tier 3)" +KERRIGAN_TWIN_DRONES = "Twin Drones (Kerrigan Tier 5)" +KERRIGAN_MALIGNANT_CREEP = "Malignant Creep (Kerrigan Tier 5)" +KERRIGAN_VESPENE_EFFICIENCY = "Vespene Efficiency (Kerrigan Tier 5)" +OVERLORD_VENTRAL_SACS = "Ventral Sacs (Overlord)" + +# Kerrigan Levels +KERRIGAN_LEVELS_1 = "1 Kerrigan Level" +KERRIGAN_LEVELS_2 = "2 Kerrigan Levels" +KERRIGAN_LEVELS_3 = "3 Kerrigan Levels" +KERRIGAN_LEVELS_4 = "4 Kerrigan Levels" +KERRIGAN_LEVELS_5 = "5 Kerrigan Levels" +KERRIGAN_LEVELS_6 = "6 Kerrigan Levels" +KERRIGAN_LEVELS_7 = "7 Kerrigan Levels" +KERRIGAN_LEVELS_8 = "8 Kerrigan Levels" +KERRIGAN_LEVELS_9 = "9 Kerrigan Levels" +KERRIGAN_LEVELS_10 = "10 Kerrigan Levels" +KERRIGAN_LEVELS_14 = "14 Kerrigan Levels" +KERRIGAN_LEVELS_35 = "35 Kerrigan Levels" +KERRIGAN_LEVELS_70 = "70 Kerrigan Levels" + +# Protoss Units +ZEALOT = "Zealot" +STALKER = "Stalker" +HIGH_TEMPLAR = "High Templar" +DARK_TEMPLAR = "Dark Templar" +IMMORTAL = "Immortal" +COLOSSUS = "Colossus" +PHOENIX = "Phoenix" +VOID_RAY = "Void Ray" +CARRIER = "Carrier" +OBSERVER = "Observer" +CENTURION = "Centurion" +SENTINEL = "Sentinel" +SUPPLICANT = "Supplicant" +INSTIGATOR = "Instigator" +SLAYER = "Slayer" +SENTRY = "Sentry" +ENERGIZER = "Energizer" +HAVOC = "Havoc" +SIGNIFIER = "Signifier" +ASCENDANT = "Ascendant" +AVENGER = "Avenger" +BLOOD_HUNTER = "Blood Hunter" +DRAGOON = "Dragoon" +DARK_ARCHON = "Dark Archon" +ADEPT = "Adept" +WARP_PRISM = "Warp Prism" +ANNIHILATOR = "Annihilator" +VANGUARD = "Vanguard" +WRATHWALKER = "Wrathwalker" +REAVER = "Reaver" +DISRUPTOR = "Disruptor" +MIRAGE = "Mirage" +CORSAIR = "Corsair" +DESTROYER = "Destroyer" +SCOUT = "Scout" +TEMPEST = "Tempest" +MOTHERSHIP = "Mothership" +ARBITER = "Arbiter" +ORACLE = "Oracle" + +# Upgrades +PROTOSS_UPGRADE_PREFIX = "Progressive Protoss" +PROTOSS_GROUND_UPGRADE_PREFIX = f"{PROTOSS_UPGRADE_PREFIX} Ground" +PROTOSS_AIR_UPGRADE_PREFIX = f"{PROTOSS_UPGRADE_PREFIX} Air" +PROGRESSIVE_PROTOSS_GROUND_WEAPON = f"{PROTOSS_GROUND_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_PROTOSS_GROUND_ARMOR = f"{PROTOSS_GROUND_UPGRADE_PREFIX} Armor" +PROGRESSIVE_PROTOSS_SHIELDS = f"{PROTOSS_UPGRADE_PREFIX} Shields" +PROGRESSIVE_PROTOSS_AIR_WEAPON = f"{PROTOSS_AIR_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_PROTOSS_AIR_ARMOR = f"{PROTOSS_AIR_UPGRADE_PREFIX} Armor" +PROGRESSIVE_PROTOSS_WEAPON_UPGRADE = f"{PROTOSS_UPGRADE_PREFIX} Weapon Upgrade" +PROGRESSIVE_PROTOSS_ARMOR_UPGRADE = f"{PROTOSS_UPGRADE_PREFIX} Armor Upgrade" +PROGRESSIVE_PROTOSS_GROUND_UPGRADE = f"{PROTOSS_GROUND_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_PROTOSS_AIR_UPGRADE = f"{PROTOSS_AIR_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE = f"{PROTOSS_UPGRADE_PREFIX} Weapon/Armor Upgrade" + +# Buildings +PHOTON_CANNON = "Photon Cannon" +KHAYDARIN_MONOLITH = "Khaydarin Monolith" +SHIELD_BATTERY = "Shield Battery" + +# Unit Upgrades +SUPPLICANT_BLOOD_SHIELD = "Blood Shield (Supplicant)" +SUPPLICANT_SOUL_AUGMENTATION = "Soul Augmentation (Supplicant)" +SUPPLICANT_SHIELD_REGENERATION = "Shield Regeneration (Supplicant)" +ADEPT_SHOCKWAVE = "Shockwave (Adept)" +ADEPT_RESONATING_GLAIVES = "Resonating Glaives (Adept)" +ADEPT_PHASE_BULWARK = "Phase Bulwark (Adept)" +STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES = "Disintegrating Particles (Stalker/Instigator/Slayer)" +STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION = "Particle Reflection (Stalker/Instigator/Slayer)" +DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS = "High Impact Phase Disruptor (Dragoon)" +DRAGOON_TRILLIC_COMPRESSION_SYSTEM = "Trillic Compression System (Dragoon)" +DRAGOON_SINGULARITY_CHARGE = "Singularity Charge (Dragoon)" +DRAGOON_ENHANCED_STRIDER_SERVOS = "Enhanced Strider Servos (Dragoon)" +SCOUT_COMBAT_SENSOR_ARRAY = "Combat Sensor Array (Scout)" +SCOUT_APIAL_SENSORS = "Apial Sensors (Scout)" +SCOUT_GRAVITIC_THRUSTERS = "Gravitic Thrusters (Scout)" +SCOUT_ADVANCED_PHOTON_BLASTERS = "Advanced Photon Blasters (Scout)" +TEMPEST_TECTONIC_DESTABILIZERS = "Tectonic Destabilizers (Tempest)" +TEMPEST_QUANTIC_REACTOR = "Quantic Reactor (Tempest)" +TEMPEST_GRAVITY_SLING = "Gravity Sling (Tempest)" +PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX = "Ionic Wavelength Flux (Phoenix/Mirage)" +PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS = "Anion Pulse-Crystals (Phoenix/Mirage)" +CORSAIR_STEALTH_DRIVE = "Stealth Drive (Corsair)" +CORSAIR_ARGUS_JEWEL = "Argus Jewel (Corsair)" +CORSAIR_SUSTAINING_DISRUPTION = "Sustaining Disruption (Corsair)" +CORSAIR_NEUTRON_SHIELDS = "Neutron Shields (Corsair)" +ORACLE_STEALTH_DRIVE = "Stealth Drive (Oracle)" +ORACLE_STASIS_CALIBRATION = "Stasis Calibration (Oracle)" +ORACLE_TEMPORAL_ACCELERATION_BEAM = "Temporal Acceleration Beam (Oracle)" +ARBITER_CHRONOSTATIC_REINFORCEMENT = "Chronostatic Reinforcement (Arbiter)" +ARBITER_KHAYDARIN_CORE = "Khaydarin Core (Arbiter)" +ARBITER_SPACETIME_ANCHOR = "Spacetime Anchor (Arbiter)" +ARBITER_RESOURCE_EFFICIENCY = "Resource Efficiency (Arbiter)" +ARBITER_ENHANCED_CLOAK_FIELD = "Enhanced Cloak Field (Arbiter)" +CARRIER_GRAVITON_CATAPULT = "Graviton Catapult (Carrier)" +CARRIER_HULL_OF_PAST_GLORIES = "Hull of Past Glories (Carrier)" +VOID_RAY_DESTROYER_FLUX_VANES = "Flux Vanes (Void Ray/Destroyer)" +DESTROYER_REFORGED_BLOODSHARD_CORE = "Reforged Bloodshard Core (Destroyer)" +WARP_PRISM_GRAVITIC_DRIVE = "Gravitic Drive (Warp Prism)" +WARP_PRISM_PHASE_BLASTER = "Phase Blaster (Warp Prism)" +WARP_PRISM_WAR_CONFIGURATION = "War Configuration (Warp Prism)" +OBSERVER_GRAVITIC_BOOSTERS = "Gravitic Boosters (Observer)" +OBSERVER_SENSOR_ARRAY = "Sensor Array (Observer)" +REAVER_SCARAB_DAMAGE = "Scarab Damage (Reaver)" +REAVER_SOLARITE_PAYLOAD = "Solarite Payload (Reaver)" +REAVER_REAVER_CAPACITY = "Reaver Capacity (Reaver)" +REAVER_RESOURCE_EFFICIENCY = "Resource Efficiency (Reaver)" +VANGUARD_AGONY_LAUNCHERS = "Agony Launchers (Vanguard)" +VANGUARD_MATTER_DISPERSION = "Matter Dispersion (Vanguard)" +IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE = "Singularity Charge (Immortal/Annihilator)" +IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS = "Advanced Targeting Mechanics (Immortal/Annihilator)" +COLOSSUS_PACIFICATION_PROTOCOL = "Pacification Protocol (Colossus)" +WRATHWALKER_RAPID_POWER_CYCLING = "Rapid Power Cycling (Wrathwalker)" +WRATHWALKER_EYE_OF_WRATH = "Eye of Wrath (Wrathwalker)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN = "Shroud of Adun (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING = "Shadow Guard Training (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK = "Blink (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY = "Resource Efficiency (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_DARK_ARCHON_MELD = "Dark Archon Meld (Dark Templar)" +HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM = "Unshackled Psionic Storm (High Templar/Signifier)" +HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION = "Hallucination (High Templar/Signifier)" +HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET = "Khaydarin Amulet (High Templar/Signifier)" +ARCHON_HIGH_ARCHON = "High Archon (Archon)" +DARK_ARCHON_FEEDBACK = "Feedback (Dark Archon)" +DARK_ARCHON_MAELSTROM = "Maelstrom (Dark Archon)" +DARK_ARCHON_ARGUS_TALISMAN = "Argus Talisman (Dark Archon)" +ASCENDANT_POWER_OVERWHELMING = "Power Overwhelming (Ascendant)" +ASCENDANT_CHAOTIC_ATTUNEMENT = "Chaotic Attunement (Ascendant)" +ASCENDANT_BLOOD_AMULET = "Blood Amulet (Ascendant)" +SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE = "Cloaking Module (Sentry/Energizer/Havoc)" +SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING = "Rapid Recharging (Sentry/Energizer/Havoc/Shield Battery)" +SENTRY_FORCE_FIELD = "Force Field (Sentry)" +SENTRY_HALLUCINATION = "Hallucination (Sentry)" +ENERGIZER_RECLAMATION = "Reclamation (Energizer)" +ENERGIZER_FORGED_CHASSIS = "Forged Chassis (Energizer)" +HAVOC_DETECT_WEAKNESS = "Detect Weakness (Havoc)" +HAVOC_BLOODSHARD_RESONANCE = "Bloodshard Resonance (Havoc)" +ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS = "Leg Enhancements (Zealot/Sentinel/Centurion)" +ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY = "Shield Capacity (Zealot/Sentinel/Centurion)" + +# Spear Of Adun +SOA_CHRONO_SURGE = "Chrono Surge (Spear of Adun Calldown)" +SOA_PROGRESSIVE_PROXY_PYLON = "Progressive Proxy Pylon (Spear of Adun Calldown)" +SOA_PYLON_OVERCHARGE = "Pylon Overcharge (Spear of Adun Calldown)" +SOA_ORBITAL_STRIKE = "Orbital Strike (Spear of Adun Calldown)" +SOA_TEMPORAL_FIELD = "Temporal Field (Spear of Adun Calldown)" +SOA_SOLAR_LANCE = "Solar Lance (Spear of Adun Calldown)" +SOA_MASS_RECALL = "Mass Recall (Spear of Adun Calldown)" +SOA_SHIELD_OVERCHARGE = "Shield Overcharge (Spear of Adun Calldown)" +SOA_DEPLOY_FENIX = "Deploy Fenix (Spear of Adun Calldown)" +SOA_PURIFIER_BEAM = "Purifier Beam (Spear of Adun Calldown)" +SOA_TIME_STOP = "Time Stop (Spear of Adun Calldown)" +SOA_SOLAR_BOMBARDMENT = "Solar Bombardment (Spear of Adun Calldown)" + +# Generic upgrades +MATRIX_OVERLOAD = "Matrix Overload" +QUATRO = "Quatro" +NEXUS_OVERCHARGE = "Nexus Overcharge" +ORBITAL_ASSIMILATORS = "Orbital Assimilators" +WARP_HARMONIZATION = "Warp Harmonization" +GUARDIAN_SHELL = "Guardian Shell" +RECONSTRUCTION_BEAM = "Reconstruction Beam (Spear of Adun Auto-Cast)" +OVERWATCH = "Overwatch (Spear of Adun Auto-Cast)" +SUPERIOR_WARP_GATES = "Superior Warp Gates" +ENHANCED_TARGETING = "Enhanced Targeting" +OPTIMIZED_ORDNANCE = "Optimized Ordnance" +KHALAI_INGENUITY = "Khalai Ingenuity" +AMPLIFIED_ASSIMILATORS = "Amplified Assimilators" + +# Filler items +STARTING_MINERALS = "Additional Starting Minerals" +STARTING_VESPENE = "Additional Starting Vespene" +STARTING_SUPPLY = "Additional Starting Supply" +NOTHING = "Nothing" diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py new file mode 100644 index 000000000000..8277d0e7e13d --- /dev/null +++ b/worlds/sc2/Items.py @@ -0,0 +1,2553 @@ +import inspect +from pydoc import describe + +from BaseClasses import Item, ItemClassification, MultiWorld +import typing + +from .Options import get_option_value, RequiredTactics +from .MissionTables import SC2Mission, SC2Race, SC2Campaign, campaign_mission_table +from . import ItemNames +from worlds.AutoWorld import World + + +class ItemData(typing.NamedTuple): + code: int + type: str + number: int # Important for bot commands to send the item into the game + race: SC2Race + classification: ItemClassification = ItemClassification.useful + quantity: int = 1 + parent_item: typing.Optional[str] = None + origin: typing.Set[str] = {"wol"} + description: typing.Optional[str] = None + important_for_filtering: bool = False + + def is_important_for_filtering(self): + return self.important_for_filtering \ + or self.classification == ItemClassification.progression \ + or self.classification == ItemClassification.progression_skip_balancing + + +class StarcraftItem(Item): + game: str = "Starcraft 2" + + +def get_full_item_list(): + return item_table + + +SC2WOL_ITEM_ID_OFFSET = 1000 +SC2HOTS_ITEM_ID_OFFSET = SC2WOL_ITEM_ID_OFFSET + 1000 +SC2LOTV_ITEM_ID_OFFSET = SC2HOTS_ITEM_ID_OFFSET + 1000 + +# Descriptions +WEAPON_ARMOR_UPGRADE_NOTE = inspect.cleandoc(""" + Must be researched during the mission if the mission type isn't set to auto-unlock generic upgrades. +""") +LASER_TARGETING_SYSTEMS_DESCRIPTION = "Increases vision by 2 and weapon range by 1." +STIMPACK_SMALL_COST = 10 +STIMPACK_SMALL_HEAL = 30 +STIMPACK_LARGE_COST = 20 +STIMPACK_LARGE_HEAL = 60 +STIMPACK_TEMPLATE = inspect.cleandoc(""" + Level 1: Stimpack: Increases unit movement and attack speed for 15 seconds. Injures the unit for {} life. + Level 2: Super Stimpack: Instead of injuring the unit, heals the unit for {} life instead. +""") +STIMPACK_SMALL_DESCRIPTION = STIMPACK_TEMPLATE.format(STIMPACK_SMALL_COST, STIMPACK_SMALL_HEAL) +STIMPACK_LARGE_DESCRIPTION = STIMPACK_TEMPLATE.format(STIMPACK_LARGE_COST, STIMPACK_LARGE_HEAL) +SMART_SERVOS_DESCRIPTION = "Increases transformation speed between modes." +INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE = "{} can be trained from a {} without an attached Tech Lab." +RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE = "Reduces {} resource and supply cost." +RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE = "Reduces {} resource cost." +CLOAK_DESCRIPTION_TEMPLATE = "Allows {} to use the Cloak ability." + + +# The items are sorted by their IDs. The IDs shall be kept for compatibility with older games. +item_table = { + # WoL + ItemNames.MARINE: + ItemData(0 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="General-purpose infantry."), + ItemNames.MEDIC: + ItemData(1 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Support trooper. Heals nearby biological units."), + ItemNames.FIREBAT: + ItemData(2 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Specialized anti-infantry attacker."), + ItemNames.MARAUDER: + ItemData(3 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy assault infantry."), + ItemNames.REAPER: + ItemData(4 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Raider. Capable of jumping up and down cliffs. Throws explosive mines."), + ItemNames.HELLION: + ItemData(5 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Fast scout. Has a flame attack that damages all enemy units in its line of fire."), + ItemNames.VULTURE: + ItemData(6 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Fast skirmish unit. Can use the Spider Mine ability."), + ItemNames.GOLIATH: + ItemData(7 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy-fire support unit."), + ItemNames.DIAMONDBACK: + ItemData(8 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Fast, high-damage hovertank. Rail Gun can fire while the Diamondback is moving."), + ItemNames.SIEGE_TANK: + ItemData(9 + SC2WOL_ITEM_ID_OFFSET, "Unit", 9, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy tank. Long-range artillery in Siege Mode."), + ItemNames.MEDIVAC: + ItemData(10 + SC2WOL_ITEM_ID_OFFSET, "Unit", 10, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Air transport. Heals nearby biological units."), + ItemNames.WRAITH: + ItemData(11 + SC2WOL_ITEM_ID_OFFSET, "Unit", 11, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Highly mobile flying unit. Excellent at surgical strikes."), + ItemNames.VIKING: + ItemData(12 + SC2WOL_ITEM_ID_OFFSET, "Unit", 12, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Durable support flyer. Loaded with strong anti-capital air missiles. + Can switch into Assault Mode to attack ground units. + """ + )), + ItemNames.BANSHEE: + ItemData(13 + SC2WOL_ITEM_ID_OFFSET, "Unit", 13, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Tactical-strike aircraft."), + ItemNames.BATTLECRUISER: + ItemData(14 + SC2WOL_ITEM_ID_OFFSET, "Unit", 14, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Powerful warship."), + ItemNames.GHOST: + ItemData(15 + SC2WOL_ITEM_ID_OFFSET, "Unit", 15, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Infiltration unit. Can use Snipe and Cloak abilities. Can also call down Tactical Nukes. + """ + )), + ItemNames.SPECTRE: + ItemData(16 + SC2WOL_ITEM_ID_OFFSET, "Unit", 16, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Infiltration unit. Can use Ultrasonic Pulse, Psionic Lash, and Cloak. + Can also call down Tactical Nukes. + """ + )), + ItemNames.THOR: + ItemData(17 + SC2WOL_ITEM_ID_OFFSET, "Unit", 17, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy assault mech."), + # EE units + ItemNames.LIBERATOR: + ItemData(18 + SC2WOL_ITEM_ID_OFFSET, "Unit", 18, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"nco", "ext"}, + description=inspect.cleandoc( + """ + Artillery fighter. Loaded with missiles that deal area damage to enemy air targets. + Can switch into Defender Mode to provide siege support. + """ + )), + ItemNames.VALKYRIE: + ItemData(19 + SC2WOL_ITEM_ID_OFFSET, "Unit", 19, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"bw"}, + description=inspect.cleandoc( + """ + Advanced anti-aircraft fighter. + Able to use cluster missiles that deal area damage to air targets. + """ + )), + ItemNames.WIDOW_MINE: + ItemData(20 + SC2WOL_ITEM_ID_OFFSET, "Unit", 20, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Robotic mine. Launches missiles at nearby enemy units while burrowed. + Attacks deal splash damage in a small area around the target. + Widow Mine is revealed when Sentinel Missile is on cooldown. + """ + )), + ItemNames.CYCLONE: + ItemData(21 + SC2WOL_ITEM_ID_OFFSET, "Unit", 21, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Mobile assault vehicle. Can use Lock On to quickly fire while moving. + """ + )), + ItemNames.HERC: + ItemData(22 + SC2WOL_ITEM_ID_OFFSET, "Unit", 26, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Front-line infantry. Can use Grapple. + """ + )), + ItemNames.WARHOUND: + ItemData(23 + SC2WOL_ITEM_ID_OFFSET, "Unit", 27, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Anti-vehicle mech. Haywire missiles do bonus damage to mechanical units. + """ + )), + + # Some other items are moved to Upgrade group because of the way how the bot message is parsed + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: + ItemData(100 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases damage of Terran infantry units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: + ItemData(102 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases armor of Terran infantry units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: + ItemData(103 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases damage of Terran vehicle units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: + ItemData(104 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases armor of Terran vehicle units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON: + ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases damage of Terran starship units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR: + ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases armor of Terran starship units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + # Upgrade bundle 'number' values are used as indices to get affected 'number's + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 1, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 3, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 5, SC2Race.TERRAN, quantity=3), + + # Unit and structure upgrades + ItemNames.BUNKER_PROJECTILE_ACCELERATOR: + ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Increases range of all units in the Bunker by 1."), + ItemNames.BUNKER_NEOSTEEL_BUNKER: + ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Increases the number of Bunker slots by 2."), + ItemNames.MISSILE_TURRET_TITANIUM_HOUSING: + ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MISSILE_TURRET, + description="Increases Missile Turret life by 75."), + ItemNames.MISSILE_TURRET_HELLSTORM_BATTERIES: + ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3, SC2Race.TERRAN, + parent_item=ItemNames.MISSILE_TURRET, + description="The Missile Turret unleashes an additional flurry of missiles with each attack."), + ItemNames.SCV_ADVANCED_CONSTRUCTION: + ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4, SC2Race.TERRAN, + description="Multiple SCVs can construct a structure, reducing its construction time."), + ItemNames.SCV_DUAL_FUSION_WELDERS: + ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5, SC2Race.TERRAN, + description="SCVs repair twice as fast."), + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM: + ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 24, SC2Race.TERRAN, + quantity=2, + description=inspect.cleandoc( + """ + Level 1: While on low health, Terran structures are repaired to half health instead of burning down. + Level 2: Terran structures are repaired to full health instead of half health + """ + )), + ItemNames.PROGRESSIVE_ORBITAL_COMMAND: + ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 26, SC2Race.TERRAN, + quantity=2, classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Level 1: Allows Command Centers to use Scanner Sweep and Calldown: MULE abilities. + Level 2: Orbital Command abilities work even in Planetary Fortress mode. + """ + )), + ItemNames.MARINE_PROGRESSIVE_STIMPACK: + ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MARINE, quantity=2, + description=STIMPACK_SMALL_DESCRIPTION), + ItemNames.MARINE_COMBAT_SHIELD: + ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MARINE, + description="Increases Marine life by 10."), + ItemNames.MEDIC_ADVANCED_MEDIC_FACILITIES: + ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Medics", "Barracks")), + ItemNames.MEDIC_STABILIZER_MEDPACKS: + ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MEDIC, + description="Increases Medic heal speed. Reduces the amount of energy required for each heal."), + ItemNames.FIREBAT_INCINERATOR_GAUNTLETS: + ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.FIREBAT, + description="Increases Firebat's damage radius by 40%"), + ItemNames.FIREBAT_JUGGERNAUT_PLATING: + ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, + description="Increases Firebat's armor by 2."), + ItemNames.MARAUDER_CONCUSSIVE_SHELLS: + ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, + description="Marauder attack temporarily slows all units in target area."), + ItemNames.MARAUDER_KINETIC_FOAM: + ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, + description="Increases Marauder life by 25."), + ItemNames.REAPER_U238_ROUNDS: + ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, + description=inspect.cleandoc( + """ + Increases Reaper pistol attack range by 1. + Reaper pistols do additional 3 damage to Light Armor. + """ + )), + ItemNames.REAPER_G4_CLUSTERBOMB: + ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.REAPER, + description="Timed explosive that does heavy area damage."), + ItemNames.CYCLONE_MAG_FIELD_ACCELERATORS: + ItemData(218 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 18, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="Increases Cyclone Lock On damage"), + ItemNames.CYCLONE_MAG_FIELD_LAUNCHERS: + ItemData(219 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 19, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="Increases Cyclone attack range by 2."), + ItemNames.MARINE_LASER_TARGETING_SYSTEM: + ItemData(220 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.MARINE_MAGRAIL_MUNITIONS: + ItemData(221 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 20, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MARINE, origin={"nco"}, + description="Deals 20 damage to target unit. Autocast on attack with a cooldown."), + ItemNames.MARINE_OPTIMIZED_LOGISTICS: + ItemData(222 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 21, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}, + description="Increases Marine training speed."), + ItemNames.MEDIC_RESTORATION: + ItemData(223 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 22, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}, + description="Removes negative status effects from target allied unit."), + ItemNames.MEDIC_OPTICAL_FLARE: + ItemData(224 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 23, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}, + description="Reduces vision range of target enemy unit. Disables detection."), + ItemNames.MEDIC_RESOURCE_EFFICIENCY: + ItemData(225 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Medic")), + ItemNames.FIREBAT_PROGRESSIVE_STIMPACK: + ItemData(226 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 6, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, quantity=2, origin={"bw"}, + description=STIMPACK_LARGE_DESCRIPTION), + ItemNames.FIREBAT_RESOURCE_EFFICIENCY: + ItemData(227 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 25, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Firebat")), + ItemNames.MARAUDER_PROGRESSIVE_STIMPACK: + ItemData(228 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 8, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, quantity=2, origin={"nco"}, + description=STIMPACK_LARGE_DESCRIPTION), + ItemNames.MARAUDER_LASER_TARGETING_SYSTEM: + ItemData(229 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 26, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.MARAUDER_MAGRAIL_MUNITIONS: + ItemData(230 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 27, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}, + description="Deals 20 damage to target unit. Autocast on attack with a cooldown."), + ItemNames.MARAUDER_INTERNAL_TECH_MODULE: + ItemData(231 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 28, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Marauders", "Barracks")), + ItemNames.SCV_HOSTILE_ENVIRONMENT_ADAPTATION: + ItemData(232 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 29, SC2Race.TERRAN, + classification=ItemClassification.filler, origin={"bw"}, + description="Increases SCV life by 15 and attack speed slightly."), + ItemNames.MEDIC_ADAPTIVE_MEDPACKS: + ItemData(233 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MEDIC, origin={"ext"}, + description="Allows Medics to heal mechanical and air units."), + ItemNames.MEDIC_NANO_PROJECTOR: + ItemData(234 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"ext"}, + description="Increases Medic heal range by 2."), + ItemNames.FIREBAT_INFERNAL_PRE_IGNITER: + ItemData(235 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"bw"}, + description="Firebats do an additional 4 damage to Light Armor."), + ItemNames.FIREBAT_KINETIC_FOAM: + ItemData(236 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"ext"}, + description="Increases Firebat life by 100."), + ItemNames.FIREBAT_NANO_PROJECTORS: + ItemData(237 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"ext"}, + description="Increases Firebat attack range by 2"), + ItemNames.MARAUDER_JUGGERNAUT_PLATING: + ItemData(238 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, origin={"ext"}, + description="Increases Marauder's armor by 2."), + ItemNames.REAPER_JET_PACK_OVERDRIVE: + ItemData(239 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, origin={"ext"}, + description=inspect.cleandoc( + """ + Allows the Reaper to fly for 10 seconds. + While flying, the Reaper can attack air units. + """ + )), + ItemNames.HELLION_INFERNAL_PLATING: + ItemData(240 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, origin={"ext"}, + description="Increases Hellion and Hellbat armor by 2."), + ItemNames.VULTURE_AUTO_REPAIR: + ItemData(241 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8, SC2Race.TERRAN, + parent_item=ItemNames.VULTURE, origin={"ext"}, + description="Vultures regenerate life."), + ItemNames.GOLIATH_SHAPED_HULL: + ItemData(242 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "ext"}, + description="Increases Goliath life by 25."), + ItemNames.GOLIATH_RESOURCE_EFFICIENCY: + ItemData(243 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, SC2Race.TERRAN, + parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Goliath")), + ItemNames.GOLIATH_INTERNAL_TECH_MODULE: + ItemData(244 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Goliaths", "Factory")), + ItemNames.SIEGE_TANK_SHAPED_HULL: + ItemData(245 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco", "ext"}, + description="Increases Siege Tank life by 25."), + ItemNames.SIEGE_TANK_RESOURCE_EFFICIENCY: + ItemData(246 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Siege Tank")), + ItemNames.PREDATOR_CLOAK: + ItemData(247 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}, + description=CLOAK_DESCRIPTION_TEMPLATE.format("Predators")), + ItemNames.PREDATOR_CHARGE: + ItemData(248 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}, + description="Allows Predators to intercept enemy ground units."), + ItemNames.MEDIVAC_SCATTER_VEIL: + ItemData(249 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, SC2Race.TERRAN, + parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Medivacs get 100 shields."), + ItemNames.REAPER_PROGRESSIVE_STIMPACK: + ItemData(250 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 10, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, quantity=2, origin={"nco"}, + description=STIMPACK_SMALL_DESCRIPTION), + ItemNames.REAPER_LASER_TARGETING_SYSTEM: + ItemData(251 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.REAPER_ADVANCED_CLOAKING_FIELD: + ItemData(252 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, origin={"nco"}, + description="Reapers are permanently cloaked."), + ItemNames.REAPER_SPIDER_MINES: + ItemData(253 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}, + important_for_filtering=True, + description="Allows Reapers to lay Spider Mines. 3 charges per Reaper."), + ItemNames.REAPER_COMBAT_DRUGS: + ItemData(254 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"ext"}, + description="Reapers regenerate life while out of combat."), + ItemNames.HELLION_HELLBAT_ASPECT: + ItemData(255 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.HELLION, origin={"nco"}, + description="Allows Hellions to transform into Hellbats."), + ItemNames.HELLION_SMART_SERVOS: + ItemData(256 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, origin={"nco"}, + description="Transforms faster between modes. Hellions can attack while moving."), + ItemNames.HELLION_OPTIMIZED_LOGISTICS: + ItemData(257 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}, + description="Increases Hellion training speed."), + ItemNames.HELLION_JUMP_JETS: + ItemData(258 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}, + description=inspect.cleandoc( + """ + Increases movement speed in Hellion mode. + In Hellbat mode, launches the Hellbat toward enemy ground units and briefly stuns them. + """ + )), + ItemNames.HELLION_PROGRESSIVE_STIMPACK: + ItemData(259 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 12, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, quantity=2, origin={"nco"}, + description=STIMPACK_LARGE_DESCRIPTION), + ItemNames.VULTURE_ION_THRUSTERS: + ItemData(260 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, origin={"bw"}, + description="Increases Vulture movement speed."), + ItemNames.VULTURE_AUTO_LAUNCHERS: + ItemData(261 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 26, SC2Race.TERRAN, + parent_item=ItemNames.VULTURE, origin={"bw"}, + description="Allows Vultures to attack while moving."), + ItemNames.SPIDER_MINE_HIGH_EXPLOSIVE_MUNITION: + ItemData(262 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 27, SC2Race.TERRAN, + origin={"bw"}, + description="Increases Spider mine damage."), + ItemNames.GOLIATH_JUMP_JETS: + ItemData(263 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 28, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.GOLIATH, origin={"nco"}, + description="Allows Goliaths to jump up and down cliffs."), + ItemNames.GOLIATH_OPTIMIZED_LOGISTICS: + ItemData(264 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 29, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco"}, + description="Increases Goliath training speed."), + ItemNames.DIAMONDBACK_HYPERFLUXOR: + ItemData(265 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 0, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description="Increases Diamondback attack speed."), + ItemNames.DIAMONDBACK_BURST_CAPACITORS: + ItemData(266 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description=inspect.cleandoc( + """ + While not attacking, the Diamondback charges its weapon. + The next attack does 10 additional damage. + """ + )), + ItemNames.DIAMONDBACK_RESOURCE_EFFICIENCY: + ItemData(267 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 2, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Diamondback")), + ItemNames.SIEGE_TANK_JUMP_JETS: + ItemData(268 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=inspect.cleandoc( + """ + Repositions Siege Tank to a target location. + Can be used in either mode and to jump up and down cliffs. + """ + )), + ItemNames.SIEGE_TANK_SPIDER_MINES: + ItemData(269 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 4, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + important_for_filtering=True, + description=inspect.cleandoc( + """ + Allows Siege Tanks to lay Spider Mines. + Lays 3 Spider Mines at once. 3 charges + """ + )), + ItemNames.SIEGE_TANK_SMART_SERVOS: + ItemData(270 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 5, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=SMART_SERVOS_DESCRIPTION), + ItemNames.SIEGE_TANK_GRADUATING_RANGE: + ItemData(271 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"ext"}, + description=inspect.cleandoc( + """ + Increases the Siege Tank's attack range by 1 every 3 seconds while in Siege Mode, + up to a maximum of 5 additional range. + """ + )), + ItemNames.SIEGE_TANK_LASER_TARGETING_SYSTEM: + ItemData(272 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 7, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.SIEGE_TANK_ADVANCED_SIEGE_TECH: + ItemData(273 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 8, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, origin={"ext"}, + description="Siege Tanks gain +3 armor in Siege Mode."), + ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE: + ItemData(274 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 9, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Siege Tanks", "Factory")), + ItemNames.PREDATOR_RESOURCE_EFFICIENCY: + ItemData(275 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 10, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}, + description="Decreases Predator resource and supply cost."), + ItemNames.MEDIVAC_EXPANDED_HULL: + ItemData(276 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 11, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Increases Medivac cargo space by 4."), + ItemNames.MEDIVAC_AFTERBURNERS: + ItemData(277 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 12, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Ability. Temporarily increases the Medivac's movement speed by 70%."), + ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY: + ItemData(278 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 13, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.WRAITH, origin={"ext"}, + description=inspect.cleandoc( + """ + Burst Lasers do more damage and can hit both ground and air targets. + Replaces Gemini Missiles weapon. + """ + )), + ItemNames.VIKING_SMART_SERVOS: + ItemData(279 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 14, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, origin={"ext"}, + description=SMART_SERVOS_DESCRIPTION), + ItemNames.VIKING_ANTI_MECHANICAL_MUNITION: + ItemData(280 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 15, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, origin={"ext"}, + description="Increases Viking damage to mechanical units while in Assault Mode."), + ItemNames.DIAMONDBACK_ION_THRUSTERS: + ItemData(281 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 21, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description="Increases Diamondback movement speed."), + ItemNames.WARHOUND_RESOURCE_EFFICIENCY: + ItemData(282 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 13, SC2Race.TERRAN, + parent_item=ItemNames.WARHOUND, origin={"ext"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Warhound")), + ItemNames.WARHOUND_REINFORCED_PLATING: + ItemData(283 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 14, SC2Race.TERRAN, + parent_item=ItemNames.WARHOUND, origin={"ext"}, + description="Increases Warhound armor by 2."), + ItemNames.HERC_RESOURCE_EFFICIENCY: + ItemData(284 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 15, SC2Race.TERRAN, + parent_item=ItemNames.HERC, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("HERC")), + ItemNames.HERC_JUGGERNAUT_PLATING: + ItemData(285 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 16, SC2Race.TERRAN, + parent_item=ItemNames.HERC, origin={"ext"}, + description="Increases HERC armor by 2."), + ItemNames.HERC_KINETIC_FOAM: + ItemData(286 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 17, SC2Race.TERRAN, + parent_item=ItemNames.HERC, origin={"ext"}, + description="Increases HERC life by 50."), + + ItemNames.HELLION_TWIN_LINKED_FLAMETHROWER: + ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 16, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HELLION, + description="Doubles the width of the Hellion's flame attack."), + ItemNames.HELLION_THERMITE_FILAMENTS: + ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 17, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, + description="Hellions do an additional 10 damage to Light Armor."), + ItemNames.SPIDER_MINE_CERBERUS_MINE: + ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 18, SC2Race.TERRAN, + classification=ItemClassification.filler, + description="Increases trigger and blast radius of Spider Mines."), + ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE: + ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 16, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Allows Vultures to replace used Spider Mines. Costs 15 minerals. + Level 2: Replacing used Spider Mines no longer costs minerals. + """ + )), + ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM: + ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 19, SC2Race.TERRAN, + parent_item=ItemNames.GOLIATH, + description="Goliaths can attack both ground and air targets simultaneously."), + ItemNames.GOLIATH_ARES_CLASS_TARGETING_SYSTEM: + ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 20, SC2Race.TERRAN, + parent_item=ItemNames.GOLIATH, + description="Increases Goliath ground attack range by 1 and air by 3."), + ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL: + ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 4, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Tri-Lithium Power Cell: Increases Diamondback attack range by 1. + Level 2: Tungsten Spikes: Increases Diamondback attack range by 3. + """ + )), + ItemNames.DIAMONDBACK_SHAPED_HULL: + ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 22, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK, + description="Increases Diamondback life by 50."), + ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS: + ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 23, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, + description="Siege Tanks do an additional 40 damage to the primary target in Siege Mode."), + ItemNames.SIEGE_TANK_SHAPED_BLAST: + ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 24, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, + description="Reduces splash damage to friendly targets while in Siege Mode by 75%."), + ItemNames.MEDIVAC_RAPID_DEPLOYMENT_TUBE: + ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, + description="Medivacs deploy loaded troops almost instantly."), + ItemNames.MEDIVAC_ADVANCED_HEALING_AI: + ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 26, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, + description="Medivacs can heal two targets at once."), + ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS: + ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 18, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Tomahawk Power Cells: Increases Wraith starting energy by 100. + Level 2: Unregistered Cloaking Module: Wraiths do not require energy to cloak and remain cloaked. + """ + )), + ItemNames.WRAITH_DISPLACEMENT_FIELD: + ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 27, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, + description="Wraiths evade 20% of incoming attacks while cloaked."), + ItemNames.VIKING_RIPWAVE_MISSILES: + ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 28, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, + description="Vikings do area damage while in Fighter Mode"), + ItemNames.VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM: + ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 29, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, + description="Increases Viking attack range by 1 in Assault mode and 2 in Fighter mode."), + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS: + ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Banshees can remain cloaked twice as long. + Level 2: Banshees do not require energy to cloak and remain cloaked. + """ + )), + ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY: + ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE, + description="Banshees do area damage in a straight line."), + ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS: + ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 2, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, quantity=2, + description="Spell. Missile Pods do damage to air targets in a target area."), + ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX: + ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 20, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Spell. For 20 seconds the Battlecruiser gains a shield that can absorb up to 200 damage. + Level 2: Passive. Battlecruiser gets 200 shields. + """ + )), + ItemNames.GHOST_OCULAR_IMPLANTS: + ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 2, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, + description="Increases Ghost sight range by 3 and attack range by 2."), + ItemNames.GHOST_CRIUS_SUIT: + ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 3, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, + description="Cloak no longer requires energy to activate or maintain."), + ItemNames.SPECTRE_PSIONIC_LASH: + ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 4, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SPECTRE, + description="Spell. Deals 200 damage to a single target."), + ItemNames.SPECTRE_NYX_CLASS_CLOAKING_MODULE: + ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 5, SC2Race.TERRAN, + parent_item=ItemNames.SPECTRE, + description="Cloak no longer requires energy to activate or maintain."), + ItemNames.THOR_330MM_BARRAGE_CANNON: + ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 6, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, + description=inspect.cleandoc( + """ + Improves 250mm Strike Cannons ability to deal area damage and stun units in a small area. + Can be also freely aimed on ground. + """ + )), + ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL: + ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 22, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, quantity=2, + description=inspect.cleandoc(""" + Level 1: Allows destroyed Thors to be reconstructed on the field. Costs Vespene Gas. + Level 2: Thors are automatically reconstructed after falling for free. + """ + )), + ItemNames.LIBERATOR_ADVANCED_BALLISTICS: + ItemData(326 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 7, SC2Race.TERRAN, + parent_item=ItemNames.LIBERATOR, origin={"ext"}, + description="Increases Liberator range by 3 in Defender Mode."), + ItemNames.LIBERATOR_RAID_ARTILLERY: + ItemData(327 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 8, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description="Allows Liberators to attack structures while in Defender Mode."), + ItemNames.WIDOW_MINE_DRILLING_CLAWS: + ItemData(328 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 9, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description="Allows Widow Mines to burrow and unburrow faster."), + ItemNames.WIDOW_MINE_CONCEALMENT: + ItemData(329 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 10, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description="Burrowed Widow Mines are no longer revealed when the Sentinel Missile is on cooldown."), + ItemNames.MEDIVAC_ADVANCED_CLOAKING_FIELD: + ItemData(330 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 11, SC2Race.TERRAN, + parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Medivacs are permanently cloaked."), + ItemNames.WRAITH_TRIGGER_OVERRIDE: + ItemData(331 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 12, SC2Race.TERRAN, + parent_item=ItemNames.WRAITH, origin={"ext"}, + description="Wraith attack speed increases by 10% with each attack, up to a maximum of 100%."), + ItemNames.WRAITH_INTERNAL_TECH_MODULE: + ItemData(332 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 13, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, origin={"bw"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Wraiths", "Starport")), + ItemNames.WRAITH_RESOURCE_EFFICIENCY: + ItemData(333 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 14, SC2Race.TERRAN, + parent_item=ItemNames.WRAITH, origin={"bw"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Wraith")), + ItemNames.VIKING_SHREDDER_ROUNDS: + ItemData(334 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 15, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.VIKING, origin={"ext"}, + description="Attacks in Assault mode do line splash damage."), + ItemNames.VIKING_WILD_MISSILES: + ItemData(335 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 16, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, origin={"ext"}, + description="Launches 5 rockets at the target unit. Each rocket does 25 (40 vs armored) damage."), + ItemNames.BANSHEE_SHAPED_HULL: + ItemData(336 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 17, SC2Race.TERRAN, + parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee life by 100."), + ItemNames.BANSHEE_ADVANCED_TARGETING_OPTICS: + ItemData(337 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 18, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee attack range by 2 while cloaked."), + ItemNames.BANSHEE_DISTORTION_BLASTERS: + ItemData(338 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 19, SC2Race.TERRAN, + parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee attack damage by 25% while cloaked."), + ItemNames.BANSHEE_ROCKET_BARRAGE: + ItemData(339 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 20, SC2Race.TERRAN, + parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Deals 75 damage to enemy ground units in the target area."), + ItemNames.GHOST_RESOURCE_EFFICIENCY: + ItemData(340 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 21, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Ghost")), + ItemNames.SPECTRE_RESOURCE_EFFICIENCY: + ItemData(341 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 22, SC2Race.TERRAN, + parent_item=ItemNames.SPECTRE, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Spectre")), + ItemNames.THOR_BUTTON_WITH_A_SKULL_ON_IT: + ItemData(342 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 23, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.THOR, origin={"ext"}, + description="Allows Thors to launch nukes."), + ItemNames.THOR_LASER_TARGETING_SYSTEM: + ItemData(343 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.THOR_LARGE_SCALE_FIELD_CONSTRUCTION: + ItemData(344 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}, + description="Allows Thors to be built by SCVs like a structure."), + ItemNames.RAVEN_RESOURCE_EFFICIENCY: + ItemData(345 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 26, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"ext"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Raven")), + ItemNames.RAVEN_DURABLE_MATERIALS: + ItemData(346 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 27, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}, + description="Extends timed life duration of Raven's summoned objects."), + ItemNames.SCIENCE_VESSEL_IMPROVED_NANO_REPAIR: + ItemData(347 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 28, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}, + description="Nano-Repair no longer requires energy to use."), + ItemNames.SCIENCE_VESSEL_ADVANCED_AI_SYSTEMS: + ItemData(348 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 29, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}, + description="Science Vessel can use Nano-Repair at two targets at once."), + ItemNames.CYCLONE_RESOURCE_EFFICIENCY: + ItemData(349 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 0, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Cyclone")), + ItemNames.BANSHEE_HYPERFLIGHT_ROTORS: + ItemData(350 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee movement speed."), + ItemNames.BANSHEE_LASER_TARGETING_SYSTEM: + ItemData(351 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.BANSHEE_INTERNAL_TECH_MODULE: + ItemData(352 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 3, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Banshees", "Starport")), + ItemNames.BATTLECRUISER_TACTICAL_JUMP: + ItemData(353 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 4, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"nco", "ext"}, + description=inspect.cleandoc( + """ + Allows Battlecruisers to warp to a target location anywhere on the map. + """ + )), + ItemNames.BATTLECRUISER_CLOAK: + ItemData(354 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 5, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description=CLOAK_DESCRIPTION_TEMPLATE.format("Battlecruisers")), + ItemNames.BATTLECRUISER_ATX_LASER_BATTERY: + ItemData(355 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description=inspect.cleandoc( + """ + Battlecruisers can attack while moving, + do the same damage to both ground and air targets, and fire faster. + """ + )), + ItemNames.BATTLECRUISER_OPTIMIZED_LOGISTICS: + ItemData(356 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 7, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"ext"}, + description="Increases Battlecruiser training speed."), + ItemNames.BATTLECRUISER_INTERNAL_TECH_MODULE: + ItemData(357 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 8, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Battlecruisers", "Starport")), + ItemNames.GHOST_EMP_ROUNDS: + ItemData(358 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 9, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, origin={"ext"}, + description=inspect.cleandoc( + """ + Spell. Does 100 damage to shields and drains all energy from units in the targeted area. + Cloaked units hit by EMP are revealed for a short time. + """ + )), + ItemNames.GHOST_LOCKDOWN: + ItemData(359 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 10, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, origin={"bw"}, + description="Spell. Stuns a target mechanical unit for a long time."), + ItemNames.SPECTRE_IMPALER_ROUNDS: + ItemData(360 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 11, SC2Race.TERRAN, + parent_item=ItemNames.SPECTRE, origin={"ext"}, + description="Spectres do additional damage to armored targets."), + ItemNames.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD: + ItemData(361 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 14, SC2Race.TERRAN, + parent_item=ItemNames.THOR, quantity=2, origin={"ext"}, + description=inspect.cleandoc( + f""" + Level 1: Allows Thors to transform in order to use an alternative air attack. + Level 2: {SMART_SERVOS_DESCRIPTION} + """ + )), + ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE: + ItemData(363 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 12, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}, + description="Spell. Deploys a drone that can heal biological or mechanical units."), + ItemNames.RAVEN_SPIDER_MINES: + ItemData(364 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 13, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"nco"}, important_for_filtering=True, + description="Spell. Deploys 3 Spider Mines to a target location."), + ItemNames.RAVEN_RAILGUN_TURRET: + ItemData(365 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 14, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"nco"}, + description=inspect.cleandoc( + """ + Spell. Allows Ravens to deploy an advanced Auto-Turret, + that can attack enemy ground units in a straight line. + """ + )), + ItemNames.RAVEN_HUNTER_SEEKER_WEAPON: + ItemData(366 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 15, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}, + description="Allows Ravens to attack with a Hunter-Seeker weapon."), + ItemNames.RAVEN_INTERFERENCE_MATRIX: + ItemData(367 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 16, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"ext"}, + description=inspect.cleandoc( + """ + Spell. Target enemy Mechanical or Psionic unit can't attack or use abilities for a short duration. + """ + )), + ItemNames.RAVEN_ANTI_ARMOR_MISSILE: + ItemData(368 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 17, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}, + description="Spell. Decreases target and nearby enemy units armor by 2."), + ItemNames.RAVEN_INTERNAL_TECH_MODULE: + ItemData(369 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 18, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Ravens", "Starport")), + ItemNames.SCIENCE_VESSEL_EMP_SHOCKWAVE: + ItemData(370 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 19, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}, + description="Spell. Depletes all energy and shields of all units in a target area."), + ItemNames.SCIENCE_VESSEL_DEFENSIVE_MATRIX: + ItemData(371 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 20, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}, + description=inspect.cleandoc( + """ + Spell. Provides a target unit with a defensive barrier that can absorb up to 250 damage + """ + )), + ItemNames.CYCLONE_TARGETING_OPTICS: + ItemData(372 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 21, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="Increases Cyclone Lock On casting range and the range while Locked On."), + ItemNames.CYCLONE_RAPID_FIRE_LAUNCHERS: + ItemData(373 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 22, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="The first 12 shots of Lock On are fired more quickly."), + ItemNames.LIBERATOR_CLOAK: + ItemData(374 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 23, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description=CLOAK_DESCRIPTION_TEMPLATE.format("Liberators")), + ItemNames.LIBERATOR_LASER_TARGETING_SYSTEM: + ItemData(375 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.LIBERATOR_OPTIMIZED_LOGISTICS: + ItemData(376 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description="Increases Liberator training speed."), + ItemNames.WIDOW_MINE_BLACK_MARKET_LAUNCHERS: + ItemData(377 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 26, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description="Increases Widow Mine Sentinel Missile range."), + ItemNames.WIDOW_MINE_EXECUTIONER_MISSILES: + ItemData(378 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 27, SC2Race.TERRAN, + parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description=inspect.cleandoc( + """ + Reduces Sentinel Missile cooldown. + When killed, Widow Mines will launch several missiles at random enemy targets. + """ + )), + ItemNames.VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS: + ItemData(379 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 28, + SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Valkyries fire 2 additional rockets each volley."), + ItemNames.VALKYRIE_SHAPED_HULL: + ItemData(380 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 29, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Increases Valkyrie life by 50."), + ItemNames.VALKYRIE_FLECHETTE_MISSILES: + ItemData(381 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 0, SC2Race.TERRAN, + parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Equips Valkyries with Air-to-Surface missiles to attack ground units."), + ItemNames.VALKYRIE_AFTERBURNERS: + ItemData(382 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Ability. Temporarily increases the Valkyries's movement speed by 70%."), + ItemNames.CYCLONE_INTERNAL_TECH_MODULE: + ItemData(383 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.CYCLONE, origin={"ext"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Cyclones", "Factory")), + ItemNames.LIBERATOR_SMART_SERVOS: + ItemData(384 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 3, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description=SMART_SERVOS_DESCRIPTION), + ItemNames.LIBERATOR_RESOURCE_EFFICIENCY: + ItemData(385 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 4, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Liberator")), + ItemNames.HERCULES_INTERNAL_FUSION_MODULE: + ItemData(386 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 5, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HERCULES, origin={"ext"}, + description="Hercules can be trained from a Starport without having a Fusion Core."), + ItemNames.HERCULES_TACTICAL_JUMP: + ItemData(387 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 6, SC2Race.TERRAN, + parent_item=ItemNames.HERCULES, origin={"ext"}, + description=inspect.cleandoc( + """ + Allows Hercules to warp to a target location anywhere on the map. + """ + )), + ItemNames.PLANETARY_FORTRESS_PROGRESSIVE_AUGMENTED_THRUSTERS: + ItemData(388 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 28, SC2Race.TERRAN, + parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Lift Off - Planetary Fortress can lift off. + Level 2: Armament Stabilizers - Planetary Fortress can attack while lifted off. + """ + )), + ItemNames.PLANETARY_FORTRESS_ADVANCED_TARGETING: + ItemData(389 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 7, SC2Race.TERRAN, + parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}, + description="Planetary Fortress can attack air units."), + ItemNames.VALKYRIE_LAUNCHING_VECTOR_COMPENSATOR: + ItemData(390 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 8, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Allows Valkyries to shoot air while moving."), + ItemNames.VALKYRIE_RESOURCE_EFFICIENCY: + ItemData(391 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 9, SC2Race.TERRAN, + parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Valkyrie")), + ItemNames.PREDATOR_PREDATOR_S_FURY: + ItemData(392 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 10, SC2Race.TERRAN, + parent_item=ItemNames.PREDATOR, origin={"ext"}, + description="Predators can use an attack that jumps between targets."), + ItemNames.BATTLECRUISER_BEHEMOTH_PLATING: + ItemData(393 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 11, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"ext"}, + description="Increases Battlecruiser armor by 2."), + ItemNames.BATTLECRUISER_COVERT_OPS_ENGINES: + ItemData(394 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 12, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description="Increases Battlecruiser movement speed."), + + #Buildings + ItemNames.BUNKER: + ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Defensive structure. Able to load infantry units, giving them +1 range to their attacks."), + ItemNames.MISSILE_TURRET: + ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Anti-air defensive structure."), + ItemNames.SENSOR_TOWER: + ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2, SC2Race.TERRAN, + description="Reveals locations of enemy units at long range."), + + ItemNames.WAR_PIGS: + ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Mercenary Marines"), + ItemNames.DEVIL_DOGS: + ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, + description="Mercenary Firebats"), + ItemNames.HAMMER_SECURITIES: + ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2, SC2Race.TERRAN, + description="Mercenary Marauders"), + ItemNames.SPARTAN_COMPANY: + ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Mercenary Goliaths"), + ItemNames.SIEGE_BREAKERS: + ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4, SC2Race.TERRAN, + description="Mercenary Siege Tanks"), + ItemNames.HELS_ANGELS: + ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Mercenary Vikings"), + ItemNames.DUSK_WINGS: + ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6, SC2Race.TERRAN, + description="Mercenary Banshees"), + ItemNames.JACKSONS_REVENGE: + ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7, SC2Race.TERRAN, + description="Mercenary Battlecruiser"), + ItemNames.SKIBIS_ANGELS: + ItemData(508 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 8, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Medics"), + ItemNames.DEATH_HEADS: + ItemData(509 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 9, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Reapers"), + ItemNames.WINGED_NIGHTMARES: + ItemData(510 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 10, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description="Mercenary Wraiths"), + ItemNames.MIDNIGHT_RIDERS: + ItemData(511 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 11, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Liberators"), + ItemNames.BRYNHILDS: + ItemData(512 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 12, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description="Mercenary Valkyries"), + ItemNames.JOTUN: + ItemData(513 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 13, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Thor"), + + ItemNames.ULTRA_CAPACITORS: + ItemData(600 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 0, SC2Race.TERRAN, + description="Increases attack speed of units by 5% per weapon upgrade."), + ItemNames.VANADIUM_PLATING: + ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1, SC2Race.TERRAN, + description="Increases the life of units by 5% per armor upgrade."), + ItemNames.ORBITAL_DEPOTS: + ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2, SC2Race.TERRAN, + description="Supply depots are built instantly."), + ItemNames.MICRO_FILTERING: + ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3, SC2Race.TERRAN, + description="Refineries produce Vespene gas 25% faster."), + ItemNames.AUTOMATED_REFINERY: + ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4, SC2Race.TERRAN, + description="Eliminates the need for SCVs in vespene gas production."), + ItemNames.COMMAND_CENTER_REACTOR: + ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5, SC2Race.TERRAN, + description="Command Centers can train two SCVs at once."), + ItemNames.RAVEN: + ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Unit", 22, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Aerial Caster unit."), + ItemNames.SCIENCE_VESSEL: + ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Unit", 23, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Aerial Caster unit. Can repair mechanical units."), + ItemNames.TECH_REACTOR: + ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6, SC2Race.TERRAN, + description="Merges Tech Labs and Reactors into one add on structure to provide both functions."), + ItemNames.ORBITAL_STRIKE: + ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, SC2Race.TERRAN, + description="Trained units from Barracks are instantly deployed on rally point."), + ItemNames.BUNKER_SHRIKE_TURRET: + ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Adds an automated turret to Bunkers."), + ItemNames.BUNKER_FORTIFIED_BUNKER: + ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Bunkers have more life."), + ItemNames.PLANETARY_FORTRESS: + ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Building", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Allows Command Centers to upgrade into a defensive structure with a turret and additional armor. + Planetary Fortresses cannot Lift Off, or cast Orbital Command spells. + """ + )), + ItemNames.PERDITION_TURRET: + ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Building", 4, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Automated defensive turret. Burrows down while no enemies are nearby."), + ItemNames.PREDATOR: + ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Unit", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, + description="Anti-infantry specialist that deals area damage with each attack."), + ItemNames.HERCULES: + ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Unit", 25, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Massive transport ship."), + ItemNames.CELLULAR_REACTOR: + ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8, SC2Race.TERRAN, + description="All Terran spellcasters get +100 starting and maximum energy."), + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: + ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 4, SC2Race.TERRAN, quantity=3, + classification= ItemClassification.progression, + description=inspect.cleandoc( + """ + Allows Terran mechanical units to regenerate health while not in combat. + Each level increases life regeneration speed. + """ + )), + ItemNames.HIVE_MIND_EMULATOR: + ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Building", 5, SC2Race.TERRAN, + ItemClassification.progression, + description="Defensive structure. Can permanently Mind Control Zerg units."), + ItemNames.PSI_DISRUPTER: + ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Building", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Defensive structure. Slows the attack and movement speeds of all nearby Zerg units."), + ItemNames.STRUCTURE_ARMOR: + ItemData(620 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9, SC2Race.TERRAN, + description="Increases armor of all Terran structures by 2."), + ItemNames.HI_SEC_AUTO_TRACKING: + ItemData(621 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10, SC2Race.TERRAN, + description="Increases attack range of all Terran structures by 1."), + ItemNames.ADVANCED_OPTICS: + ItemData(622 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, SC2Race.TERRAN, + description="Increases attack range of all Terran mechanical units by 1."), + ItemNames.ROGUE_FORCES: + ItemData(623 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12, SC2Race.TERRAN, + description="Mercenary calldowns are no longer limited by charges."), + + ItemNames.ZEALOT: + ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Powerful melee warrior. Can use the charge ability."), + ItemNames.STALKER: + ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Ranged attack strider. Can use the Blink ability."), + ItemNames.HIGH_TEMPLAR: + ItemData(702 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Potent psionic master. Can use the Feedback and Psionic Storm abilities. Can merge into an Archon."), + ItemNames.DARK_TEMPLAR: + ItemData(703 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Deadly warrior-assassin. Permanently cloaked. Can use the Shadow Fury ability."), + ItemNames.IMMORTAL: + ItemData(704 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Assault strider. Can use Barrier to absorb damage."), + ItemNames.COLOSSUS: + ItemData(705 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Battle strider with a powerful area attack. Can walk up and down cliffs. Attacks set fire to the ground, dealing extra damage to enemies over time."), + ItemNames.PHOENIX: + ItemData(706 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Air superiority starfighter. Can use Graviton Beam and Phasing Armor abilities."), + ItemNames.VOID_RAY: + ItemData(707 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Surgical strike craft. Has the Prismatic Alignment and Prismatic Range abilities."), + ItemNames.CARRIER: + ItemData(708 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Capital ship. Builds and launches Interceptors that attack enemy targets. Repair Drones heal nearby mechanical units."), + + # Filler items to fill remaining spots + ItemNames.STARTING_MINERALS: + ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, SC2Race.ANY, quantity=0, + classification=ItemClassification.filler, + description="Increases the starting minerals for all missions."), + ItemNames.STARTING_VESPENE: + ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, SC2Race.ANY, quantity=0, + classification=ItemClassification.filler, + description="Increases the starting vespene for all missions."), + ItemNames.STARTING_SUPPLY: + ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, SC2Race.ANY, quantity=0, + classification=ItemClassification.filler, + description="Increases the starting supply for all missions."), + # This item is used to "remove" location from the game. Never placed unless plando'd + ItemNames.NOTHING: + ItemData(803 + SC2WOL_ITEM_ID_OFFSET, "Nothing Group", 2, SC2Race.ANY, quantity=0, + classification=ItemClassification.trap, + description="Does nothing. Used to remove a location from the game."), + + # Nova gear + ItemNames.NOVA_GHOST_VISOR: + ItemData(900 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 0, SC2Race.TERRAN, origin={"nco"}, + description="Reveals the locations of enemy units in the fog of war around Nova. Can detect cloaked units."), + ItemNames.NOVA_RANGEFINDER_OCULUS: + ItemData(901 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 1, SC2Race.TERRAN, origin={"nco"}, + description="Increaases Nova's vision range and non-melee weapon attack range by 2. Also increases range of melee weapons by 1."), + ItemNames.NOVA_DOMINATION: + ItemData(902 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 2, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to mind-control a target enemy unit."), + ItemNames.NOVA_BLINK: + ItemData(903 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 3, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to teleport a short distance and cloak for 10s."), + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: + ItemData(904 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 0, SC2Race.TERRAN, quantity=2, origin={"nco"}, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Level 1: Gives Nova the ability to cloak. + Level 2: Nova is permanently cloaked. + """ + )), + ItemNames.NOVA_ENERGY_SUIT_MODULE: + ItemData(905 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 4, SC2Race.TERRAN, origin={"nco"}, + description="Increases Nova's maximum energy and energy regeneration rate."), + ItemNames.NOVA_ARMORED_SUIT_MODULE: + ItemData(906 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 5, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Increases Nova's health by 100 and armour by 1. Nova also regenerates life quickly out of combat."), + ItemNames.NOVA_JUMP_SUIT_MODULE: + ItemData(907 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 6, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Increases Nova's movement speed and allows her to jump up and down cliffs."), + ItemNames.NOVA_C20A_CANISTER_RIFLE: + ItemData(908 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 7, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the C20A Canister Rifle, which has a ranged attack and allows Nova to cast Snipe."), + ItemNames.NOVA_HELLFIRE_SHOTGUN: + ItemData(909 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 8, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Hellfire Shotgun, which has a short-range area attack in a cone and allows Nova to cast Penetrating Blast."), + ItemNames.NOVA_PLASMA_RIFLE: + ItemData(910 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 9, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Plasma Rifle, which has a rapidfire ranged attack and allows Nova to cast Plasma Shot."), + ItemNames.NOVA_MONOMOLECULAR_BLADE: + ItemData(911 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 10, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Monomolecular Blade, which has a melee attack and allows Nova to cast Dash Attack."), + ItemNames.NOVA_BLAZEFIRE_GUNBLADE: + ItemData(912 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 11, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Blazefire Gunblade, which has a melee attack and allows Nova to cast Fury of One."), + ItemNames.NOVA_STIM_INFUSION: + ItemData(913 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 12, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to heal herself and temporarily increase her movement and attack speeds."), + ItemNames.NOVA_PULSE_GRENADES: + ItemData(914 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 13, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to throw a grenade dealing large damage in an area."), + ItemNames.NOVA_FLASHBANG_GRENADES: + ItemData(915 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 14, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to throw a grenade to stun enemies and disable detection in a large area."), + ItemNames.NOVA_IONIC_FORCE_FIELD: + ItemData(916 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 15, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to shield herself temporarily."), + ItemNames.NOVA_HOLO_DECOY: + ItemData(917 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 16, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to summon a decoy unit which enemies will prefer to target and takes reduced damage."), + ItemNames.NOVA_NUKE: + ItemData(918 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 17, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to launch tactical nukes built from the Shadow Ops."), + + # HotS + ItemNames.ZERGLING: + ItemData(0 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 0, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Fast inexpensive melee attacker. Hatches in pairs from a single larva. Can morph into a Baneling."), + ItemNames.SWARM_QUEEN: + ItemData(1 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 1, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Ranged support caster. Can use the Spawn Creep Tumor and Rapid Transfusion abilities."), + ItemNames.ROACH: + ItemData(2 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 2, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Durable short ranged attacker. Regenerates life quickly when burrowed."), + ItemNames.HYDRALISK: + ItemData(3 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 3, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="High-damage generalist ranged attacker."), + ItemNames.ZERGLING_BANELING_ASPECT: + ItemData(4 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 5, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Anti-ground suicide unit. Does damage over a small area on death."), + ItemNames.ABERRATION: + ItemData(5 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 5, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Durable melee attacker that deals heavy damage and can walk over other units."), + ItemNames.MUTALISK: + ItemData(6 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 6, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Fragile flying attacker. Attacks bounce between targets."), + ItemNames.SWARM_HOST: + ItemData(7 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 7, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Siege unit that attacks by rooting in place and continually spawning Locusts."), + ItemNames.INFESTOR: + ItemData(8 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 8, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Support caster that can move while burrowed. Can use the Fungal Growth, Parasitic Domination, and Consumption abilities."), + ItemNames.ULTRALISK: + ItemData(9 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 9, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Massive melee attacker. Has an area-damage cleave attack."), + ItemNames.SPORE_CRAWLER: + ItemData(10 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 10, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Anti-air defensive structure that can detect cloaked units."), + ItemNames.SPINE_CRAWLER: + ItemData(11 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 11, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Anti-ground defensive structure."), + ItemNames.CORRUPTOR: + ItemData(12 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 12, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"ext"}, + description="Anti-air flying attacker specializing in taking down enemy capital ships."), + ItemNames.SCOURGE: + ItemData(13 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 13, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"bw", "ext"}, + description="Flying anti-air suicide unit. Hatches in pairs from a single larva."), + ItemNames.BROOD_QUEEN: + ItemData(14 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 4, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"bw", "ext"}, + description="Flying support caster. Can cast the Ocular Symbiote and Spawn Broodlings abilities."), + ItemNames.DEFILER: + ItemData(15 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 14, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"bw"}, + description="Support caster. Can use the Dark Swarm, Consume, and Plague abilities."), + + ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + # Upgrade bundle 'number' values are used as indices to get affected 'number's + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 7, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 9, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 10, SC2Race.ZERG, quantity=3, origin={"hots"}), + + ItemNames.ZERGLING_HARDENED_CARAPACE: + ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, description="Increases Zergling health by +10."), + ItemNames.ZERGLING_ADRENAL_OVERLOAD: + ItemData(201 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, description="Increases Zergling attack speed."), + ItemNames.ZERGLING_METABOLIC_BOOST: + ItemData(202 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, classification=ItemClassification.filler, + description="Increases Zergling movement speed."), + ItemNames.ROACH_HYDRIODIC_BILE: + ItemData(203 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"hots"}, description="Roaches deal +8 damage to light targets."), + ItemNames.ROACH_ADAPTIVE_PLATING: + ItemData(204 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 4, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"hots"}, description="Roaches gain +3 armour when their life is below 50%."), + ItemNames.ROACH_TUNNELING_CLAWS: + ItemData(205 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 5, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"hots"}, classification=ItemClassification.filler, + description="Allows Roaches to move while burrowed."), + ItemNames.HYDRALISK_FRENZY: + ItemData(206 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 6, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"hots"}, + description="Allows Hydralisks to use the Frenzy ability, which increases their attack speed by 50%."), + ItemNames.HYDRALISK_ANCILLARY_CARAPACE: + ItemData(207 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 7, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"hots"}, classification=ItemClassification.filler, description="Hydralisks gain +20 health."), + ItemNames.HYDRALISK_GROOVED_SPINES: + ItemData(208 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 8, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"hots"}, description="Hydralisks gain +1 range."), + ItemNames.BANELING_CORROSIVE_ACID: + ItemData(209 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 9, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + description="Increases the damage banelings deal to their primary target. Splash damage remains the same."), + ItemNames.BANELING_RUPTURE: + ItemData(210 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 10, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + classification=ItemClassification.filler, + description="Increases the splash radius of baneling attacks."), + ItemNames.BANELING_REGENERATIVE_ACID: + ItemData(211 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 11, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + classification=ItemClassification.filler, + description="Banelings will heal nearby friendly units when they explode."), + ItemNames.MUTALISK_VICIOUS_GLAIVE: + ItemData(212 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 12, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"hots"}, description="Mutalisks attacks will bounce an additional 3 times."), + ItemNames.MUTALISK_RAPID_REGENERATION: + ItemData(213 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 13, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"hots"}, description="Mutalisks will regenerate quickly when out of combat."), + ItemNames.MUTALISK_SUNDERING_GLAIVE: + ItemData(214 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"hots"}, description="Mutalisks deal increased damage to their primary target."), + ItemNames.SWARM_HOST_BURROW: + ItemData(215 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 15, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, classification=ItemClassification.filler, + description="Allows Swarm Hosts to burrow instead of root to spawn locusts."), + ItemNames.SWARM_HOST_RAPID_INCUBATION: + ItemData(216 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 16, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, description="Swarm Hosts will spawn locusts 20% faster."), + ItemNames.SWARM_HOST_PRESSURIZED_GLANDS: + ItemData(217 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 17, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, classification=ItemClassification.progression, + description="Allows Swarm Host Locusts to attack air targets."), + ItemNames.ULTRALISK_BURROW_CHARGE: + ItemData(218 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 18, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, + description="Allows Ultralisks to burrow and charge at enemy units, knocking back and stunning units when it emerges."), + ItemNames.ULTRALISK_TISSUE_ASSIMILATION: + ItemData(219 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 19, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, description="Ultralisks recover health when they deal damage."), + ItemNames.ULTRALISK_MONARCH_BLADES: + ItemData(220 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 20, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, description="Ultralisks gain increased splash damage."), + ItemNames.CORRUPTOR_CAUSTIC_SPRAY: + ItemData(221 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 21, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + origin={"ext"}, + description="Allows Corruptors to use the Caustic Spray ability, which deals ramping damage to buildings over time."), + ItemNames.CORRUPTOR_CORRUPTION: + ItemData(222 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 22, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + origin={"ext"}, + description="Allows Corruptors to use the Corruption ability, which causes a target enemy unit to take increased damage."), + ItemNames.SCOURGE_VIRULENT_SPORES: + ItemData(223 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 23, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + origin={"ext"}, description="Scourge will deal splash damage."), + ItemNames.SCOURGE_RESOURCE_EFFICIENCY: + ItemData(224 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 24, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + origin={"ext"}, classification=ItemClassification.progression, + description="Reduces the cost of Scourge by 50 gas per egg."), + ItemNames.SCOURGE_SWARM_SCOURGE: + ItemData(225 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 25, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + origin={"ext"}, description="An extra Scourge will be built from each egg at no additional cost."), + ItemNames.ZERGLING_SHREDDING_CLAWS: + ItemData(226 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 26, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"ext"}, description="Zergling attacks will temporarily reduce their target's armour to 0."), + ItemNames.ROACH_GLIAL_RECONSTITUTION: + ItemData(227 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 27, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"ext"}, description="Increases Roach movement speed."), + ItemNames.ROACH_ORGANIC_CARAPACE: + ItemData(228 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 28, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"ext"}, description="Increases Roach health by +25."), + ItemNames.HYDRALISK_MUSCULAR_AUGMENTS: + ItemData(229 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 29, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"bw"}, description="Increases Hydralisk movement speed."), + ItemNames.HYDRALISK_RESOURCE_EFFICIENCY: + ItemData(230 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 0, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"bw"}, description="Reduces Hydralisk resource cost by 25/25 and supply cost by 1."), + ItemNames.BANELING_CENTRIFUGAL_HOOKS: + ItemData(231 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 1, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}, + description="Increases the movement speed of Banelings."), + ItemNames.BANELING_TUNNELING_JAWS: + ItemData(232 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 2, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}, + description="Allows Banelings to move while burrowed."), + ItemNames.BANELING_RAPID_METAMORPH: + ItemData(233 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 3, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}, description="Banelings morph faster."), + ItemNames.MUTALISK_SEVERING_GLAIVE: + ItemData(234 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"ext"}, description="Mutalisk bounce attacks will deal full damage."), + ItemNames.MUTALISK_AERODYNAMIC_GLAIVE_SHAPE: + ItemData(235 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"ext"}, description="Increases the attack range of Mutalisks by 2."), + ItemNames.SWARM_HOST_LOCUST_METABOLIC_BOOST: + ItemData(236 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 6, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, classification=ItemClassification.filler, + description="Increases Locust movement speed."), + ItemNames.SWARM_HOST_ENDURING_LOCUSTS: + ItemData(237 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 7, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, description="Increases the duration of Swarm Hosts' Locusts by 10s."), + ItemNames.SWARM_HOST_ORGANIC_CARAPACE: + ItemData(238 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, description="Increases Swarm Host health by +40."), + ItemNames.SWARM_HOST_RESOURCE_EFFICIENCY: + ItemData(239 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, description="Reduces Swarm Host resource cost by 100/25."), + ItemNames.ULTRALISK_ANABOLIC_SYNTHESIS: + ItemData(240 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 10, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"bw"}, classification=ItemClassification.filler), + ItemNames.ULTRALISK_CHITINOUS_PLATING: + ItemData(241 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 11, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"bw"}), + ItemNames.ULTRALISK_ORGANIC_CARAPACE: + ItemData(242 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"ext"}), + ItemNames.ULTRALISK_RESOURCE_EFFICIENCY: + ItemData(243 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"bw"}), + ItemNames.DEVOURER_CORROSIVE_SPRAY: + ItemData(244 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 14, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), + ItemNames.DEVOURER_GAPING_MAW: + ItemData(245 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 15, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), + ItemNames.DEVOURER_IMPROVED_OSMOSIS: + ItemData(246 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 16, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}, + classification=ItemClassification.filler), + ItemNames.DEVOURER_PRESCIENT_SPORES: + ItemData(247 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 17, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), + ItemNames.GUARDIAN_PROLONGED_DISPERSION: + ItemData(248 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 18, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), + ItemNames.GUARDIAN_PRIMAL_ADAPTATION: + ItemData(249 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 19, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), + ItemNames.GUARDIAN_SORONAN_ACID: + ItemData(250 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 20, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), + ItemNames.IMPALER_ADAPTIVE_TALONS: + ItemData(251 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 21, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}, + classification=ItemClassification.filler), + ItemNames.IMPALER_SECRETION_GLANDS: + ItemData(252 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 22, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), + ItemNames.IMPALER_HARDENED_TENTACLE_SPINES: + ItemData(253 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 23, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), + ItemNames.LURKER_SEISMIC_SPINES: + ItemData(254 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 24, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), + ItemNames.LURKER_ADAPTED_SPINES: + ItemData(255 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 25, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), + ItemNames.RAVAGER_POTENT_BILE: + ItemData(256 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 26, SC2Race.ZERG, + parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), + ItemNames.RAVAGER_BLOATED_BILE_DUCTS: + ItemData(257 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 27, SC2Race.ZERG, + parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), + ItemNames.RAVAGER_DEEP_TUNNEL: + ItemData(258 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 28, SC2Race.ZERG, + parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), + ItemNames.VIPER_PARASITIC_BOMB: + ItemData(259 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 29, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), + ItemNames.VIPER_PARALYTIC_BARBS: + ItemData(260 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 0, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), + ItemNames.VIPER_VIRULENT_MICROBES: + ItemData(261 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 1, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_POROUS_CARTILAGE: + ItemData(262 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 2, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_EVOLVED_CARAPACE: + ItemData(263 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 3, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_SPLITTER_MITOSIS: + ItemData(264 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 4, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_RESOURCE_EFFICIENCY: + ItemData(265 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 5, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.INFESTOR_INFESTED_TERRAN: + ItemData(266 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 6, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + origin={"ext"}), + ItemNames.INFESTOR_MICROBIAL_SHROUD: + ItemData(267 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 7, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + origin={"ext"}), + ItemNames.SWARM_QUEEN_SPAWN_LARVAE: + ItemData(268 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_DEEP_TUNNEL: + ItemData(269 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_ORGANIC_CARAPACE: + ItemData(270 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}, classification=ItemClassification.filler), + ItemNames.SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION: + ItemData(271 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_RESOURCE_EFFICIENCY: + ItemData(272 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 12, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_INCUBATOR_CHAMBER: + ItemData(273 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 13, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.BROOD_QUEEN_FUNGAL_GROWTH: + ItemData(274 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 14, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + origin={"ext"}), + ItemNames.BROOD_QUEEN_ENSNARE: + ItemData(275 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 15, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + origin={"ext"}), + ItemNames.BROOD_QUEEN_ENHANCED_MITOCHONDRIA: + ItemData(276 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 16, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + origin={"ext"}), + + ItemNames.ZERGLING_RAPTOR_STRAIN: + ItemData(300 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, + description="Allows Zerglings to jump up and down cliffs and leap onto enemies. Also increases Zergling attack damage by 2."), + ItemNames.ZERGLING_SWARMLING_STRAIN: + ItemData(301 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, + description="Zerglings will spawn instantly and with an extra Zergling per egg at no additional cost."), + ItemNames.ROACH_VILE_STRAIN: + ItemData(302 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 2, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}, + description="Roach attacks will slow the movement and attack speed of enemies."), + ItemNames.ROACH_CORPSER_STRAIN: + ItemData(303 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}, + description="Units killed after being attacked by Roaches will spawn 2 Roachlings."), + ItemNames.HYDRALISK_IMPALER_ASPECT: + ItemData(304 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 0, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, + description="Allows Hydralisks to morph into Impalers."), + ItemNames.HYDRALISK_LURKER_ASPECT: + ItemData(305 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 1, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, description="Allows Hydralisks to morph into Lurkers."), + ItemNames.BANELING_SPLITTER_STRAIN: + ItemData(306 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 6, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + description="Banelings will split into two smaller Splitterlings on exploding."), + ItemNames.BANELING_HUNTER_STRAIN: + ItemData(307 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 7, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + description="Allows Banelings to jump up and down cliffs and leap onto enemies."), + ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT: + ItemData(308 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 2, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, + description="Allows Mutalisks and Corruptors to morph into Brood Lords."), + ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT: + ItemData(309 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 3, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, + description="Allows Mutalisks and Corruptors to morph into Vipers."), + ItemNames.SWARM_HOST_CARRION_STRAIN: + ItemData(310 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, description="Swarm Hosts will spawn Flying Locusts."), + ItemNames.SWARM_HOST_CREEPER_STRAIN: + ItemData(311 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, classification=ItemClassification.filler, + description="Allows Swarm Hosts to teleport to any creep on the map in vision. Swarm Hosts will spread creep around them when rooted or burrowed."), + ItemNames.ULTRALISK_NOXIOUS_STRAIN: + ItemData(312 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, classification=ItemClassification.filler, + description="Ultralisks will periodically spread poison, damaging nearby biological enemies."), + ItemNames.ULTRALISK_TORRASQUE_STRAIN: + ItemData(313 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, description="Ultralisks will revive after being killed."), + + ItemNames.KERRIGAN_KINETIC_BLAST: ItemData(400 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_HEROIC_FORTITUDE: ItemData(401 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEAPING_STRIKE: ItemData(402 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CRUSHING_GRIP: ItemData(403 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CHAIN_REACTION: ItemData(404 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 4, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_PSIONIC_SHIFT: ItemData(405 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 5, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION: ItemData(406 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.filler), + ItemNames.KERRIGAN_IMPROVED_OVERLORDS: ItemData(407 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 1, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS: ItemData(408 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 2, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_WILD_MUTATION: ItemData(409 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 6, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_BANELINGS: ItemData(410 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 7, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_MEND: ItemData(411 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 8, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_TWIN_DRONES: ItemData(412 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 3, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_MALIGNANT_CREEP: ItemData(413 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 4, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_VESPENE_EFFICIENCY: ItemData(414 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 5, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_INFEST_BROODLINGS: ItemData(415 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 9, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_FURY: ItemData(416 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 10, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ABILITY_EFFICIENCY: ItemData(417 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 11, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_APOCALYPSE: ItemData(418 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 12, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_LEVIATHAN: ItemData(419 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 13, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_DROP_PODS: ItemData(420 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 14, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + # Handled separately from other abilities + ItemNames.KERRIGAN_PRIMAL_FORM: ItemData(421 + SC2HOTS_ITEM_ID_OFFSET, "Primal Form", 0, SC2Race.ZERG, origin={"hots"}), + + ItemNames.KERRIGAN_LEVELS_10: ItemData(500 + SC2HOTS_ITEM_ID_OFFSET, "Level", 10, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_9: ItemData(501 + SC2HOTS_ITEM_ID_OFFSET, "Level", 9, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_8: ItemData(502 + SC2HOTS_ITEM_ID_OFFSET, "Level", 8, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_7: ItemData(503 + SC2HOTS_ITEM_ID_OFFSET, "Level", 7, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_6: ItemData(504 + SC2HOTS_ITEM_ID_OFFSET, "Level", 6, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_5: ItemData(505 + SC2HOTS_ITEM_ID_OFFSET, "Level", 5, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_4: ItemData(506 + SC2HOTS_ITEM_ID_OFFSET, "Level", 4, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_3: ItemData(507 + SC2HOTS_ITEM_ID_OFFSET, "Level", 3, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_2: ItemData(508 + SC2HOTS_ITEM_ID_OFFSET, "Level", 2, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_1: ItemData(509 + SC2HOTS_ITEM_ID_OFFSET, "Level", 1, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_14: ItemData(510 + SC2HOTS_ITEM_ID_OFFSET, "Level", 14, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_35: ItemData(511 + SC2HOTS_ITEM_ID_OFFSET, "Level", 35, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_70: ItemData(512 + SC2HOTS_ITEM_ID_OFFSET, "Level", 70, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + + # Zerg Mercs + ItemNames.INFESTED_MEDICS: ItemData(600 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 0, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_SIEGE_TANKS: ItemData(601 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 1, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_BANSHEES: ItemData(602 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 2, SC2Race.ZERG, origin={"ext"}), + + # Misc Upgrades + ItemNames.OVERLORD_VENTRAL_SACS: ItemData(700 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 6, SC2Race.ZERG, origin={"bw"}), + + # Morphs + ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT: ItemData(800 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 6, SC2Race.ZERG, origin={"bw"}), + ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT: ItemData(801 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 7, SC2Race.ZERG, origin={"bw"}), + ItemNames.ROACH_RAVAGER_ASPECT: ItemData(802 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 8, SC2Race.ZERG, origin={"ext"}), + + + # Protoss Units (those that aren't as items in WoL (Prophecy)) + ItemNames.OBSERVER: ItemData(0 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 9, SC2Race.PROTOSS, + classification=ItemClassification.filler, origin={"wol"}, + description="Flying spy. Cloak renders the unit invisible to enemies without detection."), + ItemNames.CENTURION: ItemData(1 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 10, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Powerful melee warrior. Has the Shadow Charge and Darkcoil abilities."), + ItemNames.SENTINEL: ItemData(2 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 11, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Powerful melee warrior. Has the Charge and Reconstruction abilities."), + ItemNames.SUPPLICANT: ItemData(3 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 12, SC2Race.PROTOSS, + classification=ItemClassification.filler, important_for_filtering=True, origin={"ext"}, + description="Powerful melee warrior. Has powerful damage resistant shields."), + ItemNames.INSTIGATOR: ItemData(4 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 13, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Ranged support strider. Can store multiple Blink charges."), + ItemNames.SLAYER: ItemData(5 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 14, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Ranged attack strider. Can use the Phase Blink and Phasing Armor abilities."), + ItemNames.SENTRY: ItemData(6 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 15, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Robotic support unit can use the Guardian Shield ability and restore the shields of nearby Protoss units."), + ItemNames.ENERGIZER: ItemData(7 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 16, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Robotic support unit. Can use the Chrono Beam ability and become stationary to power nearby structures."), + ItemNames.HAVOC: ItemData(8 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 17, SC2Race.PROTOSS, + origin={"lotv"}, important_for_filtering=True, + description="Robotic support unit. Can use the Target Lock and Force Field abilities and increase the range of nearby Protoss units."), + ItemNames.SIGNIFIER: ItemData(9 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 18, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Potent permanently cloaked psionic master. Can use the Feedback and Crippling Psionic Storm abilities. Can merge into an Archon."), + ItemNames.ASCENDANT: ItemData(10 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 19, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Potent psionic master. Can use the Psionic Orb, Mind Blast, and Sacrifice abilities."), + ItemNames.AVENGER: ItemData(11 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 20, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Deadly warrior-assassin. Permanently cloaked. Recalls to the nearest Dark Shrine upon death."), + ItemNames.BLOOD_HUNTER: ItemData(12 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 21, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Deadly warrior-assassin. Permanently cloaked. Can use the Void Stasis ability."), + ItemNames.DRAGOON: ItemData(13 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 22, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Ranged assault strider. Has enhanced health and damage."), + ItemNames.DARK_ARCHON: ItemData(14 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 23, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Potent psionic master. Can use the Confuse and Mind Control abilities."), + ItemNames.ADEPT: ItemData(15 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 24, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Ranged specialist. Can use the Psionic Transfer ability."), + ItemNames.WARP_PRISM: ItemData(16 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 25, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Flying transport. Can carry units and become stationary to deploy a power field."), + ItemNames.ANNIHILATOR: ItemData(17 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 26, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Assault Strider. Can use the Shadow Cannon ability to damage air and ground units."), + ItemNames.VANGUARD: ItemData(18 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 27, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Assault Strider. Deals splash damage around the primary target."), + ItemNames.WRATHWALKER: ItemData(19 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 28, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Battle strider with a powerful single target attack. Can walk up and down cliffs."), + ItemNames.REAVER: ItemData(20 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 29, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Area damage siege unit. Builds and launches explosive Scarabs for high burst damage."), + ItemNames.DISRUPTOR: ItemData(21 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 0, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Robotic disruption unit. Can use the Purification Nova ability to deal heavy area damage."), + ItemNames.MIRAGE: ItemData(22 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 1, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Air superiority starfighter. Can use Graviton Beam and Phasing Armor abilities."), + ItemNames.CORSAIR: ItemData(23 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 2, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Air superiority starfighter. Can use the Disruption Web ability."), + ItemNames.DESTROYER: ItemData(24 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 3, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Area assault craft. Can use the Destruction Beam ability to attack multiple units at once."), + ItemNames.SCOUT: ItemData(25 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 4, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Versatile high-speed fighter."), + ItemNames.TEMPEST: ItemData(26 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 5, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Siege artillery craft. Attacks from long range. Can use the Disintegration ability."), + ItemNames.MOTHERSHIP: ItemData(27 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 6, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Ultimate Protoss vessel, Can use the Vortex and Mass Recall abilities. Cloaks nearby units and structures."), + ItemNames.ARBITER: ItemData(28 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 7, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Army support craft. Has the Stasis Field and Recall abilities. Cloaks nearby units."), + ItemNames.ORACLE: ItemData(29 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 8, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Flying caster. Can use the Revelation and Stasis Ward abilities."), + + # Protoss Upgrades + ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + # Upgrade bundle 'number' values are used as indices to get affected 'number's + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 11, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 12, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 13, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 14, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 15, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + + # Protoss Buildings + ItemNames.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, "Building", 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), + ItemNames.KHAYDARIN_MONOLITH: ItemData(201 + SC2LOTV_ITEM_ID_OFFSET, "Building", 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SHIELD_BATTERY: ItemData(202 + SC2LOTV_ITEM_ID_OFFSET, "Building", 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + + # Protoss Unit Upgrades + ItemNames.SUPPLICANT_BLOOD_SHIELD: ItemData(300 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 0, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SOUL_AUGMENTATION: ItemData(301 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SHIELD_REGENERATION: ItemData(302 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_ENHANCED_STRIDER_SERVOS: ItemData(311 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.SCOUT_COMBAT_SENSOR_ARRAY: ItemData(312 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_APIAL_SENSORS: ItemData(313 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 13, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_GRAVITIC_THRUSTERS: ItemData(314 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_ADVANCED_PHOTON_BLASTERS: ItemData(315 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.TEMPEST_TECTONIC_DESTABILIZERS: ItemData(316 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 16, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_QUANTIC_REACTOR: ItemData(317 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 17, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_GRAVITY_SLING: ItemData(318 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX: ItemData(319 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 19, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS: ItemData(320 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 20, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.CORSAIR_STEALTH_DRIVE: ItemData(321 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_ARGUS_JEWEL: ItemData(322 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 22, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_SUSTAINING_DISRUPTION: ItemData(323 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 23, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_NEUTRON_SHIELDS: ItemData(324 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 24, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.ORACLE_STEALTH_DRIVE: ItemData(325 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 25, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_STASIS_CALIBRATION: ItemData(326 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 26, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_TEMPORAL_ACCELERATION_BEAM: ItemData(327 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 27, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ARBITER_CHRONOSTATIC_REINFORCEMENT: ItemData(328 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 28, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_KHAYDARIN_CORE: ItemData(329 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 29, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_SPACETIME_ANCHOR: ItemData(330 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 0, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_RESOURCE_EFFICIENCY: ItemData(331 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_ENHANCED_CLOAK_FIELD: ItemData(332 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.CARRIER_GRAVITON_CATAPULT: + ItemData(333 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 3, SC2Race.PROTOSS, origin={"wol"}, + parent_item=ItemNames.CARRIER, + description="Carriers can launch Interceptors more quickly."), + ItemNames.CARRIER_HULL_OF_PAST_GLORIES: + ItemData(334 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 4, SC2Race.PROTOSS, origin={"bw"}, + parent_item=ItemNames.CARRIER, + description="Carriers gain +2 armour."), + ItemNames.VOID_RAY_DESTROYER_FLUX_VANES: + ItemData(335 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 5, SC2Race.PROTOSS, classification=ItemClassification.filler, + origin={"ext"}, + description="Increases Void Ray and Destroyer movement speed."), + ItemNames.DESTROYER_REFORGED_BLOODSHARD_CORE: + ItemData(336 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 6, SC2Race.PROTOSS, origin={"ext"}, + parent_item=ItemNames.DESTROYER, + description="When fully charged, the Destroyer's Destruction Beam weapon does full damage to secondary targets."), + ItemNames.WARP_PRISM_GRAVITIC_DRIVE: + ItemData(337 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 7, SC2Race.PROTOSS, classification=ItemClassification.filler, + origin={"ext"}, parent_item=ItemNames.WARP_PRISM, + description="Increases the movement speed of Warp Prisms."), + ItemNames.WARP_PRISM_PHASE_BLASTER: + ItemData(338 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 8, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, parent_item=ItemNames.WARP_PRISM, + description="Equips Warp Prisms with an auto-attack that can hit ground and air targets."), + ItemNames.WARP_PRISM_WAR_CONFIGURATION: ItemData(339 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), + ItemNames.OBSERVER_GRAVITIC_BOOSTERS: ItemData(340 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.OBSERVER_SENSOR_ARRAY: ItemData(341 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.REAVER_SCARAB_DAMAGE: ItemData(342 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 12, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_SOLARITE_PAYLOAD: ItemData(343 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_REAVER_CAPACITY: ItemData(344 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_RESOURCE_EFFICIENCY: ItemData(345 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 15, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.VANGUARD_AGONY_LAUNCHERS: ItemData(346 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 16, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.VANGUARD_MATTER_DISPERSION: ItemData(347 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 17, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE: ItemData(348 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 18, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS: ItemData(349 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.COLOSSUS_PACIFICATION_PROTOCOL: ItemData(350 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 20, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.COLOSSUS), + ItemNames.WRATHWALKER_RAPID_POWER_CYCLING: ItemData(351 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.WRATHWALKER_EYE_OF_WRATH: ItemData(352 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 22, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN: ItemData(353 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 23, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING: ItemData(354 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 24, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK: ItemData(355 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY: ItemData(356 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 26, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD: ItemData(357 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 27, SC2Race.PROTOSS, origin={"bw"}, important_for_filtering=True ,parent_item=ItemNames.DARK_TEMPLAR), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM: ItemData(358 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 28, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION: ItemData(359 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 29, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET: ItemData(360 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 0, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ARCHON_HIGH_ARCHON: ItemData(361 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 1, SC2Race.PROTOSS, origin={"ext"}, important_for_filtering=True), + ItemNames.DARK_ARCHON_FEEDBACK: ItemData(362 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 2, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_MAELSTROM: ItemData(363 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 3, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_ARGUS_TALISMAN: ItemData(364 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 4, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ASCENDANT_POWER_OVERWHELMING: ItemData(365 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_CHAOTIC_ATTUNEMENT: ItemData(366 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_BLOOD_AMULET: ItemData(367 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 7, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE: ItemData(368 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 8, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING: ItemData(369 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 9, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_FORCE_FIELD: ItemData(370 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.SENTRY_HALLUCINATION: ItemData(371 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.ENERGIZER_RECLAMATION: ItemData(372 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.ENERGIZER_FORGED_CHASSIS: ItemData(373 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.HAVOC_DETECT_WEAKNESS: ItemData(374 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 14, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.HAVOC_BLOODSHARD_RESONANCE: ItemData(375 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: ItemData(376 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 16, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: ItemData(377 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 17, SC2Race.PROTOSS, origin={"bw"}), + + # SoA Calldown powers + ItemNames.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 0, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, "Progressive Upgrade", 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2), + ItemNames.SOA_PYLON_OVERCHARGE: ItemData(702 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 1, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SOA_ORBITAL_STRIKE: ItemData(703 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 2, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TEMPORAL_FIELD: ItemData(704 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 3, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SOLAR_LANCE: ItemData(705 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_MASS_RECALL: ItemData(706 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 5, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SHIELD_OVERCHARGE: ItemData(707 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 6, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_DEPLOY_FENIX: ItemData(708 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_PURIFIER_BEAM: ItemData(709 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 8, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TIME_STOP: ItemData(710 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 9, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_SOLAR_BOMBARDMENT: ItemData(711 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 10, SC2Race.PROTOSS, origin={"lotv"}), + + # Generic Protoss Upgrades + ItemNames.MATRIX_OVERLOAD: + ItemData(800 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 0, SC2Race.PROTOSS, origin={"lotv"}, + description=r"All friendly units gain 25% movement speed and 15% attack speed within a Pylon's power field and for 15 seconds after leaving it."), + ItemNames.QUATRO: + ItemData(801 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 1, SC2Race.PROTOSS, origin={"ext"}, + description="All friendly Protoss units gain the equivalent of their +1 armour, attack, and shield upgrades."), + ItemNames.NEXUS_OVERCHARGE: + ItemData(802 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 2, SC2Race.PROTOSS, origin={"lotv"}, + important_for_filtering=True, description="The Protoss Nexus gains a long-range auto-attack."), + ItemNames.ORBITAL_ASSIMILATORS: + ItemData(803 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 3, SC2Race.PROTOSS, origin={"lotv"}, + description="Assimilators automatically harvest Vespene Gas without the need for Probes."), + ItemNames.WARP_HARMONIZATION: + ItemData(804 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 4, SC2Race.PROTOSS, origin={"lotv"}, + description=r"Stargates and Robotics Facilities can transform to utilize Warp In technology. Warp In cooldowns are 20% faster than original build times."), + ItemNames.GUARDIAN_SHELL: + ItemData(805 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 5, SC2Race.PROTOSS, origin={"lotv"}, + description="The Spear of Adun passively shields friendly Protoss units before death, making them invulnerable for 5 seconds. Each unit can only be shielded once every 60 seconds."), + ItemNames.RECONSTRUCTION_BEAM: + ItemData(806 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 6, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="The Spear of Adun will passively heal mechanical units for 5 and non-biological structures for 10 life per second. Up to 3 targets can be repaired at once."), + ItemNames.OVERWATCH: + ItemData(807 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 7, SC2Race.PROTOSS, origin={"ext"}, + description="Once per second, the Spear of Adun will last-hit a damaged enemy unit that is below 50 health."), + ItemNames.SUPERIOR_WARP_GATES: + ItemData(808 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 8, SC2Race.PROTOSS, origin={"ext"}, + description="Protoss Warp Gates can hold up to 3 charges of unit warp-ins."), + ItemNames.ENHANCED_TARGETING: + ItemData(809 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 9, SC2Race.PROTOSS, origin={"ext"}, + description="Protoss defensive structures gain +2 range."), + ItemNames.OPTIMIZED_ORDNANCE: + ItemData(810 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 10, SC2Race.PROTOSS, origin={"ext"}, + description="Increases the attack speed of Protoss defensive structures by 25%."), + ItemNames.KHALAI_INGENUITY: + ItemData(811 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 11, SC2Race.PROTOSS, origin={"ext"}, + description="Pylons, Photon Cannons, Monoliths, and Shield Batteries warp in near-instantly."), + ItemNames.AMPLIFIED_ASSIMILATORS: + ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 12, SC2Race.PROTOSS, origin={"ext"}, + description=r"Assimilators produce Vespene gas 25% faster."), +} + + +def get_item_table(): + return item_table + + +basic_units = { + SC2Race.TERRAN: { + ItemNames.MARINE, + ItemNames.MARAUDER, + ItemNames.GOLIATH, + ItemNames.HELLION, + ItemNames.VULTURE, + ItemNames.WARHOUND, + }, + SC2Race.ZERG: { + ItemNames.ZERGLING, + ItemNames.SWARM_QUEEN, + ItemNames.ROACH, + ItemNames.HYDRALISK, + }, + SC2Race.PROTOSS: { + ItemNames.ZEALOT, + ItemNames.CENTURION, + ItemNames.SENTINEL, + ItemNames.STALKER, + ItemNames.INSTIGATOR, + ItemNames.SLAYER, + ItemNames.DRAGOON, + ItemNames.ADEPT, + } +} + +advanced_basic_units = { + SC2Race.TERRAN: basic_units[SC2Race.TERRAN].union({ + ItemNames.REAPER, + ItemNames.DIAMONDBACK, + ItemNames.VIKING, + ItemNames.SIEGE_TANK, + ItemNames.BANSHEE, + ItemNames.THOR, + ItemNames.BATTLECRUISER, + ItemNames.CYCLONE + }), + SC2Race.ZERG: basic_units[SC2Race.ZERG].union({ + ItemNames.INFESTOR, + ItemNames.ABERRATION, + }), + SC2Race.PROTOSS: basic_units[SC2Race.PROTOSS].union({ + ItemNames.DARK_TEMPLAR, + ItemNames.BLOOD_HUNTER, + ItemNames.AVENGER, + ItemNames.IMMORTAL, + ItemNames.ANNIHILATOR, + ItemNames.VANGUARD, + }) +} + +no_logic_starting_units = { + SC2Race.TERRAN: advanced_basic_units[SC2Race.TERRAN].union({ + ItemNames.FIREBAT, + ItemNames.GHOST, + ItemNames.SPECTRE, + ItemNames.WRAITH, + ItemNames.RAVEN, + ItemNames.PREDATOR, + ItemNames.LIBERATOR, + ItemNames.HERC, + }), + SC2Race.ZERG: advanced_basic_units[SC2Race.ZERG].union({ + ItemNames.ULTRALISK, + ItemNames.SWARM_HOST + }), + SC2Race.PROTOSS: advanced_basic_units[SC2Race.PROTOSS].union({ + ItemNames.CARRIER, + ItemNames.TEMPEST, + ItemNames.VOID_RAY, + ItemNames.DESTROYER, + ItemNames.COLOSSUS, + ItemNames.WRATHWALKER, + ItemNames.SCOUT, + ItemNames.HIGH_TEMPLAR, + ItemNames.SIGNIFIER, + ItemNames.ASCENDANT, + ItemNames.DARK_ARCHON, + ItemNames.SUPPLICANT, + }) +} + +not_balanced_starting_units = { + ItemNames.SIEGE_TANK, + ItemNames.THOR, + ItemNames.BANSHEE, + ItemNames.BATTLECRUISER, + ItemNames.ULTRALISK, + ItemNames.CARRIER, + ItemNames.TEMPEST, +} + + +def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: + logic_level = get_option_value(world, 'required_tactics') + if logic_level == RequiredTactics.option_no_logic: + return no_logic_starting_units[race] + elif logic_level == RequiredTactics.option_advanced: + return advanced_basic_units[race] + else: + return basic_units[race] + + +# Items that can be placed before resources if not already in +# General upgrades and Mercs +second_pass_placeable_items: typing.Tuple[str, ...] = ( + # Global weapon/armor upgrades + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS, + # Terran Buildings without upgrades + ItemNames.SENSOR_TOWER, + ItemNames.HIVE_MIND_EMULATOR, + ItemNames.PSI_DISRUPTER, + ItemNames.PERDITION_TURRET, + # Terran units without upgrades + ItemNames.HERC, + ItemNames.WARHOUND, + # General Terran upgrades without any dependencies + ItemNames.SCV_ADVANCED_CONSTRUCTION, + ItemNames.SCV_DUAL_FUSION_WELDERS, + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, + ItemNames.PROGRESSIVE_ORBITAL_COMMAND, + ItemNames.ULTRA_CAPACITORS, + ItemNames.VANADIUM_PLATING, + ItemNames.ORBITAL_DEPOTS, + ItemNames.MICRO_FILTERING, + ItemNames.AUTOMATED_REFINERY, + ItemNames.COMMAND_CENTER_REACTOR, + ItemNames.TECH_REACTOR, + ItemNames.CELLULAR_REACTOR, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, # Place only L1 + ItemNames.STRUCTURE_ARMOR, + ItemNames.HI_SEC_AUTO_TRACKING, + ItemNames.ADVANCED_OPTICS, + ItemNames.ROGUE_FORCES, + # Mercenaries (All races) + *[item_name for item_name, item_data in get_full_item_list().items() + if item_data.type == "Mercenary"], + # Kerrigan and Nova levels, abilities and generally useful stuff + *[item_name for item_name, item_data in get_full_item_list().items() + if item_data.type in ("Level", "Ability", "Evolution Pit", "Nova Gear")], + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, + # Zerg static defenses + ItemNames.SPORE_CRAWLER, + ItemNames.SPINE_CRAWLER, + # Defiler, Aberration (no upgrades) + ItemNames.DEFILER, + ItemNames.ABERRATION, + # Spear of Adun Abilities + ItemNames.SOA_CHRONO_SURGE, + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON, + ItemNames.SOA_PYLON_OVERCHARGE, + ItemNames.SOA_ORBITAL_STRIKE, + ItemNames.SOA_TEMPORAL_FIELD, + ItemNames.SOA_SOLAR_LANCE, + ItemNames.SOA_MASS_RECALL, + ItemNames.SOA_SHIELD_OVERCHARGE, + ItemNames.SOA_DEPLOY_FENIX, + ItemNames.SOA_PURIFIER_BEAM, + ItemNames.SOA_TIME_STOP, + ItemNames.SOA_SOLAR_BOMBARDMENT, + # Protoss generic upgrades + ItemNames.MATRIX_OVERLOAD, + ItemNames.QUATRO, + ItemNames.NEXUS_OVERCHARGE, + ItemNames.ORBITAL_ASSIMILATORS, + ItemNames.WARP_HARMONIZATION, + ItemNames.GUARDIAN_SHELL, + ItemNames.RECONSTRUCTION_BEAM, + ItemNames.OVERWATCH, + ItemNames.SUPERIOR_WARP_GATES, + ItemNames.KHALAI_INGENUITY, + ItemNames.AMPLIFIED_ASSIMILATORS, + # Protoss static defenses + ItemNames.PHOTON_CANNON, + ItemNames.KHAYDARIN_MONOLITH, + ItemNames.SHIELD_BATTERY +) + + +filler_items: typing.Tuple[str, ...] = ( + ItemNames.STARTING_MINERALS, + ItemNames.STARTING_VESPENE, + ItemNames.STARTING_SUPPLY, +) + +# Defense rating table +# Commented defense ratings are handled in LogicMixin +defense_ratings = { + ItemNames.SIEGE_TANK: 5, + # "Maelstrom Rounds": 2, + ItemNames.PLANETARY_FORTRESS: 3, + # Bunker w/ Marine/Marauder: 3, + ItemNames.PERDITION_TURRET: 2, + ItemNames.VULTURE: 1, + ItemNames.BANSHEE: 1, + ItemNames.BATTLECRUISER: 1, + ItemNames.LIBERATOR: 4, + ItemNames.WIDOW_MINE: 1, + # "Concealment (Widow Mine)": 1 +} +zerg_defense_ratings = { + ItemNames.PERDITION_TURRET: 2, + # Bunker w/ Firebat: 2, + ItemNames.LIBERATOR: -2, + ItemNames.HIVE_MIND_EMULATOR: 3, + ItemNames.PSI_DISRUPTER: 3, +} +air_defense_ratings = { + ItemNames.MISSILE_TURRET: 2, +} + +kerrigan_levels = [item_name for item_name, item_data in get_full_item_list().items() + if item_data.type == "Level" and item_data.race == SC2Race.ZERG] + +spider_mine_sources = { + ItemNames.VULTURE, + ItemNames.REAPER_SPIDER_MINES, + ItemNames.SIEGE_TANK_SPIDER_MINES, + ItemNames.RAVEN_SPIDER_MINES, +} + +progressive_if_nco = { + ItemNames.MARINE_PROGRESSIVE_STIMPACK, + ItemNames.FIREBAT_PROGRESSIVE_STIMPACK, + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, +} + +progressive_if_ext = { + ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE, + ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS, + ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, + ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, + ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, + ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL +} + +kerrigan_actives: typing.List[typing.Set[str]] = [ + {ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE}, + {ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT}, + set(), + {ItemNames.KERRIGAN_WILD_MUTATION, ItemNames.KERRIGAN_SPAWN_BANELINGS, ItemNames.KERRIGAN_MEND}, + set(), + set(), + {ItemNames.KERRIGAN_APOCALYPSE, ItemNames.KERRIGAN_SPAWN_LEVIATHAN, ItemNames.KERRIGAN_DROP_PODS}, +] + +kerrigan_passives: typing.List[typing.Set[str]] = [ + {ItemNames.KERRIGAN_HEROIC_FORTITUDE}, + {ItemNames.KERRIGAN_CHAIN_REACTION}, + {ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION, ItemNames.KERRIGAN_IMPROVED_OVERLORDS, ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS}, + set(), + {ItemNames.KERRIGAN_TWIN_DRONES, ItemNames.KERRIGAN_MALIGNANT_CREEP, ItemNames.KERRIGAN_VESPENE_EFFICIENCY}, + {ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY, ItemNames.KERRIGAN_ABILITY_EFFICIENCY}, + set(), +] + +kerrigan_only_passives = { + ItemNames.KERRIGAN_HEROIC_FORTITUDE, ItemNames.KERRIGAN_CHAIN_REACTION, + ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY, ItemNames.KERRIGAN_ABILITY_EFFICIENCY, +} + +spear_of_adun_calldowns = { + ItemNames.SOA_CHRONO_SURGE, + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON, + ItemNames.SOA_PYLON_OVERCHARGE, + ItemNames.SOA_ORBITAL_STRIKE, + ItemNames.SOA_TEMPORAL_FIELD, + ItemNames.SOA_SOLAR_LANCE, + ItemNames.SOA_MASS_RECALL, + ItemNames.SOA_SHIELD_OVERCHARGE, + ItemNames.SOA_DEPLOY_FENIX, + ItemNames.SOA_PURIFIER_BEAM, + ItemNames.SOA_TIME_STOP, + ItemNames.SOA_SOLAR_BOMBARDMENT +} + +spear_of_adun_castable_passives = { + ItemNames.RECONSTRUCTION_BEAM, + ItemNames.OVERWATCH, +} + +nova_equipment = { + *[item_name for item_name, item_data in get_full_item_list().items() + if item_data.type == "Nova Gear"], + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE +} + +# 'number' values of upgrades for upgrade bundle items +upgrade_numbers = [ + # Terran + {0, 4, 8}, # Weapon + {2, 6, 10}, # Armor + {0, 2}, # Infantry + {4, 6}, # Vehicle + {8, 10}, # Starship + {0, 2, 4, 6, 8, 10}, # All + # Zerg + {0, 2, 6}, # Weapon + {4, 8}, # Armor + {0, 2, 4}, # Ground + {6, 8}, # Flyer + {0, 2, 4, 6, 8}, # All + # Protoss + {0, 6}, # Weapon + {2, 4, 8}, # Armor + {0, 2}, # Ground, Shields are handled specially + {6, 8}, # Air, Shields are handled specially + {0, 2, 4, 6, 8}, # All +] +# 'upgrade_numbers' indices for all upgrades +upgrade_numbers_all = { + SC2Race.TERRAN: 5, + SC2Race.ZERG: 10, + SC2Race.PROTOSS: 15, +} + +# Names of upgrades to be included for different options +upgrade_included_names = [ + { # Individual Items + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, + ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON, + ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR, + ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK, + ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK, + ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE, + ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK, + ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE, + ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON, + ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR, + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS, + ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON, + ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR, + }, + { # Bundle Weapon And Armor + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE, + }, + { # Bundle Unit Class + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE, + }, + { # Bundle All + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE, + } +] + +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if + data.code} + +# Map type to expected int +type_flaggroups: typing.Dict[SC2Race, typing.Dict[str, int]] = { + SC2Race.ANY: { + "Minerals": 0, + "Vespene": 1, + "Supply": 2, + "Goal": 3, + "Nothing Group": 4, + }, + SC2Race.TERRAN: { + "Armory 1": 0, + "Armory 2": 1, + "Armory 3": 2, + "Armory 4": 3, + "Armory 5": 4, + "Armory 6": 5, + "Progressive Upgrade": 6, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) + "Laboratory": 7, + "Upgrade": 8, # Weapon / Armor upgrades + "Unit": 9, + "Building": 10, + "Mercenary": 11, + "Nova Gear": 12, + "Progressive Upgrade 2": 13, + }, + SC2Race.ZERG: { + "Ability": 0, + "Mutation 1": 1, + "Strain": 2, + "Morph": 3, + "Upgrade": 4, + "Mercenary": 5, + "Unit": 6, + "Level": 7, + "Primal Form": 8, + "Evolution Pit": 9, + "Mutation 2": 10, + "Mutation 3": 11 + }, + SC2Race.PROTOSS: { + "Unit": 0, + "Unit 2": 1, + "Upgrade": 2, # Weapon / Armor upgrades + "Building": 3, + "Progressive Upgrade": 4, + "Spear of Adun": 5, + "Solarite Core": 6, + "Forge 1": 7, + "Forge 2": 8, + "Forge 3": 9, + } +} diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py new file mode 100644 index 000000000000..bf9c06fa3f78 --- /dev/null +++ b/worlds/sc2/Locations.py @@ -0,0 +1,1638 @@ +from enum import IntEnum +from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any +from BaseClasses import MultiWorld +from . import ItemNames +from .Options import get_option_value, kerrigan_unit_available, RequiredTactics, GrantStoryTech, LocationInclusion, \ + EnableHotsMissions +from .Rules import SC2Logic + +from BaseClasses import Location +from worlds.AutoWorld import World + +SC2WOL_LOC_ID_OFFSET = 1000 +SC2HOTS_LOC_ID_OFFSET = 20000000 # Avoid clashes with The Legend of Zelda +SC2LOTV_LOC_ID_OFFSET = SC2HOTS_LOC_ID_OFFSET + 2000 +SC2NCO_LOC_ID_OFFSET = SC2LOTV_LOC_ID_OFFSET + 2500 + + +class SC2Location(Location): + game: str = "Starcraft2" + + +class LocationType(IntEnum): + VICTORY = 0 # Winning a mission + VANILLA = 1 # Objectives that provided metaprogression in the original campaign, along with a few other locations for a balanced experience + EXTRA = 2 # Additional locations based on mission progression, collecting in-mission rewards, etc. that do not significantly increase the challenge. + CHALLENGE = 3 # Challenging objectives, often harder than just completing a mission, and often associated with Achievements + MASTERY = 4 # Extremely challenging objectives often associated with Masteries and Feats of Strength in the original campaign + + +class LocationData(NamedTuple): + region: str + name: str + code: Optional[int] + type: LocationType + rule: Optional[Callable[[Any], bool]] = Location.access_rule + + +def get_location_types(world: World, inclusion_type: LocationInclusion) -> Set[LocationType]: + """ + + :param multiworld: + :param player: + :param inclusion_type: Level of inclusion to check for + :return: A list of location types that match the inclusion type + """ + exclusion_options = [ + ("vanilla_locations", LocationType.VANILLA), + ("extra_locations", LocationType.EXTRA), + ("challenge_locations", LocationType.CHALLENGE), + ("mastery_locations", LocationType.MASTERY) + ] + excluded_location_types = set() + for option_name, location_type in exclusion_options: + if get_option_value(world, option_name) is inclusion_type: + excluded_location_types.add(location_type) + return excluded_location_types + + +def get_plando_locations(world: World) -> List[str]: + """ + + :param multiworld: + :param player: + :return: A list of locations affected by a plando in a world + """ + if world is None: + return [] + plando_locations = [] + for plando_setting in world.multiworld.plando_items[world.player]: + plando_locations += plando_setting.get("locations", []) + plando_setting_location = plando_setting.get("location", None) + if plando_setting_location is not None: + plando_locations.append(plando_setting_location) + + return plando_locations + + +def get_locations(world: Optional[World]) -> Tuple[LocationData, ...]: + # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option + logic_level = get_option_value(world, 'required_tactics') + adv_tactics = logic_level != RequiredTactics.option_standard + kerriganless = get_option_value(world, 'kerrigan_presence') not in kerrigan_unit_available \ + or get_option_value(world, "enable_hots_missions") == EnableHotsMissions.option_false + story_tech_granted = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true + logic = SC2Logic(world) + player = None if world is None else world.player + location_table: List[LocationData] = [ + # WoL + LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100, LocationType.VICTORY), + LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Second Statue", SC2WOL_LOC_ID_OFFSET + 102, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Third Statue", SC2WOL_LOC_ID_OFFSET + 103, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Special Delivery", SC2WOL_LOC_ID_OFFSET + 107, LocationType.EXTRA), + LocationData("Liberation Day", "Liberation Day: Transport", SC2WOL_LOC_ID_OFFSET + 108, LocationType.EXTRA), + LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: North Resource Pickups", SC2WOL_LOC_ID_OFFSET + 202, LocationType.EXTRA, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: Bunker", SC2WOL_LOC_ID_OFFSET + 203, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: Close Resource Pickups", SC2WOL_LOC_ID_OFFSET + 204, LocationType.EXTRA), + LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2 and + (adv_tactics or logic.terran_basic_anti_air(state))), + LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301, LocationType.VANILLA), + LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state)), + LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2), + LocationData("Zero Hour", "Zero Hour: First Hatchery", SC2WOL_LOC_ID_OFFSET + 304, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 305, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 306, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Fourth Hatchery", SC2WOL_LOC_ID_OFFSET + 307, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Ride's on its Way", SC2WOL_LOC_ID_OFFSET + 308, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Zero Hour", "Zero Hour: Hold Just a Little Longer", SC2WOL_LOC_ID_OFFSET + 309, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2), + LocationData("Zero Hour", "Zero Hour: Cavalry's on the Way", SC2WOL_LOC_ID_OFFSET + 310, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2), + LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400, LocationType.VICTORY, + lambda state: logic.terran_early_tech(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Evacuation", "Evacuation: North Chrysalis", SC2WOL_LOC_ID_OFFSET + 401, LocationType.VANILLA), + LocationData("Evacuation", "Evacuation: West Chrysalis", SC2WOL_LOC_ID_OFFSET + 402, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("Evacuation", "Evacuation: East Chrysalis", SC2WOL_LOC_ID_OFFSET + 403, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("Evacuation", "Evacuation: Reach Hanson", SC2WOL_LOC_ID_OFFSET + 404, LocationType.EXTRA), + LocationData("Evacuation", "Evacuation: Secret Resource Stash", SC2WOL_LOC_ID_OFFSET + 405, LocationType.EXTRA), + LocationData("Evacuation", "Evacuation: Flawless", SC2WOL_LOC_ID_OFFSET + 406, LocationType.CHALLENGE, + lambda state: logic.terran_early_tech(state) and + logic.terran_defense_rating(state, True, False) >= 2 and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.terran_defense_rating(state, True, False) >= 4 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: North Infested Command Center", SC2WOL_LOC_ID_OFFSET + 503, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: South Infested Command Center", SC2WOL_LOC_ID_OFFSET + 504, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: Northwest Bar", SC2WOL_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: North Bar", SC2WOL_LOC_ID_OFFSET + 506, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: South Bar", SC2WOL_LOC_ID_OFFSET + 507, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: North Nexus", SC2WOL_LOC_ID_OFFSET + 601, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: East Nexus", SC2WOL_LOC_ID_OFFSET + 602, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: First Terror Fleet", SC2WOL_LOC_ID_OFFSET + 604, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: Second Terror Fleet", SC2WOL_LOC_ID_OFFSET + 605, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: Third Terror Fleet", SC2WOL_LOC_ID_OFFSET + 606, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: Northeast Colony Base", SC2WOL_LOC_ID_OFFSET + 704, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: East Colony Base", SC2WOL_LOC_ID_OFFSET + 705, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Middle Colony Base", SC2WOL_LOC_ID_OFFSET + 706, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Southeast Colony Base", SC2WOL_LOC_ID_OFFSET + 707, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Southwest Colony Base", SC2WOL_LOC_ID_OFFSET + 708, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Southwest Gas Pickups", SC2WOL_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: East Gas Pickups", SC2WOL_LOC_ID_OFFSET + 710, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: Southeast Gas Pickups", SC2WOL_LOC_ID_OFFSET + 711, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801, LocationType.VANILLA), + LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802, LocationType.VANILLA), + LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: First Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 805, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: Second Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 806, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Moebius Base", SC2WOL_LOC_ID_OFFSET + 904, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state) or adv_tactics), + LocationData("The Dig", "The Dig: Door Outer Layer", SC2WOL_LOC_ID_OFFSET + 905, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Door Thermal Barrier", SC2WOL_LOC_ID_OFFSET + 906, LocationType.EXTRA, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Cutting Through the Core", SC2WOL_LOC_ID_OFFSET + 907, LocationType.EXTRA, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Structure Access Imminent", SC2WOL_LOC_ID_OFFSET + 908, LocationType.EXTRA, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000, LocationType.VICTORY, + lambda state: logic.terran_basic_anti_air(state) and + (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core", SC2WOL_LOC_ID_OFFSET + 1001, LocationType.VANILLA), + LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002, LocationType.VANILLA, + lambda state: (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Mid Rescue", SC2WOL_LOC_ID_OFFSET + 1005, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Nydus Roof Rescue", SC2WOL_LOC_ID_OFFSET + 1006, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008, LocationType.VANILLA, + lambda state: logic.terran_basic_anti_air(state) and + (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1009, LocationType.VANILLA, + lambda state: logic.terran_basic_anti_air(state) and + (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100, LocationType.VICTORY, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101, LocationType.VANILLA), + LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102, LocationType.VANILLA), + LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103, LocationType.VANILLA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104, LocationType.VANILLA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1105, LocationType.EXTRA), + LocationData("Supernova", "Supernova: Middle Base", SC2WOL_LOC_ID_OFFSET + 1106, LocationType.EXTRA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1107, LocationType.EXTRA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200, LocationType.VICTORY, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201, LocationType.EXTRA), + LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202, LocationType.VANILLA, + lambda state: adv_tactics or logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203, LocationType.VANILLA, + lambda state: adv_tactics or logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204, LocationType.VANILLA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205, LocationType.VANILLA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Mothership", SC2WOL_LOC_ID_OFFSET + 1206, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Expansion Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1207, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Middle Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1208, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Southeast Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1209, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Stargate Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1210, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Northwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1211, LocationType.CHALLENGE, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: West Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1212, LocationType.CHALLENGE, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Southwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1213, LocationType.CHALLENGE, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300, LocationType.VICTORY, + lambda state: adv_tactics or + logic.terran_basic_anti_air(state) and ( + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301, LocationType.VANILLA), + LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302, LocationType.VANILLA, + lambda state: adv_tactics or logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player)), + LocationData("Devil's Playground", "Devil's Playground: North Reapers", SC2WOL_LOC_ID_OFFSET + 1303, LocationType.EXTRA), + LocationData("Devil's Playground", "Devil's Playground: Middle Reapers", SC2WOL_LOC_ID_OFFSET + 1304, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player)), + LocationData("Devil's Playground", "Devil's Playground: Southwest Reapers", SC2WOL_LOC_ID_OFFSET + 1305, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player)), + LocationData("Devil's Playground", "Devil's Playground: Southeast Reapers", SC2WOL_LOC_ID_OFFSET + 1306, LocationType.EXTRA, + lambda state: adv_tactics or + logic.terran_basic_anti_air(state) and ( + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Devil's Playground", "Devil's Playground: East Reapers", SC2WOL_LOC_ID_OFFSET + 1307, LocationType.CHALLENGE, + lambda state: logic.terran_basic_anti_air(state) and + (adv_tactics or + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Devil's Playground", "Devil's Playground: Zerg Cleared", SC2WOL_LOC_ID_OFFSET + 1308, LocationType.CHALLENGE, + lambda state: logic.terran_competent_anti_air(state) and ( + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400, LocationType.VICTORY, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401, LocationType.VANILLA), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402, LocationType.VANILLA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403, LocationType.VANILLA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Middle Base", SC2WOL_LOC_ID_OFFSET + 1404, LocationType.EXTRA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Main Base", SC2WOL_LOC_ID_OFFSET + 1405, + LocationType.MASTERY, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_beats_protoss_deathball(state) + and logic.terran_base_trasher(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: No Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1406, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_competent_ground_to_air(state) + and logic.terran_beats_protoss_deathball(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 1 Terrazine Node Sealed", SC2WOL_LOC_ID_OFFSET + 1407, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_competent_ground_to_air(state) + and logic.terran_beats_protoss_deathball(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 2 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1408, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_beats_protoss_deathball(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 3 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1409, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_competent_comp(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 4 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1410, LocationType.EXTRA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 5 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1411, LocationType.EXTRA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500, LocationType.VICTORY), + LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501, LocationType.VANILLA), + LocationData("Breakout", "Breakout: Siege Tank Prison", SC2WOL_LOC_ID_OFFSET + 1502, LocationType.VANILLA), + LocationData("Breakout", "Breakout: First Checkpoint", SC2WOL_LOC_ID_OFFSET + 1503, LocationType.EXTRA), + LocationData("Breakout", "Breakout: Second Checkpoint", SC2WOL_LOC_ID_OFFSET + 1504, LocationType.EXTRA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600, LocationType.VICTORY), + LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601, LocationType.EXTRA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602, LocationType.EXTRA), + LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603, LocationType.VANILLA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604, LocationType.VANILLA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700, LocationType.VICTORY, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Close Diamondback", SC2WOL_LOC_ID_OFFSET + 1704, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Northwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1705, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: North Diamondback", SC2WOL_LOC_ID_OFFSET + 1706, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Northeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1707, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Southwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1708, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Southeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1709, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Kill Team", SC2WOL_LOC_ID_OFFSET + 1710, LocationType.CHALLENGE, + lambda state: (adv_tactics or logic.terran_common_unit(state)) and + logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: Flawless", SC2WOL_LOC_ID_OFFSET + 1711, LocationType.CHALLENGE, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: 2 Trains Destroyed", SC2WOL_LOC_ID_OFFSET + 1712, LocationType.EXTRA, + lambda state: logic.great_train_robbery_train_stopper(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: 4 Trains Destroyed", SC2WOL_LOC_ID_OFFSET + 1713, LocationType.EXTRA, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: 6 Trains Destroyed", SC2WOL_LOC_ID_OFFSET + 1714, LocationType.EXTRA, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + (adv_tactics or logic.terran_basic_anti_air)), + LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803, LocationType.VANILLA), + LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: North Command Center", SC2WOL_LOC_ID_OFFSET + 1805, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: South Command Center", SC2WOL_LOC_ID_OFFSET + 1806, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: West Command Center", SC2WOL_LOC_ID_OFFSET + 1807, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900, LocationType.VICTORY, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902, + LocationType.CHALLENGE, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904, LocationType.VANILLA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905, LocationType.VANILLA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: West Base", SC2WOL_LOC_ID_OFFSET + 1906, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Northwest Base", SC2WOL_LOC_ID_OFFSET + 1907, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Northeast Base", SC2WOL_LOC_ID_OFFSET + 1908, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1909, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000, LocationType.VICTORY, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004, LocationType.VANILLA), + LocationData("Media Blitz", "Media Blitz: All Barracks", SC2WOL_LOC_ID_OFFSET + 2005, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: All Factories", SC2WOL_LOC_ID_OFFSET + 2006, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: All Starports", SC2WOL_LOC_ID_OFFSET + 2007, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Odin Not Trashed", SC2WOL_LOC_ID_OFFSET + 2008, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Surprise Attack Ends", SC2WOL_LOC_ID_OFFSET + 2009, LocationType.EXTRA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100, LocationType.VICTORY, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101, LocationType.VANILLA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk", SC2WOL_LOC_ID_OFFSET + 2105, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Fusion Reactor", SC2WOL_LOC_ID_OFFSET + 2106, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Entrance Holding Pen", SC2WOL_LOC_ID_OFFSET + 2107, LocationType.EXTRA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Cargo Bay Warbot", SC2WOL_LOC_ID_OFFSET + 2108, LocationType.EXTRA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Escape Warbot", SC2WOL_LOC_ID_OFFSET + 2109, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200, LocationType.VICTORY), + LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201, LocationType.VANILLA), + LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202, LocationType.VANILLA), + LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203, LocationType.VANILLA), + LocationData("Whispers of Doom", "Whispers of Doom: First Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2204, LocationType.EXTRA), + LocationData("Whispers of Doom", "Whispers of Doom: Second Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2205, LocationType.EXTRA), + LocationData("Whispers of Doom", "Whispers of Doom: Third Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2206, LocationType.EXTRA), + LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301, LocationType.VANILLA, + lambda state: adv_tactics or logic.protoss_common_unit(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302, LocationType.VANILLA, + lambda state: adv_tactics or logic.protoss_common_unit(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Northeast Base", SC2WOL_LOC_ID_OFFSET + 2304, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Southwest Base", SC2WOL_LOC_ID_OFFSET + 2305, LocationType.CHALLENGE, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Maar", SC2WOL_LOC_ID_OFFSET + 2306, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Northwest Preserver", SC2WOL_LOC_ID_OFFSET + 2307, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Southwest Preserver", SC2WOL_LOC_ID_OFFSET + 2308, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: East Preserver", SC2WOL_LOC_ID_OFFSET + 2309, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400, LocationType.VICTORY, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401, LocationType.VANILLA), + LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402, LocationType.VANILLA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Base", SC2WOL_LOC_ID_OFFSET + 2403, LocationType.EXTRA), + LocationData("Echoes of the Future", "Echoes of the Future: Southwest Tendril", SC2WOL_LOC_ID_OFFSET + 2404, LocationType.EXTRA), + LocationData("Echoes of the Future", "Echoes of the Future: Southeast Tendril", SC2WOL_LOC_ID_OFFSET + 2405, LocationType.EXTRA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Northeast Tendril", SC2WOL_LOC_ID_OFFSET + 2406, LocationType.EXTRA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Northwest Tendril", SC2WOL_LOC_ID_OFFSET + 2407, LocationType.EXTRA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500, LocationType.VICTORY), + LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Urun", SC2WOL_LOC_ID_OFFSET + 2503, LocationType.EXTRA), + LocationData("In Utter Darkness", "In Utter Darkness: Mohandar", SC2WOL_LOC_ID_OFFSET + 2504, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Selendis", SC2WOL_LOC_ID_OFFSET + 2505, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Artanis", SC2WOL_LOC_ID_OFFSET + 2506, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600, LocationType.VICTORY, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 2 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2602, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 4 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2603, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 6 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2604, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 8 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2605, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Southwest Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2606, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Northwest Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2607, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Northeast Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2608, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: East Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2609, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Southeast Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2610, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Expansion Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2611, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Belly of the Beast", "Belly of the Beast: Victory", SC2WOL_LOC_ID_OFFSET + 2700, LocationType.VICTORY), + LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701, LocationType.EXTRA), + LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702, LocationType.EXTRA), + LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703, LocationType.EXTRA), + LocationData("Belly of the Beast", "Belly of the Beast: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 2704, LocationType.VANILLA), + LocationData("Belly of the Beast", "Belly of the Beast: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 2705, LocationType.VANILLA), + LocationData("Belly of the Beast", "Belly of the Beast: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 2706, LocationType.VANILLA), + LocationData("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800, LocationType.VICTORY, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: East Hatchery", SC2WOL_LOC_ID_OFFSET + 2806, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: North Hatchery", SC2WOL_LOC_ID_OFFSET + 2807, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Mid Hatchery", SC2WOL_LOC_ID_OFFSET + 2808, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("All-In", "All-In: Victory", SC2WOL_LOC_ID_OFFSET + 2900, LocationType.VICTORY, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: First Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2901, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Second Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2902, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Third Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2903, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Fourth Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2904, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Fifth Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2905, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + + # HotS + LocationData("Lab Rat", "Lab Rat: Victory", SC2HOTS_LOC_ID_OFFSET + 100, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: Gather Minerals", SC2HOTS_LOC_ID_OFFSET + 101, LocationType.VANILLA), + LocationData("Lab Rat", "Lab Rat: South Zergling Group", SC2HOTS_LOC_ID_OFFSET + 102, LocationType.VANILLA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: East Zergling Group", SC2HOTS_LOC_ID_OFFSET + 103, LocationType.VANILLA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: West Zergling Group", SC2HOTS_LOC_ID_OFFSET + 104, LocationType.VANILLA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: Hatchery", SC2HOTS_LOC_ID_OFFSET + 105, LocationType.EXTRA), + LocationData("Lab Rat", "Lab Rat: Overlord", SC2HOTS_LOC_ID_OFFSET + 106, LocationType.EXTRA), + LocationData("Lab Rat", "Lab Rat: Gas Turrets", SC2HOTS_LOC_ID_OFFSET + 107, LocationType.EXTRA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Back in the Saddle", "Back in the Saddle: Victory", SC2HOTS_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.basic_kerrigan(state) or kerriganless or logic.story_tech_granted), + LocationData("Back in the Saddle", "Back in the Saddle: Defend the Tram", SC2HOTS_LOC_ID_OFFSET + 201, LocationType.EXTRA, + lambda state: logic.basic_kerrigan(state) or kerriganless or logic.story_tech_granted), + LocationData("Back in the Saddle", "Back in the Saddle: Kinetic Blast", SC2HOTS_LOC_ID_OFFSET + 202, LocationType.VANILLA), + LocationData("Back in the Saddle", "Back in the Saddle: Crushing Grip", SC2HOTS_LOC_ID_OFFSET + 203, LocationType.VANILLA), + LocationData("Back in the Saddle", "Back in the Saddle: Reach the Sublevel", SC2HOTS_LOC_ID_OFFSET + 204, LocationType.EXTRA), + LocationData("Back in the Saddle", "Back in the Saddle: Door Section Cleared", SC2HOTS_LOC_ID_OFFSET + 205, LocationType.EXTRA, + lambda state: logic.basic_kerrigan(state) or kerriganless or logic.story_tech_granted), + LocationData("Rendezvous", "Rendezvous: Victory", SC2HOTS_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Right Queen", SC2HOTS_LOC_ID_OFFSET + 301, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Center Queen", SC2HOTS_LOC_ID_OFFSET + 302, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Left Queen", SC2HOTS_LOC_ID_OFFSET + 303, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Hold Out Finished", SC2HOTS_LOC_ID_OFFSET + 304, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Victory", SC2HOTS_LOC_ID_OFFSET + 400, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: First Ursadon Matriarch", SC2HOTS_LOC_ID_OFFSET + 401, LocationType.VANILLA), + LocationData("Harvest of Screams", "Harvest of Screams: North Ursadon Matriarch", SC2HOTS_LOC_ID_OFFSET + 402, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Harvest of Screams", "Harvest of Screams: West Ursadon Matriarch", SC2HOTS_LOC_ID_OFFSET + 403, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Lost Brood", SC2HOTS_LOC_ID_OFFSET + 404, LocationType.EXTRA), + LocationData("Harvest of Screams", "Harvest of Screams: Northeast Psi-link Spire", SC2HOTS_LOC_ID_OFFSET + 405, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Northwest Psi-link Spire", SC2HOTS_LOC_ID_OFFSET + 406, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Southwest Psi-link Spire", SC2HOTS_LOC_ID_OFFSET + 407, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Nafash", SC2HOTS_LOC_ID_OFFSET + 408, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Victory", SC2HOTS_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: East Stasis Chamber", SC2HOTS_LOC_ID_OFFSET + 501, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Center Stasis Chamber", SC2HOTS_LOC_ID_OFFSET + 502, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) or adv_tactics), + LocationData("Shoot the Messenger", "Shoot the Messenger: West Stasis Chamber", SC2HOTS_LOC_ID_OFFSET + 503, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Destroy 4 Shuttles", SC2HOTS_LOC_ID_OFFSET + 504, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Frozen Expansion", SC2HOTS_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Southwest Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 506, LocationType.EXTRA), + LocationData("Shoot the Messenger", "Shoot the Messenger: Southeast Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 507, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) or adv_tactics), + LocationData("Shoot the Messenger", "Shoot the Messenger: West Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 508, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: East Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 509, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state)), + LocationData("Enemy Within", "Enemy Within: Victory", SC2HOTS_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.zerg_pass_vents(state) + and (logic.story_tech_granted + or state.has_any({ItemNames.ZERGLING_RAPTOR_STRAIN, ItemNames.ROACH, + ItemNames.HYDRALISK, ItemNames.INFESTOR}, player)) + ), + LocationData("Enemy Within", "Enemy Within: Infest Giant Ursadon", SC2HOTS_LOC_ID_OFFSET + 601, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: First Niadra Evolution", SC2HOTS_LOC_ID_OFFSET + 602, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Second Niadra Evolution", SC2HOTS_LOC_ID_OFFSET + 603, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Third Niadra Evolution", SC2HOTS_LOC_ID_OFFSET + 604, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Warp Drive", SC2HOTS_LOC_ID_OFFSET + 605, LocationType.EXTRA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Stasis Quadrant", SC2HOTS_LOC_ID_OFFSET + 606, LocationType.EXTRA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Domination", "Domination: Victory", SC2HOTS_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Domination", "Domination: Center Infested Command Center", SC2HOTS_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: North Infested Command Center", SC2HOTS_LOC_ID_OFFSET + 702, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Repel Zagara", SC2HOTS_LOC_ID_OFFSET + 703, LocationType.EXTRA), + LocationData("Domination", "Domination: Close Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 704, LocationType.EXTRA), + LocationData("Domination", "Domination: South Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 705, LocationType.EXTRA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Southwest Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 706, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Southeast Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 707, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Domination", "Domination: North Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 708, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Northeast Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Fire in the Sky", "Fire in the Sky: Victory", SC2HOTS_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: West Biomass", SC2HOTS_LOC_ID_OFFSET + 801, LocationType.VANILLA), + LocationData("Fire in the Sky", "Fire in the Sky: North Biomass", SC2HOTS_LOC_ID_OFFSET + 802, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: South Biomass", SC2HOTS_LOC_ID_OFFSET + 803, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: Destroy 3 Gorgons", SC2HOTS_LOC_ID_OFFSET + 804, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: Close Zerg Rescue", SC2HOTS_LOC_ID_OFFSET + 805, LocationType.EXTRA), + LocationData("Fire in the Sky", "Fire in the Sky: South Zerg Rescue", SC2HOTS_LOC_ID_OFFSET + 806, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Fire in the Sky", "Fire in the Sky: North Zerg Rescue", SC2HOTS_LOC_ID_OFFSET + 807, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: West Queen Rescue", SC2HOTS_LOC_ID_OFFSET + 808, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: East Queen Rescue", SC2HOTS_LOC_ID_OFFSET + 809, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Old Soldiers", "Old Soldiers: Victory", SC2HOTS_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: East Science Lab", SC2HOTS_LOC_ID_OFFSET + 901, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: North Science Lab", SC2HOTS_LOC_ID_OFFSET + 902, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: Get Nuked", SC2HOTS_LOC_ID_OFFSET + 903, LocationType.EXTRA), + LocationData("Old Soldiers", "Old Soldiers: Entrance Gate", SC2HOTS_LOC_ID_OFFSET + 904, LocationType.EXTRA), + LocationData("Old Soldiers", "Old Soldiers: Citadel Gate", SC2HOTS_LOC_ID_OFFSET + 905, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: South Expansion", SC2HOTS_LOC_ID_OFFSET + 906, LocationType.EXTRA), + LocationData("Old Soldiers", "Old Soldiers: Rich Mineral Expansion", SC2HOTS_LOC_ID_OFFSET + 907, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: Victory", SC2HOTS_LOC_ID_OFFSET + 1000, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: Center Essence Pool", SC2HOTS_LOC_ID_OFFSET + 1001, LocationType.VANILLA), + LocationData("Waking the Ancient", "Waking the Ancient: East Essence Pool", SC2HOTS_LOC_ID_OFFSET + 1002, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + (adv_tactics and logic.zerg_basic_anti_air(state) + or logic.zerg_competent_anti_air(state))), + LocationData("Waking the Ancient", "Waking the Ancient: South Essence Pool", SC2HOTS_LOC_ID_OFFSET + 1003, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + (adv_tactics and logic.zerg_basic_anti_air(state) + or logic.zerg_competent_anti_air(state))), + LocationData("Waking the Ancient", "Waking the Ancient: Finish Feeding", SC2HOTS_LOC_ID_OFFSET + 1004, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: South Proxy Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1005, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: East Proxy Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1006, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: South Main Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1007, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: East Main Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1008, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Victory", SC2HOTS_LOC_ID_OFFSET + 1100, LocationType.VICTORY, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Tyrannozor", SC2HOTS_LOC_ID_OFFSET + 1101, LocationType.VANILLA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Reach the Pool", SC2HOTS_LOC_ID_OFFSET + 1102, LocationType.VANILLA), + LocationData("The Crucible", "The Crucible: 15 Minutes Remaining", SC2HOTS_LOC_ID_OFFSET + 1103, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: 5 Minutes Remaining", SC2HOTS_LOC_ID_OFFSET + 1104, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Pincer Attack", SC2HOTS_LOC_ID_OFFSET + 1105, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Yagdra Claims Brakk's Pack", SC2HOTS_LOC_ID_OFFSET + 1106, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Supreme", "Supreme: Victory", SC2HOTS_LOC_ID_OFFSET + 1200, LocationType.VICTORY, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: First Relic", SC2HOTS_LOC_ID_OFFSET + 1201, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Second Relic", SC2HOTS_LOC_ID_OFFSET + 1202, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Third Relic", SC2HOTS_LOC_ID_OFFSET + 1203, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Fourth Relic", SC2HOTS_LOC_ID_OFFSET + 1204, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Yagdra", SC2HOTS_LOC_ID_OFFSET + 1205, LocationType.EXTRA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Kraith", SC2HOTS_LOC_ID_OFFSET + 1206, LocationType.EXTRA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Slivan", SC2HOTS_LOC_ID_OFFSET + 1207, LocationType.EXTRA, + lambda state: logic.supreme_requirement(state)), + LocationData("Infested", "Infested: Victory", SC2HOTS_LOC_ID_OFFSET + 1300, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + ((logic.zerg_competent_anti_air(state) and state.has(ItemNames.INFESTOR, player)) or + (adv_tactics and logic.zerg_basic_anti_air(state)))), + LocationData("Infested", "Infested: East Science Facility", SC2HOTS_LOC_ID_OFFSET + 1301, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Infested", "Infested: Center Science Facility", SC2HOTS_LOC_ID_OFFSET + 1302, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Infested", "Infested: West Science Facility", SC2HOTS_LOC_ID_OFFSET + 1303, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Infested", "Infested: First Intro Garrison", SC2HOTS_LOC_ID_OFFSET + 1304, LocationType.EXTRA), + LocationData("Infested", "Infested: Second Intro Garrison", SC2HOTS_LOC_ID_OFFSET + 1305, LocationType.EXTRA), + LocationData("Infested", "Infested: Base Garrison", SC2HOTS_LOC_ID_OFFSET + 1306, LocationType.EXTRA), + LocationData("Infested", "Infested: East Garrison", SC2HOTS_LOC_ID_OFFSET + 1307, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: Mid Garrison", SC2HOTS_LOC_ID_OFFSET + 1308, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: North Garrison", SC2HOTS_LOC_ID_OFFSET + 1309, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: Close Southwest Garrison", SC2HOTS_LOC_ID_OFFSET + 1310, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: Far Southwest Garrison", SC2HOTS_LOC_ID_OFFSET + 1311, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Hand of Darkness", "Hand of Darkness: Victory", SC2HOTS_LOC_ID_OFFSET + 1400, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: North Brutalisk", SC2HOTS_LOC_ID_OFFSET + 1401, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: South Brutalisk", SC2HOTS_LOC_ID_OFFSET + 1402, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 1 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1403, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 2 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1404, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 3 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1405, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 4 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1406, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 5 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1407, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 6 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1408, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 7 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1409, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Victory", SC2HOTS_LOC_ID_OFFSET + 1500, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northwest Crystal", SC2HOTS_LOC_ID_OFFSET + 1501, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northeast Crystal", SC2HOTS_LOC_ID_OFFSET + 1502, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: South Crystal", SC2HOTS_LOC_ID_OFFSET + 1503, LocationType.VANILLA), + LocationData("Phantoms of the Void", "Phantoms of the Void: Base Established", SC2HOTS_LOC_ID_OFFSET + 1504, LocationType.EXTRA), + LocationData("Phantoms of the Void", "Phantoms of the Void: Close Temple", SC2HOTS_LOC_ID_OFFSET + 1505, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Mid Temple", SC2HOTS_LOC_ID_OFFSET + 1506, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Southeast Temple", SC2HOTS_LOC_ID_OFFSET + 1507, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northeast Temple", SC2HOTS_LOC_ID_OFFSET + 1508, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northwest Temple", SC2HOTS_LOC_ID_OFFSET + 1509, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("With Friends Like These", "With Friends Like These: Victory", SC2HOTS_LOC_ID_OFFSET + 1600, LocationType.VICTORY), + LocationData("With Friends Like These", "With Friends Like These: Pirate Capital Ship", SC2HOTS_LOC_ID_OFFSET + 1601, LocationType.VANILLA), + LocationData("With Friends Like These", "With Friends Like These: First Mineral Patch", SC2HOTS_LOC_ID_OFFSET + 1602, LocationType.VANILLA), + LocationData("With Friends Like These", "With Friends Like These: Second Mineral Patch", SC2HOTS_LOC_ID_OFFSET + 1603, LocationType.VANILLA), + LocationData("With Friends Like These", "With Friends Like These: Third Mineral Patch", SC2HOTS_LOC_ID_OFFSET + 1604, LocationType.VANILLA), + LocationData("Conviction", "Conviction: Victory", SC2HOTS_LOC_ID_OFFSET + 1700, LocationType.VICTORY, + lambda state: logic.two_kerrigan_actives(state) and + (logic.basic_kerrigan(state) or logic.story_tech_granted) or kerriganless), + LocationData("Conviction", "Conviction: First Secret Documents", SC2HOTS_LOC_ID_OFFSET + 1701, LocationType.VANILLA, + lambda state: logic.two_kerrigan_actives(state) or kerriganless), + LocationData("Conviction", "Conviction: Second Secret Documents", SC2HOTS_LOC_ID_OFFSET + 1702, LocationType.VANILLA, + lambda state: logic.two_kerrigan_actives(state) and + (logic.basic_kerrigan(state) or logic.story_tech_granted) or kerriganless), + LocationData("Conviction", "Conviction: Power Coupling", SC2HOTS_LOC_ID_OFFSET + 1703, LocationType.EXTRA, + lambda state: logic.two_kerrigan_actives(state) or kerriganless), + LocationData("Conviction", "Conviction: Door Blasted", SC2HOTS_LOC_ID_OFFSET + 1704, LocationType.EXTRA, + lambda state: logic.two_kerrigan_actives(state) or kerriganless), + LocationData("Planetfall", "Planetfall: Victory", SC2HOTS_LOC_ID_OFFSET + 1800, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: East Gate", SC2HOTS_LOC_ID_OFFSET + 1801, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Northwest Gate", SC2HOTS_LOC_ID_OFFSET + 1802, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: North Gate", SC2HOTS_LOC_ID_OFFSET + 1803, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 1 Bile Launcher Deployed", SC2HOTS_LOC_ID_OFFSET + 1804, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 2 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1805, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 3 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1806, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 4 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1807, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 5 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1808, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Sons of Korhal", SC2HOTS_LOC_ID_OFFSET + 1809, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Night Wolves", SC2HOTS_LOC_ID_OFFSET + 1810, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: West Expansion", SC2HOTS_LOC_ID_OFFSET + 1811, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Mid Expansion", SC2HOTS_LOC_ID_OFFSET + 1812, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Victory", SC2HOTS_LOC_ID_OFFSET + 1900, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: First Power Link", SC2HOTS_LOC_ID_OFFSET + 1901, LocationType.VANILLA), + LocationData("Death From Above", "Death From Above: Second Power Link", SC2HOTS_LOC_ID_OFFSET + 1902, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Third Power Link", SC2HOTS_LOC_ID_OFFSET + 1903, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Expansion Command Center", SC2HOTS_LOC_ID_OFFSET + 1904, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Main Path Command Center", SC2HOTS_LOC_ID_OFFSET + 1905, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Reckoning", "The Reckoning: Victory", SC2HOTS_LOC_ID_OFFSET + 2000, LocationType.VICTORY, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: South Lane", SC2HOTS_LOC_ID_OFFSET + 2001, LocationType.VANILLA, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: North Lane", SC2HOTS_LOC_ID_OFFSET + 2002, LocationType.VANILLA, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: East Lane", SC2HOTS_LOC_ID_OFFSET + 2003, LocationType.VANILLA, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: Odin", SC2HOTS_LOC_ID_OFFSET + 2004, LocationType.EXTRA, + lambda state: logic.the_reckoning_requirement(state)), + + # LotV Prologue + LocationData("Dark Whispers", "Dark Whispers: Victory", SC2LOTV_LOC_ID_OFFSET + 100, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: First Prisoner Group", SC2LOTV_LOC_ID_OFFSET + 101, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: Second Prisoner Group", SC2LOTV_LOC_ID_OFFSET + 102, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: First Pylon", SC2LOTV_LOC_ID_OFFSET + 103, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: Second Pylon", SC2LOTV_LOC_ID_OFFSET + 104, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: Victory", SC2LOTV_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: South Rock Formation", SC2LOTV_LOC_ID_OFFSET + 201, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: West Rock Formation", SC2LOTV_LOC_ID_OFFSET + 202, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: East Rock Formation", SC2LOTV_LOC_ID_OFFSET + 203, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state) \ + and logic.protoss_can_attack_behind_chasm(state)), + LocationData("Evil Awoken", "Evil Awoken: Victory", SC2LOTV_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: adv_tactics or logic.protoss_stalker_upgrade(state)), + LocationData("Evil Awoken", "Evil Awoken: Temple Investigated", SC2LOTV_LOC_ID_OFFSET + 301, LocationType.EXTRA), + LocationData("Evil Awoken", "Evil Awoken: Void Catalyst", SC2LOTV_LOC_ID_OFFSET + 302, LocationType.EXTRA), + LocationData("Evil Awoken", "Evil Awoken: First Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 303, LocationType.VANILLA), + LocationData("Evil Awoken", "Evil Awoken: Second Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 304, LocationType.VANILLA), + LocationData("Evil Awoken", "Evil Awoken: Third Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 305, LocationType.VANILLA), + + + # LotV + LocationData("For Aiur!", "For Aiur!: Victory", SC2LOTV_LOC_ID_OFFSET + 400, LocationType.VICTORY), + LocationData("For Aiur!", "For Aiur!: Southwest Hive", SC2LOTV_LOC_ID_OFFSET + 401, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: Northwest Hive", SC2LOTV_LOC_ID_OFFSET + 402, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: Northeast Hive", SC2LOTV_LOC_ID_OFFSET + 403, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: East Hive", SC2LOTV_LOC_ID_OFFSET + 404, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: West Conduit", SC2LOTV_LOC_ID_OFFSET + 405, LocationType.EXTRA), + LocationData("For Aiur!", "For Aiur!: Middle Conduit", SC2LOTV_LOC_ID_OFFSET + 406, LocationType.EXTRA), + LocationData("For Aiur!", "For Aiur!: Northeast Conduit", SC2LOTV_LOC_ID_OFFSET + 407, LocationType.EXTRA), + LocationData("The Growing Shadow", "The Growing Shadow: Victory", SC2LOTV_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Growing Shadow", "The Growing Shadow: Close Pylon", SC2LOTV_LOC_ID_OFFSET + 501, LocationType.VANILLA), + LocationData("The Growing Shadow", "The Growing Shadow: East Pylon", SC2LOTV_LOC_ID_OFFSET + 502, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Growing Shadow", "The Growing Shadow: West Pylon", SC2LOTV_LOC_ID_OFFSET + 503, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Growing Shadow", "The Growing Shadow: Nexus", SC2LOTV_LOC_ID_OFFSET + 504, LocationType.EXTRA), + LocationData("The Growing Shadow", "The Growing Shadow: Templar Base", SC2LOTV_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: Victory", SC2LOTV_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: Close Warp Gate", SC2LOTV_LOC_ID_OFFSET + 601, LocationType.VANILLA), + LocationData("The Spear of Adun", "The Spear of Adun: West Warp Gate", SC2LOTV_LOC_ID_OFFSET + 602, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: North Warp Gate", SC2LOTV_LOC_ID_OFFSET + 603, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: North Power Cell", SC2LOTV_LOC_ID_OFFSET + 604, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: East Power Cell", SC2LOTV_LOC_ID_OFFSET + 605, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: South Power Cell", SC2LOTV_LOC_ID_OFFSET + 606, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: Southeast Power Cell", SC2LOTV_LOC_ID_OFFSET + 607, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Victory", SC2LOTV_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Mid EMP Scrambler", SC2LOTV_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Southeast EMP Scrambler", SC2LOTV_LOC_ID_OFFSET + 702, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: North EMP Scrambler", SC2LOTV_LOC_ID_OFFSET + 703, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Mid Stabilizer", SC2LOTV_LOC_ID_OFFSET + 704, LocationType.EXTRA), + LocationData("Sky Shield", "Sky Shield: Southwest Stabilizer", SC2LOTV_LOC_ID_OFFSET + 705, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Northwest Stabilizer", SC2LOTV_LOC_ID_OFFSET + 706, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Northeast Stabilizer", SC2LOTV_LOC_ID_OFFSET + 707, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Southeast Stabilizer", SC2LOTV_LOC_ID_OFFSET + 708, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: West Raynor Base", SC2LOTV_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: East Raynor Base", SC2LOTV_LOC_ID_OFFSET + 710, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Brothers in Arms", "Brothers in Arms: Victory", SC2LOTV_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.brothers_in_arms_requirement(state)), + LocationData("Brothers in Arms", "Brothers in Arms: Mid Science Facility", SC2LOTV_LOC_ID_OFFSET + 801, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) or logic.take_over_ai_allies), + LocationData("Brothers in Arms", "Brothers in Arms: North Science Facility", SC2LOTV_LOC_ID_OFFSET + 802, LocationType.VANILLA, + lambda state: logic.brothers_in_arms_requirement(state) + or logic.take_over_ai_allies + and logic.advanced_tactics + and ( + logic.terran_common_unit(state) + or logic.protoss_common_unit(state) + ) + ), + LocationData("Brothers in Arms", "Brothers in Arms: South Science Facility", SC2LOTV_LOC_ID_OFFSET + 803, LocationType.VANILLA, + lambda state: logic.brothers_in_arms_requirement(state)), + LocationData("Amon's Reach", "Amon's Reach: Victory", SC2LOTV_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: Close Solarite Reserve", SC2LOTV_LOC_ID_OFFSET + 901, LocationType.VANILLA), + LocationData("Amon's Reach", "Amon's Reach: North Solarite Reserve", SC2LOTV_LOC_ID_OFFSET + 902, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: East Solarite Reserve", SC2LOTV_LOC_ID_OFFSET + 903, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: West Launch Bay", SC2LOTV_LOC_ID_OFFSET + 904, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: South Launch Bay", SC2LOTV_LOC_ID_OFFSET + 905, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: Northwest Launch Bay", SC2LOTV_LOC_ID_OFFSET + 906, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: East Launch Bay", SC2LOTV_LOC_ID_OFFSET + 907, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Last Stand", "Last Stand: Victory", SC2LOTV_LOC_ID_OFFSET + 1000, LocationType.VICTORY, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: West Zenith Stone", SC2LOTV_LOC_ID_OFFSET + 1001, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: North Zenith Stone", SC2LOTV_LOC_ID_OFFSET + 1002, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: East Zenith Stone", SC2LOTV_LOC_ID_OFFSET + 1003, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: 1 Billion Zerg", SC2LOTV_LOC_ID_OFFSET + 1004, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: 1.5 Billion Zerg", SC2LOTV_LOC_ID_OFFSET + 1005, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state) and ( + state.has_all({ItemNames.KHAYDARIN_MONOLITH, ItemNames.PHOTON_CANNON, ItemNames.SHIELD_BATTERY}, player) + or state.has_any({ItemNames.SOA_SOLAR_LANCE, ItemNames.SOA_DEPLOY_FENIX}, player) + )), + LocationData("Forbidden Weapon", "Forbidden Weapon: Victory", SC2LOTV_LOC_ID_OFFSET + 1100, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Forbidden Weapon", "Forbidden Weapon: South Solarite", SC2LOTV_LOC_ID_OFFSET + 1101, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Forbidden Weapon", "Forbidden Weapon: North Solarite", SC2LOTV_LOC_ID_OFFSET + 1102, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Forbidden Weapon", "Forbidden Weapon: Northwest Solarite", SC2LOTV_LOC_ID_OFFSET + 1103, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: Victory", SC2LOTV_LOC_ID_OFFSET + 1200, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: Mid Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1201, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: West Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1202, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: South Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1203, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: East Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1204, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: North Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1205, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: Titanic Warp Prism", SC2LOTV_LOC_ID_OFFSET + 1206, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Victory", SC2LOTV_LOC_ID_OFFSET + 1300, LocationType.VICTORY, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: First Hall of Revelation", SC2LOTV_LOC_ID_OFFSET + 1301, LocationType.EXTRA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Second Hall of Revelation", SC2LOTV_LOC_ID_OFFSET + 1302, LocationType.EXTRA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: First Xel'Naga Device", SC2LOTV_LOC_ID_OFFSET + 1303, LocationType.VANILLA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Second Xel'Naga Device", SC2LOTV_LOC_ID_OFFSET + 1304, LocationType.VANILLA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Third Xel'Naga Device", SC2LOTV_LOC_ID_OFFSET + 1305, LocationType.VANILLA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Victory", SC2LOTV_LOC_ID_OFFSET + 1400, LocationType.VICTORY, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Artanis", SC2LOTV_LOC_ID_OFFSET + 1401, LocationType.EXTRA), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Northwest Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1402, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Northeast Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1403, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Southwest Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1404, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Southeast Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1405, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: South Xel'Naga Vessel", SC2LOTV_LOC_ID_OFFSET + 1406, LocationType.VANILLA), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Mid Xel'Naga Vessel", SC2LOTV_LOC_ID_OFFSET + 1407, LocationType.VANILLA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: North Xel'Naga Vessel", SC2LOTV_LOC_ID_OFFSET + 1408, LocationType.VANILLA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Victory", SC2LOTV_LOC_ID_OFFSET + 1500, LocationType.VICTORY, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Zerg Cleared", SC2LOTV_LOC_ID_OFFSET + 1501, LocationType.EXTRA), + LocationData("Unsealing the Past", "Unsealing the Past: First Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1502, LocationType.EXTRA, + lambda state: logic.advanced_tactics \ + or logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Second Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1503, LocationType.EXTRA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Third Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1504, LocationType.EXTRA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Fourth Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1505, LocationType.EXTRA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: South Power Core", SC2LOTV_LOC_ID_OFFSET + 1506, LocationType.VANILLA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: East Power Core", SC2LOTV_LOC_ID_OFFSET + 1507, LocationType.VANILLA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Purification", "Purification: Victory", SC2LOTV_LOC_ID_OFFSET + 1600, LocationType.VICTORY, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: North Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1601, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: North Sector: Northeast Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1602, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: North Sector: Southeast Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1603, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: South Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1604, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: South Sector: North Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1605, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: South Sector: East Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1606, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: West Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1607, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: West Sector: Mid Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1608, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: West Sector: East Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1609, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: East Sector: North Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1610, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: East Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1611, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: East Sector: South Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1612, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: Purifier Warden", SC2LOTV_LOC_ID_OFFSET + 1613, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Victory", SC2LOTV_LOC_ID_OFFSET + 1700, LocationType.VICTORY, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: First Terrazine Fog", SC2LOTV_LOC_ID_OFFSET + 1701, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Southwest Guardian", SC2LOTV_LOC_ID_OFFSET + 1702, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: West Guardian", SC2LOTV_LOC_ID_OFFSET + 1703, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Northwest Guardian", SC2LOTV_LOC_ID_OFFSET + 1704, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Northeast Guardian", SC2LOTV_LOC_ID_OFFSET + 1705, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: North Mothership", SC2LOTV_LOC_ID_OFFSET + 1706, LocationType.VANILLA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: South Mothership", SC2LOTV_LOC_ID_OFFSET + 1707, LocationType.VANILLA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Rak'Shir", "Rak'Shir: Victory", SC2LOTV_LOC_ID_OFFSET + 1800, LocationType.VICTORY, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Rak'Shir", "Rak'Shir: North Slayn Elemental", SC2LOTV_LOC_ID_OFFSET + 1801, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Rak'Shir", "Rak'Shir: Southwest Slayn Elemental", SC2LOTV_LOC_ID_OFFSET + 1802, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Rak'Shir", "Rak'Shir: East Slayn Elemental", SC2LOTV_LOC_ID_OFFSET + 1803, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Templar's Charge", "Templar's Charge: Victory", SC2LOTV_LOC_ID_OFFSET + 1900, LocationType.VICTORY, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Northwest Power Core", SC2LOTV_LOC_ID_OFFSET + 1901, LocationType.EXTRA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Northeast Power Core", SC2LOTV_LOC_ID_OFFSET + 1902, LocationType.EXTRA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Southeast Power Core", SC2LOTV_LOC_ID_OFFSET + 1903, LocationType.EXTRA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: West Hybrid Stasis Chamber", SC2LOTV_LOC_ID_OFFSET + 1904, LocationType.VANILLA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Southeast Hybrid Stasis Chamber", SC2LOTV_LOC_ID_OFFSET + 1905, LocationType.VANILLA, + lambda state: logic.protoss_fleet(state)), + LocationData("Templar's Return", "Templar's Return: Victory", SC2LOTV_LOC_ID_OFFSET + 2000, LocationType.VICTORY, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Citadel: First Gate", SC2LOTV_LOC_ID_OFFSET + 2001, LocationType.EXTRA), + LocationData("Templar's Return", "Templar's Return: Citadel: Second Gate", SC2LOTV_LOC_ID_OFFSET + 2002, LocationType.EXTRA), + LocationData("Templar's Return", "Templar's Return: Citadel: Power Structure", SC2LOTV_LOC_ID_OFFSET + 2003, LocationType.VANILLA), + LocationData("Templar's Return", "Templar's Return: Temple Grounds: Gather Army", SC2LOTV_LOC_ID_OFFSET + 2004, LocationType.VANILLA, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Temple Grounds: Power Structure", SC2LOTV_LOC_ID_OFFSET + 2005, LocationType.VANILLA, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Caverns: Purifier", SC2LOTV_LOC_ID_OFFSET + 2006, LocationType.EXTRA, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Caverns: Dark Templar", SC2LOTV_LOC_ID_OFFSET + 2007, LocationType.EXTRA, + lambda state: logic.templars_return_requirement(state)), + LocationData("The Host", "The Host: Victory", SC2LOTV_LOC_ID_OFFSET + 2100, LocationType.VICTORY, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Southeast Void Shard", SC2LOTV_LOC_ID_OFFSET + 2101, LocationType.VICTORY, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: South Void Shard", SC2LOTV_LOC_ID_OFFSET + 2102, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Southwest Void Shard", SC2LOTV_LOC_ID_OFFSET + 2103, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: North Void Shard", SC2LOTV_LOC_ID_OFFSET + 2104, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Northwest Void Shard", SC2LOTV_LOC_ID_OFFSET + 2105, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Nerazim Warp in Zone", SC2LOTV_LOC_ID_OFFSET + 2106, LocationType.VANILLA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Tal'darim Warp in Zone", SC2LOTV_LOC_ID_OFFSET + 2107, LocationType.VANILLA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Purifier Warp in Zone", SC2LOTV_LOC_ID_OFFSET + 2108, LocationType.VANILLA, + lambda state: logic.the_host_requirement(state)), + LocationData("Salvation", "Salvation: Victory", SC2LOTV_LOC_ID_OFFSET + 2200, LocationType.VICTORY, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Fabrication Matrix", SC2LOTV_LOC_ID_OFFSET + 2201, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Assault Cluster", SC2LOTV_LOC_ID_OFFSET + 2202, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Hull Breach", SC2LOTV_LOC_ID_OFFSET + 2203, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Core Critical", SC2LOTV_LOC_ID_OFFSET + 2204, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + + # Epilogue + LocationData("Into the Void", "Into the Void: Victory", SC2LOTV_LOC_ID_OFFSET + 2300, LocationType.VICTORY, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Corruption Source", SC2LOTV_LOC_ID_OFFSET + 2301, LocationType.EXTRA), + LocationData("Into the Void", "Into the Void: Southwest Forward Position", SC2LOTV_LOC_ID_OFFSET + 2302, LocationType.VANILLA, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Northwest Forward Position", SC2LOTV_LOC_ID_OFFSET + 2303, LocationType.VANILLA, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Southeast Forward Position", SC2LOTV_LOC_ID_OFFSET + 2304, LocationType.VANILLA, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Northeast Forward Position", SC2LOTV_LOC_ID_OFFSET + 2305, LocationType.VANILLA), + LocationData("The Essence of Eternity", "The Essence of Eternity: Victory", SC2LOTV_LOC_ID_OFFSET + 2400, LocationType.VICTORY, + lambda state: logic.essence_of_eternity_requirement(state)), + LocationData("The Essence of Eternity", "The Essence of Eternity: Void Trashers", SC2LOTV_LOC_ID_OFFSET + 2401, LocationType.EXTRA), + LocationData("Amon's Fall", "Amon's Fall: Victory", SC2LOTV_LOC_ID_OFFSET + 2500, LocationType.VICTORY, + lambda state: logic.amons_fall_requirement(state)), + + # Nova Covert Ops + LocationData("The Escape", "The Escape: Victory", SC2NCO_LOC_ID_OFFSET + 100, LocationType.VICTORY, + lambda state: logic.the_escape_requirement(state)), + LocationData("The Escape", "The Escape: Rifle", SC2NCO_LOC_ID_OFFSET + 101, LocationType.VANILLA, + lambda state: logic.the_escape_first_stage_requirement(state)), + LocationData("The Escape", "The Escape: Grenades", SC2NCO_LOC_ID_OFFSET + 102, LocationType.VANILLA, + lambda state: logic.the_escape_first_stage_requirement(state)), + LocationData("The Escape", "The Escape: Agent Delta", SC2NCO_LOC_ID_OFFSET + 103, LocationType.VANILLA, + lambda state: logic.the_escape_requirement(state)), + LocationData("The Escape", "The Escape: Agent Pierce", SC2NCO_LOC_ID_OFFSET + 104, LocationType.VANILLA, + lambda state: logic.the_escape_requirement(state)), + LocationData("The Escape", "The Escape: Agent Stone", SC2NCO_LOC_ID_OFFSET + 105, LocationType.VANILLA, + lambda state: logic.the_escape_requirement(state)), + LocationData("Sudden Strike", "Sudden Strike: Victory", SC2NCO_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.sudden_strike_can_reach_objectives(state)), + LocationData("Sudden Strike", "Sudden Strike: Research Center", SC2NCO_LOC_ID_OFFSET + 201, LocationType.VANILLA, + lambda state: logic.sudden_strike_can_reach_objectives(state)), + LocationData("Sudden Strike", "Sudden Strike: Weaponry Labs", SC2NCO_LOC_ID_OFFSET + 202, LocationType.VANILLA, + lambda state: logic.sudden_strike_requirement(state)), + LocationData("Sudden Strike", "Sudden Strike: Brutalisk", SC2NCO_LOC_ID_OFFSET + 203, LocationType.EXTRA, + lambda state: logic.sudden_strike_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Victory", SC2NCO_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: logic.enemy_intelligence_third_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: West Garrison", SC2NCO_LOC_ID_OFFSET + 301, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Close Garrison", SC2NCO_LOC_ID_OFFSET + 302, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Northeast Garrison", SC2NCO_LOC_ID_OFFSET + 303, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Southeast Garrison", SC2NCO_LOC_ID_OFFSET + 304, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state) + and logic.enemy_intelligence_cliff_garrison(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: South Garrison", SC2NCO_LOC_ID_OFFSET + 305, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: All Garrisons", SC2NCO_LOC_ID_OFFSET + 306, LocationType.VANILLA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state) + and logic.enemy_intelligence_cliff_garrison(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Forces Rescued", SC2NCO_LOC_ID_OFFSET + 307, LocationType.VANILLA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Communications Hub", SC2NCO_LOC_ID_OFFSET + 308, LocationType.VANILLA, + lambda state: logic.enemy_intelligence_second_stage_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: Victory", SC2NCO_LOC_ID_OFFSET + 400, LocationType.VICTORY, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Base: West Hatchery", SC2NCO_LOC_ID_OFFSET + 401, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Base: North Hatchery", SC2NCO_LOC_ID_OFFSET + 402, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Base: East Hatchery", SC2NCO_LOC_ID_OFFSET + 403, LocationType.VANILLA), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Base: Northwest Hatchery", SC2NCO_LOC_ID_OFFSET + 404, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Base: Southwest Hatchery", SC2NCO_LOC_ID_OFFSET + 405, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Base: East Hatchery", SC2NCO_LOC_ID_OFFSET + 406, LocationType.VANILLA), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Shield Projector", SC2NCO_LOC_ID_OFFSET + 407, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: East Shield Projector", SC2NCO_LOC_ID_OFFSET + 408, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Shield Projector", SC2NCO_LOC_ID_OFFSET + 409, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: West Shield Projector", SC2NCO_LOC_ID_OFFSET + 410, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: Fleet Beacon", SC2NCO_LOC_ID_OFFSET + 411, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Victory", SC2NCO_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 1 Terrazine Node Collected", SC2NCO_LOC_ID_OFFSET + 501, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 2 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 502, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 3 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 503, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 4 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 504, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 5 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: HERC Outpost", SC2NCO_LOC_ID_OFFSET + 506, LocationType.VANILLA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Umojan Mine", SC2NCO_LOC_ID_OFFSET + 507, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Blightbringer", SC2NCO_LOC_ID_OFFSET + 508, LocationType.VANILLA, + lambda state: logic.night_terrors_requirement(state) + and logic.nova_ranged_weapon(state) + and state.has_any( + {ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_PULSE_GRENADES, ItemNames.NOVA_STIM_INFUSION, + ItemNames.NOVA_HOLO_DECOY}, player)), + LocationData("Night Terrors", "Night Terrors: Science Facility", SC2NCO_LOC_ID_OFFSET + 509, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Eradicators", SC2NCO_LOC_ID_OFFSET + 510, LocationType.VANILLA, + lambda state: logic.night_terrors_requirement(state) + and logic.nova_any_weapon(state)), + LocationData("Flashpoint", "Flashpoint: Victory", SC2NCO_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Close North Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 601, LocationType.EXTRA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + or logic.terran_common_unit(state)), + LocationData("Flashpoint", "Flashpoint: Close East Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 602, LocationType.EXTRA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + or logic.terran_common_unit(state)), + LocationData("Flashpoint", "Flashpoint: Far North Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 603, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Far East Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 604, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Experimental Weapon", SC2NCO_LOC_ID_OFFSET + 605, LocationType.VANILLA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Northwest Subway Entrance", SC2NCO_LOC_ID_OFFSET + 606, LocationType.VANILLA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + and logic.terran_common_unit(state) + or logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Southeast Subway Entrance", SC2NCO_LOC_ID_OFFSET + 607, LocationType.VANILLA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + and logic.terran_common_unit(state) + or logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Northeast Subway Entrance", SC2NCO_LOC_ID_OFFSET + 608, LocationType.VANILLA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Expansion Hatchery", SC2NCO_LOC_ID_OFFSET + 609, LocationType.EXTRA, + lambda state: state.has(ItemNames.LIBERATOR_RAID_ARTILLERY, player) and logic.terran_common_unit(state) + or logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Baneling Spawns", SC2NCO_LOC_ID_OFFSET + 610, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Mutalisk Spawns", SC2NCO_LOC_ID_OFFSET + 611, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Nydus Worm Spawns", SC2NCO_LOC_ID_OFFSET + 612, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Lurker Spawns", SC2NCO_LOC_ID_OFFSET + 613, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Brood Lord Spawns", SC2NCO_LOC_ID_OFFSET + 614, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Ultralisk Spawns", SC2NCO_LOC_ID_OFFSET + 615, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Victory", SC2NCO_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.enemy_shadow_victory(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Sewers: Domination Visor", SC2NCO_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.enemy_shadow_domination(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Sewers: Resupply Crate", SC2NCO_LOC_ID_OFFSET + 702, LocationType.EXTRA, + lambda state: logic.enemy_shadow_first_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Sewers: Facility Access", SC2NCO_LOC_ID_OFFSET + 703, LocationType.VANILLA, + lambda state: logic.enemy_shadow_first_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Northwest Door Lock", SC2NCO_LOC_ID_OFFSET + 704, LocationType.VANILLA, + lambda state: logic.enemy_shadow_door_controls(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Southeast Door Lock", SC2NCO_LOC_ID_OFFSET + 705, LocationType.VANILLA, + lambda state: logic.enemy_shadow_door_controls(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Blazefire Gunblade", SC2NCO_LOC_ID_OFFSET + 706, LocationType.VANILLA, + lambda state: logic.enemy_shadow_second_stage(state) + and (story_tech_granted + or state.has(ItemNames.NOVA_BLINK, player) + or (adv_tactics and state.has_all({ItemNames.NOVA_DOMINATION, ItemNames.NOVA_HOLO_DECOY, ItemNames.NOVA_JUMP_SUIT_MODULE}, player)) + ) + ), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Blink Suit", SC2NCO_LOC_ID_OFFSET + 707, LocationType.VANILLA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Advanced Weaponry", SC2NCO_LOC_ID_OFFSET + 708, LocationType.VANILLA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Entrance Resupply Crate", SC2NCO_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.enemy_shadow_first_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: West Resupply Crate", SC2NCO_LOC_ID_OFFSET + 710, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: North Resupply Crate", SC2NCO_LOC_ID_OFFSET + 711, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: East Resupply Crate", SC2NCO_LOC_ID_OFFSET + 712, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: South Resupply Crate", SC2NCO_LOC_ID_OFFSET + 713, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("Dark Skies", "Dark Skies: Victory", SC2NCO_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: First Squadron of Dominion Fleet", SC2NCO_LOC_ID_OFFSET + 801, LocationType.EXTRA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: Remainder of Dominion Fleet", SC2NCO_LOC_ID_OFFSET + 802, LocationType.EXTRA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: Ji'nara", SC2NCO_LOC_ID_OFFSET + 803, LocationType.EXTRA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: Science Facility", SC2NCO_LOC_ID_OFFSET + 804, LocationType.VANILLA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("End Game", "End Game: Victory", SC2NCO_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.end_game_requirement(state) and logic.nova_any_weapon(state)), + LocationData("End Game", "End Game: Xanthos", SC2NCO_LOC_ID_OFFSET + 901, LocationType.VANILLA, + lambda state: logic.end_game_requirement(state)), + ] + + beat_events = [] + # Filtering out excluded locations + if world is not None: + excluded_location_types = get_location_types(world, LocationInclusion.option_disabled) + plando_locations = get_plando_locations(world) + exclude_locations = get_option_value(world, "exclude_locations") + location_table = [location for location in location_table + if (location.type is LocationType.VICTORY or location.name not in exclude_locations) + and location.type not in excluded_location_types + or location.name in plando_locations] + for i, location_data in enumerate(location_table): + # Removing all item-based logic on No Logic + if logic_level == RequiredTactics.option_no_logic: + location_data = location_data._replace(rule=Location.access_rule) + location_table[i] = location_data + # Generating Beat event locations + if location_data.name.endswith((": Victory", ": Defeat")): + beat_events.append( + location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None) + ) + return tuple(location_table + beat_events) + +lookup_location_id_to_type = {loc.code: loc.type for loc in get_locations(None) if loc.code is not None} \ No newline at end of file diff --git a/worlds/sc2/MissionTables.py b/worlds/sc2/MissionTables.py new file mode 100644 index 000000000000..4dece46411bf --- /dev/null +++ b/worlds/sc2/MissionTables.py @@ -0,0 +1,736 @@ +from typing import NamedTuple, Dict, List, Set, Union, Literal, Iterable, Callable +from enum import IntEnum, Enum + + +class SC2Race(IntEnum): + ANY = 0 + TERRAN = 1 + ZERG = 2 + PROTOSS = 3 + + +class MissionPools(IntEnum): + STARTER = 0 + EASY = 1 + MEDIUM = 2 + HARD = 3 + VERY_HARD = 4 + FINAL = 5 + + +class SC2CampaignGoalPriority(IntEnum): + """ + Campaign's priority to goal election + """ + NONE = 0 + MINI_CAMPAIGN = 1 # A goal shouldn't be in a mini-campaign if there's at least one 'big' campaign + HARD = 2 # A campaign ending with a hard mission + VERY_HARD = 3 # A campaign ending with a very hard mission + EPILOGUE = 4 # Epilogue shall be always preferred as the goal if present + + +class SC2Campaign(Enum): + + def __new__(cls, *args, **kwargs): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + + def __init__(self, campaign_id: int, name: str, goal_priority: SC2CampaignGoalPriority, race: SC2Race): + self.id = campaign_id + self.campaign_name = name + self.goal_priority = goal_priority + self.race = race + + GLOBAL = 0, "Global", SC2CampaignGoalPriority.NONE, SC2Race.ANY + WOL = 1, "Wings of Liberty", SC2CampaignGoalPriority.VERY_HARD, SC2Race.TERRAN + PROPHECY = 2, "Prophecy", SC2CampaignGoalPriority.MINI_CAMPAIGN, SC2Race.PROTOSS + HOTS = 3, "Heart of the Swarm", SC2CampaignGoalPriority.HARD, SC2Race.ZERG + PROLOGUE = 4, "Whispers of Oblivion (Legacy of the Void: Prologue)", SC2CampaignGoalPriority.MINI_CAMPAIGN, SC2Race.PROTOSS + LOTV = 5, "Legacy of the Void", SC2CampaignGoalPriority.VERY_HARD, SC2Race.PROTOSS + EPILOGUE = 6, "Into the Void (Legacy of the Void: Epilogue)", SC2CampaignGoalPriority.EPILOGUE, SC2Race.ANY + NCO = 7, "Nova Covert Ops", SC2CampaignGoalPriority.HARD, SC2Race.TERRAN + + +class SC2Mission(Enum): + + def __new__(cls, *args, **kwargs): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + + def __init__(self, mission_id: int, name: str, campaign: SC2Campaign, area: str, race: SC2Race, pool: MissionPools, map_file: str, build: bool = True): + self.id = mission_id + self.mission_name = name + self.campaign = campaign + self.area = area + self.race = race + self.pool = pool + self.map_file = map_file + self.build = build + + # Wings of Liberty + LIBERATION_DAY = 1, "Liberation Day", SC2Campaign.WOL, "Mar Sara", SC2Race.ANY, MissionPools.STARTER, "ap_liberation_day", False + THE_OUTLAWS = 2, "The Outlaws", SC2Campaign.WOL, "Mar Sara", SC2Race.TERRAN, MissionPools.EASY, "ap_the_outlaws" + ZERO_HOUR = 3, "Zero Hour", SC2Campaign.WOL, "Mar Sara", SC2Race.TERRAN, MissionPools.EASY, "ap_zero_hour" + EVACUATION = 4, "Evacuation", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.EASY, "ap_evacuation" + OUTBREAK = 5, "Outbreak", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.EASY, "ap_outbreak" + SAFE_HAVEN = 6, "Safe Haven", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_safe_haven" + HAVENS_FALL = 7, "Haven's Fall", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_havens_fall" + SMASH_AND_GRAB = 8, "Smash and Grab", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.EASY, "ap_smash_and_grab" + THE_DIG = 9, "The Dig", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_the_dig" + THE_MOEBIUS_FACTOR = 10, "The Moebius Factor", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_the_moebius_factor" + SUPERNOVA = 11, "Supernova", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.HARD, "ap_supernova" + MAW_OF_THE_VOID = 12, "Maw of the Void", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.HARD, "ap_maw_of_the_void" + DEVILS_PLAYGROUND = 13, "Devil's Playground", SC2Campaign.WOL, "Covert", SC2Race.TERRAN, MissionPools.EASY, "ap_devils_playground" + WELCOME_TO_THE_JUNGLE = 14, "Welcome to the Jungle", SC2Campaign.WOL, "Covert", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_welcome_to_the_jungle" + BREAKOUT = 15, "Breakout", SC2Campaign.WOL, "Covert", SC2Race.ANY, MissionPools.STARTER, "ap_breakout", False + GHOST_OF_A_CHANCE = 16, "Ghost of a Chance", SC2Campaign.WOL, "Covert", SC2Race.ANY, MissionPools.STARTER, "ap_ghost_of_a_chance", False + THE_GREAT_TRAIN_ROBBERY = 17, "The Great Train Robbery", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_the_great_train_robbery" + CUTTHROAT = 18, "Cutthroat", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_cutthroat" + ENGINE_OF_DESTRUCTION = 19, "Engine of Destruction", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.HARD, "ap_engine_of_destruction" + MEDIA_BLITZ = 20, "Media Blitz", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_media_blitz" + PIERCING_OF_THE_SHROUD = 21, "Piercing the Shroud", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.STARTER, "ap_piercing_the_shroud", False + GATES_OF_HELL = 26, "Gates of Hell", SC2Campaign.WOL, "Char", SC2Race.TERRAN, MissionPools.HARD, "ap_gates_of_hell" + BELLY_OF_THE_BEAST = 27, "Belly of the Beast", SC2Campaign.WOL, "Char", SC2Race.ANY, MissionPools.STARTER, "ap_belly_of_the_beast", False + SHATTER_THE_SKY = 28, "Shatter the Sky", SC2Campaign.WOL, "Char", SC2Race.TERRAN, MissionPools.HARD, "ap_shatter_the_sky" + ALL_IN = 29, "All-In", SC2Campaign.WOL, "Char", SC2Race.TERRAN, MissionPools.VERY_HARD, "ap_all_in" + + # Prophecy + WHISPERS_OF_DOOM = 22, "Whispers of Doom", SC2Campaign.PROPHECY, "_1", SC2Race.ANY, MissionPools.STARTER, "ap_whispers_of_doom", False + A_SINISTER_TURN = 23, "A Sinister Turn", SC2Campaign.PROPHECY, "_2", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_a_sinister_turn" + ECHOES_OF_THE_FUTURE = 24, "Echoes of the Future", SC2Campaign.PROPHECY, "_3", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_echoes_of_the_future" + IN_UTTER_DARKNESS = 25, "In Utter Darkness", SC2Campaign.PROPHECY, "_4", SC2Race.PROTOSS, MissionPools.HARD, "ap_in_utter_darkness" + + # Heart of the Swarm + LAB_RAT = 30, "Lab Rat", SC2Campaign.HOTS, "Umoja", SC2Race.ZERG, MissionPools.STARTER, "ap_lab_rat" + BACK_IN_THE_SADDLE = 31, "Back in the Saddle", SC2Campaign.HOTS, "Umoja", SC2Race.ANY, MissionPools.STARTER, "ap_back_in_the_saddle", False + RENDEZVOUS = 32, "Rendezvous", SC2Campaign.HOTS, "Umoja", SC2Race.ZERG, MissionPools.EASY, "ap_rendezvous" + HARVEST_OF_SCREAMS = 33, "Harvest of Screams", SC2Campaign.HOTS, "Kaldir", SC2Race.ZERG, MissionPools.EASY, "ap_harvest_of_screams" + SHOOT_THE_MESSENGER = 34, "Shoot the Messenger", SC2Campaign.HOTS, "Kaldir", SC2Race.ZERG, MissionPools.EASY, "ap_shoot_the_messenger" + ENEMY_WITHIN = 35, "Enemy Within", SC2Campaign.HOTS, "Kaldir", SC2Race.ANY, MissionPools.EASY, "ap_enemy_within", False + DOMINATION = 36, "Domination", SC2Campaign.HOTS, "Char", SC2Race.ZERG, MissionPools.EASY, "ap_domination" + FIRE_IN_THE_SKY = 37, "Fire in the Sky", SC2Campaign.HOTS, "Char", SC2Race.ZERG, MissionPools.MEDIUM, "ap_fire_in_the_sky" + OLD_SOLDIERS = 38, "Old Soldiers", SC2Campaign.HOTS, "Char", SC2Race.ZERG, MissionPools.MEDIUM, "ap_old_soldiers" + WAKING_THE_ANCIENT = 39, "Waking the Ancient", SC2Campaign.HOTS, "Zerus", SC2Race.ZERG, MissionPools.MEDIUM, "ap_waking_the_ancient" + THE_CRUCIBLE = 40, "The Crucible", SC2Campaign.HOTS, "Zerus", SC2Race.ZERG, MissionPools.MEDIUM, "ap_the_crucible" + SUPREME = 41, "Supreme", SC2Campaign.HOTS, "Zerus", SC2Race.ANY, MissionPools.MEDIUM, "ap_supreme", False + INFESTED = 42, "Infested", SC2Campaign.HOTS, "Skygeirr Station", SC2Race.ZERG, MissionPools.MEDIUM, "ap_infested" + HAND_OF_DARKNESS = 43, "Hand of Darkness", SC2Campaign.HOTS, "Skygeirr Station", SC2Race.ZERG, MissionPools.HARD, "ap_hand_of_darkness" + PHANTOMS_OF_THE_VOID = 44, "Phantoms of the Void", SC2Campaign.HOTS, "Skygeirr Station", SC2Race.ZERG, MissionPools.HARD, "ap_phantoms_of_the_void" + WITH_FRIENDS_LIKE_THESE = 45, "With Friends Like These", SC2Campaign.HOTS, "Dominion Space", SC2Race.ANY, MissionPools.STARTER, "ap_with_friends_like_these", False + CONVICTION = 46, "Conviction", SC2Campaign.HOTS, "Dominion Space", SC2Race.ANY, MissionPools.MEDIUM, "ap_conviction", False + PLANETFALL = 47, "Planetfall", SC2Campaign.HOTS, "Korhal", SC2Race.ZERG, MissionPools.HARD, "ap_planetfall" + DEATH_FROM_ABOVE = 48, "Death From Above", SC2Campaign.HOTS, "Korhal", SC2Race.ZERG, MissionPools.HARD, "ap_death_from_above" + THE_RECKONING = 49, "The Reckoning", SC2Campaign.HOTS, "Korhal", SC2Race.ZERG, MissionPools.HARD, "ap_the_reckoning" + + # Prologue + DARK_WHISPERS = 50, "Dark Whispers", SC2Campaign.PROLOGUE, "_1", SC2Race.PROTOSS, MissionPools.EASY, "ap_dark_whispers" + GHOSTS_IN_THE_FOG = 51, "Ghosts in the Fog", SC2Campaign.PROLOGUE, "_2", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_ghosts_in_the_fog" + EVIL_AWOKEN = 52, "Evil Awoken", SC2Campaign.PROLOGUE, "_3", SC2Race.PROTOSS, MissionPools.STARTER, "ap_evil_awoken", False + + # LotV + FOR_AIUR = 53, "For Aiur!", SC2Campaign.LOTV, "Aiur", SC2Race.ANY, MissionPools.STARTER, "ap_for_aiur", False + THE_GROWING_SHADOW = 54, "The Growing Shadow", SC2Campaign.LOTV, "Aiur", SC2Race.PROTOSS, MissionPools.EASY, "ap_the_growing_shadow" + THE_SPEAR_OF_ADUN = 55, "The Spear of Adun", SC2Campaign.LOTV, "Aiur", SC2Race.PROTOSS, MissionPools.EASY, "ap_the_spear_of_adun" + SKY_SHIELD = 56, "Sky Shield", SC2Campaign.LOTV, "Korhal", SC2Race.PROTOSS, MissionPools.EASY, "ap_sky_shield" + BROTHERS_IN_ARMS = 57, "Brothers in Arms", SC2Campaign.LOTV, "Korhal", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_brothers_in_arms" + AMON_S_REACH = 58, "Amon's Reach", SC2Campaign.LOTV, "Shakuras", SC2Race.PROTOSS, MissionPools.EASY, "ap_amon_s_reach" + LAST_STAND = 59, "Last Stand", SC2Campaign.LOTV, "Shakuras", SC2Race.PROTOSS, MissionPools.HARD, "ap_last_stand" + FORBIDDEN_WEAPON = 60, "Forbidden Weapon", SC2Campaign.LOTV, "Purifier", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_forbidden_weapon" + TEMPLE_OF_UNIFICATION = 61, "Temple of Unification", SC2Campaign.LOTV, "Ulnar", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_temple_of_unification" + THE_INFINITE_CYCLE = 62, "The Infinite Cycle", SC2Campaign.LOTV, "Ulnar", SC2Race.ANY, MissionPools.HARD, "ap_the_infinite_cycle", False + HARBINGER_OF_OBLIVION = 63, "Harbinger of Oblivion", SC2Campaign.LOTV, "Ulnar", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_harbinger_of_oblivion" + UNSEALING_THE_PAST = 64, "Unsealing the Past", SC2Campaign.LOTV, "Purifier", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_unsealing_the_past" + PURIFICATION = 65, "Purification", SC2Campaign.LOTV, "Purifier", SC2Race.PROTOSS, MissionPools.HARD, "ap_purification" + STEPS_OF_THE_RITE = 66, "Steps of the Rite", SC2Campaign.LOTV, "Tal'darim", SC2Race.PROTOSS, MissionPools.HARD, "ap_steps_of_the_rite" + RAK_SHIR = 67, "Rak'Shir", SC2Campaign.LOTV, "Tal'darim", SC2Race.PROTOSS, MissionPools.HARD, "ap_rak_shir" + TEMPLAR_S_CHARGE = 68, "Templar's Charge", SC2Campaign.LOTV, "Moebius", SC2Race.PROTOSS, MissionPools.HARD, "ap_templar_s_charge" + TEMPLAR_S_RETURN = 69, "Templar's Return", SC2Campaign.LOTV, "Return to Aiur", SC2Race.PROTOSS, MissionPools.EASY, "ap_templar_s_return", False + THE_HOST = 70, "The Host", SC2Campaign.LOTV, "Return to Aiur", SC2Race.PROTOSS, MissionPools.HARD, "ap_the_host", + SALVATION = 71, "Salvation", SC2Campaign.LOTV, "Return to Aiur", SC2Race.PROTOSS, MissionPools.VERY_HARD, "ap_salvation" + + # Epilogue + INTO_THE_VOID = 72, "Into the Void", SC2Campaign.EPILOGUE, "_1", SC2Race.PROTOSS, MissionPools.VERY_HARD, "ap_into_the_void" + THE_ESSENCE_OF_ETERNITY = 73, "The Essence of Eternity", SC2Campaign.EPILOGUE, "_2", SC2Race.TERRAN, MissionPools.VERY_HARD, "ap_the_essence_of_eternity" + AMON_S_FALL = 74, "Amon's Fall", SC2Campaign.EPILOGUE, "_3", SC2Race.ZERG, MissionPools.VERY_HARD, "ap_amon_s_fall" + + # Nova Covert Ops + THE_ESCAPE = 75, "The Escape", SC2Campaign.NCO, "_1", SC2Race.ANY, MissionPools.MEDIUM, "ap_the_escape", False + SUDDEN_STRIKE = 76, "Sudden Strike", SC2Campaign.NCO, "_1", SC2Race.TERRAN, MissionPools.EASY, "ap_sudden_strike" + ENEMY_INTELLIGENCE = 77, "Enemy Intelligence", SC2Campaign.NCO, "_1", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_enemy_intelligence" + TROUBLE_IN_PARADISE = 78, "Trouble In Paradise", SC2Campaign.NCO, "_2", SC2Race.TERRAN, MissionPools.HARD, "ap_trouble_in_paradise" + NIGHT_TERRORS = 79, "Night Terrors", SC2Campaign.NCO, "_2", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_night_terrors" + FLASHPOINT = 80, "Flashpoint", SC2Campaign.NCO, "_2", SC2Race.TERRAN, MissionPools.HARD, "ap_flashpoint" + IN_THE_ENEMY_S_SHADOW = 81, "In the Enemy's Shadow", SC2Campaign.NCO, "_3", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_in_the_enemy_s_shadow", False + DARK_SKIES = 82, "Dark Skies", SC2Campaign.NCO, "_3", SC2Race.TERRAN, MissionPools.HARD, "ap_dark_skies" + END_GAME = 83, "End Game", SC2Campaign.NCO, "_3", SC2Race.TERRAN, MissionPools.VERY_HARD, "ap_end_game" + + +class MissionConnection: + campaign: SC2Campaign + connect_to: int # -1 connects to Menu + + def __init__(self, connect_to, campaign = SC2Campaign.GLOBAL): + self.campaign = campaign + self.connect_to = connect_to + + def _asdict(self): + return { + "campaign": self.campaign.id, + "connect_to": self.connect_to + } + + +class MissionInfo(NamedTuple): + mission: SC2Mission + required_world: List[Union[MissionConnection, Dict[Literal["campaign", "connect_to"], int]]] + category: str + number: int = 0 # number of worlds need beaten + completion_critical: bool = False # missions needed to beat game + or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed + ui_vertical_padding: int = 0 + + +class FillMission(NamedTuple): + type: MissionPools + connect_to: List[MissionConnection] + category: str + number: int = 0 # number of worlds need beaten + completion_critical: bool = False # missions needed to beat game + or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed + removal_priority: int = 0 # how many missions missing from the pool required to remove this mission + + + +def vanilla_shuffle_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.WOL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7, removal_priority=1), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Artifact", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Artifact", number=11, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.WOL)], "Artifact", number=14, completion_critical=True, removal_priority=7), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.WOL)], "Artifact", completion_critical=True, removal_priority=6), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Covert", number=4), + FillMission(MissionPools.MEDIUM, [MissionConnection(12, SC2Campaign.WOL)], "Covert"), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=3), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=2), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Rebellion", number=6), + FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(18, SC2Campaign.WOL)], "Rebellion", removal_priority=8), + FillMission(MissionPools.HARD, [MissionConnection(19, SC2Campaign.WOL)], "Rebellion", removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(22, SC2Campaign.WOL), MissionConnection(23, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) + ], + SC2Campaign.PROPHECY: [ + FillMission(MissionPools.MEDIUM, [MissionConnection(8, SC2Campaign.WOL)], "_1"), + FillMission(MissionPools.HARD, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2", removal_priority=2), + FillMission(MissionPools.HARD, [MissionConnection(1, SC2Campaign.PROPHECY)], "_3", removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(2, SC2Campaign.PROPHECY)], "_4"), + ], + SC2Campaign.HOTS: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.HOTS)], "Umoja", completion_critical=True, removal_priority=1), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Kaldir", completion_critical=True, removal_priority=2), + FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Char", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(6, SC2Campaign.HOTS)], "Char", completion_critical=True, removal_priority=3), + FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.HOTS)], "Char", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS)], "Zerus", completion_critical=True, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(9, SC2Campaign.HOTS)], "Zerus", completion_critical=True, removal_priority=4), + FillMission(MissionPools.MEDIUM, [MissionConnection(10, SC2Campaign.HOTS)], "Zerus", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(15, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(14, SC2Campaign.HOTS), MissionConnection(16, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(18, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + ], + SC2Campaign.PROLOGUE: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2", removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.PROLOGUE)], "_3") + ], + SC2Campaign.LOTV: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True, removal_priority=3), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Korhal", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.LOTV)], "Korhal", completion_critical=True, removal_priority=7), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV)], "Shakuras", completion_critical=True, removal_priority=6), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.LOTV)], "Ulnar", completion_critical=True, removal_priority=1), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.LOTV)], "Purifier", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV)], "Moebius", completion_critical=True, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True, removal_priority=2), + FillMission(MissionPools.FINAL, [MissionConnection(17, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + ], + SC2Campaign.EPILOGUE: [ + FillMission(MissionPools.VERY_HARD, [MissionConnection(24, SC2Campaign.WOL), MissionConnection(19, SC2Campaign.HOTS), MissionConnection(18, SC2Campaign.LOTV)], "_1", completion_critical=True), + FillMission(MissionPools.VERY_HARD, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True, removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.EPILOGUE)], "_3", completion_critical=True), + ], + SC2Campaign.NCO: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=6), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=7), + FillMission(MissionPools.HARD, [MissionConnection(3, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=3), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=2), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(7, SC2Campaign.NCO)], "_3", completion_critical=True), + ] + } + + +def mini_campaign_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.WOL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Artifact", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Artifact", number=4, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Covert", number=2), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.WOL)], "Covert"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Rebellion", number=3), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(10, SC2Campaign.WOL), MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) + ], + SC2Campaign.PROPHECY: [ + FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.WOL)], "_1"), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2"), + ], + SC2Campaign.HOTS: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Kaldir"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.HOTS)], "Kaldir"), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Char"), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Char"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.HOTS)], "Zerus", number=3), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS)], "Zerus"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Skygeirr Station", number=5), + FillMission(MissionPools.HARD, [MissionConnection(7, SC2Campaign.HOTS)], "Skygeirr Station"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Dominion Space", number=5), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.HOTS)], "Dominion Space"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Korhal", completion_critical=True, number=8), + FillMission(MissionPools.FINAL, [MissionConnection(11, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + ], + SC2Campaign.PROLOGUE: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2") + ], + SC2Campaign.LOTV: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur",completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Korhal", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV), MissionConnection(3, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(8, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + ], + SC2Campaign.EPILOGUE: [ + FillMission(MissionPools.VERY_HARD, [MissionConnection(12, SC2Campaign.WOL), MissionConnection(12, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.LOTV)], "_1", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True), + ], + SC2Campaign.NCO: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_2", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_3", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(3, SC2Campaign.NCO)], "_3", completion_critical=True), + ] + } + + +def gauntlet_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "II", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1)], "III", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2)], "IV", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3)], "V", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4)], "VI", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(5)], "Final", completion_critical=True) + ] + } + + +def mini_gauntlet_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "II", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1)], "III", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(2)], "Final", completion_critical=True) + ] + } + + +def grid_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(6), MissionConnection( 3)], "_1", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(7)], "_1", or_requirements=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(4)], "_2", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(5), MissionConnection(10), MissionConnection(7)], "_2", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(3), MissionConnection(6), MissionConnection(11)], "_2", or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(4), MissionConnection(9), MissionConnection(12)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(5), MissionConnection(8), MissionConnection(10), MissionConnection(13)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(6), MissionConnection(9), MissionConnection(11), MissionConnection(14)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(7), MissionConnection(10)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(8), MissionConnection(13)], "_4", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(9), MissionConnection(12), MissionConnection(14)], "_4", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(10), MissionConnection(13)], "_4", or_requirements=True), + FillMission(MissionPools.FINAL, [MissionConnection(11), MissionConnection(14)], "_4", or_requirements=True) + ] + } + +def mini_grid_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(5)], "_1", or_requirements=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(3)], "_2", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(4)], "_2", or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3), MissionConnection(7)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(4), MissionConnection(6)], "_3", or_requirements=True), + FillMission(MissionPools.FINAL, [MissionConnection(5), MissionConnection(7)], "_3", or_requirements=True) + ] + } + +def tiny_grid_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0)], "_1"), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), + FillMission(MissionPools.FINAL, [MissionConnection(1), MissionConnection(2)], "_2", or_requirements=True), + ] + } + +def blitz_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I"), + FillMission(MissionPools.EASY, [MissionConnection(-1)], "I"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "II", number=1, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "II", number=1, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "III", number=2, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "III", number=2, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "IV", number=3, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "IV", number=3, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "V", number=4, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "V", number=4, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "Final", number=5, or_requirements=True), + FillMission(MissionPools.FINAL, [MissionConnection(0), MissionConnection(1)], "Final", number=5, or_requirements=True) + ] + } + + +mission_orders: List[Callable[[], Dict[SC2Campaign, List[FillMission]]]] = [ + vanilla_shuffle_order, + vanilla_shuffle_order, + mini_campaign_order, + grid_order, + mini_grid_order, + blitz_order, + gauntlet_order, + mini_gauntlet_order, + tiny_grid_order +] + + +vanilla_mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = { + SC2Campaign.WOL: { + SC2Mission.LIBERATION_DAY.mission_name: MissionInfo(SC2Mission.LIBERATION_DAY, [], SC2Mission.LIBERATION_DAY.area, completion_critical=True), + SC2Mission.THE_OUTLAWS.mission_name: MissionInfo(SC2Mission.THE_OUTLAWS, [MissionConnection(1, SC2Campaign.WOL)], SC2Mission.THE_OUTLAWS.area, completion_critical=True), + SC2Mission.ZERO_HOUR.mission_name: MissionInfo(SC2Mission.ZERO_HOUR, [MissionConnection(2, SC2Campaign.WOL)], SC2Mission.ZERO_HOUR.area, completion_critical=True), + SC2Mission.EVACUATION.mission_name: MissionInfo(SC2Mission.EVACUATION, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.EVACUATION.area), + SC2Mission.OUTBREAK.mission_name: MissionInfo(SC2Mission.OUTBREAK, [MissionConnection(4, SC2Campaign.WOL)], SC2Mission.OUTBREAK.area), + SC2Mission.SAFE_HAVEN.mission_name: MissionInfo(SC2Mission.SAFE_HAVEN, [MissionConnection(5, SC2Campaign.WOL)], SC2Mission.SAFE_HAVEN.area, number=7), + SC2Mission.HAVENS_FALL.mission_name: MissionInfo(SC2Mission.HAVENS_FALL, [MissionConnection(5, SC2Campaign.WOL)], SC2Mission.HAVENS_FALL.area, number=7), + SC2Mission.SMASH_AND_GRAB.mission_name: MissionInfo(SC2Mission.SMASH_AND_GRAB, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.SMASH_AND_GRAB.area, completion_critical=True), + SC2Mission.THE_DIG.mission_name: MissionInfo(SC2Mission.THE_DIG, [MissionConnection(8, SC2Campaign.WOL)], SC2Mission.THE_DIG.area, number=8, completion_critical=True), + SC2Mission.THE_MOEBIUS_FACTOR.mission_name: MissionInfo(SC2Mission.THE_MOEBIUS_FACTOR, [MissionConnection(9, SC2Campaign.WOL)], SC2Mission.THE_MOEBIUS_FACTOR.area, number=11, completion_critical=True), + SC2Mission.SUPERNOVA.mission_name: MissionInfo(SC2Mission.SUPERNOVA, [MissionConnection(10, SC2Campaign.WOL)], SC2Mission.SUPERNOVA.area, number=14, completion_critical=True), + SC2Mission.MAW_OF_THE_VOID.mission_name: MissionInfo(SC2Mission.MAW_OF_THE_VOID, [MissionConnection(11, SC2Campaign.WOL)], SC2Mission.MAW_OF_THE_VOID.area, completion_critical=True), + SC2Mission.DEVILS_PLAYGROUND.mission_name: MissionInfo(SC2Mission.DEVILS_PLAYGROUND, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.DEVILS_PLAYGROUND.area, number=4), + SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name: MissionInfo(SC2Mission.WELCOME_TO_THE_JUNGLE, [MissionConnection(13, SC2Campaign.WOL)], SC2Mission.WELCOME_TO_THE_JUNGLE.area), + SC2Mission.BREAKOUT.mission_name: MissionInfo(SC2Mission.BREAKOUT, [MissionConnection(14, SC2Campaign.WOL)], SC2Mission.BREAKOUT.area, number=8), + SC2Mission.GHOST_OF_A_CHANCE.mission_name: MissionInfo(SC2Mission.GHOST_OF_A_CHANCE, [MissionConnection(14, SC2Campaign.WOL)], SC2Mission.GHOST_OF_A_CHANCE.area, number=8), + SC2Mission.THE_GREAT_TRAIN_ROBBERY.mission_name: MissionInfo(SC2Mission.THE_GREAT_TRAIN_ROBBERY, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.THE_GREAT_TRAIN_ROBBERY.area, number=6), + SC2Mission.CUTTHROAT.mission_name: MissionInfo(SC2Mission.CUTTHROAT, [MissionConnection(17, SC2Campaign.WOL)], SC2Mission.THE_GREAT_TRAIN_ROBBERY.area), + SC2Mission.ENGINE_OF_DESTRUCTION.mission_name: MissionInfo(SC2Mission.ENGINE_OF_DESTRUCTION, [MissionConnection(18, SC2Campaign.WOL)], SC2Mission.ENGINE_OF_DESTRUCTION.area), + SC2Mission.MEDIA_BLITZ.mission_name: MissionInfo(SC2Mission.MEDIA_BLITZ, [MissionConnection(19, SC2Campaign.WOL)], SC2Mission.MEDIA_BLITZ.area), + SC2Mission.PIERCING_OF_THE_SHROUD.mission_name: MissionInfo(SC2Mission.PIERCING_OF_THE_SHROUD, [MissionConnection(20, SC2Campaign.WOL)], SC2Mission.PIERCING_OF_THE_SHROUD.area), + SC2Mission.GATES_OF_HELL.mission_name: MissionInfo(SC2Mission.GATES_OF_HELL, [MissionConnection(12, SC2Campaign.WOL)], SC2Mission.GATES_OF_HELL.area, completion_critical=True), + SC2Mission.BELLY_OF_THE_BEAST.mission_name: MissionInfo(SC2Mission.BELLY_OF_THE_BEAST, [MissionConnection(22, SC2Campaign.WOL)], SC2Mission.BELLY_OF_THE_BEAST.area, completion_critical=True), + SC2Mission.SHATTER_THE_SKY.mission_name: MissionInfo(SC2Mission.SHATTER_THE_SKY, [MissionConnection(22, SC2Campaign.WOL)], SC2Mission.SHATTER_THE_SKY.area, completion_critical=True), + SC2Mission.ALL_IN.mission_name: MissionInfo(SC2Mission.ALL_IN, [MissionConnection(23, SC2Campaign.WOL), MissionConnection(24, SC2Campaign.WOL)], SC2Mission.ALL_IN.area, or_requirements=True, completion_critical=True) + }, + SC2Campaign.PROPHECY: { + SC2Mission.WHISPERS_OF_DOOM.mission_name: MissionInfo(SC2Mission.WHISPERS_OF_DOOM, [MissionConnection(9, SC2Campaign.WOL)], SC2Mission.WHISPERS_OF_DOOM.area), + SC2Mission.A_SINISTER_TURN.mission_name: MissionInfo(SC2Mission.A_SINISTER_TURN, [MissionConnection(1, SC2Campaign.PROPHECY)], SC2Mission.A_SINISTER_TURN.area), + SC2Mission.ECHOES_OF_THE_FUTURE.mission_name: MissionInfo(SC2Mission.ECHOES_OF_THE_FUTURE, [MissionConnection(2, SC2Campaign.PROPHECY)], SC2Mission.ECHOES_OF_THE_FUTURE.area), + SC2Mission.IN_UTTER_DARKNESS.mission_name: MissionInfo(SC2Mission.IN_UTTER_DARKNESS, [MissionConnection(3, SC2Campaign.PROPHECY)], SC2Mission.IN_UTTER_DARKNESS.area) + }, + SC2Campaign.HOTS: { + SC2Mission.LAB_RAT.mission_name: MissionInfo(SC2Mission.LAB_RAT, [], SC2Mission.LAB_RAT.area, completion_critical=True), + SC2Mission.BACK_IN_THE_SADDLE.mission_name: MissionInfo(SC2Mission.BACK_IN_THE_SADDLE, [MissionConnection(1, SC2Campaign.HOTS)], SC2Mission.BACK_IN_THE_SADDLE.area, completion_critical=True), + SC2Mission.RENDEZVOUS.mission_name: MissionInfo(SC2Mission.RENDEZVOUS, [MissionConnection(2, SC2Campaign.HOTS)], SC2Mission.RENDEZVOUS.area, completion_critical=True), + SC2Mission.HARVEST_OF_SCREAMS.mission_name: MissionInfo(SC2Mission.HARVEST_OF_SCREAMS, [MissionConnection(3, SC2Campaign.HOTS)], SC2Mission.HARVEST_OF_SCREAMS.area), + SC2Mission.SHOOT_THE_MESSENGER.mission_name: MissionInfo(SC2Mission.SHOOT_THE_MESSENGER, [MissionConnection(4, SC2Campaign.HOTS)], SC2Mission.SHOOT_THE_MESSENGER.area), + SC2Mission.ENEMY_WITHIN.mission_name: MissionInfo(SC2Mission.ENEMY_WITHIN, [MissionConnection(5, SC2Campaign.HOTS)], SC2Mission.ENEMY_WITHIN.area), + SC2Mission.DOMINATION.mission_name: MissionInfo(SC2Mission.DOMINATION, [MissionConnection(3, SC2Campaign.HOTS)], SC2Mission.DOMINATION.area), + SC2Mission.FIRE_IN_THE_SKY.mission_name: MissionInfo(SC2Mission.FIRE_IN_THE_SKY, [MissionConnection(7, SC2Campaign.HOTS)], SC2Mission.FIRE_IN_THE_SKY.area), + SC2Mission.OLD_SOLDIERS.mission_name: MissionInfo(SC2Mission.OLD_SOLDIERS, [MissionConnection(8, SC2Campaign.HOTS)], SC2Mission.OLD_SOLDIERS.area), + SC2Mission.WAKING_THE_ANCIENT.mission_name: MissionInfo(SC2Mission.WAKING_THE_ANCIENT, [MissionConnection(6, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.HOTS)], SC2Mission.WAKING_THE_ANCIENT.area, completion_critical=True, or_requirements=True), + SC2Mission.THE_CRUCIBLE.mission_name: MissionInfo(SC2Mission.THE_CRUCIBLE, [MissionConnection(10, SC2Campaign.HOTS)], SC2Mission.THE_CRUCIBLE.area, completion_critical=True), + SC2Mission.SUPREME.mission_name: MissionInfo(SC2Mission.SUPREME, [MissionConnection(11, SC2Campaign.HOTS)], SC2Mission.SUPREME.area, completion_critical=True), + SC2Mission.INFESTED.mission_name: MissionInfo(SC2Mission.INFESTED, [MissionConnection(6, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.HOTS), MissionConnection(12, SC2Campaign.HOTS)], SC2Mission.INFESTED.area), + SC2Mission.HAND_OF_DARKNESS.mission_name: MissionInfo(SC2Mission.HAND_OF_DARKNESS, [MissionConnection(13, SC2Campaign.HOTS)], SC2Mission.HAND_OF_DARKNESS.area), + SC2Mission.PHANTOMS_OF_THE_VOID.mission_name: MissionInfo(SC2Mission.PHANTOMS_OF_THE_VOID, [MissionConnection(14, SC2Campaign.HOTS)], SC2Mission.PHANTOMS_OF_THE_VOID.area), + SC2Mission.WITH_FRIENDS_LIKE_THESE.mission_name: MissionInfo(SC2Mission.WITH_FRIENDS_LIKE_THESE, [MissionConnection(6, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.HOTS), MissionConnection(12, SC2Campaign.HOTS)], SC2Mission.WITH_FRIENDS_LIKE_THESE.area), + SC2Mission.CONVICTION.mission_name: MissionInfo(SC2Mission.CONVICTION, [MissionConnection(16, SC2Campaign.HOTS)], SC2Mission.CONVICTION.area), + SC2Mission.PLANETFALL.mission_name: MissionInfo(SC2Mission.PLANETFALL, [MissionConnection(15, SC2Campaign.HOTS), MissionConnection(17, SC2Campaign.HOTS)], SC2Mission.PLANETFALL.area, completion_critical=True), + SC2Mission.DEATH_FROM_ABOVE.mission_name: MissionInfo(SC2Mission.DEATH_FROM_ABOVE, [MissionConnection(18, SC2Campaign.HOTS)], SC2Mission.DEATH_FROM_ABOVE.area, completion_critical=True), + SC2Mission.THE_RECKONING.mission_name: MissionInfo(SC2Mission.THE_RECKONING, [MissionConnection(19, SC2Campaign.HOTS)], SC2Mission.THE_RECKONING.area, completion_critical=True), + }, + SC2Campaign.PROLOGUE: { + SC2Mission.DARK_WHISPERS.mission_name: MissionInfo(SC2Mission.DARK_WHISPERS, [], SC2Mission.DARK_WHISPERS.area), + SC2Mission.GHOSTS_IN_THE_FOG.mission_name: MissionInfo(SC2Mission.GHOSTS_IN_THE_FOG, [MissionConnection(1, SC2Campaign.PROLOGUE)], SC2Mission.GHOSTS_IN_THE_FOG.area), + SC2Mission.EVIL_AWOKEN.mission_name: MissionInfo(SC2Mission.EVIL_AWOKEN, [MissionConnection(2, SC2Campaign.PROLOGUE)], SC2Mission.EVIL_AWOKEN.area) + }, + SC2Campaign.LOTV: { + SC2Mission.FOR_AIUR.mission_name: MissionInfo(SC2Mission.FOR_AIUR, [], SC2Mission.FOR_AIUR.area, completion_critical=True), + SC2Mission.THE_GROWING_SHADOW.mission_name: MissionInfo(SC2Mission.THE_GROWING_SHADOW, [MissionConnection(1, SC2Campaign.LOTV)], SC2Mission.THE_GROWING_SHADOW.area, completion_critical=True), + SC2Mission.THE_SPEAR_OF_ADUN.mission_name: MissionInfo(SC2Mission.THE_SPEAR_OF_ADUN, [MissionConnection(2, SC2Campaign.LOTV)], SC2Mission.THE_SPEAR_OF_ADUN.area, completion_critical=True), + SC2Mission.SKY_SHIELD.mission_name: MissionInfo(SC2Mission.SKY_SHIELD, [MissionConnection(3, SC2Campaign.LOTV)], SC2Mission.SKY_SHIELD.area, completion_critical=True), + SC2Mission.BROTHERS_IN_ARMS.mission_name: MissionInfo(SC2Mission.BROTHERS_IN_ARMS, [MissionConnection(4, SC2Campaign.LOTV)], SC2Mission.BROTHERS_IN_ARMS.area, completion_critical=True), + SC2Mission.AMON_S_REACH.mission_name: MissionInfo(SC2Mission.AMON_S_REACH, [MissionConnection(3, SC2Campaign.LOTV)], SC2Mission.AMON_S_REACH.area, completion_critical=True), + SC2Mission.LAST_STAND.mission_name: MissionInfo(SC2Mission.LAST_STAND, [MissionConnection(6, SC2Campaign.LOTV)], SC2Mission.LAST_STAND.area, completion_critical=True), + SC2Mission.FORBIDDEN_WEAPON.mission_name: MissionInfo(SC2Mission.FORBIDDEN_WEAPON, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], SC2Mission.FORBIDDEN_WEAPON.area, completion_critical=True, or_requirements=True), + SC2Mission.TEMPLE_OF_UNIFICATION.mission_name: MissionInfo(SC2Mission.TEMPLE_OF_UNIFICATION, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV), MissionConnection(8, SC2Campaign.LOTV)], SC2Mission.TEMPLE_OF_UNIFICATION.area, completion_critical=True), + SC2Mission.THE_INFINITE_CYCLE.mission_name: MissionInfo(SC2Mission.THE_INFINITE_CYCLE, [MissionConnection(9, SC2Campaign.LOTV)], SC2Mission.THE_INFINITE_CYCLE.area, completion_critical=True), + SC2Mission.HARBINGER_OF_OBLIVION.mission_name: MissionInfo(SC2Mission.HARBINGER_OF_OBLIVION, [MissionConnection(10, SC2Campaign.LOTV)], SC2Mission.HARBINGER_OF_OBLIVION.area, completion_critical=True), + SC2Mission.UNSEALING_THE_PAST.mission_name: MissionInfo(SC2Mission.UNSEALING_THE_PAST, [MissionConnection(11, SC2Campaign.LOTV)], SC2Mission.UNSEALING_THE_PAST.area, completion_critical=True), + SC2Mission.PURIFICATION.mission_name: MissionInfo(SC2Mission.PURIFICATION, [MissionConnection(12, SC2Campaign.LOTV)], SC2Mission.PURIFICATION.area, completion_critical=True), + SC2Mission.STEPS_OF_THE_RITE.mission_name: MissionInfo(SC2Mission.STEPS_OF_THE_RITE, [MissionConnection(11, SC2Campaign.LOTV)], SC2Mission.STEPS_OF_THE_RITE.area, completion_critical=True), + SC2Mission.RAK_SHIR.mission_name: MissionInfo(SC2Mission.RAK_SHIR, [MissionConnection(14, SC2Campaign.LOTV)], SC2Mission.RAK_SHIR.area, completion_critical=True), + SC2Mission.TEMPLAR_S_CHARGE.mission_name: MissionInfo(SC2Mission.TEMPLAR_S_CHARGE, [MissionConnection(13, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV)], SC2Mission.TEMPLAR_S_CHARGE.area, completion_critical=True, or_requirements=True), + SC2Mission.TEMPLAR_S_RETURN.mission_name: MissionInfo(SC2Mission.TEMPLAR_S_RETURN, [MissionConnection(13, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV), MissionConnection(16, SC2Campaign.LOTV)], SC2Mission.TEMPLAR_S_RETURN.area, completion_critical=True), + SC2Mission.THE_HOST.mission_name: MissionInfo(SC2Mission.THE_HOST, [MissionConnection(17, SC2Campaign.LOTV)], SC2Mission.THE_HOST.area, completion_critical=True), + SC2Mission.SALVATION.mission_name: MissionInfo(SC2Mission.SALVATION, [MissionConnection(18, SC2Campaign.LOTV)], SC2Mission.SALVATION.area, completion_critical=True), + }, + SC2Campaign.EPILOGUE: { + SC2Mission.INTO_THE_VOID.mission_name: MissionInfo(SC2Mission.INTO_THE_VOID, [MissionConnection(25, SC2Campaign.WOL), MissionConnection(20, SC2Campaign.HOTS), MissionConnection(19, SC2Campaign.LOTV)], SC2Mission.INTO_THE_VOID.area, completion_critical=True), + SC2Mission.THE_ESSENCE_OF_ETERNITY.mission_name: MissionInfo(SC2Mission.THE_ESSENCE_OF_ETERNITY, [MissionConnection(1, SC2Campaign.EPILOGUE)], SC2Mission.THE_ESSENCE_OF_ETERNITY.area, completion_critical=True), + SC2Mission.AMON_S_FALL.mission_name: MissionInfo(SC2Mission.AMON_S_FALL, [MissionConnection(2, SC2Campaign.EPILOGUE)], SC2Mission.AMON_S_FALL.area, completion_critical=True), + }, + SC2Campaign.NCO: { + SC2Mission.THE_ESCAPE.mission_name: MissionInfo(SC2Mission.THE_ESCAPE, [], SC2Mission.THE_ESCAPE.area, completion_critical=True), + SC2Mission.SUDDEN_STRIKE.mission_name: MissionInfo(SC2Mission.SUDDEN_STRIKE, [MissionConnection(1, SC2Campaign.NCO)], SC2Mission.SUDDEN_STRIKE.area, completion_critical=True), + SC2Mission.ENEMY_INTELLIGENCE.mission_name: MissionInfo(SC2Mission.ENEMY_INTELLIGENCE, [MissionConnection(2, SC2Campaign.NCO)], SC2Mission.ENEMY_INTELLIGENCE.area, completion_critical=True), + SC2Mission.TROUBLE_IN_PARADISE.mission_name: MissionInfo(SC2Mission.TROUBLE_IN_PARADISE, [MissionConnection(3, SC2Campaign.NCO)], SC2Mission.TROUBLE_IN_PARADISE.area, completion_critical=True), + SC2Mission.NIGHT_TERRORS.mission_name: MissionInfo(SC2Mission.NIGHT_TERRORS, [MissionConnection(4, SC2Campaign.NCO)], SC2Mission.NIGHT_TERRORS.area, completion_critical=True), + SC2Mission.FLASHPOINT.mission_name: MissionInfo(SC2Mission.FLASHPOINT, [MissionConnection(5, SC2Campaign.NCO)], SC2Mission.FLASHPOINT.area, completion_critical=True), + SC2Mission.IN_THE_ENEMY_S_SHADOW.mission_name: MissionInfo(SC2Mission.IN_THE_ENEMY_S_SHADOW, [MissionConnection(6, SC2Campaign.NCO)], SC2Mission.IN_THE_ENEMY_S_SHADOW.area, completion_critical=True), + SC2Mission.DARK_SKIES.mission_name: MissionInfo(SC2Mission.DARK_SKIES, [MissionConnection(7, SC2Campaign.NCO)], SC2Mission.DARK_SKIES.area, completion_critical=True), + SC2Mission.END_GAME.mission_name: MissionInfo(SC2Mission.END_GAME, [MissionConnection(8, SC2Campaign.NCO)], SC2Mission.END_GAME.area, completion_critical=True), + } +} + +lookup_id_to_mission: Dict[int, SC2Mission] = { + mission.id: mission for mission in SC2Mission +} + +lookup_name_to_mission: Dict[str, SC2Mission] = { + mission.mission_name: mission for mission in SC2Mission +} + +lookup_id_to_campaign: Dict[int, SC2Campaign] = { + campaign.id: campaign for campaign in SC2Campaign +} + + +campaign_mission_table: Dict[SC2Campaign, Set[SC2Mission]] = { + campaign: set() for campaign in SC2Campaign +} +for mission in SC2Mission: + campaign_mission_table[mission.campaign].add(mission) + + +def get_campaign_difficulty(campaign: SC2Campaign, excluded_missions: Iterable[SC2Mission] = ()) -> MissionPools: + """ + + :param campaign: + :param excluded_missions: + :return: Campaign's the most difficult non-excluded mission + """ + excluded_mission_set = set(excluded_missions) + included_missions = campaign_mission_table[campaign].difference(excluded_mission_set) + return max([mission.pool for mission in included_missions]) + + +def get_campaign_goal_priority(campaign: SC2Campaign, excluded_missions: Iterable[SC2Mission] = ()) -> SC2CampaignGoalPriority: + """ + Gets a modified campaign goal priority. + If all the campaign's goal missions are excluded, it's ineligible to have the goal + If the campaign's very hard missions are excluded, the priority is lowered to hard + :param campaign: + :param excluded_missions: + :return: + """ + if excluded_missions is None: + return campaign.goal_priority + else: + goal_missions = set(get_campaign_potential_goal_missions(campaign)) + excluded_mission_set = set(excluded_missions) + remaining_goals = goal_missions.difference(excluded_mission_set) + if remaining_goals == set(): + # All potential goals are excluded, the campaign can't be a goal + return SC2CampaignGoalPriority.NONE + elif campaign.goal_priority == SC2CampaignGoalPriority.VERY_HARD: + # Check if a very hard campaign doesn't get rid of it's last very hard mission + difficulty = get_campaign_difficulty(campaign, excluded_missions) + if difficulty == MissionPools.VERY_HARD: + return SC2CampaignGoalPriority.VERY_HARD + else: + return SC2CampaignGoalPriority.HARD + else: + return campaign.goal_priority + + +class SC2CampaignGoal(NamedTuple): + mission: SC2Mission + location: str + + +campaign_final_mission_locations: Dict[SC2Campaign, SC2CampaignGoal] = { + SC2Campaign.WOL: SC2CampaignGoal(SC2Mission.ALL_IN, "All-In: Victory"), + SC2Campaign.PROPHECY: SC2CampaignGoal(SC2Mission.IN_UTTER_DARKNESS, "In Utter Darkness: Kills"), + SC2Campaign.HOTS: None, + SC2Campaign.PROLOGUE: SC2CampaignGoal(SC2Mission.EVIL_AWOKEN, "Evil Awoken: Victory"), + SC2Campaign.LOTV: SC2CampaignGoal(SC2Mission.SALVATION, "Salvation: Victory"), + SC2Campaign.EPILOGUE: None, + SC2Campaign.NCO: SC2CampaignGoal(SC2Mission.END_GAME, "End Game: Victory"), +} + +campaign_alt_final_mission_locations: Dict[SC2Campaign, Dict[SC2Mission, str]] = { + SC2Campaign.WOL: { + SC2Mission.MAW_OF_THE_VOID: "Maw of the Void: Victory", + SC2Mission.ENGINE_OF_DESTRUCTION: "Engine of Destruction: Victory", + SC2Mission.SUPERNOVA: "Supernova: Victory", + SC2Mission.GATES_OF_HELL: "Gates of Hell: Victory", + SC2Mission.SHATTER_THE_SKY: "Shatter the Sky: Victory" + }, + SC2Campaign.PROPHECY: None, + SC2Campaign.HOTS: { + SC2Mission.THE_RECKONING: "The Reckoning: Victory", + SC2Mission.THE_CRUCIBLE: "The Crucible: Victory", + SC2Mission.HAND_OF_DARKNESS: "Hand of Darkness: Victory", + SC2Mission.PHANTOMS_OF_THE_VOID: "Phantoms of the Void: Victory", + SC2Mission.PLANETFALL: "Planetfall: Victory", + SC2Mission.DEATH_FROM_ABOVE: "Death From Above: Victory" + }, + SC2Campaign.PROLOGUE: { + SC2Mission.GHOSTS_IN_THE_FOG: "Ghosts in the Fog: Victory" + }, + SC2Campaign.LOTV: { + SC2Mission.THE_HOST: "The Host: Victory", + SC2Mission.TEMPLAR_S_CHARGE: "Templar's Charge: Victory" + }, + SC2Campaign.EPILOGUE: { + SC2Mission.AMON_S_FALL: "Amon's Fall: Victory", + SC2Mission.INTO_THE_VOID: "Into the Void: Victory", + SC2Mission.THE_ESSENCE_OF_ETERNITY: "The Essence of Eternity: Victory", + }, + SC2Campaign.NCO: { + SC2Mission.FLASHPOINT: "Flashpoint: Victory", + SC2Mission.DARK_SKIES: "Dark Skies: Victory", + SC2Mission.NIGHT_TERRORS: "Night Terrors: Victory", + SC2Mission.TROUBLE_IN_PARADISE: "Trouble In Paradise: Victory" + } +} + +campaign_race_exceptions: Dict[SC2Mission, SC2Race] = { + SC2Mission.WITH_FRIENDS_LIKE_THESE: SC2Race.TERRAN +} + + +def get_goal_location(mission: SC2Mission) -> Union[str, None]: + """ + + :param mission: + :return: Goal location assigned to the goal mission + """ + campaign = mission.campaign + primary_campaign_goal = campaign_final_mission_locations[campaign] + if primary_campaign_goal is not None: + if primary_campaign_goal.mission == mission: + return primary_campaign_goal.location + + campaign_alt_goals = campaign_alt_final_mission_locations[campaign] + if campaign_alt_goals is not None and mission in campaign_alt_goals: + return campaign_alt_goals.get(mission) + + return mission.mission_name + ": Victory" + + +def get_campaign_potential_goal_missions(campaign: SC2Campaign) -> List[SC2Mission]: + """ + + :param campaign: + :return: All missions that can be the campaign's goal + """ + missions: List[SC2Mission] = list() + primary_goal_mission = campaign_final_mission_locations[campaign] + if primary_goal_mission is not None: + missions.append(primary_goal_mission.mission) + alt_goal_locations = campaign_alt_final_mission_locations[campaign] + if alt_goal_locations is not None: + for mission in alt_goal_locations.keys(): + missions.append(mission) + + return missions + + +def get_no_build_missions() -> List[SC2Mission]: + return [mission for mission in SC2Mission if not mission.build] diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py new file mode 100644 index 000000000000..88febb7096ef --- /dev/null +++ b/worlds/sc2/Options.py @@ -0,0 +1,908 @@ +from dataclasses import dataclass, fields, Field +from typing import FrozenSet, Union, Set + +from Options import Choice, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range, PerGameCommonOptions +from .MissionTables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_no_build_missions, \ + campaign_mission_table +from worlds.AutoWorld import World + + +class GameDifficulty(Choice): + """ + The difficulty of the campaign, affects enemy AI, starting units, and game speed. + + For those unfamiliar with the Archipelago randomizer, the recommended settings are one difficulty level + lower than the vanilla game + """ + display_name = "Game Difficulty" + option_casual = 0 + option_normal = 1 + option_hard = 2 + option_brutal = 3 + default = 1 + + +class GameSpeed(Choice): + """Optional setting to override difficulty-based game speed.""" + display_name = "Game Speed" + option_default = 0 + option_slower = 1 + option_slow = 2 + option_normal = 3 + option_fast = 4 + option_faster = 5 + default = option_default + + +class DisableForcedCamera(Toggle): + """ + Prevents the game from moving or locking the camera without the player's consent. + """ + display_name = "Disable Forced Camera Movement" + + +class SkipCutscenes(Toggle): + """ + Skips all cutscenes and prevents dialog from blocking progress. + """ + display_name = "Skip Cutscenes" + + +class AllInMap(Choice): + """Determines what version of All-In (WoL final map) that will be generated for the campaign.""" + display_name = "All In Map" + option_ground = 0 + option_air = 1 + + +class MissionOrder(Choice): + """ + Determines the order the missions are played in. The last three mission orders end in a random mission. + Vanilla (83 total if all campaigns enabled): Keeps the standard mission order and branching from the vanilla Campaigns. + Vanilla Shuffled (83 total if all campaigns enabled): Keeps same branching paths from the vanilla Campaigns but randomizes the order of missions within. + Mini Campaign (47 total if all campaigns enabled): Shorter version of the campaign with randomized missions and optional branches. + Medium Grid (16): A 4x4 grid of random missions. Start at the top-left and forge a path towards bottom-right mission to win. + Mini Grid (9): A 3x3 version of Grid. Complete the bottom-right mission to win. + Blitz (12): 12 random missions that open up very quickly. Complete the bottom-right mission to win. + Gauntlet (7): Linear series of 7 random missions to complete the campaign. + Mini Gauntlet (4): Linear series of 4 random missions to complete the campaign. + Tiny Grid (4): A 2x2 version of Grid. Complete the bottom-right mission to win. + Grid (variable): A grid that will resize to use all non-excluded missions. Corners may be omitted to make the grid more square. Complete the bottom-right mission to win. + """ + display_name = "Mission Order" + option_vanilla = 0 + option_vanilla_shuffled = 1 + option_mini_campaign = 2 + option_medium_grid = 3 + option_mini_grid = 4 + option_blitz = 5 + option_gauntlet = 6 + option_mini_gauntlet = 7 + option_tiny_grid = 8 + option_grid = 9 + + +class MaximumCampaignSize(Range): + """ + Sets an upper bound on how many missions to include when a variable-size mission order is selected. + If a set-size mission order is selected, does nothing. + """ + display_name = "Maximum Campaign Size" + range_start = 1 + range_end = 83 + default = 83 + + +class GridTwoStartPositions(Toggle): + """ + If turned on and 'grid' mission order is selected, removes a mission from the starting + corner sets the adjacent two missions as the starter missions. + """ + display_name = "Start with two unlocked missions on grid" + default = Toggle.option_false + + +class ColorChoice(Choice): + option_white = 0 + option_red = 1 + option_blue = 2 + option_teal = 3 + option_purple = 4 + option_yellow = 5 + option_orange = 6 + option_green = 7 + option_light_pink = 8 + option_violet = 9 + option_light_grey = 10 + option_dark_green = 11 + option_brown = 12 + option_light_green = 13 + option_dark_grey = 14 + option_pink = 15 + option_rainbow = 16 + option_default = 17 + default = option_default + + +class PlayerColorTerranRaynor(ColorChoice): + """Determines in-game team color for playable Raynor's Raiders (Terran) factions.""" + display_name = "Terran Player Color (Raynor)" + + +class PlayerColorProtoss(ColorChoice): + """Determines in-game team color for playable Protoss factions.""" + display_name = "Protoss Player Color" + + +class PlayerColorZerg(ColorChoice): + """Determines in-game team color for playable Zerg factions before Kerrigan becomes Primal Kerrigan.""" + display_name = "Zerg Player Color" + + +class PlayerColorZergPrimal(ColorChoice): + """Determines in-game team color for playable Zerg factions after Kerrigan becomes Primal Kerrigan.""" + display_name = "Zerg Player Color (Primal)" + + +class EnableWolMissions(DefaultOnToggle): + """ + Enables missions from main Wings of Liberty campaign. + """ + display_name = "Enable Wings of Liberty missions" + + +class EnableProphecyMissions(DefaultOnToggle): + """ + Enables missions from Prophecy mini-campaign. + """ + display_name = "Enable Prophecy missions" + + +class EnableHotsMissions(DefaultOnToggle): + """ + Enables missions from Heart of the Swarm campaign. + """ + display_name = "Enable Heart of the Swarm missions" + + +class EnableLotVPrologueMissions(DefaultOnToggle): + """ + Enables missions from Prologue campaign. + """ + display_name = "Enable Prologue (Legacy of the Void) missions" + + +class EnableLotVMissions(DefaultOnToggle): + """ + Enables missions from Legacy of the Void campaign. + """ + display_name = "Enable Legacy of the Void (main campaign) missions" + + +class EnableEpilogueMissions(DefaultOnToggle): + """ + Enables missions from Epilogue campaign. + These missions are considered very hard. + + Enabling Wings of Liberty, Heart of the Swarm and Legacy of the Void is strongly recommended in order to play Epilogue. + Not recommended for short mission orders. + See also: Exclude Very Hard Missions + """ + display_name = "Enable Epilogue missions" + + +class EnableNCOMissions(DefaultOnToggle): + """ + Enables missions from Nova Covert Ops campaign. + + Note: For best gameplay experience it's recommended to also enable Wings of Liberty campaign. + """ + display_name = "Enable Nova Covert Ops missions" + + +class ShuffleCampaigns(DefaultOnToggle): + """ + Shuffles the missions between campaigns if enabled. + Only available for Vanilla Shuffled and Mini Campaign mission order + """ + display_name = "Shuffle Campaigns" + + +class ShuffleNoBuild(DefaultOnToggle): + """ + Determines if the no-build missions are included in the shuffle. + If turned off, the no-build missions will not appear. Has no effect for Vanilla mission order. + """ + display_name = "Shuffle No-Build Missions" + + +class StarterUnit(Choice): + """ + Unlocks a random unit at the start of the game. + + Off: No units are provided, the first unit must be obtained from the randomizer + Balanced: A unit that doesn't give the player too much power early on is given + Any Starter Unit: Any starter unit can be given + """ + display_name = "Starter Unit" + option_off = 0 + option_balanced = 1 + option_any_starter_unit = 2 + + +class RequiredTactics(Choice): + """ + Determines the maximum tactical difficulty of the world (separate from mission difficulty). Higher settings + increase randomness. + + Standard: All missions can be completed with good micro and macro. + Advanced: Completing missions may require relying on starting units and micro-heavy units. + No Logic: Units and upgrades may be placed anywhere. LIKELY TO RENDER THE RUN IMPOSSIBLE ON HARDER DIFFICULTIES! + Locks Grant Story Tech option to true. + """ + display_name = "Required Tactics" + option_standard = 0 + option_advanced = 1 + option_no_logic = 2 + + +class GenericUpgradeMissions(Range): + """Determines the percentage of missions in the mission order that must be completed before + level 1 of all weapon and armor upgrades is unlocked. Level 2 upgrades require double the amount of missions, + and level 3 requires triple the amount. The required amounts are always rounded down. + If set to 0, upgrades are instead added to the item pool and must be found to be used.""" + display_name = "Generic Upgrade Missions" + range_start = 0 + range_end = 100 + default = 0 + + +class GenericUpgradeResearch(Choice): + """Determines how weapon and armor upgrades affect missions once unlocked. + + Vanilla: Upgrades must be researched as normal. + Auto In No-Build: In No-Build missions, upgrades are automatically researched. + In all other missions, upgrades must be researched as normal. + Auto In Build: In No-Build missions, upgrades are unavailable as normal. + In all other missions, upgrades are automatically researched. + Always Auto: Upgrades are automatically researched in all missions.""" + display_name = "Generic Upgrade Research" + option_vanilla = 0 + option_auto_in_no_build = 1 + option_auto_in_build = 2 + option_always_auto = 3 + + +class GenericUpgradeItems(Choice): + """Determines how weapon and armor upgrades are split into items. All options produce 3 levels of each item. + Does nothing if upgrades are unlocked by completed mission counts. + + Individual Items: All weapon and armor upgrades are each an item, + resulting in 18 total upgrade items for Terran and 15 total items for Zerg and Protoss each. + Bundle Weapon And Armor: All types of weapon upgrades are one item per race, + and all types of armor upgrades are one item per race, + resulting in 18 total items. + Bundle Unit Class: Weapon and armor upgrades are merged, + but upgrades are bundled separately for each race: + Infantry, Vehicle, and Starship upgrades for Terran (9 items), + Ground and Flyer upgrades for Zerg (6 items), + Ground and Air upgrades for Protoss (6 items), + resulting in 21 total items. + Bundle All: All weapon and armor upgrades are one item per race, + resulting in 9 total items.""" + display_name = "Generic Upgrade Items" + option_individual_items = 0 + option_bundle_weapon_and_armor = 1 + option_bundle_unit_class = 2 + option_bundle_all = 3 + + +class NovaCovertOpsItems(Toggle): + """ + If turned on, the equipment upgrades from Nova Covert Ops may be present in the world. + + If Nova Covert Ops campaign is enabled, this option is locked to be turned on. + """ + display_name = "Nova Covert Ops Items" + default = Toggle.option_true + + +class BroodWarItems(Toggle): + """If turned on, returning items from StarCraft: Brood War may appear in the world.""" + display_name = "Brood War Items" + default = Toggle.option_true + + +class ExtendedItems(Toggle): + """If turned on, original items that did not appear in Campaign mode may appear in the world.""" + display_name = "Extended Items" + default = Toggle.option_true + + +# Current maximum number of upgrades for a unit +MAX_UPGRADES_OPTION = 12 + + +class EnsureGenericItems(Range): + """ + Specifies a minimum percentage of the generic item pool that will be present for the slot. + The generic item pool is the pool of all generically useful items after all exclusions. + Generically-useful items include: Worker upgrades, Building upgrades, economy upgrades, + Mercenaries, Kerrigan levels and abilities, and Spear of Adun abilities + Increasing this percentage will make units less common. + """ + display_name = "Ensure Generic Items" + range_start = 0 + range_end = 100 + default = 25 + + +class MinNumberOfUpgrades(Range): + """ + Set a minimum to the number of upgrades a unit/structure can have. + Note that most units have 4 or 6 upgrades. + If a unit has fewer upgrades than the minimum, it will have all of its upgrades. + + Doesn't affect shared unit upgrades. + """ + display_name = "Minimum number of upgrades per unit/structure" + range_start = 0 + range_end = MAX_UPGRADES_OPTION + default = 2 + + +class MaxNumberOfUpgrades(Range): + """ + Set a maximum to the number of upgrades a unit/structure can have. -1 is used to define unlimited. + Note that most unit have 4 to 6 upgrades. + + Doesn't affect shared unit upgrades. + """ + display_name = "Maximum number of upgrades per unit/structure" + range_start = -1 + range_end = MAX_UPGRADES_OPTION + default = -1 + + +class KerriganPresence(Choice): + """ + Determines whether Kerrigan is playable outside of missions that require her. + + Vanilla: Kerrigan is playable as normal, appears in the same missions as in vanilla game. + Not Present: Kerrigan is not playable, unless the mission requires her to be present. Other hero units stay playable, + and locations normally requiring Kerrigan can be checked by any unit. + Kerrigan level items, active abilities and passive abilities affecting her will not appear. + In missions where the Kerrigan unit is required, story abilities are given in same way as Grant Story Tech is set to true + Not Present And No Passives: In addition to the above, Kerrigan's passive abilities affecting other units (such as Twin Drones) will not appear. + + Note: Always set to "Not Present" if Heart of the Swarm campaign is disabled. + """ + display_name = "Kerrigan Presence" + option_vanilla = 0 + option_not_present = 1 + option_not_present_and_no_passives = 2 + + +class KerriganLevelsPerMissionCompleted(Range): + """ + Determines how many levels Kerrigan gains when a mission is beaten. + + NOTE: Setting this too low can result in generation failures if The Infinite Cycle or Supreme are in the mission pool. + """ + display_name = "Levels Per Mission Beaten" + range_start = 0 + range_end = 20 + default = 0 + + +class KerriganLevelsPerMissionCompletedCap(Range): + """ + Limits how many total levels Kerrigan can gain from beating missions. This does not affect levels gained from items. + Set to -1 to disable this limit. + + NOTE: The following missions have these level requirements: + Supreme: 35 + The Infinite Cycle: 70 + See Grant Story Levels for more details. + """ + display_name = "Levels Per Mission Beaten Cap" + range_start = -1 + range_end = 140 + default = -1 + + +class KerriganLevelItemSum(Range): + """ + Determines the sum of the level items in the world. This does not affect levels gained from beating missions. + + NOTE: The following missions have these level requirements: + Supreme: 35 + The Infinite Cycle: 70 + See Grant Story Levels for more details. + """ + display_name = "Kerrigan Level Item Sum" + range_start = 0 + range_end = 140 + default = 70 + + +class KerriganLevelItemDistribution(Choice): + """Determines the amount and size of Kerrigan level items. + + Vanilla: Uses the distribution in the vanilla campaign. + This entails 32 individual levels and 6 packs of varying sizes. + This distribution always adds up to 70, ignoring the Level Item Sum setting. + Smooth: Uses a custom, condensed distribution of 10 items between sizes 4 and 10, + intended to fit more levels into settings with little room for filler while keeping some variance in level gains. + This distribution always adds up to 70, ignoring the Level Item Sum setting. + Size 70: Uses items worth 70 levels each. + Size 35: Uses items worth 35 levels each. + Size 14: Uses items worth 14 levels each. + Size 10: Uses items worth 10 levels each. + Size 7: Uses items worth 7 levels each. + Size 5: Uses items worth 5 levels each. + Size 2: Uses items worth 2 level eachs. + Size 1: Uses individual levels. As there are not enough locations in the game for this distribution, + this will result in a greatly reduced total level, and is likely to remove many other items.""" + display_name = "Kerrigan Level Item Distribution" + option_vanilla = 0 + option_smooth = 1 + option_size_70 = 2 + option_size_35 = 3 + option_size_14 = 4 + option_size_10 = 5 + option_size_7 = 6 + option_size_5 = 7 + option_size_2 = 8 + option_size_1 = 9 + default = option_smooth + + +class KerriganTotalLevelCap(Range): + """ + Limits how many total levels Kerrigan can gain from any source. Depending on your other settings, + there may be more levels available in the world, but they will not affect Kerrigan. + Set to -1 to disable this limit. + + NOTE: The following missions have these level requirements: + Supreme: 35 + The Infinite Cycle: 70 + See Grant Story Levels for more details. + """ + display_name = "Total Level Cap" + range_start = -1 + range_end = 140 + default = -1 + + +class StartPrimaryAbilities(Range): + """Number of Primary Abilities (Kerrigan Tier 1, 2, and 4) to start the game with. + If set to 4, a Tier 7 ability is also included.""" + display_name = "Starting Primary Abilities" + range_start = 0 + range_end = 4 + default = 0 + + +class KerriganPrimalStatus(Choice): + """Determines when Kerrigan appears in her Primal Zerg form. + This greatly increases her energy regeneration. + + Vanilla: Kerrigan is human in missions that canonically appear before The Crucible, + and zerg thereafter. + Always Zerg: Kerrigan is always zerg. + Always Human: Kerrigan is always human. + Level 35: Kerrigan is human until reaching level 35, and zerg thereafter. + Half Completion: Kerrigan is human until half of the missions in the world are completed, + and zerg thereafter. + Item: Kerrigan's Primal Form is an item. She is human until it is found, and zerg thereafter.""" + display_name = "Kerrigan Primal Status" + option_vanilla = 0 + option_always_zerg = 1 + option_always_human = 2 + option_level_35 = 3 + option_half_completion = 4 + option_item = 5 + + +class SpearOfAdunPresence(Choice): + """ + Determines in which missions Spear of Adun calldowns will be available. + Affects only abilities used from Spear of Adun top menu. + + Not Present: Spear of Adun calldowns are unavailable. + LotV Protoss: Spear of Adun calldowns are only available in LotV main campaign + Protoss: Spear od Adun calldowns are available in any Protoss mission + Everywhere: Spear od Adun calldowns are available in any mission of any race + """ + display_name = "Spear of Adun Presence" + option_not_present = 0 + option_lotv_protoss = 1 + option_protoss = 2 + option_everywhere = 3 + default = option_lotv_protoss + + # Fix case + @classmethod + def get_option_name(cls, value: int) -> str: + if value == SpearOfAdunPresence.option_lotv_protoss: + return "LotV Protoss" + else: + return super().get_option_name(value) + + +class SpearOfAdunPresentInNoBuild(Toggle): + """ + Determines if Spear of Adun calldowns are available in no-build missions. + + If turned on, Spear of Adun calldown powers are available in missions specified under "Spear of Adun Presence". + If turned off, Spear of Adun calldown powers are unavailable in all no-build missions + """ + display_name = "Spear of Adun Present in No-Build" + + +class SpearOfAdunAutonomouslyCastAbilityPresence(Choice): + """ + Determines availability of Spear of Adun powers, that are autonomously cast. + Affects abilities like Reconstruction Beam or Overwatch + + Not Presents: Autocasts are not available. + LotV Protoss: Spear of Adun autocasts are only available in LotV main campaign + Protoss: Spear od Adun autocasts are available in any Protoss mission + Everywhere: Spear od Adun autocasts are available in any mission of any race + """ + display_name = "Spear of Adun Autonomously Cast Powers Presence" + option_not_present = 0 + option_lotv_protoss = 1 + option_protoss = 2 + option_everywhere = 3 + default = option_lotv_protoss + + # Fix case + @classmethod + def get_option_name(cls, value: int) -> str: + if value == SpearOfAdunPresence.option_lotv_protoss: + return "LotV Protoss" + else: + return super().get_option_name(value) + + +class SpearOfAdunAutonomouslyCastPresentInNoBuild(Toggle): + """ + Determines if Spear of Adun autocasts are available in no-build missions. + + If turned on, Spear of Adun autocasts are available in missions specified under "Spear of Adun Autonomously Cast Powers Presence". + If turned off, Spear of Adun autocasts are unavailable in all no-build missions + """ + display_name = "Spear of Adun Autonomously Cast Powers Present in No-Build" + + +class GrantStoryTech(Toggle): + """ + If set true, grants special tech required for story mission completion for duration of the mission. + Otherwise, you need to find these tech by a normal means as items. + Affects story missions like Back in the Saddle and Supreme + + Locked to true if Required Tactics is set to no logic. + """ + display_name = "Grant Story Tech" + + +class GrantStoryLevels(Choice): + """ + If enabled, grants Kerrigan the required minimum levels for the following missions: + Supreme: 35 + The Infinite Cycle: 70 + The bonus levels only apply during the listed missions, and can exceed the Total Level Cap. + + If disabled, either of these missions is included, and there are not enough levels in the world, generation may fail. + To prevent this, either increase the amount of levels in the world, or enable this option. + + If disabled and Required Tactics is set to no logic, this option is forced to Minimum. + + Disabled: Kerrigan does not get bonus levels for these missions, + instead the levels must be gained from items or beating missions. + Additive: Kerrigan gains bonus levels equal to the mission's required level. + Minimum: Kerrigan is either at her real level, or at the mission's required level, + depending on which is higher. + """ + display_name = "Grant Story Levels" + option_disabled = 0 + option_additive = 1 + option_minimum = 2 + default = option_minimum + + +class TakeOverAIAllies(Toggle): + """ + On maps supporting this feature allows you to take control over an AI Ally. + """ + display_name = "Take Over AI Allies" + + +class LockedItems(ItemSet): + """Guarantees that these items will be unlockable""" + display_name = "Locked Items" + + +class ExcludedItems(ItemSet): + """Guarantees that these items will not be unlockable""" + display_name = "Excluded Items" + + +class ExcludedMissions(OptionSet): + """Guarantees that these missions will not appear in the campaign + Doesn't apply to vanilla mission order. + It may be impossible to build a valid campaign if too many missions are excluded.""" + display_name = "Excluded Missions" + valid_keys = {mission.mission_name for mission in SC2Mission} + + +class ExcludeVeryHardMissions(Choice): + """ + Excludes Very Hard missions outside of Epilogue campaign (All-In, Salvation, and all Epilogue missions are considered Very Hard). + Doesn't apply to "Vanilla" mission order. + + Default: Not excluded for mission orders "Vanilla Shuffled" or "Grid" with Maximum Campaign Size >= 20, + excluded for any other order + Yes: Non-Epilogue Very Hard missions are excluded and won't be generated + No: Non-Epilogue Very Hard missions can appear normally. Not recommended for too short mission orders. + + See also: Excluded Missions, Enable Epilogue Missions, Maximum Campaign Size + """ + display_name = "Exclude Very Hard Missions" + option_default = 0 + option_true = 1 + option_false = 2 + + @classmethod + def get_option_name(cls, value): + return ["Default", "Yes", "No"][int(value)] + + +class LocationInclusion(Choice): + option_enabled = 0 + option_resources = 1 + option_disabled = 2 + + +class VanillaLocations(LocationInclusion): + """ + Enables or disables item rewards for completing vanilla objectives. + Vanilla objectives are bonus objectives from the vanilla game, + along with some additional objectives to balance the missions. + Enable these locations for a balanced experience. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Vanilla Locations" + + +class ExtraLocations(LocationInclusion): + """ + Enables or disables item rewards for mission progress and minor objectives. + This includes mandatory mission objectives, + collecting reinforcements and resource pickups, + destroying structures, and overcoming minor challenges. + Enables these locations to add more checks and items to your world. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Extra Locations" + + +class ChallengeLocations(LocationInclusion): + """ + Enables or disables item rewards for completing challenge tasks. + Challenges are tasks that are more difficult than completing the mission, and are often based on achievements. + You might be required to visit the same mission later after getting stronger in order to finish these tasks. + Enable these locations to increase the difficulty of completing the multiworld. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Challenge Locations" + + +class MasteryLocations(LocationInclusion): + """ + Enables or disables item rewards for overcoming especially difficult challenges. + These challenges are often based on Mastery achievements and Feats of Strength. + Enable these locations to add the most difficult checks to the world. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Mastery Locations" + + +class MineralsPerItem(Range): + """ + Configures how many minerals are given per resource item. + """ + display_name = "Minerals Per Item" + range_start = 0 + range_end = 500 + default = 25 + + +class VespenePerItem(Range): + """ + Configures how much vespene gas is given per resource item. + """ + display_name = "Vespene Per Item" + range_start = 0 + range_end = 500 + default = 25 + + +class StartingSupplyPerItem(Range): + """ + Configures how much starting supply per is given per item. + """ + display_name = "Starting Supply Per Item" + range_start = 0 + range_end = 200 + default = 5 + + +@dataclass +class Starcraft2Options(PerGameCommonOptions): + game_difficulty: GameDifficulty + game_speed: GameSpeed + disable_forced_camera: DisableForcedCamera + skip_cutscenes: SkipCutscenes + all_in_map: AllInMap + mission_order: MissionOrder + maximum_campaign_size: MaximumCampaignSize + grid_two_start_positions: GridTwoStartPositions + player_color_terran_raynor: PlayerColorTerranRaynor + player_color_protoss: PlayerColorProtoss + player_color_zerg: PlayerColorZerg + player_color_zerg_primal: PlayerColorZergPrimal + enable_wol_missions: EnableWolMissions + enable_prophecy_missions: EnableProphecyMissions + enable_hots_missions: EnableHotsMissions + enable_lotv_prologue_missions: EnableLotVPrologueMissions + enable_lotv_missions: EnableLotVMissions + enable_epilogue_missions: EnableEpilogueMissions + enable_nco_missions: EnableNCOMissions + shuffle_campaigns: ShuffleCampaigns + shuffle_no_build: ShuffleNoBuild + starter_unit: StarterUnit + required_tactics: RequiredTactics + ensure_generic_items: EnsureGenericItems + min_number_of_upgrades: MinNumberOfUpgrades + max_number_of_upgrades: MaxNumberOfUpgrades + generic_upgrade_missions: GenericUpgradeMissions + generic_upgrade_research: GenericUpgradeResearch + generic_upgrade_items: GenericUpgradeItems + kerrigan_presence: KerriganPresence + kerrigan_levels_per_mission_completed: KerriganLevelsPerMissionCompleted + kerrigan_levels_per_mission_completed_cap: KerriganLevelsPerMissionCompletedCap + kerrigan_level_item_sum: KerriganLevelItemSum + kerrigan_level_item_distribution: KerriganLevelItemDistribution + kerrigan_total_level_cap: KerriganTotalLevelCap + start_primary_abilities: StartPrimaryAbilities + kerrigan_primal_status: KerriganPrimalStatus + spear_of_adun_presence: SpearOfAdunPresence + spear_of_adun_present_in_no_build: SpearOfAdunPresentInNoBuild + spear_of_adun_autonomously_cast_ability_presence: SpearOfAdunAutonomouslyCastAbilityPresence + spear_of_adun_autonomously_cast_present_in_no_build: SpearOfAdunAutonomouslyCastPresentInNoBuild + grant_story_tech: GrantStoryTech + grant_story_levels: GrantStoryLevels + take_over_ai_allies: TakeOverAIAllies + locked_items: LockedItems + excluded_items: ExcludedItems + excluded_missions: ExcludedMissions + exclude_very_hard_missions: ExcludeVeryHardMissions + nco_items: NovaCovertOpsItems + bw_items: BroodWarItems + ext_items: ExtendedItems + vanilla_locations: VanillaLocations + extra_locations: ExtraLocations + challenge_locations: ChallengeLocations + mastery_locations: MasteryLocations + minerals_per_item: MineralsPerItem + vespene_per_item: VespenePerItem + starting_supply_per_item: StartingSupplyPerItem + + +def get_option_value(world: World, name: str) -> Union[int, FrozenSet]: + if world is None: + field: Field = [class_field for class_field in fields(Starcraft2Options) if class_field.name == name][0] + return field.type.default + + player_option = getattr(world.options, name) + + return player_option.value + + +def get_enabled_campaigns(world: World) -> Set[SC2Campaign]: + enabled_campaigns = set() + if get_option_value(world, "enable_wol_missions"): + enabled_campaigns.add(SC2Campaign.WOL) + if get_option_value(world, "enable_prophecy_missions"): + enabled_campaigns.add(SC2Campaign.PROPHECY) + if get_option_value(world, "enable_hots_missions"): + enabled_campaigns.add(SC2Campaign.HOTS) + if get_option_value(world, "enable_lotv_prologue_missions"): + enabled_campaigns.add(SC2Campaign.PROLOGUE) + if get_option_value(world, "enable_lotv_missions"): + enabled_campaigns.add(SC2Campaign.LOTV) + if get_option_value(world, "enable_epilogue_missions"): + enabled_campaigns.add(SC2Campaign.EPILOGUE) + if get_option_value(world, "enable_nco_missions"): + enabled_campaigns.add(SC2Campaign.NCO) + return enabled_campaigns + + +def get_disabled_campaigns(world: World) -> Set[SC2Campaign]: + all_campaigns = set(SC2Campaign) + enabled_campaigns = get_enabled_campaigns(world) + disabled_campaigns = all_campaigns.difference(enabled_campaigns) + disabled_campaigns.remove(SC2Campaign.GLOBAL) + return disabled_campaigns + + +def get_excluded_missions(world: World) -> Set[SC2Mission]: + mission_order_type = get_option_value(world, "mission_order") + excluded_mission_names = get_option_value(world, "excluded_missions") + shuffle_no_build = get_option_value(world, "shuffle_no_build") + disabled_campaigns = get_disabled_campaigns(world) + + excluded_missions: Set[SC2Mission] = set([lookup_name_to_mission[name] for name in excluded_mission_names]) + + # Excluding Very Hard missions depending on options + if (get_option_value(world, "exclude_very_hard_missions") == ExcludeVeryHardMissions.option_true + ) or ( + get_option_value(world, "exclude_very_hard_missions") == ExcludeVeryHardMissions.option_default + and ( + mission_order_type not in [MissionOrder.option_vanilla_shuffled, MissionOrder.option_grid] + or ( + mission_order_type == MissionOrder.option_grid + and get_option_value(world, "maximum_campaign_size") < 20 + ) + ) + ): + excluded_missions = excluded_missions.union( + [mission for mission in SC2Mission if + mission.pool == MissionPools.VERY_HARD and mission.campaign != SC2Campaign.EPILOGUE] + ) + # Omitting No-Build missions if not shuffling no-build + if not shuffle_no_build: + excluded_missions = excluded_missions.union(get_no_build_missions()) + # Omitting missions not in enabled campaigns + for campaign in disabled_campaigns: + excluded_missions = excluded_missions.union(campaign_mission_table[campaign]) + + return excluded_missions + + +campaign_depending_orders = [ + MissionOrder.option_vanilla, + MissionOrder.option_vanilla_shuffled, + MissionOrder.option_mini_campaign +] + +kerrigan_unit_available = [ + KerriganPresence.option_vanilla, +] \ No newline at end of file diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py new file mode 100644 index 000000000000..f5f6faa96d62 --- /dev/null +++ b/worlds/sc2/PoolFilter.py @@ -0,0 +1,661 @@ +from typing import Callable, Dict, List, Set, Union, Tuple, Optional +from BaseClasses import Item, Location +from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, \ + progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment +from .MissionTables import mission_orders, MissionInfo, MissionPools, \ + get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, \ + SC2Campaign, SC2Race, SC2CampaignGoalPriority, SC2Mission +from .Options import get_option_value, MissionOrder, \ + get_enabled_campaigns, get_disabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, \ + TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, \ + ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels +from . import ItemNames +from worlds.AutoWorld import World + +# Items with associated upgrades +UPGRADABLE_ITEMS = {item.parent_item for item in get_full_item_list().values() if item.parent_item} + +BARRACKS_UNITS = { + ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, + ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, +} +FACTORY_UNITS = { + ItemNames.HELLION, ItemNames.VULTURE, ItemNames.GOLIATH, ItemNames.DIAMONDBACK, + ItemNames.SIEGE_TANK, ItemNames.THOR, ItemNames.PREDATOR, ItemNames.WIDOW_MINE, + ItemNames.CYCLONE, ItemNames.WARHOUND, +} +STARPORT_UNITS = { + ItemNames.MEDIVAC, ItemNames.WRAITH, ItemNames.VIKING, ItemNames.BANSHEE, + ItemNames.BATTLECRUISER, ItemNames.HERCULES, ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, + ItemNames.LIBERATOR, ItemNames.VALKYRIE, +} + + +def filter_missions(world: World) -> Dict[MissionPools, List[SC2Mission]]: + + """ + Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets + """ + world: World = world + mission_order_type = get_option_value(world, "mission_order") + shuffle_no_build = get_option_value(world, "shuffle_no_build") + enabled_campaigns = get_enabled_campaigns(world) + grant_story_tech = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true + grant_story_levels = get_option_value(world, "grant_story_levels") != GrantStoryLevels.option_disabled + extra_locations = get_option_value(world, "extra_locations") + excluded_missions: Set[SC2Mission] = get_excluded_missions(world) + mission_pools: Dict[MissionPools, List[SC2Mission]] = {} + for mission in SC2Mission: + if not mission_pools.get(mission.pool): + mission_pools[mission.pool] = list() + mission_pools[mission.pool].append(mission) + # A bit of safeguard: + for mission_pool in MissionPools: + if not mission_pools.get(mission_pool): + mission_pools[mission_pool] = [] + + if mission_order_type == MissionOrder.option_vanilla: + # Vanilla uses the entire mission pool + goal_priorities: Dict[SC2Campaign, SC2CampaignGoalPriority] = {campaign: get_campaign_goal_priority(campaign) for campaign in enabled_campaigns} + goal_level = max(goal_priorities.values()) + candidate_campaigns: List[SC2Campaign] = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level] + candidate_campaigns.sort(key=lambda it: it.id) + goal_campaign = world.random.choice(candidate_campaigns) + if campaign_final_mission_locations[goal_campaign] is not None: + mission_pools[MissionPools.FINAL] = [campaign_final_mission_locations[goal_campaign].mission] + else: + mission_pools[MissionPools.FINAL] = [list(campaign_alt_final_mission_locations[goal_campaign].keys())[0]] + remove_final_mission_from_other_pools(mission_pools) + return mission_pools + + # Finding the goal map + goal_mission: Optional[SC2Mission] = None + if mission_order_type in campaign_depending_orders: + # Prefer long campaigns over shorter ones and harder missions over easier ones + goal_priorities = {campaign: get_campaign_goal_priority(campaign, excluded_missions) for campaign in enabled_campaigns} + goal_level = max(goal_priorities.values()) + candidate_campaigns: List[SC2Campaign] = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level] + candidate_campaigns.sort(key=lambda it: it.id) + + goal_campaign = world.random.choice(candidate_campaigns) + primary_goal = campaign_final_mission_locations[goal_campaign] + if primary_goal is None or primary_goal.mission in excluded_missions: + # No primary goal or its mission is excluded + candidate_missions = list(campaign_alt_final_mission_locations[goal_campaign].keys()) + candidate_missions = [mission for mission in candidate_missions if mission not in excluded_missions] + if len(candidate_missions) == 0: + raise Exception("There are no valid goal missions. Please exclude fewer missions.") + goal_mission = world.random.choice(candidate_missions) + else: + goal_mission = primary_goal.mission + else: + # Find one of the missions with the hardest difficulty + available_missions: List[SC2Mission] = \ + [mission for mission in SC2Mission + if (mission not in excluded_missions and mission.campaign in enabled_campaigns)] + available_missions.sort(key=lambda it: it.id) + # Loop over pools, from hardest to easiest + for mission_pool in range(MissionPools.VERY_HARD, MissionPools.STARTER - 1, -1): + pool_missions: List[SC2Mission] = [mission for mission in available_missions if mission.pool == mission_pool] + if pool_missions: + goal_mission = world.random.choice(pool_missions) + break + if goal_mission is None: + raise Exception("There are no valid goal missions. Please exclude fewer missions.") + + # Excluding missions + for difficulty, mission_pool in mission_pools.items(): + mission_pools[difficulty] = [mission for mission in mission_pool if mission not in excluded_missions] + mission_pools[MissionPools.FINAL] = [goal_mission] + + # Mission pool changes + adv_tactics = get_option_value(world, "required_tactics") != RequiredTactics.option_standard + + def move_mission(mission: SC2Mission, current_pool, new_pool): + if mission in mission_pools[current_pool]: + mission_pools[current_pool].remove(mission) + mission_pools[new_pool].append(mission) + # WoL + if shuffle_no_build == ShuffleNoBuild.option_false or adv_tactics: + # Replacing No Build missions with Easy missions + # WoL + move_mission(SC2Mission.ZERO_HOUR, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.EVACUATION, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.DEVILS_PLAYGROUND, MissionPools.EASY, MissionPools.STARTER) + # LotV + move_mission(SC2Mission.THE_GROWING_SHADOW, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.THE_SPEAR_OF_ADUN, MissionPools.EASY, MissionPools.STARTER) + if extra_locations == ExtraLocations.option_enabled: + move_mission(SC2Mission.SKY_SHIELD, MissionPools.EASY, MissionPools.STARTER) + # Pushing this to Easy + move_mission(SC2Mission.THE_GREAT_TRAIN_ROBBERY, MissionPools.MEDIUM, MissionPools.EASY) + if shuffle_no_build == ShuffleNoBuild.option_false: + # Pushing Outbreak to Normal, as it cannot be placed as the second mission on Build-Only + move_mission(SC2Mission.OUTBREAK, MissionPools.EASY, MissionPools.MEDIUM) + # Pushing extra Normal missions to Easy + move_mission(SC2Mission.ECHOES_OF_THE_FUTURE, MissionPools.MEDIUM, MissionPools.EASY) + move_mission(SC2Mission.CUTTHROAT, MissionPools.MEDIUM, MissionPools.EASY) + # Additional changes on Advanced Tactics + if adv_tactics: + # WoL + move_mission(SC2Mission.THE_GREAT_TRAIN_ROBBERY, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.SMASH_AND_GRAB, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.THE_MOEBIUS_FACTOR, MissionPools.MEDIUM, MissionPools.EASY) + move_mission(SC2Mission.WELCOME_TO_THE_JUNGLE, MissionPools.MEDIUM, MissionPools.EASY) + move_mission(SC2Mission.ENGINE_OF_DESTRUCTION, MissionPools.HARD, MissionPools.MEDIUM) + # LotV + move_mission(SC2Mission.AMON_S_REACH, MissionPools.EASY, MissionPools.STARTER) + # Prophecy needs to be adjusted on tiny grid + if enabled_campaigns == {SC2Campaign.PROPHECY} and mission_order_type == MissionOrder.option_tiny_grid: + move_mission(SC2Mission.A_SINISTER_TURN, MissionPools.MEDIUM, MissionPools.EASY) + # Prologue's only valid starter is the goal mission + if enabled_campaigns == {SC2Campaign.PROLOGUE} \ + or mission_order_type in campaign_depending_orders \ + and get_option_value(world, "shuffle_campaigns") == ShuffleCampaigns.option_false: + move_mission(SC2Mission.DARK_WHISPERS, MissionPools.EASY, MissionPools.STARTER) + # HotS + kerriganless = get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ + or SC2Campaign.HOTS not in enabled_campaigns + if adv_tactics: + # Medium -> Easy + for mission in (SC2Mission.FIRE_IN_THE_SKY, SC2Mission.WAKING_THE_ANCIENT, SC2Mission.CONVICTION): + move_mission(mission, MissionPools.MEDIUM, MissionPools.EASY) + # Hard -> Medium + move_mission(SC2Mission.PHANTOMS_OF_THE_VOID, MissionPools.HARD, MissionPools.MEDIUM) + if not kerriganless: + # Additional starter mission assuming player starts with minimal anti-air + move_mission(SC2Mission.WAKING_THE_ANCIENT, MissionPools.EASY, MissionPools.STARTER) + if grant_story_tech: + # Additional starter mission if player is granted story tech + move_mission(SC2Mission.ENEMY_WITHIN, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.TEMPLAR_S_RETURN, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.THE_ESCAPE, MissionPools.MEDIUM, MissionPools.STARTER) + move_mission(SC2Mission.IN_THE_ENEMY_S_SHADOW, MissionPools.MEDIUM, MissionPools.STARTER) + if (grant_story_tech and grant_story_levels) or kerriganless: + # The player has, all the stuff he needs, provided under these settings + move_mission(SC2Mission.SUPREME, MissionPools.MEDIUM, MissionPools.STARTER) + move_mission(SC2Mission.THE_INFINITE_CYCLE, MissionPools.HARD, MissionPools.STARTER) + if get_option_value(world, "take_over_ai_allies") == TakeOverAIAllies.option_true: + move_mission(SC2Mission.HARBINGER_OF_OBLIVION, MissionPools.MEDIUM, MissionPools.STARTER) + if len(mission_pools[MissionPools.STARTER]) < 2 and not kerriganless or adv_tactics: + # Conditionally moving Easy missions to Starter + move_mission(SC2Mission.HARVEST_OF_SCREAMS, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.DOMINATION, MissionPools.EASY, MissionPools.STARTER) + if len(mission_pools[MissionPools.STARTER]) < 2: + move_mission(SC2Mission.TEMPLAR_S_RETURN, MissionPools.EASY, MissionPools.STARTER) + if len(mission_pools[MissionPools.STARTER]) + len(mission_pools[MissionPools.EASY]) < 2: + # Flashpoint needs just a few items at start but competent comp at the end + move_mission(SC2Mission.FLASHPOINT, MissionPools.HARD, MissionPools.EASY) + + remove_final_mission_from_other_pools(mission_pools) + return mission_pools + + +def remove_final_mission_from_other_pools(mission_pools: Dict[MissionPools, List[SC2Mission]]): + final_missions = mission_pools[MissionPools.FINAL] + for pool, missions in mission_pools.items(): + if pool == MissionPools.FINAL: + continue + for final_mission in final_missions: + while final_mission in missions: + missions.remove(final_mission) + + +def get_item_upgrades(inventory: List[Item], parent_item: Union[Item, str]) -> List[Item]: + item_name = parent_item.name if isinstance(parent_item, Item) else parent_item + return [ + inv_item for inv_item in inventory + if get_full_item_list()[inv_item.name].parent_item == item_name + ] + + +def get_item_quantity(item: Item, world: World): + if (not get_option_value(world, "nco_items")) \ + and SC2Campaign.NCO in get_disabled_campaigns(world) \ + and item.name in progressive_if_nco: + return 1 + if (not get_option_value(world, "ext_items")) \ + and item.name in progressive_if_ext: + return 1 + return get_full_item_list()[item.name].quantity + + +def copy_item(item: Item): + return Item(item.name, item.classification, item.code, item.player) + + +def num_missions(world: World) -> int: + mission_order_type = get_option_value(world, "mission_order") + if mission_order_type != MissionOrder.option_grid: + mission_order = mission_orders[mission_order_type]() + misssions = [mission for campaign in mission_order for mission in mission_order[campaign]] + return len(misssions) - 1 # Menu + else: + mission_pools = filter_missions(world) + return sum(len(pool) for _, pool in mission_pools.items()) + + +class ValidInventory: + + def has(self, item: str, player: int): + return item in self.logical_inventory + + def has_any(self, items: Set[str], player: int): + return any(item in self.logical_inventory for item in items) + + def has_all(self, items: Set[str], player: int): + return all(item in self.logical_inventory for item in items) + + def has_group(self, item_group: str, player: int, count: int = 1): + return False # Deliberately fails here, as item pooling is not aware about mission layout + + def count_group(self, item_name_group: str, player: int) -> int: + return 0 # For item filtering assume no missions are beaten + + def count(self, item: str, player: int) -> int: + return len([inventory_item for inventory_item in self.logical_inventory if inventory_item == item]) + + def has_units_per_structure(self) -> bool: + return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ + len(FACTORY_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ + len(STARPORT_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure + + def generate_reduced_inventory(self, inventory_size: int, mission_requirements: List[Tuple[str, Callable]]) -> List[Item]: + """Attempts to generate a reduced inventory that can fulfill the mission requirements.""" + inventory: List[Item] = list(self.item_pool) + locked_items: List[Item] = list(self.locked_items) + item_list = get_full_item_list() + self.logical_inventory = [ + item.name for item in inventory + locked_items + self.existing_items + if item_list[item.name].is_important_for_filtering() # Track all Progression items and those with complex rules for filtering + ] + requirements = mission_requirements + parent_items = self.item_children.keys() + parent_lookup = {child: parent for parent, children in self.item_children.items() for child in children} + minimum_upgrades = get_option_value(self.world, "min_number_of_upgrades") + + def attempt_removal(item: Item) -> bool: + inventory.remove(item) + # Only run logic checks when removing logic items + if item.name in self.logical_inventory: + self.logical_inventory.remove(item.name) + if not all(requirement(self) for (_, requirement) in mission_requirements): + # If item cannot be removed, lock or revert + self.logical_inventory.append(item.name) + for _ in range(get_item_quantity(item, self.world)): + locked_items.append(copy_item(item)) + return False + return True + + # Limit the maximum number of upgrades + maxNbUpgrade = get_option_value(self.world, "max_number_of_upgrades") + if maxNbUpgrade != -1: + unit_avail_upgrades = {} + # Needed to take into account locked/existing items + unit_nb_upgrades = {} + for item in inventory: + cItem = item_list[item.name] + if item.name in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: + unit_avail_upgrades[item.name] = [] + unit_nb_upgrades[item.name] = 0 + elif cItem.parent_item is not None: + if cItem.parent_item not in unit_avail_upgrades: + unit_avail_upgrades[cItem.parent_item] = [item] + unit_nb_upgrades[cItem.parent_item] = 1 + else: + unit_avail_upgrades[cItem.parent_item].append(item) + unit_nb_upgrades[cItem.parent_item] += 1 + # For those two categories, we count them but dont include them in removal + for item in locked_items + self.existing_items: + cItem = item_list[item.name] + if item.name in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: + unit_avail_upgrades[item.name] = [] + unit_nb_upgrades[item.name] = 0 + elif cItem.parent_item is not None: + if cItem.parent_item not in unit_avail_upgrades: + unit_nb_upgrades[cItem.parent_item] = 1 + else: + unit_nb_upgrades[cItem.parent_item] += 1 + # Making sure that the upgrades being removed is random + shuffled_unit_upgrade_list = list(unit_avail_upgrades.keys()) + self.world.random.shuffle(shuffled_unit_upgrade_list) + for unit in shuffled_unit_upgrade_list: + while (unit_nb_upgrades[unit] > maxNbUpgrade) \ + and (len(unit_avail_upgrades[unit]) > 0): + itemCandidate = self.world.random.choice(unit_avail_upgrades[unit]) + success = attempt_removal(itemCandidate) + # Whatever it succeed to remove the iventory or it fails and thus + # lock it, the upgrade is no longer available for removal + unit_avail_upgrades[unit].remove(itemCandidate) + if success: + unit_nb_upgrades[unit] -= 1 + + # Locking minimum upgrades for items that have already been locked/placed when minimum required + if minimum_upgrades > 0: + known_items = self.existing_items + locked_items + known_parents = [item for item in known_items if item in parent_items] + for parent in known_parents: + child_items = self.item_children[parent] + removable_upgrades = [item for item in inventory if item in child_items] + locked_upgrade_count = sum(1 if item in child_items else 0 for item in known_items) + self.world.random.shuffle(removable_upgrades) + while len(removable_upgrades) > 0 and locked_upgrade_count < minimum_upgrades: + item_to_lock = removable_upgrades.pop() + inventory.remove(item_to_lock) + locked_items.append(copy_item(item_to_lock)) + locked_upgrade_count += 1 + + if self.min_units_per_structure > 0 and self.has_units_per_structure(): + requirements.append(("Minimum units per structure", lambda state: state.has_units_per_structure())) + + # Determining if the full-size inventory can complete campaign + failed_locations: List[str] = [location for (location, requirement) in requirements if not requirement(self)] + if len(failed_locations) > 0: + raise Exception(f"Too many items excluded - couldn't satisfy access rules for the following locations:\n{failed_locations}") + + # Optionally locking generic items + generic_items = [item for item in inventory if item.name in second_pass_placeable_items] + reserved_generic_percent = get_option_value(self.world, "ensure_generic_items") / 100 + reserved_generic_amount = int(len(generic_items) * reserved_generic_percent) + removable_generic_items = [] + self.world.random.shuffle(generic_items) + for item in generic_items[:reserved_generic_amount]: + locked_items.append(copy_item(item)) + inventory.remove(item) + if item.name not in self.logical_inventory and item.name not in self.locked_items: + removable_generic_items.append(item) + + # Main cull process + unused_items: List[str] = [] # Reusable items for the second pass + while len(inventory) + len(locked_items) > inventory_size: + if len(inventory) == 0: + # There are more items than locations and all of them are already locked due to YAML or logic. + # First, drop non-logic generic items to free up space + while len(removable_generic_items) > 0 and len(locked_items) > inventory_size: + removed_item = removable_generic_items.pop() + locked_items.remove(removed_item) + # If there still isn't enough space, push locked items into start inventory + self.world.random.shuffle(locked_items) + while len(locked_items) > inventory_size: + item: Item = locked_items.pop() + self.multiworld.push_precollected(item) + break + # Select random item from removable items + item = self.world.random.choice(inventory) + # Do not remove item if it would drop upgrades below minimum + if minimum_upgrades > 0: + parent_item = parent_lookup.get(item, None) + if parent_item: + count = sum(1 if item in self.item_children[parent_item] else 0 for item in inventory + locked_items) + if count <= minimum_upgrades: + if parent_item in inventory: + # Attempt to remove parent instead, if possible + item = parent_item + else: + # Lock remaining upgrades + for item in self.item_children[parent_item]: + if item in inventory: + inventory.remove(item) + locked_items.append(copy_item(item)) + continue + + # Drop child items when removing a parent + if item in parent_items: + items_to_remove = [item for item in self.item_children[item] if item in inventory] + success = attempt_removal(item) + if success: + while len(items_to_remove) > 0: + item_to_remove = items_to_remove.pop() + if item_to_remove not in inventory: + continue + attempt_removal(item_to_remove) + else: + # Unimportant upgrades may be added again in the second pass + if attempt_removal(item): + unused_items.append(item.name) + + pool_items: List[str] = [item.name for item in (inventory + locked_items + self.existing_items)] + unused_items = [ + unused_item for unused_item in unused_items + if item_list[unused_item].parent_item is None + or item_list[unused_item].parent_item in pool_items + ] + + # Removing extra dependencies + # WoL + logical_inventory_set = set(self.logical_inventory) + if not spider_mine_sources & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Spider Mine)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Spider Mine)")] + if not BARRACKS_UNITS & logical_inventory_set: + inventory = [ + item for item in inventory + if not (item.name.startswith(ItemNames.TERRAN_INFANTRY_UPGRADE_PREFIX) + or item.name == ItemNames.ORBITAL_STRIKE)] + unused_items = [ + item_name for item_name in unused_items + if not (item_name.startswith( + ItemNames.TERRAN_INFANTRY_UPGRADE_PREFIX) + or item_name == ItemNames.ORBITAL_STRIKE)] + if not FACTORY_UNITS & logical_inventory_set: + inventory = [item for item in inventory if not item.name.startswith(ItemNames.TERRAN_VEHICLE_UPGRADE_PREFIX)] + unused_items = [item_name for item_name in unused_items if not item_name.startswith(ItemNames.TERRAN_VEHICLE_UPGRADE_PREFIX)] + if not STARPORT_UNITS & logical_inventory_set: + inventory = [item for item in inventory if not item.name.startswith(ItemNames.TERRAN_SHIP_UPGRADE_PREFIX)] + unused_items = [item_name for item_name in unused_items if not item_name.startswith(ItemNames.TERRAN_SHIP_UPGRADE_PREFIX)] + # HotS + # Baneling without sources => remove Baneling and upgrades + if (ItemNames.ZERGLING_BANELING_ASPECT in self.logical_inventory + and ItemNames.ZERGLING not in self.logical_inventory + and ItemNames.KERRIGAN_SPAWN_BANELINGS not in self.logical_inventory + ): + inventory = [item for item in inventory if item.name != ItemNames.ZERGLING_BANELING_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.ZERGLING_BANELING_ASPECT] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.ZERGLING_BANELING_ASPECT] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.ZERGLING_BANELING_ASPECT] + # Spawn Banelings without Zergling => remove Baneling unit, keep upgrades except macro ones + if (ItemNames.ZERGLING_BANELING_ASPECT in self.logical_inventory + and ItemNames.ZERGLING not in self.logical_inventory + and ItemNames.KERRIGAN_SPAWN_BANELINGS in self.logical_inventory + ): + inventory = [item for item in inventory if item.name != ItemNames.ZERGLING_BANELING_ASPECT] + inventory = [item for item in inventory if item.name != ItemNames.BANELING_RAPID_METAMORPH] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.ZERGLING_BANELING_ASPECT] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.BANELING_RAPID_METAMORPH] + if not {ItemNames.MUTALISK, ItemNames.CORRUPTOR, ItemNames.SCOURGE} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.startswith(ItemNames.ZERG_FLYER_UPGRADE_PREFIX)] + locked_items = [item for item in locked_items if not item.name.startswith(ItemNames.ZERG_FLYER_UPGRADE_PREFIX)] + unused_items = [item_name for item_name in unused_items if not item_name.startswith(ItemNames.ZERG_FLYER_UPGRADE_PREFIX)] + # T3 items removal rules - remove morph and its upgrades if the basic unit isn't in + if not {ItemNames.MUTALISK, ItemNames.CORRUPTOR} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Mutalisk/Corruptor)")] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Mutalisk/Corruptor)")] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT] + if ItemNames.ROACH not in logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.ROACH_RAVAGER_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.ROACH_RAVAGER_ASPECT] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.ROACH_RAVAGER_ASPECT] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.ROACH_RAVAGER_ASPECT] + if ItemNames.HYDRALISK not in logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Hydralisk)")] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.HYDRALISK_LURKER_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.HYDRALISK_IMPALER_ASPECT] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Hydralisk)")] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.HYDRALISK_LURKER_ASPECT] + unused_items = [item_name for item_name in unused_items if item_list[item_name].parent_item != ItemNames.HYDRALISK_IMPALER_ASPECT] + # LotV + # Shared unit upgrades between several units + if not {ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Stalker/Instigator/Slayer)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Stalker/Instigator/Slayer)")] + if not {ItemNames.PHOENIX, ItemNames.MIRAGE} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Phoenix/Mirage)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Phoenix/Mirage)")] + if not {ItemNames.VOID_RAY, ItemNames.DESTROYER} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Void Ray/Destroyer)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Void Ray/Destroyer)")] + if not {ItemNames.IMMORTAL, ItemNames.ANNIHILATOR} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Immortal/Annihilator)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Immortal/Annihilator)")] + if not {ItemNames.DARK_TEMPLAR, ItemNames.AVENGER, ItemNames.BLOOD_HUNTER} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Dark Templar/Avenger/Blood Hunter)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Dark Templar/Avenger/Blood Hunter)")] + if not {ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ASCENDANT, ItemNames.DARK_TEMPLAR} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Archon)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Archon)")] + logical_inventory_set.difference_update([item_name for item_name in logical_inventory_set if item_name.endswith("(Archon)")]) + if not {ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ARCHON_HIGH_ARCHON} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(High Templar/Signifier)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(High Templar/Signifier)")] + if ItemNames.SUPPLICANT not in logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.ASCENDANT_POWER_OVERWHELMING] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.ASCENDANT_POWER_OVERWHELMING] + if not {ItemNames.DARK_ARCHON, ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Dark Archon)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Dark Archon)")] + if not {ItemNames.SENTRY, ItemNames.ENERGIZER, ItemNames.HAVOC} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Sentry/Energizer/Havoc)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Sentry/Energizer/Havoc)")] + if not {ItemNames.SENTRY, ItemNames.ENERGIZER, ItemNames.HAVOC, ItemNames.SHIELD_BATTERY} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Sentry/Energizer/Havoc/Shield Battery)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Sentry/Energizer/Havoc/Shield Battery)")] + if not {ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Zealot/Sentinel/Centurion)")] + unused_items = [item_name for item_name in unused_items if not item_name.endswith("(Zealot/Sentinel/Centurion)")] + # Static defense upgrades only if static defense present + if not {ItemNames.PHOTON_CANNON, ItemNames.KHAYDARIN_MONOLITH, ItemNames.NEXUS_OVERCHARGE, ItemNames.SHIELD_BATTERY} & logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.ENHANCED_TARGETING] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.ENHANCED_TARGETING] + if not {ItemNames.PHOTON_CANNON, ItemNames.KHAYDARIN_MONOLITH, ItemNames.NEXUS_OVERCHARGE} & logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.OPTIMIZED_ORDNANCE] + unused_items = [item_name for item_name in unused_items if item_name != ItemNames.OPTIMIZED_ORDNANCE] + + # Cull finished, adding locked items back into inventory + inventory += locked_items + + # Replacing empty space with generically useful items + replacement_items = [item for item in self.item_pool + if (item not in inventory + and item not in self.locked_items + and ( + item.name in second_pass_placeable_items + or item.name in unused_items))] + self.world.random.shuffle(replacement_items) + while len(inventory) < inventory_size and len(replacement_items) > 0: + item = replacement_items.pop() + inventory.append(item) + + return inventory + + def __init__(self, world: World , + item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], + used_races: Set[SC2Race], nova_equipment_used: bool): + self.multiworld = world.multiworld + self.player = world.player + self.world: World = world + self.logical_inventory = list() + self.locked_items = locked_items[:] + self.existing_items = existing_items + soa_presence = get_option_value(world, "spear_of_adun_presence") + soa_autocast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") + # Initial filter of item pool + self.item_pool = [] + item_quantities: dict[str, int] = dict() + # Inventory restrictiveness based on number of missions with checks + mission_count = num_missions(world) + self.min_units_per_structure = int(mission_count / 7) + min_upgrades = 1 if mission_count < 10 else 2 + for item in item_pool: + item_info = get_full_item_list()[item.name] + if item_info.race != SC2Race.ANY and item_info.race not in used_races: + if soa_presence == SpearOfAdunPresence.option_everywhere \ + and item.name in spear_of_adun_calldowns: + # Add SoA powers regardless of used races as it's present everywhere + self.item_pool.append(item) + if soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere \ + and item.name in spear_of_adun_castable_passives: + self.item_pool.append(item) + # Drop any item belonging to a race not used in the campaign + continue + if item.name in nova_equipment and not nova_equipment_used: + # Drop Nova equipment if there's no NCO mission generated + continue + if item_info.type == "Upgrade": + # Locking upgrades based on mission duration + if item.name not in item_quantities: + item_quantities[item.name] = 0 + item_quantities[item.name] += 1 + if item_quantities[item.name] <= min_upgrades: + self.locked_items.append(item) + else: + self.item_pool.append(item) + elif item_info.type == "Goal": + self.locked_items.append(item) + else: + self.item_pool.append(item) + self.item_children: Dict[Item, List[Item]] = dict() + for item in self.item_pool + locked_items + existing_items: + if item.name in UPGRADABLE_ITEMS: + self.item_children[item] = get_item_upgrades(self.item_pool, item) + + +def filter_items(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], location_cache: List[Location], + item_pool: List[Item], existing_items: List[Item], locked_items: List[Item]) -> List[Item]: + """ + Returns a semi-randomly pruned set of items based on number of available locations. + The returned inventory must be capable of logically accessing every location in the world. + """ + open_locations = [location for location in location_cache if location.item is None] + inventory_size = len(open_locations) + used_races = get_used_races(mission_req_table, world) + nova_equipment_used = is_nova_equipment_used(mission_req_table) + mission_requirements = [(location.name, location.access_rule) for location in location_cache] + valid_inventory = ValidInventory(world, item_pool, existing_items, locked_items, used_races, nova_equipment_used) + + valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements) + return valid_items + + +def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], world: World) -> Set[SC2Race]: + grant_story_tech = get_option_value(world, "grant_story_tech") + take_over_ai_allies = get_option_value(world, "take_over_ai_allies") + kerrigan_presence = get_option_value(world, "kerrigan_presence") in kerrigan_unit_available \ + and SC2Campaign.HOTS in get_enabled_campaigns(world) + missions = missions_in_mission_table(mission_req_table) + + # By missions + races = set([mission.race for mission in missions]) + + # Conditionally logic-less no-builds (They're set to SC2Race.ANY): + if grant_story_tech == GrantStoryTech.option_false: + if SC2Mission.ENEMY_WITHIN in missions: + # Zerg units need to be unlocked + races.add(SC2Race.ZERG) + if kerrigan_presence \ + and not missions.isdisjoint({SC2Mission.BACK_IN_THE_SADDLE, SC2Mission.SUPREME, SC2Mission.CONVICTION, SC2Mission.THE_INFINITE_CYCLE}): + # You need some Kerrigan abilities (they're granted if Kerriganless or story tech granted) + races.add(SC2Race.ZERG) + + # If you take over the AI Ally, you need to have its race stuff + if take_over_ai_allies == TakeOverAIAllies.option_true \ + and not missions.isdisjoint({SC2Mission.THE_RECKONING}): + # Jimmy in The Reckoning + races.add(SC2Race.TERRAN) + + return races + +def is_nova_equipment_used(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> bool: + missions = missions_in_mission_table(mission_req_table) + return any([mission.campaign == SC2Campaign.NCO for mission in missions]) + + +def missions_in_mission_table(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> Set[SC2Mission]: + return set([mission.mission for campaign_missions in mission_req_table.values() for mission in + campaign_missions.values()]) diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py new file mode 100644 index 000000000000..84830a9a32bd --- /dev/null +++ b/worlds/sc2/Regions.py @@ -0,0 +1,691 @@ +from typing import List, Dict, Tuple, Optional, Callable, NamedTuple, Union +import math + +from BaseClasses import MultiWorld, Region, Entrance, Location, CollectionState +from .Locations import LocationData +from .Options import get_option_value, MissionOrder, get_enabled_campaigns, campaign_depending_orders, \ + GridTwoStartPositions +from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, \ + MissionPools, SC2Campaign, get_goal_location, SC2Mission, MissionConnection +from .PoolFilter import filter_missions +from worlds.AutoWorld import World + + +class SC2MissionSlot(NamedTuple): + campaign: SC2Campaign + slot: Union[MissionPools, SC2Mission, None] + + +def create_regions( + world: World, locations: Tuple[LocationData, ...], location_cache: List[Location] +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + """ + Creates region connections by calling the multiworld's `connect()` methods + Returns a 3-tuple containing: + * dict[SC2Campaign, Dict[str, MissionInfo]] mapping a campaign and mission name to its data + * int The number of missions in the world + * str The name of the goal location + """ + mission_order_type: int = get_option_value(world, "mission_order") + + if mission_order_type == MissionOrder.option_vanilla: + return create_vanilla_regions(world, locations, location_cache) + elif mission_order_type == MissionOrder.option_grid: + return create_grid_regions(world, locations, location_cache) + else: + return create_structured_regions(world, locations, location_cache, mission_order_type) + +def create_vanilla_regions( + world: World, + locations: Tuple[LocationData, ...], + location_cache: List[Location], +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + locations_per_region = get_locations_per_region(locations) + regions = [create_region(world, locations_per_region, location_cache, "Menu")] + + mission_pools: Dict[MissionPools, List[SC2Mission]] = filter_missions(world) + final_mission = mission_pools[MissionPools.FINAL][0] + + enabled_campaigns = get_enabled_campaigns(world) + names: Dict[str, int] = {} + + # Generating all regions and locations for each enabled campaign + for campaign in enabled_campaigns: + for region_name in vanilla_mission_req_table[campaign].keys(): + regions.append(create_region(world, locations_per_region, location_cache, region_name)) + world.multiworld.regions += regions + vanilla_mission_reqs = {campaign: missions for campaign, missions in vanilla_mission_req_table.items() if campaign in enabled_campaigns} + + def wol_cleared_missions(state: CollectionState, mission_count: int) -> bool: + return state.has_group("WoL Missions", world.player, mission_count) + + player: int = world.player + if SC2Campaign.WOL in enabled_campaigns: + connect(world, names, 'Menu', 'Liberation Day') + connect(world, names, 'Liberation Day', 'The Outlaws', + lambda state: state.has("Beat Liberation Day", player)) + connect(world, names, 'The Outlaws', 'Zero Hour', + lambda state: state.has("Beat The Outlaws", player)) + connect(world, names, 'Zero Hour', 'Evacuation', + lambda state: state.has("Beat Zero Hour", player)) + connect(world, names, 'Evacuation', 'Outbreak', + lambda state: state.has("Beat Evacuation", player)) + connect(world, names, "Outbreak", "Safe Haven", + lambda state: wol_cleared_missions(state, 7) and state.has("Beat Outbreak", player)) + connect(world, names, "Outbreak", "Haven's Fall", + lambda state: wol_cleared_missions(state, 7) and state.has("Beat Outbreak", player)) + connect(world, names, 'Zero Hour', 'Smash and Grab', + lambda state: state.has("Beat Zero Hour", player)) + connect(world, names, 'Smash and Grab', 'The Dig', + lambda state: wol_cleared_missions(state, 8) and state.has("Beat Smash and Grab", player)) + connect(world, names, 'The Dig', 'The Moebius Factor', + lambda state: wol_cleared_missions(state, 11) and state.has("Beat The Dig", player)) + connect(world, names, 'The Moebius Factor', 'Supernova', + lambda state: wol_cleared_missions(state, 14) and state.has("Beat The Moebius Factor", player)) + connect(world, names, 'Supernova', 'Maw of the Void', + lambda state: state.has("Beat Supernova", player)) + connect(world, names, 'Zero Hour', "Devil's Playground", + lambda state: wol_cleared_missions(state, 4) and state.has("Beat Zero Hour", player)) + connect(world, names, "Devil's Playground", 'Welcome to the Jungle', + lambda state: state.has("Beat Devil's Playground", player)) + connect(world, names, "Welcome to the Jungle", 'Breakout', + lambda state: wol_cleared_missions(state, 8) and state.has("Beat Welcome to the Jungle", player)) + connect(world, names, "Welcome to the Jungle", 'Ghost of a Chance', + lambda state: wol_cleared_missions(state, 8) and state.has("Beat Welcome to the Jungle", player)) + connect(world, names, "Zero Hour", 'The Great Train Robbery', + lambda state: wol_cleared_missions(state, 6) and state.has("Beat Zero Hour", player)) + connect(world, names, 'The Great Train Robbery', 'Cutthroat', + lambda state: state.has("Beat The Great Train Robbery", player)) + connect(world, names, 'Cutthroat', 'Engine of Destruction', + lambda state: state.has("Beat Cutthroat", player)) + connect(world, names, 'Engine of Destruction', 'Media Blitz', + lambda state: state.has("Beat Engine of Destruction", player)) + connect(world, names, 'Media Blitz', 'Piercing the Shroud', + lambda state: state.has("Beat Media Blitz", player)) + connect(world, names, 'Maw of the Void', 'Gates of Hell', + lambda state: state.has("Beat Maw of the Void", player)) + connect(world, names, 'Gates of Hell', 'Belly of the Beast', + lambda state: state.has("Beat Gates of Hell", player)) + connect(world, names, 'Gates of Hell', 'Shatter the Sky', + lambda state: state.has("Beat Gates of Hell", player)) + connect(world, names, 'Gates of Hell', 'All-In', + lambda state: state.has('Beat Gates of Hell', player) and ( + state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player))) + + if SC2Campaign.PROPHECY in enabled_campaigns: + if SC2Campaign.WOL in enabled_campaigns: + connect(world, names, 'The Dig', 'Whispers of Doom', + lambda state: state.has("Beat The Dig", player)), + else: + vanilla_mission_reqs[SC2Campaign.PROPHECY] = vanilla_mission_reqs[SC2Campaign.PROPHECY].copy() + vanilla_mission_reqs[SC2Campaign.PROPHECY][SC2Mission.WHISPERS_OF_DOOM.mission_name] = MissionInfo( + SC2Mission.WHISPERS_OF_DOOM, [], SC2Mission.WHISPERS_OF_DOOM.area) + connect(world, names, 'Menu', 'Whispers of Doom'), + connect(world, names, 'Whispers of Doom', 'A Sinister Turn', + lambda state: state.has("Beat Whispers of Doom", player)) + connect(world, names, 'A Sinister Turn', 'Echoes of the Future', + lambda state: state.has("Beat A Sinister Turn", player)) + connect(world, names, 'Echoes of the Future', 'In Utter Darkness', + lambda state: state.has("Beat Echoes of the Future", player)) + + if SC2Campaign.HOTS in enabled_campaigns: + connect(world, names, 'Menu', 'Lab Rat'), + connect(world, names, 'Lab Rat', 'Back in the Saddle', + lambda state: state.has("Beat Lab Rat", player)), + connect(world, names, 'Back in the Saddle', 'Rendezvous', + lambda state: state.has("Beat Back in the Saddle", player)), + connect(world, names, 'Rendezvous', 'Harvest of Screams', + lambda state: state.has("Beat Rendezvous", player)), + connect(world, names, 'Harvest of Screams', 'Shoot the Messenger', + lambda state: state.has("Beat Harvest of Screams", player)), + connect(world, names, 'Shoot the Messenger', 'Enemy Within', + lambda state: state.has("Beat Shoot the Messenger", player)), + connect(world, names, 'Rendezvous', 'Domination', + lambda state: state.has("Beat Rendezvous", player)), + connect(world, names, 'Domination', 'Fire in the Sky', + lambda state: state.has("Beat Domination", player)), + connect(world, names, 'Fire in the Sky', 'Old Soldiers', + lambda state: state.has("Beat Fire in the Sky", player)), + connect(world, names, 'Old Soldiers', 'Waking the Ancient', + lambda state: state.has("Beat Old Soldiers", player)), + connect(world, names, 'Enemy Within', 'Waking the Ancient', + lambda state: state.has("Beat Enemy Within", player)), + connect(world, names, 'Waking the Ancient', 'The Crucible', + lambda state: state.has("Beat Waking the Ancient", player)), + connect(world, names, 'The Crucible', 'Supreme', + lambda state: state.has("Beat The Crucible", player)), + connect(world, names, 'Supreme', 'Infested', + lambda state: state.has("Beat Supreme", player) and + state.has("Beat Old Soldiers", player) and + state.has("Beat Enemy Within", player)), + connect(world, names, 'Infested', 'Hand of Darkness', + lambda state: state.has("Beat Infested", player)), + connect(world, names, 'Hand of Darkness', 'Phantoms of the Void', + lambda state: state.has("Beat Hand of Darkness", player)), + connect(world, names, 'Supreme', 'With Friends Like These', + lambda state: state.has("Beat Supreme", player) and + state.has("Beat Old Soldiers", player) and + state.has("Beat Enemy Within", player)), + connect(world, names, 'With Friends Like These', 'Conviction', + lambda state: state.has("Beat With Friends Like These", player)), + connect(world, names, 'Conviction', 'Planetfall', + lambda state: state.has("Beat Conviction", player) and + state.has("Beat Phantoms of the Void", player)), + connect(world, names, 'Planetfall', 'Death From Above', + lambda state: state.has("Beat Planetfall", player)), + connect(world, names, 'Death From Above', 'The Reckoning', + lambda state: state.has("Beat Death From Above", player)), + + if SC2Campaign.PROLOGUE in enabled_campaigns: + connect(world, names, "Menu", "Dark Whispers") + connect(world, names, "Dark Whispers", "Ghosts in the Fog", + lambda state: state.has("Beat Dark Whispers", player)) + connect(world, names, "Ghosts in the Fog", "Evil Awoken", + lambda state: state.has("Beat Ghosts in the Fog", player)) + + if SC2Campaign.LOTV in enabled_campaigns: + connect(world, names, "Menu", "For Aiur!") + connect(world, names, "For Aiur!", "The Growing Shadow", + lambda state: state.has("Beat For Aiur!", player)), + connect(world, names, "The Growing Shadow", "The Spear of Adun", + lambda state: state.has("Beat The Growing Shadow", player)), + connect(world, names, "The Spear of Adun", "Sky Shield", + lambda state: state.has("Beat The Spear of Adun", player)), + connect(world, names, "Sky Shield", "Brothers in Arms", + lambda state: state.has("Beat Sky Shield", player)), + connect(world, names, "Brothers in Arms", "Forbidden Weapon", + lambda state: state.has("Beat Brothers in Arms", player)), + connect(world, names, "The Spear of Adun", "Amon's Reach", + lambda state: state.has("Beat The Spear of Adun", player)), + connect(world, names, "Amon's Reach", "Last Stand", + lambda state: state.has("Beat Amon's Reach", player)), + connect(world, names, "Last Stand", "Forbidden Weapon", + lambda state: state.has("Beat Last Stand", player)), + connect(world, names, "Forbidden Weapon", "Temple of Unification", + lambda state: state.has("Beat Brothers in Arms", player) + and state.has("Beat Last Stand", player) + and state.has("Beat Forbidden Weapon", player)), + connect(world, names, "Temple of Unification", "The Infinite Cycle", + lambda state: state.has("Beat Temple of Unification", player)), + connect(world, names, "The Infinite Cycle", "Harbinger of Oblivion", + lambda state: state.has("Beat The Infinite Cycle", player)), + connect(world, names, "Harbinger of Oblivion", "Unsealing the Past", + lambda state: state.has("Beat Harbinger of Oblivion", player)), + connect(world, names, "Unsealing the Past", "Purification", + lambda state: state.has("Beat Unsealing the Past", player)), + connect(world, names, "Purification", "Templar's Charge", + lambda state: state.has("Beat Purification", player)), + connect(world, names, "Harbinger of Oblivion", "Steps of the Rite", + lambda state: state.has("Beat Harbinger of Oblivion", player)), + connect(world, names, "Steps of the Rite", "Rak'Shir", + lambda state: state.has("Beat Steps of the Rite", player)), + connect(world, names, "Rak'Shir", "Templar's Charge", + lambda state: state.has("Beat Rak'Shir", player)), + connect(world, names, "Templar's Charge", "Templar's Return", + lambda state: state.has("Beat Purification", player) + and state.has("Beat Rak'Shir", player) + and state.has("Beat Templar's Charge", player)), + connect(world, names, "Templar's Return", "The Host", + lambda state: state.has("Beat Templar's Return", player)), + connect(world, names, "The Host", "Salvation", + lambda state: state.has("Beat The Host", player)), + + if SC2Campaign.EPILOGUE in enabled_campaigns: + # TODO: Make this aware about excluded campaigns + connect(world, names, "Salvation", "Into the Void", + lambda state: state.has("Beat Salvation", player) + and state.has("Beat The Reckoning", player) + and state.has("Beat All-In", player)), + connect(world, names, "Into the Void", "The Essence of Eternity", + lambda state: state.has("Beat Into the Void", player)), + connect(world, names, "The Essence of Eternity", "Amon's Fall", + lambda state: state.has("Beat The Essence of Eternity", player)), + + if SC2Campaign.NCO in enabled_campaigns: + connect(world, names, "Menu", "The Escape") + connect(world, names, "The Escape", "Sudden Strike", + lambda state: state.has("Beat The Escape", player)) + connect(world, names, "Sudden Strike", "Enemy Intelligence", + lambda state: state.has("Beat Sudden Strike", player)) + connect(world, names, "Enemy Intelligence", "Trouble In Paradise", + lambda state: state.has("Beat Enemy Intelligence", player)) + connect(world, names, "Trouble In Paradise", "Night Terrors", + lambda state: state.has("Beat Trouble In Paradise", player)) + connect(world, names, "Night Terrors", "Flashpoint", + lambda state: state.has("Beat Night Terrors", player)) + connect(world, names, "Flashpoint", "In the Enemy's Shadow", + lambda state: state.has("Beat Flashpoint", player)) + connect(world, names, "In the Enemy's Shadow", "Dark Skies", + lambda state: state.has("Beat In the Enemy's Shadow", player)) + connect(world, names, "Dark Skies", "End Game", + lambda state: state.has("Beat Dark Skies", player)) + + goal_location = get_goal_location(final_mission) + assert goal_location, f"Unable to find a goal location for mission {final_mission}" + setup_final_location(goal_location, location_cache) + + return (vanilla_mission_reqs, final_mission.id, goal_location) + + +def create_grid_regions( + world: World, + locations: Tuple[LocationData, ...], + location_cache: List[Location], +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + locations_per_region = get_locations_per_region(locations) + + mission_pools = filter_missions(world) + final_mission = mission_pools[MissionPools.FINAL][0] + + mission_pool = [mission for mission_pool in mission_pools.values() for mission in mission_pool] + + num_missions = min(len(mission_pool), get_option_value(world, "maximum_campaign_size")) + remove_top_left: bool = get_option_value(world, "grid_two_start_positions") == GridTwoStartPositions.option_true + + regions = [create_region(world, locations_per_region, location_cache, "Menu")] + names: Dict[str, int] = {} + missions: Dict[Tuple[int, int], SC2Mission] = {} + + grid_size_x, grid_size_y, num_corners_to_remove = get_grid_dimensions(num_missions + remove_top_left) + # pick missions in order along concentric diagonals + # each diagonal will have the same difficulty + # this keeps long sides from possibly stealing lower-difficulty missions from future columns + num_diagonals = grid_size_x + grid_size_y - 1 + diagonal_difficulty = MissionPools.STARTER + missions_to_add = mission_pools[MissionPools.STARTER] + for diagonal in range(num_diagonals): + if diagonal == num_diagonals - 1: + diagonal_difficulty = MissionPools.FINAL + grid_coords = (grid_size_x-1, grid_size_y-1) + missions[grid_coords] = final_mission + break + if diagonal == 0 and remove_top_left: + continue + diagonal_length = min(diagonal + 1, num_diagonals - diagonal, grid_size_x, grid_size_y) + if len(missions_to_add) < diagonal_length: + raise Exception(f"There are not enough {diagonal_difficulty.name} missions to fill the campaign. Please exclude fewer missions.") + for i in range(diagonal_length): + # (0,0) + (0,1)*diagonal + (1,-1)*i + (1,-1)*max(diagonal - grid_size_y + 1, 0) + grid_coords = (i + max(diagonal - grid_size_y + 1, 0), diagonal - i - max(diagonal - grid_size_y + 1, 0)) + if grid_coords == (grid_size_x - 1, 0) and num_corners_to_remove >= 2: + pass + elif grid_coords == (0, grid_size_y - 1) and num_corners_to_remove >= 1: + pass + else: + mission_index = world.random.randint(0, len(missions_to_add) - 1) + missions[grid_coords] = missions_to_add.pop(mission_index) + + if diagonal_difficulty < MissionPools.VERY_HARD: + diagonal_difficulty = MissionPools(diagonal_difficulty.value + 1) + missions_to_add.extend(mission_pools[diagonal_difficulty]) + + # Generating regions and locations from selected missions + for x in range(grid_size_x): + for y in range(grid_size_y): + if missions.get((x, y)): + regions.append(create_region(world, locations_per_region, location_cache, missions[(x, y)].mission_name)) + world.multiworld.regions += regions + + # This pattern is horrifying, why are we using the dict as an ordered dict??? + slot_map: Dict[Tuple[int, int], int] = {} + for index, coords in enumerate(missions): + slot_map[coords] = index + 1 + + mission_req_table: Dict[str, MissionInfo] = {} + for coords, mission in missions.items(): + prepend_vertical = 0 + if not mission: + continue + connections: List[MissionConnection] = [] + if coords == (0, 0) or (remove_top_left and sum(coords) == 1): + # Connect to the "Menu" starting region + connect(world, names, "Menu", mission.mission_name) + else: + for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): + connected_coords = (coords[0] + dx, coords[1] + dy) + if connected_coords in missions: + # connections.append(missions[connected_coords]) + connections.append(MissionConnection(slot_map[connected_coords])) + connect(world, names, missions[connected_coords].mission_name, mission.mission_name, + make_grid_connect_rule(missions, connected_coords, world.player), + ) + if coords[1] == 1 and not missions.get((coords[0], 0)): + prepend_vertical = 1 + mission_req_table[mission.mission_name] = MissionInfo( + mission, + connections, + category=f'_{coords[0] + 1}', + or_requirements=True, + ui_vertical_padding=prepend_vertical, + ) + + final_mission_id = final_mission.id + # Changing the completion condition for alternate final missions into an event + final_location = get_goal_location(final_mission) + setup_final_location(final_location, location_cache) + + return {SC2Campaign.GLOBAL: mission_req_table}, final_mission_id, final_location + + +def make_grid_connect_rule( + missions: Dict[Tuple[int, int], SC2Mission], + connected_coords: Tuple[int, int], + player: int +) -> Callable[[CollectionState], bool]: + return lambda state: state.has(f"Beat {missions[connected_coords].mission_name}", player) + + +def create_structured_regions( + world: World, + locations: Tuple[LocationData, ...], + location_cache: List[Location], + mission_order_type: int, +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + locations_per_region = get_locations_per_region(locations) + + mission_order = mission_orders[mission_order_type]() + enabled_campaigns = get_enabled_campaigns(world) + shuffle_campaigns = get_option_value(world, "shuffle_campaigns") + + mission_pools: Dict[MissionPools, List[SC2Mission]] = filter_missions(world) + final_mission = mission_pools[MissionPools.FINAL][0] + + regions = [create_region(world, locations_per_region, location_cache, "Menu")] + + names: Dict[str, int] = {} + + mission_slots: List[SC2MissionSlot] = [] + mission_pool = [mission for mission_pool in mission_pools.values() for mission in mission_pool] + + if mission_order_type in campaign_depending_orders: + # Do slot removal per campaign + for campaign in enabled_campaigns: + campaign_mission_pool = [mission for mission in mission_pool if mission.campaign == campaign] + campaign_mission_pool_size = len(campaign_mission_pool) + + removals = len(mission_order[campaign]) - campaign_mission_pool_size + + for mission in mission_order[campaign]: + # Removing extra missions if mission pool is too small + if 0 < mission.removal_priority <= removals: + mission_slots.append(SC2MissionSlot(campaign, None)) + elif mission.type == MissionPools.FINAL: + if campaign == final_mission.campaign: + # Campaign is elected to be goal + mission_slots.append(SC2MissionSlot(campaign, final_mission)) + else: + # Not the goal, find the most difficult mission in the pool and set the difficulty + campaign_difficulty = max(mission.pool for mission in campaign_mission_pool) + mission_slots.append(SC2MissionSlot(campaign, campaign_difficulty)) + else: + mission_slots.append(SC2MissionSlot(campaign, mission.type)) + else: + order = mission_order[SC2Campaign.GLOBAL] + # Determining if missions must be removed + mission_pool_size = sum(len(mission_pool) for mission_pool in mission_pools.values()) + removals = len(order) - mission_pool_size + + # Initial fill out of mission list and marking All-In mission + for mission in order: + # Removing extra missions if mission pool is too small + if 0 < mission.removal_priority <= removals: + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, None)) + elif mission.type == MissionPools.FINAL: + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, final_mission)) + else: + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, mission.type)) + + no_build_slots = [] + easy_slots = [] + medium_slots = [] + hard_slots = [] + very_hard_slots = [] + + # Search through missions to find slots needed to fill + for i in range(len(mission_slots)): + mission_slot = mission_slots[i] + if mission_slot is None: + continue + if isinstance(mission_slot, SC2MissionSlot): + if mission_slot.slot is None: + continue + if mission_slot.slot == MissionPools.STARTER: + no_build_slots.append(i) + elif mission_slot.slot == MissionPools.EASY: + easy_slots.append(i) + elif mission_slot.slot == MissionPools.MEDIUM: + medium_slots.append(i) + elif mission_slot.slot == MissionPools.HARD: + hard_slots.append(i) + elif mission_slot.slot == MissionPools.VERY_HARD: + very_hard_slots.append(i) + + def pick_mission(slot): + if shuffle_campaigns or mission_order_type not in campaign_depending_orders: + # Pick a mission from any campaign + filler = world.random.randint(0, len(missions_to_add) - 1) + mission = missions_to_add.pop(filler) + slot_campaign = mission_slots[slot].campaign + mission_slots[slot] = SC2MissionSlot(slot_campaign, mission) + else: + # Pick a mission from required campaign + slot_campaign = mission_slots[slot].campaign + campaign_mission_candidates = [mission for mission in missions_to_add if mission.campaign == slot_campaign] + mission = world.random.choice(campaign_mission_candidates) + missions_to_add.remove(mission) + mission_slots[slot] = SC2MissionSlot(slot_campaign, mission) + + # Add no_build missions to the pool and fill in no_build slots + missions_to_add: List[SC2Mission] = mission_pools[MissionPools.STARTER] + if len(no_build_slots) > len(missions_to_add): + raise Exception("There are no valid No-Build missions. Please exclude fewer missions.") + for slot in no_build_slots: + pick_mission(slot) + + # Add easy missions into pool and fill in easy slots + missions_to_add = missions_to_add + mission_pools[MissionPools.EASY] + if len(easy_slots) > len(missions_to_add): + raise Exception("There are not enough Easy missions to fill the campaign. Please exclude fewer missions.") + for slot in easy_slots: + pick_mission(slot) + + # Add medium missions into pool and fill in medium slots + missions_to_add = missions_to_add + mission_pools[MissionPools.MEDIUM] + if len(medium_slots) > len(missions_to_add): + raise Exception("There are not enough Easy and Medium missions to fill the campaign. Please exclude fewer missions.") + for slot in medium_slots: + pick_mission(slot) + + # Add hard missions into pool and fill in hard slots + missions_to_add = missions_to_add + mission_pools[MissionPools.HARD] + if len(hard_slots) > len(missions_to_add): + raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.") + for slot in hard_slots: + pick_mission(slot) + + # Add very hard missions into pool and fill in very hard slots + missions_to_add = missions_to_add + mission_pools[MissionPools.VERY_HARD] + if len(very_hard_slots) > len(missions_to_add): + raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.") + for slot in very_hard_slots: + pick_mission(slot) + + # Generating regions and locations from selected missions + for mission_slot in mission_slots: + if isinstance(mission_slot.slot, SC2Mission): + regions.append(create_region(world, locations_per_region, location_cache, mission_slot.slot.mission_name)) + world.multiworld.regions += regions + + campaigns: List[SC2Campaign] + if mission_order_type in campaign_depending_orders: + campaigns = list(enabled_campaigns) + else: + campaigns = [SC2Campaign.GLOBAL] + + mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = {} + campaign_mission_slots: Dict[SC2Campaign, List[SC2MissionSlot]] = \ + { + campaign: [mission_slot for mission_slot in mission_slots if campaign == mission_slot.campaign] + for campaign in campaigns + } + + slot_map: Dict[SC2Campaign, List[int]] = dict() + + for campaign in campaigns: + mission_req_table.update({campaign: dict()}) + + # Mapping original mission slots to shifted mission slots when missions are removed + slot_map[campaign] = [] + slot_offset = 0 + for position, mission in enumerate(campaign_mission_slots[campaign]): + slot_map[campaign].append(position - slot_offset + 1) + if mission is None or mission.slot is None: + slot_offset += 1 + + def build_connection_rule(mission_names: List[str], missions_req: int) -> Callable: + player = world.player + if len(mission_names) > 1: + return lambda state: state.has_all({f"Beat {name}" for name in mission_names}, player) \ + and state.has_group("Missions", player, missions_req) + else: + return lambda state: state.has(f"Beat {mission_names[0]}", player) \ + and state.has_group("Missions", player, missions_req) + + for campaign in campaigns: + # Loop through missions to create requirements table and connect regions + for i, mission in enumerate(campaign_mission_slots[campaign]): + if mission is None or mission.slot is None: + continue + connections: List[MissionConnection] = [] + all_connections: List[SC2MissionSlot] = [] + connection: MissionConnection + for connection in mission_order[campaign][i].connect_to: + if connection.connect_to == -1: + continue + # If mission normally connects to an excluded campaign, connect to menu instead + if connection.campaign not in campaign_mission_slots: + connection.connect_to = -1 + continue + while campaign_mission_slots[connection.campaign][connection.connect_to].slot is None: + connection.connect_to -= 1 + all_connections.append(campaign_mission_slots[connection.campaign][connection.connect_to]) + for connection in mission_order[campaign][i].connect_to: + if connection.connect_to == -1: + connect(world, names, "Menu", mission.slot.mission_name) + else: + required_mission = campaign_mission_slots[connection.campaign][connection.connect_to] + if ((required_mission is None or required_mission.slot is None) + and not mission_order[campaign][i].completion_critical): # Drop non-critical null slots + continue + while required_mission is None or required_mission.slot is None: # Substituting null slot with prior slot + connection.connect_to -= 1 + required_mission = campaign_mission_slots[connection.campaign][connection.connect_to] + required_missions = [required_mission] if mission_order[campaign][i].or_requirements else all_connections + if isinstance(required_mission.slot, SC2Mission): + required_mission_name = required_mission.slot.mission_name + required_missions_names = [mission.slot.mission_name for mission in required_missions] + connect(world, names, required_mission_name, mission.slot.mission_name, + build_connection_rule(required_missions_names, mission_order[campaign][i].number)) + connections.append(MissionConnection(slot_map[connection.campaign][connection.connect_to], connection.campaign)) + + mission_req_table[campaign].update({mission.slot.mission_name: MissionInfo( + mission.slot, connections, mission_order[campaign][i].category, + number=mission_order[campaign][i].number, + completion_critical=mission_order[campaign][i].completion_critical, + or_requirements=mission_order[campaign][i].or_requirements)}) + + final_mission_id = final_mission.id + # Changing the completion condition for alternate final missions into an event + final_location = get_goal_location(final_mission) + setup_final_location(final_location, location_cache) + + return mission_req_table, final_mission_id, final_location + + +def setup_final_location(final_location, location_cache): + # Final location should be near the end of the cache + for i in range(len(location_cache) - 1, -1, -1): + if location_cache[i].name == final_location: + location_cache[i].address = None + break + + +def create_location(player: int, location_data: LocationData, region: Region, + location_cache: List[Location]) -> Location: + location = Location(player, location_data.name, location_data.code, region) + location.access_rule = location_data.rule + + location_cache.append(location) + + return location + + +def create_region(world: World, locations_per_region: Dict[str, List[LocationData]], + location_cache: List[Location], name: str) -> Region: + region = Region(name, world.player, world.multiworld) + + if name in locations_per_region: + for location_data in locations_per_region[name]: + location = create_location(world.player, location_data, region, location_cache) + region.locations.append(location) + + return region + + +def connect(world: World, used_names: Dict[str, int], source: str, target: str, + rule: Optional[Callable] = None): + source_region = world.get_region(source) + target_region = world.get_region(target) + + if target not in used_names: + used_names[target] = 1 + name = target + else: + used_names[target] += 1 + name = target + (' ' * used_names[target]) + + connection = Entrance(world.player, name, source_region) + + if rule: + connection.access_rule = rule + + source_region.exits.append(connection) + connection.connect(target_region) + + +def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: + per_region: Dict[str, List[LocationData]] = {} + + for location in locations: + per_region.setdefault(location.region, []).append(location) + + return per_region + + +def get_factors(number: int) -> Tuple[int, int]: + """ + Simple factorization into pairs of numbers (x, y) using a sieve method. + Returns the factorization that is most square, i.e. where x + y is minimized. + Factor order is such that x <= y. + """ + assert number > 0 + for divisor in range(math.floor(math.sqrt(number)), 1, -1): + quotient = number // divisor + if quotient * divisor == number: + return divisor, quotient + return 1, number + + +def get_grid_dimensions(size: int) -> Tuple[int, int, int]: + """ + Get the dimensions of a grid mission order from the number of missions, int the format (x, y, error). + * Error will always be 0, 1, or 2, so the missions can be removed from the corners that aren't the start or end. + * Dimensions are chosen such that x <= y, as buttons in the UI are wider than they are tall. + * Dimensions are chosen to be maximally square. That is, x + y + error is minimized. + * If multiple options of the same rating are possible, the one with the larger error is chosen, + as it will appear more square. Compare 3x11 to 5x7-2 for an example of this. + """ + dimension_candidates: List[Tuple[int, int, int]] = [(*get_factors(size + x), x) for x in (2, 1, 0)] + best_dimension = min(dimension_candidates, key=sum) + return best_dimension + diff --git a/worlds/sc2/Rules.py b/worlds/sc2/Rules.py new file mode 100644 index 000000000000..8b9097ea1d78 --- /dev/null +++ b/worlds/sc2/Rules.py @@ -0,0 +1,952 @@ +from typing import Set + +from BaseClasses import CollectionState +from .Options import get_option_value, RequiredTactics, kerrigan_unit_available, AllInMap, \ + GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, SpearOfAdunAutonomouslyCastAbilityPresence, \ + get_enabled_campaigns, MissionOrder +from .Items import get_basic_units, defense_ratings, zerg_defense_ratings, kerrigan_actives, air_defense_ratings, \ + kerrigan_levels, get_full_item_list +from .MissionTables import SC2Race, SC2Campaign +from . import ItemNames +from worlds.AutoWorld import World + + +class SC2Logic: + + def lock_any_item(self, state: CollectionState, items: Set[str]) -> bool: + """ + Guarantees that at least one of these items will remain in the world. Doesn't affect placement. + Needed for cases when the dynamic pool filtering could remove all the item prerequisites + :param state: + :param items: + :return: + """ + return self.is_item_placement(state) \ + or state.has_any(items, self.player) + + def is_item_placement(self, state): + """ + Tells if it's item placement or item pool filter + :param state: + :return: True for item placement, False for pool filter + """ + # has_group with count = 0 is always true for item placement and always false for SC2 item filtering + return state.has_group("Missions", self.player, 0) + + # WoL + def terran_common_unit(self, state: CollectionState) -> bool: + return state.has_any(self.basic_terran_units, self.player) + + def terran_early_tech(self, state: CollectionState): + """ + Basic combat unit that can be deployed quickly from mission start + :param state + :return: + """ + return ( + state.has_any({ItemNames.MARINE, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.HELLION}, self.player) + or (self.advanced_tactics and state.has_any({ItemNames.GOLIATH, ItemNames.DIAMONDBACK, ItemNames.VIKING, ItemNames.BANSHEE}, self.player)) + ) + + def terran_air(self, state: CollectionState) -> bool: + """ + Air units or drops on advanced tactics + :param state: + :return: + """ + return (state.has_any({ItemNames.VIKING, ItemNames.WRAITH, ItemNames.BANSHEE, ItemNames.BATTLECRUISER}, self.player) or self.advanced_tactics + and state.has_any({ItemNames.HERCULES, ItemNames.MEDIVAC}, self.player) and self.terran_common_unit(state) + ) + + def terran_air_anti_air(self, state: CollectionState) -> bool: + """ + Air-to-air + :param state: + :return: + """ + return ( + state.has(ItemNames.VIKING, self.player) + or state.has_all({ItemNames.WRAITH, ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) + or state.has_all({ItemNames.BATTLECRUISER, ItemNames.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) + or self.advanced_tactics and state.has_any({ItemNames.WRAITH, ItemNames.VALKYRIE, ItemNames.BATTLECRUISER}, self.player) + ) + + def terran_competent_ground_to_air(self, state: CollectionState) -> bool: + """ + Ground-to-air + :param state: + :return: + """ + return ( + state.has(ItemNames.GOLIATH, self.player) + or state.has(ItemNames.MARINE, self.player) and self.terran_bio_heal(state) + or self.advanced_tactics and state.has(ItemNames.CYCLONE, self.player) + ) + + def terran_competent_anti_air(self, state: CollectionState) -> bool: + """ + Good AA unit + :param state: + :return: + """ + return ( + self.terran_competent_ground_to_air(state) + or self.terran_air_anti_air(state) + ) + + def welcome_to_the_jungle_requirement(self, state: CollectionState) -> bool: + """ + Welcome to the Jungle requirements - able to deal with Scouts, Void Rays, Zealots and Stalkers + :param state: + :return: + """ + return ( + self.terran_common_unit(state) + and self.terran_competent_ground_to_air(state) + ) or ( + self.advanced_tactics + and state.has_any({ItemNames.MARINE, ItemNames.VULTURE}, self.player) + and self.terran_air_anti_air(state) + ) + + def terran_basic_anti_air(self, state: CollectionState) -> bool: + """ + Basic AA to deal with few air units + :param state: + :return: + """ + return ( + state.has_any({ + ItemNames.MISSILE_TURRET, ItemNames.THOR, ItemNames.WAR_PIGS, ItemNames.SPARTAN_COMPANY, + ItemNames.HELS_ANGELS, ItemNames.BATTLECRUISER, ItemNames.MARINE, ItemNames.WRAITH, + ItemNames.VALKYRIE, ItemNames.CYCLONE, ItemNames.WINGED_NIGHTMARES, ItemNames.BRYNHILDS + }, self.player) + or self.terran_competent_anti_air(state) + or self.advanced_tactics and state.has_any({ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.WIDOW_MINE, ItemNames.LIBERATOR}, self.player) + ) + + def terran_defense_rating(self, state: CollectionState, zerg_enemy: bool, air_enemy: bool = True) -> int: + """ + Ability to handle defensive missions + :param state: + :param zerg_enemy: + :param air_enemy: + :return: + """ + defense_score = sum((defense_ratings[item] for item in defense_ratings if state.has(item, self.player))) + # Manned Bunker + if state.has_any({ItemNames.MARINE, ItemNames.MARAUDER}, self.player) and state.has(ItemNames.BUNKER, self.player): + defense_score += 3 + elif zerg_enemy and state.has(ItemNames.FIREBAT, self.player) and state.has(ItemNames.BUNKER, self.player): + defense_score += 2 + # Siege Tank upgrades + if state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS}, self.player): + defense_score += 2 + if state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_GRADUATING_RANGE}, self.player): + defense_score += 1 + # Widow Mine upgrade + if state.has_all({ItemNames.WIDOW_MINE, ItemNames.WIDOW_MINE_CONCEALMENT}, self.player): + defense_score += 1 + # Viking with splash + if state.has_all({ItemNames.VIKING, ItemNames.VIKING_SHREDDER_ROUNDS}, self.player): + defense_score += 2 + + # General enemy-based rules + if zerg_enemy: + defense_score += sum((zerg_defense_ratings[item] for item in zerg_defense_ratings if state.has(item, self.player))) + if air_enemy: + defense_score += sum((air_defense_ratings[item] for item in air_defense_ratings if state.has(item, self.player))) + if air_enemy and zerg_enemy and state.has(ItemNames.VALKYRIE, self.player): + # Valkyries shred mass Mutas, most common air enemy that's massed in these cases + defense_score += 2 + # Advanced Tactics bumps defense rating requirements down by 2 + if self.advanced_tactics: + defense_score += 2 + return defense_score + + def terran_competent_comp(self, state: CollectionState) -> bool: + """ + Ability to deal with most of hard missions + :param state: + :return: + """ + return ( + ( + (state.has_any({ItemNames.MARINE, ItemNames.MARAUDER}, self.player) and self.terran_bio_heal(state)) + or state.has_any({ItemNames.THOR, ItemNames.BANSHEE, ItemNames.SIEGE_TANK}, self.player) + or state.has_all({ItemNames.LIBERATOR, ItemNames.LIBERATOR_RAID_ARTILLERY}, self.player) + ) + and self.terran_competent_anti_air(state) + ) or ( + state.has(ItemNames.BATTLECRUISER, self.player) and self.terran_common_unit(state) + ) + + def great_train_robbery_train_stopper(self, state: CollectionState) -> bool: + """ + Ability to deal with trains (moving target with a lot of HP) + :param state: + :return: + """ + return ( + state.has_any({ItemNames.SIEGE_TANK, ItemNames.DIAMONDBACK, ItemNames.MARAUDER, ItemNames.CYCLONE, ItemNames.BANSHEE}, self.player) + or self.advanced_tactics + and ( + state.has_all({ItemNames.REAPER, ItemNames.REAPER_G4_CLUSTERBOMB}, self.player) + or state.has_all({ItemNames.SPECTRE, ItemNames.SPECTRE_PSIONIC_LASH}, self.player) + or state.has_any({ItemNames.VULTURE, ItemNames.LIBERATOR}, self.player) + ) + ) + + def terran_can_rescue(self, state) -> bool: + """ + Rescuing in The Moebius Factor + :param state: + :return: + """ + return state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES, ItemNames.RAVEN, ItemNames.VIKING}, self.player) or self.advanced_tactics + + def terran_beats_protoss_deathball(self, state: CollectionState) -> bool: + """ + Ability to deal with Immortals, Colossi with some air support + :param state: + :return: + """ + return ( + ( + state.has_any({ItemNames.BANSHEE, ItemNames.BATTLECRUISER}, self.player) + or state.has_all({ItemNames.LIBERATOR, ItemNames.LIBERATOR_RAID_ARTILLERY}, self.player) + ) and self.terran_competent_anti_air(state) + or self.terran_competent_comp(state) and self.terran_air_anti_air(state) + ) + + def marine_medic_upgrade(self, state: CollectionState) -> bool: + """ + Infantry upgrade to infantry-only no-build segments + :param state: + :return: + """ + return state.has_any({ + ItemNames.MARINE_COMBAT_SHIELD, ItemNames.MARINE_MAGRAIL_MUNITIONS, ItemNames.MEDIC_STABILIZER_MEDPACKS + }, self.player) \ + or (state.count(ItemNames.MARINE_PROGRESSIVE_STIMPACK, self.player) >= 2 + and state.has_group("Missions", self.player, 1)) + + def terran_survives_rip_field(self, state: CollectionState) -> bool: + """ + Ability to deal with large areas with environment damage + :param state: + :return: + """ + return (state.has(ItemNames.BATTLECRUISER, self.player) + or self.terran_air(state) and self.terran_competent_anti_air(state) and self.terran_sustainable_mech_heal(state)) + + def terran_sustainable_mech_heal(self, state: CollectionState) -> bool: + """ + Can heal mech units without spending resources + :param state: + :return: + """ + return state.has(ItemNames.SCIENCE_VESSEL, self.player) \ + or state.has_all({ItemNames.MEDIC, ItemNames.MEDIC_ADAPTIVE_MEDPACKS}, self.player) \ + or state.count(ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 3 \ + or (self.advanced_tactics + and ( + state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) + or state.count(ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 2) + ) + + def terran_bio_heal(self, state: CollectionState) -> bool: + """ + Ability to heal bio units + :param state: + :return: + """ + return state.has_any({ItemNames.MEDIC, ItemNames.MEDIVAC}, self.player) \ + or self.advanced_tactics and state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) + + def terran_base_trasher(self, state: CollectionState) -> bool: + """ + Can attack heavily defended bases + :param state: + :return: + """ + return state.has(ItemNames.SIEGE_TANK, self.player) \ + or state.has_all({ItemNames.BATTLECRUISER, ItemNames.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) \ + or state.has_all({ItemNames.LIBERATOR, ItemNames.LIBERATOR_RAID_ARTILLERY}, self.player) \ + or (self.advanced_tactics + and ((state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, self.player) + or self.can_nuke(state)) + and ( + state.has_all({ItemNames.VIKING, ItemNames.VIKING_SHREDDER_ROUNDS}, self.player) + or state.has_all({ItemNames.BANSHEE, ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY}, self.player)) + ) + ) + + def terran_mobile_detector(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.RAVEN, ItemNames.SCIENCE_VESSEL, ItemNames.PROGRESSIVE_ORBITAL_COMMAND}, self.player) + + def can_nuke(self, state: CollectionState) -> bool: + """ + Ability to launch nukes + :param state: + :return: + """ + return (self.advanced_tactics + and (state.has_any({ItemNames.GHOST, ItemNames.SPECTRE}, self.player) + or state.has_all({ItemNames.THOR, ItemNames.THOR_BUTTON_WITH_A_SKULL_ON_IT}, self.player))) + + def terran_respond_to_colony_infestations(self, state: CollectionState) -> bool: + """ + Can deal quickly with Brood Lords and Mutas in Haven's Fall and being able to progress the mission + :param state: + :return: + """ + return ( + self.terran_common_unit(state) + and self.terran_competent_anti_air(state) + and ( + self.terran_air_anti_air(state) + or state.has_any({ItemNames.BATTLECRUISER, ItemNames.VALKYRIE}, self.player) + ) + and self.terran_defense_rating(state, True) >= 3 + ) + + def engine_of_destruction_requirement(self, state: CollectionState): + return self.marine_medic_upgrade(state) \ + and ( + self.terran_competent_anti_air(state) + and self.terran_common_unit(state) or state.has(ItemNames.WRAITH, self.player) + ) + + def all_in_requirement(self, state: CollectionState): + """ + All-in + :param state: + :return: + """ + beats_kerrigan = state.has_any({ItemNames.MARINE, ItemNames.BANSHEE, ItemNames.GHOST}, self.player) or self.advanced_tactics + if get_option_value(self.world, 'all_in_map') == AllInMap.option_ground: + # Ground + defense_rating = self.terran_defense_rating(state, True, False) + if state.has_any({ItemNames.BATTLECRUISER, ItemNames.BANSHEE}, self.player): + defense_rating += 2 + return defense_rating >= 13 and beats_kerrigan + else: + # Air + defense_rating = self.terran_defense_rating(state, True, True) + return defense_rating >= 9 and beats_kerrigan \ + and state.has_any({ItemNames.VIKING, ItemNames.BATTLECRUISER, ItemNames.VALKYRIE}, self.player) \ + and state.has_any({ItemNames.HIVE_MIND_EMULATOR, ItemNames.PSI_DISRUPTER, ItemNames.MISSILE_TURRET}, self.player) + + # HotS + def zerg_common_unit(self, state: CollectionState) -> bool: + return state.has_any(self.basic_zerg_units, self.player) + + def zerg_competent_anti_air(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.HYDRALISK, ItemNames.MUTALISK, ItemNames.CORRUPTOR, ItemNames.BROOD_QUEEN}, self.player) \ + or state.has_all({ItemNames.SWARM_HOST, ItemNames.SWARM_HOST_PRESSURIZED_GLANDS}, self.player) \ + or state.has_all({ItemNames.SCOURGE, ItemNames.SCOURGE_RESOURCE_EFFICIENCY}, self.player) \ + or (self.advanced_tactics and state.has(ItemNames.INFESTOR, self.player)) + + def zerg_basic_anti_air(self, state: CollectionState) -> bool: + return self.zerg_competent_anti_air(state) or self.kerrigan_unit_available in kerrigan_unit_available or \ + state.has_any({ItemNames.SWARM_QUEEN, ItemNames.SCOURGE}, self.player) or (self.advanced_tactics and state.has(ItemNames.SPORE_CRAWLER, self.player)) + + def morph_brood_lord(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.MUTALISK, ItemNames.CORRUPTOR}, self.player) \ + and state.has(ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, self.player) + + def morph_viper(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.MUTALISK, ItemNames.CORRUPTOR}, self.player) \ + and state.has(ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, self.player) + + def morph_impaler_or_lurker(self, state: CollectionState) -> bool: + return state.has(ItemNames.HYDRALISK, self.player) and state.has_any({ItemNames.HYDRALISK_IMPALER_ASPECT, ItemNames.HYDRALISK_LURKER_ASPECT}, self.player) + + def zerg_competent_comp(self, state: CollectionState) -> bool: + advanced = self.advanced_tactics + core_unit = state.has_any({ItemNames.ROACH, ItemNames.ABERRATION, ItemNames.ZERGLING}, self.player) + support_unit = state.has_any({ItemNames.SWARM_QUEEN, ItemNames.HYDRALISK}, self.player) \ + or self.morph_brood_lord(state) \ + or advanced and (state.has_any({ItemNames.INFESTOR, ItemNames.DEFILER}, self.player) or self.morph_viper(state)) + if core_unit and support_unit: + return True + vespene_unit = state.has_any({ItemNames.ULTRALISK, ItemNames.ABERRATION}, self.player) \ + or advanced and self.morph_viper(state) + return vespene_unit and state.has_any({ItemNames.ZERGLING, ItemNames.SWARM_QUEEN}, self.player) + + def spread_creep(self, state: CollectionState) -> bool: + return self.advanced_tactics or state.has(ItemNames.SWARM_QUEEN, self.player) + + def zerg_competent_defense(self, state: CollectionState) -> bool: + return ( + self.zerg_common_unit(state) + and ( + ( + state.has(ItemNames.SWARM_HOST, self.player) + or self.morph_brood_lord(state) + or self.morph_impaler_or_lurker(state) + ) or ( + self.advanced_tactics + and (self.morph_viper(state) + or state.has(ItemNames.SPINE_CRAWLER, self.player)) + ) + ) + ) + + def basic_kerrigan(self, state: CollectionState) -> bool: + # One active ability that can be used to defeat enemies directly on Standard + if not self.advanced_tactics and \ + not state.has_any({ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE, + ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT, + ItemNames.KERRIGAN_SPAWN_BANELINGS}, self.player): + return False + # Two non-ultimate abilities + count = 0 + for item in (ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE, ItemNames.KERRIGAN_HEROIC_FORTITUDE, + ItemNames.KERRIGAN_CHAIN_REACTION, ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT, + ItemNames.KERRIGAN_SPAWN_BANELINGS, ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY): + if state.has(item, self.player): + count += 1 + if count >= 2: + return True + return False + + def two_kerrigan_actives(self, state: CollectionState) -> bool: + count = 0 + for i in range(7): + if state.has_any(kerrigan_actives[i], self.player): + count += 1 + return count >= 2 + + def zerg_pass_vents(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or state.has_any({ItemNames.ZERGLING, ItemNames.HYDRALISK, ItemNames.ROACH}, self.player) \ + or (self.advanced_tactics and state.has(ItemNames.INFESTOR, self.player)) + + def supreme_requirement(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or not self.kerrigan_unit_available \ + or ( + state.has_all({ItemNames.KERRIGAN_LEAPING_STRIKE, ItemNames.KERRIGAN_MEND}, self.player) + and self.kerrigan_levels(state, 35) + ) + + def kerrigan_levels(self, state: CollectionState, target: int) -> bool: + if self.story_levels_granted or not self.kerrigan_unit_available: + return True # Levels are granted + if self.kerrigan_levels_per_mission_completed > 0 \ + and self.kerrigan_levels_per_mission_completed_cap > 0 \ + and not self.is_item_placement(state): + # Levels can be granted from mission completion. + # Item pool filtering isn't aware of missions beaten. Assume that missions beaten will fulfill this rule. + return True + # Levels from missions beaten + levels = self.kerrigan_levels_per_mission_completed * state.count_group("Missions", self.player) + if self.kerrigan_levels_per_mission_completed_cap != -1: + levels = min(levels, self.kerrigan_levels_per_mission_completed_cap) + # Levels from items + for kerrigan_level_item in kerrigan_levels: + level_amount = get_full_item_list()[kerrigan_level_item].number + item_count = state.count(kerrigan_level_item, self.player) + levels += item_count * level_amount + # Total level cap + if self.kerrigan_total_level_cap != -1: + levels = min(levels, self.kerrigan_total_level_cap) + + return levels >= target + + + def the_reckoning_requirement(self, state: CollectionState) -> bool: + if self.take_over_ai_allies: + return self.terran_competent_comp(state) \ + and self.zerg_competent_comp(state) \ + and (self.zerg_competent_anti_air(state) + or self.terran_competent_anti_air(state)) + else: + return self.zerg_competent_comp(state) \ + and self.zerg_competent_anti_air(state) + + # LotV + + def protoss_common_unit(self, state: CollectionState) -> bool: + return state.has_any(self.basic_protoss_units, self.player) + + def protoss_basic_anti_air(self, state: CollectionState) -> bool: + return self.protoss_competent_anti_air(state) \ + or state.has_any({ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.CARRIER, ItemNames.SCOUT, + ItemNames.DARK_ARCHON, ItemNames.WRATHWALKER, ItemNames.MOTHERSHIP}, self.player) \ + or state.has_all({ItemNames.WARP_PRISM, ItemNames.WARP_PRISM_PHASE_BLASTER}, self.player) \ + or self.advanced_tactics and state.has_any( + {ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ASCENDANT, ItemNames.DARK_TEMPLAR, + ItemNames.SENTRY, ItemNames.ENERGIZER}, self.player) + + def protoss_anti_armor_anti_air(self, state: CollectionState) -> bool: + return self.protoss_competent_anti_air(state) \ + or state.has_any({ItemNames.SCOUT, ItemNames.WRATHWALKER}, self.player) \ + or (state.has_any({ItemNames.IMMORTAL, ItemNames.ANNIHILATOR}, self.player) + and state.has(ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS, self.player)) + + def protoss_anti_light_anti_air(self, state: CollectionState) -> bool: + return self.protoss_competent_anti_air(state) \ + or state.has_any({ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.CARRIER}, self.player) + + def protoss_competent_anti_air(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.STALKER, ItemNames.SLAYER, ItemNames.INSTIGATOR, ItemNames.DRAGOON, ItemNames.ADEPT, + ItemNames.VOID_RAY, ItemNames.DESTROYER, ItemNames.TEMPEST}, self.player) \ + or (state.has_any({ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.CARRIER}, self.player) + and state.has_any({ItemNames.SCOUT, ItemNames.WRATHWALKER}, self.player)) \ + or (self.advanced_tactics + and state.has_any({ItemNames.IMMORTAL, ItemNames.ANNIHILATOR}, self.player) + and state.has(ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS, self.player)) + + def protoss_has_blink(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER}, self.player) \ + or ( + state.has(ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK, self.player) + and state.has_any({ItemNames.DARK_TEMPLAR, ItemNames.BLOOD_HUNTER, ItemNames.AVENGER}, self.player) + ) + + def protoss_can_attack_behind_chasm(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.SCOUT, ItemNames.TEMPEST, + ItemNames.CARRIER, ItemNames.VOID_RAY, ItemNames.DESTROYER, ItemNames.MOTHERSHIP}, self.player) \ + or self.protoss_has_blink(state) \ + or (state.has(ItemNames.WARP_PRISM, self.player) + and (self.protoss_common_unit(state) or state.has(ItemNames.WARP_PRISM_PHASE_BLASTER, self.player))) \ + or (self.advanced_tactics + and state.has_any({ItemNames.ORACLE, ItemNames.ARBITER}, self.player)) + + def protoss_fleet(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.CARRIER, ItemNames.TEMPEST, ItemNames.VOID_RAY, ItemNames.DESTROYER}, self.player) + + def templars_return_requirement(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or ( + state.has_any({ItemNames.IMMORTAL, ItemNames.ANNIHILATOR}, self.player) + and state.has_any({ItemNames.COLOSSUS, ItemNames.VANGUARD, ItemNames.REAVER, ItemNames.DARK_TEMPLAR}, self.player) + and state.has_any({ItemNames.SENTRY, ItemNames.HIGH_TEMPLAR}, self.player) + ) + + def brothers_in_arms_requirement(self, state: CollectionState) -> bool: + return ( + self.protoss_common_unit(state) + and self.protoss_anti_armor_anti_air(state) + and self.protoss_hybrid_counter(state) + ) or ( + self.take_over_ai_allies + and ( + self.terran_common_unit(state) + or self.protoss_common_unit(state) + ) + and ( + self.terran_competent_anti_air(state) + or self.protoss_anti_armor_anti_air(state) + ) + and ( + self.protoss_hybrid_counter(state) + or state.has_any({ItemNames.BATTLECRUISER, ItemNames.LIBERATOR, ItemNames.SIEGE_TANK}, self.player) + or state.has_all({ItemNames.SPECTRE, ItemNames.SPECTRE_PSIONIC_LASH}, self.player) + or (state.has(ItemNames.IMMORTAL, self.player) + and state.has_any({ItemNames.MARINE, ItemNames.MARAUDER}, self.player) + and self.terran_bio_heal(state)) + ) + ) + + def protoss_hybrid_counter(self, state: CollectionState) -> bool: + """ + Ground Hybrids + """ + return state.has_any( + {ItemNames.ANNIHILATOR, ItemNames.ASCENDANT, ItemNames.TEMPEST, ItemNames.CARRIER, ItemNames.VOID_RAY, + ItemNames.WRATHWALKER, ItemNames.VANGUARD}, self.player) \ + or (state.has(ItemNames.IMMORTAL, self.player) or self.advanced_tactics) and state.has_any( + {ItemNames.STALKER, ItemNames.DRAGOON, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.SLAYER}, self.player) + + def the_infinite_cycle_requirement(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or not self.kerrigan_unit_available \ + or ( + self.two_kerrigan_actives(state) + and self.basic_kerrigan(state) + and self.kerrigan_levels(state, 70) + ) + + def protoss_basic_splash(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.ZEALOT, ItemNames.COLOSSUS, ItemNames.VANGUARD, ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, + ItemNames.DARK_TEMPLAR, ItemNames.REAVER, ItemNames.ASCENDANT}, self.player) + + def protoss_static_defense(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.PHOTON_CANNON, ItemNames.KHAYDARIN_MONOLITH}, self.player) + + def last_stand_requirement(self, state: CollectionState) -> bool: + return self.protoss_common_unit(state) \ + and self.protoss_competent_anti_air(state) \ + and self.protoss_static_defense(state) \ + and ( + self.advanced_tactics + or self.protoss_basic_splash(state) + ) + + def harbinger_of_oblivion_requirement(self, state: CollectionState) -> bool: + return self.protoss_anti_armor_anti_air(state) and ( + self.take_over_ai_allies + or ( + self.protoss_common_unit(state) + and self.protoss_hybrid_counter(state) + ) + ) + + def protoss_competent_comp(self, state: CollectionState) -> bool: + return self.protoss_common_unit(state) \ + and self.protoss_competent_anti_air(state) \ + and self.protoss_hybrid_counter(state) \ + and self.protoss_basic_splash(state) + + def protoss_stalker_upgrade(self, state: CollectionState) -> bool: + return ( + state.has_any( + { + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION + }, self.player) + and self.lock_any_item(state, {ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER}) + ) + + def steps_of_the_rite_requirement(self, state: CollectionState) -> bool: + return self.protoss_competent_comp(state) \ + or ( + self.protoss_common_unit(state) + and self.protoss_competent_anti_air(state) + and self.protoss_static_defense(state) + ) + + def protoss_heal(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.CARRIER, ItemNames.SENTRY, ItemNames.SHIELD_BATTERY, ItemNames.RECONSTRUCTION_BEAM}, self.player) + + def templars_charge_requirement(self, state: CollectionState) -> bool: + return self.protoss_heal(state) \ + and self.protoss_anti_armor_anti_air(state) \ + and ( + self.protoss_fleet(state) + or (self.advanced_tactics + and self.protoss_competent_comp(state) + ) + ) + + def the_host_requirement(self, state: CollectionState) -> bool: + return (self.protoss_fleet(state) + and self.protoss_static_defense(state) + ) or ( + self.protoss_competent_comp(state) + and state.has(ItemNames.SOA_TIME_STOP, self.player) + ) + + def salvation_requirement(self, state: CollectionState) -> bool: + return [ + self.protoss_competent_comp(state), + self.protoss_fleet(state), + self.protoss_static_defense(state) + ].count(True) >= 2 + + def into_the_void_requirement(self, state: CollectionState) -> bool: + return self.protoss_competent_comp(state) \ + or ( + self.take_over_ai_allies + and ( + state.has(ItemNames.BATTLECRUISER, self.player) + or ( + state.has(ItemNames.ULTRALISK, self.player) + and self.protoss_competent_anti_air(state) + ) + ) + ) + + def essence_of_eternity_requirement(self, state: CollectionState) -> bool: + defense_score = self.terran_defense_rating(state, False, True) + if self.take_over_ai_allies and self.protoss_static_defense(state): + defense_score += 2 + return defense_score >= 10 \ + and ( + self.terran_competent_anti_air(state) + or self.take_over_ai_allies + and self.protoss_competent_anti_air(state) + ) \ + and ( + state.has(ItemNames.BATTLECRUISER, self.player) + or (state.has(ItemNames.BANSHEE, self.player) and state.has_any({ItemNames.VIKING, ItemNames.VALKYRIE}, + self.player)) + or self.take_over_ai_allies and self.protoss_fleet(state) + ) \ + and state.has_any({ItemNames.SIEGE_TANK, ItemNames.LIBERATOR}, self.player) + + def amons_fall_requirement(self, state: CollectionState) -> bool: + if self.take_over_ai_allies: + return ( + ( + state.has_any({ItemNames.BATTLECRUISER, ItemNames.CARRIER}, self.player) + ) + or (state.has(ItemNames.ULTRALISK, self.player) + and self.protoss_competent_anti_air(state) + and ( + state.has_any({ItemNames.LIBERATOR, ItemNames.BANSHEE, ItemNames.VALKYRIE, ItemNames.VIKING}, self.player) + or state.has_all({ItemNames.WRAITH, ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) + or self.protoss_fleet(state) + ) + and (self.terran_sustainable_mech_heal(state) + or (self.spear_of_adun_autonomously_cast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere + and state.has(ItemNames.RECONSTRUCTION_BEAM, self.player)) + ) + ) + ) \ + and self.terran_competent_anti_air(state) \ + and self.protoss_competent_comp(state) \ + and self.zerg_competent_comp(state) + else: + return state.has(ItemNames.MUTALISK, self.player) and self.zerg_competent_comp(state) + + def nova_any_weapon(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.NOVA_C20A_CANISTER_RIFLE, ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_PLASMA_RIFLE, + ItemNames.NOVA_MONOMOLECULAR_BLADE, ItemNames.NOVA_BLAZEFIRE_GUNBLADE}, self.player) + + def nova_ranged_weapon(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.NOVA_C20A_CANISTER_RIFLE, ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_PLASMA_RIFLE}, + self.player) + + def nova_splash(self, state: CollectionState) -> bool: + return state.has_any({ + ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_BLAZEFIRE_GUNBLADE, ItemNames.NOVA_PULSE_GRENADES + }, self.player) \ + or self.advanced_tactics and state.has_any( + {ItemNames.NOVA_PLASMA_RIFLE, ItemNames.NOVA_MONOMOLECULAR_BLADE}, self.player) + + def nova_dash(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_MONOMOLECULAR_BLADE, ItemNames.NOVA_BLINK}, self.player) + + def nova_full_stealth(self, state: CollectionState) -> bool: + return state.count(ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player) >= 2 + + def nova_heal(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_ARMORED_SUIT_MODULE, ItemNames.NOVA_STIM_INFUSION}, self.player) + + def nova_escape_assist(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_BLINK, ItemNames.NOVA_HOLO_DECOY, ItemNames.NOVA_IONIC_FORCE_FIELD}, self.player) + + def the_escape_stuff_granted(self) -> bool: + """ + The NCO first mission requires having too much stuff first before actually able to do anything + :return: + """ + return self.story_tech_granted \ + or (self.mission_order == MissionOrder.option_vanilla and self.enabled_campaigns == {SC2Campaign.NCO}) + + def the_escape_first_stage_requirement(self, state: CollectionState) -> bool: + return self.the_escape_stuff_granted() \ + or (self.nova_ranged_weapon(state) and (self.nova_full_stealth(state) or self.nova_heal(state))) + + def the_escape_requirement(self, state: CollectionState) -> bool: + return self.the_escape_first_stage_requirement(state) \ + and (self.the_escape_stuff_granted() or self.nova_splash(state)) + + def terran_cliffjumper(self, state: CollectionState) -> bool: + return state.has(ItemNames.REAPER, self.player) \ + or state.has_all({ItemNames.GOLIATH, ItemNames.GOLIATH_JUMP_JETS}, self.player) \ + or state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_JUMP_JETS}, self.player) + + def terran_able_to_snipe_defiler(self, state: CollectionState) -> bool: + return state.has_all({ItemNames.NOVA_JUMP_SUIT_MODULE, ItemNames.NOVA_C20A_CANISTER_RIFLE}, self.player) \ + or state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS, ItemNames.SIEGE_TANK_JUMP_JETS}, self.player) + + def sudden_strike_requirement(self, state: CollectionState) -> bool: + return self.sudden_strike_can_reach_objectives(state) \ + and self.terran_able_to_snipe_defiler(state) \ + and state.has_any({ItemNames.SIEGE_TANK, ItemNames.VULTURE}, self.player) \ + and self.nova_splash(state) \ + and (self.terran_defense_rating(state, True, False) >= 2 + or state.has(ItemNames.NOVA_JUMP_SUIT_MODULE, self.player)) + + def sudden_strike_can_reach_objectives(self, state: CollectionState) -> bool: + return self.terran_cliffjumper(state) \ + or state.has_any({ItemNames.BANSHEE, ItemNames.VIKING}, self.player) \ + or ( + self.advanced_tactics + and state.has(ItemNames.MEDIVAC, self.player) + and state.has_any({ItemNames.MARINE, ItemNames.MARAUDER, ItemNames.VULTURE, ItemNames.HELLION, + ItemNames.GOLIATH}, self.player) + ) + + def enemy_intelligence_garrisonable_unit(self, state: CollectionState) -> bool: + """ + Has unit usable as a Garrison in Enemy Intelligence + :param state: + :return: + """ + return state.has_any( + {ItemNames.MARINE, ItemNames.REAPER, ItemNames.MARAUDER, ItemNames.GHOST, ItemNames.SPECTRE, + ItemNames.HELLION, ItemNames.GOLIATH, ItemNames.WARHOUND, ItemNames.DIAMONDBACK, ItemNames.VIKING}, + self.player) + + def enemy_intelligence_cliff_garrison(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.REAPER, ItemNames.VIKING, ItemNames.MEDIVAC, ItemNames.HERCULES}, self.player) \ + or state.has_all({ItemNames.GOLIATH, ItemNames.GOLIATH_JUMP_JETS}, self.player) \ + or self.advanced_tactics and state.has_any({ItemNames.HELS_ANGELS, ItemNames.BRYNHILDS}, self.player) + + def enemy_intelligence_first_stage_requirement(self, state: CollectionState) -> bool: + return self.enemy_intelligence_garrisonable_unit(state) \ + and (self.terran_competent_comp(state) + or ( + self.terran_common_unit(state) + and self.terran_competent_anti_air(state) + and state.has(ItemNames.NOVA_NUKE, self.player) + ) + ) \ + and self.terran_defense_rating(state, True, True) >= 5 + + def enemy_intelligence_second_stage_requirement(self, state: CollectionState) -> bool: + return self.enemy_intelligence_first_stage_requirement(state) \ + and self.enemy_intelligence_cliff_garrison(state) \ + and ( + self.story_tech_granted + or ( + self.nova_any_weapon(state) + and ( + self.nova_full_stealth(state) + or (self.nova_heal(state) + and self.nova_splash(state) + and self.nova_ranged_weapon(state)) + ) + ) + ) + + def enemy_intelligence_third_stage_requirement(self, state: CollectionState) -> bool: + return self.enemy_intelligence_second_stage_requirement(state) \ + and ( + self.story_tech_granted + or ( + state.has(ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player) + and self.nova_dash(state) + ) + ) + + def trouble_in_paradise_requirement(self, state: CollectionState) -> bool: + return self.nova_any_weapon(state) \ + and self.nova_splash(state) \ + and self.terran_beats_protoss_deathball(state) \ + and self.terran_defense_rating(state, True, True) >= 7 + + def night_terrors_requirement(self, state: CollectionState) -> bool: + return self.terran_common_unit(state) \ + and self.terran_competent_anti_air(state) \ + and ( + # These can handle the waves of infested, even volatile ones + state.has(ItemNames.SIEGE_TANK, self.player) + or state.has_all({ItemNames.VIKING, ItemNames.VIKING_SHREDDER_ROUNDS}, self.player) + or ( + ( + # Regular infesteds + state.has(ItemNames.FIREBAT, self.player) + or state.has_all({ItemNames.HELLION, ItemNames.HELLION_HELLBAT_ASPECT}, self.player) + or ( + self.advanced_tactics + and state.has_any({ItemNames.PERDITION_TURRET, ItemNames.PLANETARY_FORTRESS}, self.player) + ) + ) + and self.terran_bio_heal(state) + and ( + # Volatile infesteds + state.has(ItemNames.LIBERATOR, self.player) + or ( + self.advanced_tactics + and state.has_any({ItemNames.HERC, ItemNames.VULTURE}, self.player) + ) + ) + ) + ) + + def flashpoint_far_requirement(self, state: CollectionState) -> bool: + return self.terran_competent_comp(state) \ + and self.terran_mobile_detector(state) \ + and self.terran_defense_rating(state, True, False) >= 6 + + def enemy_shadow_tripwires_tool(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_FLASHBANG_GRENADES, ItemNames.NOVA_BLINK, ItemNames.NOVA_DOMINATION}, + self.player) + + def enemy_shadow_door_unlocks_tool(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_DOMINATION, ItemNames.NOVA_BLINK, ItemNames.NOVA_JUMP_SUIT_MODULE}, + self.player) + + def enemy_shadow_domination(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or (self.nova_ranged_weapon(state) + and (self.nova_full_stealth(state) + or state.has(ItemNames.NOVA_JUMP_SUIT_MODULE, self.player) + or (self.nova_heal(state) and self.nova_splash(state)) + ) + ) + + def enemy_shadow_first_stage(self, state: CollectionState) -> bool: + return self.enemy_shadow_domination(state) \ + and (self.story_tech_granted + or ((self.nova_full_stealth(state) and self.enemy_shadow_tripwires_tool(state)) + or (self.nova_heal(state) and self.nova_splash(state)) + ) + ) + + def enemy_shadow_second_stage(self, state: CollectionState) -> bool: + return self.enemy_shadow_first_stage(state) \ + and (self.story_tech_granted + or self.nova_splash(state) + or self.nova_heal(state) + or self.nova_escape_assist(state) + ) + + def enemy_shadow_door_controls(self, state: CollectionState) -> bool: + return self.enemy_shadow_second_stage(state) \ + and (self.story_tech_granted or self.enemy_shadow_door_unlocks_tool(state)) + + def enemy_shadow_victory(self, state: CollectionState) -> bool: + return self.enemy_shadow_door_controls(state) \ + and (self.story_tech_granted or self.nova_heal(state)) + + def dark_skies_requirement(self, state: CollectionState) -> bool: + return self.terran_common_unit(state) \ + and self.terran_beats_protoss_deathball(state) \ + and self.terran_defense_rating(state, False, True) >= 8 + + def end_game_requirement(self, state: CollectionState) -> bool: + return self.terran_competent_comp(state) \ + and self.terran_mobile_detector(state) \ + and ( + state.has_any({ItemNames.BATTLECRUISER, ItemNames.LIBERATOR, ItemNames.BANSHEE}, self.player) + or state.has_all({ItemNames.WRAITH, ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) + ) \ + and (state.has_any({ItemNames.BATTLECRUISER, ItemNames.VIKING, ItemNames.LIBERATOR}, self.player) + or (self.advanced_tactics + and state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, self.player) + ) + ) + + def __init__(self, world: World): + self.world: World = world + self.player = None if world is None else world.player + self.logic_level = get_option_value(world, 'required_tactics') + self.advanced_tactics = self.logic_level != RequiredTactics.option_standard + self.take_over_ai_allies = get_option_value(world, "take_over_ai_allies") == TakeOverAIAllies.option_true + self.kerrigan_unit_available = get_option_value(world, 'kerrigan_presence') in kerrigan_unit_available \ + and SC2Campaign.HOTS in get_enabled_campaigns(world) + self.kerrigan_levels_per_mission_completed = get_option_value(world, "kerrigan_levels_per_mission_completed") + self.kerrigan_levels_per_mission_completed_cap = get_option_value(world, "kerrigan_levels_per_mission_completed_cap") + self.kerrigan_total_level_cap = get_option_value(world, "kerrigan_total_level_cap") + self.story_tech_granted = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true + self.story_levels_granted = get_option_value(world, "grant_story_levels") != GrantStoryLevels.option_disabled + self.basic_terran_units = get_basic_units(world, SC2Race.TERRAN) + self.basic_zerg_units = get_basic_units(world, SC2Race.ZERG) + self.basic_protoss_units = get_basic_units(world, SC2Race.PROTOSS) + self.spear_of_adun_autonomously_cast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") + self.enabled_campaigns = get_enabled_campaigns(world) + self.mission_order = get_option_value(world, "mission_order") diff --git a/worlds/sc2/Starcraft2.kv b/worlds/sc2/Starcraft2.kv new file mode 100644 index 000000000000..6b112c2f00a6 --- /dev/null +++ b/worlds/sc2/Starcraft2.kv @@ -0,0 +1,28 @@ + + scroll_type: ["content", "bars"] + bar_width: dp(12) + effect_cls: "ScrollEffect" + + + cols: 1 + size_hint_y: None + height: self.minimum_height + 15 + padding: [5,0,dp(12),0] + +: + cols: 1 + +: + rows: 1 + +: + cols: 1 + spacing: [0,5] + +: + text_size: self.size + markup: True + halign: 'center' + valign: 'middle' + padding: [5,0,5,0] + outline_width: 1 diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py new file mode 100644 index 000000000000..ec8a447d931e --- /dev/null +++ b/worlds/sc2/__init__.py @@ -0,0 +1,490 @@ +import typing +from dataclasses import fields + +from typing import List, Set, Iterable, Sequence, Dict, Callable, Union +from math import floor, ceil +from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification +from worlds.AutoWorld import WebWorld, World +from . import ItemNames +from .Items import StarcraftItem, filler_items, get_item_table, get_full_item_list, \ + get_basic_units, ItemData, upgrade_included_names, progressive_if_nco, kerrigan_actives, kerrigan_passives, \ + kerrigan_only_passives, progressive_if_ext, not_balanced_starting_units, spear_of_adun_calldowns, \ + spear_of_adun_castable_passives, nova_equipment +from .ItemGroups import item_name_groups +from .Locations import get_locations, LocationType, get_location_types, get_plando_locations +from .Regions import create_regions +from .Options import get_option_value, LocationInclusion, KerriganLevelItemDistribution, \ + KerriganPresence, KerriganPrimalStatus, RequiredTactics, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, \ + get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options +from .PoolFilter import filter_items, get_item_upgrades, UPGRADABLE_ITEMS, missions_in_mission_table, get_used_races +from .MissionTables import MissionInfo, SC2Campaign, lookup_name_to_mission, SC2Mission, \ + SC2Race + + +class Starcraft2WebWorld(WebWorld): + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Starcraft 2 randomizer connected to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["TheCondor", "Phaneros"] + ) + + setup_fr = Tutorial( + setup_en.tutorial_name, + setup_en.description, + "Français", + "setup_fr.md", + "setup/fr", + ["Neocerber"] + ) + + tutorials = [setup_en, setup_fr] + + +class SC2World(World): + """ + StarCraft II is a science fiction real-time strategy video game developed and published by Blizzard Entertainment. + Play as one of three factions across four campaigns in a battle for supremacy of the Koprulu Sector. + """ + + game = "Starcraft 2" + web = Starcraft2WebWorld() + + item_name_to_id = {name: data.code for name, data in get_full_item_list().items()} + location_name_to_id = {location.name: location.code for location in get_locations(None)} + options_dataclass = Starcraft2Options + options: Starcraft2Options + + item_name_groups = item_name_groups + locked_locations: typing.List[str] + location_cache: typing.List[Location] + mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = {} + final_mission_id: int + victory_item: str + required_client_version = 0, 4, 5 + + def __init__(self, multiworld: MultiWorld, player: int): + super(SC2World, self).__init__(multiworld, player) + self.location_cache = [] + self.locked_locations = [] + + def create_item(self, name: str) -> Item: + data = get_full_item_list()[name] + return StarcraftItem(name, data.classification, data.code, self.player) + + def create_regions(self): + self.mission_req_table, self.final_mission_id, self.victory_item = create_regions( + self, get_locations(self), self.location_cache + ) + + def create_items(self): + setup_events(self.player, self.locked_locations, self.location_cache) + + excluded_items = get_excluded_items(self) + + starter_items = assign_starter_items(self, excluded_items, self.locked_locations, self.location_cache) + + fill_resource_locations(self, self.locked_locations, self.location_cache) + + pool = get_item_pool(self, self.mission_req_table, starter_items, excluded_items, self.location_cache) + + fill_item_pool_with_dummy_items(self, self.locked_locations, self.location_cache, pool) + + self.multiworld.itempool += pool + + def set_rules(self): + self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player) + + def get_filler_item_name(self) -> str: + return self.random.choice(filler_items) + + def fill_slot_data(self): + slot_data = {} + for option_name in [field.name for field in fields(Starcraft2Options)]: + option = get_option_value(self, option_name) + if type(option) in {str, int}: + slot_data[option_name] = int(option) + slot_req_table = {} + + # Serialize data + for campaign in self.mission_req_table: + slot_req_table[campaign.id] = {} + for mission in self.mission_req_table[campaign]: + slot_req_table[campaign.id][mission] = self.mission_req_table[campaign][mission]._asdict() + # Replace mission objects with mission IDs + slot_req_table[campaign.id][mission]["mission"] = slot_req_table[campaign.id][mission]["mission"].id + + for index in range(len(slot_req_table[campaign.id][mission]["required_world"])): + # TODO this is a band-aid, sometimes the mission_req_table already contains dicts + # as far as I can tell it's related to having multiple vanilla mission orders + if not isinstance(slot_req_table[campaign.id][mission]["required_world"][index], dict): + slot_req_table[campaign.id][mission]["required_world"][index] = slot_req_table[campaign.id][mission]["required_world"][index]._asdict() + + enabled_campaigns = get_enabled_campaigns(self) + slot_data["plando_locations"] = get_plando_locations(self) + slot_data["nova_covert_ops_only"] = (enabled_campaigns == {SC2Campaign.NCO}) + slot_data["mission_req"] = slot_req_table + slot_data["final_mission"] = self.final_mission_id + slot_data["version"] = 3 + + if SC2Campaign.HOTS not in enabled_campaigns: + slot_data["kerrigan_presence"] = KerriganPresence.option_not_present + return slot_data + + +def setup_events(player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]): + for location in location_cache: + if location.address is None: + item = Item(location.name, ItemClassification.progression, None, player) + + locked_locations.append(location.name) + + location.place_locked_item(item) + + +def get_excluded_items(world: World) -> Set[str]: + excluded_items: Set[str] = set(get_option_value(world, 'excluded_items')) + for item in world.multiworld.precollected_items[world.player]: + excluded_items.add(item.name) + locked_items: Set[str] = set(get_option_value(world, 'locked_items')) + # Starter items are also excluded items + starter_items: Set[str] = set(get_option_value(world, 'start_inventory')) + item_table = get_full_item_list() + soa_presence = get_option_value(world, "spear_of_adun_presence") + soa_autocast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") + enabled_campaigns = get_enabled_campaigns(world) + + # Ensure no item is both guaranteed and excluded + invalid_items = excluded_items.intersection(locked_items) + invalid_count = len(invalid_items) + # Don't count starter items that can appear multiple times + invalid_count -= len([item for item in starter_items.intersection(locked_items) if item_table[item].quantity != 1]) + if invalid_count > 0: + raise Exception(f"{invalid_count} item{'s are' if invalid_count > 1 else ' is'} both locked and excluded from generation. Please adjust your excluded items and locked items.") + + def smart_exclude(item_choices: Set[str], choices_to_keep: int): + expected_choices = len(item_choices) + if expected_choices == 0: + return + item_choices = set(item_choices) + starter_choices = item_choices.intersection(starter_items) + excluded_choices = item_choices.intersection(excluded_items) + item_choices.difference_update(excluded_choices) + item_choices.difference_update(locked_items) + candidates = sorted(item_choices) + exclude_amount = min(expected_choices - choices_to_keep - len(excluded_choices) + len(starter_choices), len(candidates)) + if exclude_amount > 0: + excluded_items.update(world.random.sample(candidates, exclude_amount)) + + # Nova gear exclusion if NCO not in campaigns + if SC2Campaign.NCO not in enabled_campaigns: + excluded_items = excluded_items.union(nova_equipment) + + kerrigan_presence = get_option_value(world, "kerrigan_presence") + # Exclude Primal Form item if option is not set or Kerrigan is unavailable + if get_option_value(world, "kerrigan_primal_status") != KerriganPrimalStatus.option_item or \ + (kerrigan_presence in {KerriganPresence.option_not_present, KerriganPresence.option_not_present_and_no_passives}): + excluded_items.add(ItemNames.KERRIGAN_PRIMAL_FORM) + + # no Kerrigan & remove all passives => remove all abilities + if kerrigan_presence == KerriganPresence.option_not_present_and_no_passives: + for tier in range(7): + smart_exclude(kerrigan_actives[tier].union(kerrigan_passives[tier]), 0) + else: + # no Kerrigan, but keep non-Kerrigan passives + if kerrigan_presence == KerriganPresence.option_not_present: + smart_exclude(kerrigan_only_passives, 0) + for tier in range(7): + smart_exclude(kerrigan_actives[tier], 0) + + # SOA exclusion, other cases are handled by generic race logic + if (soa_presence == SpearOfAdunPresence.option_lotv_protoss and SC2Campaign.LOTV not in enabled_campaigns) \ + or soa_presence == SpearOfAdunPresence.option_not_present: + excluded_items.update(spear_of_adun_calldowns) + if (soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_lotv_protoss \ + and SC2Campaign.LOTV not in enabled_campaigns) \ + or soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: + excluded_items.update(spear_of_adun_castable_passives) + + return excluded_items + + +def assign_starter_items(world: World, excluded_items: Set[str], locked_locations: List[str], location_cache: typing.List[Location]) -> List[Item]: + starter_items: List[Item] = [] + non_local_items = get_option_value(world, "non_local_items") + starter_unit = get_option_value(world, "starter_unit") + enabled_campaigns = get_enabled_campaigns(world) + first_mission = get_first_mission(world.mission_req_table) + # Ensuring that first mission is completable + if starter_unit == StarterUnit.option_off: + starter_mission_locations = [location.name for location in location_cache + if location.parent_region.name == first_mission + and location.access_rule == Location.access_rule] + if not starter_mission_locations: + # Force early unit if first mission is impossible without one + starter_unit = StarterUnit.option_any_starter_unit + + if starter_unit != StarterUnit.option_off: + first_race = lookup_name_to_mission[first_mission].race + + if first_race == SC2Race.ANY: + # If the first mission is a logic-less no-build + mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = world.mission_req_table + races = get_used_races(mission_req_table, world) + races.remove(SC2Race.ANY) + if lookup_name_to_mission[first_mission].race in races: + # The campaign's race is in (At least one mission that's not logic-less no-build exists) + first_race = lookup_name_to_mission[first_mission].campaign.race + elif len(races) > 0: + # The campaign only has logic-less no-build missions. Find any other valid race + first_race = world.random.choice(list(races)) + + if first_race != SC2Race.ANY: + # The race of the early unit has been chosen + basic_units = get_basic_units(world, first_race) + if starter_unit == StarterUnit.option_balanced: + basic_units = basic_units.difference(not_balanced_starting_units) + if first_mission == SC2Mission.DARK_WHISPERS.mission_name: + # Special case - you don't have a logicless location but need an AA + basic_units = basic_units.difference( + {ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.BLOOD_HUNTER, + ItemNames.AVENGER, ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD}) + if first_mission == SC2Mission.SUDDEN_STRIKE.mission_name: + # Special case - cliffjumpers + basic_units = {ItemNames.REAPER, ItemNames.GOLIATH, ItemNames.SIEGE_TANK, ItemNames.VIKING, ItemNames.BANSHEE} + local_basic_unit = sorted(item for item in basic_units if item not in non_local_items and item not in excluded_items) + if not local_basic_unit: + # Drop non_local_items constraint + local_basic_unit = sorted(item for item in basic_units if item not in excluded_items) + if not local_basic_unit: + raise Exception("Early Unit: At least one basic unit must be included") + + unit: Item = add_starter_item(world, excluded_items, local_basic_unit) + starter_items.append(unit) + + # NCO-only specific rules + if first_mission == SC2Mission.SUDDEN_STRIKE.mission_name: + support_item: Union[str, None] = None + if unit.name == ItemNames.REAPER: + support_item = ItemNames.REAPER_SPIDER_MINES + elif unit.name == ItemNames.GOLIATH: + support_item = ItemNames.GOLIATH_JUMP_JETS + elif unit.name == ItemNames.SIEGE_TANK: + support_item = ItemNames.SIEGE_TANK_JUMP_JETS + elif unit.name == ItemNames.VIKING: + support_item = ItemNames.VIKING_SMART_SERVOS + if support_item is not None: + starter_items.append(add_starter_item(world, excluded_items, [support_item])) + starter_items.append(add_starter_item(world, excluded_items, [ItemNames.NOVA_JUMP_SUIT_MODULE])) + starter_items.append( + add_starter_item(world, excluded_items, + [ + ItemNames.NOVA_HELLFIRE_SHOTGUN, + ItemNames.NOVA_PLASMA_RIFLE, + ItemNames.NOVA_PULSE_GRENADES + ])) + if enabled_campaigns == {SC2Campaign.NCO}: + starter_items.append(add_starter_item(world, excluded_items, [ItemNames.LIBERATOR_RAID_ARTILLERY])) + + starter_abilities = get_option_value(world, 'start_primary_abilities') + assert isinstance(starter_abilities, int) + if starter_abilities: + ability_count = starter_abilities + ability_tiers = [0, 1, 3] + world.random.shuffle(ability_tiers) + if ability_count > 3: + ability_tiers.append(6) + for tier in ability_tiers: + abilities = kerrigan_actives[tier].union(kerrigan_passives[tier]).difference(excluded_items, non_local_items) + if not abilities: + abilities = kerrigan_actives[tier].union(kerrigan_passives[tier]).difference(excluded_items) + if abilities: + ability_count -= 1 + starter_items.append(add_starter_item(world, excluded_items, list(abilities))) + if ability_count == 0: + break + + return starter_items + + +def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> str: + # The first world should also be the starting world + campaigns = mission_req_table.keys() + lowest_id = min([campaign.id for campaign in campaigns]) + first_campaign = [campaign for campaign in campaigns if campaign.id == lowest_id][0] + first_mission = list(mission_req_table[first_campaign])[0] + return first_mission + + +def add_starter_item(world: World, excluded_items: Set[str], item_list: Sequence[str]) -> Item: + + item_name = world.random.choice(sorted(item_list)) + + excluded_items.add(item_name) + + item = create_item_with_correct_settings(world.player, item_name) + + world.multiworld.push_precollected(item) + + return item + + +def get_item_pool(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], + starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]: + pool: List[Item] = [] + + # For the future: goal items like Artifact Shards go here + locked_items = [] + + # YAML items + yaml_locked_items = get_option_value(world, 'locked_items') + assert not isinstance(yaml_locked_items, int) + + # Adjust generic upgrade availability based on options + include_upgrades = get_option_value(world, 'generic_upgrade_missions') == 0 + upgrade_items = get_option_value(world, 'generic_upgrade_items') + assert isinstance(upgrade_items, int) + + # Include items from outside main campaigns + item_sets = {'wol', 'hots', 'lotv'} + if get_option_value(world, 'nco_items') \ + or SC2Campaign.NCO in get_enabled_campaigns(world): + item_sets.add('nco') + if get_option_value(world, 'bw_items'): + item_sets.add('bw') + if get_option_value(world, 'ext_items'): + item_sets.add('ext') + + def allowed_quantity(name: str, data: ItemData) -> int: + if name in excluded_items \ + or data.type == "Upgrade" and (not include_upgrades or name not in upgrade_included_names[upgrade_items]) \ + or not data.origin.intersection(item_sets): + return 0 + elif name in progressive_if_nco and 'nco' not in item_sets: + return 1 + elif name in progressive_if_ext and 'ext' not in item_sets: + return 1 + else: + return data.quantity + + for name, data in get_item_table().items(): + for _ in range(allowed_quantity(name, data)): + item = create_item_with_correct_settings(world.player, name) + if name in yaml_locked_items: + locked_items.append(item) + else: + pool.append(item) + + existing_items = starter_items + [item for item in world.multiworld.precollected_items[world.player] if item not in starter_items] + existing_names = [item.name for item in existing_items] + + # Check the parent item integrity, exclude items + pool[:] = [item for item in pool if pool_contains_parent(item, pool + locked_items + existing_items)] + + # Removing upgrades for excluded items + for item_name in excluded_items: + if item_name in existing_names: + continue + invalid_upgrades = get_item_upgrades(pool, item_name) + for invalid_upgrade in invalid_upgrades: + pool.remove(invalid_upgrade) + + fill_pool_with_kerrigan_levels(world, pool) + filtered_pool = filter_items(world, mission_req_table, location_cache, pool, existing_items, locked_items) + return filtered_pool + + +def fill_item_pool_with_dummy_items(self: SC2World, locked_locations: List[str], + location_cache: List[Location], pool: List[Item]): + for _ in range(len(location_cache) - len(locked_locations) - len(pool)): + item = create_item_with_correct_settings(self.player, self.get_filler_item_name()) + pool.append(item) + + +def create_item_with_correct_settings(player: int, name: str) -> Item: + data = get_full_item_list()[name] + + item = Item(name, data.classification, data.code, player) + + return item + + +def pool_contains_parent(item: Item, pool: Iterable[Item]): + item_data = get_full_item_list().get(item.name) + if item_data.parent_item is None: + # The item has not associated parent, the item is valid + return True + parent_item = item_data.parent_item + # Check if the pool contains the parent item + return parent_item in [pool_item.name for pool_item in pool] + + +def fill_resource_locations(world: World, locked_locations: List[str], location_cache: List[Location]): + """ + Filters the locations in the world using a trash or Nothing item + :param multiworld: + :param player: + :param locked_locations: + :param location_cache: + :return: + """ + open_locations = [location for location in location_cache if location.item is None] + plando_locations = get_plando_locations(world) + resource_location_types = get_location_types(world, LocationInclusion.option_resources) + location_data = {sc2_location.name: sc2_location for sc2_location in get_locations(world)} + for location in open_locations: + # Go through the locations that aren't locked yet (early unit, etc) + if location.name not in plando_locations: + # The location is not plando'd + sc2_location = location_data[location.name] + if sc2_location.type in resource_location_types: + item_name = world.random.choice(filler_items) + item = create_item_with_correct_settings(world.player, item_name) + location.place_locked_item(item) + locked_locations.append(location.name) + + +def place_exclusion_item(item_name, location, locked_locations, player): + item = create_item_with_correct_settings(player, item_name) + location.place_locked_item(item) + locked_locations.append(location.name) + + +def fill_pool_with_kerrigan_levels(world: World, item_pool: List[Item]): + total_levels = get_option_value(world, "kerrigan_level_item_sum") + if get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ + or total_levels == 0 \ + or SC2Campaign.HOTS not in get_enabled_campaigns(world): + return + + def add_kerrigan_level_items(level_amount: int, item_amount: int): + name = f"{level_amount} Kerrigan Level" + if level_amount > 1: + name += "s" + for _ in range(item_amount): + item_pool.append(create_item_with_correct_settings(world.player, name)) + + sizes = [70, 35, 14, 10, 7, 5, 2, 1] + option = get_option_value(world, "kerrigan_level_item_distribution") + + assert isinstance(option, int) + assert isinstance(total_levels, int) + + if option in (KerriganLevelItemDistribution.option_vanilla, KerriganLevelItemDistribution.option_smooth): + distribution = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + if option == KerriganLevelItemDistribution.option_vanilla: + distribution = [32, 0, 0, 1, 3, 0, 0, 0, 1, 1] + else: # Smooth + distribution = [0, 0, 0, 1, 1, 2, 2, 2, 1, 1] + for tier in range(len(distribution)): + add_kerrigan_level_items(tier + 1, distribution[tier]) + else: + size = sizes[option - 2] + round_func: Callable[[float], int] = round + if total_levels > 70: + round_func = floor + else: + round_func = ceil + add_kerrigan_level_items(size, round_func(float(total_levels) / size)) diff --git a/worlds/sc2/docs/contributors.md b/worlds/sc2/docs/contributors.md new file mode 100644 index 000000000000..5b62466d7e45 --- /dev/null +++ b/worlds/sc2/docs/contributors.md @@ -0,0 +1,42 @@ +# Contributors +Contibutors are listed with preferred or Discord names first, with github usernames prepended with an `@` + +## Update 2024.0 +### Code Changes +* Ziktofel (@Ziktofel) +* Salzkorn (@Salzkorn) +* EnvyDragon (@EnvyDragon) +* Phanerus (@MatthewMarinets) +* Madi Sylveon (@MadiMadsen) +* Magnemania (@Magnemania) +* Subsourian (@Subsourian) +* Hopop (@hopop201) +* Alice Voltaire (@AliceVoltaire) +* Genderdruid (@ArchonofFail) +* CrazedCollie (@FoxOfWar) + +### Additional Beta testing and bug reports +* Varcklen (@Varcklen) +* BicolourSnake (@Bicoloursnake) +* @NobleXenon +* Severencir (@Severencir) +* neocerber (@neocerber) +* Mati (@Matiya-star) +* Ixzine +* sweetox +* 8thDaughterOfFrost +* The M8 +* Berserker (@Berserker66) +* KaitoKid +* Sheen +* ProfBytes +* IncoherentOrange +* eudaimonistic +* Figment + +## Older versions +Not all contributors to older versions of Archipelago Starcraft 2 are known. + +TheCondor (@TheCondor07) is the original maintainer of the project. Other known contributors include: +* soldieroforder +* Berserker (@Berserker66) diff --git a/worlds/sc2/docs/en_Starcraft 2.md b/worlds/sc2/docs/en_Starcraft 2.md new file mode 100644 index 000000000000..06464e3cd2fd --- /dev/null +++ b/worlds/sc2/docs/en_Starcraft 2.md @@ -0,0 +1,67 @@ +# Starcraft 2 + +## Game page in other languages: +* [Français](/games/Starcraft%202/info/fr) + +## What does randomization do to this game? + +The following unlocks are randomized as items: +1. Your ability to build any non-worker unit. +2. Unit specific upgrades including some combinations not available in the vanilla campaigns, such as both strain choices simultaneously for Zerg and every Spear of Adun upgrade simultaneously for Protoss! +3. Your ability to get the generic unit upgrades, such as attack and armour upgrades. +4. Other miscellaneous upgrades such as laboratory upgrades and mercenaries for Terran, Kerrigan levels and upgrades for Zerg, and Spear of Adun upgrades for Protoss. +5. Small boosts to your starting mineral, vespene gas, and supply totals on each mission. + +You find items by making progress in these categories: +* Completing missions +* Completing bonus objectives (like by gathering lab research material in Wings of Liberty) +* Reaching milestones in the mission, such as completing part of a main objective +* Completing challenges based on achievements in the base game, such as clearing all Zerg on Devil's Playground + +Except for mission completion, these categories can be disabled in the game's settings. For instance, you can disable getting items for reaching required milestones. + +When you receive items, they will immediately become available, even during a mission, and you will be +notified via a text box in the top-right corner of the game screen. Item unlocks are also logged in the Archipelago client. + +Missions are launched through the Starcraft 2 Archipelago client, through the Starcraft 2 Launcher tab. The between mission segments on the Hyperion, the Leviathan, and the Spear of Adun are not included. Additionally, metaprogression currencies such as credits and Solarite are not used. + +## What is the goal of this game when randomized? + +The goal is to beat the final mission in the mission order. The yaml configuration file controls the mission order and how missions are shuffled. + +## What non-randomized changes are there from vanilla Starcraft 2? + +1. Some missions have more vespene geysers available to allow a wider variety of units. +2. Many new units and upgrades have been added as items, coming from co-op, melee, later campaigns, later expansions, brood war, and original ideas. +3. Higher-tech production structures, including Factories, Starports, Robotics Facilities, and Stargates, no longer have tech requirements. +4. Zerg missions have been adjusted to give the player a starting Lair where they would only have Hatcheries. +5. Upgrades with a downside have had the downside removed, such as automated refineries costing more or tech reactors taking longer to build. +6. Unit collision within the vents in Enemy Within has been adjusted to allow larger units to travel through them without getting stuck in odd places. +7. Several vanilla bugs have been fixed. + +## Which of my items can be in another player's world? + +By default, any of StarCraft 2's items (specified above) can be in another player's world. See the +[Advanced YAML Guide](/tutorial/Archipelago/advanced_settings/en) +for more information on how to change this. + +## Unique Local Commands + +The following commands are only available when using the Starcraft 2 Client to play with Archipelago. You can list them any time in the client with `/help`. + +* `/download_data` Download the most recent release of the necessary files for playing SC2 with Archipelago. Will overwrite existing files +* `/difficulty [difficulty]` Overrides the difficulty set for the world. + * Options: casual, normal, hard, brutal +* `/game_speed [game_speed]` Overrides the game speed for the world + * Options: default, slower, slow, normal, fast, faster +* `/color [faction] [color]` Changes your color for one of your playable factions. + * Faction options: raynor, kerrigan, primal, protoss, nova + * Color options: white, red, blue, teal, purple, yellow, orange, green, lightpink, violet, lightgrey, darkgreen, brown, lightgreen, darkgrey, pink, rainbow, random, default +* `/option [option_name] [option_value]` Sets an option normally controlled by your yaml after generation. + * Run without arguments to list all options. + * Options pertain to automatic cutscene skipping, Kerrigan presence, Spear of Adun presence, starting resource amounts, controlling AI allies, etc. +* `/disable_mission_check` Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play the next mission in a chain the other player is doing. +* `/play [mission_id]` Starts a Starcraft 2 mission based off of the mission_id provided +* `/available` Get what missions are currently available to play +* `/unfinished` Get what missions are currently available to play and have not had all locations checked +* `/set_path [path]` Manually set the SC2 install directory (if the automatic detection fails) diff --git a/worlds/sc2/docs/fr_Starcraft 2.md b/worlds/sc2/docs/fr_Starcraft 2.md new file mode 100644 index 000000000000..4fcc8e689baa --- /dev/null +++ b/worlds/sc2/docs/fr_Starcraft 2.md @@ -0,0 +1,95 @@ +# *StarCraft 2* + +## Quel est l'effet de la *randomization* sur ce jeu ? + +Les éléments qui suivent sont les *items* qui sont *randomized* et qui doivent être débloqués pour être utilisés dans +le jeu: +1. La capacité de produire des unités, excepté les drones/probes/scv. +2. Des améliorations spécifiques à certaines unités incluant quelques combinaisons qui ne sont pas disponibles dans les +campagnes génériques, comme le fait d'avoir les deux types d'évolution en même temps pour une unité *Zerg* et toutes +les améliorations de la *Spear of Adun* simultanément pour les *Protoss*. +3. L'accès aux améliorations génériques des unités, e.g. les améliorations d'attaque et d'armure. +4. D'autres améliorations diverses telles que les améliorations de laboratoire et les mercenaires pour les *Terran*, +les niveaux et les améliorations de Kerrigan pour les *Zerg*, et les améliorations de la *Spear of Adun* pour les +*Protoss*. +5. Avoir des *minerals*, du *vespene gas*, et du *supply* au début de chaque mission. + +Les *items* sont trouvés en accomplissant du progrès dans les catégories suivantes: +* Terminer des missions +* Réussir des objectifs supplémentaires (e.g., récolter le matériel pour les recherches dans *Wings of Liberty*) +* Atteindre des étapes importantes dans la mission, e.g. réussir des sous-objectifs +* Réussir des défis basés sur les succès du jeu de base, e.g. éliminer tous les *Zerg* dans la mission +*Devil's Playground* + +Ces catégories, outre la première, peuvent être désactivées dans les options du jeu. +Par exemple, vous pouvez désactiver le fait d'obtenir des *items* lorsque des étapes importantes d'une mission sont +accomplies. + +Quand vous recevez un *item*, il devient immédiatement disponible, même pendant une mission, et vous serez avertis via +la boîte de texte situé dans le coin en haut à droite de *StarCraft 2*. +L'acquisition d'un *item* est aussi indiquée dans le client d'Archipelago. + +Les missions peuvent être lancées par le client *StarCraft 2 Archipelago*, via l'interface graphique de l'onglet +*StarCraft 2 Launcher*. +Les segments qui se passent sur l'*Hyperion*, un Léviathan et la *Spear of Adun* ne sont pas inclus. +De plus, les points de progression tels que les crédits ou la Solarite ne sont pas utilisés dans *StarCraft 2 +Archipelago*. + +## Quel est le but de ce jeu quand il est *randomized*? + +Le but est de réussir la mission finale dans la disposition des missions (e.g. *blitz*, *grid*, etc.). +Les choix faits dans le fichier *yaml* définissent la disposition des missions et comment elles sont mélangées. + +## Quelles sont les modifications non aléatoires comparativement à la version de base de *StarCraft 2* + +1. Certaines des missions ont plus de *vespene geysers* pour permettre l'utilisation d'une plus grande variété d'unités. +2. Plusieurs unités et améliorations ont été ajoutées sous la forme d*items*. +Ils proviennent de la version *co-op*, *melee*, des autres campagnes, d'expansions ultérieures, de *Brood War*, ou de +l'imagination des développeurs de *StarCraft 2 Archipelago*. +3. Les structures de production, e.g. *Factory*, *Starport*, *Robotics Facility*, and *Stargate*, n'ont plus +d'exigences technologiques. +4. Les missions avec la race *Zerg* ont été modifiées pour que les joueurs débuttent avec un *Lair* lorsqu'elles +commençaient avec une *Hatchery*. +5. Les désavantages des améliorations ont été enlevés, e.g. *automated refinery* qui coûte plus cher ou les *tech +reactors* qui prennent plus de temps à construire. +6. La collision des unités dans les couloirs de la mission *Enemy Within* a été ajustée pour permettre des unités +plus larges de les traverser sans être coincés dans des endroits étranges. +7. Plusieurs *bugs* du jeu original ont été corrigés. + +## Quels sont les *items* qui peuvent être dans le monde d'un autre joueur? + +Par défaut, tous les *items* de *StarCraft 2 Archipelago* (voir la section précédente) peuvent être dans le monde d'un +autre joueur. +Consulter [*Advanced YAML Guide*](/tutorial/Archipelago/advanced_settings/en) pour savoir comment +changer ça. + +## Commandes du client qui sont uniques à ce jeu + +Les commandes qui suivent sont seulement disponibles uniquement pour le client de *StarCraft 2 Archipelago*. +Vous pouvez les afficher en utilisant la commande `/help` dans le client de *StarCraft 2 Archipelago*. +Toutes ces commandes affectent seulement le client où elles sont utilisées. + +* `/download_data` Télécharge les versions les plus récentes des fichiers pour jouer à *StarCraft 2 Archipelago*. +Les fichiers existants vont être écrasés. +* `/difficulty [difficulty]` Remplace la difficulté choisie pour le monde. + * Les options sont *casual*, *normal*, *hard*, et *brutal*. +* `/game_speed [game_speed]` Remplace la vitesse du jeu pour le monde. + * Les options sont *default*, *slower*, *slow*, *normal*, *fast*, and *faster*. +* `/color [faction] [color]` Remplace la couleur d'une des *factions* qui est jouable. + * Les options de *faction*: raynor, kerrigan, primal, protoss, nova. + * Les options de couleur: *white*, *red*, *blue*, *teal*, *purple*, *yellow*, *orange*, *green*, *lightpink*, +*violet*, *lightgrey*, *darkgreen*, *brown*, *lightgreen*, *darkgrey*, *pink*, *rainbow*, *random*, *default*. +* `/option [option_name] [option_value]` Permet de changer un option normalement définit dans le *yaml*. + * Si la commande est lancée sans option, la liste des options qui sont modifiables va être affichée. + * Les options qui peuvent être changées avec cette commande incluent sauter les cinématiques automatiquement, la +présence de Kerrigan dans les missions, la disponibilité de la *Spear of Adun*, la quantité de ressources +supplémentaires données au début des missions, la capacité de contrôler les alliées IA, etc. +* `/disable_mission_check` Désactive les requit pour lancer les missions. +Cette option a pour but de permettre de jouer en mode coopératif en permettant à un joueur de jouer à la prochaine +mission de la chaîne qu'un autre joueur est en train d'entamer. +* `/play [mission_id]` Lance la mission correspondant à l'identifiant donné. +* `/available` Affiche les missions qui sont présentement accessibles. +* `/unfinished` Affiche les missions qui sont présentement accessibles et dont certains des objectifs permettant +l'accès à un *item* n'ont pas été accomplis. +* `/set_path [path]` Permet de définir manuellement où *StarCraft 2* est installé ce qui est pertinent seulement si la +détection automatique de cette dernière échoue. diff --git a/worlds/sc2/docs/setup_en.md b/worlds/sc2/docs/setup_en.md new file mode 100644 index 000000000000..991ed57e8741 --- /dev/null +++ b/worlds/sc2/docs/setup_en.md @@ -0,0 +1,143 @@ +# StarCraft 2 Randomizer Setup Guide + +This guide contains instructions on how to install and troubleshoot the StarCraft 2 Archipelago client, as well as where +to obtain a config file for StarCraft 2. + +## Required Software + +- [StarCraft 2](https://starcraft2.com/en-us/) +- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) + +## How do I install this randomizer? + +1. Install StarCraft 2 and Archipelago using the links above. The StarCraft 2 Archipelago client is downloaded by the Archipelago installer. + - Linux users should also follow the instructions found at the bottom of this page + (["Running in Linux"](#running-in-linux)). +2. Run ArchipelagoStarcraft2Client.exe. + - macOS users should instead follow the instructions found at ["Running in macOS"](#running-in-macos) for this step only. +3. Type the command `/download_data`. This will automatically install the Maps and Data files from the third link above. + +## Where do I get a config file (aka "YAML") for this game? + +Yaml files are configuration files that tell Archipelago how you'd like your game to be randomized, even if you're only using default options. +When you're setting up a multiworld, every world needs its own yaml file. + +There are three basic ways to get a yaml: +* You can go to the [Player Options](/games/Starcraft%202/player-options) page, set your options in the GUI, and export the yaml. +* You can generate a template, either by downloading it from the [Player Options](/games/Starcraft%202/player-options) page or by generating it from the Launcher (ArchipelagoLauncher.exe). The template includes descriptions of each option, you just have to edit it in your text editor of choice. +* You can ask someone else to share their yaml to use it for yourself or adjust it as you wish. + +Remember the name you enter in the options page or in the yaml file, you'll need it to connect later! + +Check out [Creating a YAML](/tutorial/Archipelago/setup/en#creating-a-yaml) for more game-agnostic information. + +### Common yaml questions +#### How do I know I set my yaml up correctly? + +The simplest way to check is to use the website [validator](/check). + +You can also test it by attempting to generate a multiworld with your yaml. Save your yaml to the Players/ folder within your Archipelago installation and run ArchipelagoGenerate.exe. You should see a new .zip file within the output/ folder of your Archipelago installation if things worked correctly. It's advisable to run ArchipelagoGenerate through a terminal so that you can see the printout, which will include any errors and the precise output file name if it's successful. If you don't like terminals, you can also check the log file in the logs/ folder. + +#### What does Progression Balancing do? + +For Starcraft 2, not much. It's an Archipelago-wide option meant to shift required items earlier in the playthrough, but Starcraft 2 tends to be much more open in what items you can use. As such, this adjustment isn't very noticeable. It can also increase generation times, so we generally recommend turning it off. + +#### How do I specify items in a list, like in excluded items? + +You can look up the syntax for yaml collections in the [YAML specification](https://yaml.org/spec/1.2.2/#21-collections). For lists, every item goes on its own line, started with a hyphen: + +```yaml +excluded_items: + - Battlecruiser + - Drop-Pods (Kerrigan Tier 7) +``` + +An empty list is just a matching pair of square brackets: `[]`. That's the default value in the template, which should let you know to use this syntax. + +#### How do I specify items for the starting inventory? + +The starting inventory is a YAML mapping rather than a list, which associates an item with the amount you start with. The syntax looks like the item name, followed by a colon, then a whitespace character, and then the value: + +```yaml +start_inventory: + Micro-Filtering: 1 + Additional Starting Vespene: 5 +``` + +An empty mapping is just a matching pair of curly braces: `{}`. That's the default value in the template, which should let you know to use this syntax. + +#### How do I know the exact names of items and locations? + +The [*datapackage*](/datapackage) page of the Archipelago website provides a complete list of the items and locations for each game that it currently supports, including StarCraft 2. + +You can also look up a complete list of the item names in the [Icon Repository](https://matthewmarinets.github.io/ap_sc2_icons/) page. +This page also contains supplementary information of each item. +However, the items shown in that page might differ from those shown in the datapackage page of Archipelago since the former is generated, most of the time, from beta versions of StarCraft 2 Archipelago undergoing development. + +As for the locations, you can see all the locations associated to a mission in your world by placing your cursor over the mission in the 'StarCraft 2 Launcher' tab in the client. + +## How do I join a MultiWorld game? + +1. Run ArchipelagoStarcraft2Client.exe. + - macOS users should instead follow the instructions found at ["Running in macOS"](#running-in-macos) for this step only. +2. Type `/connect [server ip]`. + - If you're running through the website, the server IP should be displayed near the top of the room page. +3. Type your slot name from your YAML when prompted. +4. If the server has a password, enter that when prompted. +5. Once connected, switch to the 'StarCraft 2 Launcher' tab in the client. There, you can see all the missions in your world. Unreachable missions will have greyed-out text. Just click on an available mission to start it! + +## The game isn't launching when I try to start a mission. + +First, check the log file for issues (stored at `[Archipelago Directory]/logs/SC2Client.txt`). If you can't figure out +the log file, visit our [Discord's](https://discord.com/invite/8Z65BR2) tech-support channel for help. Please include a +specific description of what's going wrong and attach your log file to your message. + +## Running in macOS + +To run StarCraft 2 through Archipelago in macOS, you will need to run the client via source as seen here: [macOS Guide](/tutorial/Archipelago/mac/en). Note: to launch the client, you will need to run the command `python3 Starcraft2Client.py`. + +## Running in Linux + +To run StarCraft 2 through Archipelago in Linux, you will need to install the game using Wine, then run the Linux build +of the Archipelago client. + +Make sure you have StarCraft 2 installed using Wine, and that you have followed the +[installation procedures](#how-do-i-install-this-randomizer?) to add the Archipelago maps to the correct location. You will not +need to copy the .dll files. If you're having trouble installing or running StarCraft 2 on Linux, I recommend using the +Lutris installer. + +Copy the following into a .sh file, replacing the values of **WINE** and **SC2PATH** variables with the relevant +locations, as well as setting **PATH_TO_ARCHIPELAGO** to the directory containing the AppImage if it is not in the same +folder as the script. + +```sh +# Let the client know we're running SC2 in Wine +export SC2PF=WineLinux +export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +# FIXME Replace with path to the version of Wine used to run SC2 +export WINE="/usr/bin/wine" + +# FIXME Replace with path to StarCraft II install folder +export SC2PATH="/home/user/Games/starcraft-ii/drive_c/Program Files (x86)/StarCraft II/" + +# FIXME Set to directory which contains Archipelago AppImage file +PATH_TO_ARCHIPELAGO= + +# Gets the latest version of Archipelago AppImage in PATH_TO_ARCHIPELAGO. +# If PATH_TO_ARCHIPELAGO is not set, this defaults to the directory containing +# this script file. +ARCHIPELAGO="$(ls ${PATH_TO_ARCHIPELAGO:-$(dirname $0)}/Archipelago_*.AppImage | sort -r | head -1)" + +# Start the Archipelago client +$ARCHIPELAGO Starcraft2Client +``` + +For Lutris installs, you can run `lutris -l` to get the numerical ID of your StarCraft II install, then run the command +below, replacing **${ID}** with the numerical ID. + + lutris lutris:rungameid/${ID} --output-script sc2.sh + +This will get all of the relevant environment variables Lutris sets to run StarCraft 2 in a script, including the path +to the Wine binary that Lutris uses. You can then remove the line that runs the Battle.Net launcher and copy the code +above into the existing script. diff --git a/worlds/sc2/docs/setup_fr.md b/worlds/sc2/docs/setup_fr.md new file mode 100644 index 000000000000..bb6c35bce1c7 --- /dev/null +++ b/worlds/sc2/docs/setup_fr.md @@ -0,0 +1,214 @@ +# Guide d'installation du *StarCraft 2 Randomizer* + +Ce guide contient les instructions pour installer et dépanner le client de *StarCraft 2 Archipelago*, ainsi que des +indications pour obtenir un fichier de configuration de *StarCraft 2 Archipelago* et comment modifier ce dernier. + +## Logiciels requis + +- [*StarCraft 2*](https://starcraft2.com/en-us/) +- [La version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) + +## Comment est-ce que j'installe ce *randomizer*? + +1. Installer *StarCraft 2* et Archipelago en suivant les instructions indiquées dans les liens précédents. Le client de +*StarCraft 2 Archipelago* est téléchargé par le programme d'installation d'Archipelago. + - Les utilisateurs de Linux devraient aussi suivre les instructions qui se retrouvent à la fin de cette page +(["Exécuter sous Linux"](#exécuter-sous-linux)). + - Notez que votre jeu *StarCraft 2* doit être en anglais pour fonctionner avec Archipelago. +2. Exécuter `ArchipelagoStarcraft2Client.exe`. + - Uniquement pour cette étape, les utilisateurs de macOS devraient plutôt suivre les instructions qui se trouvent à +["Exécuter sous macOS"](#exécuter-sous-macos). +3. Dans le client de *StarCraft 2 Archipelago*, écrire la commande `/download_data`. Cette commande va lancer +l'installation des fichiers qui sont nécessaires pour jouer à *StarCraft 2 Archipelago*. + +## Où est-ce que j'obtiens le fichier de configuration (i.e., le *yaml*) pour ce jeu? + +Un fichier dans le format *yaml* est utilisé pour communiquer à Archipelago comment vous voulez que votre jeu soit +*randomized*. +Ce dernier est nécessaire même si vous voulez utiliser les options par défaut. +L'approche usuelle pour générer un *multiworld* consiste à avoir un fichier *yaml* par monde. + +Il y a trois approches pour obtenir un fichier *yaml* pour *StarCraft 2 Randomizer*: +* Vous pouvez aller à la page [*Player options*](/games/Starcraft%202/player-options) qui vous permet de définir vos +choix via une interface graphique et ensuite télécharger le *yaml* correspondant à ces choix. +* Vous pouvez obtenir le modèle de base en le téléchargeant à la page +[*Player options*](/games/Starcraft%202/player-options) ou en cliquant sur *Generate template* après avoir exécuté le +*Launcher* d'Archipelago (i.e., `ArchipelagoLauncher.exe`). Ce modèle de base inclut une description pour chacune des +options et vous n'avez qu'à modifier les options dans un éditeur de texte de votre choix. +* Vous pouvez demander à quelqu'un d'autre de partager un de ces fichiers *yaml* pour l'utiliser ou l'ajuster à vos +préférences. + +Prenez soin de vous rappeler du nom de joueur que vous avez inscrit dans la page à options ou dans le fichier *yaml* +puisque vous en aurez besoin pour vous connecter à votre monde! + +Notez que la page *Player options* ne permet pas de définir certaines des options avancées, e.g., l'exclusion de +certaines unités ou de leurs améliorations. +Utilisez la page [*Weighted Options*](/weighted-options) pour avoir accès à ces dernières. + +Si vous désirez des informations et/ou instructions générales sur l'utilisation d'un fichier *yaml* pour Archipelago, +veuillez consulter [*Creating a YAML*](/tutorial/Archipelago/setup/en#creating-a-yaml). + +### Questions récurrentes à propos du fichier *yaml* +#### Comment est-ce que je sais que mon *yaml* est bien défini? + +La manière la plus simple de valider votre *yaml* est d'utiliser le +[système de validation](/check) du site web. + +Vous pouvez aussi le tester en tentant de générer un *multiworld* avec votre *yaml*. +Pour faire ça, sauvegardez votre *yaml* dans le dossier `Players/` de votre installation d'Archipelago et exécutez +`ArchipelagoGenerate.exe`. +Si votre *yaml* est bien défini, vous devriez voir un nouveau fichier, avec l'extension `.zip`, apparaître dans le +dossier `output/` de votre installation d'Archipelago. +Il est recommandé de lancer `ArchipelagoGenerate.exe` via un terminal afin que vous puissiez voir les messages générés +par le logiciel, ce qui va inclure toutes erreurs qui ont eu lieu et le nom de fichier généré. +Si vous n'appréciez pas le fait d'utiliser un terminal, vous pouvez aussi regarder le fichier *log* qui va être produit +dans le dossier `logs/`. + +#### À quoi sert l'option *Progression Balancing*? + +Pour *Starcraft 2*, cette option ne fait pas grand-chose. +Il s'agit d'une option d'Archipelago permettant d'équilibrer la progression des mondes en interchangeant les *items* +dans les *spheres*. +Si le *Progression Balancing* d'un monde est plus grand que ceux des autres, les *items* de progression de ce monde ont +plus de chance d'être obtenus tôt et vice-versa si sa valeur est plus petite que celle des autres mondes. +Cependant, *Starcraft 2* est beaucoup plus permissif en termes d'*items* qui permettent de progresser, ce réglage à +donc peu d'influence sur la progression dans *StarCraft 2*. +Vu qu'il augmente le temps de génération d'un *MultiWorld*, nous recommandons de le désactiver, c-à-d le définir à +zéro, pour *Starcraft 2*. + + +#### Comment est-ce que je définis une liste d'*items*, e.g. pour l'option *excluded items*? + +Vous pouvez lire sur la syntaxe des conteneurs dans le format *yaml* à la page +[*YAML specification*](https://yaml.org/spec/1.2.2/#21-collections). +Pour les listes, chaque *item* doit être sur sa propre ligne et doit être précédé par un trait d'union. + +```yaml +excluded_items: + - Battlecruiser + - Drop-Pods (Kerrigan Tier 7) +``` + +Une liste vide est représentée par une paire de crochets: `[]`. +Il s'agit de la valeur par défaut dans le modèle de base, ce qui devrait vous aider à apprendre à utiliser cette +syntaxe. + +#### Comment est-ce que je fais pour avoir des *items* dès le départ? + +L'option *starting inventory* est un *map* et non une liste. +Ainsi, elle permet de spécifier le nombre de chaque *item* avec lequel vous allez commencer. +Sa syntaxe consiste à indiquer le nom de l'*item*, suivi par un deux-points, puis par un espace et enfin par le nombre +désiré de cet *item*. + +```yaml +start_inventory: + Micro-Filtering: 1 + Additional Starting Vespene: 5 +``` + +Un *map* vide est représenté par une paire d'accolades: `{}`. +Il s'agit de la valeur par défaut dans le modèle de base, ce qui devrait vous aider à apprendre à utiliser cette +syntaxe. + +#### Comment est-ce que je fais pour connaître le nom des *items* et des *locations* dans *StarCraft 2 Archipelago*? + +La page [*datapackage*](/datapackage) d'Archipelago liste l'ensemble des *items* et des *locations* de tous les jeux +que le site web prend en charge actuellement, dont ceux de *StarCraft 2*. + +Vous trouverez aussi la liste complète des *items* de *StarCraft 2 Archipelago* à la page +[*Icon Repository*](https://matthewmarinets.github.io/ap_sc2_icons/). +Notez que cette page contient diverses informations supplémentaires sur chacun des *items*. +Cependant, l'information présente dans cette dernière peut différer de celle du *datapackage* d'Archipelago +puisqu'elle est générée, habituellement, à partir de la version en développement de *StarCraft 2 Archipelago* qui +n'ont peut-être pas encore été inclus dans le site web d'Archipelago. + +## Comment est-ce que je peux joindre un *MultiWorld*? + +1. Exécuter `ArchipelagoStarcraft2Client.exe`. + - Uniquement pour cette étape, les utilisateurs de macOS devraient plutôt suivre les instructions à la page +["Exécuter sous macOS"](#exécuter-sous-macos). +2. Entrer la commande `/connect [server ip]`. + - Si le *MultiWorld* est hébergé via un siteweb, l'IP du server devrait être indiqué dans le haut de la page de +votre *room*. +3. Inscrivez le nom de joueur spécifié dans votre *yaml* lorsque vous y êtes invité. +4. Si le serveur a un mot de passe, l'inscrire lorsque vous y êtes invité. +5. Une fois connecté, aller sur l'onglet *StarCraft 2 Launcher* dans le client. Dans cet onglet, vous devriez trouver +toutes les missions de votre monde. Les missions qui ne sont pas disponibles présentement auront leur texte dans une +nuance de gris. Vous n'avez qu'à cliquer une des missions qui est disponible pour la commencer! + +## *StarCraft 2* ne démarre pas quand je tente de commencer une mission + +Pour commencer, regarder le fichier *log* pour trouver le problème (ce dernier devrait être dans +`[Archipelago Directory]/logs/SC2Client.txt`). +Si vous ne comprenez pas le problème avec le fichier *log*, visitez notre +[*Discord*](https://discord.com/invite/8Z65BR2) pour demander de l'aide dans le forum *tech-support*. +Dans votre message, veuillez inclure une description détaillée de ce qui ne marche pas et ajouter en pièce jointe le +fichier *log*. + +## Mon profil de raccourcis clavier n'est pas disponibles quand je joue à *StarCraft 2 Archipelago* + +Pour que votre profil de raccourcis clavier fonctionne dans Archipelago, vous devez copier votre fichier de raccourcis +qui se trouve dans `Documents/StarCraft II/Accounts/######/Hotkeys` vers `Documents/StarCraft II/Hotkeys`. +Si le dossier n'existe pas, créez-le. + +Pour que *StarCraft 2 Archipelago* utilise votre profil, suivez les étapes suivantes. +Lancez *Starcraft 2* via l'application *Battle.net*. +Changez votre profil de raccourcis clavier pour le mode standard et acceptez, puis sélectionnez votre profil +personnalisé et acceptez. +Vous n'aurez besoin de faire ça qu'une seule fois. + +## Exécuter sous macOS + +Pour exécuter *StarCraft 2* via Archipelago sous macOS, vous devez exécuter le client à partir de la source +comme indiqué ici: [*macOS Guide*](/tutorial/Archipelago/mac/en). +Notez que pour lancer le client, vous devez exécuter la commande `python3 Starcraft2Client.py`. + +## Exécuter sous Linux + +Pour exécuter *StarCraft 2* via Archipelago sous Linux, vous allez devoir installer le jeu avec *Wine* et ensuite +exécuter le client d'Archipelago pour Linux. + +Confirmez que vous avez installé *StarCraft 2* via *Wine* et que vous avez suivi les +[instructions d'installation](#comment-est-ce-que-j'installe-ce-randomizer?) pour ajouter les *Maps* et les *Data +files* nécessairent pour *StarCraft 2 Archipelago* au bon endroit. +Vous n'avez pas besoin de copier les fichiers `.dll`. +Si vous avez des difficultés pour installer ou exécuter *StarCraft 2* sous Linux, il est recommandé d'utiliser le +logiciel *Lutris*. + +Copier ce qui suit dans un fichier avec l'extension `.sh`, en prenant soin de définir les variables **WINE** et +**SC2PATH** avec les bons chemins et de définir **PATH_TO_ARCHIPELAGO** avec le chemin vers le dossier qui contient le +*AppImage* si ce dernier n'est pas dans le même dossier que ce script. + +```sh +# Permet au client de savoir que SC2 est exécuté via Wine +export SC2PF=WineLinux +export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +# À_CHANGER Remplacer le chemin avec celui qui correspond à la version de Wine utilisé pour exécuter SC2 +export WINE="/usr/bin/wine" + +# À_CHANGER Remplacer le chemin par celui qui indique où StarCraft II est installé +export SC2PATH="/home/user/Games/starcraft-ii/drive_c/Program Files (x86)/StarCraft II/" + +# À_CHANGER Indiquer le dossier qui contient l'AppImage d'Archipelago +PATH_TO_ARCHIPELAGO= + +# Obtiens la dernière version de l'AppImage de Archipelago dans le dossier PATH_TO_ARCHIPELAGO. +# Si PATH_TO_ARCHIPELAGO n'est pas défini, la valeur par défaut est le dossier qui contient ce script. +ARCHIPELAGO="$(ls ${PATH_TO_ARCHIPELAGO:-$(dirname $0)}/Archipelago_*.AppImage | sort -r | head -1)" + +# Lance le client de Archipelago +$ARCHIPELAGO Starcraft2Client +``` + +Pour une installation via Lutris, vous pouvez exécuter `lutris -l` pour obtenir l'identifiant numérique de votre +installation *StarCraft II* et ensuite exécuter la commande suivante, en remplacant **${ID}** pour cet identifiant +numérique. + + lutris lutris:rungameid/${ID} --output-script sc2.sh + +Cette commande va définir toutes les variables d'environnement nécessaires pour exécuter *StarCraft 2* dans un script, +incluant le chemin vers l'exécutable *Wine* que Lutris utilise. +Après ça, vous pouvez enlever la ligne qui permet de démarrer *Battle.Net* et copier le code décrit plus haut dans le +script produit. + diff --git a/worlds/sc2wol/requirements.txt b/worlds/sc2/requirements.txt similarity index 62% rename from worlds/sc2wol/requirements.txt rename to worlds/sc2/requirements.txt index 9b84863c4590..5bc808b639db 100644 --- a/worlds/sc2wol/requirements.txt +++ b/worlds/sc2/requirements.txt @@ -1,2 +1 @@ nest-asyncio >= 1.5.5 -six >= 1.16.0 \ No newline at end of file diff --git a/worlds/sc2/test/__init__.py b/worlds/sc2/test/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/sc2/test/test_Regions.py b/worlds/sc2/test/test_Regions.py new file mode 100644 index 000000000000..c268b65da9a8 --- /dev/null +++ b/worlds/sc2/test/test_Regions.py @@ -0,0 +1,41 @@ +import unittest +from .test_base import Sc2TestBase +from .. import Regions +from .. import Options, MissionTables + +class TestGridsizes(unittest.TestCase): + def test_grid_sizes_meet_specs(self): + self.assertTupleEqual((1, 2, 0), Regions.get_grid_dimensions(2)) + self.assertTupleEqual((1, 3, 0), Regions.get_grid_dimensions(3)) + self.assertTupleEqual((2, 2, 0), Regions.get_grid_dimensions(4)) + self.assertTupleEqual((2, 3, 1), Regions.get_grid_dimensions(5)) + self.assertTupleEqual((2, 4, 1), Regions.get_grid_dimensions(7)) + self.assertTupleEqual((2, 4, 0), Regions.get_grid_dimensions(8)) + self.assertTupleEqual((3, 3, 0), Regions.get_grid_dimensions(9)) + self.assertTupleEqual((2, 5, 0), Regions.get_grid_dimensions(10)) + self.assertTupleEqual((3, 4, 1), Regions.get_grid_dimensions(11)) + self.assertTupleEqual((3, 4, 0), Regions.get_grid_dimensions(12)) + self.assertTupleEqual((3, 5, 0), Regions.get_grid_dimensions(15)) + self.assertTupleEqual((4, 4, 0), Regions.get_grid_dimensions(16)) + self.assertTupleEqual((4, 6, 0), Regions.get_grid_dimensions(24)) + self.assertTupleEqual((5, 5, 0), Regions.get_grid_dimensions(25)) + self.assertTupleEqual((5, 6, 1), Regions.get_grid_dimensions(29)) + self.assertTupleEqual((5, 7, 2), Regions.get_grid_dimensions(33)) + + +class TestGridGeneration(Sc2TestBase): + options = { + "mission_order": Options.MissionOrder.option_grid, + "excluded_missions": [MissionTables.SC2Mission.ZERO_HOUR.mission_name,], + "enable_hots_missions": False, + "enable_prophecy_missions": True, + "enable_lotv_prologue_missions": False, + "enable_lotv_missions": False, + "enable_epilogue_missions": False, + "enable_nco_missions": False + } + + def test_size_matches_exclusions(self): + self.assertNotIn(MissionTables.SC2Mission.ZERO_HOUR.mission_name, self.multiworld.regions) + # WoL has 29 missions. -1 for Zero Hour being excluded, +1 for the automatically-added menu location + self.assertEqual(len(self.multiworld.regions), 29) diff --git a/worlds/sc2/test/test_base.py b/worlds/sc2/test/test_base.py new file mode 100644 index 000000000000..28529e37edd5 --- /dev/null +++ b/worlds/sc2/test/test_base.py @@ -0,0 +1,11 @@ +from typing import * + +from test.TestBase import WorldTestBase +from .. import SC2World +from .. import Client + +class Sc2TestBase(WorldTestBase): + game = Client.SC2Context.game + world: SC2World + player: ClassVar[int] = 1 + skip_long_tests: bool = True diff --git a/worlds/sc2/test/test_options.py b/worlds/sc2/test/test_options.py new file mode 100644 index 000000000000..30d21f39697e --- /dev/null +++ b/worlds/sc2/test/test_options.py @@ -0,0 +1,7 @@ +import unittest +from .test_base import Sc2TestBase +from .. import Options, MissionTables + +class TestOptions(unittest.TestCase): + def test_campaign_size_option_max_matches_number_of_missions(self): + self.assertEqual(Options.MaximumCampaignSize.range_end, len(MissionTables.SC2Mission)) diff --git a/worlds/sc2wol/Client.py b/worlds/sc2wol/Client.py deleted file mode 100644 index 83b7b62d2977..000000000000 --- a/worlds/sc2wol/Client.py +++ /dev/null @@ -1,1222 +0,0 @@ -from __future__ import annotations - -import asyncio -import copy -import ctypes -import logging -import multiprocessing -import os.path -import re -import sys -import tempfile -import typing -import queue -import zipfile -import io -import random -import concurrent.futures -from pathlib import Path - -# CommonClient import first to trigger ModuleUpdater -from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser -from Utils import init_logging, is_windows - -if __name__ == "__main__": - init_logging("SC2Client", exception_logger="Client") - -logger = logging.getLogger("Client") -sc2_logger = logging.getLogger("Starcraft2") - -import nest_asyncio -from worlds._sc2common import bot -from worlds._sc2common.bot.data import Race -from worlds._sc2common.bot.main import run_game -from worlds._sc2common.bot.player import Bot -from worlds.sc2wol import SC2WoLWorld -from worlds.sc2wol.Items import lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers -from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET -from worlds.sc2wol.MissionTables import lookup_id_to_mission -from worlds.sc2wol.Regions import MissionInfo - -import colorama -from NetUtils import ClientStatus, NetworkItem, RawJSONtoTextParser, JSONtoTextParser, JSONMessagePart -from MultiServer import mark_raw - -pool = concurrent.futures.ThreadPoolExecutor(1) -loop = asyncio.get_event_loop_policy().new_event_loop() -nest_asyncio.apply(loop) -max_bonus: int = 13 -victory_modulo: int = 100 - -# GitHub repo where the Map/mod data is hosted for /download_data command -DATA_REPO_OWNER = "Ziktofel" -DATA_REPO_NAME = "Archipelago-SC2-data" -DATA_API_VERSION = "API2" - - -# Data version file path. -# This file is used to tell if the downloaded data are outdated -# Associated with /download_data command -def get_metadata_file(): - return os.environ["SC2PATH"] + os.sep + "ArchipelagoSC2Metadata.txt" - - -class StarcraftClientProcessor(ClientCommandProcessor): - ctx: SC2Context - - def _cmd_difficulty(self, difficulty: str = "") -> bool: - """Overrides the current difficulty set for the world. Takes the argument casual, normal, hard, or brutal""" - options = difficulty.split() - num_options = len(options) - - if num_options > 0: - difficulty_choice = options[0].lower() - if difficulty_choice == "casual": - self.ctx.difficulty_override = 0 - elif difficulty_choice == "normal": - self.ctx.difficulty_override = 1 - elif difficulty_choice == "hard": - self.ctx.difficulty_override = 2 - elif difficulty_choice == "brutal": - self.ctx.difficulty_override = 3 - else: - self.output("Unable to parse difficulty '" + options[0] + "'") - return False - - self.output("Difficulty set to " + options[0]) - return True - - else: - if self.ctx.difficulty == -1: - self.output("Please connect to a seed before checking difficulty.") - else: - current_difficulty = self.ctx.difficulty - if self.ctx.difficulty_override >= 0: - current_difficulty = self.ctx.difficulty_override - self.output("Current difficulty: " + ["Casual", "Normal", "Hard", "Brutal"][current_difficulty]) - self.output("To change the difficulty, add the name of the difficulty after the command.") - return False - - - def _cmd_game_speed(self, game_speed: str = "") -> bool: - """Overrides the current game speed for the world. - Takes the arguments default, slower, slow, normal, fast, faster""" - options = game_speed.split() - num_options = len(options) - - if num_options > 0: - speed_choice = options[0].lower() - if speed_choice == "default": - self.ctx.game_speed_override = 0 - elif speed_choice == "slower": - self.ctx.game_speed_override = 1 - elif speed_choice == "slow": - self.ctx.game_speed_override = 2 - elif speed_choice == "normal": - self.ctx.game_speed_override = 3 - elif speed_choice == "fast": - self.ctx.game_speed_override = 4 - elif speed_choice == "faster": - self.ctx.game_speed_override = 5 - else: - self.output("Unable to parse game speed '" + options[0] + "'") - return False - - self.output("Game speed set to " + options[0]) - return True - - else: - if self.ctx.game_speed == -1: - self.output("Please connect to a seed before checking game speed.") - else: - current_speed = self.ctx.game_speed - if self.ctx.game_speed_override >= 0: - current_speed = self.ctx.game_speed_override - self.output("Current game speed: " - + ["Default", "Slower", "Slow", "Normal", "Fast", "Faster"][current_speed]) - self.output("To change the game speed, add the name of the speed after the command," - " or Default to select based on difficulty.") - return False - - def _cmd_color(self, color: str = "") -> bool: - player_colors = [ - "White", "Red", "Blue", "Teal", - "Purple", "Yellow", "Orange", "Green", - "LightPink", "Violet", "LightGrey", "DarkGreen", - "Brown", "LightGreen", "DarkGrey", "Pink", - "Rainbow", "Random", "Default" - ] - match_colors = [player_color.lower() for player_color in player_colors] - if color: - if color.lower() not in match_colors: - self.output(color + " is not a valid color. Available colors: " + ', '.join(player_colors)) - return False - if color.lower() == "random": - color = random.choice(player_colors[:16]) - self.ctx.player_color = match_colors.index(color.lower()) - self.output("Color set to " + player_colors[self.ctx.player_color]) - else: - self.output("Current player color: " + player_colors[self.ctx.player_color]) - self.output("To change your colors, add the name of the color after the command.") - self.output("Available colors: " + ', '.join(player_colors)) - - def _cmd_disable_mission_check(self) -> bool: - """Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play - the next mission in a chain the other player is doing.""" - self.ctx.missions_unlocked = True - sc2_logger.info("Mission check has been disabled") - return True - - def _cmd_play(self, mission_id: str = "") -> bool: - """Start a Starcraft 2 mission""" - - options = mission_id.split() - num_options = len(options) - - if num_options > 0: - mission_number = int(options[0]) - - self.ctx.play_mission(mission_number) - - else: - sc2_logger.info( - "Mission ID needs to be specified. Use /unfinished or /available to view ids for available missions.") - return False - - return True - - def _cmd_available(self) -> bool: - """Get what missions are currently available to play""" - - request_available_missions(self.ctx) - return True - - def _cmd_unfinished(self) -> bool: - """Get what missions are currently available to play and have not had all locations checked""" - - request_unfinished_missions(self.ctx) - return True - - @mark_raw - def _cmd_set_path(self, path: str = '') -> bool: - """Manually set the SC2 install directory (if the automatic detection fails).""" - if path: - os.environ["SC2PATH"] = path - is_mod_installed_correctly() - return True - else: - sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.") - return False - - def _cmd_download_data(self) -> bool: - """Download the most recent release of the necessary files for playing SC2 with - Archipelago. Will overwrite existing files.""" - pool.submit(self._download_data) - return True - - @staticmethod - def _download_data() -> bool: - if "SC2PATH" not in os.environ: - check_game_install_path() - - if os.path.exists(get_metadata_file()): - with open(get_metadata_file(), "r") as f: - metadata = f.read() - else: - metadata = None - - tempzip, metadata = download_latest_release_zip(DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, - metadata=metadata, force_download=True) - - if tempzip != '': - try: - zipfile.ZipFile(tempzip).extractall(path=os.environ["SC2PATH"]) - sc2_logger.info(f"Download complete. Package installed.") - with open(get_metadata_file(), "w") as f: - f.write(metadata) - finally: - os.remove(tempzip) - else: - sc2_logger.warning("Download aborted/failed. Read the log for more information.") - return False - return True - - -class SC2JSONtoTextParser(JSONtoTextParser): - def __init__(self, ctx): - self.handlers = { - "ItemSend": self._handle_color, - "ItemCheat": self._handle_color, - "Hint": self._handle_color, - } - super().__init__(ctx) - - def _handle_color(self, node: JSONMessagePart): - codes = node["color"].split(";") - buffer = "".join(self.color_code(code) for code in codes if code in self.color_codes) - return buffer + self._handle_text(node) + '
    ' - - def color_code(self, code: str): - return '' - - -class SC2Context(CommonContext): - command_processor = StarcraftClientProcessor - game = "Starcraft 2 Wings of Liberty" - items_handling = 0b111 - difficulty = -1 - game_speed = -1 - all_in_choice = 0 - mission_order = 0 - player_color = 2 - mission_req_table: typing.Dict[str, MissionInfo] = {} - final_mission: int = 29 - announcements = queue.Queue() - sc2_run_task: typing.Optional[asyncio.Task] = None - missions_unlocked: bool = False # allow launching missions ignoring requirements - generic_upgrade_missions = 0 - generic_upgrade_research = 0 - generic_upgrade_items = 0 - current_tooltip = None - last_loc_list = None - difficulty_override = -1 - game_speed_override = -1 - mission_id_to_location_ids: typing.Dict[int, typing.List[int]] = {} - last_bot: typing.Optional[ArchipelagoBot] = None - - def __init__(self, *args, **kwargs): - super(SC2Context, self).__init__(*args, **kwargs) - self.raw_text_parser = SC2JSONtoTextParser(self) - - async def server_auth(self, password_requested: bool = False): - if password_requested and not self.password: - await super(SC2Context, self).server_auth(password_requested) - await self.get_username() - await self.send_connect() - if self.ui: - self.ui.first_check = True - - def on_package(self, cmd: str, args: dict): - if cmd in {"Connected"}: - self.difficulty = args["slot_data"]["game_difficulty"] - if "game_speed" in args["slot_data"]: - self.game_speed = args["slot_data"]["game_speed"] - else: - self.game_speed = 0 - self.all_in_choice = args["slot_data"]["all_in_map"] - slot_req_table = args["slot_data"]["mission_req"] - # Maintaining backwards compatibility with older slot data - self.mission_req_table = { - mission: MissionInfo( - **{field: value for field, value in mission_info.items() if field in MissionInfo._fields} - ) - for mission, mission_info in slot_req_table.items() - } - self.mission_order = args["slot_data"].get("mission_order", 0) - self.final_mission = args["slot_data"].get("final_mission", 29) - self.player_color = args["slot_data"].get("player_color", 2) - self.generic_upgrade_missions = args["slot_data"].get("generic_upgrade_missions", 0) - self.generic_upgrade_items = args["slot_data"].get("generic_upgrade_items", 0) - self.generic_upgrade_research = args["slot_data"].get("generic_upgrade_research", 0) - - self.build_location_to_mission_mapping() - - # Looks for the required maps and mods for SC2. Runs check_game_install_path. - maps_present = is_mod_installed_correctly() - if os.path.exists(get_metadata_file()): - with open(get_metadata_file(), "r") as f: - current_ver = f.read() - sc2_logger.debug(f"Current version: {current_ver}") - if is_mod_update_available(DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, current_ver): - sc2_logger.info("NOTICE: Update for required files found. Run /download_data to install.") - elif maps_present: - sc2_logger.warning("NOTICE: Your map files may be outdated (version number not found). " - "Run /download_data to update them.") - - - def on_print_json(self, args: dict): - # goes to this world - if "receiving" in args and self.slot_concerns_self(args["receiving"]): - relevant = True - # found in this world - elif "item" in args and self.slot_concerns_self(args["item"].player): - relevant = True - # not related - else: - relevant = False - - if relevant: - self.announcements.put(self.raw_text_parser(copy.deepcopy(args["data"]))) - - super(SC2Context, self).on_print_json(args) - - def run_gui(self): - from kvui import GameManager, HoverBehavior, ServerToolTip - from kivy.app import App - from kivy.clock import Clock - from kivy.uix.tabbedpanel import TabbedPanelItem - from kivy.uix.gridlayout import GridLayout - from kivy.lang import Builder - from kivy.uix.label import Label - from kivy.uix.button import Button - from kivy.uix.floatlayout import FloatLayout - from kivy.properties import StringProperty - - class HoverableButton(HoverBehavior, Button): - pass - - class MissionButton(HoverableButton): - tooltip_text = StringProperty("Test") - ctx: SC2Context - - def __init__(self, *args, **kwargs): - super(HoverableButton, self).__init__(*args, **kwargs) - self.layout = FloatLayout() - self.popuplabel = ServerToolTip(text=self.text) - self.layout.add_widget(self.popuplabel) - - def on_enter(self): - self.popuplabel.text = self.tooltip_text - - if self.ctx.current_tooltip: - App.get_running_app().root.remove_widget(self.ctx.current_tooltip) - - if self.tooltip_text == "": - self.ctx.current_tooltip = None - else: - App.get_running_app().root.add_widget(self.layout) - self.ctx.current_tooltip = self.layout - - def on_leave(self): - self.ctx.ui.clear_tooltip() - - @property - def ctx(self) -> CommonContext: - return App.get_running_app().ctx - - class MissionLayout(GridLayout): - pass - - class MissionCategory(GridLayout): - pass - - class SC2Manager(GameManager): - logging_pairs = [ - ("Client", "Archipelago"), - ("Starcraft2", "Starcraft2"), - ] - base_title = "Archipelago Starcraft 2 Client" - - mission_panel = None - last_checked_locations = {} - mission_id_to_button = {} - launching: typing.Union[bool, int] = False # if int -> mission ID - refresh_from_launching = True - first_check = True - ctx: SC2Context - - def __init__(self, ctx): - super().__init__(ctx) - - def clear_tooltip(self): - if self.ctx.current_tooltip: - App.get_running_app().root.remove_widget(self.ctx.current_tooltip) - - self.ctx.current_tooltip = None - - def build(self): - container = super().build() - - panel = TabbedPanelItem(text="Starcraft 2 Launcher") - self.mission_panel = panel.content = MissionLayout() - - self.tabs.add_widget(panel) - - Clock.schedule_interval(self.build_mission_table, 0.5) - - return container - - def build_mission_table(self, dt): - if (not self.launching and (not self.last_checked_locations == self.ctx.checked_locations or - not self.refresh_from_launching)) or self.first_check: - self.refresh_from_launching = True - - self.mission_panel.clear_widgets() - if self.ctx.mission_req_table: - self.last_checked_locations = self.ctx.checked_locations.copy() - self.first_check = False - - self.mission_id_to_button = {} - categories = {} - available_missions, unfinished_missions = calc_unfinished_missions(self.ctx) - - # separate missions into categories - for mission in self.ctx.mission_req_table: - if not self.ctx.mission_req_table[mission].category in categories: - categories[self.ctx.mission_req_table[mission].category] = [] - - categories[self.ctx.mission_req_table[mission].category].append(mission) - - for category in categories: - category_panel = MissionCategory() - if category.startswith('_'): - category_display_name = '' - else: - category_display_name = category - category_panel.add_widget( - Label(text=category_display_name, size_hint_y=None, height=50, outline_width=1)) - - for mission in categories[category]: - text: str = mission - tooltip: str = "" - mission_id: int = self.ctx.mission_req_table[mission].id - # Map has uncollected locations - if mission in unfinished_missions: - text = f"[color=6495ED]{text}[/color]" - elif mission in available_missions: - text = f"[color=FFFFFF]{text}[/color]" - # Map requirements not met - else: - text = f"[color=a9a9a9]{text}[/color]" - tooltip = f"Requires: " - if self.ctx.mission_req_table[mission].required_world: - tooltip += ", ".join(list(self.ctx.mission_req_table)[req_mission - 1] for - req_mission in - self.ctx.mission_req_table[mission].required_world) - - if self.ctx.mission_req_table[mission].number: - tooltip += " and " - if self.ctx.mission_req_table[mission].number: - tooltip += f"{self.ctx.mission_req_table[mission].number} missions completed" - remaining_location_names: typing.List[str] = [ - self.ctx.location_names[loc] for loc in self.ctx.locations_for_mission(mission) - if loc in self.ctx.missing_locations] - - if mission_id == self.ctx.final_mission: - if mission in available_missions: - text = f"[color=FFBC95]{mission}[/color]" - else: - text = f"[color=D0C0BE]{mission}[/color]" - if tooltip: - tooltip += "\n" - tooltip += "Final Mission" - - if remaining_location_names: - if tooltip: - tooltip += "\n" - tooltip += f"Uncollected locations:\n" - tooltip += "\n".join(remaining_location_names) - - mission_button = MissionButton(text=text, size_hint_y=None, height=50) - mission_button.tooltip_text = tooltip - mission_button.bind(on_press=self.mission_callback) - self.mission_id_to_button[mission_id] = mission_button - category_panel.add_widget(mission_button) - - category_panel.add_widget(Label(text="")) - self.mission_panel.add_widget(category_panel) - - elif self.launching: - self.refresh_from_launching = False - - self.mission_panel.clear_widgets() - self.mission_panel.add_widget(Label(text="Launching Mission: " + - lookup_id_to_mission[self.launching])) - if self.ctx.ui: - self.ctx.ui.clear_tooltip() - - def mission_callback(self, button): - if not self.launching: - mission_id: int = next(k for k, v in self.mission_id_to_button.items() if v == button) - if self.ctx.play_mission(mission_id): - self.launching = mission_id - Clock.schedule_once(self.finish_launching, 10) - - def finish_launching(self, dt): - self.launching = False - - self.ui = SC2Manager(self) - self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") - import pkgutil - data = pkgutil.get_data(SC2WoLWorld.__module__, "Starcraft2.kv").decode() - Builder.load_string(data) - - async def shutdown(self): - await super(SC2Context, self).shutdown() - if self.last_bot: - self.last_bot.want_close = True - if self.sc2_run_task: - self.sc2_run_task.cancel() - - def play_mission(self, mission_id: int) -> bool: - if self.missions_unlocked or \ - is_mission_available(self, mission_id): - if self.sc2_run_task: - if not self.sc2_run_task.done(): - sc2_logger.warning("Starcraft 2 Client is still running!") - self.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task - if self.slot is None: - sc2_logger.warning("Launching Mission without Archipelago authentication, " - "checks will not be registered to server.") - self.sc2_run_task = asyncio.create_task(starcraft_launch(self, mission_id), - name="Starcraft 2 Launch") - return True - else: - sc2_logger.info( - f"{lookup_id_to_mission[mission_id]} is not currently unlocked. " - f"Use /unfinished or /available to see what is available.") - return False - - def build_location_to_mission_mapping(self): - mission_id_to_location_ids: typing.Dict[int, typing.Set[int]] = { - mission_info.id: set() for mission_info in self.mission_req_table.values() - } - - for loc in self.server_locations: - mission_id, objective = divmod(loc - SC2WOL_LOC_ID_OFFSET, victory_modulo) - mission_id_to_location_ids[mission_id].add(objective) - self.mission_id_to_location_ids = {mission_id: sorted(objectives) for mission_id, objectives in - mission_id_to_location_ids.items()} - - def locations_for_mission(self, mission: str): - mission_id: int = self.mission_req_table[mission].id - objectives = self.mission_id_to_location_ids[self.mission_req_table[mission].id] - for objective in objectives: - yield SC2WOL_LOC_ID_OFFSET + mission_id * 100 + objective - - -async def main(): - multiprocessing.freeze_support() - parser = get_base_parser() - parser.add_argument('--name', default=None, help="Slot Name to connect as.") - args = parser.parse_args() - - ctx = SC2Context(args.connect, args.password) - ctx.auth = args.name - if ctx.server_task is None: - ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") - - if gui_enabled: - ctx.run_gui() - ctx.run_cli() - - await ctx.exit_event.wait() - - await ctx.shutdown() - - -maps_table = [ - "ap_liberation_day", "ap_the_outlaws", "ap_zero_hour", - "ap_evacuation", "ap_outbreak", "ap_safe_haven", "ap_havens_fall", - "ap_smash_and_grab", "ap_the_dig", "ap_the_moebius_factor", "ap_supernova", "ap_maw_of_the_void", - "ap_devils_playground", "ap_welcome_to_the_jungle", "ap_breakout", "ap_ghost_of_a_chance", - "ap_the_great_train_robbery", "ap_cutthroat", "ap_engine_of_destruction", "ap_media_blitz", "ap_piercing_the_shroud", - "ap_whispers_of_doom", "ap_a_sinister_turn", "ap_echoes_of_the_future", "ap_in_utter_darkness", - "ap_gates_of_hell", "ap_belly_of_the_beast", "ap_shatter_the_sky", "ap_all_in" -] - -wol_default_categories = [ - "Mar Sara", "Mar Sara", "Mar Sara", "Colonist", "Colonist", "Colonist", "Colonist", - "Artifact", "Artifact", "Artifact", "Artifact", "Artifact", "Covert", "Covert", "Covert", "Covert", - "Rebellion", "Rebellion", "Rebellion", "Rebellion", "Rebellion", "Prophecy", "Prophecy", "Prophecy", "Prophecy", - "Char", "Char", "Char", "Char" -] -wol_default_category_names = [ - "Mar Sara", "Colonist", "Artifact", "Covert", "Rebellion", "Prophecy", "Char" -] - - -def calculate_items(ctx: SC2Context) -> typing.List[int]: - items = ctx.items_received - network_item: NetworkItem - accumulators: typing.List[int] = [0 for _ in type_flaggroups] - - for network_item in items: - name: str = lookup_id_to_name[network_item.item] - item_data: ItemData = get_full_item_list()[name] - - # exists exactly once - if item_data.quantity == 1: - accumulators[type_flaggroups[item_data.type]] |= 1 << item_data.number - - # exists multiple times - elif item_data.type == "Upgrade" or item_data.type == "Progressive Upgrade": - flaggroup = type_flaggroups[item_data.type] - - # Generic upgrades apply only to Weapon / Armor upgrades - if item_data.type != "Upgrade" or ctx.generic_upgrade_items == 0: - accumulators[flaggroup] += 1 << item_data.number - else: - for bundled_number in upgrade_numbers[item_data.number]: - accumulators[flaggroup] += 1 << bundled_number - - # sum - else: - accumulators[type_flaggroups[item_data.type]] += item_data.number - - # Upgrades from completed missions - if ctx.generic_upgrade_missions > 0: - upgrade_flaggroup = type_flaggroups["Upgrade"] - num_missions = ctx.generic_upgrade_missions * len(ctx.mission_req_table) - amounts = [ - num_missions // 100, - 2 * num_missions // 100, - 3 * num_missions // 100 - ] - upgrade_count = 0 - completed = len([id for id in ctx.mission_id_to_location_ids if SC2WOL_LOC_ID_OFFSET + victory_modulo * id in ctx.checked_locations]) - for amount in amounts: - if completed >= amount: - upgrade_count += 1 - # Equivalent to "Progressive Weapon/Armor Upgrade" item - for bundled_number in upgrade_numbers[5]: - accumulators[upgrade_flaggroup] += upgrade_count << bundled_number - - return accumulators - - -def calc_difficulty(difficulty): - if difficulty == 0: - return 'C' - elif difficulty == 1: - return 'N' - elif difficulty == 2: - return 'H' - elif difficulty == 3: - return 'B' - - return 'X' - - -async def starcraft_launch(ctx: SC2Context, mission_id: int): - sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.") - - with DllDirectory(None): - run_game(bot.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id), - name="Archipelago", fullscreen=True)], realtime=True) - - -class ArchipelagoBot(bot.bot_ai.BotAI): - game_running: bool = False - mission_completed: bool = False - boni: typing.List[bool] - setup_done: bool - ctx: SC2Context - mission_id: int - want_close: bool = False - can_read_game = False - last_received_update: int = 0 - - def __init__(self, ctx: SC2Context, mission_id): - self.setup_done = False - self.ctx = ctx - self.ctx.last_bot = self - self.mission_id = mission_id - self.boni = [False for _ in range(max_bonus)] - - super(ArchipelagoBot, self).__init__() - - async def on_step(self, iteration: int): - if self.want_close: - self.want_close = False - await self._client.leave() - return - game_state = 0 - if not self.setup_done: - self.setup_done = True - start_items = calculate_items(self.ctx) - if self.ctx.difficulty_override >= 0: - difficulty = calc_difficulty(self.ctx.difficulty_override) - else: - difficulty = calc_difficulty(self.ctx.difficulty) - if self.ctx.game_speed_override >= 0: - game_speed = self.ctx.game_speed_override - else: - game_speed = self.ctx.game_speed - await self.chat_send("?SetOptions {} {} {} {}".format( - difficulty, - self.ctx.generic_upgrade_research, - self.ctx.all_in_choice, - game_speed - )) - await self.chat_send("?GiveResources {} {} {}".format( - start_items[8], - start_items[9], - start_items[10] - )) - await self.chat_send("?GiveTerranTech {} {} {} {} {} {} {} {} {} {}".format( - start_items[0], start_items[1], start_items[2], start_items[3], start_items[4], - start_items[5], start_items[6], start_items[12], start_items[13], start_items[14])) - await self.chat_send("?GiveProtossTech {}".format(start_items[7])) - await self.chat_send("?SetColor rr " + str(self.ctx.player_color)) # TODO: Add faction color options - await self.chat_send("?LoadFinished") - self.last_received_update = len(self.ctx.items_received) - - else: - if not self.ctx.announcements.empty(): - message = self.ctx.announcements.get(timeout=1) - await self.chat_send("?SendMessage " + message) - self.ctx.announcements.task_done() - - # Archipelago reads the health - for unit in self.all_own_units(): - if unit.health_max == 38281: - game_state = int(38281 - unit.health) - self.can_read_game = True - - if iteration == 160 and not game_state & 1: - await self.chat_send("?SendMessage Warning: Archipelago unable to connect or has lost connection to " + - "Starcraft 2 (This is likely a map issue)") - - if self.last_received_update < len(self.ctx.items_received): - current_items = calculate_items(self.ctx) - await self.chat_send("?GiveTerranTech {} {} {} {} {} {} {} {} {} {}".format( - current_items[0], current_items[1], current_items[2], current_items[3], current_items[4], - current_items[5], current_items[6], current_items[12], current_items[13], current_items[14])) - await self.chat_send("?GiveProtossTech {}".format(current_items[7])) - self.last_received_update = len(self.ctx.items_received) - - if game_state & 1: - if not self.game_running: - print("Archipelago Connected") - self.game_running = True - - if self.can_read_game: - if game_state & (1 << 1) and not self.mission_completed: - if self.mission_id != self.ctx.final_mission: - print("Mission Completed") - await self.ctx.send_msgs( - [{"cmd": 'LocationChecks', - "locations": [SC2WOL_LOC_ID_OFFSET + victory_modulo * self.mission_id]}]) - self.mission_completed = True - else: - print("Game Complete") - await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}]) - self.mission_completed = True - - for x, completed in enumerate(self.boni): - if not completed and game_state & (1 << (x + 2)): - await self.ctx.send_msgs( - [{"cmd": 'LocationChecks', - "locations": [SC2WOL_LOC_ID_OFFSET + victory_modulo * self.mission_id + x + 1]}]) - self.boni[x] = True - - else: - await self.chat_send("?SendMessage LostConnection - Lost connection to game.") - - -def request_unfinished_missions(ctx: SC2Context): - if ctx.mission_req_table: - message = "Unfinished Missions: " - unlocks = initialize_blank_mission_dict(ctx.mission_req_table) - unfinished_locations = initialize_blank_mission_dict(ctx.mission_req_table) - - _, unfinished_missions = calc_unfinished_missions(ctx, unlocks=unlocks) - - # Removing All-In from location pool - final_mission = lookup_id_to_mission[ctx.final_mission] - if final_mission in unfinished_missions.keys(): - message = f"Final Mission Available: {final_mission}[{ctx.final_mission}]\n" + message - if unfinished_missions[final_mission] == -1: - unfinished_missions.pop(final_mission) - - message += ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}[{ctx.mission_req_table[mission].id}] " + - mark_up_objectives( - f"[{len(unfinished_missions[mission])}/" - f"{sum(1 for _ in ctx.locations_for_mission(mission))}]", - ctx, unfinished_locations, mission) - for mission in unfinished_missions) - - if ctx.ui: - ctx.ui.log_panels['All'].on_message_markup(message) - ctx.ui.log_panels['Starcraft2'].on_message_markup(message) - else: - sc2_logger.info(message) - else: - sc2_logger.warning("No mission table found, you are likely not connected to a server.") - - -def calc_unfinished_missions(ctx: SC2Context, unlocks=None): - unfinished_missions = [] - locations_completed = [] - - if not unlocks: - unlocks = initialize_blank_mission_dict(ctx.mission_req_table) - - available_missions = calc_available_missions(ctx, unlocks) - - for name in available_missions: - objectives = set(ctx.locations_for_mission(name)) - if objectives: - objectives_completed = ctx.checked_locations & objectives - if len(objectives_completed) < len(objectives): - unfinished_missions.append(name) - locations_completed.append(objectives_completed) - - else: # infer that this is the final mission as it has no objectives - unfinished_missions.append(name) - locations_completed.append(-1) - - return available_missions, dict(zip(unfinished_missions, locations_completed)) - - -def is_mission_available(ctx: SC2Context, mission_id_to_check): - unfinished_missions = calc_available_missions(ctx) - - return any(mission_id_to_check == ctx.mission_req_table[mission].id for mission in unfinished_missions) - - -def mark_up_mission_name(ctx: SC2Context, mission, unlock_table): - """Checks if the mission is required for game completion and adds '*' to the name to mark that.""" - - if ctx.mission_req_table[mission].completion_critical: - if ctx.ui: - message = "[color=AF99EF]" + mission + "[/color]" - else: - message = "*" + mission + "*" - else: - message = mission - - if ctx.ui: - unlocks = unlock_table[mission] - - if len(unlocks) > 0: - pre_message = f"[ref={list(ctx.mission_req_table).index(mission)}|Unlocks: " - pre_message += ", ".join(f"{unlock}({ctx.mission_req_table[unlock].id})" for unlock in unlocks) - pre_message += f"]" - message = pre_message + message + "[/ref]" - - return message - - -def mark_up_objectives(message, ctx, unfinished_locations, mission): - formatted_message = message - - if ctx.ui: - locations = unfinished_locations[mission] - - pre_message = f"[ref={list(ctx.mission_req_table).index(mission) + 30}|" - pre_message += "
    ".join(location for location in locations) - pre_message += f"]" - formatted_message = pre_message + message + "[/ref]" - - return formatted_message - - -def request_available_missions(ctx: SC2Context): - if ctx.mission_req_table: - message = "Available Missions: " - - # Initialize mission unlock table - unlocks = initialize_blank_mission_dict(ctx.mission_req_table) - - missions = calc_available_missions(ctx, unlocks) - message += \ - ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}" - f"[{ctx.mission_req_table[mission].id}]" - for mission in missions) - - if ctx.ui: - ctx.ui.log_panels['All'].on_message_markup(message) - ctx.ui.log_panels['Starcraft2'].on_message_markup(message) - else: - sc2_logger.info(message) - else: - sc2_logger.warning("No mission table found, you are likely not connected to a server.") - - -def calc_available_missions(ctx: SC2Context, unlocks=None): - available_missions = [] - missions_complete = 0 - - # Get number of missions completed - for loc in ctx.checked_locations: - if loc % victory_modulo == 0: - missions_complete += 1 - - for name in ctx.mission_req_table: - # Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips - if unlocks: - for unlock in ctx.mission_req_table[name].required_world: - unlocks[list(ctx.mission_req_table)[unlock - 1]].append(name) - - if mission_reqs_completed(ctx, name, missions_complete): - available_missions.append(name) - - return available_missions - - -def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete: int): - """Returns a bool signifying if the mission has all requirements complete and can be done - - Arguments: - ctx -- instance of SC2Context - locations_to_check -- the mission string name to check - missions_complete -- an int of how many missions have been completed - mission_path -- a list of missions that have already been checked -""" - if len(ctx.mission_req_table[mission_name].required_world) >= 1: - # A check for when the requirements are being or'd - or_success = False - - # Loop through required missions - for req_mission in ctx.mission_req_table[mission_name].required_world: - req_success = True - - # Check if required mission has been completed - if not (ctx.mission_req_table[list(ctx.mission_req_table)[req_mission - 1]].id * - victory_modulo + SC2WOL_LOC_ID_OFFSET) in ctx.checked_locations: - if not ctx.mission_req_table[mission_name].or_requirements: - return False - else: - req_success = False - - # Grid-specific logic (to avoid long path checks and infinite recursion) - if ctx.mission_order in (3, 4): - if req_success: - return True - else: - if req_mission is ctx.mission_req_table[mission_name].required_world[-1]: - return False - else: - continue - - # Recursively check required mission to see if it's requirements are met, in case !collect has been done - # Skipping recursive check on Grid settings to speed up checks and avoid infinite recursion - if not mission_reqs_completed(ctx, list(ctx.mission_req_table)[req_mission - 1], missions_complete): - if not ctx.mission_req_table[mission_name].or_requirements: - return False - else: - req_success = False - - # If requirement check succeeded mark or as satisfied - if ctx.mission_req_table[mission_name].or_requirements and req_success: - or_success = True - - if ctx.mission_req_table[mission_name].or_requirements: - # Return false if or requirements not met - if not or_success: - return False - - # Check number of missions - if missions_complete >= ctx.mission_req_table[mission_name].number: - return True - else: - return False - else: - return True - - -def initialize_blank_mission_dict(location_table): - unlocks = {} - - for mission in list(location_table): - unlocks[mission] = [] - - return unlocks - - -def check_game_install_path() -> bool: - # First thing: go to the default location for ExecuteInfo. - # An exception for Windows is included because it's very difficult to find ~\Documents if the user moved it. - if is_windows: - # The next five lines of utterly inscrutable code are brought to you by copy-paste from Stack Overflow. - # https://stackoverflow.com/questions/6227590/finding-the-users-my-documents-path/30924555# - import ctypes.wintypes - CSIDL_PERSONAL = 5 # My Documents - SHGFP_TYPE_CURRENT = 0 # Get current, not default value - - buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) - ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) - documentspath = buf.value - einfo = str(documentspath / Path("StarCraft II\\ExecuteInfo.txt")) - else: - einfo = str(bot.paths.get_home() / Path(bot.paths.USERPATH[bot.paths.PF])) - - # Check if the file exists. - if os.path.isfile(einfo): - - # Open the file and read it, picking out the latest executable's path. - with open(einfo) as f: - content = f.read() - if content: - try: - base = re.search(r" = (.*)Versions", content).group(1) - except AttributeError: - sc2_logger.warning(f"Found {einfo}, but it was empty. Run SC2 through the Blizzard launcher, then " - f"try again.") - return False - if os.path.exists(base): - executable = bot.paths.latest_executeble(Path(base).expanduser() / "Versions") - - # Finally, check the path for an actual executable. - # If we find one, great. Set up the SC2PATH. - if os.path.isfile(executable): - sc2_logger.info(f"Found an SC2 install at {base}!") - sc2_logger.debug(f"Latest executable at {executable}.") - os.environ["SC2PATH"] = base - sc2_logger.debug(f"SC2PATH set to {base}.") - return True - else: - sc2_logger.warning(f"We may have found an SC2 install at {base}, but couldn't find {executable}.") - else: - sc2_logger.warning(f"{einfo} pointed to {base}, but we could not find an SC2 install there.") - else: - sc2_logger.warning(f"Couldn't find {einfo}. Run SC2 through the Blizzard launcher, then try again. " - f"If that fails, please run /set_path with your SC2 install directory.") - return False - - -def is_mod_installed_correctly() -> bool: - """Searches for all required files.""" - if "SC2PATH" not in os.environ: - check_game_install_path() - - mapdir = os.environ['SC2PATH'] / Path('Maps/ArchipelagoCampaign') - mods = ["ArchipelagoCore", "ArchipelagoPlayer", "ArchipelagoPlayerWoL", "ArchipelagoTriggers"] - modfiles = [os.environ["SC2PATH"] / Path("Mods/" + mod + ".SC2Mod") for mod in mods] - wol_required_maps = ["WoL" + os.sep + map_name + ".SC2Map" for map_name in maps_table] - needs_files = False - - # Check for maps. - missing_maps = [] - for mapfile in wol_required_maps: - if not os.path.isfile(mapdir / mapfile): - missing_maps.append(mapfile) - if len(missing_maps) >= 19: - sc2_logger.warning(f"All map files missing from {mapdir}.") - needs_files = True - elif len(missing_maps) > 0: - for map in missing_maps: - sc2_logger.debug(f"Missing {map} from {mapdir}.") - sc2_logger.warning(f"Missing {len(missing_maps)} map files.") - needs_files = True - else: # Must be no maps missing - sc2_logger.info(f"All maps found in {mapdir}.") - - # Check for mods. - for modfile in modfiles: - if os.path.isfile(modfile) or os.path.isdir(modfile): - sc2_logger.info(f"Archipelago mod found at {modfile}.") - else: - sc2_logger.warning(f"Archipelago mod could not be found at {modfile}.") - needs_files = True - - # Final verdict. - if needs_files: - sc2_logger.warning(f"Required files are missing. Run /download_data to acquire them.") - return False - else: - sc2_logger.debug(f"All map/mod files are properly installed.") - return True - - -class DllDirectory: - # Credit to Black Sliver for this code. - # More info: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw - _old: typing.Optional[str] = None - _new: typing.Optional[str] = None - - def __init__(self, new: typing.Optional[str]): - self._new = new - - def __enter__(self): - old = self.get() - if self.set(self._new): - self._old = old - - def __exit__(self, *args): - if self._old is not None: - self.set(self._old) - - @staticmethod - def get() -> typing.Optional[str]: - if sys.platform == "win32": - n = ctypes.windll.kernel32.GetDllDirectoryW(0, None) - buf = ctypes.create_unicode_buffer(n) - ctypes.windll.kernel32.GetDllDirectoryW(n, buf) - return buf.value - # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific - return None - - @staticmethod - def set(s: typing.Optional[str]) -> bool: - if sys.platform == "win32": - return ctypes.windll.kernel32.SetDllDirectoryW(s) != 0 - # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific - return False - - -def download_latest_release_zip(owner: str, repo: str, api_version: str, metadata: str = None, force_download=False) -> (str, str): - """Downloads the latest release of a GitHub repo to the current directory as a .zip file.""" - import requests - - headers = {"Accept": 'application/vnd.github.v3+json'} - url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" - - r1 = requests.get(url, headers=headers) - if r1.status_code == 200: - latest_metadata = r1.json() - cleanup_downloaded_metadata(latest_metadata) - latest_metadata = str(latest_metadata) - # sc2_logger.info(f"Latest version: {latest_metadata}.") - else: - sc2_logger.warning(f"Status code: {r1.status_code}") - sc2_logger.warning(f"Failed to reach GitHub. Could not find download link.") - sc2_logger.warning(f"text: {r1.text}") - return "", metadata - - if (force_download is False) and (metadata == latest_metadata): - sc2_logger.info("Latest version already installed.") - return "", metadata - - sc2_logger.info(f"Attempting to download latest version of API version {api_version} of {repo}.") - download_url = r1.json()["assets"][0]["browser_download_url"] - - r2 = requests.get(download_url, headers=headers) - if r2.status_code == 200 and zipfile.is_zipfile(io.BytesIO(r2.content)): - tempdir = tempfile.gettempdir() - file = tempdir + os.sep + f"{repo}.zip" - with open(file, "wb") as fh: - fh.write(r2.content) - sc2_logger.info(f"Successfully downloaded {repo}.zip.") - return file, latest_metadata - else: - sc2_logger.warning(f"Status code: {r2.status_code}") - sc2_logger.warning("Download failed.") - sc2_logger.warning(f"text: {r2.text}") - return "", metadata - - -def cleanup_downloaded_metadata(medatada_json): - for asset in medatada_json['assets']: - del asset['download_count'] - - -def is_mod_update_available(owner: str, repo: str, api_version: str, metadata: str) -> bool: - import requests - - headers = {"Accept": 'application/vnd.github.v3+json'} - url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" - - r1 = requests.get(url, headers=headers) - if r1.status_code == 200: - latest_metadata = r1.json() - cleanup_downloaded_metadata(latest_metadata) - latest_metadata = str(latest_metadata) - if metadata != latest_metadata: - return True - else: - return False - - else: - sc2_logger.warning(f"Failed to reach GitHub while checking for updates.") - sc2_logger.warning(f"Status code: {r1.status_code}") - sc2_logger.warning(f"text: {r1.text}") - return False - - -def launch(): - colorama.init() - asyncio.run(main()) - colorama.deinit() diff --git a/worlds/sc2wol/Items.py b/worlds/sc2wol/Items.py deleted file mode 100644 index 971a75375fe4..000000000000 --- a/worlds/sc2wol/Items.py +++ /dev/null @@ -1,421 +0,0 @@ -from BaseClasses import Item, ItemClassification, MultiWorld -import typing - -from .Options import get_option_value -from .MissionTables import vanilla_mission_req_table - - -class ItemData(typing.NamedTuple): - code: typing.Optional[int] - type: typing.Optional[str] - number: typing.Optional[int] - classification: ItemClassification = ItemClassification.useful - quantity: int = 1 - parent_item: str = None - origin: typing.Set[str] = {"wol"} - - -class StarcraftWoLItem(Item): - game: str = "Starcraft 2 Wings of Liberty" - - -def get_full_item_list(): - return item_table - - -SC2WOL_ITEM_ID_OFFSET = 1000 - -item_table = { - "Marine": ItemData(0 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, classification=ItemClassification.progression), - "Medic": ItemData(1 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, classification=ItemClassification.progression), - "Firebat": ItemData(2 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, classification=ItemClassification.progression), - "Marauder": ItemData(3 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, classification=ItemClassification.progression), - "Reaper": ItemData(4 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, classification=ItemClassification.progression), - "Hellion": ItemData(5 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, classification=ItemClassification.progression), - "Vulture": ItemData(6 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, classification=ItemClassification.progression), - "Goliath": ItemData(7 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, classification=ItemClassification.progression), - "Diamondback": ItemData(8 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, classification=ItemClassification.progression), - "Siege Tank": ItemData(9 + SC2WOL_ITEM_ID_OFFSET, "Unit", 9, classification=ItemClassification.progression), - "Medivac": ItemData(10 + SC2WOL_ITEM_ID_OFFSET, "Unit", 10, classification=ItemClassification.progression), - "Wraith": ItemData(11 + SC2WOL_ITEM_ID_OFFSET, "Unit", 11, classification=ItemClassification.progression), - "Viking": ItemData(12 + SC2WOL_ITEM_ID_OFFSET, "Unit", 12, classification=ItemClassification.progression), - "Banshee": ItemData(13 + SC2WOL_ITEM_ID_OFFSET, "Unit", 13, classification=ItemClassification.progression), - "Battlecruiser": ItemData(14 + SC2WOL_ITEM_ID_OFFSET, "Unit", 14, classification=ItemClassification.progression), - "Ghost": ItemData(15 + SC2WOL_ITEM_ID_OFFSET, "Unit", 15, classification=ItemClassification.progression), - "Spectre": ItemData(16 + SC2WOL_ITEM_ID_OFFSET, "Unit", 16, classification=ItemClassification.progression), - "Thor": ItemData(17 + SC2WOL_ITEM_ID_OFFSET, "Unit", 17, classification=ItemClassification.progression), - # EE units - "Liberator": ItemData(18 + SC2WOL_ITEM_ID_OFFSET, "Unit", 18, classification=ItemClassification.progression, origin={"nco", "ext"}), - "Valkyrie": ItemData(19 + SC2WOL_ITEM_ID_OFFSET, "Unit", 19, classification=ItemClassification.progression, origin={"bw"}), - "Widow Mine": ItemData(20 + SC2WOL_ITEM_ID_OFFSET, "Unit", 20, classification=ItemClassification.progression, origin={"ext"}), - "Cyclone": ItemData(21 + SC2WOL_ITEM_ID_OFFSET, "Unit", 21, classification=ItemClassification.progression, origin={"ext"}), - - # Some other items are moved to Upgrade group because of the way how the bot message is parsed - "Progressive Infantry Weapon": ItemData(100 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, quantity=3), - "Progressive Infantry Armor": ItemData(102 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, quantity=3), - "Progressive Vehicle Weapon": ItemData(103 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, quantity=3), - "Progressive Vehicle Armor": ItemData(104 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 6, quantity=3), - "Progressive Ship Weapon": ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, quantity=3), - "Progressive Ship Armor": ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, quantity=3), - # Upgrade bundle 'number' values are used as indices to get affected 'number's - "Progressive Weapon Upgrade": ItemData(107 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, quantity=3), - "Progressive Armor Upgrade": ItemData(108 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 1, quantity=3), - "Progressive Infantry Upgrade": ItemData(109 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, quantity=3), - "Progressive Vehicle Upgrade": ItemData(110 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 3, quantity=3), - "Progressive Ship Upgrade": ItemData(111 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, quantity=3), - "Progressive Weapon/Armor Upgrade": ItemData(112 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 5, quantity=3), - - "Projectile Accelerator (Bunker)": ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0, parent_item="Bunker"), - "Neosteel Bunker (Bunker)": ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1, parent_item="Bunker"), - "Titanium Housing (Missile Turret)": ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, classification=ItemClassification.filler, parent_item="Missile Turret"), - "Hellstorm Batteries (Missile Turret)": ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3, parent_item="Missile Turret"), - "Advanced Construction (SCV)": ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4), - "Dual-Fusion Welders (SCV)": ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5), - "Fire-Suppression System (Building)": ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6), - "Orbital Command (Building)": ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7), - "Progressive Stimpack (Marine)": ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 0, parent_item="Marine", quantity=2), - "Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, classification=ItemClassification.progression, parent_item="Marine"), - "Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, classification=ItemClassification.filler, parent_item="Medic"), - "Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, classification=ItemClassification.progression, parent_item="Medic"), - "Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.filler, parent_item="Firebat"), - "Juggernaut Plating (Firebat)": ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13, parent_item="Firebat"), - "Concussive Shells (Marauder)": ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14, parent_item="Marauder"), - "Kinetic Foam (Marauder)": ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15, parent_item="Marauder"), - "U-238 Rounds (Reaper)": ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16, parent_item="Reaper"), - "G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.progression, parent_item="Reaper"), - # Items from EE - "Mag-Field Accelerators (Cyclone)": ItemData(218 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 18, parent_item="Cyclone", origin={"ext"}), - "Mag-Field Launchers (Cyclone)": ItemData(219 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 19, parent_item="Cyclone", origin={"ext"}), - # Items from new mod - "Laser Targeting System (Marine)": ItemData(220 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8, classification=ItemClassification.filler, parent_item="Marine", origin={"nco"}), # Freed slot from Stimpack - "Magrail Munitions (Marine)": ItemData(221 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 20, parent_item="Marine", origin={"nco"}), - "Optimized Logistics (Marine)": ItemData(222 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 21, classification=ItemClassification.filler, parent_item="Marine", origin={"nco"}), - "Restoration (Medic)": ItemData(223 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 22, classification=ItemClassification.filler, parent_item="Medic", origin={"bw"}), - "Optical Flare (Medic)": ItemData(224 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 23, classification=ItemClassification.filler, parent_item="Medic", origin={"bw"}), - "Optimized Logistics (Medic)": ItemData(225 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 24, classification=ItemClassification.filler, parent_item="Medic", origin={"bw"}), - "Progressive Stimpack (Firebat)": ItemData(226 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 6, parent_item="Firebat", quantity=2, origin={"bw"}), - "Optimized Logistics (Firebat)": ItemData(227 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 25, parent_item="Firebat", origin={"bw"}), - "Progressive Stimpack (Marauder)": ItemData(228 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 8, parent_item="Marauder", quantity=2, origin={"nco"}), - "Laser Targeting System (Marauder)": ItemData(229 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 26, classification=ItemClassification.filler, parent_item="Marauder", origin={"nco"}), - "Magrail Munitions (Marauder)": ItemData(230 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 27, classification=ItemClassification.filler, parent_item="Marauder", origin={"nco"}), - "Internal Tech Module (Marauder)": ItemData(231 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 28, classification=ItemClassification.filler, parent_item="Marauder", origin={"nco"}), - - # Items from new mod - "Progressive Stimpack (Reaper)": ItemData(250 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 10, parent_item="Reaper", quantity=2, origin={"nco"}), - "Laser Targeting System (Reaper)": ItemData(251 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 0, classification=ItemClassification.filler, parent_item="Reaper", origin={"nco"}), - "Advanced Cloaking Field (Reaper)": ItemData(252 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 1, parent_item="Reaper", origin={"nco"}), - "Spider Mines (Reaper)": ItemData(253 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 2, classification=ItemClassification.filler, parent_item="Reaper", origin={"nco"}), - "Combat Drugs (Reaper)": ItemData(254 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 3, classification=ItemClassification.filler, parent_item="Reaper", origin={"ext"}), - "Hellbat Aspect (Hellion)": ItemData(255 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 4, parent_item="Hellion", origin={"nco"}), - "Smart Servos (Hellion)": ItemData(256 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 5, parent_item="Hellion", origin={"nco"}), - "Optimized Logistics (Hellion)": ItemData(257 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 6, classification=ItemClassification.filler, parent_item="Hellion", origin={"nco"}), - "Jump Jets (Hellion)": ItemData(258 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 7, classification=ItemClassification.filler, parent_item="Hellion", origin={"nco"}), - "Progressive Stimpack (Hellion)": ItemData(259 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 12, parent_item="Hellion", quantity=2, origin={"nco"}), - "Ion Thrusters (Vulture)": ItemData(260 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 8, classification=ItemClassification.filler, parent_item="Vulture", origin={"bw"}), - "Auto Launchers (Vulture)": ItemData(261 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 9, parent_item="Vulture", origin={"bw"}), - "High Explosive Munition (Spider Mine)": ItemData(262 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 10, origin={"bw"}), - "Jump Jets (Goliath)": ItemData(263 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 11, classification=ItemClassification.filler, parent_item="Goliath", origin={"nco"}), - "Optimized Logistics (Goliath)": ItemData(264 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 12, classification=ItemClassification.filler, parent_item="Goliath", origin={"nco"}), - "Hyperfluxor (Diamondback)": ItemData(265 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 13, parent_item="Diamondback", origin={"ext"}), - "Burst Capacitors (Diamondback)": ItemData(266 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 14, classification=ItemClassification.filler, parent_item="Diamondback", origin={"ext"}), - "Optimized Logistics (Diamondback)": ItemData(267 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 15, parent_item="Diamondback", origin={"ext"}), - "Jump Jets (Siege Tank)": ItemData(268 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 16, parent_item="Siege Tank", origin={"nco"}), - "Spider Mines (Siege Tank)": ItemData(269 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 17, classification=ItemClassification.filler, parent_item="Siege Tank", origin={"nco"}), - "Smart Servos (Siege Tank)": ItemData(270 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 18, classification=ItemClassification.filler, parent_item="Siege Tank", origin={"nco"}), - "Graduating Range (Siege Tank)": ItemData(271 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 19, classification=ItemClassification.progression, parent_item="Siege Tank", origin={"ext"}), - "Laser Targeting System (Siege Tank)": ItemData(272 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 20, parent_item="Siege Tank", origin={"nco"}), - "Advanced Siege Tech (Siege Tank)": ItemData(273 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 21, parent_item="Siege Tank", origin={"ext"}), - "Internal Tech Module (Siege Tank)": ItemData(274 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 22, classification=ItemClassification.filler, parent_item="Siege Tank", origin={"nco"}), - "Optimized Logistics (Predator)": ItemData(275 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 23, classification=ItemClassification.filler, parent_item="Predator", origin={"ext"}), - "Expanded Hull (Medivac)": ItemData(276 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 24, classification=ItemClassification.filler, parent_item="Medivac", origin={"ext"}), - "Afterburners (Medivac)": ItemData(277 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 25, classification=ItemClassification.filler, parent_item="Medivac", origin={"ext"}), - "Advanced Laser Technology (Wraith)": ItemData(278 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 26, classification=ItemClassification.progression, parent_item="Wraith", origin={"ext"}), - "Smart Servos (Viking)": ItemData(279 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 27, parent_item="Viking", origin={"ext"}), - "Magrail Munitions (Viking)": ItemData(280 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 28, parent_item="Viking", origin={"ext"}), - - "Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.filler, parent_item="Hellion"), - "Thermite Filaments (Hellion)": ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1, parent_item="Hellion"), - "Cerberus Mine (Spider Mine)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, classification=ItemClassification.filler), - "Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, classification=ItemClassification.filler, parent_item="Vulture"), - "Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4, parent_item="Goliath"), - "Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5, parent_item="Goliath"), - "Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.filler, parent_item="Diamondback"), - "Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, classification=ItemClassification.filler, parent_item="Diamondback"), - "Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8, classification=ItemClassification.progression, parent_item="Siege Tank"), - "Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9, parent_item="Siege Tank"), - "Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.filler, parent_item="Medivac"), - "Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, classification=ItemClassification.filler, parent_item="Medivac"), - "Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.filler, parent_item="Wraith"), - "Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, classification=ItemClassification.filler, parent_item="Wraith"), - "Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14, parent_item="Viking"), - "Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15, parent_item="Viking"), - "Progressive Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 2, classification=ItemClassification.filler, parent_item="Banshee", quantity=2), - "Shockwave Missile Battery (Banshee)": ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17, parent_item="Banshee"), - "Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, classification=ItemClassification.filler, parent_item="Battlecruiser"), - "Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.filler, parent_item="Battlecruiser"), - "Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20, parent_item="Ghost"), - "Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21, parent_item="Ghost"), - "Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, classification=ItemClassification.progression, parent_item="Spectre"), - "Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23, parent_item="Spectre"), - "330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.filler, parent_item="Thor"), - "Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.filler, parent_item="Thor"), - # Items from EE - "Advanced Ballistics (Liberator)": ItemData(326 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 26, parent_item="Liberator", origin={"ext"}), - "Raid Artillery (Liberator)": ItemData(327 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 27, classification=ItemClassification.progression, parent_item="Liberator", origin={"nco"}), - "Drilling Claws (Widow Mine)": ItemData(328 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 28, classification=ItemClassification.filler, parent_item="Widow Mine", origin={"ext"}), - "Concealment (Widow Mine)": ItemData(329 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 29, classification=ItemClassification.progression, parent_item="Widow Mine", origin={"ext"}), - - #Items from new mod - "Hyperflight Rotors (Banshee)": ItemData(350 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 0, classification=ItemClassification.filler, parent_item="Banshee", origin={"ext"}), - "Laser Targeting System (Banshee)": ItemData(351 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 1, classification=ItemClassification.filler, parent_item="Banshee", origin={"nco"}), - "Internal Tech Module (Banshee)": ItemData(352 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 2, classification=ItemClassification.filler, parent_item="Banshee", origin={"nco"}), - "Tactical Jump (Battlecruiser)": ItemData(353 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 3, parent_item="Battlecruiser", origin={"nco", "ext"}), - "Cloak (Battlecruiser)": ItemData(354 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 4, parent_item="Battlecruiser", origin={"nco"}), - "ATX Laser Battery (Battlecruiser)": ItemData(355 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 5, classification=ItemClassification.progression, parent_item="Battlecruiser", origin={"nco"}), - "Optimized Logistics (Battlecruiser)": ItemData(356 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 6, classification=ItemClassification.filler, parent_item="Battlecruiser", origin={"ext"}), - "Internal Tech Module (Battlecruiser)": ItemData(357 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 7, classification=ItemClassification.filler, parent_item="Battlecruiser", origin={"nco"}), - "EMP Rounds (Ghost)": ItemData(358 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 8, parent_item="Ghost", origin={"ext"}), - "Lockdown (Ghost)": ItemData(359 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 9, parent_item="Ghost", origin={"bw"}), - "Impaler Rounds (Spectre)": ItemData(360 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 10, parent_item="Spectre", origin={"ext"}), - "Progressive High Impact Payload (Thor)": ItemData(361 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 14, parent_item="Thor", quantity=2, origin={"ext"}), # L2 is Smart Servos - "Bio Mechanical Repair Drone (Raven)": ItemData(363 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 13, parent_item="Raven", origin={"nco"}), - "Spider Mines (Raven)": ItemData(364 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 14, parent_item="Raven", origin={"nco"}), - "Railgun Turret (Raven)": ItemData(365 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 15, parent_item="Raven", origin={"nco"}), - "Hunter-Seeker Weapon (Raven)": ItemData(366 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 16, parent_item="Raven", origin={"nco"}), - "Interference Matrix (Raven)": ItemData(367 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 17, parent_item="Raven", origin={"ext"}), - "Anti-Armor Missile (Raven)": ItemData(368 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 18, classification=ItemClassification.filler, parent_item="Raven", origin={"ext"}), - "Internal Tech Module (Raven)": ItemData(369 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 19, classification=ItemClassification.filler, parent_item="Raven", origin={"nco"}), - "EMP Shockwave (Science Vessel)": ItemData(370 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 20, parent_item="Science Vessel", origin={"bw"}), - "Defensive Matrix (Science Vessel)": ItemData(371 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 21, parent_item="Science Vessel", origin={"bw"}), - "Targeting Optics (Cyclone)": ItemData(372 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 22, parent_item="Cyclone", origin={"ext"}), - "Rapid Fire Launchers (Cyclone)": ItemData(373 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 23, parent_item="Cyclone", origin={"ext"}), - "Cloak (Liberator)": ItemData(374 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 24, classification=ItemClassification.filler, parent_item="Liberator", origin={"nco"}), - "Laser Targeting System (Liberator)": ItemData(375 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 25, classification=ItemClassification.filler, parent_item="Liberator", origin={"ext"}), - "Optimized Logistics (Liberator)": ItemData(376 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 26, classification=ItemClassification.filler, parent_item="Liberator", origin={"nco"}), - "Black Market Launchers (Widow Mine)": ItemData(377 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 27, classification=ItemClassification.filler, parent_item="Widow Mine", origin={"ext"}), - "Executioner Missiles (Widow Mine)": ItemData(378 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 28, parent_item="Widow Mine", origin={"ext"}), - - # Just lazy to create a new group for one unit - "Enhanced Cluster Launchers (Valkyrie)": ItemData(379 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17, parent_item="Valkyrie", origin={"ext"}), - "Shaped Hull (Valkyrie)": ItemData(380 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 20, classification=ItemClassification.filler, parent_item="Valkyrie", origin={"ext"}), - "Burst Lasers (Valkyrie)": ItemData(381 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 21, parent_item="Valkyrie", origin={"ext"}), - "Afterburners (Valkyrie)": ItemData(382 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 22, classification=ItemClassification.filler, parent_item="Valkyrie", origin={"ext"}), - - "Bunker": ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, classification=ItemClassification.progression), - "Missile Turret": ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, classification=ItemClassification.progression), - "Sensor Tower": ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2), - - "War Pigs": ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0, classification=ItemClassification.progression), - "Devil Dogs": ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, classification=ItemClassification.filler), - "Hammer Securities": ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2), - "Spartan Company": ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3, classification=ItemClassification.progression), - "Siege Breakers": ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4), - "Hel's Angel": ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5, classification=ItemClassification.progression), - "Dusk Wings": ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6), - "Jackson's Revenge": ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7), - - "Ultra-Capacitors": ItemData(600 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 0), - "Vanadium Plating": ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1), - "Orbital Depots": ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2), - "Micro-Filtering": ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3), - "Automated Refinery": ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4), - "Command Center Reactor": ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5), - "Raven": ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6), - "Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, classification=ItemClassification.progression), - "Tech Reactor": ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8), - "Orbital Strike": ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9), - "Shrike Turret (Bunker)": ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10, parent_item="Bunker"), - "Fortified Bunker (Bunker)": ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, parent_item="Bunker"), - "Planetary Fortress": ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12, classification=ItemClassification.progression), - "Perdition Turret": ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 13, classification=ItemClassification.progression), - "Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14, classification=ItemClassification.filler), - "Hercules": ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 15, classification=ItemClassification.progression), - "Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16), - "Progressive Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 4, quantity=2), - "Hive Mind Emulator": ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 18, ItemClassification.progression), - "Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19, classification=ItemClassification.progression), - - "Zealot": ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 0, classification=ItemClassification.progression), - "Stalker": ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 1, classification=ItemClassification.progression), - "High Templar": ItemData(702 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 2, classification=ItemClassification.progression), - "Dark Templar": ItemData(703 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 3, classification=ItemClassification.progression), - "Immortal": ItemData(704 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 4, classification=ItemClassification.progression), - "Colossus": ItemData(705 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 5), - "Phoenix": ItemData(706 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 6, classification=ItemClassification.filler), - "Void Ray": ItemData(707 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 7, classification=ItemClassification.progression), - "Carrier": ItemData(708 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 8, classification=ItemClassification.progression), - - # Filler items to fill remaining spots - "+15 Starting Minerals": ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, quantity=0, classification=ItemClassification.filler), - "+15 Starting Vespene": ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, quantity=0, classification=ItemClassification.filler), - # This Filler item isn't placed by the generator yet unless plando'd - "+2 Starting Supply": ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, quantity=0, classification=ItemClassification.filler), - # This item is used to "remove" location from the game. Never placed unless plando'd - "Nothing": ItemData(803 + SC2WOL_ITEM_ID_OFFSET, "Nothing Group", 2, quantity=0, classification=ItemClassification.trap), - - # "Keystone Piece": ItemData(850 + SC2WOL_ITEM_ID_OFFSET, "Goal", 0, quantity=0, classification=ItemClassification.progression_skip_balancing) -} - -def get_item_table(multiworld: MultiWorld, player: int): - return item_table - -basic_units = { - 'Marine', - 'Marauder', - 'Goliath', - 'Hellion', - 'Vulture' -} - -advanced_basic_units = basic_units.union({ - 'Reaper', - 'Diamondback', - 'Viking' -}) - - -def get_basic_units(multiworld: MultiWorld, player: int) -> typing.Set[str]: - if get_option_value(multiworld, player, 'required_tactics') > 0: - return advanced_basic_units - else: - return basic_units - - -item_name_groups = {} -for item, data in get_full_item_list().items(): - item_name_groups.setdefault(data.type, []).append(item) - if data.type in ("Armory 1", "Armory 2") and '(' in item: - short_name = item[:item.find(' (')] - item_name_groups[short_name] = [item] -item_name_groups["Missions"] = ["Beat " + mission_name for mission_name in vanilla_mission_req_table] - - -# Items that can be placed before resources if not already in -# General upgrades and Mercs -second_pass_placeable_items: typing.Tuple[str, ...] = ( - # Buildings without upgrades - "Sensor Tower", - "Hive Mind Emulator", - "Psi Disrupter", - "Perdition Turret", - # General upgrades without any dependencies - "Advanced Construction (SCV)", - "Dual-Fusion Welders (SCV)", - "Fire-Suppression System (Building)", - "Orbital Command (Building)", - "Ultra-Capacitors", - "Vanadium Plating", - "Orbital Depots", - "Micro-Filtering", - "Automated Refinery", - "Command Center Reactor", - "Tech Reactor", - "Planetary Fortress", - "Cellular Reactor", - "Progressive Regenerative Bio-Steel", # Place only L1 - # Mercenaries - "War Pigs", - "Devil Dogs", - "Hammer Securities", - "Spartan Company", - "Siege Breakers", - "Hel's Angel", - "Dusk Wings", - "Jackson's Revenge" -) - - -filler_items: typing.Tuple[str, ...] = ( - '+15 Starting Minerals', - '+15 Starting Vespene' -) - -# Defense rating table -# Commented defense ratings are handled in LogicMixin -defense_ratings = { - "Siege Tank": 5, - # "Maelstrom Rounds": 2, - "Planetary Fortress": 3, - # Bunker w/ Marine/Marauder: 3, - "Perdition Turret": 2, - "Missile Turret": 2, - "Vulture": 2, - "Liberator": 2, - "Widow Mine": 2 - # "Concealment (Widow Mine)": 1 -} -zerg_defense_ratings = { - "Perdition Turret": 2, - # Bunker w/ Firebat: 2, - "Hive Mind Emulator": 3, - "Psi Disruptor": 3 -} - -spider_mine_sources = { - "Vulture", - "Spider Mines (Reaper)", - "Spider Mines (Siege Tank)", - "Spider Mines (Raven)" -} - -progressive_if_nco = { - "Progressive Stimpack (Marine)", - "Progressive Stimpack (Firebat)", - "Progressive Cross-Spectrum Dampeners (Banshee)", - "Progressive Regenerative Bio-Steel" -} - -# 'number' values of upgrades for upgrade bundle items -upgrade_numbers = [ - {0, 4, 8}, # Weapon - {2, 6, 10}, # Armor - {0, 2}, # Infantry - {4, 6}, # Vehicle - {8, 10}, # Starship - {0, 2, 4, 6, 8, 10} # All -] -# Names of upgrades to be included for different options -upgrade_included_names = [ - { # Individual Items - "Progressive Infantry Weapon", - "Progressive Infantry Armor", - "Progressive Vehicle Weapon", - "Progressive Vehicle Armor", - "Progressive Ship Weapon", - "Progressive Ship Armor" - }, - { # Bundle Weapon And Armor - "Progressive Weapon Upgrade", - "Progressive Armor Upgrade" - }, - { # Bundle Unit Class - "Progressive Infantry Upgrade", - "Progressive Vehicle Upgrade", - "Progressive Starship Upgrade" - }, - { # Bundle All - "Progressive Weapon/Armor Upgrade" - } -] - -lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if - data.code} -# Map type to expected int -type_flaggroups: typing.Dict[str, int] = { - "Unit": 0, - "Upgrade": 1, # Weapon / Armor upgrades - "Armory 1": 2, # Unit upgrades - "Armory 2": 3, # Unit upgrades - "Building": 4, - "Mercenary": 5, - "Laboratory": 6, - "Protoss": 7, - "Minerals": 8, - "Vespene": 9, - "Supply": 10, - "Goal": 11, - "Armory 3": 12, # Unit upgrades - "Armory 4": 13, # Unit upgrades - "Progressive Upgrade": 14, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) - "Nothing Group": 15 -} diff --git a/worlds/sc2wol/Locations.py b/worlds/sc2wol/Locations.py deleted file mode 100644 index fba7051337df..000000000000 --- a/worlds/sc2wol/Locations.py +++ /dev/null @@ -1,516 +0,0 @@ -from enum import IntEnum -from typing import List, Tuple, Optional, Callable, NamedTuple -from BaseClasses import MultiWorld -from .Options import get_option_value - -from BaseClasses import Location - -SC2WOL_LOC_ID_OFFSET = 1000 - - -class SC2WoLLocation(Location): - game: str = "Starcraft2WoL" - - -class LocationType(IntEnum): - VICTORY = 0 # Winning a mission - MISSION_PROGRESS = 1 # All tasks done for progressing the mission normally towards victory. All cleaning of expansion bases falls here - BONUS = 2 # Bonus objective, getting a campaign or mission bonus in vanilla (credits, research, bonus units or resources) - CHALLENGE = 3 # Challenging objectives, often harder than just completing a mission - OPTIONAL_BOSS = 4 # Any boss that's not required to win the mission. All Brutalisks, Loki, etc. - -class LocationData(NamedTuple): - region: str - name: str - code: Optional[int] - type: LocationType - rule: Callable = lambda state: True - - -def get_locations(multiworld: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]: - # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option - logic_level = get_option_value(multiworld, player, 'required_tactics') - location_table: List[LocationData] = [ - LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100, LocationType.VICTORY), - LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Second Statue", SC2WOL_LOC_ID_OFFSET + 102, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Third Statue", SC2WOL_LOC_ID_OFFSET + 103, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Special Delivery", SC2WOL_LOC_ID_OFFSET + 107, LocationType.MISSION_PROGRESS), - LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("The Outlaws", "The Outlaws: North Resource Pickups", SC2WOL_LOC_ID_OFFSET + 202, LocationType.BONUS), - LocationData("The Outlaws", "The Outlaws: Bunker", SC2WOL_LOC_ID_OFFSET + 203, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 2 and - (logic_level > 0 or state._sc2wol_has_anti_air(multiworld, player))), - LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301, LocationType.BONUS), - LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 2), - LocationData("Zero Hour", "Zero Hour: First Hatchery", SC2WOL_LOC_ID_OFFSET + 304, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 305, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 306, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Fourth Hatchery", SC2WOL_LOC_ID_OFFSET + 307, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Evacuation", "Evacuation: North Chrysalis", SC2WOL_LOC_ID_OFFSET + 401, LocationType.BONUS), - LocationData("Evacuation", "Evacuation: West Chrysalis", SC2WOL_LOC_ID_OFFSET + 402, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Evacuation", "Evacuation: East Chrysalis", SC2WOL_LOC_ID_OFFSET + 403, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Evacuation", "Evacuation: Reach Hanson", SC2WOL_LOC_ID_OFFSET + 404, LocationType.MISSION_PROGRESS), - LocationData("Evacuation", "Evacuation: Secret Resource Stash", SC2WOL_LOC_ID_OFFSET + 405, LocationType.BONUS), - LocationData("Evacuation", "Evacuation: Flawless", SC2WOL_LOC_ID_OFFSET + 406, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500, LocationType.VICTORY, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 4 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: North Infested Command Center", SC2WOL_LOC_ID_OFFSET + 503, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: South Infested Command Center", SC2WOL_LOC_ID_OFFSET + 504, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: Northwest Bar", SC2WOL_LOC_ID_OFFSET + 505, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: North Bar", SC2WOL_LOC_ID_OFFSET + 506, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: South Bar", SC2WOL_LOC_ID_OFFSET + 507, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: North Nexus", SC2WOL_LOC_ID_OFFSET + 601, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: East Nexus", SC2WOL_LOC_ID_OFFSET + 602, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: First Terror Fleet", SC2WOL_LOC_ID_OFFSET + 604, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: Second Terror Fleet", SC2WOL_LOC_ID_OFFSET + 605, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: Third Terror Fleet", SC2WOL_LOC_ID_OFFSET + 606, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: Northeast Colony Base", SC2WOL_LOC_ID_OFFSET + 704, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: East Colony Base", SC2WOL_LOC_ID_OFFSET + 705, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: Middle Colony Base", SC2WOL_LOC_ID_OFFSET + 706, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: Southeast Colony Base", SC2WOL_LOC_ID_OFFSET + 707, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: Southwest Colony Base", SC2WOL_LOC_ID_OFFSET + 708, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801, LocationType.BONUS), - LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802, LocationType.BONUS), - LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: First Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 805, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: Second Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 806, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900, LocationType.VICTORY, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, False) >= 7), - LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5), - LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5), - LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5), - LocationData("The Dig", "The Dig: Moebius Base", SC2WOL_LOC_ID_OFFSET + 904, LocationType.MISSION_PROGRESS), - LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000, LocationType.VICTORY, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core", SC2WOL_LOC_ID_OFFSET + 1001, LocationType.MISSION_PROGRESS), - LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002, LocationType.MISSION_PROGRESS, - lambda state: (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Mid Rescue", SC2WOL_LOC_ID_OFFSET + 1005, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Nydus Roof Rescue", SC2WOL_LOC_ID_OFFSET + 1006, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1009, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100, LocationType.VICTORY, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101, LocationType.BONUS), - LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102, LocationType.BONUS), - LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103, LocationType.BONUS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104, LocationType.BONUS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1105, LocationType.MISSION_PROGRESS), - LocationData("Supernova", "Supernova: Middle Base", SC2WOL_LOC_ID_OFFSET + 1106, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1107, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200, LocationType.VICTORY, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201, LocationType.MISSION_PROGRESS), - LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204, LocationType.BONUS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205, LocationType.BONUS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Mothership", SC2WOL_LOC_ID_OFFSET + 1206, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Expansion Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1207, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Middle Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1208, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Southeast Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1209, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Stargate Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1210, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Northwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1211, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: West Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1212, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Southwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1213, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300, LocationType.VICTORY, - lambda state: logic_level > 0 or - state._sc2wol_has_anti_air(multiworld, player) and ( - state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301, LocationType.BONUS), - LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302, LocationType.OPTIONAL_BOSS, - lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Devil's Playground: North Reapers", SC2WOL_LOC_ID_OFFSET + 1303, LocationType.BONUS), - LocationData("Devil's Playground", "Devil's Playground: Middle Reapers", SC2WOL_LOC_ID_OFFSET + 1304, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Devil's Playground: Southwest Reapers", SC2WOL_LOC_ID_OFFSET + 1305, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Devil's Playground: Southeast Reapers", SC2WOL_LOC_ID_OFFSET + 1306, LocationType.BONUS, - lambda state: logic_level > 0 or - state._sc2wol_has_anti_air(multiworld, player) and ( - state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Devil's Playground", "Devil's Playground: East Reapers", SC2WOL_LOC_ID_OFFSET + 1307, LocationType.BONUS, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (logic_level > 0 or - state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400, LocationType.VICTORY, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401, LocationType.BONUS), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402, LocationType.BONUS, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403, LocationType.BONUS, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Middle Base", SC2WOL_LOC_ID_OFFSET + 1404, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Main Base", SC2WOL_LOC_ID_OFFSET + 1405, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: No Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1406, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_has_competent_ground_to_air(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 1 Terrazine Node Sealed", SC2WOL_LOC_ID_OFFSET + 1407, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_has_competent_ground_to_air(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 2 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1408, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 3 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1409, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 4 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1410, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 5 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1411, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500, LocationType.VICTORY), - LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501, LocationType.BONUS), - LocationData("Breakout", "Breakout: Siege Tank Prison", SC2WOL_LOC_ID_OFFSET + 1502, LocationType.BONUS), - LocationData("Breakout", "Breakout: First Checkpoint", SC2WOL_LOC_ID_OFFSET + 1503, LocationType.MISSION_PROGRESS), - LocationData("Breakout", "Breakout: Second Checkpoint", SC2WOL_LOC_ID_OFFSET + 1504, LocationType.MISSION_PROGRESS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600, LocationType.VICTORY), - LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601, LocationType.MISSION_PROGRESS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602, LocationType.MISSION_PROGRESS), - LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603, LocationType.BONUS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604, LocationType.BONUS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700, LocationType.VICTORY, - lambda state: state._sc2wol_has_train_killers(multiworld, player) and - state._sc2wol_has_anti_air(multiworld, player)), - LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Close Diamondback", SC2WOL_LOC_ID_OFFSET + 1704, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Northwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1705, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: North Diamondback", SC2WOL_LOC_ID_OFFSET + 1706, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Northeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1707, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Southwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1708, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Southeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1709, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Kill Team", SC2WOL_LOC_ID_OFFSET + 1710, LocationType.CHALLENGE, - lambda state: (logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player)) and - state._sc2wol_has_train_killers(multiworld, player) and - state._sc2wol_has_anti_air(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 or state._sc2wol_has_anti_air)), - LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803, LocationType.BONUS), - LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: North Command Center", SC2WOL_LOC_ID_OFFSET + 1805, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: South Command Center", SC2WOL_LOC_ID_OFFSET + 1806, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: West Command Center", SC2WOL_LOC_ID_OFFSET + 1807, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901, LocationType.MISSION_PROGRESS), - LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903, LocationType.BONUS), - LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: West Base", SC2WOL_LOC_ID_OFFSET + 1906, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Northwest Base", SC2WOL_LOC_ID_OFFSET + 1907, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Northeast Base", SC2WOL_LOC_ID_OFFSET + 1908, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1909, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004, LocationType.BONUS), - LocationData("Media Blitz", "Media Blitz: All Barracks", SC2WOL_LOC_ID_OFFSET + 2005, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: All Factories", SC2WOL_LOC_ID_OFFSET + 2006, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: All Starports", SC2WOL_LOC_ID_OFFSET + 2007, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Odin Not Trashed", SC2WOL_LOC_ID_OFFSET + 2008, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100, LocationType.VICTORY, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101, LocationType.BONUS), - LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102, LocationType.BONUS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103,LocationType.BONUS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104, LocationType.BONUS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk", SC2WOL_LOC_ID_OFFSET + 2105, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Fusion Reactor", SC2WOL_LOC_ID_OFFSET + 2106, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200, LocationType.VICTORY), - LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201, LocationType.BONUS), - LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202, LocationType.BONUS), - LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203, LocationType.BONUS), - LocationData("Whispers of Doom", "Whispers of Doom: First Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2204, LocationType.MISSION_PROGRESS), - LocationData("Whispers of Doom", "Whispers of Doom: Second Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2205, LocationType.MISSION_PROGRESS), - LocationData("Whispers of Doom", "Whispers of Doom: Third Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2206, LocationType.MISSION_PROGRESS), - LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300, LocationType.VICTORY, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303, LocationType.BONUS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Northeast Base", SC2WOL_LOC_ID_OFFSET + 2304, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Southwest Base", SC2WOL_LOC_ID_OFFSET + 2305, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Maar", SC2WOL_LOC_ID_OFFSET + 2306, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Northwest Preserver", SC2WOL_LOC_ID_OFFSET + 2307, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Southwest Preserver", SC2WOL_LOC_ID_OFFSET + 2308, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: East Preserver", SC2WOL_LOC_ID_OFFSET + 2309, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400, LocationType.VICTORY, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401, LocationType.BONUS), - LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Base", SC2WOL_LOC_ID_OFFSET + 2403, LocationType.MISSION_PROGRESS), - LocationData("Echoes of the Future", "Echoes of the Future: Southwest Tendril", SC2WOL_LOC_ID_OFFSET + 2404, LocationType.MISSION_PROGRESS), - LocationData("Echoes of the Future", "Echoes of the Future: Southeast Tendril", SC2WOL_LOC_ID_OFFSET + 2405, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Northeast Tendril", SC2WOL_LOC_ID_OFFSET + 2406, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Northwest Tendril", SC2WOL_LOC_ID_OFFSET + 2407, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500, LocationType.VICTORY), - LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501, LocationType.BONUS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Urun", SC2WOL_LOC_ID_OFFSET + 2503, LocationType.MISSION_PROGRESS), - LocationData("In Utter Darkness", "In Utter Darkness: Mohandar", SC2WOL_LOC_ID_OFFSET + 2504, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Selendis", SC2WOL_LOC_ID_OFFSET + 2505, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Artanis", SC2WOL_LOC_ID_OFFSET + 2506, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 2 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2602, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 4 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2603, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 6 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2604, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 8 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2605, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Belly of the Beast", "Belly of the Beast: Victory", SC2WOL_LOC_ID_OFFSET + 2700, LocationType.VICTORY), - LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701, LocationType.MISSION_PROGRESS), - LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702, LocationType.MISSION_PROGRESS), - LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703, LocationType.MISSION_PROGRESS), - LocationData("Belly of the Beast", "Belly of the Beast: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 2704, LocationType.BONUS), - LocationData("Belly of the Beast", "Belly of the Beast: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 2705, LocationType.BONUS), - LocationData("Belly of the Beast", "Belly of the Beast: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 2706, LocationType.BONUS), - LocationData("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: East Hatchery", SC2WOL_LOC_ID_OFFSET + 2806, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: North Hatchery", SC2WOL_LOC_ID_OFFSET + 2807, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Mid Hatchery", SC2WOL_LOC_ID_OFFSET + 2808, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("All-In", "All-In: Victory", None, LocationType.VICTORY, - lambda state: state._sc2wol_final_mission_requirements(multiworld, player)) - ] - - beat_events = [] - - for i, location_data in enumerate(location_table): - # Removing all item-based logic on No Logic - if logic_level == 2: - location_data = location_data._replace(rule=Location.access_rule) - location_table[i] = location_data - # Generating Beat event locations - if location_data.name.endswith((": Victory", ": Defeat")): - beat_events.append( - location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None) - ) - return tuple(location_table + beat_events) diff --git a/worlds/sc2wol/LogicMixin.py b/worlds/sc2wol/LogicMixin.py deleted file mode 100644 index 112302beb207..000000000000 --- a/worlds/sc2wol/LogicMixin.py +++ /dev/null @@ -1,148 +0,0 @@ -from BaseClasses import MultiWorld -from worlds.AutoWorld import LogicMixin -from .Options import get_option_value -from .Items import get_basic_units, defense_ratings, zerg_defense_ratings - - -class SC2WoLLogic(LogicMixin): - def _sc2wol_has_common_unit(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any(get_basic_units(multiworld, player), player) - - def _sc2wol_has_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Viking', 'Wraith', 'Banshee', 'Battlecruiser'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 \ - and self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(multiworld, player) - - def _sc2wol_has_air_anti_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has('Viking', player) \ - or self.has_all({'Wraith', 'Advanced Laser Technology (Wraith)'}, player) \ - or self.has_all({'Battlecruiser', 'ATX Laser Battery (Battlecruiser)'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Wraith', 'Valkyrie', 'Battlecruiser'}, player) - - def _sc2wol_has_competent_ground_to_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has('Goliath', player) \ - or self.has('Marine', player) and self.has_any({'Medic', 'Medivac'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Cyclone', player) - - def _sc2wol_has_competent_anti_air(self, multiworld: MultiWorld, player: int) -> bool: - return self._sc2wol_has_competent_ground_to_air(multiworld, player) \ - or self._sc2wol_has_air_anti_air(multiworld, player) - - def _sc2wol_welcome_to_the_jungle_requirement(self, multiworld: MultiWorld, player: int) -> bool: - return ( - self._sc2wol_has_common_unit(multiworld, player) - and self._sc2wol_has_competent_ground_to_air(multiworld, player) - ) or ( - get_option_value(multiworld, player, 'required_tactics') > 0 - and self.has_any({'Marine', 'Vulture'}, player) - and self._sc2wol_has_air_anti_air(multiworld, player) - ) - - def _sc2wol_has_anti_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser', 'Marine', 'Wraith', 'Valkyrie', 'Cyclone'}, player) \ - or self._sc2wol_has_competent_anti_air(multiworld, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre', 'Widow Mine', 'Liberator'}, player) - - def _sc2wol_defense_rating(self, multiworld: MultiWorld, player: int, zerg_enemy: bool, air_enemy: bool = True) -> bool: - defense_score = sum((defense_ratings[item] for item in defense_ratings if self.has(item, player))) - if self.has_any({'Marine', 'Marauder'}, player) and self.has('Bunker', player): - defense_score += 3 - if self.has_all({'Siege Tank', 'Maelstrom Rounds (Siege Tank)'}, player): - defense_score += 2 - if self.has_all({'Siege Tank', 'Graduating Range (Siege Tank)'}, player): - defense_score += 1 - if self.has_all({'Widow Mine', 'Concealment (Widow Mine)'}, player): - defense_score += 1 - if zerg_enemy: - defense_score += sum((zerg_defense_ratings[item] for item in zerg_defense_ratings if self.has(item, player))) - if self.has('Firebat', player) and self.has('Bunker', player): - defense_score += 2 - if not air_enemy and self.has('Missile Turret', player): - defense_score -= defense_ratings['Missile Turret'] - # Advanced Tactics bumps defense rating requirements down by 2 - if get_option_value(multiworld, player, 'required_tactics') > 0: - defense_score += 2 - return defense_score - - def _sc2wol_has_competent_comp(self, multiworld: MultiWorld, player: int) -> bool: - return \ - ( - ( - self.has_any({'Marine', 'Marauder'}, player) and self.has_any({'Medivac', 'Medic'}, player) - or self.has_any({'Thor', 'Banshee', 'Siege Tank'}, player) - or self.has_all({'Liberator', 'Raid Artillery (Liberator)'}, player) - ) and self._sc2wol_has_competent_anti_air(multiworld, player) - ) \ - or \ - ( - self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(multiworld, player) - ) - - def _sc2wol_has_train_killers(self, multiworld: MultiWorld, player: int) -> bool: - return ( - self.has_any({'Siege Tank', 'Diamondback', 'Marauder', 'Cyclone'}, player) - or get_option_value(multiworld, player, 'required_tactics') > 0 - and ( - self.has_all({'Reaper', "G-4 Clusterbomb"}, player) - or self.has_all({'Spectre', 'Psionic Lash'}, player) - or self.has_any({'Vulture', 'Liberator'}, player) - ) - ) - - def _sc2wol_able_to_rescue(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 - - def _sc2wol_has_protoss_common_units(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Zealot', 'Immortal', 'Stalker', 'Dark Templar'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('High Templar', player) - - def _sc2wol_has_protoss_medium_units(self, multiworld: MultiWorld, player: int) -> bool: - return self._sc2wol_has_protoss_common_units(multiworld, player) and \ - self.has_any({'Stalker', 'Void Ray', 'Carrier'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Dark Templar', player) - - def _sc2wol_beats_protoss_deathball(self, multiworld: MultiWorld, player: int) -> bool: - return (self.has_any({'Banshee', 'Battlecruiser'}, player) or - self.has_all({'Liberator', 'Raid Artillery (Liberator)'}, player)) \ - and self._sc2wol_has_competent_anti_air(multiworld, player) or \ - self._sc2wol_has_competent_comp(multiworld, player) and self._sc2wol_has_air_anti_air(multiworld, player) - - def _sc2wol_has_mm_upgrade(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({"Combat Shield (Marine)", "Stabilizer Medpacks (Medic)"}, player) - - def _sc2wol_survives_rip_field(self, multiworld: MultiWorld, player: int) -> bool: - return self.has("Battlecruiser", player) or \ - self._sc2wol_has_air(multiworld, player) and \ - self._sc2wol_has_competent_anti_air(multiworld, player) and \ - self.has("Science Vessel", player) - - def _sc2wol_has_nukes(self, multiworld: MultiWorld, player: int) -> bool: - return get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player) - - def _sc2wol_can_respond_to_colony_infestations(self, multiworld: MultiWorld, player: int) -> bool: - return self._sc2wol_has_common_unit(multiworld, player) \ - and self._sc2wol_has_competent_anti_air(multiworld, player) \ - and \ - ( - self._sc2wol_has_air_anti_air(multiworld, player) or - self.has_any({'Battlecruiser', 'Valkyrie'}), player - ) \ - and \ - self._sc2wol_defense_rating(multiworld, player, True) >= 3 - - def _sc2wol_final_mission_requirements(self, multiworld: MultiWorld, player: int): - beats_kerrigan = self.has_any({'Marine', 'Banshee', 'Ghost'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 - if get_option_value(multiworld, player, 'all_in_map') == 0: - # Ground - defense_rating = self._sc2wol_defense_rating(multiworld, player, True, False) - if self.has_any({'Battlecruiser', 'Banshee'}, player): - defense_rating += 3 - return defense_rating >= 12 and beats_kerrigan - else: - # Air - defense_rating = self._sc2wol_defense_rating(multiworld, player, True, True) - return defense_rating >= 8 and beats_kerrigan \ - and self.has_any({'Viking', 'Battlecruiser', 'Valkyrie'}, player) \ - and self.has_any({'Hive Mind Emulator', 'Psi Disruptor', 'Missile Turret'}, player) - - def _sc2wol_cleared_missions(self, multiworld: MultiWorld, player: int, mission_count: int) -> bool: - return self.has_group("Missions", player, mission_count) diff --git a/worlds/sc2wol/MissionTables.py b/worlds/sc2wol/MissionTables.py deleted file mode 100644 index 298cd7a978a6..000000000000 --- a/worlds/sc2wol/MissionTables.py +++ /dev/null @@ -1,230 +0,0 @@ -from typing import NamedTuple, Dict, List -from enum import IntEnum - -no_build_regions_list = ["Liberation Day", "Breakout", "Ghost of a Chance", "Piercing the Shroud", "Whispers of Doom", - "Belly of the Beast"] -easy_regions_list = ["The Outlaws", "Zero Hour", "Evacuation", "Outbreak", "Smash and Grab", "Devil's Playground"] -medium_regions_list = ["Safe Haven", "Haven's Fall", "The Dig", "The Moebius Factor", "Supernova", - "Welcome to the Jungle", "The Great Train Robbery", "Cutthroat", "Media Blitz", - "A Sinister Turn", "Echoes of the Future"] -hard_regions_list = ["Maw of the Void", "Engine of Destruction", "In Utter Darkness", "Gates of Hell", - "Shatter the Sky"] - - -class MissionPools(IntEnum): - STARTER = 0 - EASY = 1 - MEDIUM = 2 - HARD = 3 - FINAL = 4 - - -class MissionInfo(NamedTuple): - id: int - required_world: List[int] - category: str - number: int = 0 # number of worlds need beaten - completion_critical: bool = False # missions needed to beat game - or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed - - -class FillMission(NamedTuple): - type: int - connect_to: List[int] # -1 connects to Menu - category: str - number: int = 0 # number of worlds need beaten - completion_critical: bool = False # missions needed to beat game - or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed - removal_priority: int = 0 # how many missions missing from the pool required to remove this mission - - -vanilla_shuffle_order = [ - FillMission(MissionPools.STARTER, [-1], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [0], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [1], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [2], "Colonist"), - FillMission(MissionPools.MEDIUM, [3], "Colonist"), - FillMission(MissionPools.HARD, [4], "Colonist", number=7), - FillMission(MissionPools.HARD, [4], "Colonist", number=7, removal_priority=1), - FillMission(MissionPools.EASY, [2], "Artifact", completion_critical=True), - FillMission(MissionPools.MEDIUM, [7], "Artifact", number=8, completion_critical=True), - FillMission(MissionPools.HARD, [8], "Artifact", number=11, completion_critical=True), - FillMission(MissionPools.HARD, [9], "Artifact", number=14, completion_critical=True, removal_priority=11), - FillMission(MissionPools.HARD, [10], "Artifact", completion_critical=True, removal_priority=10), - FillMission(MissionPools.MEDIUM, [2], "Covert", number=4), - FillMission(MissionPools.MEDIUM, [12], "Covert"), - FillMission(MissionPools.HARD, [13], "Covert", number=8, removal_priority=3), - FillMission(MissionPools.HARD, [13], "Covert", number=8, removal_priority=2), - FillMission(MissionPools.MEDIUM, [2], "Rebellion", number=6), - FillMission(MissionPools.HARD, [16], "Rebellion"), - FillMission(MissionPools.HARD, [17], "Rebellion"), - FillMission(MissionPools.HARD, [18], "Rebellion", removal_priority=12), - FillMission(MissionPools.HARD, [19], "Rebellion", removal_priority=5), - FillMission(MissionPools.MEDIUM, [8], "Prophecy", removal_priority=9), - FillMission(MissionPools.HARD, [21], "Prophecy", removal_priority=8), - FillMission(MissionPools.HARD, [22], "Prophecy", removal_priority=7), - FillMission(MissionPools.HARD, [23], "Prophecy", removal_priority=6), - FillMission(MissionPools.HARD, [11], "Char", completion_critical=True), - FillMission(MissionPools.HARD, [25], "Char", completion_critical=True, removal_priority=4), - FillMission(MissionPools.HARD, [25], "Char", completion_critical=True), - FillMission(MissionPools.FINAL, [26, 27], "Char", completion_critical=True, or_requirements=True) -] - -mini_campaign_order = [ - FillMission(MissionPools.STARTER, [-1], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [0], "Colonist"), - FillMission(MissionPools.MEDIUM, [1], "Colonist"), - FillMission(MissionPools.EASY, [0], "Artifact", completion_critical=True), - FillMission(MissionPools.MEDIUM, [3], "Artifact", number=4, completion_critical=True), - FillMission(MissionPools.HARD, [4], "Artifact", number=8, completion_critical=True), - FillMission(MissionPools.MEDIUM, [0], "Covert", number=2), - FillMission(MissionPools.HARD, [6], "Covert"), - FillMission(MissionPools.MEDIUM, [0], "Rebellion", number=3), - FillMission(MissionPools.HARD, [8], "Rebellion"), - FillMission(MissionPools.MEDIUM, [4], "Prophecy"), - FillMission(MissionPools.HARD, [10], "Prophecy"), - FillMission(MissionPools.HARD, [5], "Char", completion_critical=True), - FillMission(MissionPools.HARD, [5], "Char", completion_critical=True), - FillMission(MissionPools.FINAL, [12, 13], "Char", completion_critical=True, or_requirements=True) -] - -gauntlet_order = [ - FillMission(MissionPools.STARTER, [-1], "I", completion_critical=True), - FillMission(MissionPools.EASY, [0], "II", completion_critical=True), - FillMission(MissionPools.EASY, [1], "III", completion_critical=True), - FillMission(MissionPools.MEDIUM, [2], "IV", completion_critical=True), - FillMission(MissionPools.MEDIUM, [3], "V", completion_critical=True), - FillMission(MissionPools.HARD, [4], "VI", completion_critical=True), - FillMission(MissionPools.FINAL, [5], "Final", completion_critical=True) -] - -mini_gauntlet_order = [ - FillMission(MissionPools.STARTER, [-1], "I", completion_critical=True), - FillMission(MissionPools.EASY, [0], "II", completion_critical=True), - FillMission(MissionPools.MEDIUM, [1], "III", completion_critical=True), - FillMission(MissionPools.FINAL, [2], "Final", completion_critical=True) -] - -grid_order = [ - FillMission(MissionPools.STARTER, [-1], "_1"), - FillMission(MissionPools.EASY, [0], "_1"), - FillMission(MissionPools.MEDIUM, [1, 6, 3], "_1", or_requirements=True), - FillMission(MissionPools.HARD, [2, 7], "_1", or_requirements=True), - FillMission(MissionPools.EASY, [0], "_2"), - FillMission(MissionPools.MEDIUM, [1, 4], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [2, 5, 10, 7], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [3, 6, 11], "_2", or_requirements=True), - FillMission(MissionPools.MEDIUM, [4, 9, 12], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [5, 8, 10, 13], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [6, 9, 11, 14], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [7, 10], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [8, 13], "_4", or_requirements=True), - FillMission(MissionPools.HARD, [9, 12, 14], "_4", or_requirements=True), - FillMission(MissionPools.HARD, [10, 13], "_4", or_requirements=True), - FillMission(MissionPools.FINAL, [11, 14], "_4", or_requirements=True) -] - -mini_grid_order = [ - FillMission(MissionPools.STARTER, [-1], "_1"), - FillMission(MissionPools.EASY, [0], "_1"), - FillMission(MissionPools.MEDIUM, [1, 5], "_1", or_requirements=True), - FillMission(MissionPools.EASY, [0], "_2"), - FillMission(MissionPools.MEDIUM, [1, 3], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [2, 4], "_2", or_requirements=True), - FillMission(MissionPools.MEDIUM, [3, 7], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [4, 6], "_3", or_requirements=True), - FillMission(MissionPools.FINAL, [5, 7], "_3", or_requirements=True) -] - -tiny_grid_order = [ - FillMission(MissionPools.STARTER, [-1], "_1"), - FillMission(MissionPools.MEDIUM, [0], "_1"), - FillMission(MissionPools.EASY, [0], "_2"), - FillMission(MissionPools.FINAL, [1, 2], "_2", or_requirements=True), -] - -blitz_order = [ - FillMission(MissionPools.STARTER, [-1], "I"), - FillMission(MissionPools.EASY, [-1], "I"), - FillMission(MissionPools.MEDIUM, [0, 1], "II", number=1, or_requirements=True), - FillMission(MissionPools.MEDIUM, [0, 1], "II", number=1, or_requirements=True), - FillMission(MissionPools.MEDIUM, [0, 1], "III", number=2, or_requirements=True), - FillMission(MissionPools.MEDIUM, [0, 1], "III", number=2, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "IV", number=3, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "IV", number=3, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "V", number=4, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "V", number=4, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "Final", number=5, or_requirements=True), - FillMission(MissionPools.FINAL, [0, 1], "Final", number=5, or_requirements=True) -] - -mission_orders = [ - vanilla_shuffle_order, - vanilla_shuffle_order, - mini_campaign_order, - grid_order, - mini_grid_order, - blitz_order, - gauntlet_order, - mini_gauntlet_order, - tiny_grid_order -] - - -vanilla_mission_req_table = { - "Liberation Day": MissionInfo(1, [], "Mar Sara", completion_critical=True), - "The Outlaws": MissionInfo(2, [1], "Mar Sara", completion_critical=True), - "Zero Hour": MissionInfo(3, [2], "Mar Sara", completion_critical=True), - "Evacuation": MissionInfo(4, [3], "Colonist"), - "Outbreak": MissionInfo(5, [4], "Colonist"), - "Safe Haven": MissionInfo(6, [5], "Colonist", number=7), - "Haven's Fall": MissionInfo(7, [5], "Colonist", number=7), - "Smash and Grab": MissionInfo(8, [3], "Artifact", completion_critical=True), - "The Dig": MissionInfo(9, [8], "Artifact", number=8, completion_critical=True), - "The Moebius Factor": MissionInfo(10, [9], "Artifact", number=11, completion_critical=True), - "Supernova": MissionInfo(11, [10], "Artifact", number=14, completion_critical=True), - "Maw of the Void": MissionInfo(12, [11], "Artifact", completion_critical=True), - "Devil's Playground": MissionInfo(13, [3], "Covert", number=4), - "Welcome to the Jungle": MissionInfo(14, [13], "Covert"), - "Breakout": MissionInfo(15, [14], "Covert", number=8), - "Ghost of a Chance": MissionInfo(16, [14], "Covert", number=8), - "The Great Train Robbery": MissionInfo(17, [3], "Rebellion", number=6), - "Cutthroat": MissionInfo(18, [17], "Rebellion"), - "Engine of Destruction": MissionInfo(19, [18], "Rebellion"), - "Media Blitz": MissionInfo(20, [19], "Rebellion"), - "Piercing the Shroud": MissionInfo(21, [20], "Rebellion"), - "Whispers of Doom": MissionInfo(22, [9], "Prophecy"), - "A Sinister Turn": MissionInfo(23, [22], "Prophecy"), - "Echoes of the Future": MissionInfo(24, [23], "Prophecy"), - "In Utter Darkness": MissionInfo(25, [24], "Prophecy"), - "Gates of Hell": MissionInfo(26, [12], "Char", completion_critical=True), - "Belly of the Beast": MissionInfo(27, [26], "Char", completion_critical=True), - "Shatter the Sky": MissionInfo(28, [26], "Char", completion_critical=True), - "All-In": MissionInfo(29, [27, 28], "Char", completion_critical=True, or_requirements=True) -} - -lookup_id_to_mission: Dict[int, str] = { - data.id: mission_name for mission_name, data in vanilla_mission_req_table.items() if data.id} - -starting_mission_locations = { - "Liberation Day": "Liberation Day: Victory", - "Breakout": "Breakout: Victory", - "Ghost of a Chance": "Ghost of a Chance: Victory", - "Piercing the Shroud": "Piercing the Shroud: Victory", - "Whispers of Doom": "Whispers of Doom: Victory", - "Belly of the Beast": "Belly of the Beast: Victory", - "Zero Hour": "Zero Hour: First Group Rescued", - "Evacuation": "Evacuation: Reach Hanson", - "Devil's Playground": "Devil's Playground: Tosh's Miners", - "Smash and Grab": "Smash and Grab: First Relic", - "The Great Train Robbery": "The Great Train Robbery: North Defiler" -} - - -alt_final_mission_locations = { - "Maw of the Void": "Maw of the Void: Victory", - "Engine of Destruction": "Engine of Destruction: Victory", - "Supernova": "Supernova: Victory", - "Gates of Hell": "Gates of Hell: Victory", - "Shatter the Sky": "Shatter the Sky: Victory" -} \ No newline at end of file diff --git a/worlds/sc2wol/Options.py b/worlds/sc2wol/Options.py deleted file mode 100644 index e4b6a740669a..000000000000 --- a/worlds/sc2wol/Options.py +++ /dev/null @@ -1,362 +0,0 @@ -from typing import Dict, FrozenSet, Union -from BaseClasses import MultiWorld -from Options import Choice, Option, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range -from .MissionTables import vanilla_mission_req_table - -ORDER_VANILLA = 0 -ORDER_VANILLA_SHUFFLED = 1 - -class GameDifficulty(Choice): - """ - The difficulty of the campaign, affects enemy AI, starting units, and game speed. - - For those unfamiliar with the Archipelago randomizer, the recommended settings are one difficulty level - lower than the vanilla game - """ - display_name = "Game Difficulty" - option_casual = 0 - option_normal = 1 - option_hard = 2 - option_brutal = 3 - default = 1 - -class GameSpeed(Choice): - """Optional setting to override difficulty-based game speed.""" - display_name = "Game Speed" - option_default = 0 - option_slower = 1 - option_slow = 2 - option_normal = 3 - option_fast = 4 - option_faster = 5 - default = option_default - -class FinalMap(Choice): - """ - Determines if the final map and goal of the campaign. - All in: You need to beat All-in map - Random Hard: A random hard mission is selected as a goal. - Beat this mission in order to complete the game. - All-in map won't be in the campaign - - Vanilla mission order always ends with All in mission! - - Warning: Using All-in with a short mission order (7 or fewer missions) is not recommended, - as there might not be enough locations to place all the required items, - any excess required items will be placed into the player's starting inventory! - - This option is short-lived. It may be changed in the future - """ - display_name = "Final Map" - option_all_in = 0 - option_random_hard = 1 - -class AllInMap(Choice): - """Determines what version of All-In (final map) that will be generated for the campaign.""" - display_name = "All In Map" - option_ground = 0 - option_air = 1 - - -class MissionOrder(Choice): - """ - Determines the order the missions are played in. The last three mission orders end in a random mission. - Vanilla (29): Keeps the standard mission order and branching from the WoL Campaign. - Vanilla Shuffled (29): Keeps same branching paths from the WoL Campaign but randomizes the order of missions within. - Mini Campaign (15): Shorter version of the campaign with randomized missions and optional branches. - Grid (16): A 4x4 grid of random missions. Start at the top-left and forge a path towards bottom-right mission to win. - Mini Grid (9): A 3x3 version of Grid. Complete the bottom-right mission to win. - Blitz (12): 12 random missions that open up very quickly. Complete the bottom-right mission to win. - Gauntlet (7): Linear series of 7 random missions to complete the campaign. - Mini Gauntlet (4): Linear series of 4 random missions to complete the campaign. - Tiny Grid (4): A 2x2 version of Grid. Complete the bottom-right mission to win. - """ - display_name = "Mission Order" - option_vanilla = 0 - option_vanilla_shuffled = 1 - option_mini_campaign = 2 - option_grid = 3 - option_mini_grid = 4 - option_blitz = 5 - option_gauntlet = 6 - option_mini_gauntlet = 7 - option_tiny_grid = 8 - - -class PlayerColor(Choice): - """Determines in-game team color.""" - display_name = "Player Color" - option_white = 0 - option_red = 1 - option_blue = 2 - option_teal = 3 - option_purple = 4 - option_yellow = 5 - option_orange = 6 - option_green = 7 - option_light_pink = 8 - option_violet = 9 - option_light_grey = 10 - option_dark_green = 11 - option_brown = 12 - option_light_green = 13 - option_dark_grey = 14 - option_pink = 15 - option_rainbow = 16 - option_default = 17 - default = option_default - - -class ShuffleProtoss(DefaultOnToggle): - """Determines if the 3 protoss missions are included in the shuffle if Vanilla mission order is not enabled. - If turned off, the 3 protoss missions will not appear and Protoss units are removed from the pool.""" - display_name = "Shuffle Protoss Missions" - - -class ShuffleNoBuild(DefaultOnToggle): - """Determines if the 5 no-build missions are included in the shuffle if Vanilla mission order is not enabled. - If turned off, the 5 no-build missions will not appear.""" - display_name = "Shuffle No-Build Missions" - - -class EarlyUnit(DefaultOnToggle): - """ - Guarantees that the first mission will contain a unit. - - Each mission available to be the first mission has a pre-defined location where the unit should spawn. - This location gets overriden over any exclusion. It's guaranteed to be reachable with an empty inventory. - """ - display_name = "Early Unit" - - -class RequiredTactics(Choice): - """Determines the maximum tactical difficulty of the seed (separate from mission difficulty). Higher settings - increase randomness. - - Standard: All missions can be completed with good micro and macro. - Advanced: Completing missions may require relying on starting units and micro-heavy units. - No Logic: Units and upgrades may be placed anywhere. LIKELY TO RENDER THE RUN IMPOSSIBLE ON HARDER DIFFICULTIES!""" - display_name = "Required Tactics" - option_standard = 0 - option_advanced = 1 - option_no_logic = 2 - - -class UnitsAlwaysHaveUpgrades(DefaultOnToggle): - """ - If turned on, all upgrades will be present for each unit and structure in the seed. - This usually results in fewer units. - - See also: Max Number of Upgrades - """ - display_name = "Units Always Have Upgrades" - - -class GenericUpgradeMissions(Range): - """Determines the percentage of missions in the mission order that must be completed before - level 1 of all weapon and armor upgrades is unlocked. Level 2 upgrades require double the amount of missions, - and level 3 requires triple the amount. The required amounts are always rounded down. - If set to 0, upgrades are instead added to the item pool and must be found to be used.""" - display_name = "Generic Upgrade Missions" - range_start = 0 - range_end = 100 - default = 0 - - -class GenericUpgradeResearch(Choice): - """Determines how weapon and armor upgrades affect missions once unlocked. - - Vanilla: Upgrades must be researched as normal. - Auto In No-Build: In No-Build missions, upgrades are automatically researched. - In all other missions, upgrades must be researched as normal. - Auto In Build: In No-Build missions, upgrades are unavailable as normal. - In all other missions, upgrades are automatically researched. - Always Auto: Upgrades are automatically researched in all missions.""" - display_name = "Generic Upgrade Research" - option_vanilla = 0 - option_auto_in_no_build = 1 - option_auto_in_build = 2 - option_always_auto = 3 - - -class GenericUpgradeItems(Choice): - """Determines how weapon and armor upgrades are split into items. All options produce 3 levels of each item. - Does nothing if upgrades are unlocked by completed mission counts. - - Individual Items: All weapon and armor upgrades are each an item, - resulting in 18 total upgrade items. - Bundle Weapon And Armor: All types of weapon upgrades are one item, - and all types of armor upgrades are one item, - resulting in 6 total items. - Bundle Unit Class: Weapon and armor upgrades are merged, - but Infantry, Vehicle, and Starship upgrades are bundled separately, - resulting in 9 total items. - Bundle All: All weapon and armor upgrades are one item, - resulting in 3 total items.""" - display_name = "Generic Upgrade Items" - option_individual_items = 0 - option_bundle_weapon_and_armor = 1 - option_bundle_unit_class = 2 - option_bundle_all = 3 - - -class NovaCovertOpsItems(Toggle): - """If turned on, the equipment upgrades from Nova Covert Ops may be present in the world.""" - display_name = "Nova Covert Ops Items" - default = Toggle.option_true - - -class BroodWarItems(Toggle): - """If turned on, returning items from StarCraft: Brood War may appear in the world.""" - display_name = "Brood War Items" - default = Toggle.option_true - - -class ExtendedItems(Toggle): - """If turned on, original items that did not appear in Campaign mode may appear in the world.""" - display_name = "Extended Items" - default = Toggle.option_true - - -class MaxNumberOfUpgrades(Range): - """ - Set a maximum to the number of upgrades a unit/structure can have. -1 is used to define unlimited. - Note that most unit have 4 or 6 upgrades. - - If used with Units Always Have Upgrades, each unit has this given amount of upgrades (if there enough upgrades exist) - - See also: Units Always Have Upgrades - """ - display_name = "Maximum number of upgrades per unit/structure" - range_start = -1 - # Do not know the maximum, but it is less than 123! - range_end = 123 - default = -1 - - -class LockedItems(ItemSet): - """Guarantees that these items will be unlockable""" - display_name = "Locked Items" - - -class ExcludedItems(ItemSet): - """Guarantees that these items will not be unlockable""" - display_name = "Excluded Items" - - -class ExcludedMissions(OptionSet): - """Guarantees that these missions will not appear in the campaign - Doesn't apply to vanilla mission order. - It may be impossible to build a valid campaign if too many missions are excluded.""" - display_name = "Excluded Missions" - valid_keys = {mission_name for mission_name in vanilla_mission_req_table.keys() if mission_name != 'All-In'} - - -class LocationInclusion(Choice): - option_enabled = 0 - option_trash = 1 - option_nothing = 2 - - -class MissionProgressLocations(LocationInclusion): - """ - Enables or disables item rewards for progressing (not finishing) a mission. - Progressing a mission is usually a task of completing or progressing into a main objective. - Clearing an expansion base also counts here. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Mission Progress Locations" - - -class BonusLocations(LocationInclusion): - """ - Enables or disables item rewards for completing bonus tasks. - Bonus tasks are those giving you a campaign-wide or mission-wide bonus in vanilla game: - Research, credits, bonus units or resources, etc. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Bonus Locations" - - -class ChallengeLocations(LocationInclusion): - """ - Enables or disables item rewards for completing challenge tasks. - Challenges are tasks that have usually higher requirements to be completed - than to complete the mission they're in successfully. - You might be required to visit the same mission later when getting stronger in order to finish these tasks. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Challenge Locations" - - -class OptionalBossLocations(LocationInclusion): - """ - Enables or disables item rewards for defeating optional bosses. - An optional boss is any boss that's not required to kill in order to finish the mission successfully. - All Brutalisks, Loki, etc. belongs here. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Optional Boss Locations" - - -# noinspection PyTypeChecker -sc2wol_options: Dict[str, Option] = { - "game_difficulty": GameDifficulty, - "game_speed": GameSpeed, - "all_in_map": AllInMap, - "final_map": FinalMap, - "mission_order": MissionOrder, - "player_color": PlayerColor, - "shuffle_protoss": ShuffleProtoss, - "shuffle_no_build": ShuffleNoBuild, - "early_unit": EarlyUnit, - "required_tactics": RequiredTactics, - "units_always_have_upgrades": UnitsAlwaysHaveUpgrades, - "max_number_of_upgrades": MaxNumberOfUpgrades, - "generic_upgrade_missions": GenericUpgradeMissions, - "generic_upgrade_research": GenericUpgradeResearch, - "generic_upgrade_items": GenericUpgradeItems, - "locked_items": LockedItems, - "excluded_items": ExcludedItems, - "excluded_missions": ExcludedMissions, - "nco_items": NovaCovertOpsItems, - "bw_items": BroodWarItems, - "ext_items": ExtendedItems, - "mission_progress_locations": MissionProgressLocations, - "bonus_locations": BonusLocations, - "challenge_locations": ChallengeLocations, - "optional_boss_locations": OptionalBossLocations -} - - -def get_option_value(multiworld: MultiWorld, player: int, name: str) -> Union[int, FrozenSet]: - if multiworld is None: - return sc2wol_options[name].default - - player_option = getattr(multiworld, name)[player] - - return player_option.value diff --git a/worlds/sc2wol/PoolFilter.py b/worlds/sc2wol/PoolFilter.py deleted file mode 100644 index 23422a3d1ea5..000000000000 --- a/worlds/sc2wol/PoolFilter.py +++ /dev/null @@ -1,367 +0,0 @@ -from typing import Callable, Dict, List, Set -from BaseClasses import MultiWorld, ItemClassification, Item, Location -from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, filler_items, \ - progressive_if_nco -from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list,\ - mission_orders, MissionInfo, alt_final_mission_locations, MissionPools -from .Options import get_option_value, MissionOrder, FinalMap, MissionProgressLocations, LocationInclusion -from .LogicMixin import SC2WoLLogic - -# Items with associated upgrades -UPGRADABLE_ITEMS = [ - "Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre", - "Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator", "Widow Mine", "Cyclone", - "Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "Raven", "Science Vessel", "Liberator", "Valkyrie", - "Bunker", "Missile Turret" -] - -BARRACKS_UNITS = {"Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre"} -FACTORY_UNITS = {"Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator", "Widow Mine", "Cyclone"} -STARPORT_UNITS = {"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "Hercules", "Science Vessel", "Raven", "Liberator", "Valkyrie"} - -PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"} - - -def filter_missions(multiworld: MultiWorld, player: int) -> Dict[int, List[str]]: - """ - Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets - """ - - mission_order_type = get_option_value(multiworld, player, "mission_order") - shuffle_no_build = get_option_value(multiworld, player, "shuffle_no_build") - shuffle_protoss = get_option_value(multiworld, player, "shuffle_protoss") - excluded_missions = get_option_value(multiworld, player, "excluded_missions") - final_map = get_option_value(multiworld, player, "final_map") - mission_pools = { - MissionPools.STARTER: no_build_regions_list[:], - MissionPools.EASY: easy_regions_list[:], - MissionPools.MEDIUM: medium_regions_list[:], - MissionPools.HARD: hard_regions_list[:], - MissionPools.FINAL: [] - } - if mission_order_type == MissionOrder.option_vanilla: - # Vanilla uses the entire mission pool - mission_pools[MissionPools.FINAL] = ['All-In'] - return mission_pools - # Omitting No-Build missions if not shuffling no-build - if not shuffle_no_build: - excluded_missions = excluded_missions.union(no_build_regions_list) - # Omitting Protoss missions if not shuffling protoss - if not shuffle_protoss: - excluded_missions = excluded_missions.union(PROTOSS_REGIONS) - # Replacing All-In with alternate ending depending on option - if final_map == FinalMap.option_random_hard: - final_mission = multiworld.random.choice([mission for mission in alt_final_mission_locations.keys() if mission not in excluded_missions]) - excluded_missions.add(final_mission) - else: - final_mission = 'All-In' - # Excluding missions - for difficulty, mission_pool in mission_pools.items(): - mission_pools[difficulty] = [mission for mission in mission_pool if mission not in excluded_missions] - mission_pools[MissionPools.FINAL].append(final_mission) - # Mission pool changes on Build-Only - if not get_option_value(multiworld, player, 'shuffle_no_build'): - def move_mission(mission_name, current_pool, new_pool): - if mission_name in mission_pools[current_pool]: - mission_pools[current_pool].remove(mission_name) - mission_pools[new_pool].append(mission_name) - # Replacing No Build missions with Easy missions - move_mission("Zero Hour", MissionPools.EASY, MissionPools.STARTER) - move_mission("Evacuation", MissionPools.EASY, MissionPools.STARTER) - move_mission("Devil's Playground", MissionPools.EASY, MissionPools.STARTER) - # Pushing Outbreak to Normal, as it cannot be placed as the second mission on Build-Only - move_mission("Outbreak", MissionPools.EASY, MissionPools.MEDIUM) - # Pushing extra Normal missions to Easy - move_mission("The Great Train Robbery", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Echoes of the Future", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Cutthroat", MissionPools.MEDIUM, MissionPools.EASY) - # Additional changes on Advanced Tactics - if get_option_value(multiworld, player, "required_tactics") > 0: - move_mission("The Great Train Robbery", MissionPools.EASY, MissionPools.STARTER) - move_mission("Smash and Grab", MissionPools.EASY, MissionPools.STARTER) - move_mission("Moebius Factor", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Welcome to the Jungle", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Engine of Destruction", MissionPools.HARD, MissionPools.MEDIUM) - - return mission_pools - - -def get_item_upgrades(inventory: List[Item], parent_item: Item or str): - item_name = parent_item.name if isinstance(parent_item, Item) else parent_item - return [ - inv_item for inv_item in inventory - if get_full_item_list()[inv_item.name].parent_item == item_name - ] - - -def get_item_quantity(item: Item, multiworld: MultiWorld, player: int): - if (not get_option_value(multiworld, player, "nco_items")) \ - and item.name in progressive_if_nco: - return 1 - return get_full_item_list()[item.name].quantity - - -def copy_item(item: Item): - return Item(item.name, item.classification, item.code, item.player) - - -class ValidInventory: - - def has(self, item: str, player: int): - return item in self.logical_inventory - - def has_any(self, items: Set[str], player: int): - return any(item in self.logical_inventory for item in items) - - def has_all(self, items: Set[str], player: int): - return all(item in self.logical_inventory for item in items) - - def has_units_per_structure(self) -> bool: - return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ - len(FACTORY_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ - len(STARPORT_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure - - def generate_reduced_inventory(self, inventory_size: int, mission_requirements: List[Callable]) -> List[Item]: - """Attempts to generate a reduced inventory that can fulfill the mission requirements.""" - inventory = list(self.item_pool) - locked_items = list(self.locked_items) - self.logical_inventory = { - item.name for item in inventory + locked_items + self.existing_items - if item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing) - } - requirements = mission_requirements - cascade_keys = self.cascade_removal_map.keys() - units_always_have_upgrades = get_option_value(self.multiworld, self.player, "units_always_have_upgrades") - - def attempt_removal(item: Item) -> bool: - # If item can be removed and has associated items, remove them as well - inventory.remove(item) - # Only run logic checks when removing logic items - if item.name in self.logical_inventory: - self.logical_inventory.remove(item.name) - if not all(requirement(self) for requirement in requirements): - # If item cannot be removed, lock or revert - self.logical_inventory.add(item.name) - for _ in range(get_item_quantity(item, self.multiworld, self.player)): - locked_items.append(copy_item(item)) - return False - return True - - # Limit the maximum number of upgrades - maxUpgrad = get_option_value(self.multiworld, self.player, - "max_number_of_upgrades") - if maxUpgrad != -1: - unit_avail_upgrades = {} - # Needed to take into account locked/existing items - unit_nb_upgrades = {} - for item in inventory: - cItem = get_full_item_list()[item.name] - if cItem.type in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: - unit_avail_upgrades[item.name] = [] - unit_nb_upgrades[item.name] = 0 - elif cItem.parent_item is not None: - if cItem.parent_item not in unit_avail_upgrades: - unit_avail_upgrades[cItem.parent_item] = [item] - unit_nb_upgrades[cItem.parent_item] = 1 - else: - unit_avail_upgrades[cItem.parent_item].append(item) - unit_nb_upgrades[cItem.parent_item] += 1 - # For those two categories, we count them but dont include them in removal - for item in locked_items + self.existing_items: - cItem = get_full_item_list()[item.name] - if cItem.type in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: - unit_avail_upgrades[item.name] = [] - unit_nb_upgrades[item.name] = 0 - elif cItem.parent_item is not None: - if cItem.parent_item not in unit_avail_upgrades: - unit_nb_upgrades[cItem.parent_item] = 1 - else: - unit_nb_upgrades[cItem.parent_item] += 1 - # Making sure that the upgrades being removed is random - # Currently, only for combat shield vs Stabilizer Medpacks... - shuffled_unit_upgrade_list = list(unit_avail_upgrades.keys()) - self.multiworld.random.shuffle(shuffled_unit_upgrade_list) - for unit in shuffled_unit_upgrade_list: - while (unit_nb_upgrades[unit] > maxUpgrad) \ - and (len(unit_avail_upgrades[unit]) > 0): - itemCandidate = self.multiworld.random.choice(unit_avail_upgrades[unit]) - _ = attempt_removal(itemCandidate) - # Whatever it succeed to remove the iventory or it fails and thus - # lock it, the upgrade is no longer available for removal - unit_avail_upgrades[unit].remove(itemCandidate) - unit_nb_upgrades[unit] -= 1 - - # Locking associated items for items that have already been placed when units_always_have_upgrades is on - if units_always_have_upgrades: - existing_items = set(self.existing_items[:] + locked_items) - while existing_items: - existing_item = existing_items.pop() - items_to_lock = self.cascade_removal_map.get(existing_item, [existing_item]) - if get_full_item_list()[existing_item.name].type != "Upgrade": - # Don't process general upgrades, they may have been pre-locked per-level - for item in items_to_lock: - if item in inventory: - item_quantity = inventory.count(item) - # Unit upgrades, lock all levels - for _ in range(item_quantity): - inventory.remove(item) - if item not in locked_items: - # Lock all the associated items if not already locked - for _ in range(item_quantity): - locked_items.append(copy_item(item)) - if item in existing_items: - existing_items.remove(item) - - if self.min_units_per_structure > 0 and self.has_units_per_structure(): - requirements.append(lambda state: state.has_units_per_structure()) - - # Determining if the full-size inventory can complete campaign - if not all(requirement(self) for requirement in requirements): - raise Exception("Too many items excluded - campaign is impossible to complete.") - - while len(inventory) + len(locked_items) > inventory_size: - if len(inventory) == 0: - # There are more items than locations and all of them are already locked due to YAML or logic. - # Random items from locked ones will go to starting items - self.multiworld.random.shuffle(locked_items) - while len(locked_items) > inventory_size: - item: Item = locked_items.pop() - self.multiworld.push_precollected(item) - break - # Select random item from removable items - item = self.multiworld.random.choice(inventory) - # Cascade removals to associated items - if item in cascade_keys: - items_to_remove = self.cascade_removal_map[item] - transient_items = [] - cascade_failure = False - while len(items_to_remove) > 0: - item_to_remove = items_to_remove.pop() - transient_items.append(item_to_remove) - if item_to_remove not in inventory: - if units_always_have_upgrades and item_to_remove in locked_items: - cascade_failure = True - break - else: - continue - success = attempt_removal(item_to_remove) - if not success and units_always_have_upgrades: - cascade_failure = True - transient_items += items_to_remove - break - # Lock all associated items if any of them cannot be removed on Units Always Have Upgrades - if cascade_failure: - for transient_item in transient_items: - if transient_item in inventory: - for _ in range(inventory.count(transient_item)): - inventory.remove(transient_item) - if transient_item not in locked_items: - for _ in range(get_item_quantity(transient_item, self.multiworld, self.player)): - locked_items.append(copy_item(transient_item)) - if transient_item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing): - self.logical_inventory.add(transient_item.name) - else: - attempt_removal(item) - - if not spider_mine_sources & self.logical_inventory: - inventory = [item for item in inventory if not item.name.endswith("(Spider Mine)")] - if not BARRACKS_UNITS & self.logical_inventory: - inventory = [item for item in inventory if - not (item.name.startswith("Progressive Infantry") or item.name == "Orbital Strike")] - if not FACTORY_UNITS & self.logical_inventory: - inventory = [item for item in inventory if not item.name.startswith("Progressive Vehicle")] - if not STARPORT_UNITS & self.logical_inventory: - inventory = [item for item in inventory if not item.name.startswith("Progressive Ship")] - - # Cull finished, adding locked items back into inventory - inventory += locked_items - - # Replacing empty space with generically useful items - replacement_items = [item for item in self.item_pool - if (item not in inventory - and item not in self.locked_items - and item.name in second_pass_placeable_items)] - self.multiworld.random.shuffle(replacement_items) - while len(inventory) < inventory_size and len(replacement_items) > 0: - item = replacement_items.pop() - inventory.append(item) - - return inventory - - def _read_logic(self): - self._sc2wol_has_common_unit = lambda world, player: SC2WoLLogic._sc2wol_has_common_unit(self, world, player) - self._sc2wol_has_air = lambda world, player: SC2WoLLogic._sc2wol_has_air(self, world, player) - self._sc2wol_has_air_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_air_anti_air(self, world, player) - self._sc2wol_has_competent_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_competent_anti_air(self, world, player) - self._sc2wol_has_competent_ground_to_air = lambda world, player: SC2WoLLogic._sc2wol_has_competent_ground_to_air(self, world, player) - self._sc2wol_has_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_anti_air(self, world, player) - self._sc2wol_defense_rating = lambda world, player, zerg_enemy, air_enemy=False: SC2WoLLogic._sc2wol_defense_rating(self, world, player, zerg_enemy, air_enemy) - self._sc2wol_has_competent_comp = lambda world, player: SC2WoLLogic._sc2wol_has_competent_comp(self, world, player) - self._sc2wol_has_train_killers = lambda world, player: SC2WoLLogic._sc2wol_has_train_killers(self, world, player) - self._sc2wol_able_to_rescue = lambda world, player: SC2WoLLogic._sc2wol_able_to_rescue(self, world, player) - self._sc2wol_beats_protoss_deathball = lambda world, player: SC2WoLLogic._sc2wol_beats_protoss_deathball(self, world, player) - self._sc2wol_survives_rip_field = lambda world, player: SC2WoLLogic._sc2wol_survives_rip_field(self, world, player) - self._sc2wol_has_protoss_common_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_common_units(self, world, player) - self._sc2wol_has_protoss_medium_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_medium_units(self, world, player) - self._sc2wol_has_mm_upgrade = lambda world, player: SC2WoLLogic._sc2wol_has_mm_upgrade(self, world, player) - self._sc2wol_welcome_to_the_jungle_requirement = lambda world, player: SC2WoLLogic._sc2wol_welcome_to_the_jungle_requirement(self, world, player) - self._sc2wol_can_respond_to_colony_infestations = lambda world, player: SC2WoLLogic._sc2wol_can_respond_to_colony_infestations(self, world, player) - self._sc2wol_final_mission_requirements = lambda world, player: SC2WoLLogic._sc2wol_final_mission_requirements(self, world, player) - - def __init__(self, multiworld: MultiWorld, player: int, - item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], - has_protoss: bool): - self.multiworld = multiworld - self.player = player - self.logical_inventory = set() - self.locked_items = locked_items[:] - self.existing_items = existing_items - self._read_logic() - # Initial filter of item pool - self.item_pool = [] - item_quantities: dict[str, int] = dict() - # Inventory restrictiveness based on number of missions with checks - mission_order_type = get_option_value(self.multiworld, self.player, "mission_order") - mission_count = len(mission_orders[mission_order_type]) - 1 - self.min_units_per_structure = int(mission_count / 7) - min_upgrades = 1 if mission_count < 10 else 2 - for item in item_pool: - item_info = get_full_item_list()[item.name] - if item_info.type == "Upgrade": - # Locking upgrades based on mission duration - if item.name not in item_quantities: - item_quantities[item.name] = 0 - item_quantities[item.name] += 1 - if item_quantities[item.name] < min_upgrades: - self.locked_items.append(item) - else: - self.item_pool.append(item) - elif item_info.type == "Goal": - locked_items.append(item) - elif item_info.type != "Protoss" or has_protoss: - self.item_pool.append(item) - self.cascade_removal_map: Dict[Item, List[Item]] = dict() - for item in self.item_pool + locked_items + existing_items: - if item.name in UPGRADABLE_ITEMS: - upgrades = get_item_upgrades(self.item_pool, item) - associated_items = [*upgrades, item] - self.cascade_removal_map[item] = associated_items - if get_option_value(multiworld, player, "units_always_have_upgrades"): - for upgrade in upgrades: - self.cascade_removal_map[upgrade] = associated_items - - -def filter_items(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo], location_cache: List[Location], - item_pool: List[Item], existing_items: List[Item], locked_items: List[Item]) -> List[Item]: - """ - Returns a semi-randomly pruned set of items based on number of available locations. - The returned inventory must be capable of logically accessing every location in the world. - """ - open_locations = [location for location in location_cache if location.item is None] - inventory_size = len(open_locations) - has_protoss = bool(PROTOSS_REGIONS.intersection(mission_req_table.keys())) - mission_requirements = [location.access_rule for location in location_cache] - valid_inventory = ValidInventory(multiworld, player, item_pool, existing_items, locked_items, has_protoss) - - valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements) - return valid_items diff --git a/worlds/sc2wol/Regions.py b/worlds/sc2wol/Regions.py index f588ce7e982e..e69de29bb2d1 100644 --- a/worlds/sc2wol/Regions.py +++ b/worlds/sc2wol/Regions.py @@ -1,313 +0,0 @@ -from typing import List, Set, Dict, Tuple, Optional, Callable -from BaseClasses import MultiWorld, Region, Entrance, Location -from .Locations import LocationData -from .Options import get_option_value, MissionOrder -from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, alt_final_mission_locations, \ - MissionPools, vanilla_shuffle_order -from .PoolFilter import filter_missions - -PROPHECY_CHAIN_MISSION_COUNT = 4 - -VANILLA_SHUFFLED_FIRST_PROPHECY_MISSION = 21 - -def create_regions(multiworld: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location])\ - -> Tuple[Dict[str, MissionInfo], int, str]: - locations_per_region = get_locations_per_region(locations) - - mission_order_type = get_option_value(multiworld, player, "mission_order") - mission_order = mission_orders[mission_order_type] - - mission_pools = filter_missions(multiworld, player) - - regions = [create_region(multiworld, player, locations_per_region, location_cache, "Menu")] - - names: Dict[str, int] = {} - - if mission_order_type == MissionOrder.option_vanilla: - - # Generating all regions and locations - for region_name in vanilla_mission_req_table.keys(): - regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name)) - multiworld.regions += regions - - connect(multiworld, player, names, 'Menu', 'Liberation Day'), - connect(multiworld, player, names, 'Liberation Day', 'The Outlaws', - lambda state: state.has("Beat Liberation Day", player)), - connect(multiworld, player, names, 'The Outlaws', 'Zero Hour', - lambda state: state.has("Beat The Outlaws", player)), - connect(multiworld, player, names, 'Zero Hour', 'Evacuation', - lambda state: state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, 'Evacuation', 'Outbreak', - lambda state: state.has("Beat Evacuation", player)), - connect(multiworld, player, names, "Outbreak", "Safe Haven", - lambda state: state._sc2wol_cleared_missions(multiworld, player, 7) and - state.has("Beat Outbreak", player)), - connect(multiworld, player, names, "Outbreak", "Haven's Fall", - lambda state: state._sc2wol_cleared_missions(multiworld, player, 7) and - state.has("Beat Outbreak", player)), - connect(multiworld, player, names, 'Zero Hour', 'Smash and Grab', - lambda state: state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, 'Smash and Grab', 'The Dig', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 8) and - state.has("Beat Smash and Grab", player)), - connect(multiworld, player, names, 'The Dig', 'The Moebius Factor', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 11) and - state.has("Beat The Dig", player)), - connect(multiworld, player, names, 'The Moebius Factor', 'Supernova', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 14) and - state.has("Beat The Moebius Factor", player)), - connect(multiworld, player, names, 'Supernova', 'Maw of the Void', - lambda state: state.has("Beat Supernova", player)), - connect(multiworld, player, names, 'Zero Hour', "Devil's Playground", - lambda state: state._sc2wol_cleared_missions(multiworld, player, 4) and - state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, "Devil's Playground", 'Welcome to the Jungle', - lambda state: state.has("Beat Devil's Playground", player)), - connect(multiworld, player, names, "Welcome to the Jungle", 'Breakout', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 8) and - state.has("Beat Welcome to the Jungle", player)), - connect(multiworld, player, names, "Welcome to the Jungle", 'Ghost of a Chance', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 8) and - state.has("Beat Welcome to the Jungle", player)), - connect(multiworld, player, names, "Zero Hour", 'The Great Train Robbery', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 6) and - state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, 'The Great Train Robbery', 'Cutthroat', - lambda state: state.has("Beat The Great Train Robbery", player)), - connect(multiworld, player, names, 'Cutthroat', 'Engine of Destruction', - lambda state: state.has("Beat Cutthroat", player)), - connect(multiworld, player, names, 'Engine of Destruction', 'Media Blitz', - lambda state: state.has("Beat Engine of Destruction", player)), - connect(multiworld, player, names, 'Media Blitz', 'Piercing the Shroud', - lambda state: state.has("Beat Media Blitz", player)), - connect(multiworld, player, names, 'The Dig', 'Whispers of Doom', - lambda state: state.has("Beat The Dig", player)), - connect(multiworld, player, names, 'Whispers of Doom', 'A Sinister Turn', - lambda state: state.has("Beat Whispers of Doom", player)), - connect(multiworld, player, names, 'A Sinister Turn', 'Echoes of the Future', - lambda state: state.has("Beat A Sinister Turn", player)), - connect(multiworld, player, names, 'Echoes of the Future', 'In Utter Darkness', - lambda state: state.has("Beat Echoes of the Future", player)), - connect(multiworld, player, names, 'Maw of the Void', 'Gates of Hell', - lambda state: state.has("Beat Maw of the Void", player)), - connect(multiworld, player, names, 'Gates of Hell', 'Belly of the Beast', - lambda state: state.has("Beat Gates of Hell", player)), - connect(multiworld, player, names, 'Gates of Hell', 'Shatter the Sky', - lambda state: state.has("Beat Gates of Hell", player)), - connect(multiworld, player, names, 'Gates of Hell', 'All-In', - lambda state: state.has('Beat Gates of Hell', player) and ( - state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player))) - - return vanilla_mission_req_table, 29, 'All-In: Victory' - - else: - missions = [] - - remove_prophecy = mission_order_type == 1 and not get_option_value(multiworld, player, "shuffle_protoss") - - final_mission = mission_pools[MissionPools.FINAL][0] - - # Determining if missions must be removed - mission_pool_size = sum(len(mission_pool) for mission_pool in mission_pools.values()) - removals = len(mission_order) - mission_pool_size - # Removing entire Prophecy chain on vanilla shuffled when not shuffling protoss - if remove_prophecy: - removals -= PROPHECY_CHAIN_MISSION_COUNT - - # Initial fill out of mission list and marking all-in mission - for mission in mission_order: - # Removing extra missions if mission pool is too small - # Also handle lower removal priority than Prophecy - if 0 < mission.removal_priority <= removals or mission.category == 'Prophecy' and remove_prophecy \ - or (remove_prophecy and mission_order_type == MissionOrder.option_vanilla_shuffled - and mission.removal_priority > vanilla_shuffle_order[ - VANILLA_SHUFFLED_FIRST_PROPHECY_MISSION].removal_priority - and 0 < mission.removal_priority <= removals + PROPHECY_CHAIN_MISSION_COUNT): - missions.append(None) - elif mission.type == MissionPools.FINAL: - missions.append(final_mission) - else: - missions.append(mission.type) - - no_build_slots = [] - easy_slots = [] - medium_slots = [] - hard_slots = [] - - # Search through missions to find slots needed to fill - for i in range(len(missions)): - if missions[i] is None: - continue - if missions[i] == MissionPools.STARTER: - no_build_slots.append(i) - elif missions[i] == MissionPools.EASY: - easy_slots.append(i) - elif missions[i] == MissionPools.MEDIUM: - medium_slots.append(i) - elif missions[i] == MissionPools.HARD: - hard_slots.append(i) - - # Add no_build missions to the pool and fill in no_build slots - missions_to_add = mission_pools[MissionPools.STARTER] - if len(no_build_slots) > len(missions_to_add): - raise Exception("There are no valid No-Build missions. Please exclude fewer missions.") - for slot in no_build_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Add easy missions into pool and fill in easy slots - missions_to_add = missions_to_add + mission_pools[MissionPools.EASY] - if len(easy_slots) > len(missions_to_add): - raise Exception("There are not enough Easy missions to fill the campaign. Please exclude fewer missions.") - for slot in easy_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Add medium missions into pool and fill in medium slots - missions_to_add = missions_to_add + mission_pools[MissionPools.MEDIUM] - if len(medium_slots) > len(missions_to_add): - raise Exception("There are not enough Easy and Medium missions to fill the campaign. Please exclude fewer missions.") - for slot in medium_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Add hard missions into pool and fill in hard slots - missions_to_add = missions_to_add + mission_pools[MissionPools.HARD] - if len(hard_slots) > len(missions_to_add): - raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.") - for slot in hard_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Generating regions and locations from selected missions - for region_name in missions: - regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name)) - multiworld.regions += regions - - # Mapping original mission slots to shifted mission slots when missions are removed - slot_map = [] - slot_offset = 0 - for position, mission in enumerate(missions): - slot_map.append(position - slot_offset + 1) - if mission is None: - slot_offset += 1 - - # Loop through missions to create requirements table and connect regions - # TODO: Handle 'and' connections - mission_req_table = {} - - def build_connection_rule(mission_names: List[str], missions_req: int) -> Callable: - if len(mission_names) > 1: - return lambda state: state.has_all({f"Beat {name}" for name in mission_names}, player) and \ - state._sc2wol_cleared_missions(multiworld, player, missions_req) - else: - return lambda state: state.has(f"Beat {mission_names[0]}", player) and \ - state._sc2wol_cleared_missions(multiworld, player, missions_req) - - for i, mission in enumerate(missions): - if mission is None: - continue - connections = [] - all_connections = [] - for connection in mission_order[i].connect_to: - if connection == -1: - continue - while missions[connection] is None: - connection -= 1 - all_connections.append(missions[connection]) - for connection in mission_order[i].connect_to: - required_mission = missions[connection] - if connection == -1: - connect(multiworld, player, names, "Menu", mission) - else: - if required_mission is None and not mission_order[i].completion_critical: # Drop non-critical null slots - continue - while required_mission is None: # Substituting null slot with prior slot - connection -= 1 - required_mission = missions[connection] - required_missions = [required_mission] if mission_order[i].or_requirements else all_connections - connect(multiworld, player, names, required_mission, mission, - build_connection_rule(required_missions, mission_order[i].number)) - connections.append(slot_map[connection]) - - mission_req_table.update({mission: MissionInfo( - vanilla_mission_req_table[mission].id, connections, mission_order[i].category, - number=mission_order[i].number, - completion_critical=mission_order[i].completion_critical, - or_requirements=mission_order[i].or_requirements)}) - - final_mission_id = vanilla_mission_req_table[final_mission].id - - # Changing the completion condition for alternate final missions into an event - if final_mission != 'All-In': - final_location = alt_final_mission_locations[final_mission] - # Final location should be near the end of the cache - for i in range(len(location_cache) - 1, -1, -1): - if location_cache[i].name == final_location: - location_cache[i].locked = True - location_cache[i].event = True - location_cache[i].address = None - break - else: - final_location = 'All-In: Victory' - - return mission_req_table, final_mission_id, final_location - -def create_location(player: int, location_data: LocationData, region: Region, - location_cache: List[Location]) -> Location: - location = Location(player, location_data.name, location_data.code, region) - location.access_rule = location_data.rule - - if id is None: - location.event = True - location.locked = True - - location_cache.append(location) - - return location - - -def create_region(multiworld: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], - location_cache: List[Location], name: str) -> Region: - region = Region(name, player, multiworld) - - if name in locations_per_region: - for location_data in locations_per_region[name]: - location = create_location(player, location_data, region, location_cache) - region.locations.append(location) - - return region - - -def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str, - rule: Optional[Callable] = None): - sourceRegion = world.get_region(source, player) - targetRegion = world.get_region(target, player) - - if target not in used_names: - used_names[target] = 1 - name = target - else: - used_names[target] += 1 - name = target + (' ' * used_names[target]) - - connection = Entrance(player, name, sourceRegion) - - if rule: - connection.access_rule = rule - - sourceRegion.exits.append(connection) - connection.connect(targetRegion) - - -def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: - per_region: Dict[str, List[LocationData]] = {} - - for location in locations: - per_region.setdefault(location.region, []).append(location) - - return per_region diff --git a/worlds/sc2wol/Starcraft2.kv b/worlds/sc2wol/Starcraft2.kv deleted file mode 100644 index f0785b89e428..000000000000 --- a/worlds/sc2wol/Starcraft2.kv +++ /dev/null @@ -1,16 +0,0 @@ -: - rows: 1 - -: - cols: 1 - padding: [10,5,10,5] - spacing: [0,5] - -: - text_size: self.size - markup: True - halign: 'center' - valign: 'middle' - padding: [5,0,5,0] - markup: True - outline_width: 1 diff --git a/worlds/sc2wol/__init__.py b/worlds/sc2wol/__init__.py deleted file mode 100644 index 5c487f8fee09..000000000000 --- a/worlds/sc2wol/__init__.py +++ /dev/null @@ -1,324 +0,0 @@ -import typing - -from typing import List, Set, Tuple, Dict -from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification -from worlds.AutoWorld import WebWorld, World -from .Items import StarcraftWoLItem, filler_items, item_name_groups, get_item_table, get_full_item_list, \ - get_basic_units, ItemData, upgrade_included_names, progressive_if_nco -from .Locations import get_locations, LocationType -from .Regions import create_regions -from .Options import sc2wol_options, get_option_value, LocationInclusion -from .LogicMixin import SC2WoLLogic -from .PoolFilter import filter_missions, filter_items, get_item_upgrades -from .MissionTables import starting_mission_locations, MissionInfo - - -class Starcraft2WoLWebWorld(WebWorld): - setup = Tutorial( - "Multiworld Setup Guide", - "A guide to setting up the Starcraft 2 randomizer connected to an Archipelago Multiworld", - "English", - "setup_en.md", - "setup/en", - ["TheCondor"] - ) - - tutorials = [setup] - - -class SC2WoLWorld(World): - """ - StarCraft II: Wings of Liberty is a science fiction real-time strategy video game developed and published by Blizzard Entertainment. - Command Raynor's Raiders in collecting pieces of the Keystone in order to stop the zerg threat posed by the Queen of Blades. - """ - - game = "Starcraft 2 Wings of Liberty" - web = Starcraft2WoLWebWorld() - data_version = 5 - - item_name_to_id = {name: data.code for name, data in get_full_item_list().items()} - location_name_to_id = {location.name: location.code for location in get_locations(None, None)} - option_definitions = sc2wol_options - - item_name_groups = item_name_groups - locked_locations: typing.List[str] - location_cache: typing.List[Location] - mission_req_table = {} - final_mission_id: int - victory_item: str - required_client_version = 0, 4, 3 - - def __init__(self, multiworld: MultiWorld, player: int): - super(SC2WoLWorld, self).__init__(multiworld, player) - self.location_cache = [] - self.locked_locations = [] - - def create_item(self, name: str) -> Item: - data = get_full_item_list()[name] - return StarcraftWoLItem(name, data.classification, data.code, self.player) - - def create_regions(self): - self.mission_req_table, self.final_mission_id, self.victory_item = create_regions( - self.multiworld, self.player, get_locations(self.multiworld, self.player), self.location_cache - ) - - def create_items(self): - setup_events(self.player, self.locked_locations, self.location_cache) - - excluded_items = get_excluded_items(self.multiworld, self.player) - - starter_items = assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations) - - filter_locations(self.multiworld, self.player, self.locked_locations, self.location_cache) - - pool = get_item_pool(self.multiworld, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache) - - fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool) - - self.multiworld.itempool += pool - - def set_rules(self): - self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player) - - def get_filler_item_name(self) -> str: - return self.multiworld.random.choice(filler_items) - - def fill_slot_data(self): - slot_data = {} - for option_name in sc2wol_options: - option = getattr(self.multiworld, option_name)[self.player] - if type(option.value) in {str, int}: - slot_data[option_name] = int(option.value) - slot_req_table = {} - for mission in self.mission_req_table: - slot_req_table[mission] = self.mission_req_table[mission]._asdict() - - slot_data["mission_req"] = slot_req_table - slot_data["final_mission"] = self.final_mission_id - return slot_data - - -def setup_events(player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]): - for location in location_cache: - if location.address is None: - item = Item(location.name, ItemClassification.progression, None, player) - - locked_locations.append(location.name) - - location.place_locked_item(item) - - -def get_excluded_items(multiworld: MultiWorld, player: int) -> Set[str]: - excluded_items: Set[str] = set() - - for item in multiworld.precollected_items[player]: - excluded_items.add(item.name) - - excluded_items_option = getattr(multiworld, 'excluded_items', []) - - excluded_items.update(excluded_items_option[player].value) - - return excluded_items - - -def assign_starter_items(multiworld: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]) -> List[Item]: - non_local_items = multiworld.non_local_items[player].value - if get_option_value(multiworld, player, "early_unit"): - local_basic_unit = sorted(item for item in get_basic_units(multiworld, player) if item not in non_local_items and item not in excluded_items) - if not local_basic_unit: - raise Exception("At least one basic unit must be local") - - # The first world should also be the starting world - first_mission = list(multiworld.worlds[player].mission_req_table)[0] - if first_mission in starting_mission_locations: - first_location = starting_mission_locations[first_mission] - elif first_mission == "In Utter Darkness": - first_location = first_mission + ": Defeat" - else: - first_location = first_mission + ": Victory" - - return [assign_starter_item(multiworld, player, excluded_items, locked_locations, first_location, local_basic_unit)] - else: - return [] - - -def assign_starter_item(multiworld: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str], - location: str, item_list: Tuple[str, ...]) -> Item: - - item_name = multiworld.random.choice(item_list) - - excluded_items.add(item_name) - - item = create_item_with_correct_settings(player, item_name) - - multiworld.get_location(location, player).place_locked_item(item) - - locked_locations.append(location) - - return item - - -def get_item_pool(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo], - starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]: - pool: List[Item] = [] - - # For the future: goal items like Artifact Shards go here - locked_items = [] - - # YAML items - yaml_locked_items = get_option_value(multiworld, player, 'locked_items') - - # Adjust generic upgrade availability based on options - include_upgrades = get_option_value(multiworld, player, 'generic_upgrade_missions') == 0 - upgrade_items = get_option_value(multiworld, player, 'generic_upgrade_items') - - # Include items from outside Wings of Liberty - item_sets = {'wol'} - if get_option_value(multiworld, player, 'nco_items'): - item_sets.add('nco') - if get_option_value(multiworld, player, 'bw_items'): - item_sets.add('bw') - if get_option_value(multiworld, player, 'ext_items'): - item_sets.add('ext') - - def allowed_quantity(name: str, data: ItemData) -> int: - if name in excluded_items \ - or data.type == "Upgrade" and (not include_upgrades or name not in upgrade_included_names[upgrade_items]) \ - or not data.origin.intersection(item_sets): - return 0 - elif name in progressive_if_nco and 'nco' not in item_sets: - return 1 - else: - return data.quantity - - for name, data in get_item_table(multiworld, player).items(): - for i in range(allowed_quantity(name, data)): - item = create_item_with_correct_settings(player, name) - if name in yaml_locked_items: - locked_items.append(item) - else: - pool.append(item) - - existing_items = starter_items + [item for item in multiworld.precollected_items[player]] - existing_names = [item.name for item in existing_items] - - # Check the parent item integrity, exclude items - pool[:] = [item for item in pool if pool_contains_parent(item, pool + locked_items + existing_items)] - - # Removing upgrades for excluded items - for item_name in excluded_items: - if item_name in existing_names: - continue - invalid_upgrades = get_item_upgrades(pool, item_name) - for invalid_upgrade in invalid_upgrades: - pool.remove(invalid_upgrade) - - filtered_pool = filter_items(multiworld, player, mission_req_table, location_cache, pool, existing_items, locked_items) - return filtered_pool - - -def fill_item_pool_with_dummy_items(self: SC2WoLWorld, multiworld: MultiWorld, player: int, locked_locations: List[str], - location_cache: List[Location], pool: List[Item]): - for _ in range(len(location_cache) - len(locked_locations) - len(pool)): - item = create_item_with_correct_settings(player, self.get_filler_item_name()) - pool.append(item) - - -def create_item_with_correct_settings(player: int, name: str) -> Item: - data = get_full_item_list()[name] - - item = Item(name, data.classification, data.code, player) - - return item - - -def pool_contains_parent(item: Item, pool: [Item]): - item_data = get_full_item_list().get(item.name) - if item_data.parent_item is None: - # The item has not associated parent, the item is valid - return True - parent_item = item_data.parent_item - # Check if the pool contains the parent item - return parent_item in [pool_item.name for pool_item in pool] - - -def filter_locations(multiworld: MultiWorld, player, locked_locations: List[str], location_cache: List[Location]): - """ - Filters the locations in the world using a trash or Nothing item - :param multiworld: - :param player: - :param locked_locations: - :param location_cache: - :return: - """ - open_locations = [location for location in location_cache if location.item is None] - plando_locations = get_plando_locations(multiworld, player) - mission_progress_locations = get_option_value(multiworld, player, "mission_progress_locations") - bonus_locations = get_option_value(multiworld, player, "bonus_locations") - challenge_locations = get_option_value(multiworld, player, "challenge_locations") - optional_boss_locations = get_option_value(multiworld, player, "optional_boss_locations") - location_data = get_locations(multiworld, player) - for location in open_locations: - # Go through the locations that aren't locked yet (early unit, etc) - if location.name not in plando_locations: - # The location is not plando'd - sc2_location = [sc2_location for sc2_location in location_data if sc2_location.name == location.name][0] - location_type = sc2_location.type - - if location_type == LocationType.MISSION_PROGRESS \ - and mission_progress_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, mission_progress_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - if location_type == LocationType.BONUS \ - and bonus_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, bonus_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - if location_type == LocationType.CHALLENGE \ - and challenge_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, challenge_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - if location_type == LocationType.OPTIONAL_BOSS \ - and optional_boss_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, optional_boss_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - -def place_exclusion_item(item_name, location, locked_locations, player): - item = create_item_with_correct_settings(player, item_name) - location.place_locked_item(item) - locked_locations.append(location.name) - - -def get_exclusion_item(multiworld: MultiWorld, option) -> str: - """ - Gets the exclusion item according to settings (trash/nothing) - :param multiworld: - :param option: - :return: Item used for location exclusion - """ - if option == LocationInclusion.option_nothing: - return "Nothing" - elif option == LocationInclusion.option_trash: - index = multiworld.random.randint(0, len(filler_items) - 1) - return filler_items[index] - raise Exception(f"Unsupported option type: {option}") - - -def get_plando_locations(multiworld: MultiWorld, player) -> List[str]: - """ - - :param multiworld: - :param player: - :return: A list of locations affected by a plando in a world - """ - plando_locations = [] - for plando_setting in multiworld.plando_items[player]: - plando_locations += plando_setting.get("locations", []) - plando_setting_location = plando_setting.get("location", None) - if plando_setting_location is not None: - plando_locations.append(plando_setting_location) - - return plando_locations diff --git a/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md b/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md deleted file mode 100644 index 18bda6478457..000000000000 --- a/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md +++ /dev/null @@ -1,54 +0,0 @@ -# Starcraft 2 Wings of Liberty - -## What does randomization do to this game? - -The following unlocks are randomized as items: -1. Your ability to build any non-worker unit (including Marines!). -2. Your ability to upgrade infantry weapons, infantry armor, vehicle weapons, etc. -3. All armory upgrades -4. All laboratory upgrades -5. All mercenaries -6. Small boosts to your starting mineral and vespene gas totals on each mission - -You find items by making progress in bonus objectives (like by rescuing allies in 'Zero Hour') and by completing -missions. When you receive items, they will immediately become available, even during a mission, and you will be -notified via a text box in the top-right corner of the game screen. (The text client for StarCraft 2 also records all -items in all worlds.) - -Missions are launched only through the text client. The Hyperion is never visited. Additionally, credits are not used. - -## What is the goal of this game when randomized? - -The goal is to beat the final mission: 'All In'. The config file determines which variant you must complete. - -## What non-randomized changes are there from vanilla Starcraft 2? - -1. Some missions have more vespene geysers available to allow a wider variety of units. -2. Starports no longer require Factories in order to be built. -3. In 'A Sinister Turn' and 'Echoes of the Future', you can research Protoss air weapon/armor upgrades. - -## Which of my items can be in another player's world? - -By default, any of StarCraft 2's items (specified above) can be in another player's world. See the -[Advanced YAML Guide](https://archipelago.gg/tutorial/Archipelago/advanced_settings/en) -for more information on how to change this. - -## Unique Local Commands - -The following commands are only available when using the Starcraft 2 Client to play with Archipelago. - -- `/difficulty [difficulty]` Overrides the difficulty set for the world. - - Options: casual, normal, hard, brutal -- `/game_speed [game_speed]` Overrides the game speed for the world - - Options: default, slower, slow, normal, fast, faster -- `/color [color]` Changes your color (Currently has no effect) - - Options: white, red, blue, teal, purple, yellow, orange, green, lightpink, violet, lightgrey, darkgreen, brown, - lightgreen, darkgrey, pink, rainbow, random, default -- `/disable_mission_check` Disables the check to see if a mission is available to play. Meant for co-op runs where one - player can play the next mission in a chain the other player is doing. -- `/play [mission_id]` Starts a Starcraft 2 mission based off of the mission_id provided -- `/available` Get what missions are currently available to play -- `/unfinished` Get what missions are currently available to play and have not had all locations checked -- `/set_path [path]` Menually set the SC2 install directory (if the automatic detection fails) -- `/download_data` Download the most recent release of the necassry files for playing SC2 with Archipelago. Will - overwrite existing files diff --git a/worlds/sc2wol/docs/setup_en.md b/worlds/sc2wol/docs/setup_en.md deleted file mode 100644 index 9bfeb3d235bc..000000000000 --- a/worlds/sc2wol/docs/setup_en.md +++ /dev/null @@ -1,98 +0,0 @@ -# StarCraft 2 Wings of Liberty Randomizer Setup Guide - -This guide contains instructions on how to install and troubleshoot the StarCraft 2 Archipelago client, as well as where -to obtain a config file for StarCraft 2. - -## Required Software - -- [StarCraft 2](https://starcraft2.com/en-us/) -- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) -- [StarCraft 2 AP Maps and Data](https://github.com/Ziktofel/Archipelago-SC2-data/releases) - -## How do I install this randomizer? - -1. Install StarCraft 2 and Archipelago using the first two links above. (The StarCraft 2 client for Archipelago is - included by default.) - - Linux users should also follow the instructions found at the bottom of this page - (["Running in Linux"](#running-in-linux)). -2. Run ArchipelagoStarcraft2Client.exe. - - macOS users should instead follow the instructions found at ["Running in macOS"](#running-in-macos) for this step only. -3. Type the command `/download_data`. This will automatically install the Maps and Data files from the third link above. - -## Where do I get a config file (aka "YAML") for this game? - -The [Player Settings](https://archipelago.gg/games/Starcraft%202%20Wings%20of%20Liberty/player-settings) page on this -website allows you to choose your personal settings for the randomizer and download them into a config file. Remember -the name you type in the `Player Name` box; that's the "slot name" the client will ask you for when you attempt to -connect! - -### And why do I need a config file? - -Config files tell Archipelago how you'd like your game to be randomized, even if you're only using default settings. -When you're setting up a multiworld, every world needs its own config file. -Check out [Creating a YAML](https://archipelago.gg/tutorial/Archipelago/setup/en#creating-a-yaml) for more information. - -## How do I join a MultiWorld game? - -1. Run ArchipelagoStarcraft2Client.exe. - - macOS users should instead follow the instructions found at ["Running in macOS"](#running-in-macos) for this step only. -2. Type `/connect [server ip]`. -3. Type your slot name and the server's password when prompted. -4. Once connected, switch to the 'StarCraft 2 Launcher' tab in the client. There, you can see every mission. By default, - only 'Liberation Day' will be available at the beginning. Just click on a mission to start it! - -## The game isn't launching when I try to start a mission. - -First, check the log file for issues (stored at `[Archipelago Directory]/logs/SC2Client.txt`). If you can't figure out -the log file, visit our [Discord's](https://discord.com/invite/8Z65BR2) tech-support channel for help. Please include a -specific description of what's going wrong and attach your log file to your message. - -## Running in macOS - -To run StarCraft 2 through Archipelago in macOS, you will need to run the client via source as seen here: [macOS Guide](https://archipelago.gg/tutorial/Archipelago/mac/en). Note: when running the client, you will need to run the command `python3 Starcraft2Client.py`. - -## Running in Linux - -To run StarCraft 2 through Archipelago in Linux, you will need to install the game using Wine, then run the Linux build -of the Archipelago client. - -Make sure you have StarCraft 2 installed using Wine, and that you have followed the -[installation procedures](#how-do-i-install-this-randomizer?) to add the Archipelago maps to the correct location. You will not -need to copy the .dll files. If you're having trouble installing or running StarCraft 2 on Linux, I recommend using the -Lutris installer. - -Copy the following into a .sh file, replacing the values of **WINE** and **SC2PATH** variables with the relevant -locations, as well as setting **PATH_TO_ARCHIPELAGO** to the directory containing the AppImage if it is not in the same -folder as the script. - -```sh -# Let the client know we're running SC2 in Wine -export SC2PF=WineLinux -export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python - -# FIXME Replace with path to the version of Wine used to run SC2 -export WINE="/usr/bin/wine" - -# FIXME Replace with path to StarCraft II install folder -export SC2PATH="/home/user/Games/starcraft-ii/drive_c/Program Files (x86)/StarCraft II/" - -# FIXME Set to directory which contains Archipelago AppImage file -PATH_TO_ARCHIPELAGO= - -# Gets the latest version of Archipelago AppImage in PATH_TO_ARCHIPELAGO. -# If PATH_TO_ARCHIPELAGO is not set, this defaults to the directory containing -# this script file. -ARCHIPELAGO="$(ls ${PATH_TO_ARCHIPELAGO:-$(dirname $0)}/Archipelago_*.AppImage | sort -r | head -1)" - -# Start the Archipelago client -$ARCHIPELAGO Starcraft2Client -``` - -For Lutris installs, you can run `lutris -l` to get the numerical ID of your StarCraft II install, then run the command -below, replacing **${ID}** with the numerical ID. - - lutris lutris:rungameid/${ID} --output-script sc2.sh - -This will get all of the relevant environment variables Lutris sets to run StarCraft 2 in a script, including the path -to the Wine binary that Lutris uses. You can then remove the line that runs the Battle.Net launcher and copy the code -above into the existing script. diff --git a/worlds/shivers/Items.py b/worlds/shivers/Items.py index caf24ded2987..10d234d450bb 100644 --- a/worlds/shivers/Items.py +++ b/worlds/shivers/Items.py @@ -33,28 +33,38 @@ class ItemData(typing.NamedTuple): "Lightning Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 17, "pot"), "Sand Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 18, "pot"), "Metal Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 19, "pot"), + "Water Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 20, "pot_type2"), + "Wax Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 21, "pot_type2"), + "Ash Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 22, "pot_type2"), + "Oil Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 23, "pot_type2"), + "Cloth Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 24, "pot_type2"), + "Wood Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 25, "pot_type2"), + "Crystal Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 26, "pot_type2"), + "Lightning Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 27, "pot_type2"), + "Sand Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 28, "pot_type2"), + "Metal Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "pot_type2"), #Keys - "Key for Office Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 20, "key"), - "Key for Bedroom Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 21, "key"), - "Key for Three Floor Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 22, "key"), - "Key for Workshop": ItemData(SHIVERS_ITEM_ID_OFFSET + 23, "key"), - "Key for Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 24, "key"), - "Key for Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 25, "key"), - "Key for Greenhouse Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 26, "key"), - "Key for Ocean Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 27, "key"), - "Key for Projector Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 28, "key"), - "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "key"), - "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), - "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), - "Key for Tiki Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), - "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), - "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), - "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), - "Key for Bedroom": ItemData(SHIVERS_ITEM_ID_OFFSET + 36, "key"), - "Key for Underground Lake Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 37, "key"), - "Key for Janitor Closet": ItemData(SHIVERS_ITEM_ID_OFFSET + 38, "key"), - "Key for Front Door": ItemData(SHIVERS_ITEM_ID_OFFSET + 39, "key-optional"), + "Key for Office Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), + "Key for Bedroom Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), + "Key for Three Floor Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), + "Key for Workshop": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), + "Key for Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), + "Key for Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), + "Key for Greenhouse Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 36, "key"), + "Key for Ocean Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 37, "key"), + "Key for Projector Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 38, "key"), + "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 39, "key"), + "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 40, "key"), + "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 41, "key"), + "Key for Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 42, "key"), + "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 43, "key"), + "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 44, "key"), + "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 45, "key"), + "Key for Bedroom": ItemData(SHIVERS_ITEM_ID_OFFSET + 46, "key"), + "Key for Underground Lake Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 47, "key"), + "Key for Janitor Closet": ItemData(SHIVERS_ITEM_ID_OFFSET + 48, "key"), + "Key for Front Door": ItemData(SHIVERS_ITEM_ID_OFFSET + 49, "key-optional"), #Abilities "Crawling": ItemData(SHIVERS_ITEM_ID_OFFSET + 50, "ability"), @@ -83,6 +93,16 @@ class ItemData(typing.NamedTuple): "Lightning Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 87, "potduplicate"), "Sand Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 88, "potduplicate"), "Metal Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 89, "potduplicate"), + "Water Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 140, "potduplicate_type2"), + "Wax Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 141, "potduplicate_type2"), + "Ash Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 142, "potduplicate_type2"), + "Oil Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 143, "potduplicate_type2"), + "Cloth Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 144, "potduplicate_type2"), + "Wood Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 145, "potduplicate_type2"), + "Crystal Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 146, "potduplicate_type2"), + "Lightning Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 147, "potduplicate_type2"), + "Sand Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 148, "potduplicate_type2"), + "Metal Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 149, "potduplicate_type2"), #Filler "Empty": ItemData(SHIVERS_ITEM_ID_OFFSET + 90, "filler"), @@ -90,7 +110,7 @@ class ItemData(typing.NamedTuple): "Water Always Available in Lobby": ItemData(SHIVERS_ITEM_ID_OFFSET + 92, "filler2", ItemClassification.filler), "Wax Always Available in Library": ItemData(SHIVERS_ITEM_ID_OFFSET + 93, "filler2", ItemClassification.filler), "Wax Always Available in Anansi Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 94, "filler2", ItemClassification.filler), - "Wax Always Available in Tiki Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 95, "filler2", ItemClassification.filler), + "Wax Always Available in Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 95, "filler2", ItemClassification.filler), "Ash Always Available in Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 96, "filler2", ItemClassification.filler), "Ash Always Available in Burial Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 97, "filler2", ItemClassification.filler), "Oil Always Available in Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 98, "filler2", ItemClassification.filler), diff --git a/worlds/shivers/Options.py b/worlds/shivers/Options.py index 6d1880406910..2f33eb18e5d1 100644 --- a/worlds/shivers/Options.py +++ b/worlds/shivers/Options.py @@ -1,46 +1,100 @@ -from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions +from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions, Range from dataclasses import dataclass +class IxupiCapturesNeeded(Range): + """ + Number of Ixupi Captures needed for goal condition. + """ + display_name = "Number of Ixupi Captures Needed" + range_start = 1 + range_end = 10 + default = 10 + class LobbyAccess(Choice): - """Chooses how keys needed to reach the lobby are placed. + """ + Chooses how keys needed to reach the lobby are placed. - Normal: Keys are placed anywhere - Early: Keys are placed early - - Local: Keys are placed locally""" + - Local: Keys are placed locally + """ display_name = "Lobby Access" option_normal = 0 option_early = 1 option_local = 2 + default = 1 class PuzzleHintsRequired(DefaultOnToggle): - """If turned on puzzle hints will be available before the corresponding puzzle is required. For example: The Tiki - Drums puzzle will be placed after access to the security cameras which give you the solution. Turning this off - allows for greater randomization.""" + """ + If turned on puzzle hints/solutions will be available before the corresponding puzzle is required. + + For example: The Red Door puzzle will be logically required only after access to the Beth's Address Book which gives you the solution. + + Turning this off allows for greater randomization. + """ display_name = "Puzzle Hints Required" class InformationPlaques(Toggle): - """Adds Information Plaques as checks.""" + """ + Adds Information Plaques as checks. + (40 Locations) + """ display_name = "Include Information Plaques" class FrontDoorUsable(Toggle): - """Adds a key to unlock the front door of the museum.""" + """ + Adds a key to unlock the front door of the museum. + """ display_name = "Front Door Usable" class ElevatorsStaySolved(DefaultOnToggle): - """Adds elevators as checks and will remain open upon solving them.""" + """ + Adds elevators as checks and will remain open upon solving them. + (3 Locations) + """ display_name = "Elevators Stay Solved" class EarlyBeth(DefaultOnToggle): - """Beth's body is open at the start of the game. This allows any pot piece to be placed in the slide and early checks on the second half of the final riddle.""" + """ + Beth's body is open at the start of the game. This allows any pot piece to be placed in the slide and early checks on the second half of the final riddle. + """ display_name = "Early Beth" class EarlyLightning(Toggle): - """Allows lightning to be captured at any point in the game. You will still need to capture all ten Ixupi for victory.""" + """ + Allows lightning to be captured at any point in the game. You will still need to capture all ten Ixupi for victory. + (1 Location) + """ display_name = "Early Lightning" +class LocationPotPieces(Choice): + """ + Chooses where pot pieces will be located within the multiworld. + - Own World: Pot pieces will be located within your own world + - Different World: Pot pieces will be located in another world + - Any World: Pot pieces will be located in any world + """ + display_name = "Location of Pot Pieces" + option_own_world = 0 + option_different_world = 1 + option_any_world = 2 + +class FullPots(Choice): + """ + Chooses if pots will be in pieces or already completed + - Pieces: Only pot pieces will be added to the item pool + - Complete: Only completed pots will be added to the item pool + - Mixed: Each pot will be randomly chosen to be pieces or already completed. + """ + display_name = "Full Pots" + option_pieces = 0 + option_complete = 1 + option_mixed = 2 + @dataclass class ShiversOptions(PerGameCommonOptions): + ixupi_captures_needed: IxupiCapturesNeeded lobby_access: LobbyAccess puzzle_hints_required: PuzzleHintsRequired include_information_plaques: InformationPlaques @@ -48,3 +102,5 @@ class ShiversOptions(PerGameCommonOptions): elevators_stay_solved: ElevatorsStaySolved early_beth: EarlyBeth early_lightning: EarlyLightning + location_pot_pieces: LocationPotPieces + full_pots: FullPots diff --git a/worlds/shivers/Rules.py b/worlds/shivers/Rules.py index 57488ff33314..5288fa2c9c3f 100644 --- a/worlds/shivers/Rules.py +++ b/worlds/shivers/Rules.py @@ -1,4 +1,4 @@ -from typing import Dict, List, TYPE_CHECKING +from typing import Dict, TYPE_CHECKING from collections.abc import Callable from BaseClasses import CollectionState from worlds.generic.Rules import forbid_item @@ -8,58 +8,58 @@ def water_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Lobby", "Region", player) or (state.can_reach("Janitor Closet", "Region", player) and cloth_capturable(state, player))) \ - and state.has_all({"Water Pot Bottom", "Water Pot Top", "Water Pot Bottom DUPE", "Water Pot Top DUPE"}, player) + return state.has_all({"Water Pot Bottom", "Water Pot Top", "Water Pot Bottom DUPE", "Water Pot Top DUPE"}, player) or \ + state.has_all({"Water Pot Complete", "Water Pot Complete DUPE"}, player) def wax_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Library", "Region", player) or state.can_reach("Anansi", "Region", player)) \ - and state.has_all({"Wax Pot Bottom", "Wax Pot Top", "Wax Pot Bottom DUPE", "Wax Pot Top DUPE"}, player) + return state.has_all({"Wax Pot Bottom", "Wax Pot Top", "Wax Pot Bottom DUPE", "Wax Pot Top DUPE"}, player) or \ + state.has_all({"Wax Pot Complete", "Wax Pot Complete DUPE"}, player) def ash_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Office", "Region", player) or state.can_reach("Burial", "Region", player)) \ - and state.has_all({"Ash Pot Bottom", "Ash Pot Top", "Ash Pot Bottom DUPE", "Ash Pot Top DUPE"}, player) + return state.has_all({"Ash Pot Bottom", "Ash Pot Top", "Ash Pot Bottom DUPE", "Ash Pot Top DUPE"}, player) or \ + state.has_all({"Ash Pot Complete", "Ash Pot Complete DUPE"}, player) def oil_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Prehistoric", "Region", player) or state.can_reach("Tar River", "Region", player)) \ - and state.has_all({"Oil Pot Bottom", "Oil Pot Top", "Oil Pot Bottom DUPE", "Oil Pot Top DUPE"}, player) + return state.has_all({"Oil Pot Bottom", "Oil Pot Top", "Oil Pot Bottom DUPE", "Oil Pot Top DUPE"}, player) or \ + state.has_all({"Oil Pot Complete", "Oil Pot Complete DUPE"}, player) def cloth_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Egypt", "Region", player) or state.can_reach("Burial", "Region", player) or state.can_reach("Janitor Closet", "Region", player)) \ - and state.has_all({"Cloth Pot Bottom", "Cloth Pot Top", "Cloth Pot Bottom DUPE", "Cloth Pot Top DUPE"}, player) + return state.has_all({"Cloth Pot Bottom", "Cloth Pot Top", "Cloth Pot Bottom DUPE", "Cloth Pot Top DUPE"}, player) or \ + state.has_all({"Cloth Pot Complete", "Cloth Pot Complete DUPE"}, player) def wood_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Workshop", "Region", player) or state.can_reach("Blue Maze", "Region", player) or state.can_reach("Gods Room", "Region", player) or state.can_reach("Anansi", "Region", player)) \ - and state.has_all({"Wood Pot Bottom", "Wood Pot Top", "Wood Pot Bottom DUPE", "Wood Pot Top DUPE"}, player) + return state.has_all({"Wood Pot Bottom", "Wood Pot Top", "Wood Pot Bottom DUPE", "Wood Pot Top DUPE"}, player) or \ + state.has_all({"Wood Pot Complete", "Wood Pot Complete DUPE"}, player) def crystal_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Lobby", "Region", player) or state.can_reach("Ocean", "Region", player)) \ - and state.has_all({"Crystal Pot Bottom", "Crystal Pot Top", "Crystal Pot Bottom DUPE", "Crystal Pot Top DUPE"}, player) + return state.has_all({"Crystal Pot Bottom", "Crystal Pot Top", "Crystal Pot Bottom DUPE", "Crystal Pot Top DUPE"}, player) or \ + state.has_all({"Crystal Pot Complete", "Crystal Pot Complete DUPE"}, player) def sand_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Greenhouse", "Region", player) or state.can_reach("Ocean", "Region", player)) \ - and state.has_all({"Sand Pot Bottom", "Sand Pot Top", "Sand Pot Bottom DUPE", "Sand Pot Top DUPE"}, player) + return state.has_all({"Sand Pot Bottom", "Sand Pot Top", "Sand Pot Bottom DUPE", "Sand Pot Top DUPE"}, player) or \ + state.has_all({"Sand Pot Complete", "Sand Pot Complete DUPE"}, player) def metal_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Projector Room", "Region", player) or state.can_reach("Prehistoric", "Region", player) or state.can_reach("Bedroom", "Region", player)) \ - and state.has_all({"Metal Pot Bottom", "Metal Pot Top", "Metal Pot Bottom DUPE", "Metal Pot Top DUPE"}, player) + return state.has_all({"Metal Pot Bottom", "Metal Pot Top", "Metal Pot Bottom DUPE", "Metal Pot Top DUPE"}, player) or \ + state.has_all({"Metal Pot Complete", "Metal Pot Complete DUPE"}, player) def lightning_capturable(state: CollectionState, player: int) -> bool: - return (first_nine_ixupi_capturable or state.multiworld.early_lightning[player].value) \ - and state.can_reach("Generator", "Region", player) \ - and state.has_all({"Lightning Pot Bottom", "Lightning Pot Top", "Lightning Pot Bottom DUPE", "Lightning Pot Top DUPE"}, player) + return (first_nine_ixupi_capturable(state, player) or state.multiworld.worlds[player].options.early_lightning.value) \ + and (state.has_all({"Lightning Pot Bottom", "Lightning Pot Top", "Lightning Pot Bottom DUPE", "Lightning Pot Top DUPE"}, player) or \ + state.has_all({"Lightning Pot Complete", "Lightning Pot Complete DUPE"}, player)) def beths_body_available(state: CollectionState, player: int) -> bool: - return (first_nine_ixupi_capturable(state, player) or state.multiworld.early_beth[player].value) \ + return (first_nine_ixupi_capturable(state, player) or state.multiworld.worlds[player].options.early_beth.value) \ and state.can_reach("Generator", "Region", player) @@ -71,8 +71,14 @@ def first_nine_ixupi_capturable(state: CollectionState, player: int) -> bool: and metal_capturable(state, player) +def all_skull_dials_available(state: CollectionState, player: int) -> bool: + return state.can_reach("Prehistoric", "Region", player) and state.can_reach("Tar River", "Region", player) \ + and state.can_reach("Egypt", "Region", player) and state.can_reach("Burial", "Region", player) \ + and state.can_reach("Gods Room", "Region", player) and state.can_reach("Werewolf", "Region", player) + + def get_rules_lookup(player: int): - rules_lookup: Dict[str, List[Callable[[CollectionState], bool]]] = { + rules_lookup: Dict[str, Dict[str, Callable[[CollectionState], bool]]] = { "entrances": { "To Office Elevator From Underground Blue Tunnels": lambda state: state.has("Key for Office Elevator", player), "To Office Elevator From Office": lambda state: state.has("Key for Office Elevator", player), @@ -96,8 +102,8 @@ def get_rules_lookup(player: int): "To Lobby From Egypt": lambda state: state.has("Key for Egypt Room", player), "To Egypt From Lobby": lambda state: state.has("Key for Egypt Room", player), "To Janitor Closet": lambda state: state.has("Key for Janitor Closet", player), - "To Tiki From Burial": lambda state: state.has("Key for Tiki Room", player), - "To Burial From Tiki": lambda state: state.has("Key for Tiki Room", player), + "To Shaman From Burial": lambda state: state.has("Key for Shaman Room", player), + "To Burial From Shaman": lambda state: state.has("Key for Shaman Room", player), "To Inventions From UFO": lambda state: state.has("Key for UFO Room", player), "To UFO From Inventions": lambda state: state.has("Key for UFO Room", player), "To Torture From Inventions": lambda state: state.has("Key for Torture Room", player), @@ -116,11 +122,9 @@ def get_rules_lookup(player: int): "To Tar River From Lobby": lambda state: (state.has("Crawling", player) and oil_capturable(state, player) and state.can_reach("Tar River", "Region", player)), "To Burial From Egypt": lambda state: state.can_reach("Egypt", "Region", player), "To Gods Room From Anansi": lambda state: state.can_reach("Gods Room", "Region", player), - "To Slide Room": lambda state: ( - state.can_reach("Prehistoric", "Region", player) and state.can_reach("Tar River", "Region",player) and - state.can_reach("Egypt", "Region", player) and state.can_reach("Burial", "Region", player) and - state.can_reach("Gods Room", "Region", player) and state.can_reach("Werewolf", "Region", player)), - "To Lobby From Slide Room": lambda state: (beths_body_available(state, player)) + "To Slide Room": lambda state: all_skull_dials_available(state, player), + "To Lobby From Slide Room": lambda state: beths_body_available(state, player), + "To Water Capture From Janitor Closet": lambda state: cloth_capturable(state, player) }, "locations_required": { "Puzzle Solved Anansi Musicbox": lambda state: state.can_reach("Clock Tower", "Region", player), @@ -141,11 +145,12 @@ def get_rules_lookup(player: int): "Final Riddle: Norse God Stone Message": lambda state: (state.can_reach("Fortune Teller", "Region", player) and state.can_reach("UFO", "Region", player)), "Final Riddle: Beth's Body Page 17": lambda state: beths_body_available(state, player), "Final Riddle: Guillotine Dropped": lambda state: beths_body_available(state, player), + "Puzzle Solved Skull Dial Door": lambda state: all_skull_dials_available(state, player), }, "locations_puzzle_hints": { "Puzzle Solved Clock Tower Door": lambda state: state.can_reach("Three Floor Elevator", "Region", player), "Puzzle Solved Clock Chains": lambda state: state.can_reach("Bedroom", "Region", player), - "Puzzle Solved Tiki Drums": lambda state: state.can_reach("Clock Tower", "Region", player), + "Puzzle Solved Shaman Drums": lambda state: state.can_reach("Clock Tower", "Region", player), "Puzzle Solved Red Door": lambda state: state.can_reach("Maintenance Tunnels", "Region", player), "Puzzle Solved UFO Symbols": lambda state: state.can_reach("Library", "Region", player), "Puzzle Solved Maze Door": lambda state: state.can_reach("Projector Room", "Region", player), @@ -191,18 +196,29 @@ def set_rules(world: "ShiversWorld") -> None: for location_name, rule in rules_lookup["lightning"].items(): multiworld.get_location(location_name, player).access_rule = rule + # Register indirect conditions + multiworld.register_indirect_condition(world.get_region("Burial"), world.get_entrance("To Slide Room")) + multiworld.register_indirect_condition(world.get_region("Egypt"), world.get_entrance("To Slide Room")) + multiworld.register_indirect_condition(world.get_region("Gods Room"), world.get_entrance("To Slide Room")) + multiworld.register_indirect_condition(world.get_region("Prehistoric"), world.get_entrance("To Slide Room")) + multiworld.register_indirect_condition(world.get_region("Tar River"), world.get_entrance("To Slide Room")) + multiworld.register_indirect_condition(world.get_region("Werewolf"), world.get_entrance("To Slide Room")) + multiworld.register_indirect_condition(world.get_region("Prehistoric"), world.get_entrance("To Tar River From Lobby")) + # forbid cloth in janitor closet and oil in tar river forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Bottom DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Top DUPE", player) + forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Complete DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Bottom DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Top DUPE", player) + forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Complete DUPE", player) # Filler Item Forbids forbid_item(multiworld.get_location("Puzzle Solved Lyre", player), "Easier Lyre", player) forbid_item(multiworld.get_location("Ixupi Captured Water", player), "Water Always Available in Lobby", player) forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Library", player) forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Anansi Room", player) - forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Tiki Room", player) + forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Shaman Room", player) forbid_item(multiworld.get_location("Ixupi Captured Ash", player), "Ash Always Available in Office", player) forbid_item(multiworld.get_location("Ixupi Captured Ash", player), "Ash Always Available in Burial Room", player) forbid_item(multiworld.get_location("Ixupi Captured Oil", player), "Oil Always Available in Prehistoric Room", player) @@ -221,8 +237,8 @@ def set_rules(world: "ShiversWorld") -> None: forbid_item(multiworld.get_location("Ixupi Captured Metal", player), "Metal Always Available in Prehistoric", player) # Set completion condition - multiworld.completion_condition[player] = lambda state: (first_nine_ixupi_capturable(state, player) and lightning_capturable(state, player)) - - - - + multiworld.completion_condition[player] = lambda state: (( + water_capturable(state, player) + wax_capturable(state, player) + ash_capturable(state, player) \ + + oil_capturable(state, player) + cloth_capturable(state, player) + wood_capturable(state, player) \ + + crystal_capturable(state, player) + sand_capturable(state, player) + metal_capturable(state, player) \ + + lightning_capturable(state, player)) >= world.options.ixupi_captures_needed.value) diff --git a/worlds/shivers/__init__.py b/worlds/shivers/__init__.py index e43e91fb5ae3..a2d7bc14644e 100644 --- a/worlds/shivers/__init__.py +++ b/worlds/shivers/__init__.py @@ -1,3 +1,4 @@ +from typing import List from .Items import item_table, ShiversItem from .Rules import set_rules from BaseClasses import Item, Tutorial, Region, Location @@ -22,7 +23,7 @@ class ShiversWorld(World): Shivers is a horror themed point and click adventure. Explore the mysteries of Windlenot's Museum of the Strange and Unusual. """ - game: str = "Shivers" + game = "Shivers" topology_present = False web = ShiversWeb() options_dataclass = ShiversOptions @@ -30,7 +31,13 @@ class ShiversWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = Constants.location_name_to_id - + shivers_item_id_offset = 27000 + pot_completed_list: List[int] + + + def generate_early(self): + self.pot_completed_list = [] + def create_item(self, name: str) -> Item: data = item_table[name] return ShiversItem(name, data.classification, data.code, self.player) @@ -78,9 +85,28 @@ def create_items(self) -> None: #Add items to item pool itempool = [] for name, data in item_table.items(): - if data.type in {"pot", "key", "ability", "filler2"}: + if data.type in {"key", "ability", "filler2"}: itempool.append(self.create_item(name)) + # Pot pieces/Completed/Mixed: + for i in range(10): + if self.options.full_pots == "pieces": + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + i])) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 10 + i])) + elif self.options.full_pots == "complete": + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 20 + i])) + else: + # Roll for if pieces or a complete pot will be used. + # Pot Pieces + if self.random.randint(0, 1) == 0: + self.pot_completed_list.append(0) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + i])) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 10 + i])) + # Completed Pot + else: + self.pot_completed_list.append(1) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 20 + i])) + #Add Filler itempool += [self.create_item("Easier Lyre") for i in range(9)] @@ -88,7 +114,6 @@ def create_items(self) -> None: filler_needed = len(self.multiworld.get_unfilled_locations(self.player)) - 24 - len(itempool) itempool += [self.random.choices([self.create_item("Heal"), self.create_item("Easier Lyre")], weights=[95, 5])[0] for i in range(filler_needed)] - #Place library escape items. Choose a location to place the escape item library_region = self.multiworld.get_region("Library", self.player) librarylocation = self.random.choice([loc for loc in library_region.locations if not loc.name.startswith("Accessible:")]) @@ -123,14 +148,14 @@ def create_items(self) -> None: self.multiworld.itempool += itempool #Lobby acess: - if self.options.lobby_access == 1: + if self.options.lobby_access == "early": if lobby_access_keys == 1: self.multiworld.early_items[self.player]["Key for Underground Lake Room"] = 1 self.multiworld.early_items[self.player]["Key for Office Elevator"] = 1 self.multiworld.early_items[self.player]["Key for Office"] = 1 elif lobby_access_keys == 2: self.multiworld.early_items[self.player]["Key for Front Door"] = 1 - if self.options.lobby_access == 2: + if self.options.lobby_access == "local": if lobby_access_keys == 1: self.multiworld.local_early_items[self.player]["Key for Underground Lake Room"] = 1 self.multiworld.local_early_items[self.player]["Key for Office Elevator"] = 1 @@ -138,6 +163,12 @@ def create_items(self) -> None: elif lobby_access_keys == 2: self.multiworld.local_early_items[self.player]["Key for Front Door"] = 1 + #Pot piece shuffle location: + if self.options.location_pot_pieces == "own_world": + self.options.local_items.value |= {name for name, data in item_table.items() if data.type == "pot" or data.type == "pot_type2"} + if self.options.location_pot_pieces == "different_world": + self.options.non_local_items.value |= {name for name, data in item_table.items() if data.type == "pot" or data.type == "pot_type2"} + def pre_fill(self) -> None: # Prefills event storage locations with duplicate pots storagelocs = [] @@ -149,7 +180,23 @@ def pre_fill(self) -> None: if loc_name.startswith("Accessible: "): storagelocs.append(self.multiworld.get_location(loc_name, self.player)) - storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate'] + #Pot pieces/Completed/Mixed: + if self.options.full_pots == "pieces": + storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate'] + elif self.options.full_pots == "complete": + storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate_type2'] + storageitems += [self.create_item("Empty") for i in range(10)] + else: + for i in range(10): + #Pieces + if self.pot_completed_list[i] == 0: + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 70 + i])] + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 80 + i])] + #Complete + else: + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 140 + i])] + storageitems += [self.create_item("Empty")] + storageitems += [self.create_item("Empty") for i in range(3)] state = self.multiworld.get_all_state(True) @@ -166,11 +213,13 @@ def pre_fill(self) -> None: def fill_slot_data(self) -> dict: return { - "storageplacements": self.storage_placements, - "excludedlocations": {str(excluded_location).replace('ExcludeLocations(', '').replace(')', '') for excluded_location in self.multiworld.exclude_locations.values()}, - "elevatorsstaysolved": {self.options.elevators_stay_solved.value}, - "earlybeth": {self.options.early_beth.value}, - "earlylightning": {self.options.early_lightning.value}, + "StoragePlacements": self.storage_placements, + "ExcludedLocations": list(self.options.exclude_locations.value), + "IxupiCapturesNeeded": self.options.ixupi_captures_needed.value, + "ElevatorsStaySolved": self.options.elevators_stay_solved.value, + "EarlyBeth": self.options.early_beth.value, + "EarlyLightning": self.options.early_lightning.value, + "FrontDoorUsable": self.options.front_door_usable.value } diff --git a/worlds/shivers/data/excluded_locations.json b/worlds/shivers/data/excluded_locations.json index a37285eb1d29..29655d4a5024 100644 --- a/worlds/shivers/data/excluded_locations.json +++ b/worlds/shivers/data/excluded_locations.json @@ -1,45 +1,45 @@ { "plaques": [ - "Information Plaque: Transforming Masks (Lobby)", - "Information Plaque: Jade Skull (Lobby)", - "Information Plaque: Bronze Unicorn (Prehistoric)", - "Information Plaque: Griffin (Prehistoric)", - "Information Plaque: Eagles Nest (Prehistoric)", - "Information Plaque: Large Spider (Prehistoric)", - "Information Plaque: Starfish (Prehistoric)", - "Information Plaque: Quartz Crystal (Ocean)", - "Information Plaque: Poseidon (Ocean)", - "Information Plaque: Colossus of Rhodes (Ocean)", - "Information Plaque: Poseidon's Temple (Ocean)", - "Information Plaque: Subterranean World (Underground Maze)", - "Information Plaque: Dero (Underground Maze)", - "Information Plaque: Tomb of the Ixupi (Egypt)", - "Information Plaque: The Sphinx (Egypt)", - "Information Plaque: Curse of Anubis (Egypt)", - "Information Plaque: Norse Burial Ship (Burial)", - "Information Plaque: Paracas Burial Bundles (Burial)", - "Information Plaque: Spectacular Coffins of Ghana (Burial)", - "Information Plaque: Cremation (Burial)", - "Information Plaque: Animal Crematorium (Burial)", - "Information Plaque: Witch Doctors of the Congo (Tiki)", - "Information Plaque: Sarombe doctor of Mozambique (Tiki)", - "Information Plaque: Fisherman's Canoe God (Gods)", - "Information Plaque: Mayan Gods (Gods)", - "Information Plaque: Thor (Gods)", - "Information Plaque: Celtic Janus Sculpture (Gods)", - "Information Plaque: Sumerian Bull God - An (Gods)", - "Information Plaque: Sumerian Lyre (Gods)", - "Information Plaque: Chuen (Gods)", - "Information Plaque: African Creation Myth (Anansi)", - "Information Plaque: Apophis the Serpent (Anansi)", - "Information Plaque: Death (Anansi)", - "Information Plaque: Cyclops (Pegasus)", - "Information Plaque: Lycanthropy (Werewolf)", - "Information Plaque: Coincidence or Extraterrestrial Visits? (UFO)", - "Information Plaque: Planets (UFO)", - "Information Plaque: Astronomical Construction (UFO)", - "Information Plaque: Guillotine (Torture)", - "Information Plaque: Aliens (UFO)" + "Information Plaque: (Lobby) Transforming Masks", + "Information Plaque: (Lobby) Jade Skull", + "Information Plaque: (Prehistoric) Bronze Unicorn", + "Information Plaque: (Prehistoric) Griffin", + "Information Plaque: (Prehistoric) Eagles Nest", + "Information Plaque: (Prehistoric) Large Spider", + "Information Plaque: (Prehistoric) Starfish", + "Information Plaque: (Ocean) Quartz Crystal", + "Information Plaque: (Ocean) Poseidon", + "Information Plaque: (Ocean) Colossus of Rhodes", + "Information Plaque: (Ocean) Poseidon's Temple", + "Information Plaque: (Underground Maze) Subterranean World", + "Information Plaque: (Underground Maze) Dero", + "Information Plaque: (Egypt) Tomb of the Ixupi", + "Information Plaque: (Egypt) The Sphinx", + "Information Plaque: (Egypt) Curse of Anubis", + "Information Plaque: (Burial) Norse Burial Ship", + "Information Plaque: (Burial) Paracas Burial Bundles", + "Information Plaque: (Burial) Spectacular Coffins of Ghana", + "Information Plaque: (Burial) Cremation", + "Information Plaque: (Burial) Animal Crematorium", + "Information Plaque: (Shaman) Witch Doctors of the Congo", + "Information Plaque: (Shaman) Sarombe doctor of Mozambique", + "Information Plaque: (Gods) Fisherman's Canoe God", + "Information Plaque: (Gods) Mayan Gods", + "Information Plaque: (Gods) Thor", + "Information Plaque: (Gods) Celtic Janus Sculpture", + "Information Plaque: (Gods) Sumerian Bull God - An", + "Information Plaque: (Gods) Sumerian Lyre", + "Information Plaque: (Gods) Chuen", + "Information Plaque: (Anansi) African Creation Myth", + "Information Plaque: (Anansi) Apophis the Serpent", + "Information Plaque: (Anansi) Death", + "Information Plaque: (Pegasus) Cyclops", + "Information Plaque: (Werewolf) Lycanthropy", + "Information Plaque: (UFO) Coincidence or Extraterrestrial Visits?", + "Information Plaque: (UFO) Planets", + "Information Plaque: (UFO) Astronomical Construction", + "Information Plaque: (Torture) Guillotine", + "Information Plaque: (UFO) Aliens" ], "elevators": [ "Puzzle Solved Office Elevator", diff --git a/worlds/shivers/data/locations.json b/worlds/shivers/data/locations.json index fdf8ed69d1e5..64fe3647348d 100644 --- a/worlds/shivers/data/locations.json +++ b/worlds/shivers/data/locations.json @@ -13,7 +13,7 @@ "Puzzle Solved Columns of RA", "Puzzle Solved Burial Door", "Puzzle Solved Chinese Solitaire", - "Puzzle Solved Tiki Drums", + "Puzzle Solved Shaman Drums", "Puzzle Solved Lyre", "Puzzle Solved Red Door", "Puzzle Solved Fortune Teller Door", @@ -38,7 +38,7 @@ "Flashback Memory Obtained Theater Movie", "Flashback Memory Obtained Museum Blueprints", "Flashback Memory Obtained Beth's Address Book", - "Flashback Memory Obtained Merick's Notebook", + "Flashback Memory Obtained Merrick's Notebook", "Flashback Memory Obtained Professor Windlenot's Diary", "Ixupi Captured Water", "Ixupi Captured Wax", @@ -68,48 +68,48 @@ "Puzzle Hint Found: Gallows Information Plaque", "Puzzle Hint Found: Mastermind Information Plaque", "Puzzle Hint Found: Elevator Writing", - "Puzzle Hint Found: Tiki Security Camera", + "Puzzle Hint Found: Shaman Security Camera", "Puzzle Hint Found: Tape Recorder Heard", - "Information Plaque: Transforming Masks (Lobby)", - "Information Plaque: Jade Skull (Lobby)", - "Information Plaque: Bronze Unicorn (Prehistoric)", - "Information Plaque: Griffin (Prehistoric)", - "Information Plaque: Eagles Nest (Prehistoric)", - "Information Plaque: Large Spider (Prehistoric)", - "Information Plaque: Starfish (Prehistoric)", - "Information Plaque: Quartz Crystal (Ocean)", - "Information Plaque: Poseidon (Ocean)", - "Information Plaque: Colossus of Rhodes (Ocean)", - "Information Plaque: Poseidon's Temple (Ocean)", - "Information Plaque: Subterranean World (Underground Maze)", - "Information Plaque: Dero (Underground Maze)", - "Information Plaque: Tomb of the Ixupi (Egypt)", - "Information Plaque: The Sphinx (Egypt)", - "Information Plaque: Curse of Anubis (Egypt)", - "Information Plaque: Norse Burial Ship (Burial)", - "Information Plaque: Paracas Burial Bundles (Burial)", - "Information Plaque: Spectacular Coffins of Ghana (Burial)", - "Information Plaque: Cremation (Burial)", - "Information Plaque: Animal Crematorium (Burial)", - "Information Plaque: Witch Doctors of the Congo (Tiki)", - "Information Plaque: Sarombe doctor of Mozambique (Tiki)", - "Information Plaque: Fisherman's Canoe God (Gods)", - "Information Plaque: Mayan Gods (Gods)", - "Information Plaque: Thor (Gods)", - "Information Plaque: Celtic Janus Sculpture (Gods)", - "Information Plaque: Sumerian Bull God - An (Gods)", - "Information Plaque: Sumerian Lyre (Gods)", - "Information Plaque: Chuen (Gods)", - "Information Plaque: African Creation Myth (Anansi)", - "Information Plaque: Apophis the Serpent (Anansi)", - "Information Plaque: Death (Anansi)", - "Information Plaque: Cyclops (Pegasus)", - "Information Plaque: Lycanthropy (Werewolf)", - "Information Plaque: Coincidence or Extraterrestrial Visits? (UFO)", - "Information Plaque: Planets (UFO)", - "Information Plaque: Astronomical Construction (UFO)", - "Information Plaque: Guillotine (Torture)", - "Information Plaque: Aliens (UFO)", + "Information Plaque: (Lobby) Transforming Masks", + "Information Plaque: (Lobby) Jade Skull", + "Information Plaque: (Prehistoric) Bronze Unicorn", + "Information Plaque: (Prehistoric) Griffin", + "Information Plaque: (Prehistoric) Eagles Nest", + "Information Plaque: (Prehistoric) Large Spider", + "Information Plaque: (Prehistoric) Starfish", + "Information Plaque: (Ocean) Quartz Crystal", + "Information Plaque: (Ocean) Poseidon", + "Information Plaque: (Ocean) Colossus of Rhodes", + "Information Plaque: (Ocean) Poseidon's Temple", + "Information Plaque: (Underground Maze Staircase) Subterranean World", + "Information Plaque: (Underground Maze) Dero", + "Information Plaque: (Egypt) Tomb of the Ixupi", + "Information Plaque: (Egypt) The Sphinx", + "Information Plaque: (Egypt) Curse of Anubis", + "Information Plaque: (Burial) Norse Burial Ship", + "Information Plaque: (Burial) Paracas Burial Bundles", + "Information Plaque: (Burial) Spectacular Coffins of Ghana", + "Information Plaque: (Burial) Cremation", + "Information Plaque: (Burial) Animal Crematorium", + "Information Plaque: (Shaman) Witch Doctors of the Congo", + "Information Plaque: (Shaman) Sarombe doctor of Mozambique", + "Information Plaque: (Gods) Fisherman's Canoe God", + "Information Plaque: (Gods) Mayan Gods", + "Information Plaque: (Gods) Thor", + "Information Plaque: (Gods) Celtic Janus Sculpture", + "Information Plaque: (Gods) Sumerian Bull God - An", + "Information Plaque: (Gods) Sumerian Lyre", + "Information Plaque: (Gods) Chuen", + "Information Plaque: (Anansi) African Creation Myth", + "Information Plaque: (Anansi) Apophis the Serpent", + "Information Plaque: (Anansi) Death", + "Information Plaque: (Pegasus) Cyclops", + "Information Plaque: (Werewolf) Lycanthropy", + "Information Plaque: (UFO) Coincidence or Extraterrestrial Visits?", + "Information Plaque: (UFO) Planets", + "Information Plaque: (UFO) Astronomical Construction", + "Information Plaque: (Torture) Guillotine", + "Information Plaque: (UFO) Aliens", "Puzzle Solved Office Elevator", "Puzzle Solved Bedroom Elevator", "Puzzle Solved Three Floor Elevator", @@ -119,16 +119,6 @@ "Outside": [ "Puzzle Solved Gears", "Puzzle Solved Stone Henge", - "Ixupi Captured Water", - "Ixupi Captured Wax", - "Ixupi Captured Ash", - "Ixupi Captured Oil", - "Ixupi Captured Cloth", - "Ixupi Captured Wood", - "Ixupi Captured Crystal", - "Ixupi Captured Sand", - "Ixupi Captured Metal", - "Ixupi Captured Lightning", "Puzzle Solved Office Elevator", "Puzzle Solved Three Floor Elevator", "Puzzle Hint Found: Combo Lock in Mailbox", @@ -176,13 +166,14 @@ "Lobby": [ "Puzzle Solved Theater Door", "Flashback Memory Obtained Museum Brochure", - "Information Plaque: Jade Skull (Lobby)", - "Information Plaque: Transforming Masks (Lobby)", + "Information Plaque: (Lobby) Jade Skull", + "Information Plaque: (Lobby) Transforming Masks", "Accessible: Storage: Slide", - "Accessible: Storage: Eagles Head" + "Accessible: Storage: Transforming Mask" ], "Generator": [ - "Final Riddle: Beth's Body Page 17" + "Final Riddle: Beth's Body Page 17", + "Ixupi Captured Lightning" ], "Theater Back Hallways": [ "Puzzle Solved Clock Tower Door" @@ -193,7 +184,7 @@ "Clock Tower": [ "Flashback Memory Obtained Beth's Ghost", "Accessible: Storage: Clock Tower", - "Puzzle Hint Found: Tiki Security Camera" + "Puzzle Hint Found: Shaman Security Camera" ], "Projector Room": [ "Flashback Memory Obtained Theater Movie" @@ -204,12 +195,13 @@ "Flashback Memory Obtained Museum Blueprints", "Accessible: Storage: Ocean", "Puzzle Hint Found: Sirens Song Heard", - "Information Plaque: Quartz Crystal (Ocean)", - "Information Plaque: Poseidon (Ocean)", - "Information Plaque: Colossus of Rhodes (Ocean)", - "Information Plaque: Poseidon's Temple (Ocean)" + "Information Plaque: (Ocean) Quartz Crystal", + "Information Plaque: (Ocean) Poseidon", + "Information Plaque: (Ocean) Colossus of Rhodes", + "Information Plaque: (Ocean) Poseidon's Temple" ], "Maze Staircase": [ + "Information Plaque: (Underground Maze Staircase) Subterranean World", "Puzzle Solved Maze Door" ], "Egypt": [ @@ -217,38 +209,38 @@ "Puzzle Solved Burial Door", "Accessible: Storage: Egypt", "Puzzle Hint Found: Egyptian Sphinx Heard", - "Information Plaque: Tomb of the Ixupi (Egypt)", - "Information Plaque: The Sphinx (Egypt)", - "Information Plaque: Curse of Anubis (Egypt)" + "Information Plaque: (Egypt) Tomb of the Ixupi", + "Information Plaque: (Egypt) The Sphinx", + "Information Plaque: (Egypt) Curse of Anubis" ], "Burial": [ "Puzzle Solved Chinese Solitaire", - "Flashback Memory Obtained Merick's Notebook", + "Flashback Memory Obtained Merrick's Notebook", "Accessible: Storage: Chinese Solitaire", - "Information Plaque: Norse Burial Ship (Burial)", - "Information Plaque: Paracas Burial Bundles (Burial)", - "Information Plaque: Spectacular Coffins of Ghana (Burial)", - "Information Plaque: Animal Crematorium (Burial)", - "Information Plaque: Cremation (Burial)" - ], - "Tiki": [ - "Puzzle Solved Tiki Drums", - "Accessible: Storage: Tiki Hut", - "Information Plaque: Witch Doctors of the Congo (Tiki)", - "Information Plaque: Sarombe doctor of Mozambique (Tiki)" + "Information Plaque: (Burial) Norse Burial Ship", + "Information Plaque: (Burial) Paracas Burial Bundles", + "Information Plaque: (Burial) Spectacular Coffins of Ghana", + "Information Plaque: (Burial) Animal Crematorium", + "Information Plaque: (Burial) Cremation" + ], + "Shaman": [ + "Puzzle Solved Shaman Drums", + "Accessible: Storage: Shaman Hut", + "Information Plaque: (Shaman) Witch Doctors of the Congo", + "Information Plaque: (Shaman) Sarombe doctor of Mozambique" ], "Gods Room": [ "Puzzle Solved Lyre", "Puzzle Solved Red Door", "Accessible: Storage: Lyre", "Final Riddle: Norse God Stone Message", - "Information Plaque: Fisherman's Canoe God (Gods)", - "Information Plaque: Mayan Gods (Gods)", - "Information Plaque: Thor (Gods)", - "Information Plaque: Celtic Janus Sculpture (Gods)", - "Information Plaque: Sumerian Bull God - An (Gods)", - "Information Plaque: Sumerian Lyre (Gods)", - "Information Plaque: Chuen (Gods)" + "Information Plaque: (Gods) Fisherman's Canoe God", + "Information Plaque: (Gods) Mayan Gods", + "Information Plaque: (Gods) Thor", + "Information Plaque: (Gods) Celtic Janus Sculpture", + "Information Plaque: (Gods) Sumerian Bull God - An", + "Information Plaque: (Gods) Sumerian Lyre", + "Information Plaque: (Gods) Chuen" ], "Blue Maze": [ "Puzzle Solved Fortune Teller Door" @@ -265,28 +257,28 @@ "Puzzle Solved UFO Symbols", "Accessible: Storage: UFO", "Final Riddle: Planets Aligned", - "Information Plaque: Coincidence or Extraterrestrial Visits? (UFO)", - "Information Plaque: Planets (UFO)", - "Information Plaque: Astronomical Construction (UFO)", - "Information Plaque: Aliens (UFO)" + "Information Plaque: (UFO) Coincidence or Extraterrestrial Visits?", + "Information Plaque: (UFO) Planets", + "Information Plaque: (UFO) Astronomical Construction", + "Information Plaque: (UFO) Aliens" ], "Anansi": [ "Puzzle Solved Anansi Musicbox", "Flashback Memory Obtained Ancient Astrology", "Accessible: Storage: Skeleton", "Accessible: Storage: Anansi", - "Information Plaque: African Creation Myth (Anansi)", - "Information Plaque: Apophis the Serpent (Anansi)", - "Information Plaque: Death (Anansi)", - "Information Plaque: Cyclops (Pegasus)", - "Information Plaque: Lycanthropy (Werewolf)" + "Information Plaque: (Anansi) African Creation Myth", + "Information Plaque: (Anansi) Apophis the Serpent", + "Information Plaque: (Anansi) Death", + "Information Plaque: (Pegasus) Cyclops", + "Information Plaque: (Werewolf) Lycanthropy" ], "Torture": [ "Puzzle Solved Gallows", - "Accessible: Storage: Hanging", + "Accessible: Storage: Gallows", "Final Riddle: Guillotine Dropped", "Puzzle Hint Found: Gallows Information Plaque", - "Information Plaque: Guillotine (Torture)" + "Information Plaque: (Torture) Guillotine" ], "Puzzle Room Mastermind": [ "Puzzle Solved Mastermind", @@ -296,17 +288,16 @@ "Puzzle Solved Marble Flipper" ], "Prehistoric": [ - "Information Plaque: Bronze Unicorn (Prehistoric)", - "Information Plaque: Griffin (Prehistoric)", - "Information Plaque: Eagles Nest (Prehistoric)", - "Information Plaque: Large Spider (Prehistoric)", - "Information Plaque: Starfish (Prehistoric)", + "Information Plaque: (Prehistoric) Bronze Unicorn", + "Information Plaque: (Prehistoric) Griffin", + "Information Plaque: (Prehistoric) Eagles Nest", + "Information Plaque: (Prehistoric) Large Spider", + "Information Plaque: (Prehistoric) Starfish", "Accessible: Storage: Eagles Nest" ], "Tar River": [ "Accessible: Storage: Tar River", - "Information Plaque: Subterranean World (Underground Maze)", - "Information Plaque: Dero (Underground Maze)" + "Information Plaque: (Underground Maze) Dero" ], "Theater": [ "Accessible: Storage: Theater" @@ -320,6 +311,33 @@ "Skull Dial Bridge": [ "Accessible: Storage: Skull Bridge", "Puzzle Solved Skull Dial Door" + ], + "Water Capture": [ + "Ixupi Captured Water" + ], + "Wax Capture": [ + "Ixupi Captured Wax" + ], + "Ash Capture": [ + "Ixupi Captured Ash" + ], + "Oil Capture": [ + "Ixupi Captured Oil" + ], + "Cloth Capture": [ + "Ixupi Captured Cloth" + ], + "Wood Capture": [ + "Ixupi Captured Wood" + ], + "Crystal Capture": [ + "Ixupi Captured Crystal" + ], + "Sand Capture": [ + "Ixupi Captured Sand" + ], + "Metal Capture": [ + "Ixupi Captured Metal" ] } -} +} diff --git a/worlds/shivers/data/regions.json b/worlds/shivers/data/regions.json index 3e81136c45f8..aeb5aa737366 100644 --- a/worlds/shivers/data/regions.json +++ b/worlds/shivers/data/regions.json @@ -7,35 +7,35 @@ ["Underground Lake", ["To Underground Tunnels From Underground Lake", "To Underground Blue Tunnels From Underground Lake"]], ["Underground Blue Tunnels", ["To Underground Lake From Underground Blue Tunnels", "To Office Elevator From Underground Blue Tunnels"]], ["Office Elevator", ["To Underground Blue Tunnels From Office Elevator","To Office From Office Elevator"]], - ["Office", ["To Office Elevator From Office", "To Workshop", "To Lobby From Office", "To Bedroom Elevator From Office"]], - ["Workshop", ["To Office From Workshop"]], + ["Office", ["To Office Elevator From Office", "To Workshop", "To Lobby From Office", "To Bedroom Elevator From Office", "To Ash Capture From Office"]], + ["Workshop", ["To Office From Workshop", "To Wood Capture From Workshop"]], ["Bedroom Elevator", ["To Office From Bedroom Elevator", "To Bedroom"]], - ["Bedroom", ["To Bedroom Elevator From Bedroom"]], - ["Lobby", ["To Office From Lobby", "To Library From Lobby", "To Theater From Lobby", "To Prehistoric From Lobby", "To Egypt From Lobby", "To Tar River From Lobby", "To Outside From Lobby"]], - ["Library", ["To Lobby From Library", "To Maintenance Tunnels From Library"]], + ["Bedroom", ["To Bedroom Elevator From Bedroom", "To Metal Capture From Bedroom"]], + ["Lobby", ["To Office From Lobby", "To Library From Lobby", "To Theater From Lobby", "To Prehistoric From Lobby", "To Egypt From Lobby", "To Tar River From Lobby", "To Outside From Lobby", "To Water Capture From Lobby", "To Crystal Capture From Lobby"]], + ["Library", ["To Lobby From Library", "To Maintenance Tunnels From Library", "To Wax Capture From Library"]], ["Maintenance Tunnels", ["To Library From Maintenance Tunnels", "To Three Floor Elevator From Maintenance Tunnels", "To Generator"]], ["Generator", ["To Maintenance Tunnels From Generator"]], ["Theater", ["To Lobby From Theater", "To Theater Back Hallways From Theater"]], ["Theater Back Hallways", ["To Theater From Theater Back Hallways", "To Clock Tower Staircase From Theater Back Hallways", "To Maintenance Tunnels From Theater Back Hallways", "To Projector Room"]], ["Clock Tower Staircase", ["To Theater Back Hallways From Clock Tower Staircase", "To Clock Tower"]], ["Clock Tower", ["To Clock Tower Staircase From Clock Tower"]], - ["Projector Room", ["To Theater Back Hallways From Projector Room"]], - ["Prehistoric", ["To Lobby From Prehistoric", "To Greenhouse", "To Ocean From Prehistoric"]], - ["Greenhouse", ["To Prehistoric From Greenhouse"]], - ["Ocean", ["To Prehistoric From Ocean", "To Maze Staircase From Ocean"]], + ["Projector Room", ["To Theater Back Hallways From Projector Room", "To Metal Capture From Projector Room"]], + ["Prehistoric", ["To Lobby From Prehistoric", "To Greenhouse", "To Ocean From Prehistoric", "To Oil Capture From Prehistoric", "To Metal Capture From Prehistoric"]], + ["Greenhouse", ["To Prehistoric From Greenhouse", "To Sand Capture From Greenhouse"]], + ["Ocean", ["To Prehistoric From Ocean", "To Maze Staircase From Ocean", "To Crystal Capture From Ocean", "To Sand Capture From Ocean"]], ["Maze Staircase", ["To Ocean From Maze Staircase", "To Maze From Maze Staircase"]], ["Maze", ["To Maze Staircase From Maze", "To Tar River"]], - ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River"]], - ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt"]], - ["Burial", ["To Egypt From Burial", "To Tiki From Burial"]], - ["Tiki", ["To Burial From Tiki", "To Gods Room"]], - ["Gods Room", ["To Tiki From Gods Room", "To Anansi From Gods Room"]], - ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi"]], + ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River", "To Oil Capture From Tar River"]], + ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt", "To Cloth Capture From Egypt"]], + ["Burial", ["To Egypt From Burial", "To Shaman From Burial", "To Ash Capture From Burial", "To Cloth Capture From Burial"]], + ["Shaman", ["To Burial From Shaman", "To Gods Room", "To Wax Capture From Shaman"]], + ["Gods Room", ["To Shaman From Gods Room", "To Anansi From Gods Room", "To Wood Capture From Gods Room"]], + ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi", "To Wax Capture From Anansi", "To Wood Capture From Anansi"]], ["Werewolf", ["To Anansi From Werewolf", "To Night Staircase From Werewolf"]], ["Night Staircase", ["To Werewolf From Night Staircase", "To Janitor Closet", "To UFO"]], - ["Janitor Closet", ["To Night Staircase From Janitor Closet"]], + ["Janitor Closet", ["To Night Staircase From Janitor Closet", "To Water Capture From Janitor Closet", "To Cloth Capture From Janitor Closet"]], ["UFO", ["To Night Staircase From UFO", "To Inventions From UFO"]], - ["Blue Maze", ["To Egypt From Blue Maze", "To Three Floor Elevator From Blue Maze Bottom", "To Three Floor Elevator From Blue Maze Top", "To Fortune Teller", "To Inventions From Blue Maze"]], + ["Blue Maze", ["To Egypt From Blue Maze", "To Three Floor Elevator From Blue Maze Bottom", "To Three Floor Elevator From Blue Maze Top", "To Fortune Teller", "To Inventions From Blue Maze", "To Wood Capture From Blue Maze"]], ["Three Floor Elevator", ["To Maintenance Tunnels From Three Floor Elevator", "To Blue Maze From Three Floor Elevator"]], ["Fortune Teller", ["To Blue Maze From Fortune Teller"]], ["Inventions", ["To Blue Maze From Inventions", "To UFO From Inventions", "To Torture From Inventions"]], @@ -43,7 +43,16 @@ ["Puzzle Room Mastermind", ["To Torture", "To Puzzle Room Marbles From Puzzle Room Mastermind"]], ["Puzzle Room Marbles", ["To Puzzle Room Mastermind From Puzzle Room Marbles", "To Skull Dial Bridge From Puzzle Room Marbles"]], ["Skull Dial Bridge", ["To Puzzle Room Marbles From Skull Dial Bridge", "To Slide Room"]], - ["Slide Room", ["To Skull Dial Bridge From Slide Room", "To Lobby From Slide Room"]] + ["Slide Room", ["To Skull Dial Bridge From Slide Room", "To Lobby From Slide Room"]], + ["Water Capture", []], + ["Wax Capture", []], + ["Ash Capture", []], + ["Oil Capture", []], + ["Cloth Capture", []], + ["Wood Capture", []], + ["Crystal Capture", []], + ["Sand Capture", []], + ["Metal Capture", []] ], "mandatory_connections": [ ["To Registry", "Registry"], @@ -109,13 +118,13 @@ ["To Tar River", "Tar River"], ["To Tar River From Lobby", "Tar River"], ["To Burial From Egypt", "Burial"], - ["To Burial From Tiki", "Burial"], + ["To Burial From Shaman", "Burial"], ["To Blue Maze From Three Floor Elevator", "Blue Maze"], ["To Blue Maze From Fortune Teller", "Blue Maze"], ["To Blue Maze From Inventions", "Blue Maze"], ["To Blue Maze From Egypt", "Blue Maze"], - ["To Tiki From Burial", "Tiki"], - ["To Tiki From Gods Room", "Tiki"], + ["To Shaman From Burial", "Shaman"], + ["To Shaman From Gods Room", "Shaman"], ["To Gods Room", "Gods Room" ], ["To Gods Room From Anansi", "Gods Room"], ["To Anansi From Gods Room", "Anansi"], @@ -140,6 +149,29 @@ ["To Puzzle Room Marbles From Skull Dial Bridge", "Puzzle Room Marbles"], ["To Skull Dial Bridge From Puzzle Room Marbles", "Skull Dial Bridge"], ["To Skull Dial Bridge From Slide Room", "Skull Dial Bridge"], - ["To Slide Room", "Slide Room"] + ["To Slide Room", "Slide Room"], + ["To Wax Capture From Library", "Wax Capture"], + ["To Wax Capture From Shaman", "Wax Capture"], + ["To Wax Capture From Anansi", "Wax Capture"], + ["To Water Capture From Lobby", "Water Capture"], + ["To Water Capture From Janitor Closet", "Water Capture"], + ["To Ash Capture From Office", "Ash Capture"], + ["To Ash Capture From Burial", "Ash Capture"], + ["To Oil Capture From Prehistoric", "Oil Capture"], + ["To Oil Capture From Tar River", "Oil Capture"], + ["To Cloth Capture From Egypt", "Cloth Capture"], + ["To Cloth Capture From Burial", "Cloth Capture"], + ["To Cloth Capture From Janitor Closet", "Cloth Capture"], + ["To Wood Capture From Workshop", "Wood Capture"], + ["To Wood Capture From Gods Room", "Wood Capture"], + ["To Wood Capture From Anansi", "Wood Capture"], + ["To Wood Capture From Blue Maze", "Wood Capture"], + ["To Crystal Capture From Lobby", "Crystal Capture"], + ["To Crystal Capture From Ocean", "Crystal Capture"], + ["To Sand Capture From Greenhouse", "Sand Capture"], + ["To Sand Capture From Ocean", "Sand Capture"], + ["To Metal Capture From Bedroom", "Metal Capture"], + ["To Metal Capture From Projector Room", "Metal Capture"], + ["To Metal Capture From Prehistoric", "Metal Capture"] ] -} \ No newline at end of file +} diff --git a/worlds/shivers/docs/en_Shivers.md b/worlds/shivers/docs/en_Shivers.md index 51730057b034..2c56152a7a0c 100644 --- a/worlds/shivers/docs/en_Shivers.md +++ b/worlds/shivers/docs/en_Shivers.md @@ -1,8 +1,8 @@ # Shivers -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a configuration file. ## What does randomization do to this game? @@ -12,8 +12,8 @@ these are randomized. Crawling has been added and is required to use any crawl s ## What is considered a location check in Shivers? -1. All puzzle solves are location checks excluding elevator puzzles. -2. All Ixupi captures are location checks excluding Lightning. +1. All puzzle solves are location checks. +2. All Ixupi captures are location checks. 3. Puzzle hints/solutions are location checks. For example, looking at the Atlantis map. 4. Optionally information plaques are location checks. @@ -23,9 +23,9 @@ If the player receives a key then the corresponding door will be unlocked. If th ## What is the victory condition? -Victory is achieved when the player captures Lightning in the generator room. +Victory is achieved when the player has captured the required number Ixupi set in their options. ## Encountered a bug? -Please contact GodlFire on Discord for bugs related to Shivers world generation.\ +Please contact GodlFire on Discord for bugs related to Shivers world generation.
    Please contact GodlFire or mouse on Discord for bugs related to the Shivers Randomizer. diff --git a/worlds/shivers/docs/setup_en.md b/worlds/shivers/docs/setup_en.md index ee33bb70408e..c53edcdf2b57 100644 --- a/worlds/shivers/docs/setup_en.md +++ b/worlds/shivers/docs/setup_en.md @@ -5,7 +5,7 @@ - [Shivers (GOG version)](https://www.gog.com/en/game/shivers) or original disc - [ScummVM](https://www.scummvm.org/downloads/) version 2.7.0 or later -- [Shivers Randomizer](https://www.speedrun.com/shivers/resources) +- [Shivers Randomizer](https://github.com/GodlFire/Shivers-Randomizer-CSharp/releases/latest) Latest release version ## Setup ScummVM for Shivers @@ -33,8 +33,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Shivers Player Settings Page](/games/Shivers/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Shivers Player Options Page](/games/Shivers/player-options) ### Verifying your config file diff --git a/worlds/shorthike/Items.py b/worlds/shorthike/Items.py new file mode 100644 index 000000000000..7a5a81db9be6 --- /dev/null +++ b/worlds/shorthike/Items.py @@ -0,0 +1,62 @@ +from BaseClasses import ItemClassification +from typing import TypedDict, Dict, List, Set + +class ItemDict(TypedDict): + name: str + id: int + count: int + classification: ItemClassification + +base_id = 82000 + +item_table: List[ItemDict] = [ + {"name": "Stick", "id": base_id + 1, "count": 0, "classification": ItemClassification.progression_skip_balancing}, + {"name": "Seashell", "id": base_id + 2, "count": 23, "classification": ItemClassification.progression_skip_balancing}, + {"name": "Golden Feather", "id": base_id + 3, "count": 0, "classification": ItemClassification.progression}, + {"name": "Silver Feather", "id": base_id + 4, "count": 0, "classification": ItemClassification.useful}, + {"name": "Bucket", "id": base_id + 5, "count": 0, "classification": ItemClassification.progression}, + {"name": "Bait", "id": base_id + 6, "count": 2, "classification": ItemClassification.filler}, + {"name": "Progressive Fishing Rod", "id": base_id + 7, "count": 2, "classification": ItemClassification.progression}, + {"name": "Shovel", "id": base_id + 8, "count": 1, "classification": ItemClassification.progression}, + {"name": "Toy Shovel", "id": base_id + 9, "count": 0, "classification": ItemClassification.progression_skip_balancing}, + {"name": "Compass", "id": base_id + 10, "count": 1, "classification": ItemClassification.useful}, + {"name": "Medal", "id": base_id + 11, "count": 3, "classification": ItemClassification.filler}, + {"name": "Shell Necklace", "id": base_id + 12, "count": 1, "classification": ItemClassification.progression}, + {"name": "Wristwatch", "id": base_id + 13, "count": 1, "classification": ItemClassification.progression}, + {"name": "Motorboat Key", "id": base_id + 14, "count": 1, "classification": ItemClassification.progression}, + {"name": "Pickaxe", "id": base_id + 15, "count": 3, "classification": ItemClassification.useful}, + {"name": "Fishing Journal", "id": base_id + 16, "count": 1, "classification": ItemClassification.useful}, + {"name": "A Stormy View Map", "id": base_id + 17, "count": 1, "classification": ItemClassification.filler}, + {"name": "The King Map", "id": base_id + 18, "count": 1, "classification": ItemClassification.filler}, + {"name": "The Treasure of Sid Beach Map", "id": base_id + 19, "count": 1, "classification": ItemClassification.filler}, + {"name": "In Her Shadow Map", "id": base_id + 20, "count": 1, "classification": ItemClassification.filler}, + {"name": "Sunhat", "id": base_id + 21, "count": 1, "classification": ItemClassification.filler}, + {"name": "Baseball Cap", "id": base_id + 22, "count": 1, "classification": ItemClassification.filler}, + {"name": "Provincial Park Hat", "id": base_id + 23, "count": 1, "classification": ItemClassification.filler}, + {"name": "Headband", "id": base_id + 24, "count": 1, "classification": ItemClassification.progression}, + {"name": "Running Shoes", "id": base_id + 25, "count": 1, "classification": ItemClassification.useful}, + {"name": "Camping Permit", "id": base_id + 26, "count": 1, "classification": ItemClassification.progression}, + {"name": "Walkie Talkie", "id": base_id + 27, "count": 0, "classification": ItemClassification.useful}, + + # Not in the item pool for now + #{"name": "Boating Manual", "id": base_id + ~, "count": 1, "classification": ItemClassification.filler}, + + # Different Coin Amounts (Fillers) + {"name": "7 Coins", "id": base_id + 28, "count": 3, "classification": ItemClassification.filler}, + {"name": "15 Coins", "id": base_id + 29, "count": 1, "classification": ItemClassification.filler}, + {"name": "18 Coins", "id": base_id + 30, "count": 1, "classification": ItemClassification.filler}, + {"name": "21 Coins", "id": base_id + 31, "count": 2, "classification": ItemClassification.filler}, + {"name": "25 Coins", "id": base_id + 32, "count": 7, "classification": ItemClassification.filler}, + {"name": "27 Coins", "id": base_id + 33, "count": 1, "classification": ItemClassification.filler}, + {"name": "32 Coins", "id": base_id + 34, "count": 1, "classification": ItemClassification.useful}, + {"name": "33 Coins", "id": base_id + 35, "count": 6, "classification": ItemClassification.useful}, + {"name": "50 Coins", "id": base_id + 36, "count": 1, "classification": ItemClassification.useful}, + + # Filler item determined by settings + {"name": "13 Coins", "id": base_id + 37, "count": 0, "classification": ItemClassification.filler}, +] + +group_table: Dict[str, Set[str]] = { + "Coins": {"7 Coins", "13 Coins", "15 Coins", "18 Coins", "21 Coins", "25 Coins", "27 Coins", "32 Coins", "33 Coins", "50 Coins"}, + "Maps": {"A Stormy View Map", "The King Map", "The Treasure of Sid Beach Map", "In Her Shadow Map"}, +} diff --git a/worlds/shorthike/Locations.py b/worlds/shorthike/Locations.py new file mode 100644 index 000000000000..657035a03011 --- /dev/null +++ b/worlds/shorthike/Locations.py @@ -0,0 +1,709 @@ +from typing import List, TypedDict + +class LocationInfo(TypedDict): + name: str + id: int + inGameId: str + needsShovel: bool + purchase: int + minGoldenFeathers: int + minGoldenFeathersEasy: int + minGoldenFeathersBucket: int + +base_id = 83000 + +location_table: List[LocationInfo] = [ + # Original Seashell Locations + {"name": "Start Beach Seashell", + "id": base_id + 1, + "inGameId": "PickUps.3", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beach Hut Seashell", + "id": base_id + 2, + "inGameId": "PickUps.2", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beach Umbrella Seashell", + "id": base_id + 3, + "inGameId": "PickUps.8", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Mound Seashell", + "id": base_id + 4, + "inGameId": "PickUps.12", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Seashell", + "id": base_id + 5, + "inGameId": "PickUps.11", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Beach Seashell", + "id": base_id + 6, + "inGameId": "PickUps.18", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Rock Seashell", + "id": base_id + 7, + "inGameId": "PickUps.17", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Beach Seashell", + "id": base_id + 8, + "inGameId": "PickUps.19", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "West River Seashell", + "id": base_id + 9, + "inGameId": "PickUps.10", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "West Riverbank Seashell", + "id": base_id + 10, + "inGameId": "PickUps.4", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Stone Tower Riverbank Seashell", + "id": base_id + 11, + "inGameId": "PickUps.23", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Beach Seashell", + "id": base_id + 12, + "inGameId": "PickUps.6", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "North Coast Seashell", + "id": base_id + 13, + "inGameId": "PickUps.7", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Boat Cliff Seashell", + "id": base_id + 14, + "inGameId": "PickUps.14", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Boat Isle Mound Seashell", + "id": base_id + 15, + "inGameId": "PickUps.22", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "East Coast Seashell", + "id": base_id + 16, + "inGameId": "PickUps.21", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "House North Beach Seashell", + "id": base_id + 17, + "inGameId": "PickUps.16", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Airstream Island North Seashell", + "id": base_id + 18, + "inGameId": "PickUps.13", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Airstream Island South Seashell", + "id": base_id + 19, + "inGameId": "PickUps.15", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Secret Island Beach Seashell", + "id": base_id + 20, + "inGameId": "PickUps.1", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Seashell", + "id": base_id + 126, + "inGameId": "PickUps.20", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path Seashell", + "id": base_id + 127, + "inGameId": "PickUps.9", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Visitor's Center Shop + {"name": "Visitor's Center Shop Golden Feather 1", + "id": base_id + 21, + "inGameId": "CampRangerNPC[0]", + "needsShovel": False, "purchase": 40, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Shop Golden Feather 2", + "id": base_id + 22, + "inGameId": "CampRangerNPC[1]", + "needsShovel": False, "purchase": 40, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Shop Hat", + "id": base_id + 23, + "inGameId": "CampRangerNPC[9]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Tough Bird Salesman + {"name": "Tough Bird Salesman Golden Feather 1", + "id": base_id + 24, + "inGameId": "ToughBirdNPC (1)[0]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman Golden Feather 2", + "id": base_id + 25, + "inGameId": "ToughBirdNPC (1)[1]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman Golden Feather 3", + "id": base_id + 26, + "inGameId": "ToughBirdNPC (1)[2]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman Golden Feather 4", + "id": base_id + 27, + "inGameId": "ToughBirdNPC (1)[3]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman (400 Coins)", + "id": base_id + 28, + "inGameId": "ToughBirdNPC (1)[9]", + "needsShovel": False, "purchase": 400, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Beachstickball + {"name": "Beachstickball (10 Hits)", + "id": base_id + 29, + "inGameId": "VolleyballOpponent[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beachstickball (20 Hits)", + "id": base_id + 30, + "inGameId": "VolleyballOpponent[1]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beachstickball (30 Hits)", + "id": base_id + 31, + "inGameId": "VolleyballOpponent[2]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Misc Item Locations + {"name": "Shovel Kid Trade", + "id": base_id + 32, + "inGameId": "Frog_StandingNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Compass Guy", + "id": base_id + 33, + "inGameId": "Fox_WalkingNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Hawk Peak Bucket Rock", + "id": base_id + 34, + "inGameId": "Tools.23", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands Bucket Rock", + "id": base_id + 35, + "inGameId": "Tools.42", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Bill the Walrus Fisherman", + "id": base_id + 36, + "inGameId": "SittingNPC (1)[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Catch 3 Fish Reward", + "id": base_id + 37, + "inGameId": "FishBuyer[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Catch All Fish Reward", + "id": base_id + 38, + "inGameId": "FishBuyer[1]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 7, "minGoldenFeathersEasy": 9, "minGoldenFeathersBucket": 7}, + {"name": "Permit Guy Bribe", + "id": base_id + 39, + "inGameId": "CamperNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Catch Fish with Permit", + "id": base_id + 129, + "inGameId": "Player[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Return Camping Permit", + "id": base_id + 130, + "inGameId": "CamperNPC[1]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Original Pickaxe Locations + {"name": "Blocked Mine Pickaxe 1", + "id": base_id + 40, + "inGameId": "Tools.31", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Blocked Mine Pickaxe 2", + "id": base_id + 41, + "inGameId": "Tools.32", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Blocked Mine Pickaxe 3", + "id": base_id + 42, + "inGameId": "Tools.33", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Toy Shovel Locations + {"name": "Blackwood Trail Lookout Toy Shovel", + "id": base_id + 43, + "inGameId": "PickUps.27", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Beach Toy Shovel", + "id": base_id + 44, + "inGameId": "PickUps.30", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Beach Toy Shovel", + "id": base_id + 45, + "inGameId": "PickUps.29", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Blackwood Trail Rock Toy Shovel", + "id": base_id + 46, + "inGameId": "PickUps.26", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Beach Hut Cliff Toy Shovel", + "id": base_id + 128, + "inGameId": "PickUps.28", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Stick Locations + {"name": "Secret Island Beach Trail Stick", + "id": base_id + 47, + "inGameId": "PickUps.25", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Below Lighthouse Walkway Stick", + "id": base_id + 48, + "inGameId": "Tools.3", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Beach Hut Rocky Pool Sand Stick", + "id": base_id + 49, + "inGameId": "Tools.0", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Cliff Overlooking West River Waterfall Stick", + "id": base_id + 50, + "inGameId": "Tools.2", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 0}, + {"name": "Trail to Tough Bird Salesman Stick", + "id": base_id + 51, + "inGameId": "Tools.8", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Beach Stick", + "id": base_id + 52, + "inGameId": "Tools.4", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beachstickball Court Stick", + "id": base_id + 53, + "inGameId": "VolleyballMinigame.4", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Stick Under Sid Beach Umbrella", + "id": base_id + 54, + "inGameId": "Tools.1", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Boating + {"name": "Boat Rental", + "id": base_id + 55, + "inGameId": "DadDeer[0]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Boat Challenge Reward", + "id": base_id + 56, + "inGameId": "DeerKidBoat[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Not a location for now, corresponding with the Boating Manual + # {"name": "Receive Boating Manual", + # "id": base_id + 133, + # "inGameId": "DadDeer[1]", + # "needsShovel": False, "purchase": 0, + # "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Map Locations + {"name": "Outlook Point Dog Gift", + "id": base_id + 57, + "inGameId": "Dog_WalkingNPC_BlueEyed[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Original Clothes Locations + {"name": "Collect 15 Seashells", + "id": base_id + 58, + "inGameId": "LittleKidNPCVariant (1)[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Return to Shell Kid", + "id": base_id + 132, + "inGameId": "LittleKidNPCVariant (1)[1]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Taylor the Turtle Headband Gift", + "id": base_id + 59, + "inGameId": "Turtle_WalkingNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Sue the Rabbit Shoes Reward", + "id": base_id + 60, + "inGameId": "Bunny_WalkingNPC (1)[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Purchase Sunhat", + "id": base_id + 61, + "inGameId": "SittingNPC[0]", + "needsShovel": False, "purchase": 100, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Golden Feather Locations + {"name": "Blackwood Forest Golden Feather", + "id": base_id + 62, + "inGameId": "Feathers.3", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Ranger May Shell Necklace Golden Feather", + "id": base_id + 63, + "inGameId": "AuntMayNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sand Castle Golden Feather", + "id": base_id + 64, + "inGameId": "SandProvince.3", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Artist Golden Feather", + "id": base_id + 65, + "inGameId": "StandingNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Visitor Camp Rock Golden Feather", + "id": base_id + 66, + "inGameId": "Feathers.8", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Outlook Cliff Golden Feather", + "id": base_id + 67, + "inGameId": "Feathers.2", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Cliff Golden Feather", + "id": base_id + 68, + "inGameId": "Feathers.7", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 0}, + + # Original Silver Feather Locations + {"name": "Secret Island Peak", + "id": base_id + 69, + "inGameId": "PickUps.24", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 5, "minGoldenFeathersEasy": 7, "minGoldenFeathersBucket": 7}, + {"name": "Wristwatch Trade", + "id": base_id + 70, + "inGameId": "Goat_StandingNPC[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Golden Chests + {"name": "Lighthouse Golden Chest", + "id": base_id + 71, + "inGameId": "Feathers.0", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 2, "minGoldenFeathersEasy": 3, "minGoldenFeathersBucket": 0}, + {"name": "Outlook Golden Chest", + "id": base_id + 72, + "inGameId": "Feathers.6", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Stone Tower Golden Chest", + "id": base_id + 73, + "inGameId": "Feathers.5", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Cliff Golden Chest", + "id": base_id + 74, + "inGameId": "Feathers.4", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 10, "minGoldenFeathersBucket": 10}, + + # Chests + {"name": "Blackwood Cliff Chest", + "id": base_id + 75, + "inGameId": "Coins.22", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "White Coast Trail Chest", + "id": base_id + 76, + "inGameId": "Coins.6", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Chest", + "id": base_id + 77, + "inGameId": "Coins.7", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Buried Treasure Chest", + "id": base_id + 78, + "inGameId": "Coins.46", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Cliff Chest", + "id": base_id + 79, + "inGameId": "Coins.9", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Buried Chest", + "id": base_id + 80, + "inGameId": "Coins.94", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Hidden Chest", + "id": base_id + 81, + "inGameId": "Coins.42", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Chest", + "id": base_id + 82, + "inGameId": "Coins.10", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 2}, + {"name": "Caravan Cliff Chest", + "id": base_id + 83, + "inGameId": "Coins.12", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Caravan Arch Chest", + "id": base_id + 84, + "inGameId": "Coins.11", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "King Buried Treasure Chest", + "id": base_id + 85, + "inGameId": "Coins.41", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path Buried Chest", + "id": base_id + 86, + "inGameId": "Coins.48", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path West Chest", + "id": base_id + 87, + "inGameId": "Coins.33", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path East Chest", + "id": base_id + 88, + "inGameId": "Coins.62", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "West Waterfall Chest", + "id": base_id + 89, + "inGameId": "Coins.20", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Stone Tower West Cliff Chest", + "id": base_id + 90, + "inGameId": "PickUps.0", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Bucket Path Chest", + "id": base_id + 91, + "inGameId": "Coins.50", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Bucket Cliff Chest", + "id": base_id + 92, + "inGameId": "Coins.49", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "In Her Shadow Buried Treasure Chest", + "id": base_id + 93, + "inGameId": "Feathers.9", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Buried Chest", + "id": base_id + 94, + "inGameId": "Coins.86", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Chest", + "id": base_id + 95, + "inGameId": "Coins.64", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "House North Beach Chest", + "id": base_id + 96, + "inGameId": "Coins.65", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "East Coast Chest", + "id": base_id + 97, + "inGameId": "Coins.98", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Fisherman's Boat Chest 1", + "id": base_id + 99, + "inGameId": "Boat.0", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Fisherman's Boat Chest 2", + "id": base_id + 100, + "inGameId": "Boat.7", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Airstream Island Chest", + "id": base_id + 101, + "inGameId": "Coins.31", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "West River Waterfall Head Chest", + "id": base_id + 102, + "inGameId": "Coins.34", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Old Building Chest", + "id": base_id + 103, + "inGameId": "Coins.104", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Old Building West Chest", + "id": base_id + 104, + "inGameId": "Coins.109", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Old Building East Chest", + "id": base_id + 105, + "inGameId": "Coins.8", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Hawk Peak West Chest", + "id": base_id + 106, + "inGameId": "Coins.21", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "Hawk Peak East Buried Chest", + "id": base_id + 107, + "inGameId": "Coins.76", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "Hawk Peak Northeast Chest", + "id": base_id + 108, + "inGameId": "Coins.79", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "Northern East Coast Chest", + "id": base_id + 109, + "inGameId": "Coins.45", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 0}, + {"name": "North Coast Chest", + "id": base_id + 110, + "inGameId": "Coins.28", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Coast Buried Chest", + "id": base_id + 111, + "inGameId": "Coins.47", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Small South Island Buried Chest", + "id": base_id + 112, + "inGameId": "Coins.87", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Secret Island Bottom Chest", + "id": base_id + 113, + "inGameId": "Coins.88", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Secret Island Treehouse Chest", + "id": base_id + 114, + "inGameId": "Coins.89", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 1}, + {"name": "Sunhat Island Buried Chest", + "id": base_id + 115, + "inGameId": "Coins.112", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands South Buried Chest", + "id": base_id + 116, + "inGameId": "Coins.119", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands West Chest", + "id": base_id + 117, + "inGameId": "Coins.121", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands North Buried Chest", + "id": base_id + 118, + "inGameId": "Coins.117", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands East Chest", + "id": base_id + 119, + "inGameId": "Coins.120", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands South Hidden Chest", + "id": base_id + 120, + "inGameId": "Coins.124", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "A Stormy View Buried Treasure Chest", + "id": base_id + 121, + "inGameId": "Coins.113", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands Ruins Buried Chest", + "id": base_id + 122, + "inGameId": "Coins.118", + "needsShovel": True, "purchase": 0, + "minGoldenFeathers": 2, "minGoldenFeathersEasy": 4, "minGoldenFeathersBucket": 0}, + + # Race Rewards + {"name": "Lighthouse Race Reward", + "id": base_id + 123, + "inGameId": "RaceOpponent[0]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 2, "minGoldenFeathersEasy": 3, "minGoldenFeathersBucket": 1}, + {"name": "Old Building Race Reward", + "id": base_id + 124, + "inGameId": "RaceOpponent[1]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 0}, + {"name": "Hawk Peak Race Reward", + "id": base_id + 125, + "inGameId": "RaceOpponent[2]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 7, "minGoldenFeathersEasy": 9, "minGoldenFeathersBucket": 7}, + {"name": "Lose Race Gift", + "id": base_id + 131, + "inGameId": "RaceOpponent[9]", + "needsShovel": False, "purchase": 0, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, +] diff --git a/worlds/shorthike/Options.py b/worlds/shorthike/Options.py new file mode 100644 index 000000000000..3d9bf81a3cf8 --- /dev/null +++ b/worlds/shorthike/Options.py @@ -0,0 +1,170 @@ +from dataclasses import dataclass +from Options import Choice, OptionGroup, PerGameCommonOptions, Range, StartInventoryPool, Toggle, DefaultOnToggle + +class Goal(Choice): + """Choose the end goal. + Nap: Complete the climb to the top of Hawk Peak and take a nap + Photo: Get your picture taken at the top of Hawk Peak + Races: Complete all three races with Avery + Help Everyone: Travel around Hawk Peak and help every character with their troubles + Fishmonger: Catch one of every fish from around Hawk Peak""" + display_name = "Goal" + option_nap = 0 + option_photo = 1 + option_races = 2 + option_help_everyone = 3 + option_fishmonger = 4 + default = 3 + +class CoinsInShops(Toggle): + """When enabled, the randomizer can place coins into locations that are purchased, such as shops.""" + display_name = "Coins in Purchaseable Locations" + default = False + +class GoldenFeathers(Range): + """ + Number of Golden Feathers in the item pool. + (Note that for the Photo and Help Everyone goals, a minimum of 12 Golden Feathers is enforced) + """ + display_name = "Golden Feathers" + range_start = 0 + range_end = 20 + default = 20 + +class SilverFeathers(Range): + """Number of Silver Feathers in the item pool.""" + display_name = "Silver Feathers" + range_start = 0 + range_end = 20 + default = 2 + +class Buckets(Range): + """Number of Buckets in the item pool.""" + display_name = "Buckets" + range_start = 0 + range_end = 2 + default = 2 + +class Sticks(Range): + """Number of Sticks in the item pool.""" + display_name = "Sticks" + range_start = 1 + range_end = 8 + default = 8 + +class ToyShovels(Range): + """Number of Toy Shovels in the item pool.""" + display_name = "Toy Shovels" + range_start = 1 + range_end = 5 + default = 5 + +class GoldenFeatherProgression(Choice): + """Determines which locations are considered in logic based on the required amount of golden feathers to reach them. + Easy: Locations will be considered inaccessible until the player has enough golden feathers to easily reach them. A minimum of 10 golden feathers is recommended for this setting. + Normal: Locations will be considered inaccessible until the player has the minimum possible number of golden feathers to reach them. A minimum of 7 golden feathers is recommended for this setting. + Hard: Removes the requirement of golden feathers for progression entirely and glitches may need to be used to progress""" + display_name = "Golden Feather Progression" + option_easy = 0 + option_normal = 1 + option_hard = 2 + default = 1 + +class CostMultiplier(Range): + """The percentage that all item shop costs will be of the vanilla values.""" + display_name = "Shop Cost Multiplier" + range_start = 25 + range_end = 200 + default = 100 + +class FillerCoinAmount(Choice): + """The number of coins that will be in each filler coin item.""" + display_name = "Coins per Filler Item" + option_7_coins = 0 + option_13_coins = 1 + option_15_coins = 2 + option_18_coins = 3 + option_21_coins = 4 + option_25_coins = 5 + option_27_coins = 6 + option_32_coins = 7 + option_33_coins = 8 + option_50_coins = 9 + default = 1 + +class RandomWalkieTalkie(DefaultOnToggle): + """ + When enabled, the Walkie Talkie item will be placed into the item pool. Otherwise, it will be placed in its vanilla location. + This item usually allows the player to locate Avery around the map or restart a race. + """ + display_name = "Randomize Walkie Talkie" + +class EasierRaces(Toggle): + """When enabled, the Running Shoes will be added as a logical requirement for beating any of the races.""" + display_name = "Easier Races" + +class ShopCheckLogic(Choice): + """Determines which items will be added as logical requirements to making certain purchases in shops.""" + display_name = "Shop Check Logic" + option_nothing = 0 + option_fishing_rod = 1 + option_shovel = 2 + option_fishing_rod_and_shovel = 3 + option_golden_fishing_rod = 4 + option_golden_fishing_rod_and_shovel = 5 + default = 1 + +class MinShopCheckLogic(Choice): + """ + Determines the minimum cost of a shop item that will have the shop check logic applied to it. + If the cost of a shop item is less than this value, no items will be required to access it. + This is based on the vanilla prices of the shop item. The set cost multiplier will not affect this value. + """ + display_name = "Minimum Shop Check Logic Application" + option_40_coins = 0 + option_100_coins = 1 + option_400_coins = 2 + default = 1 + +@dataclass +class ShortHikeOptions(PerGameCommonOptions): + start_inventory_from_pool: StartInventoryPool + goal: Goal + coins_in_shops: CoinsInShops + golden_feathers: GoldenFeathers + silver_feathers: SilverFeathers + buckets: Buckets + sticks: Sticks + toy_shovels: ToyShovels + golden_feather_progression: GoldenFeatherProgression + cost_multiplier: CostMultiplier + filler_coin_amount: FillerCoinAmount + random_walkie_talkie: RandomWalkieTalkie + easier_races: EasierRaces + shop_check_logic: ShopCheckLogic + min_shop_check_logic: MinShopCheckLogic + +shorthike_option_groups = [ + OptionGroup("General Options", [ + Goal, + FillerCoinAmount, + RandomWalkieTalkie + ]), + OptionGroup("Logic Options", [ + GoldenFeatherProgression, + EasierRaces + ]), + OptionGroup("Item Pool Options", [ + GoldenFeathers, + SilverFeathers, + Buckets, + Sticks, + ToyShovels + ]), + OptionGroup("Shop Options", [ + CoinsInShops, + CostMultiplier, + ShopCheckLogic, + MinShopCheckLogic + ]) +] diff --git a/worlds/shorthike/Rules.py b/worlds/shorthike/Rules.py new file mode 100644 index 000000000000..33741c6d80c6 --- /dev/null +++ b/worlds/shorthike/Rules.py @@ -0,0 +1,106 @@ +from worlds.generic.Rules import forbid_items_for_player, add_rule +from .Options import Goal, GoldenFeatherProgression, MinShopCheckLogic, ShopCheckLogic + + +def create_rules(self, location_table): + multiworld = self.multiworld + player = self.player + options = self.options + + # Shovel Rules + for loc in location_table: + if loc["needsShovel"]: + forbid_items_for_player(multiworld.get_location(loc["name"], player), self.item_name_groups['Maps'], player) + add_rule(multiworld.get_location(loc["name"], player), + lambda state: state.has("Shovel", player)) + + # Shop Rules + if loc["purchase"] and not options.coins_in_shops: + forbid_items_for_player(multiworld.get_location(loc["name"], player), self.item_name_groups['Coins'], player) + if loc["purchase"] >= get_min_shop_logic_cost(self) and options.shop_check_logic != ShopCheckLogic.option_nothing: + if options.shop_check_logic in {ShopCheckLogic.option_fishing_rod, ShopCheckLogic.option_fishing_rod_and_shovel}: + add_rule(multiworld.get_location(loc["name"], player), + lambda state: state.has("Progressive Fishing Rod", player)) + if options.shop_check_logic in {ShopCheckLogic.option_golden_fishing_rod, ShopCheckLogic.option_golden_fishing_rod_and_shovel}: + add_rule(multiworld.get_location(loc["name"], player), + lambda state: state.has("Progressive Fishing Rod", player, 2)) + if options.shop_check_logic in {ShopCheckLogic.option_shovel, ShopCheckLogic.option_fishing_rod_and_shovel, ShopCheckLogic.option_golden_fishing_rod_and_shovel}: + add_rule(multiworld.get_location(loc["name"], player), + lambda state: state.has("Shovel", player)) + + # Minimum Feather Rules + if options.golden_feather_progression != GoldenFeatherProgression.option_hard: + min_feathers = get_min_feathers(self, loc["minGoldenFeathers"], loc["minGoldenFeathersEasy"]) + + if options.buckets > 0 and loc["minGoldenFeathersBucket"] < min_feathers: + add_rule(multiworld.get_location(loc["name"], player), + lambda state, loc=loc, min_feathers=min_feathers: state.has("Golden Feather", player, min_feathers) + or (state.has("Bucket", player) and state.has("Golden Feather", player, loc["minGoldenFeathersBucket"]))) + elif min_feathers > 0: + add_rule(multiworld.get_location(loc["name"], player), + lambda state, min_feathers=min_feathers: state.has("Golden Feather", player, min_feathers)) + add_rule(multiworld.get_location("Shovel Kid Trade", player), + lambda state: state.has("Toy Shovel", player)) + add_rule(multiworld.get_location("Sand Castle Golden Feather", player), + lambda state: state.has("Toy Shovel", player)) + + # Fishing Rules + add_rule(multiworld.get_location("Catch 3 Fish Reward", player), + lambda state: state.has("Progressive Fishing Rod", player)) + add_rule(multiworld.get_location("Catch Fish with Permit", player), + lambda state: state.has("Progressive Fishing Rod", player)) + add_rule(multiworld.get_location("Catch All Fish Reward", player), + lambda state: state.has("Progressive Fishing Rod", player, 2)) + + # Misc Rules + add_rule(multiworld.get_location("Return Camping Permit", player), + lambda state: state.has("Camping Permit", player)) + add_rule(multiworld.get_location("Boat Challenge Reward", player), + lambda state: state.has("Motorboat Key", player)) + add_rule(multiworld.get_location("Collect 15 Seashells", player), + lambda state: state.has("Seashell", player, 15)) + add_rule(multiworld.get_location("Wristwatch Trade", player), + lambda state: state.has("Wristwatch", player)) + add_rule(multiworld.get_location("Sue the Rabbit Shoes Reward", player), + lambda state: state.has("Headband", player)) + add_rule(multiworld.get_location("Return to Shell Kid", player), + lambda state: state.has("Shell Necklace", player) and state.has("Seashell", player, 15)) + add_rule(multiworld.get_location("Ranger May Shell Necklace Golden Feather", player), + lambda state: state.has("Shell Necklace", player)) + add_rule(multiworld.get_location("Beachstickball (10 Hits)", player), + lambda state: state.has("Stick", player)) + add_rule(multiworld.get_location("Beachstickball (20 Hits)", player), + lambda state: state.has("Stick", player)) + add_rule(multiworld.get_location("Beachstickball (30 Hits)", player), + lambda state: state.has("Stick", player)) + + # Race Rules + if options.easier_races: + add_rule(multiworld.get_location("Lighthouse Race Reward", player), + lambda state: state.has("Running Shoes", player)) + add_rule(multiworld.get_location("Old Building Race Reward", player), + lambda state: state.has("Running Shoes", player)) + add_rule(multiworld.get_location("Hawk Peak Race Reward", player), + lambda state: state.has("Running Shoes", player)) + +def get_min_feathers(self, min_golden_feathers, min_golden_feathers_easy): + options = self.options + + min_feathers = min_golden_feathers + if options.golden_feather_progression == GoldenFeatherProgression.option_easy: + min_feathers = min_golden_feathers_easy + if min_feathers > options.golden_feathers: + if options.goal not in {Goal.option_help_everyone, Goal.option_photo}: + min_feathers = options.golden_feathers + + return min_feathers + +def get_min_shop_logic_cost(self): + options = self.options + + if options.min_shop_check_logic == MinShopCheckLogic.option_40_coins: + return 40 + elif options.min_shop_check_logic == MinShopCheckLogic.option_100_coins: + return 100 + elif options.min_shop_check_logic == MinShopCheckLogic.option_400_coins: + return 400 diff --git a/worlds/shorthike/__init__.py b/worlds/shorthike/__init__.py new file mode 100644 index 000000000000..299169a40c6b --- /dev/null +++ b/worlds/shorthike/__init__.py @@ -0,0 +1,153 @@ +from typing import ClassVar, Dict, Any, Type +from BaseClasses import ItemClassification, Region, Location, Item, Tutorial +from Options import PerGameCommonOptions +from worlds.AutoWorld import World, WebWorld +from .Items import item_table, group_table, base_id +from .Locations import location_table +from .Rules import create_rules, get_min_feathers +from .Options import ShortHikeOptions, shorthike_option_groups + +class ShortHikeWeb(WebWorld): + theme = "ocean" + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the A Short Hike randomizer connected to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["Chandler"] + )] + option_groups = shorthike_option_groups + +class ShortHikeWorld(World): + """ + A Short Hike is a relaxing adventure set on the islands of Hawk Peak. Fly and climb using Claire's wings and Golden Feathers + to make your way up to the summit. Along the way you'll meet other hikers, discover hidden treasures, + and take in the beautiful world around you. + """ + + game = "A Short Hike" + web = ShortHikeWeb() + + item_name_to_id = {item["name"]: item["id"] for item in item_table} + location_name_to_id = {loc["name"]: loc["id"] for loc in location_table} + location_name_to_game_id = {loc["name"]: loc["inGameId"] for loc in location_table} + + item_name_groups = group_table + + options_dataclass: ClassVar[Type[PerGameCommonOptions]] = ShortHikeOptions + options: ShortHikeOptions + + required_client_version = (0, 4, 4) + + def get_filler_item_name(self) -> str: + return self.options.filler_coin_amount.current_option_name + + def create_item(self, name: str) -> "ShortHikeItem": + item_id: int = self.item_name_to_id[name] + id = item_id - base_id - 1 + + classification = item_table[id]["classification"] + if self.options.easier_races and name == "Running Shoes": + classification = ItemClassification.progression + + return ShortHikeItem(name, classification, item_id, player=self.player) + + def create_items(self) -> None: + itempool = [] + for item in item_table: + count = item["count"] + + if count <= 0: + continue + else: + for i in range(count): + itempool.append(self.create_item(item["name"])) + + feather_count = self.options.golden_feathers + if self.options.goal == 1 or self.options.goal == 3: + if feather_count < 12: + feather_count = 12 + + itempool += [self.create_item("Golden Feather") for _ in range(feather_count)] + itempool += [self.create_item("Silver Feather") for _ in range(self.options.silver_feathers)] + itempool += [self.create_item("Bucket") for _ in range(self.options.buckets)] + itempool += [self.create_item("Stick") for _ in range(self.options.sticks)] + itempool += [self.create_item("Toy Shovel") for _ in range(self.options.toy_shovels)] + + if self.options.random_walkie_talkie: + itempool.append(self.create_item("Walkie Talkie")) + else: + self.multiworld.get_location("Lose Race Gift", self.player).place_locked_item(self.create_item("Walkie Talkie")) + + junk = len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool) + itempool += [self.create_item(self.get_filler_item_name()) for _ in range(junk)] + + self.multiworld.itempool += itempool + + def create_regions(self) -> None: + menu_region = Region("Menu", self.player, self.multiworld) + self.multiworld.regions.append(menu_region) + + main_region = Region("Hawk Peak", self.player, self.multiworld) + + for loc in self.location_name_to_id.keys(): + main_region.locations.append(ShortHikeLocation(self.player, loc, self.location_name_to_id[loc], main_region)) + + self.multiworld.regions.append(main_region) + + menu_region.connect(main_region) + + if self.options.goal == "nap": + # Nap + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, get_min_feathers(self, 7, 9)) + or (state.has("Bucket", self.player) and state.has("Golden Feather", self.player, 7))) + elif self.options.goal == "photo": + # Photo + self.multiworld.completion_condition[self.player] = lambda state: state.has("Golden Feather", self.player, 12) + elif self.options.goal == "races": + # Races + self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Hawk Peak Race Reward", self.player) + elif self.options.goal == "help_everyone": + # Help Everyone + self.multiworld.completion_condition[self.player] = lambda state: (state.can_reach_location("Collect 15 Seashells", self.player) + and state.has("Golden Feather", self.player, 12) + and state.can_reach_location("Tough Bird Salesman (400 Coins)", self.player) + and state.can_reach_location("Ranger May Shell Necklace Golden Feather", self.player) + and state.can_reach_location("Sue the Rabbit Shoes Reward", self.player) + and state.can_reach_location("Wristwatch Trade", self.player) + and state.can_reach_location("Return Camping Permit", self.player) + and state.can_reach_location("Boat Challenge Reward", self.player) + and state.can_reach_location("Shovel Kid Trade", self.player) + and state.can_reach_location("Purchase Sunhat", self.player) + and state.can_reach_location("Artist Golden Feather", self.player)) + elif self.options.goal == "fishmonger": + # Fishmonger + self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Catch All Fish Reward", self.player) + + def set_rules(self): + create_rules(self, location_table) + + def fill_slot_data(self) -> Dict[str, Any]: + options = self.options + + settings = { + "goal": int(options.goal), + "logicLevel": int(options.golden_feather_progression), + "costMultiplier": int(options.cost_multiplier), + "shopCheckLogic": int(options.shop_check_logic), + "minShopCheckLogic": int(options.min_shop_check_logic), + "easierRaces": bool(options.easier_races), + } + + slot_data = { + "settings": settings, + } + + return slot_data + +class ShortHikeItem(Item): + game: str = "A Short Hike" + +class ShortHikeLocation(Location): + game: str = "A Short Hike" diff --git a/worlds/shorthike/docs/en_A Short Hike.md b/worlds/shorthike/docs/en_A Short Hike.md new file mode 100644 index 000000000000..11b5b4b8ca85 --- /dev/null +++ b/worlds/shorthike/docs/en_A Short Hike.md @@ -0,0 +1,29 @@ +# A Short Hike + +## What does randomization do to this game? + +All items that can be obtained from chests, the ground, and NPCs are randomized. + +## What does another world's item look like in A Short Hike? + +All items are replaced with chests that can contain items from other worlds. +Items will appear with the Archipelago logo next to them when obtained. + +## Which characters need to be helped for the Help Everyone goal? + +To achieve the Help Everyone goal, the following characters will need to be helped: +- Pay Tough Bird Salesman's Tuition Fee +- Give Frog a Toy Shovel +- Return the Camper's Camping Permit +- Complete the Deer Kid's Boating Challenge +- Find Sue's Headband +- Clean Up and Purchase the Sunhat from the Deer +- Return the Camper's Wristwatch +- Cheer Up the Artist +- Collect 15 Shells for the Kid +- Give the Shell Necklace to Aunt May +- Help the Fox Climb the Mountain + +## Can I have more than one save at a time? + +You can have up to 3 saves at a time. To switch between them, use the Save Data button in the options menu. diff --git a/worlds/shorthike/docs/setup_en.md b/worlds/shorthike/docs/setup_en.md new file mode 100644 index 000000000000..96e4d8dbbd1d --- /dev/null +++ b/worlds/shorthike/docs/setup_en.md @@ -0,0 +1,27 @@ +# A Short Hike Multiworld Setup Guide + +## Required Software + +- A Short Hike: [Steam](https://store.steampowered.com/app/1055540/A_Short_Hike/) + - The Epic Games Store or itch.io version of A Short Hike will also work. +- A Short Hike Randomizer: [GitHub](https://github.com/BrandenEK/AShortHike.Randomizer) + +## Optional Software + +- [PopTracker](https://github.com/black-sliver/PopTracker/) + - [Chandler's A Short Hike PopTracker Pack](https://github.com/chandler05/shorthike-archipelago-poptracker/releases) + +## Installation + +Open the [Randomizer Repository](https://github.com/BrandenEK/AShortHike.Randomizer) and follow +the installation instructions listed there. + +## Connecting + +A Short Hike will prompt you with the server details when a new game is started or a previous one is continued. +Enter in the Server Address and Port, Name, and Password (optional) in the popup menu that appears and hit connect. + +## Tracking + +Install PopTracker from the link above and place the PopTracker pack into the packs folder. +Connect to Archipelago via the AP button in the top left. diff --git a/worlds/sm/Client.py b/worlds/sm/Client.py index ed3f2d5b3d30..b9c13433aeb7 100644 --- a/worlds/sm/Client.py +++ b/worlds/sm/Client.py @@ -123,7 +123,7 @@ async def game_watcher(self, ctx): location_id = locations_start_id + item_index ctx.locations_checked.add(location_id) - location = ctx.location_names[location_id] + location = ctx.location_names.lookup_in_game(location_id) snes_logger.info( f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}]) @@ -139,7 +139,7 @@ async def game_watcher(self, ctx): if item_out_ptr < len(ctx.items_received): item = ctx.items_received[item_out_ptr] item_id = item.item - items_start_id - if bool(ctx.items_handling & 0b010): + if bool(ctx.items_handling & 0b010) or item.location < 0: # item.location < 0 for !getitem to work location_id = (item.location - locations_start_id) if (item.location >= 0 and item.player == ctx.slot) else 0xFF else: location_id = 0x00 #backward compat @@ -151,9 +151,8 @@ async def game_watcher(self, ctx): snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF])) logging.info('Received %s from %s (%s) (%d/%d in list)' % ( - color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - ctx.location_names[item.location], item_out_ptr, len(ctx.items_received))) + ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received))) await snes_flush_writes(ctx) - diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 3e9015eab766..826b1447793d 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -99,7 +99,6 @@ class SMWorld(World): game: str = "Super Metroid" topology_present = True - data_version = 3 option_definitions = sm_options settings: typing.ClassVar[SMSettings] @@ -358,16 +357,26 @@ def pre_fill(self): def post_fill(self): def get_player_ItemLocation(progression_only: bool): return [ - ItemLocation(copy.copy(ItemManager.Items[ - itemLoc.item.type if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else - 'ArchipelagoItem']), - copy.copy(locationsDict[itemLoc.name] if itemLoc.game == self.game else - locationsDict[first_local_collected_loc.name]), - itemLoc.item.player, - True) - for itemLoc in spheres if itemLoc.item.player == self.player and (not progression_only or itemLoc.item.advancement) - ] - + ItemLocation( + copy.copy( + ItemManager.Items[ + itemLoc.item.type + if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items + else 'ArchipelagoItem' + ] + ), + copy.copy( + locationsDict[itemLoc.name] + if itemLoc.game == self.game + else locationsDict[first_local_collected_loc.name] + ), + itemLoc.item.player, + True + ) + for itemLoc in spheres + if itemLoc.item.player == self.player and (not progression_only or itemLoc.item.advancement) + ] + # Having a sorted itemLocs from collection order is required for escapeTrigger when Tourian is Disabled. # We cant use stage_post_fill for this as its called after worlds' post_fill. # get_spheres could be cached in multiworld? diff --git a/worlds/sm/docs/en_Super Metroid.md b/worlds/sm/docs/en_Super Metroid.md index 5c87e026f634..c8c1d0faabee 100644 --- a/worlds/sm/docs/en_Super Metroid.md +++ b/worlds/sm/docs/en_Super Metroid.md @@ -1,8 +1,8 @@ # Super Metroid -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/sm/docs/multiworld_en.md b/worlds/sm/docs/multiworld_en.md index 0e82be769571..8f30630bc96d 100644 --- a/worlds/sm/docs/multiworld_en.md +++ b/worlds/sm/docs/multiworld_en.md @@ -2,16 +2,18 @@ ## Required Software -- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). - - -- Hardware or software capable of loading and playing SNES ROM files - - An emulator capable of connecting to SNI such as: - - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - - BizHawk from: [TASVideos](https://tasvideos.org/BizHawk) - - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other - compatible hardware +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). +- [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above. +- SNI is not compatible with (Q)Usb2Snes. +- Hardware or software capable of loading and playing SNES ROM files, including: + - An emulator capable of connecting to SNI + ([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases), + [BSNES-plus](https://github.com/black-sliver/bsnes-plus), + [BizHawk](http://tasvideos.org/BizHawk.html), or + [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer) + - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note: + modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system, + but it is not supported.** - Your legally obtained Super Metroid ROM file, probably named `Super Metroid (Japan, USA).sfc` ## Installation Procedures @@ -44,8 +46,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Super Metroid Player Settings Page](/games/Super%20Metroid/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Super Metroid Player Options Page](/games/Super%20Metroid/player-options) ### Verifying your config file @@ -54,8 +56,8 @@ validator page: [YAML Validation page](/check) ## Generating a Single-Player Game -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [Super Metroid Player Settings Page](/games/Super%20Metroid/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [Super Metroid Player Options Page](/games/Super%20Metroid/player-options) 2. You will be presented with a "Seed Info" page. 3. Click the "Create New Room" link. 4. You will be presented with a server page, from which you can download your patch file. @@ -81,6 +83,11 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +#### snes9x-nwa + +1. Click on the Network Menu and check **Enable Emu Network Control** +2. Load your ROM file if it hasn't already been loaded. + ##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. @@ -92,6 +99,12 @@ first time launching, you may be prompted to allow it to communicate through the 6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. +#### BSNES-Plus + +1. Load your ROM file if it hasn't already been loaded. +2. The emulator should automatically connect while SNI is running. + + ##### BizHawk 1. Ensure you have the BSNES core loaded. This is done with the main menubar, under: diff --git a/worlds/sm64ex/Options.py b/worlds/sm64ex/Options.py index d9a877df2b37..60ec4bbe13c2 100644 --- a/worlds/sm64ex/Options.py +++ b/worlds/sm64ex/Options.py @@ -1,5 +1,6 @@ import typing -from Options import Option, DefaultOnToggle, Range, Toggle, DeathLink, Choice +from dataclasses import dataclass +from Options import DefaultOnToggle, Range, Toggle, DeathLink, Choice, PerGameCommonOptions, OptionSet from .Items import action_item_table class EnableCoinStars(DefaultOnToggle): @@ -114,35 +115,37 @@ class StrictMoveRequirements(DefaultOnToggle): if Move Randomization is enabled""" display_name = "Strict Move Requirements" -def getMoveRandomizerOption(action: str): - class MoveRandomizerOption(Toggle): - """Mario is unable to perform this action until a corresponding item is picked up. - This option is incompatible with builds using a 'nomoverando' branch.""" - display_name = f"Randomize {action}" - return MoveRandomizerOption - - -sm64_options: typing.Dict[str, type(Option)] = { - "AreaRandomizer": AreaRandomizer, - "BuddyChecks": BuddyChecks, - "ExclamationBoxes": ExclamationBoxes, - "ProgressiveKeys": ProgressiveKeys, - "EnableCoinStars": EnableCoinStars, - "StrictCapRequirements": StrictCapRequirements, - "StrictCannonRequirements": StrictCannonRequirements, - "StrictMoveRequirements": StrictMoveRequirements, - "AmountOfStars": AmountOfStars, - "FirstBowserStarDoorCost": FirstBowserStarDoorCost, - "BasementStarDoorCost": BasementStarDoorCost, - "SecondFloorStarDoorCost": SecondFloorStarDoorCost, - "MIPS1Cost": MIPS1Cost, - "MIPS2Cost": MIPS2Cost, - "StarsToFinish": StarsToFinish, - "death_link": DeathLink, - "CompletionType": CompletionType, -} - -for action in action_item_table: - # HACK: Disable randomization of double jump - if action == 'Double Jump': continue - sm64_options[f"MoveRandomizer{action.replace(' ','')}"] = getMoveRandomizerOption(action) +class EnableMoveRandomizer(Toggle): + """Mario is unable to perform some actions until a corresponding item is picked up. + This option is incompatible with builds using a 'nomoverando' branch. + Specific actions to randomize can be specified in the YAML.""" + display_name = "Enable Move Randomizer" + +class MoveRandomizerActions(OptionSet): + """Which actions to randomize when Move Randomizer is enabled""" + display_name = "Randomized Moves" + # HACK: Disable randomization for double jump + valid_keys = [action for action in action_item_table if action != 'Double Jump'] + default = valid_keys + +@dataclass +class SM64Options(PerGameCommonOptions): + area_rando: AreaRandomizer + buddy_checks: BuddyChecks + exclamation_boxes: ExclamationBoxes + progressive_keys: ProgressiveKeys + enable_coin_stars: EnableCoinStars + enable_move_rando: EnableMoveRandomizer + move_rando_actions: MoveRandomizerActions + strict_cap_requirements: StrictCapRequirements + strict_cannon_requirements: StrictCannonRequirements + strict_move_requirements: StrictMoveRequirements + amount_of_stars: AmountOfStars + first_bowser_star_door_cost: FirstBowserStarDoorCost + basement_star_door_cost: BasementStarDoorCost + second_floor_star_door_cost: SecondFloorStarDoorCost + mips1_cost: MIPS1Cost + mips2_cost: MIPS2Cost + stars_to_finish: StarsToFinish + death_link: DeathLink + completion_type: CompletionType diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index 8c2d32e401bf..6fc2d74b96dc 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -2,6 +2,7 @@ from enum import Enum from BaseClasses import MultiWorld, Region, Entrance, Location +from .Options import SM64Options from .Locations import SM64Location, location_table, locBoB_table, locWhomp_table, locJRB_table, locCCM_table, \ locBBH_table, \ locHMC_table, locLLL_table, locSSL_table, locDDD_table, locSL_table, \ @@ -36,6 +37,11 @@ class SM64Levels(int, Enum): BOWSER_IN_THE_FIRE_SEA = 191 WING_MARIO_OVER_THE_RAINBOW = 311 + +class SM64Region(Region): + subregions: typing.List[Region] = [] + + # sm64paintings is a dict of entrances, format LEVEL | AREA sm64_level_to_paintings: typing.Dict[SM64Levels, str] = { SM64Levels.BOB_OMB_BATTLEFIELD: "Bob-omb Battlefield", @@ -73,7 +79,7 @@ class SM64Levels(int, Enum): sm64_entrances_to_level = {**sm64_paintings_to_level, **sm64_secrets_to_level } sm64_level_to_entrances = {**sm64_level_to_paintings, **sm64_level_to_secrets } -def create_regions(world: MultiWorld, player: int): +def create_regions(world: MultiWorld, options: SM64Options, player: int): regSS = Region("Menu", player, world, "Castle Area") create_default_locs(regSS, locSS_table) world.regions.append(regSS) @@ -81,35 +87,39 @@ def create_regions(world: MultiWorld, player: int): regBoB = create_region("Bob-omb Battlefield", player, world) create_locs(regBoB, "BoB: Big Bob-Omb on the Summit", "BoB: Footrace with Koopa The Quick", "BoB: Mario Wings to the Sky", "BoB: Behind Chain Chomp's Gate", "BoB: Bob-omb Buddy") - create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins") - if (world.EnableCoinStars[player].value): + bob_island = create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins") + regBoB.subregions = [bob_island] + if options.enable_coin_stars: create_locs(regBoB, "BoB: 100 Coins") regWhomp = create_region("Whomp's Fortress", player, world) create_locs(regWhomp, "WF: Chip Off Whomp's Block", "WF: Shoot into the Wild Blue", "WF: Red Coins on the Floating Isle", "WF: Fall onto the Caged Island", "WF: Blast Away the Wall") - create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy") - if (world.EnableCoinStars[player].value): + wf_tower = create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy") + regWhomp.subregions = [wf_tower] + if options.enable_coin_stars: create_locs(regWhomp, "WF: 100 Coins") regJRB = create_region("Jolly Roger Bay", player, world) create_locs(regJRB, "JRB: Plunder in the Sunken Ship", "JRB: Can the Eel Come Out to Play?", "JRB: Treasure of the Ocean Cave", "JRB: Blast to the Stone Pillar", "JRB: Through the Jet Stream", "JRB: Bob-omb Buddy") jrb_upper = create_subregion(regJRB, 'JRB: Upper', "JRB: Red Coins on the Ship Afloat") - if (world.EnableCoinStars[player].value): + regJRB.subregions = [jrb_upper] + if options.enable_coin_stars: create_locs(jrb_upper, "JRB: 100 Coins") regCCM = create_region("Cool, Cool Mountain", player, world) create_default_locs(regCCM, locCCM_table) - if (world.EnableCoinStars[player].value): + if options.enable_coin_stars: create_locs(regCCM, "CCM: 100 Coins") regBBH = create_region("Big Boo's Haunt", player, world) create_locs(regBBH, "BBH: Go on a Ghost Hunt", "BBH: Ride Big Boo's Merry-Go-Round", "BBH: Secret of the Haunted Books", "BBH: Seek the 8 Red Coins") bbh_third_floor = create_subregion(regBBH, "BBH: Third Floor", "BBH: Eye to Eye in the Secret Room") - create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion") - if (world.EnableCoinStars[player].value): + bbh_roof = create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion") + regBBH.subregions = [bbh_third_floor, bbh_roof] + if options.enable_coin_stars: create_locs(regBBH, "BBH: 100 Coins") regPSS = create_region("The Princess's Secret Slide", player, world) @@ -130,31 +140,34 @@ def create_regions(world: MultiWorld, player: int): create_locs(regHMC, "HMC: Swimming Beast in the Cavern", "HMC: Metal-Head Mario Can Move!", "HMC: Watch for Rolling Rocks", "HMC: Navigating the Toxic Maze","HMC: 1Up Block Past Rolling Rocks") hmc_red_coin_area = create_subregion(regHMC, "HMC: Red Coin Area", "HMC: Elevate for 8 Red Coins") - create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit") - if (world.EnableCoinStars[player].value): + hmc_pit_islands = create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit") + regHMC.subregions = [hmc_red_coin_area, hmc_pit_islands] + if options.enable_coin_stars: create_locs(hmc_red_coin_area, "HMC: 100 Coins") regLLL = create_region("Lethal Lava Land", player, world) create_locs(regLLL, "LLL: Boil the Big Bully", "LLL: Bully the Bullies", "LLL: 8-Coin Puzzle with 15 Pieces", "LLL: Red-Hot Log Rolling") - create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano") - if (world.EnableCoinStars[player].value): + lll_upper_volcano = create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano") + regLLL.subregions = [lll_upper_volcano] + if options.enable_coin_stars: create_locs(regLLL, "LLL: 100 Coins") regSSL = create_region("Shifting Sand Land", player, world) - create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Inside the Ancient Pyramid", + create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Free Flying for 8 Red Coins", "SSL: Bob-omb Buddy", "SSL: 1Up Block Outside Pyramid", "SSL: 1Up Block Pyramid Left Path", "SSL: 1Up Block Pyramid Back") - create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle") - if (world.EnableCoinStars[player].value): + ssl_upper_pyramid = create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Inside the Ancient Pyramid", + "SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle") + regSSL.subregions = [ssl_upper_pyramid] + if options.enable_coin_stars: create_locs(regSSL, "SSL: 100 Coins") regDDD = create_region("Dire, Dire Docks", player, world) create_locs(regDDD, "DDD: Board Bowser's Sub", "DDD: Chests in the Current", "DDD: Through the Jet Stream", - "DDD: The Manta Ray's Reward", "DDD: Collect the Caps...") - ddd_moving_poles = create_subregion(regDDD, "DDD: Moving Poles", "DDD: Pole-Jumping for Red Coins") - if (world.EnableCoinStars[player].value): - create_locs(ddd_moving_poles, "DDD: 100 Coins") + "DDD: The Manta Ray's Reward", "DDD: Collect the Caps...", "DDD: Pole-Jumping for Red Coins") + if options.enable_coin_stars: + create_locs(regDDD, "DDD: 100 Coins") regCotMC = create_region("Cavern of the Metal Cap", player, world) create_default_locs(regCotMC, locCotMC_table) @@ -163,21 +176,23 @@ def create_regions(world: MultiWorld, player: int): create_default_locs(regVCutM, locVCutM_table) regBitFS = create_region("Bowser in the Fire Sea", player, world) - create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys()) + bitfs_upper = create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys()) + regBitFS.subregions = [bitfs_upper] create_region("Second Floor", player, world) regSL = create_region("Snowman's Land", player, world) create_default_locs(regSL, locSL_table) - if (world.EnableCoinStars[player].value): + if options.enable_coin_stars: create_locs(regSL, "SL: 100 Coins") regWDW = create_region("Wet-Dry World", player, world) create_locs(regWDW, "WDW: Express Elevator--Hurry Up!") wdw_top = create_subregion(regWDW, "WDW: Top", "WDW: Shocking Arrow Lifts!", "WDW: Top o' the Town", "WDW: Secrets in the Shallows & Sky", "WDW: Bob-omb Buddy") - create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown") - if (world.EnableCoinStars[player].value): + wdw_downtown = create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown") + regWDW.subregions = [wdw_top, wdw_downtown] + if options.enable_coin_stars: create_locs(wdw_top, "WDW: 100 Coins") regTTM = create_region("Tall, Tall Mountain", player, world) @@ -185,37 +200,41 @@ def create_regions(world: MultiWorld, player: int): "TTM: Bob-omb Buddy", "TTM: 1Up Block on Red Mushroom") ttm_top = create_subregion(ttm_middle, "TTM: Top", "TTM: Scale the Mountain", "TTM: Mystery of the Monkey Cage", "TTM: Mysterious Mountainside", "TTM: Breathtaking View from Bridge") - if (world.EnableCoinStars[player].value): + regTTM.subregions = [ttm_middle, ttm_top] + if options.enable_coin_stars: create_locs(ttm_top, "TTM: 100 Coins") create_region("Tiny-Huge Island (Huge)", player, world) create_region("Tiny-Huge Island (Tiny)", player, world) regTHI = create_region("Tiny-Huge Island", player, world) - create_locs(regTHI, "THI: The Tip Top of the Huge Island", "THI: 1Up Block THI Small near Start") - thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: Pluck the Piranha Flower", "THI: Rematch with Koopa the Quick", + create_locs(regTHI, "THI: 1Up Block THI Small near Start") + thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: The Tip Top of the Huge Island", "THI: Pluck the Piranha Flower", "THI: Rematch with Koopa the Quick", "THI: Five Itty Bitty Secrets", "THI: Wiggler's Red Coins", "THI: Bob-omb Buddy", "THI: 1Up Block THI Large near Start", "THI: 1Up Block Windy Area") thi_large_top = create_subregion(thi_pipes, "THI: Large Top", "THI: Make Wiggler Squirm") - if (world.EnableCoinStars[player].value): + regTHI.subregions = [thi_pipes, thi_large_top] + if options.enable_coin_stars: create_locs(thi_large_top, "THI: 100 Coins") regFloor3 = create_region("Third Floor", player, world) regTTC = create_region("Tick Tock Clock", player, world) create_locs(regTTC, "TTC: Stop Time for Red Coins") - ttc_lower = create_subregion(regTTC, "TTC: Lower", "TTC: Roll into the Cage", "TTC: Get a Hand", "TTC: 1Up Block Midway Up") + ttc_lower = create_subregion(regTTC, "TTC: Lower", "TTC: Roll into the Cage", "TTC: Get a Hand") ttc_upper = create_subregion(ttc_lower, "TTC: Upper", "TTC: Timed Jumps on Moving Bars", "TTC: The Pit and the Pendulums") - ttc_top = create_subregion(ttc_upper, "TTC: Top", "TTC: Stomp on the Thwomp", "TTC: 1Up Block at the Top") - if (world.EnableCoinStars[player].value): + ttc_top = create_subregion(ttc_upper, "TTC: Top", "TTC: 1Up Block Midway Up", "TTC: Stomp on the Thwomp", "TTC: 1Up Block at the Top") + regTTC.subregions = [ttc_lower, ttc_upper, ttc_top] + if options.enable_coin_stars: create_locs(ttc_top, "TTC: 100 Coins") regRR = create_region("Rainbow Ride", player, world) create_locs(regRR, "RR: Swingin' in the Breeze", "RR: Tricky Triangles!", "RR: 1Up Block Top of Red Coin Maze", "RR: 1Up Block Under Fly Guy", "RR: Bob-omb Buddy") rr_maze = create_subregion(regRR, "RR: Maze", "RR: Coins Amassed in a Maze") - create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow") - create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky") - if (world.EnableCoinStars[player].value): + rr_cruiser = create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow") + rr_house = create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky") + regRR.subregions = [rr_maze, rr_cruiser, rr_house] + if options.enable_coin_stars: create_locs(rr_maze, "RR: 100 Coins") regWMotR = create_region("Wing Mario over the Rainbow", player, world) @@ -223,7 +242,8 @@ def create_regions(world: MultiWorld, player: int): regBitS = create_region("Bowser in the Sky", player, world) create_locs(regBitS, "Bowser in the Sky 1Up Block") - create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins") + bits_top = create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins") + regBitS.subregions = [bits_top] def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None): @@ -232,14 +252,14 @@ def connect_regions(world: MultiWorld, player: int, source: str, target: str, ru sourceRegion.connect(targetRegion, rule=rule) -def create_region(name: str, player: int, world: MultiWorld) -> Region: - region = Region(name, player, world) +def create_region(name: str, player: int, world: MultiWorld) -> SM64Region: + region = SM64Region(name, player, world) world.regions.append(region) return region -def create_subregion(source_region: Region, name: str, *locs: str) -> Region: - region = Region(name, source_region.player, source_region.multiworld) +def create_subregion(source_region: Region, name: str, *locs: str) -> SM64Region: + region = SM64Region(name, source_region.player, source_region.multiworld) connection = Entrance(source_region.player, name, source_region) source_region.exits.append(connection) connection.connect(region) diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index f2b8e0bcdf2d..9add8d9b2932 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -3,6 +3,7 @@ from BaseClasses import MultiWorld from ..generic.Rules import add_rule, set_rule from .Locations import location_table +from .Options import SM64Options from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level,\ sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances from .Items import action_item_table @@ -24,7 +25,7 @@ def fix_reg(entrance_map: Dict[SM64Levels, str], entrance: SM64Levels, invalid_r swapdict[entrance], swapdict[rand_entrance] = rand_region, old_dest swapdict.pop(entrance) -def set_rules(world, player: int, area_connections: dict, star_costs: dict, move_rando_bitvec: int): +def set_rules(world, options: SM64Options, player: int, area_connections: dict, star_costs: dict, move_rando_bitvec: int): randomized_level_to_paintings = sm64_level_to_paintings.copy() randomized_level_to_secrets = sm64_level_to_secrets.copy() valid_move_randomizer_start_courses = [ @@ -32,19 +33,19 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move "Big Boo's Haunt", "Lethal Lava Land", "Shifting Sand Land", "Dire, Dire Docks", "Snowman's Land" ] # Excluding WF, HMC, WDW, TTM, THI, TTC, and RR - if world.AreaRandomizer[player].value >= 1: # Some randomization is happening, randomize Courses + if options.area_rando >= 1: # Some randomization is happening, randomize Courses randomized_level_to_paintings = shuffle_dict_keys(world,sm64_level_to_paintings) # If not shuffling later, ensure a valid start course on move randomizer - if world.AreaRandomizer[player].value < 3 and move_rando_bitvec > 0: + if options.area_rando < 3 and move_rando_bitvec > 0: swapdict = randomized_level_to_paintings.copy() invalid_start_courses = {course for course in randomized_level_to_paintings.values() if course not in valid_move_randomizer_start_courses} fix_reg(randomized_level_to_paintings, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, world) fix_reg(randomized_level_to_paintings, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, world) - if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well + if options.area_rando == 2: # Randomize Secrets as well randomized_level_to_secrets = shuffle_dict_keys(world,sm64_level_to_secrets) randomized_entrances = {**randomized_level_to_paintings, **randomized_level_to_secrets} - if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool + if options.area_rando == 3: # Randomize Courses and Secrets in one pool randomized_entrances = shuffle_dict_keys(world, randomized_entrances) # Guarantee first entrance is a course swapdict = randomized_entrances.copy() @@ -67,7 +68,7 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move area_connections.update({int(entrance_lvl): int(sm64_entrances_to_level[destination]) for (entrance_lvl,destination) in randomized_entrances.items()}) randomized_entrances_s = {sm64_level_to_entrances[entrance_lvl]: destination for (entrance_lvl,destination) in randomized_entrances.items()} - rf = RuleFactory(world, player, move_rando_bitvec) + rf = RuleFactory(world, options, player, move_rando_bitvec) connect_regions(world, player, "Menu", randomized_entrances_s["Bob-omb Battlefield"]) connect_regions(world, player, "Menu", randomized_entrances_s["Whomp's Fortress"], lambda state: state.has("Power Star", player, 1)) @@ -107,9 +108,9 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, star_costs["SecondFloorDoorCost"])) - connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"]) - connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"]) - connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"]) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"], rf.build_rule("LG/TJ/SF/BF/WK")) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"], rf.build_rule("TJ/SF/BF")) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"], rf.build_rule("TJ/SF/BF")) connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, star_costs["StarsToFinish"])) # Course Rules @@ -118,14 +119,14 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move rf.assign_rule("BoB: Mario Wings to the Sky", "CANN & WC | CAPLESS & CANN") rf.assign_rule("BoB: Behind Chain Chomp's Gate", "GP | MOVELESS") # Whomp's Fortress - rf.assign_rule("WF: Tower", "{{WF: Chip Off Whomp's Block}}") + rf.assign_rule("WF: Tower", "GP") rf.assign_rule("WF: Chip Off Whomp's Block", "GP") rf.assign_rule("WF: Shoot into the Wild Blue", "WK & TJ/SF | CANN") rf.assign_rule("WF: Fall onto the Caged Island", "CL & {WF: Tower} | MOVELESS & TJ | MOVELESS & LJ | MOVELESS & CANN") rf.assign_rule("WF: Blast Away the Wall", "CANN | CANNLESS & LG") # Jolly Roger Bay rf.assign_rule("JRB: Upper", "TJ/BF/SF/WK | MOVELESS & LG") - rf.assign_rule("JRB: Red Coins on the Ship Afloat", "CL/CANN/TJ/BF/WK") + rf.assign_rule("JRB: Red Coins on the Ship Afloat", "CL/CANN/TJ | MOVELESS & BF/WK") rf.assign_rule("JRB: Blast to the Stone Pillar", "CANN+CL | CANNLESS & MOVELESS | CANN & MOVELESS") rf.assign_rule("JRB: Through the Jet Stream", "MC | CAPLESS") # Cool, Cool Mountain @@ -146,9 +147,10 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move rf.assign_rule("LLL: Upper Volcano", "CL") # Shifting Sand Land rf.assign_rule("SSL: Upper Pyramid", "CL & TJ/BF/SF/LG | MOVELESS") - rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ/SF/BF & TJ+WC | TJ/SF/BF & CAPLESS | MOVELESS") + rf.assign_rule("SSL: Stand Tall on the Four Pillars", "TJ+WC+GP | CANN+WC+GP | TJ/SF/BF & CAPLESS | MOVELESS") + rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ+WC | CANN+WC | TJ/SF/BF & CAPLESS | MOVELESS & CAPLESS") # Dire, Dire Docks - rf.assign_rule("DDD: Moving Poles", "CL & {{Bowser in the Fire Sea Key}} | TJ+DV+LG+WK & MOVELESS") + rf.assign_rule("DDD: Pole-Jumping for Red Coins", "CL & {{Bowser in the Fire Sea Key}} | TJ+DV+LG+WK & MOVELESS") rf.assign_rule("DDD: Through the Jet Stream", "MC | CAPLESS") rf.assign_rule("DDD: Collect the Caps...", "VC+MC | CAPLESS & VC") # Snowman's Land @@ -165,21 +167,21 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move rf.assign_rule("TTM: Top", "MOVELESS & TJ | LJ/DV & LG/KK | MOVELESS & WK & SF/LG | MOVELESS & KK/DV") rf.assign_rule("TTM: Blast to the Lonely Mushroom", "CANN | CANNLESS & LJ | MOVELESS & CANNLESS") # Tiny-Huge Island + rf.assign_rule("THI: 1Up Block THI Small near Start", "NAR | {THI: Pipes}") rf.assign_rule("THI: Pipes", "NAR | LJ/TJ/DV/LG | MOVELESS & BF/SF/KK") rf.assign_rule("THI: Large Top", "NAR | LJ/TJ/DV | MOVELESS") rf.assign_rule("THI: Wiggler's Red Coins", "WK") rf.assign_rule("THI: Make Wiggler Squirm", "GP | MOVELESS & DV") # Tick Tock Clock rf.assign_rule("TTC: Lower", "LG/TJ/SF/BF/WK") - rf.assign_rule("TTC: Upper", "CL | SF+WK") - rf.assign_rule("TTC: Top", "CL | SF+WK") - rf.assign_rule("TTC: Stomp on the Thwomp", "LG & TJ/SF/BF") + rf.assign_rule("TTC: Upper", "CL | MOVELESS & WK") + rf.assign_rule("TTC: Top", "TJ+LG | MOVELESS & WK/TJ") rf.assign_rule("TTC: Stop Time for Red Coins", "NAR | {TTC: Lower}") # Rainbow Ride rf.assign_rule("RR: Maze", "WK | LJ & SF/BF/TJ | MOVELESS & LG/TJ") rf.assign_rule("RR: Bob-omb Buddy", "WK | MOVELESS & LG") - rf.assign_rule("RR: Swingin' in the Breeze", "LG/TJ/BF/SF") - rf.assign_rule("RR: Tricky Triangles!", "LG/TJ/BF/SF") + rf.assign_rule("RR: Swingin' in the Breeze", "LG/TJ/BF/SF | MOVELESS") + rf.assign_rule("RR: Tricky Triangles!", "LG/TJ/BF/SF | MOVELESS") rf.assign_rule("RR: Cruiser", "WK/SF/BF/LG/TJ") rf.assign_rule("RR: House", "TJ/SF/BF/LG") rf.assign_rule("RR: Somewhere Over the Rainbow", "CANN") @@ -198,14 +200,14 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move # Bowser in the Sky rf.assign_rule("BitS: Top", "CL+TJ | CL+SF+LG | MOVELESS & TJ+WK+LG") # 100 Coin Stars - if world.EnableCoinStars[player]: + if options.enable_coin_stars: rf.assign_rule("BoB: 100 Coins", "CANN & WC | CANNLESS & WC & TJ") rf.assign_rule("WF: 100 Coins", "GP | MOVELESS") rf.assign_rule("JRB: 100 Coins", "GP & {JRB: Upper}") rf.assign_rule("HMC: 100 Coins", "GP") rf.assign_rule("SSL: 100 Coins", "{SSL: Upper Pyramid} | GP") - rf.assign_rule("DDD: 100 Coins", "GP") - rf.assign_rule("SL: 100 Coins", "VC | MOVELESS") + rf.assign_rule("DDD: 100 Coins", "GP & {{DDD: Pole-Jumping for Red Coins}}") + rf.assign_rule("SL: 100 Coins", "VC | CAPLESS") rf.assign_rule("WDW: 100 Coins", "GP | {WDW: Downtown}") rf.assign_rule("TTC: 100 Coins", "GP") rf.assign_rule("THI: 100 Coins", "GP") @@ -224,12 +226,12 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player) - if world.CompletionType[player] == "last_bowser_stage": - world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Sky", 'Region', player) - elif world.CompletionType[player] == "all_bowser_stages": + if options.completion_type == "last_bowser_stage": + world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player) + elif options.completion_type == "all_bowser_stages": world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Dark World", 'Region', player) and \ - state.can_reach("Bowser in the Fire Sea", 'Region', player) and \ - state.can_reach("Bowser in the Sky", 'Region', player) + state.can_reach("BitFS: Upper", 'Region', player) and \ + state.can_reach("BitS: Top", 'Region', player) class RuleFactory: @@ -244,6 +246,7 @@ class RuleFactory: token_table = { "TJ": "Triple Jump", + "DJ": "Triple Jump", "LJ": "Long Jump", "BF": "Backflip", "SF": "Side Flip", @@ -261,14 +264,14 @@ class RuleFactory: class SM64LogicException(Exception): pass - def __init__(self, world, player, move_rando_bitvec): + def __init__(self, world, options: SM64Options, player: int, move_rando_bitvec: int): self.world = world self.player = player self.move_rando_bitvec = move_rando_bitvec - self.area_randomizer = world.AreaRandomizer[player].value > 0 - self.capless = not world.StrictCapRequirements[player] - self.cannonless = not world.StrictCannonRequirements[player] - self.moveless = not world.StrictMoveRequirements[player] or not move_rando_bitvec > 0 + self.area_randomizer = options.area_rando > 0 + self.capless = not options.strict_cap_requirements + self.cannonless = not options.strict_cannon_requirements + self.moveless = not options.strict_move_requirements def assign_rule(self, target_name: str, rule_expr: str): target = self.world.get_location(target_name, self.player) if target_name in location_table else self.world.get_entrance(target_name, self.player) diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index e54a4b7a9103..833ae56ca302 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -3,10 +3,10 @@ import json from .Items import item_table, action_item_table, cannon_item_table, SM64Item from .Locations import location_table, SM64Location -from .Options import sm64_options +from .Options import SM64Options from .Rules import set_rules -from .Regions import create_regions, sm64_level_to_entrances -from BaseClasses import Item, Tutorial, ItemClassification +from .Regions import create_regions, sm64_level_to_entrances, SM64Levels +from BaseClasses import Item, Tutorial, ItemClassification, Region from ..AutoWorld import World, WebWorld @@ -35,12 +35,11 @@ class SM64World(World): item_name_to_id = item_table location_name_to_id = location_table - data_version = 9 required_client_version = (0, 3, 5) area_connections: typing.Dict[int, int] - option_definitions = sm64_options + options_dataclass = SM64Options number_of_stars: int move_rando_bitvec: int @@ -49,38 +48,36 @@ class SM64World(World): def generate_early(self): max_stars = 120 - if (not self.multiworld.EnableCoinStars[self.player].value): + if (not self.options.enable_coin_stars): max_stars -= 15 self.move_rando_bitvec = 0 - for action, itemid in action_item_table.items(): - # HACK: Disable randomization of double jump - if action == 'Double Jump': continue - if getattr(self.multiworld, f"MoveRandomizer{action.replace(' ','')}")[self.player].value: + if self.options.enable_move_rando: + for action in self.options.move_rando_actions.value: max_stars -= 1 - self.move_rando_bitvec |= (1 << (itemid - action_item_table['Double Jump'])) - if (self.multiworld.ExclamationBoxes[self.player].value > 0): + self.move_rando_bitvec |= (1 << (action_item_table[action] - action_item_table['Double Jump'])) + if (self.options.exclamation_boxes > 0): max_stars += 29 - self.number_of_stars = min(self.multiworld.AmountOfStars[self.player].value, max_stars) + self.number_of_stars = min(self.options.amount_of_stars, max_stars) self.filler_count = max_stars - self.number_of_stars self.star_costs = { - 'FirstBowserDoorCost': round(self.multiworld.FirstBowserStarDoorCost[self.player].value * self.number_of_stars / 100), - 'BasementDoorCost': round(self.multiworld.BasementStarDoorCost[self.player].value * self.number_of_stars / 100), - 'SecondFloorDoorCost': round(self.multiworld.SecondFloorStarDoorCost[self.player].value * self.number_of_stars / 100), - 'MIPS1Cost': round(self.multiworld.MIPS1Cost[self.player].value * self.number_of_stars / 100), - 'MIPS2Cost': round(self.multiworld.MIPS2Cost[self.player].value * self.number_of_stars / 100), - 'StarsToFinish': round(self.multiworld.StarsToFinish[self.player].value * self.number_of_stars / 100) + 'FirstBowserDoorCost': round(self.options.first_bowser_star_door_cost * self.number_of_stars / 100), + 'BasementDoorCost': round(self.options.basement_star_door_cost * self.number_of_stars / 100), + 'SecondFloorDoorCost': round(self.options.second_floor_star_door_cost * self.number_of_stars / 100), + 'MIPS1Cost': round(self.options.mips1_cost * self.number_of_stars / 100), + 'MIPS2Cost': round(self.options.mips2_cost * self.number_of_stars / 100), + 'StarsToFinish': round(self.options.stars_to_finish * self.number_of_stars / 100) } # Nudge MIPS 1 to match vanilla on default percentage - if self.number_of_stars == 120 and self.multiworld.MIPS1Cost[self.player].value == 12: + if self.number_of_stars == 120 and self.options.mips1_cost == 12: self.star_costs['MIPS1Cost'] = 15 - self.topology_present = self.multiworld.AreaRandomizer[self.player].value + self.topology_present = self.options.area_rando def create_regions(self): - create_regions(self.multiworld, self.player) + create_regions(self.multiworld, self.options, self.player) def set_rules(self): self.area_connections = {} - set_rules(self.multiworld, self.player, self.area_connections, self.star_costs, self.move_rando_bitvec) + set_rules(self.multiworld, self.options, self.player, self.area_connections, self.star_costs, self.move_rando_bitvec) if self.topology_present: # Write area_connections to spoiler log for entrance, destination in self.area_connections.items(): @@ -107,7 +104,7 @@ def create_items(self): # Power Stars self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,self.number_of_stars)] # Keys - if (not self.multiworld.ProgressiveKeys[self.player].value): + if (not self.options.progressive_keys): key1 = self.create_item("Basement Key") key2 = self.create_item("Second Floor Key") self.multiworld.itempool += [key1, key2] @@ -116,7 +113,7 @@ def create_items(self): # Caps self.multiworld.itempool += [self.create_item(cap_name) for cap_name in ["Wing Cap", "Metal Cap", "Vanish Cap"]] # Cannons - if (self.multiworld.BuddyChecks[self.player].value): + if (self.options.buddy_checks): self.multiworld.itempool += [self.create_item(name) for name, id in cannon_item_table.items()] # Moves self.multiworld.itempool += [self.create_item(action) @@ -124,7 +121,7 @@ def create_items(self): if self.move_rando_bitvec & (1 << itemid - action_item_table['Double Jump'])] def generate_basic(self): - if not (self.multiworld.BuddyChecks[self.player].value): + if not (self.options.buddy_checks): self.multiworld.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB")) self.multiworld.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF")) self.multiworld.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB")) @@ -136,7 +133,7 @@ def generate_basic(self): self.multiworld.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI")) self.multiworld.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR")) - if (self.multiworld.ExclamationBoxes[self.player].value == 0): + if (self.options.exclamation_boxes == 0): self.multiworld.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom")) self.multiworld.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom")) self.multiworld.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom")) @@ -174,8 +171,8 @@ def fill_slot_data(self): return { "AreaRando": self.area_connections, "MoveRandoVec": self.move_rando_bitvec, - "DeathLink": self.multiworld.death_link[self.player].value, - "CompletionType": self.multiworld.CompletionType[self.player].value, + "DeathLink": self.options.death_link.value, + "CompletionType": self.options.completion_type.value, **self.star_costs } @@ -200,11 +197,21 @@ def generate_output(self, output_directory: str): with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f) - def modify_multidata(self, multidata): + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): if self.topology_present: er_hint_data = {} for entrance, destination in self.area_connections.items(): - region = self.multiworld.get_region(sm64_level_to_entrances[destination], self.player) - for location in region.locations: - er_hint_data[location.address] = sm64_level_to_entrances[entrance] - multidata['er_hint_data'][self.player] = er_hint_data + regions = [self.multiworld.get_region(sm64_level_to_entrances[destination], self.player)] + if regions[0].name == "Tiny-Huge Island (Huge)": + # Special rules for Tiny-Huge Island's dual entrances + reverse_area_connections = {destination: entrance for entrance, destination in self.area_connections.items()} + entrance_name = sm64_level_to_entrances[reverse_area_connections[SM64Levels.TINY_HUGE_ISLAND_HUGE]] \ + + ' or ' + sm64_level_to_entrances[reverse_area_connections[SM64Levels.TINY_HUGE_ISLAND_TINY]] + regions[0] = self.multiworld.get_region("Tiny-Huge Island", self.player) + else: + entrance_name = sm64_level_to_entrances[entrance] + regions += regions[0].subregions + for region in regions: + for location in region.locations: + er_hint_data[location.address] = entrance_name + hint_data[self.player] = er_hint_data diff --git a/worlds/sm64ex/docs/en_Super Mario 64.md b/worlds/sm64ex/docs/en_Super Mario 64.md index def6e2a37536..4c85881a8525 100644 --- a/worlds/sm64ex/docs/en_Super Mario 64.md +++ b/worlds/sm64ex/docs/en_Super Mario 64.md @@ -1,28 +1,28 @@ # Super Mario 64 EX -## Where is the settings page? +## Where is the options page? -The player settings page for this game contains all the options you need to configure and export a config file. Player -settings page link: [SM64EX Player Settings Page](../player-settings). +The player options page for this game contains all the options you need to configure and export a config file. Player +options page link: [SM64EX Player Options Page](../player-options). ## What does randomization do to this game? -All 120 Stars, the 3 Cap Switches, the Basement and Secound Floor Key are now Location Checks and may contain Items for different games as well -as different Items from within SM64. +All 120 Stars, the 3 Cap Switches, the Basement and Second Floor Key are now location checks and may contain items for different games as well +as different items from within SM64. ## What is the goal of SM64EX when randomized? -As in most Mario Games, save the Princess! +As in most Mario games, save the Princess! ## Which items can be in another player's world? Any of the 120 Stars, and the two Castle Keys. Additionally, Cap Switches are also considered "Items" and the "!"-Boxes will only be active -when someone collects the corresponding Cap Switch Item. +when someone collects the corresponding Cap Switch item. ## What does another world's item look like in SM64EX? -The Items are visually unchanged, though after collecting a Message will pop up to inform you what you collected, +The items are visually unchanged, though after collecting a message will pop up to inform you what you collected, and who will receive it. ## When the player receives an item, what happens? -When you receive an Item, a Message will pop up to inform you where you received the Item from, +When you receive an item, a message will pop up to inform you where you received the item from, and which one it is. -NOTE: The Secret Star count in the Menu is broken. +NOTE: The Secret Star count in the menu is broken. diff --git a/worlds/sm64ex/docs/setup_en.md b/worlds/sm64ex/docs/setup_en.md index 2817d3c324c0..5983057f7d7a 100644 --- a/worlds/sm64ex/docs/setup_en.md +++ b/worlds/sm64ex/docs/setup_en.md @@ -70,7 +70,7 @@ After the compliation was successful, there will be a binary in your `sm64ex/bui ### Joining a MultiWorld Game To join, set the following launch options: `--sm64ap_name YourName --sm64ap_ip ServerIP:Port`. -For example, if you are hosting a game using the website, `YourName` will be the name from the Settings Page, `ServerIP` is `archipelago.gg` and `Port` the port given on the Archipelago room page. +For example, if you are hosting a game using the website, `YourName` will be the name from the Options Page, `ServerIP` is `archipelago.gg` and `Port` the port given on the Archipelago room page. Optionally, add `--sm64ap_passwd "YourPassword"` if the room you are using requires a password. Should your name or password have spaces, enclose it in quotes: `"YourPassword"` and `"YourName"`. @@ -82,7 +82,7 @@ Failing to use a new file may make some locations unavailable. However, this can ### Playing offline -To play offline, first generate a seed on the game's settings page. +To play offline, first generate a seed on the game's options page. Create a room and download the `.apsm64ex` file, and start the game with the `--sm64ap_file "path/to/FileName"` launch argument. ### Optional: Using Batch Files to play offline and MultiWorld games diff --git a/worlds/smw/Aesthetics.py b/worlds/smw/Aesthetics.py index 73ca61650886..16b2b138f3e2 100644 --- a/worlds/smw/Aesthetics.py +++ b/worlds/smw/Aesthetics.py @@ -1,3 +1,82 @@ +import json +import pkgutil + +from worlds.AutoWorld import World + +tileset_names = [ + "grass_hills", + "grass_forest", + "grass_rocks", + "grass_clouds", + "grass_mountains", + "cave", + "cave_rocks", + "water", + "mushroom_rocks", + "mushroom_clouds", + "mushroom_forest", + "mushroom_hills", + "mushroom_stars", + "mushroom_cave", + "forest", + "logs", + "clouds", + "castle_pillars", + "castle_windows", + "castle_wall", + "castle_small_windows", + "ghost_house", + "ghost_house_exit", + "ship_exterior", + "ship_interior", + "switch_palace", + "yoshi_house" +] + +map_names = [ + "main", + "yoshi", + "vanilla", + "forest", + "valley", + "special", + "star" +] + +level_palette_index = [ + 0xFF,0x03,0x09,0x01,0x15,0x0A,0x04,0x12,0x19,0x06,0x07,0x12,0x09,0x0F,0x13,0x09, # Levels 000-00F + 0x03,0x07,0xFF,0x15,0x19,0x04,0x04,0xFF,0x17,0xFF,0x14,0x12,0x02,0x05,0xFF,0x11, # Levels 010-01F + 0x12,0x15,0x04,0x02,0x02,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 020-02F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 030-03F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 040-04F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 050-05F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 060-06F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 070-07F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 080-08F + 0xFF,0xFF,0xFF,0x12,0x12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 090-09F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 0A0-0AF + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x19,0x08,0x09, # Levels 0B0-0BF + 0x02,0x08,0x05,0x04,0x16,0x1A,0x04,0x02,0x0C,0x19,0x19,0x09,0xFF,0x02,0x02,0x02, # Levels 0C0-0CF + 0x04,0x04,0x05,0x12,0x14,0xFF,0x12,0x10,0x05,0xFF,0x19,0x12,0x14,0x0F,0x15,0xFF, # Levels 0D0-0DF + 0x12,0x12,0xFF,0x04,0x15,0xFF,0x19,0x14,0x12,0x05,0x05,0x16,0x15,0x15,0x15,0x12, # Levels 0E0-0EF + 0x16,0x15,0x15,0x09,0x19,0x04,0x04,0x13,0x18,0x15,0x15,0x16,0x15,0x19,0x15,0x04, # Levels 0F0-0FF + 0xFF,0x11,0x08,0x02,0x1A,0x00,0x01,0x15,0xFF,0x05,0x05,0x05,0xFF,0x11,0x12,0x05, # Levels 100-10F + 0x12,0x14,0xFF,0x0D,0x15,0x06,0x05,0x05,0x05,0x0C,0x05,0x19,0x12,0x15,0x0E,0x01, # Levels 110-11F + 0x07,0x19,0x0E,0x0E,0xFF,0x04,0x0E,0x02,0x02,0xFF,0x09,0x04,0x0B,0x02,0xFF,0xFF, # Levels 120-12F + 0x07,0xFF,0x0C,0xFF,0x05,0x0C,0x0C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 130-13F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 140-14F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 150-15F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 160-16F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 170-17F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 180-18F + 0xFF,0xFF,0xFF,0x12,0x12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 190-19F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 1A0-1AF + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x19,0x19,0x12,0x02,0x05, # Levels 1B0-1BF + 0x02,0x07,0x05,0x05,0x03,0x03,0x00,0xFF,0x0F,0x10,0x05,0x05,0x12,0x11,0x14,0x14, # Levels 1C0-1CF + 0x11,0x12,0x12,0x12,0x11,0x03,0x03,0x19,0x19,0x15,0x16,0x15,0x15,0x15,0xFF,0x05, # Levels 1D0-1DF + 0x10,0x02,0x06,0x06,0x19,0x05,0x16,0x16,0x15,0x15,0x15,0xFF,0x06,0x05,0x05,0x06, # Levels 1E0-1EF + 0x05,0x05,0x12,0x14,0x12,0x05,0xFF,0x19,0x05,0x16,0x15,0x15,0x11,0x05,0x12,0x09 # Levels 1F0-1FF +] mario_palettes = [ [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x39, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0xB6, 0x30, 0xDF, 0x35, 0xFF, 0x03], # Mario @@ -145,36 +224,413 @@ 0x2D24: [0x00, 0x02, 0x03], # Star Road } -def generate_shuffled_level_music(world, player): +valid_sfxs = [ + [0x01, 1], # Jump + [0x01, 0], # Hit head + [0x02, 0], # Contact/Spinjump on an enemy + [0x03, 0], # Kick item + [0x04, 0], # Go in pipe, get hurt + [0x05, 0], # Midway point + [0x06, 0], # Yoshi gulp + [0x07, 0], # Dry bones collapse + [0x08, 0], # Kill enemy with a spin jump + [0x09, 0], # Fly with cape + [0x0A, 0], # Get powerup + [0x0B, 0], # ON/OFF switch + [0x0C, 0], # Carry item past the goal + [0x0D, 0], # Get cape + [0x0E, 0], # Swim + [0x0F, 0], # Hurt while flying + [0x10, 0], # Magikoopa shoot magic + [0x13, 0], # Enemy stomp #1 + [0x14, 0], # Enemy stomp #2 + [0x15, 0], # Enemy stomp #3 + [0x16, 0], # Enemy stomp #4 + [0x17, 0], # Enemy stomp #5 + [0x18, 0], # Enemy stomp #6 + [0x19, 0], # Enemy stomp #7 + [0x1C, 0], # Yoshi Coin + [0x1E, 0], # P-Balloon + [0x1F, 0], # Koopaling defeated + [0x20, 0], # Yoshi spit + [0x23, 0], # Lemmy/Wendy falling + [0x25, 0], # Blargg roar + [0x26, 0], # Firework whistle + [0x27, 0], # Firework bang + [0x2A, 0], # Peach pops up from the Clown Car + [0x04, 1], # Grinder + [0x01, 3], # Coin + [0x02, 3], # Hit a ? block + [0x03, 3], # Hit a block with a vine inside + [0x04, 3], # Spin jump + [0x05, 3], # 1up + [0x06, 3], # Shatter block + [0x07, 3], # Shoot fireball + [0x08, 3], # Springboard + [0x09, 3], # Bullet bill + [0x0A, 3], # Egg hatch + [0x0B, 3], # Item going into item box + [0x0C, 3], # Item falls from item box + [0x0E, 3], # L/R scroll + [0x0F, 3], # Door + [0x13, 3], # Lose Yoshi + [0x14, 3], # SMW2: New level available + [0x15, 3], # OW tile reveal + [0x16, 3], # OW castle collapse + [0x17, 3], # Fire spit + [0x18, 3], # Thunder + [0x19, 3], # Clap + [0x1A, 3], # Castle bomb + [0x1C, 3], # OW switch palace block ejection + [0x1E, 3], # Whistle + [0x1F, 3], # Yoshi mount + [0x20, 3], # Lemmy/Wendy going in lava + [0x21, 3], # Yoshi's tongue + [0x22, 3], # Message box hit + [0x23, 3], # Landing in a level tile + [0x24, 3], # P-Switch running out + [0x25, 3], # Yoshi defeats an enemy + [0x26, 3], # Swooper + [0x27, 3], # Podoboo + [0x28, 3], # Enemy hurt + [0x29, 3], # Correct + [0x2A, 3], # Wrong + [0x2B, 3], # Firework whistle + [0x2C, 3] # Firework bang +] + +game_sfx_calls = [ + 0x0565E, # Jump + 0x1BABD, # Spin jump + 0x06D41, # Hit head on ceiling + 0x0B4F2, # Hit head on sprite + 0x07EB5, # Shooting a fireball + 0x0507B, # Cape spin + 0x058A8, # Cape smash + 0x075F3, # Taking damage + 0x075E2, # Taking damage while flying + 0x07919, # Something during a boss fight + 0x05AA9, # Swim + 0x1BC04, # Spin jump off water + 0x05BA5, # Jump off a net + 0x05BB2, # Punching a net + 0x06C10, # Entering a door + 0x05254, # Entering a pipe #1 + 0x07439, # Entering a pipe #2 + 0x052A5, # Shot from a diagonal pipe + 0x072E8, # Hit a midway point + 0x07236, # Hit a wrong block + 0x07B7D, # Spawn a powerup during the goal tape + 0x1C342, # Invisible mushroom spawn + 0x04E3F, # Scrolling the screen with L/R + 0x0AAFD, # Pressing a P-Switch + 0x04557, # P-Switch running out + 0x0BAD7, # Climbing door turning + 0x0C109, # Break goal tape + 0x0C548, # Putting item in item box + 0x10012, # Trigger item box + 0x2B34D, # Collecting a coin + 0x07358, # Collecting a Yoshi Coin + 0x0C57A, # Collecting a powerup (generic) + 0x0C59C, # Collecting a feather + 0x0C309, # Collecting a P-Balloon + 0x0E6A9, # Bouncing off a springboard + 0x1117D, # Bouncing off a note block + 0x14DEC, # Bouncing off a wall springboard + 0x1067F, # Block shattering + 0x1081E, # Activate ON/OFF switch #1 + 0x1118C, # Activate ON/OFF switch #2 + 0x12045, # Fireballs hitting a block/sprite + 0x12124, # Fireballs converting an enemy into a coin + 0x12106, # Fireballs defeating a Chuck + 0x18D7D, # Activating a message box + 0x1C209, # Activating a red question block + 0x0A290, # Baby Yoshi swallowing an item #1 + 0x1C037, # Baby Yoshi swallowing an item #2 + 0x0F756, # Yoshi egg hatching + 0x0A2C5, # Yoshi growing #1 + 0x1C06C, # Yoshi growing #2 + 0x0ED5F, # Mounting Yoshi + 0x0F71D, # Yoshi hurt + 0x12481, # Yoshi hurt (projectiles) + 0x0EF0E, # Yoshi flying + 0x06F90, # Yoshi stomping an enemy + 0x06FB6, # Yoshi ground pound (yellow shell) + 0x07024, # Yoshi bounces off a triangle + 0x11BE9, # Yoshi stomping the ground + 0x0F0D3, # Yoshi swallowing a sprite + 0x0F0FD, # Yoshi eating a green berry + 0x1BA7D, # Yoshi sticking out tongue + 0x0F5A1, # Yoshi unable to eat + 0x0F2DF, # Yoshi spitting out an item + 0x0F28F, # Yoshi spitting out flames + 0x0F3EC, # Collecting Yoshi's wings (eaten) + 0x0F6C8, # Collecting Yoshi's wings (touched) + 0x7FE04, # Defeated sprite combo #1 (using Y index) + 0x7FE0E, # Defeated sprite combo #2 (using Y index) + 0x7FE18, # Defeated sprite combo #3 (using Y index) + 0x7FE22, # Defeated sprite combo #4 (using Y index) + 0x7FE2C, # Defeated sprite combo #5 (using Y index) + 0x7FE36, # Defeated sprite combo #6 (using Y index) + 0x7FE40, # Defeated sprite combo #7 (using Y index) + 0x7FE4B, # Defeated sprite combo #1 (using X index) + 0x7FE55, # Defeated sprite combo #2 (using X index) + 0x7FE5F, # Defeated sprite combo #3 (using X index) + 0x7FE69, # Defeated sprite combo #4 (using X index) + 0x7FE73, # Defeated sprite combo #5 (using X index) + 0x7FE7D, # Defeated sprite combo #6 (using X index) + 0x7FE87, # Defeated sprite combo #7 (using X index) + 0x0A728, # Kicking a carryable item + 0x0B12F, # Kicking a stunned and vulnerable enemy + 0x0A8D8, # Performing a spinjump on a immune enemy + 0x0A93F, # Defeating an enemy via spinjump + 0x0999E, # Thrown sprite hitting the ground from the side + 0x192B8, # Creating/Eating block moving + 0x195EC, # Rex stomped + 0x134A7, # Bullet bill blaster shooting + 0x13088, # Bullet bill generator #1 + 0x130DF, # Bullet bill generator #2 + 0x09631, # Bob-omb explosion + 0x15918, # Popping a bubble + 0x15D64, # Sumo bro stomping the ground + 0x15ECC, # Sumo bro lightning spawning flames + 0x1726B, # Bouncing off wiggler + 0x08390, # Banzai bill spawn + 0x0AF17, # Thwomp hitting the ground + 0x0AFFC, # Thwimp hitting the ground + 0x14707, # Chuck running + 0x14381, # Chuck whistling + 0x144F8, # Chuck clapping + 0x14536, # Chuck jumping + 0x145AE, # Chuck splitting + 0x147D2, # Chuck bounce + 0x147F6, # Chuck hurt + 0x147B8, # Chuck defeated + 0x19D55, # Dino torch shooting fire + 0x19FFA, # Blargg attacking + 0x188FF, # Swooper bat swooping + 0x08584, # Bowser statue flame spawn + 0x18ADA, # Bowser statue shooting a flame + 0x13043, # Bowser statue flame from generator + 0x0BF28, # Magikoopa shooting a magic spell + 0x0BC5F, # Magikoopa's magic spell hitting the ground + 0x0D745, # Line guided sprites' motor + 0x0DB70, # Grinder sound + 0x0E0A1, # Podoboo jumping + 0x0E5F2, # Dry bones/Bony beetle collapsing + 0x15474, # Giant wooden pillar hitting the ground + 0x2C9C1, # Spiked columns hitting the ground + 0x19B03, # Reznor shooting a fireball + 0x19A66, # Reznor: Hitting a platform + 0x1D752, # Reznor: Bridge collapsing + 0x19ABB, # Reznor: Defeated + 0x180E9, # Big Boo: Reappearing + 0x18233, # Big Boo: Hurt + 0x181DE, # Big Boo: Defeated + 0x1CEC1, # Wendy/Lemmy: Hitting a dummy + 0x1CECB, # Wendy/Lemmy: Hurt + 0x1CE33, # Wendy/Lemmy: Hurt (correct) + 0x1CE46, # Wendy/Lemmy: Hurt (incorrect) + 0x1CE24, # Wendy/Lemmy: Defeated + 0x1CE7E, # Wendy/Lemmy: Falling into lava + 0x0CF0A, # Ludwig: Jumping + 0x0D059, # Ludwig: Shooting a fireball + 0x10414, # Morton/Roy: Pillar drop + 0x0D299, # Morton/Roy: Ground smash + 0x0D3AB, # Morton/Roy/Ludwig: Hit by a fireball + 0x0D2FD, # Morton/Roy/Ludwig: Bouncing off + 0x0D31E, # Morton/Roy/Ludwig: Bouncing off (immune) + 0x0D334, # Morton/Roy/Ludwig: Bouncing off (immune, going up a wall) + 0x0CFD0, # Morton/Roy/Ludwig: Defeated + 0x0FCCE, # Iggy/Larry: Being hit + 0x0FD40, # Iggy/Larry: Being hit by a fireball + 0x0FB60, # Iggy/Larry: Falling in lava + 0x1A8B2, # Peach emerging from Clown Car + 0x1A8E3, # Peach throwing an item + 0x1B0B8, # Bumping into Clown Car + 0x1B129, # Bowser: Hurt + 0x1AB8C, # Bowser: Slamming the ground (third phase) + 0x1A5D0, # Bowser: Throwing a Mechakoopa + 0x1A603, # Bowser: Dropping a ball + 0x1A7F6, # Bowser: Spawning a flame + 0x1B1A3, # Bowser's ball slam #1 + 0x1B1B1, # Bowser's ball slam #2 + 0x1E016, # Bowser's arena lightning effect + 0x26CAA, # Map: Level tile reveal + 0x26763, # Map: Terrain reveal + 0x21170, # Map: Using a star + 0x2666F, # Map: Castle destruction + 0x272A4, # Map: Switch palace blocks spawning + 0x203CC, # Map: Earthquake + 0x27A78, # Map: Fish jumping + 0x27736, # Map: Valley of bowser thunder + 0x013C0, # Menu: Nintendo presents + 0x01AE3, # Menu: File menu option select + 0x01AF9, # Menu: File menu option change + 0x01BBB, # Menu: Saving game + 0x273FF, # Menu: Map misc menu appearing + 0x27567, # Menu: Something during the map + 0x1767A, # Cutscene: Castle door opening + 0x17683, # Cutscene: Castle door closing + 0x17765, # Cutscene: Ghost house door opening + 0x1776E, # Cutscene: Ghost house door closing + 0x04720, # Cutscene: Detonator fuse + 0x04732, # Cutscene: Bouncing off something + 0x0475F, # Cutscene: Tossing the castle + 0x04798, # Cutscene: Picking up the castle + 0x047AC, # Cutscene: Huff + 0x047D1, # Cutscene: Hitting a castle + 0x1C830, # Cutscene: Shooting a firework + 0x625AF, # Cutscene: Egg shattering + 0x64F2C, # Cutscene: Hitting a hill + 0x6512A, # Cutscene: Castle crashing + 0x65295, # Cutscene: Explosion + 0x652B2, # Cutscene: Castle sinking + 0x652BD, # Cutscene: Castle flying + 0x652D8, # Cutscene: Fake explosion + 0x653E7, # Cutscene: Castle being hit by a hammer + 0x657D8 # Cutscene: Castle being mopped away +] + +def generate_shuffled_sfx(rom, world: World): + # Adjust "hitting sprites in succession" codes + rom.write_bytes(0x0A60B, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Thrown sprites combo #1 + rom.write_bytes(0x0A659, bytearray([0x22, 0x47, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE47 : nop #2 # Thrown sprites combo #2 + rom.write_bytes(0x0A865, bytearray([0x22, 0x47, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE47 : nop #2 # Star combo + rom.write_bytes(0x0AB57, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Bouncing off enemies + rom.write_bytes(0x172C0, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Star combo (wigglers) + rom.write_bytes(0x1961D, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Star combo (rexes) + rom.write_bytes(0x19639, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Bouncing off rexes + + COMBO_SFX_ADDR = 0x7FE00 + rom.write_bytes(COMBO_SFX_ADDR + 0x0000, bytearray([0xC0, 0x01])) # COMBO_Y: CPY #$01 + rom.write_bytes(COMBO_SFX_ADDR + 0x0002, bytearray([0xD0, 0x06])) # BNE label_0FFE0A + rom.write_bytes(COMBO_SFX_ADDR + 0x0004, bytearray([0xA9, 0x13])) # LDA #$13 + rom.write_bytes(COMBO_SFX_ADDR + 0x0006, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0009, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x000A, bytearray([0xC0, 0x02])) # label_0FFE0A: CPY #$02 + rom.write_bytes(COMBO_SFX_ADDR + 0x000C, bytearray([0xD0, 0x06])) # BNE label_0FFE14 + rom.write_bytes(COMBO_SFX_ADDR + 0x000E, bytearray([0xA9, 0x14])) # LDA #$14 + rom.write_bytes(COMBO_SFX_ADDR + 0x0010, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0013, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0014, bytearray([0xC0, 0x03])) # label_0FFE14: CPY #$03 + rom.write_bytes(COMBO_SFX_ADDR + 0x0016, bytearray([0xD0, 0x06])) # BNE label_0FFE1E + rom.write_bytes(COMBO_SFX_ADDR + 0x0018, bytearray([0xA9, 0x15])) # LDA #$15 + rom.write_bytes(COMBO_SFX_ADDR + 0x001A, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x001D, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x001E, bytearray([0xC0, 0x04])) # label_0FFE1E: CPY #$04 + rom.write_bytes(COMBO_SFX_ADDR + 0x0020, bytearray([0xD0, 0x06])) # BNE label_0FFE28 + rom.write_bytes(COMBO_SFX_ADDR + 0x0022, bytearray([0xA9, 0x16])) # LDA #$16 + rom.write_bytes(COMBO_SFX_ADDR + 0x0024, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0027, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0028, bytearray([0xC0, 0x05])) # label_0FFE28: CPY #$05 + rom.write_bytes(COMBO_SFX_ADDR + 0x002A, bytearray([0xD0, 0x06])) # BNE label_0FFE32 + rom.write_bytes(COMBO_SFX_ADDR + 0x002C, bytearray([0xA9, 0x17])) # LDA #$17 + rom.write_bytes(COMBO_SFX_ADDR + 0x002E, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0031, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0032, bytearray([0xC0, 0x06])) # label_0FFE32: CPY #$06 + rom.write_bytes(COMBO_SFX_ADDR + 0x0034, bytearray([0xD0, 0x06])) # BNE label_0FFE3C + rom.write_bytes(COMBO_SFX_ADDR + 0x0036, bytearray([0xA9, 0x18])) # LDA #$18 + rom.write_bytes(COMBO_SFX_ADDR + 0x0038, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x003B, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x003C, bytearray([0xC0, 0x07])) # label_0FFE3C: CPY #$07 + rom.write_bytes(COMBO_SFX_ADDR + 0x003E, bytearray([0xD0, 0x06])) # BNE label_0FFE46 + rom.write_bytes(COMBO_SFX_ADDR + 0x0040, bytearray([0xA9, 0x19])) # LDA #$19 + rom.write_bytes(COMBO_SFX_ADDR + 0x0042, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0045, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0046, bytearray([0x6B])) # label_0FFE46: RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0047, bytearray([0xE0, 0x01])) # COMBO_X: CPX #$01 + rom.write_bytes(COMBO_SFX_ADDR + 0x0049, bytearray([0xD0, 0x06])) # BNE label_0FFE51 + rom.write_bytes(COMBO_SFX_ADDR + 0x004B, bytearray([0xA9, 0x13])) # LDA #$13 + rom.write_bytes(COMBO_SFX_ADDR + 0x004D, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0050, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0051, bytearray([0xE0, 0x02])) # label_0FFE51: CPX #$02 + rom.write_bytes(COMBO_SFX_ADDR + 0x0053, bytearray([0xD0, 0x06])) # BNE label_0FFE5B + rom.write_bytes(COMBO_SFX_ADDR + 0x0055, bytearray([0xA9, 0x14])) # LDA #$14 + rom.write_bytes(COMBO_SFX_ADDR + 0x0057, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x005A, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x005B, bytearray([0xE0, 0x03])) # label_0FFE5B: CPX #$03 + rom.write_bytes(COMBO_SFX_ADDR + 0x005D, bytearray([0xD0, 0x06])) # BNE label_0FFE65 + rom.write_bytes(COMBO_SFX_ADDR + 0x005F, bytearray([0xA9, 0x15])) # LDA #$15 + rom.write_bytes(COMBO_SFX_ADDR + 0x0061, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0064, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0065, bytearray([0xE0, 0x04])) # label_0FFE65: CPX #$04 + rom.write_bytes(COMBO_SFX_ADDR + 0x0067, bytearray([0xD0, 0x06])) # BNE label_0FFE6F + rom.write_bytes(COMBO_SFX_ADDR + 0x0069, bytearray([0xA9, 0x16])) # LDA #$16 + rom.write_bytes(COMBO_SFX_ADDR + 0x006B, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x006E, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x006F, bytearray([0xE0, 0x05])) # label_0FFE6F: CPX #$05 + rom.write_bytes(COMBO_SFX_ADDR + 0x0071, bytearray([0xD0, 0x06])) # BNE label_0FFE79 + rom.write_bytes(COMBO_SFX_ADDR + 0x0073, bytearray([0xA9, 0x17])) # LDA #$17 + rom.write_bytes(COMBO_SFX_ADDR + 0x0075, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0078, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0079, bytearray([0xE0, 0x06])) # label_0FFE79: CPX #$06 + rom.write_bytes(COMBO_SFX_ADDR + 0x007B, bytearray([0xD0, 0x06])) # BNE label_0FFE83 + rom.write_bytes(COMBO_SFX_ADDR + 0x007D, bytearray([0xA9, 0x18])) # LDA #$18 + rom.write_bytes(COMBO_SFX_ADDR + 0x007F, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0082, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0083, bytearray([0xE0, 0x07])) # label_0FFE83: CPX #$07 + rom.write_bytes(COMBO_SFX_ADDR + 0x0085, bytearray([0xD0, 0x06])) # BNE label_0FFE8D + rom.write_bytes(COMBO_SFX_ADDR + 0x0087, bytearray([0xA9, 0x19])) # LDA #$19 + rom.write_bytes(COMBO_SFX_ADDR + 0x0089, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x008C, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x008D, bytearray([0x6B])) # label_0FFE8D: RTL + + # Adjust "Hit head on ceiling" code + rom.write_bytes(0x06D41 + 0x00, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(0x06D41 + 0x02, bytearray([0x8D, 0xF9, 0x1D])) # sta $1DF9 + rom.write_bytes(0x06D41 + 0x05, bytearray([0xEA, 0xEA, 0xEA, 0xEA])) # nop #4 + + # Manually add "Map: Stepping onto a level tile" random SFX + selected_sfx = world.random.choice(valid_sfxs) + rom.write_byte(0x2169F + 0x01, selected_sfx[0]) + rom.write_byte(0x2169F + 0x04, selected_sfx[1] + 0xF9) + + # Disable panning on Bowser's flames + rom.write_bytes(0x1A83D, bytearray([0xEA, 0xEA, 0xEA])) # nop #3 + + # Randomize SFX calls + for address in game_sfx_calls: + # Get random SFX + if world.options.sfx_shuffle != "singularity": + selected_sfx = world.random.choice(valid_sfxs) + # Write randomized SFX num + rom.write_byte(address + 0x01, selected_sfx[0]) + # Write randomized SFX port + rom.write_byte(address + 0x03, selected_sfx[1] + 0xF9) + +def generate_shuffled_level_music(world: World): shuffled_level_music = level_music_value_data.copy() - if world.music_shuffle[player] == "consistent": - world.per_slot_randoms[player].shuffle(shuffled_level_music) - elif world.music_shuffle[player] == "singularity": - single_song = world.per_slot_randoms[player].choice(shuffled_level_music) + if world.options.music_shuffle == "consistent": + world.random.shuffle(shuffled_level_music) + elif world.options.music_shuffle == "singularity": + single_song = world.random.choice(shuffled_level_music) shuffled_level_music = [single_song for i in range(len(shuffled_level_music))] return shuffled_level_music -def generate_shuffled_ow_music(world, player): +def generate_shuffled_ow_music(world: World): shuffled_ow_music = ow_music_value_data.copy() - if world.music_shuffle[player] == "consistent" or world.music_shuffle[player] == "full": - world.per_slot_randoms[player].shuffle(shuffled_ow_music) - elif world.music_shuffle[player] == "singularity": - single_song = world.per_slot_randoms[player].choice(shuffled_ow_music) + if world.options.music_shuffle == "consistent" or world.options.music_shuffle == "full": + world.random.shuffle(shuffled_ow_music) + elif world.options.music_shuffle == "singularity": + single_song = world.random.choice(shuffled_ow_music) shuffled_ow_music = [single_song for i in range(len(shuffled_ow_music))] return shuffled_ow_music -def generate_shuffled_ow_palettes(rom, world, player): - if world.overworld_palette_shuffle[player]: - for address, valid_palettes in valid_ow_palettes.items(): - chosen_palette = world.per_slot_randoms[player].choice(valid_palettes) - rom.write_byte(address, chosen_palette) +def generate_shuffled_ow_palettes(rom, world: World): + if world.options.overworld_palette_shuffle != "on_legacy": + return + + for address, valid_palettes in valid_ow_palettes.items(): + chosen_palette = world.random.choice(valid_palettes) + rom.write_byte(address, chosen_palette) -def generate_shuffled_header_data(rom, world, player): - if world.music_shuffle[player] != "full" and not world.foreground_palette_shuffle[player] and not world.background_palette_shuffle[player]: +def generate_shuffled_header_data(rom, world: World): + if world.options.music_shuffle != "full" and world.options.level_palette_shuffle != "on_legacy": return for level_id in range(0, 0x200): @@ -194,24 +650,425 @@ def generate_shuffled_header_data(rom, world, player): tileset = level_header[4] & 0x0F - if world.music_shuffle[player] == "full": + if world.options.music_shuffle == "full": level_header[2] &= 0x8F - level_header[2] |= (world.per_slot_randoms[player].randint(0, 7) << 5) + level_header[2] |= (world.random.randint(0, 7) << 5) - if (world.foreground_palette_shuffle[player] and tileset in valid_foreground_palettes): - level_header[3] &= 0xF8 - level_header[3] |= world.per_slot_randoms[player].choice(valid_foreground_palettes[tileset]) + if world.options.level_palette_shuffle == "on_legacy": + if tileset in valid_foreground_palettes: + level_header[3] &= 0xF8 + level_header[3] |= world.random.choice(valid_foreground_palettes[tileset]) - if world.background_palette_shuffle[player]: layer2_ptr_list = list(rom.read_bytes(0x2E600 + level_id * 3, 3)) layer2_ptr = (layer2_ptr_list[2] << 16 | layer2_ptr_list[1] << 8 | layer2_ptr_list[0]) if layer2_ptr in valid_background_palettes: level_header[0] &= 0x1F - level_header[0] |= (world.per_slot_randoms[player].choice(valid_background_palettes[layer2_ptr]) << 5) + level_header[0] |= (world.random.choice(valid_background_palettes[layer2_ptr]) << 5) if layer2_ptr in valid_background_colors: level_header[1] &= 0x1F - level_header[1] |= (world.per_slot_randoms[player].choice(valid_background_colors[layer2_ptr]) << 5) + level_header[1] |= (world.random.choice(valid_background_colors[layer2_ptr]) << 5) rom.write_bytes(layer1_ptr, bytes(level_header)) + +def generate_curated_level_palette_data(rom, world: World): + PALETTE_LEVEL_CODE_ADDR = 0x88000 + PALETTE_INDEX_ADDR = 0x8F000 + PALETTE_LEVEL_TILESET_ADDR = 0x8F200 + PALETTE_LEVEL_PTR_ADDR = 0x92000 + PALETTE_LEVEL_DATA_ADDR = 0xA8000 + + addr = pc_to_snes(PALETTE_LEVEL_PTR_ADDR) + snes_level_palette_pointers_1 = bytearray([0xBF, (addr)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + snes_level_palette_pointers_2 = bytearray([0xBF, (addr+2)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + + # Enable curated palette loader + rom.write_bytes(0x02BED, bytearray([0x5C, 0x00, 0x80, 0x11])) # org $00ABED : jml custom_palettes + rom.write_bytes(0x02330, bytearray([0x5C, 0x02, 0x80, 0x11])) # org $00A318 : jml custom_palettes_original + rom.write_bytes(0x013D7, bytearray([0x20, 0x30, 0xA3])) # org $0093D7 : jmp $A330 + rom.write_bytes(0x014DA, bytearray([0x20, 0x30, 0xA3])) # org $0094DA : jmp $A330 + rom.write_bytes(0x015EC, bytearray([0x20, 0x30, 0xA3])) # org $0095EC : jmp $A330 + rom.write_bytes(0x0165B, bytearray([0x20, 0x30, 0xA3])) # org $00965B : jmp $A330 + rom.write_bytes(0x02DD9, bytearray([0x20, 0x30, 0xA3])) # org $00ADD9 : jmp $A330 + rom.write_bytes(0x02E1F, bytearray([0x20, 0x30, 0xA3])) # org $00AE1F : jmp $A330 + + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0000, bytearray([0x80, 0x09])) # bra custom_palettes + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0002, bytearray([0xC2, 0x30])) # .original rep #$30 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0004, bytearray([0xA9, 0xDD, 0x7F])) # lda #$7FDD + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0007, bytearray([0x5C, 0xF2, 0xAB, 0x00])) # jml $00ABF2 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x000B, bytearray([0xC2, 0x30])) # custom_palettes: rep #$30 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x000D, bytearray([0xA9, 0x70, 0xB1])) # lda #$B170 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0010, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0012, bytearray([0x64, 0x0C])) # stz !_ptr+$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0014, bytearray([0xA9, 0x10, 0x00])) # lda.w #$0010 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0017, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0019, bytearray([0xA9, 0x07, 0x00])) # lda #$0007 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x001C, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x001E, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0021, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0023, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0026, bytearray([0xAE, 0x0B, 0x01])) # .get_index ldx $010B + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0029, bytearray([0xBF, 0x00, 0xF2, 0x11])) # lda.l level_tilesets,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x002D, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0030, bytearray([0xEB])) # xba + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0031, bytearray([0x85, 0x00])) # sta !_tileset + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0033, bytearray([0xBF, 0x00, 0xF0, 0x11])) # lda.l level_index,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0037, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003A, bytearray([0x05, 0x00])) # ora !_tileset + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003C, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003E, bytearray([0x0A])) # asl + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003F, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0040, bytearray([0x65, 0x0A])) # adc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0042, bytearray([0x85, 0x0E])) # sta !_num + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0044, bytearray([0xAA])) # tax + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0045, snes_level_palette_pointers_1) # .back_color lda.l palette_pointers,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0049, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x004B, snes_level_palette_pointers_2) # lda.l palette_pointers+$02,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x004F, bytearray([0x85, 0x0C])) # sta !_ptr+$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0051, bytearray([0xA7, 0x0A])) # lda [!_ptr] + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0053, bytearray([0x8D, 0x01, 0x07])) # sta $0701 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0056, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0058, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x005A, bytearray([0xA9, 0x02, 0x00])) # .background lda.w #$0001*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x005D, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x005F, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0062, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0064, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0067, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0069, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x006C, bytearray([0xA9, 0x42, 0x00])) # .foreground lda.w #$0021*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x006F, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0071, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0074, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0076, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0079, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x007B, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x007E, bytearray([0xA9, 0x52, 0x00])) # .berries lda.w #$0029*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0081, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0083, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0086, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0088, bytearray([0xA9, 0x02, 0x00])) # lda #$0002 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x008B, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x008D, bytearray([0xA5, 0x0A])) # lda !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x008F, bytearray([0x48])) # pha + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0090, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0093, bytearray([0x68])) # pla + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0094, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0096, bytearray([0xA9, 0x32, 0x01])) # lda.w #$0099*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0099, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x009B, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x009E, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A0, bytearray([0xA9, 0x02, 0x00])) # lda #$0002 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A3, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A5, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A8, bytearray([0xA9, 0x82, 0x00])) # .global lda.w #$0041*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00AB, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00AD, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B0, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B2, bytearray([0xA9, 0x0B, 0x00])) # lda #$000B + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B5, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B7, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00BA, bytearray([0xA5, 0x00])) # .sprite_specific lda !_tileset + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00BC, bytearray([0xC9, 0x00, 0x05])) # cmp #$0500 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00BF, bytearray([0xD0, 0x1D])) # bne .end + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00C1, bytearray([0xAD, 0x2E, 0x19])) # lda $192E + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00C4, bytearray([0x29, 0x0F, 0x00])) # and #$000F + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00C7, bytearray([0xC9, 0x02, 0x00])) # cmp #$0002 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00CA, bytearray([0xD0, 0x12])) # bne .end + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00CC, bytearray([0xA9, 0xC2, 0x01])) # lda.w #$00E1*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00CF, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D1, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D4, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D6, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D9, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00DB, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00DE, bytearray([0xE2, 0x30])) # .end sep #$30 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E0, bytearray([0x5C, 0xEC, 0xAC, 0x00])) # jml $00ACEC + + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E4, bytearray([0xA6, 0x04])) # load_colors: ldx !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E6, bytearray([0xA4, 0x06])) # ldy !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E8, bytearray([0xA7, 0x0A])) # .x_loop lda [!_ptr] + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EA, bytearray([0x9D, 0x03, 0x07])) # sta $0703,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00ED, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EF, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F1, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F2, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F3, bytearray([0x88])) # dey + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F4, bytearray([0x10, 0xF2])) # bpl .x_loop + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F6, bytearray([0xA5, 0x04])) # lda !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F8, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F9, bytearray([0x69, 0x20, 0x00])) # adc #$0020 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FC, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FE, bytearray([0xC6, 0x08])) # dec !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0100, bytearray([0x10, 0xE2])) # bpl load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0102, bytearray([0x60])) # rts + + # Load palette paths + data = pkgutil.get_data(__name__, f"data/palettes/level/palettes.json").decode("utf-8") + tilesets = json.loads(data) + + # Writes the level tileset index to ROM + rom.write_bytes(PALETTE_LEVEL_TILESET_ADDR, bytearray(level_palette_index)) + + # Builds the table in ROM that holds the palette index for each level, including sublevels + for level_id in range(0x200): + tileset_num = level_palette_index[level_id] + if tileset_num != 0xFF: + tileset = tileset_names[tileset_num] + else: + tileset = tileset_names[0x19] + palette = world.random.randint(0, len(tilesets[tileset])-1) + rom.write_bytes(PALETTE_INDEX_ADDR + level_id, bytearray([palette])) + + # Writes the actual level palette data and pointer to said data to the ROM + pal_offset = 0x0000 + tileset_num = 0 + bank_palette_count = 0 + for tileset in tilesets.keys(): + for palette in range(len(tilesets[tileset])): + # Handle bank crossing + if bank_palette_count == 110: + pal_offset = (pal_offset & 0xF8000) + 0x8000 + bank_palette_count = 0 + # Write pointer + data_ptr = pc_to_snes(PALETTE_LEVEL_DATA_ADDR + pal_offset) + rom.write_bytes(PALETTE_LEVEL_PTR_ADDR + ((tileset_num*3)<<8) + (palette*3), bytearray([data_ptr & 0xFF, (data_ptr>>8)&0xFF, (data_ptr>>16)&0xFF])) + # Write data + rom.write_bytes(PALETTE_LEVEL_DATA_ADDR + pal_offset, read_palette_file(tileset, tilesets[tileset][palette], "level")) + pal_offset += 0x128 + bank_palette_count += 1 + tileset_num += 1 + + # Fix eaten berry tiles + EATEN_BERRY_ADDR = 0x68248 + rom.write_byte(EATEN_BERRY_ADDR + 0x01, 0x04) + rom.write_byte(EATEN_BERRY_ADDR + 0x03, 0x04) + rom.write_byte(EATEN_BERRY_ADDR + 0x05, 0x04) + rom.write_byte(EATEN_BERRY_ADDR + 0x07, 0x04) + + # Fix title screen changing background colors + rom.write_bytes(0x1D30, bytearray([0xEA, 0xEA, 0xEA])) + + # Skips level intros automatically + rom.write_byte(0x4896, 0x80) + +def generate_curated_map_palette_data(rom, world: World): + PALETTE_MAP_CODE_ADDR = 0x88200 + PALETTE_UPLOADER_EDIT = 0x88400 + PALETTE_MAP_INDEX_ADDR = 0x8F400 + PALETTE_MAP_PTR_ADDR = 0x90000 + PALETTE_MAP_DATA_ADDR = 0x98000 + + addr = pc_to_snes(PALETTE_MAP_PTR_ADDR) + snes_map_palette_pointers_1 = bytearray([0xBF, (addr)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + snes_map_palette_pointers_2 = bytearray([0xBF, (addr+2)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + + rom.write_bytes(0x02D25, bytearray([0x5C, 0x09, 0x82, 0x11])) # org $00AD25 : jml map_palettes + + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0000, bytearray([0xC2, 0x30])) # map_og_palettes: rep #$30 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0002, bytearray([0xA0, 0xD8, 0xB3])) # ldy #$B3D8 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0005, bytearray([0x5C, 0x2A, 0xAD, 0x00])) # jml $00AD2A + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0009, bytearray([0xC2, 0x30])) # map_palettes: rep #$30 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x000B, bytearray([0xAD, 0x31, 0x19])) # .prepare_index lda $1931 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x000E, bytearray([0x29, 0x0F, 0x00])) # and #$000F + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0011, bytearray([0x3A])) # dec + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0012, bytearray([0xAA])) # tax + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0013, bytearray([0xEB])) # xba + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0014, bytearray([0x85, 0x0E])) # sta !_num + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0016, bytearray([0xBF, 0x00, 0xF4, 0x11])) # lda.l map_index,x + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x001A, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x001D, bytearray([0x05, 0x0E])) # ora !_num + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x001F, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0021, bytearray([0x0A])) # asl + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0022, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0023, bytearray([0x65, 0x0A])) # adc !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0025, bytearray([0xAA])) # tax + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0026, snes_map_palette_pointers_1) # lda.l map_palette_pointers,x + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x002A, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x002C, snes_map_palette_pointers_2) # lda.l map_palette_pointers+$02,x + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0030, bytearray([0x85, 0x0C])) # sta !_ptr+$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0032, bytearray([0xA7, 0x0A])) # .load_back_color lda [!_ptr] + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0034, bytearray([0x8D, 0x01, 0x07])) # sta $0701 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0037, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0039, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x003B, bytearray([0xA9, 0x82, 0x00])) # .load_layer_2 lda.w #$0041*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x003E, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0040, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0043, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0045, bytearray([0xA9, 0x03, 0x00])) # lda #$0003 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0048, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x004A, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x004D, bytearray([0xA9, 0x52, 0x00])) # .load_layer_1 lda.w #$0029*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0050, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0052, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0055, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0057, bytearray([0xA9, 0x05, 0x00])) # lda #$0005 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x005A, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x005C, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x005F, bytearray([0xA9, 0x10, 0x00])) # .load_layer_3 lda.w #$0008*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0062, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0064, bytearray([0xA9, 0x07, 0x00])) # lda #$0007 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0067, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0069, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x006C, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x006E, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0071, bytearray([0xA9, 0x02, 0x01])) # .load_sprites lda.w #$0081*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0074, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0076, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0079, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x007B, bytearray([0xA9, 0x07, 0x00])) # lda #$0007 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x007E, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0080, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0083, bytearray([0x5C, 0xA3, 0xAD, 0x00])) # .return jml $00ADA3 + + rom.write_bytes(0x2488, bytearray([0x5C, 0x00, 0x84, 0x11])) # org $00A488 : jml palette_upload + + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0000, bytearray([0xAD, 0x00, 0x01])) # palette_upload: lda $0100 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0003, bytearray([0xC9, 0x0E])) # cmp #$0E + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0005, bytearray([0xF0, 0x0A])) # beq .map + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0007, bytearray([0xAC, 0x80, 0x06])) # .regular ldy $0680 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x000A, bytearray([0xBE, 0x81, 0xA4])) # ldx.w $A47F+2,y + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x000D, bytearray([0x5C, 0x8E, 0xA4, 0x00])) # jml $00A48E + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0011, bytearray([0xAD, 0xD9, 0x13])) # .map lda $13D9 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0014, bytearray([0xC9, 0x0A])) # cmp #$0A + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0016, bytearray([0xD0, 0xEF])) # bne .regular + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0018, bytearray([0xAD, 0xE8, 0x1D])) # lda $1DE8 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x001B, bytearray([0xC9, 0x06])) # cmp #$06 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x001D, bytearray([0xD0, 0xE8])) # bne .regular + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x001F, bytearray([0x9C, 0x03, 0x07])) # stz $0703 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0022, bytearray([0x9C, 0x04, 0x07])) # stz $0704 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0025, bytearray([0x9C, 0x21, 0x21])) # stz $2121 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0028, bytearray([0xA2, 0x06])) # ldx #$06 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x002A, bytearray([0xBD, 0x49, 0x92])) # .loop lda.w $9249,x + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x002D, bytearray([0x9D, 0x20, 0x43])) # sta $4320,x + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0030, bytearray([0xCA])) # dex + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0031, bytearray([0x10, 0xF7])) # bpl .loop + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0033, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0035, bytearray([0x8D, 0x0B, 0x42])) # sta $420B + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0038, bytearray([0x5C, 0xCF, 0xA4, 0x00])) # jml $00A4CF + + # Insert this piece of ASM again in case levels are disabled + PALETTE_LEVEL_CODE_ADDR = 0x88000 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E4, bytearray([0xA6, 0x04])) # load_colors: ldx !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E6, bytearray([0xA4, 0x06])) # ldy !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E8, bytearray([0xA7, 0x0A])) # .x_loop lda [!_ptr] + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EA, bytearray([0x9D, 0x03, 0x07])) # sta $0703,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00ED, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EF, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F1, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F2, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F3, bytearray([0x88])) # dey + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F4, bytearray([0x10, 0xF2])) # bpl .x_loop + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F6, bytearray([0xA5, 0x04])) # lda !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F8, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F9, bytearray([0x69, 0x20, 0x00])) # adc #$0020 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FC, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FE, bytearray([0xC6, 0x08])) # dec !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0100, bytearray([0x10, 0xE2])) # bpl load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0102, bytearray([0x60])) # rts + + # Load palette paths + data = pkgutil.get_data(__name__, f"data/palettes/map/palettes.json").decode("utf-8") + maps = json.loads(data) + + for map_id in range(0x07): + current_map_name = map_names[map_id] + palette = world.random.randint(0, len(maps[current_map_name])-1) + rom.write_bytes(PALETTE_MAP_INDEX_ADDR + map_id, bytearray([palette])) + + # Writes the actual map palette data and pointer to said data to the ROM + pal_offset = 0x0000 + map_num = 0 + bank_palette_count = 0 + for current_map in maps.keys(): + for palette in range(len(maps[current_map])): + # Handle bank crossing + if bank_palette_count == 113: + pal_offset = (pal_offset & 0xF8000) + 0x8000 + bank_palette_count = 0 + # Write pointer + data_ptr = pc_to_snes(PALETTE_MAP_DATA_ADDR + pal_offset) + rom.write_bytes(PALETTE_MAP_PTR_ADDR + ((map_num*3)<<8) + (palette*3), bytearray([data_ptr & 0xFF, (data_ptr>>8)&0xFF, (data_ptr>>16)&0xFF])) + # Write data + rom.write_bytes(PALETTE_MAP_DATA_ADDR + pal_offset, read_palette_file(current_map, maps[current_map][palette], "map")) + # Update map mario palette + chosen_palette = world.options.mario_palette.value + rom.write_bytes(PALETTE_MAP_DATA_ADDR + pal_offset + 206, bytes(ow_mario_palettes[chosen_palette])) + pal_offset += 0x11C + bank_palette_count += 1 + map_num += 1 + + +def pc_to_snes(address): + return ((address << 1) & 0x7F0000) | (address & 0x7FFF) | 0x8000 + +def read_palette_file(tileset, filename, type_): + palette_file = pkgutil.get_data(__name__, f"data/palettes/{type_}/{tileset}/{filename}") + colors = bytearray([]) + + # Copy back colors + colors += bytearray([palette_file[0x200], palette_file[0x201]]) + + if type_ == "level": + # Copy background colors + colors += bytearray([palette_file[(0x01*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x11*2)+(i)] for i in range(14)]) + + # Copy foreground colors + colors += bytearray([palette_file[(0x21*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x31*2)+(i)] for i in range(14)]) + + # Copy berry colors + colors += bytearray([palette_file[(0x29*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x39*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x49*2)+(i)] for i in range(14)]) + + # Copy global colors + colors += bytearray([palette_file[(0x41*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x51*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x61*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x71*2)+(i)] for i in range(14)]) + + # Copy sprite colors + colors += bytearray([palette_file[(0x81*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x91*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xA1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xB1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xC1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xD1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xE1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xF1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xE9*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xF9*2)+(i)] for i in range(14)]) + + elif type_ == "map": + # Copy layer 2 colors + colors += bytearray([palette_file[(0x41*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x51*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x61*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x71*2)+(i)] for i in range(14)]) + + # Copy layer 1 colors + colors += bytearray([palette_file[(0x29*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x39*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x49*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x59*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x69*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x79*2)+(i)] for i in range(14)]) + + # Copy layer 3 colors + colors += bytearray([palette_file[(0x08*2)+(i)] for i in range(16)]) + colors += bytearray([palette_file[(0x18*2)+(i)] for i in range(16)]) + + # Copy sprite colors + colors += bytearray([palette_file[(0x81*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x91*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xA1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xB1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xC1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xD1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xE1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xF1*2)+(i)] for i in range(14)]) + + return colors diff --git a/worlds/smw/CHANGELOG.md b/worlds/smw/CHANGELOG.md new file mode 100644 index 000000000000..7f62997adcef --- /dev/null +++ b/worlds/smw/CHANGELOG.md @@ -0,0 +1,118 @@ +# Super Mario World - Changelog + + +## v2.0 + +### Features: + +- New optional Location Checks + - 3-Up Moons + - Hidden 1-Ups + - Bonus Blocks + - Blocksanity + - All blocks that contain coins or items are included, with the exception of: + - Blocks in Top Secret Area & Front Door/Bowser Castle + - Blocks that are unreachable without glitches/unreasonable movement +- New Items + - Special Zone Clear + - New Filler Items + - 1 Coin + - 5 Coins + - 10 Coins + - 50 Coins + - New Trap Items + - Reverse Trap + - Thwimp Trap +- SFX Shuffle +- Palette Shuffle Overhaul + - New Curated Palette can now be used for the Overworld and Level Palette Shuffle options + - Foreground and Background Shuffle options have been merged into a single option +- Max possible Yoshi Egg value is 255 + - UI in-game is updated to handle 3-digits + - New `Display Received Item Popups` option: `progression_minus_yoshi_eggs` + +### Quality of Life: + +- In-Game Indicators are now displayed on the map screen for location checks and received items +- In-level sprites are displayed upon receiving certain items +- The Camera Scroll unlocking is now only enabled on levels where it needs to be +- SMW can now handle receiving more than 255 items +- Significant World Code cleanup + - New Options API + - Removal of `world: MultiWorld` across the world +- The PopTracker pack now has tabs for every level/sublevel, and can automatically swap tabs while playing if connected to the server + +### Bug Fixes: + +- Several logic tweaks/fixes + + +## v1.1 + +### Features: + +- New Item + - Timer Trap +- `Bowser Castle Rooms` option which changes the behavior of the Front Door +- `Boss Shuffle` option +- `Overworld Palette Shuffle` option + +### Quality of Life: + +- `Overworld Speed` option which allows the player to move faster or slower on the map +- `Early Climb` option which guarantees that `Climb` will be found locally in an early location +- `Exclude Special Zone` option which fully excludes Special Zone levels from the seed + - Note: this option is ignored if the player chooses to fill their entire item pool with Yoshi Eggs, which are Progression Items +- Ice and Stun Traps are now queued if received while another of its type is already active, and are then activated at the next opportunity + +### Bug Fixes: + +- Fixed `Chocolate Island 4 - Dragon Coins` requiring `Run` instead of `P-Switch` +- Fixed a Literature Trap typo + + +## v1.0 - First Stable Release + +### Features: + +- Goal + - Bowser + - Defeat bosses, reach Bowser's Castle, and defeat Bowser + - Yoshi Egg Hunt + - Find a certain number of Yoshi Eggs spread across the MultiWorld` +- Locations included: + - Level exits (Normal and Secret) + - Dragon Coins + - Collect at least five Dragon Coins in a level to send a location check +- Items included: + - Run + - Carry + - Swim + - Spin Jump + - Climb + - Yoshi + - P-Switch + - P-Balloon + - Progressive Powerup + - Unlocks the ability to use Mushrooms, Fire Flowers, and Capes, progressively + - Super Star Activate + - Yellow Switch Palace + - Green Switch Palace + - Red Switch Palace + - Blue Switch Palace + - 1-Up Mushroom + - Yoshi Egg + - Only on `Yoshi Egg Hunt` goal + - Traps + - Ice Trap + - Stun Trap + - Literature Trap +- `Bowser Castle Doors` option + - Whether the Front Door and Back Door map tiles lead to the Front Door or Back Door levels +- DeathLink is supported +- Level Shuffle is supported +- Autosave is supported +- Music Shuffle is supported +- Mario's palette can be selected +- Level Palettes can be shuffled +- Starting life count can be set diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py index 50899abe3774..600e1bff8304 100644 --- a/worlds/smw/Client.py +++ b/worlds/smw/Client.py @@ -1,5 +1,4 @@ import logging -import asyncio import time from NetUtils import ClientStatus, color @@ -17,11 +16,19 @@ SMW_ROMHASH_START = 0x7FC0 ROMHASH_SIZE = 0x15 -SMW_PROGRESS_DATA = WRAM_START + 0x1F02 -SMW_DRAGON_COINS_DATA = WRAM_START + 0x1F2F -SMW_PATH_DATA = WRAM_START + 0x1EA2 -SMW_EVENT_ROM_DATA = ROM_START + 0x2D608 -SMW_ACTIVE_LEVEL_DATA = ROM_START + 0x37F70 +SMW_PROGRESS_DATA = WRAM_START + 0x1F02 +SMW_DRAGON_COINS_DATA = WRAM_START + 0x1F2F +SMW_PATH_DATA = WRAM_START + 0x1EA2 +SMW_EVENT_ROM_DATA = ROM_START + 0x2D608 +SMW_ACTIVE_LEVEL_DATA = ROM_START + 0x37F70 +SMW_MOON_DATA = WRAM_START + 0x1FEE +SMW_HIDDEN_1UP_DATA = WRAM_START + 0x1F3C +SMW_BONUS_BLOCK_DATA = WRAM_START + 0x1A000 +SMW_BLOCKSANITY_DATA = WRAM_START + 0x1A400 +SMW_BLOCKSANITY_FLAGS = WRAM_START + 0x1A010 +SMW_LEVEL_CLEAR_FLAGS = WRAM_START + 0x1A200 +SMW_SPECIAL_WORLD_CLEAR = WRAM_START + 0x1F1E + SMW_GOAL_DATA = ROM_START + 0x01BFA0 SMW_REQUIRED_BOSSES_DATA = ROM_START + 0x01BFA1 @@ -31,28 +38,39 @@ SMW_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x01BFA5 SMW_DRAGON_COINS_ACTIVE_ADDR = ROM_START + 0x01BFA6 SMW_SWAMP_DONUT_GH_ADDR = ROM_START + 0x01BFA7 - -SMW_GAME_STATE_ADDR = WRAM_START + 0x100 -SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 -SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B -SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC -SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF -SMW_MESSAGE_BOX_ADDR = WRAM_START + 0x1426 -SMW_BONUS_STAR_ADDR = WRAM_START + 0xF48 -SMW_EGG_COUNT_ADDR = WRAM_START + 0x1F24 -SMW_BOSS_COUNT_ADDR = WRAM_START + 0x1F26 -SMW_NUM_EVENTS_ADDR = WRAM_START + 0x1F2E -SMW_SFX_ADDR = WRAM_START + 0x1DFC -SMW_PAUSE_ADDR = WRAM_START + 0x13D4 -SMW_MESSAGE_QUEUE_ADDR = WRAM_START + 0xC391 - -SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x1F2B - -SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] -SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] -SMW_BAD_TEXT_BOX_LEVELS = [0x00, 0x26, 0x02, 0x4B] -SMW_BOSS_STATES = [0x80, 0xC0, 0xC1] -SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A, 0x35, 0x34, 0x31, 0x32] +SMW_MOON_ACTIVE_ADDR = ROM_START + 0x01BFA8 +SMW_HIDDEN_1UP_ACTIVE_ADDR = ROM_START + 0x01BFA9 +SMW_BONUS_BLOCK_ACTIVE_ADDR = ROM_START + 0x01BFAA +SMW_BLOCKSANITY_ACTIVE_ADDR = ROM_START + 0x01BFAB + + +SMW_GAME_STATE_ADDR = WRAM_START + 0x100 +SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 +SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B +SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC +SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF +SMW_CURRENT_SUBLEVEL_ADDR = WRAM_START + 0x10B +SMW_MESSAGE_BOX_ADDR = WRAM_START + 0x1426 +SMW_BONUS_STAR_ADDR = WRAM_START + 0xF48 +SMW_EGG_COUNT_ADDR = WRAM_START + 0x1F24 +SMW_BOSS_COUNT_ADDR = WRAM_START + 0x1F26 +SMW_NUM_EVENTS_ADDR = WRAM_START + 0x1F2E +SMW_SFX_ADDR = WRAM_START + 0x1DFC +SMW_PAUSE_ADDR = WRAM_START + 0x13D4 +SMW_MESSAGE_QUEUE_ADDR = WRAM_START + 0xC391 +SMW_ACTIVE_THWIMP_ADDR = WRAM_START + 0x0F3C +SMW_GOAL_ITEM_COUNT = WRAM_START + 0x1A01E + +SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x01F2B + +SMW_BLOCKSANITY_BLOCK_COUNT = 582 + +SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] +SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] +SMW_BAD_TEXT_BOX_LEVELS = [0x00, 0x26, 0x02, 0x4B] +SMW_BOSS_STATES = [0x80, 0xC0, 0xC1] +SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A, 0x35, 0x34, 0x31, 0x32] +SMW_UNCOLLECTABLE_DRAGON_COINS = [0x24] class SMWSNIClient(SNIClient): @@ -115,6 +133,9 @@ async def validate_rom(self, ctx): if death_link: await ctx.update_death_link(bool(death_link[0] & 0b1)) + if ctx.rom != rom_name: + ctx.current_sublevel_value = 0 + ctx.rom = rom_name return True @@ -176,6 +197,11 @@ def add_trap_to_queue(self, trap_item, trap_msg): self.trap_queue.append((trap_item, trap_msg)) + def should_show_message(self, ctx, next_item): + return ctx.receive_option == 1 or \ + (ctx.receive_option == 2 and ((next_item.flags & 1) != 0)) or \ + (ctx.receive_option == 3 and ((next_item.flags & 1) != 0 and next_item.item != 0xBC0002)) + async def handle_trap_queue(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read @@ -197,7 +223,7 @@ async def handle_trap_queue(self, ctx): next_trap, message = self.trap_queue.pop(0) - from worlds.smw.Rom import trap_rom_data + from .Rom import trap_rom_data if next_trap.item in trap_rom_data: trap_active = await snes_read(ctx, WRAM_START + trap_rom_data[next_trap.item][0], 0x3) @@ -217,6 +243,13 @@ async def handle_trap_queue(self, ctx): self.add_trap_to_queue(next_trap, message) return else: + if next_trap.item == 0xBC001D: + # Special case thwimp trap + # Do not fire if the previous thwimp hasn't reached the player's Y pos + active_thwimp = await snes_read(ctx, SMW_ACTIVE_THWIMP_ADDR, 0x1) + if active_thwimp[0] != 0xFF: + self.add_trap_to_queue(next_trap, message) + return verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) if verify_game_state[0] == 0x14 and len(trap_rom_data[next_trap.item]) > 2: snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([trap_rom_data[next_trap.item][2]])) @@ -236,13 +269,14 @@ async def handle_trap_queue(self, ctx): if active_boss[0] != 0x00: return - if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((next_trap.flags & 1) != 0)): + if self.should_show_message(ctx, next_trap): self.add_message_to_queue(message) async def game_watcher(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read - + + boss_state = await snes_read(ctx, SMW_BOSS_STATE_ADDR, 0x1) game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) if game_state is None: @@ -259,6 +293,7 @@ async def game_watcher(self, ctx): elif game_state[0] < 0x0B: # We haven't loaded a save file ctx.message_queue = [] + ctx.current_sublevel_value = 0 return elif mario_state[0] in SMW_INVALID_MARIO_STATES: # Mario can't come to the phone right now @@ -304,8 +339,18 @@ async def game_watcher(self, ctx): progress_data = bytearray(await snes_read(ctx, SMW_PROGRESS_DATA, 0x0F)) dragon_coins_data = bytearray(await snes_read(ctx, SMW_DRAGON_COINS_DATA, 0x0C)) dragon_coins_active = await snes_read(ctx, SMW_DRAGON_COINS_ACTIVE_ADDR, 0x1) - from worlds.smw.Rom import item_rom_data, ability_rom_data, trap_rom_data - from worlds.smw.Levels import location_id_to_level_id, level_info_dict + moon_data = bytearray(await snes_read(ctx, SMW_MOON_DATA, 0x0C)) + moon_active = await snes_read(ctx, SMW_MOON_ACTIVE_ADDR, 0x1) + hidden_1up_data = bytearray(await snes_read(ctx, SMW_HIDDEN_1UP_DATA, 0x0C)) + hidden_1up_active = await snes_read(ctx, SMW_HIDDEN_1UP_ACTIVE_ADDR, 0x1) + bonus_block_data = bytearray(await snes_read(ctx, SMW_BONUS_BLOCK_DATA, 0x0C)) + bonus_block_active = await snes_read(ctx, SMW_BONUS_BLOCK_ACTIVE_ADDR, 0x1) + blocksanity_data = bytearray(await snes_read(ctx, SMW_BLOCKSANITY_DATA, SMW_BLOCKSANITY_BLOCK_COUNT)) + blocksanity_flags = bytearray(await snes_read(ctx, SMW_BLOCKSANITY_FLAGS, 0xC)) + blocksanity_active = await snes_read(ctx, SMW_BLOCKSANITY_ACTIVE_ADDR, 0x1) + level_clear_flags = bytearray(await snes_read(ctx, SMW_LEVEL_CLEAR_FLAGS, 0x60)) + from .Rom import item_rom_data, ability_rom_data, trap_rom_data, icon_rom_data + from .Levels import location_id_to_level_id, level_info_dict, level_blocks_data from worlds import AutoWorldRegister for loc_name, level_data in location_id_to_level_id.items(): loc_id = AutoWorldRegister.world_types[ctx.game].location_name_to_id[loc_name] @@ -327,6 +372,54 @@ async def game_watcher(self, ctx): if bit_set: new_checks.append(loc_id) + elif level_data[1] == 3: + # Moon Check + if not moon_active or moon_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = moon_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + new_checks.append(loc_id) + elif level_data[1] == 4: + # Hidden 1-Up Check + if not hidden_1up_active or hidden_1up_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = hidden_1up_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + new_checks.append(loc_id) + elif level_data[1] == 5: + # Bonus Block Check + if not bonus_block_active or bonus_block_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = bonus_block_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + new_checks.append(loc_id) + elif level_data[1] >= 100: + if not blocksanity_active or blocksanity_active[0] == 0: + continue + block_index = level_data[1] - 100 + if blocksanity_data[block_index] != 0: + new_checks.append(loc_id) else: event_id_value = event_id + level_data[1] @@ -355,38 +448,75 @@ async def game_watcher(self, ctx): for new_check_id in new_checks: ctx.locations_checked.add(new_check_id) - location = ctx.location_names[new_check_id] + location = ctx.location_names.lookup_in_game(new_check_id) snes_logger.info( f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + # Send Current Room for Tracker + current_sublevel_data = await snes_read(ctx, SMW_CURRENT_SUBLEVEL_ADDR, 2) + current_sublevel_value = current_sublevel_data[0] + (current_sublevel_data[1] << 8) + + if game_state[0] != 0x14: + current_sublevel_value = 0 + + if ctx.current_sublevel_value != current_sublevel_value: + ctx.current_sublevel_value = current_sublevel_value + + # Send level id data to tracker + await ctx.send_msgs( + [ + { + "cmd": "Set", + "key": f"smw_curlevelid_{ctx.team}_{ctx.slot}", + "default": 0, + "want_reply": False, + "operations": [ + { + "operation": "replace", + "value": ctx.current_sublevel_value, + } + ], + } + ] + ) + if game_state[0] != 0x14: # Don't receive items or collect locations outside of in-level mode + ctx.current_sublevel_value = 0 + return + + if boss_state[0] in SMW_BOSS_STATES: + # Don't receive items or collect locations inside boss battles return - recv_count = await snes_read(ctx, SMW_RECV_PROGRESS_ADDR, 1) - recv_index = recv_count[0] + recv_count = await snes_read(ctx, SMW_RECV_PROGRESS_ADDR, 2) + if recv_count is None: + # Add a small failsafe in case we get a None. Other SNI games do this... + return + recv_index = recv_count[0] | (recv_count[1] << 8) if recv_index < len(ctx.items_received): item = ctx.items_received[recv_index] recv_index += 1 + sending_game = ctx.slot_info[item.player].game logging.info('Received %s from %s (%s) (%d/%d in list)' % ( - color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - ctx.location_names[item.location], recv_index, len(ctx.items_received))) + ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received))) - if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((item.flags & 1) != 0)): + if self.should_show_message(ctx, item): if item.item != 0xBC0012 and item.item not in trap_rom_data: # Don't send messages for Boss Tokens - item_name = ctx.item_names[item.item] + item_name = ctx.item_names.lookup_in_game(item.item) player_name = ctx.player_names[item.player] receive_message = generate_received_text(item_name, player_name) self.add_message_to_queue(receive_message) - snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index])) + snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF])) if item.item in trap_rom_data: - item_name = ctx.item_names[item.item] + item_name = ctx.item_names.lookup_in_game(item.item) player_name = ctx.player_names[item.player] receive_message = generate_received_text(item_name, player_name) @@ -405,6 +535,15 @@ async def game_watcher(self, ctx): snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([item_rom_data[item.item][2]])) snes_buffered_write(ctx, WRAM_START + item_rom_data[item.item][0], bytes([new_item_count])) + elif item.item in icon_rom_data: + queue_addr = await snes_read(ctx, WRAM_START + icon_rom_data[item.item][0], 2) + queue_addr = queue_addr[0] + (queue_addr[1] << 8) + queue_addr += 1 + snes_buffered_write(ctx, WRAM_START + icon_rom_data[item.item][0], bytes([queue_addr&0xFF, (queue_addr>>8)&0xFF])) + if (goal[0] == 0 and item.item == 0xBC0012) or (goal[0] == 1 and item.item == 0xBC0002): + goal_item_count = await snes_read(ctx, SMW_GOAL_ITEM_COUNT, 1) + snes_buffered_write(ctx, SMW_GOAL_ITEM_COUNT, bytes([goal_item_count[0] + 1])) + elif item.item in ability_rom_data: # Handle Upgrades for rom_data in ability_rom_data[item.item]: @@ -449,10 +588,16 @@ async def game_watcher(self, ctx): path_data = bytearray(await snes_read(ctx, SMW_PATH_DATA, 0x60)) donut_gh_swapped = await snes_read(ctx, SMW_SWAMP_DONUT_GH_ADDR, 0x1) new_dragon_coin = False + new_moon = False + new_hidden_1up = False + new_bonus_block = False + new_blocksanity = False + new_blocksanity_flags = False + for loc_id in ctx.checked_locations: if loc_id not in ctx.locations_checked: ctx.locations_checked.add(loc_id) - loc_name = ctx.location_names[loc_id] + loc_name = ctx.location_names.lookup_in_game(loc_id) if loc_name not in location_id_to_level_id: continue @@ -461,6 +606,8 @@ async def game_watcher(self, ctx): if level_data[1] == 2: # Dragon Coins Check + if level_data[0] in SMW_UNCOLLECTABLE_DRAGON_COINS: + continue progress_byte = (level_data[0] // 8) progress_bit = 7 - (level_data[0] % 8) @@ -470,10 +617,64 @@ async def game_watcher(self, ctx): dragon_coins_data[progress_byte] = new_data new_dragon_coin = True + elif level_data[1] == 3: + # Moon Check + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = moon_data[progress_byte] + new_data = data | (1 << progress_bit) + moon_data[progress_byte] = new_data + + new_moon = True + elif level_data[1] == 4: + # Hidden 1-Up Check + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = hidden_1up_data[progress_byte] + new_data = data | (1 << progress_bit) + hidden_1up_data[progress_byte] = new_data + + new_hidden_1up = True + elif level_data[1] == 5: + # Bonus block prize Check + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = bonus_block_data[progress_byte] + new_data = data | (1 << progress_bit) + bonus_block_data[progress_byte] = new_data + + new_bonus_block = True + elif level_data[1] >= 100: + # Blocksanity flag Check + block_index = level_data[1] - 100 + blocksanity_data[block_index] = 1 + new_blocksanity = True + + # All blocksanity blocks flag + new_blocksanity_flags = True + for block_id in level_blocks_data[level_data[0]]: + if blocksanity_data[block_id] != 1: + new_blocksanity_flags = False + continue + if new_blocksanity_flags is True: + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + data = blocksanity_flags[progress_byte] + new_data = data | (1 << progress_bit) + blocksanity_flags[progress_byte] = new_data else: if level_data[0] in SMW_UNCOLLECTABLE_LEVELS: continue + # Handle map indicators + flag = 1 if level_data[1] == 0 else 2 + level_clear_flags[level_data[0]] |= flag + event_id = event_data[level_data[0]] event_id_value = event_id + level_data[1] @@ -514,7 +715,18 @@ async def game_watcher(self, ctx): if new_dragon_coin: snes_buffered_write(ctx, SMW_DRAGON_COINS_DATA, bytes(dragon_coins_data)) + if new_moon: + snes_buffered_write(ctx, SMW_MOON_DATA, bytes(moon_data)) + if new_hidden_1up: + snes_buffered_write(ctx, SMW_HIDDEN_1UP_DATA, bytes(hidden_1up_data)) + if new_bonus_block: + snes_buffered_write(ctx, SMW_BONUS_BLOCK_DATA, bytes(bonus_block_data)) + if new_blocksanity: + snes_buffered_write(ctx, SMW_BLOCKSANITY_DATA, bytes(blocksanity_data)) + if new_blocksanity_flags: + snes_buffered_write(ctx, SMW_BLOCKSANITY_FLAGS, bytes(blocksanity_flags)) if new_events > 0: + snes_buffered_write(ctx, SMW_LEVEL_CLEAR_FLAGS, bytes(level_clear_flags)) snes_buffered_write(ctx, SMW_PROGRESS_DATA, bytes(progress_data)) snes_buffered_write(ctx, SMW_PATH_DATA, bytes(path_data)) old_events = await snes_read(ctx, SMW_NUM_EVENTS_ADDR, 0x1) diff --git a/worlds/smw/Items.py b/worlds/smw/Items.py index 5b6cce5a7f6e..eaf58b9b8e4e 100644 --- a/worlds/smw/Items.py +++ b/worlds/smw/Items.py @@ -18,6 +18,10 @@ class SMWItem(Item): # Separate tables for each type of item. junk_table = { + ItemName.one_coin: ItemData(0xBC0017, False), + ItemName.five_coins: ItemData(0xBC0018, False), + ItemName.ten_coins: ItemData(0xBC0019, False), + ItemName.fifty_coins: ItemData(0xBC001A, False), ItemName.one_up_mushroom: ItemData(0xBC0001, False), } @@ -36,6 +40,7 @@ class SMWItem(Item): ItemName.progressive_powerup: ItemData(0xBC000A, True), ItemName.p_balloon: ItemData(0xBC000B, True), ItemName.super_star_active: ItemData(0xBC000D, True), + ItemName.special_world_clear: ItemData(0xBC001B, True), } switch_palace_table = { @@ -46,10 +51,12 @@ class SMWItem(Item): } trap_table = { - ItemName.ice_trap: ItemData(0xBC0013, False, True), - ItemName.stun_trap: ItemData(0xBC0014, False, True), - ItemName.literature_trap: ItemData(0xBC0015, False, True), - ItemName.timer_trap: ItemData(0xBC0016, False, True), + ItemName.ice_trap: ItemData(0xBC0013, False, True), + ItemName.stun_trap: ItemData(0xBC0014, False, True), + ItemName.literature_trap: ItemData(0xBC0015, False, True), + ItemName.timer_trap: ItemData(0xBC0016, False, True), + ItemName.reverse_controls_trap: ItemData(0xBC001C, False, True), + ItemName.thwimp_trap: ItemData(0xBC001D, False, True), } event_table = { diff --git a/worlds/smw/Levels.py b/worlds/smw/Levels.py index 3940a08c7c5b..7aa9428b9110 100644 --- a/worlds/smw/Levels.py +++ b/worlds/smw/Levels.py @@ -1,4 +1,5 @@ +from worlds.AutoWorld import World from .Names import LocationName @@ -75,6 +76,103 @@ def __init__(self, name: str, exitAddress: int, roomID: int, exitAddressAlt=None ] +level_blocks_data = { + 0x01: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + 0x02: [12, 13], + 0x04: [14, 15, 16, 17, 18, 19], + 0x05: [20, 21, 22, 23, 24, 25], + 0x06: [26, 27, 28, 29], + 0x07: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + 0x09: [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], + 0x0A: [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], + 0x0B: [60, 61, 62], + 0x0C: [63, 64, 65, 66, 67, 68], + 0x0D: [69, 70, 71], + 0x0E: [72], + 0x0F: [73, 74, 75, 76], + 0x10: [77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111 + ], + 0x11: [112], + 0x13: [113, 114, 115, 116, 117], + 0x15: [118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140 + ], + 0x18: [141, 142], + 0x1A: [143, 144, 145], + 0x1B: [146, 147, 148, 149, 150], + 0x1C: [151, 152, 153, 154], + 0x1D: [155, 156, 157], + 0x1F: [158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168], + 0x20: [169], + 0x21: [170, 171, 172], + 0x22: [173, 174, 175, 176, 177], + 0x23: [178, 179, 180, 181, 182, 183, 184, 185, 186], + 0x24: [187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202 + ], + 0x25: [203, 204, 205, 206, 207, 208], + 0x26: [209, 210, 211, 212], + 0x27: [213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229 + ], + 0x29: [230, 231, 232, 233], + 0x2A: [234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, + 248, 249 + ], + 0x2B: [250, 251, 252, 253, 254], + 0x2D: [255, 256, 257, 258, 259, 260, 261, 262], + 0x2E: [263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, + 277, 278, 279 + ], + 0x2F: [280, 281, 282, 283, 284], + 0x33: [285, 286, 287, 288, 289, 290], + 0x34: [291, 292, 293], + 0x35: [294, 295], + 0x37: [296, 297], + 0x38: [298, 299, 300, 301], + 0x39: [302, 303, 304, 305], + 0x3A: [306, 307, 308, 309, 310, 311, 312, 313, 314], + 0x3B: [315, 316], + 0x3C: [317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330], + 0x3D: [331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341], + 0x3E: [342, 343, 344, 345, 346, 347, 348, 349, 350, 351], + 0x40: [352, 353, 354, 355, 356], + 0x41: [357, 358, 359, 360, 361], + 0x42: [362, 363, 364, 365, 366], + 0x43: [367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379], + 0x44: [380, 381, 382, 383, 384, 385, 386], + 0x46: [387, 388, 389], + 0x47: [390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, + 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416 + ], + 0x49: [417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, + 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446 + ], + 0x4A: [447, 448, 449, 450, 451], + 0x4B: [452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, + 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, + 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489 + ], + 0x4C: [490], + 0x4E: [491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, + 505, 506, 507, 508, 509, 510, 511, 512 + ], + 0x4F: [513, 514, 515, 516, 517, 518, 519, 520, 521, 522], + 0x50: [523, 524, 525], + 0x51: [526, 527], + 0x54: [528], + 0x56: [529], + 0x59: [530, 531, 532, 533, 534, 535, 536, 537, 538], + 0x5A: [539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, + 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, + 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, + 579, 580, 581 + ] +} + class SMWPath(): thisEndDirection: int otherLevelID: int @@ -330,12 +428,15 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 location_id_to_level_id = { LocationName.yoshis_island_1_exit_1: [0x29, 0], LocationName.yoshis_island_1_dragon: [0x29, 2], + LocationName.yoshis_island_1_moon: [0x29, 3], LocationName.yoshis_island_2_exit_1: [0x2A, 0], LocationName.yoshis_island_2_dragon: [0x2A, 2], LocationName.yoshis_island_3_exit_1: [0x27, 0], LocationName.yoshis_island_3_dragon: [0x27, 2], + LocationName.yoshis_island_3_bonus_block: [0x27, 5], LocationName.yoshis_island_4_exit_1: [0x26, 0], LocationName.yoshis_island_4_dragon: [0x26, 2], + LocationName.yoshis_island_4_hidden_1up: [0x26, 4], LocationName.yoshis_island_castle: [0x25, 0], LocationName.yoshis_island_koopaling: [0x25, 0], LocationName.yellow_switch_palace: [0x14, 0], @@ -343,13 +444,17 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.donut_plains_1_exit_1: [0x15, 0], LocationName.donut_plains_1_exit_2: [0x15, 1], LocationName.donut_plains_1_dragon: [0x15, 2], + LocationName.donut_plains_1_hidden_1up: [0x15, 4], LocationName.donut_plains_2_exit_1: [0x09, 0], LocationName.donut_plains_2_exit_2: [0x09, 1], LocationName.donut_plains_2_dragon: [0x09, 2], LocationName.donut_plains_3_exit_1: [0x05, 0], LocationName.donut_plains_3_dragon: [0x05, 2], + LocationName.donut_plains_3_bonus_block: [0x05, 5], LocationName.donut_plains_4_exit_1: [0x06, 0], LocationName.donut_plains_4_dragon: [0x06, 2], + LocationName.donut_plains_4_moon: [0x06, 3], + LocationName.donut_plains_4_hidden_1up: [0x06, 4], LocationName.donut_secret_1_exit_1: [0x0A, 0], LocationName.donut_secret_1_exit_2: [0x0A, 1], LocationName.donut_secret_1_dragon: [0x0A, 2], @@ -360,6 +465,7 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.donut_secret_house_exit_1: [0x13, 0], LocationName.donut_secret_house_exit_2: [0x13, 1], LocationName.donut_plains_castle: [0x07, 0], + LocationName.donut_plains_castle_hidden_1up: [0x07, 4], LocationName.donut_plains_koopaling: [0x07, 0], LocationName.green_switch_palace: [0x08, 0], @@ -371,8 +477,10 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.vanilla_dome_2_dragon: [0x3C, 2], LocationName.vanilla_dome_3_exit_1: [0x2E, 0], LocationName.vanilla_dome_3_dragon: [0x2E, 2], + LocationName.vanilla_dome_3_moon: [0x2E, 3], LocationName.vanilla_dome_4_exit_1: [0x3D, 0], LocationName.vanilla_dome_4_dragon: [0x3D, 2], + LocationName.vanilla_dome_4_hidden_1up: [0x3D, 4], LocationName.vanilla_secret_1_exit_1: [0x2D, 0], LocationName.vanilla_secret_1_exit_2: [0x2D, 1], LocationName.vanilla_secret_1_dragon: [0x2D, 2], @@ -382,7 +490,9 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.vanilla_secret_3_dragon: [0x02, 2], LocationName.vanilla_ghost_house_exit_1: [0x2B, 0], LocationName.vanilla_ghost_house_dragon: [0x2B, 2], + LocationName.vanilla_ghost_house_hidden_1up: [0x2B, 4], LocationName.vanilla_fortress: [0x0B, 0], + LocationName.vanilla_fortress_hidden_1up: [0x0B, 4], LocationName.vanilla_reznor: [0x0B, 0], LocationName.vanilla_dome_castle: [0x40, 0], LocationName.vanilla_dome_koopaling: [0x40, 0], @@ -390,13 +500,16 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.butter_bridge_1_exit_1: [0x0C, 0], LocationName.butter_bridge_1_dragon: [0x0C, 2], + LocationName.butter_bridge_1_bonus_block: [0x0C, 5], LocationName.butter_bridge_2_exit_1: [0x0D, 0], LocationName.butter_bridge_2_dragon: [0x0D, 2], LocationName.cheese_bridge_exit_1: [0x0F, 0], LocationName.cheese_bridge_exit_2: [0x0F, 1], LocationName.cheese_bridge_dragon: [0x0F, 2], + LocationName.cheese_bridge_moon: [0x0F, 3], LocationName.cookie_mountain_exit_1: [0x10, 0], LocationName.cookie_mountain_dragon: [0x10, 2], + LocationName.cookie_mountain_hidden_1up: [0x10, 4], LocationName.soda_lake_exit_1: [0x11, 0], LocationName.soda_lake_dragon: [0x11, 2], LocationName.twin_bridges_castle: [0x0E, 0], @@ -410,12 +523,14 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.forest_of_illusion_3_exit_1: [0x47, 0], LocationName.forest_of_illusion_3_exit_2: [0x47, 1], LocationName.forest_of_illusion_3_dragon: [0x47, 2], + LocationName.forest_of_illusion_3_hidden_1up: [0x47, 4], LocationName.forest_of_illusion_4_exit_1: [0x43, 0], LocationName.forest_of_illusion_4_exit_2: [0x43, 1], LocationName.forest_of_illusion_4_dragon: [0x43, 2], LocationName.forest_ghost_house_exit_1: [0x41, 0], LocationName.forest_ghost_house_exit_2: [0x41, 1], LocationName.forest_ghost_house_dragon: [0x41, 2], + LocationName.forest_ghost_house_moon: [0x41, 3], LocationName.forest_secret_exit_1: [0x46, 0], LocationName.forest_secret_dragon: [0x46, 2], LocationName.forest_fortress: [0x1F, 0], @@ -427,12 +542,15 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.chocolate_island_1_exit_1: [0x22, 0], LocationName.chocolate_island_1_dragon: [0x22, 2], + LocationName.chocolate_island_1_moon: [0x22, 3], LocationName.chocolate_island_2_exit_1: [0x24, 0], LocationName.chocolate_island_2_exit_2: [0x24, 1], LocationName.chocolate_island_2_dragon: [0x24, 2], + LocationName.chocolate_island_2_hidden_1up: [0x24, 4], LocationName.chocolate_island_3_exit_1: [0x23, 0], LocationName.chocolate_island_3_exit_2: [0x23, 1], LocationName.chocolate_island_3_dragon: [0x23, 2], + LocationName.chocolate_island_3_bonus_block: [0x23, 5], LocationName.chocolate_island_4_exit_1: [0x1D, 0], LocationName.chocolate_island_4_dragon: [0x1D, 2], LocationName.chocolate_island_5_exit_1: [0x1C, 0], @@ -442,6 +560,7 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.chocolate_fortress: [0x1B, 0], LocationName.chocolate_reznor: [0x1B, 0], LocationName.chocolate_castle: [0x1A, 0], + LocationName.chocolate_castle_hidden_1up: [0x1A, 4], LocationName.chocolate_koopaling: [0x1A, 0], LocationName.sunken_ghost_ship: [0x18, 0], @@ -449,9 +568,11 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.valley_of_bowser_1_exit_1: [0x3A, 0], LocationName.valley_of_bowser_1_dragon: [0x3A, 2], + LocationName.valley_of_bowser_1_moon: [0x3A, 3], LocationName.valley_of_bowser_2_exit_1: [0x39, 0], LocationName.valley_of_bowser_2_exit_2: [0x39, 1], LocationName.valley_of_bowser_2_dragon: [0x39, 2], + LocationName.valley_of_bowser_2_hidden_1up: [0x39, 4], LocationName.valley_of_bowser_3_exit_1: [0x37, 0], LocationName.valley_of_bowser_3_dragon: [0x37, 2], LocationName.valley_of_bowser_4_exit_1: [0x33, 0], @@ -464,6 +585,7 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.valley_castle: [0x34, 0], LocationName.valley_koopaling: [0x34, 0], LocationName.valley_castle_dragon: [0x34, 2], + LocationName.valley_castle_hidden_1up: [0x34, 4], LocationName.star_road_1_exit_1: [0x58, 0], LocationName.star_road_1_exit_2: [0x58, 1], @@ -479,6 +601,7 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.special_zone_1_exit_1: [0x4E, 0], LocationName.special_zone_1_dragon: [0x4E, 2], + LocationName.special_zone_1_hidden_1up: [0x4E, 4], LocationName.special_zone_2_exit_1: [0x4F, 0], LocationName.special_zone_2_dragon: [0x4F, 2], LocationName.special_zone_3_exit_1: [0x50, 0], @@ -493,19 +616,602 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.special_zone_7_dragon: [0x4A, 2], LocationName.special_zone_8_exit_1: [0x49, 0], LocationName.special_zone_8_dragon: [0x49, 2], + + LocationName.vanilla_secret_2_yoshi_block_1: [0x01, 100], + LocationName.vanilla_secret_2_green_block_1: [0x01, 101], + LocationName.vanilla_secret_2_powerup_block_1: [0x01, 102], + LocationName.vanilla_secret_2_powerup_block_2: [0x01, 103], + LocationName.vanilla_secret_2_multi_coin_block_1: [0x01, 104], + LocationName.vanilla_secret_2_gray_pow_block_1: [0x01, 105], + LocationName.vanilla_secret_2_coin_block_1: [0x01, 106], + LocationName.vanilla_secret_2_coin_block_2: [0x01, 107], + LocationName.vanilla_secret_2_coin_block_3: [0x01, 108], + LocationName.vanilla_secret_2_coin_block_4: [0x01, 109], + LocationName.vanilla_secret_2_coin_block_5: [0x01, 110], + LocationName.vanilla_secret_2_coin_block_6: [0x01, 111], + LocationName.vanilla_secret_3_powerup_block_1: [0x02, 112], + LocationName.vanilla_secret_3_powerup_block_2: [0x02, 113], + LocationName.donut_ghost_house_vine_block_1: [0x04, 114], + LocationName.donut_ghost_house_directional_coin_block_1: [0x04, 115], + LocationName.donut_ghost_house_life_block_1: [0x04, 116], + LocationName.donut_ghost_house_life_block_2: [0x04, 117], + LocationName.donut_ghost_house_life_block_3: [0x04, 118], + LocationName.donut_ghost_house_life_block_4: [0x04, 119], + LocationName.donut_plains_3_green_block_1: [0x05, 120], + LocationName.donut_plains_3_coin_block_1: [0x05, 121], + LocationName.donut_plains_3_coin_block_2: [0x05, 122], + LocationName.donut_plains_3_vine_block_1: [0x05, 123], + LocationName.donut_plains_3_powerup_block_1: [0x05, 124], + LocationName.donut_plains_3_bonus_block_1: [0x05, 125], + LocationName.donut_plains_4_coin_block_1: [0x06, 126], + LocationName.donut_plains_4_powerup_block_1: [0x06, 127], + LocationName.donut_plains_4_coin_block_2: [0x06, 128], + LocationName.donut_plains_4_yoshi_block_1: [0x06, 129], + LocationName.donut_plains_castle_yellow_block_1: [0x07, 130], + LocationName.donut_plains_castle_coin_block_1: [0x07, 131], + LocationName.donut_plains_castle_powerup_block_1: [0x07, 132], + LocationName.donut_plains_castle_coin_block_2: [0x07, 133], + LocationName.donut_plains_castle_vine_block_1: [0x07, 134], + LocationName.donut_plains_castle_invis_life_block_1: [0x07, 135], + LocationName.donut_plains_castle_coin_block_3: [0x07, 136], + LocationName.donut_plains_castle_coin_block_4: [0x07, 137], + LocationName.donut_plains_castle_coin_block_5: [0x07, 138], + LocationName.donut_plains_castle_green_block_1: [0x07, 139], + LocationName.donut_plains_2_coin_block_1: [0x09, 140], + LocationName.donut_plains_2_coin_block_2: [0x09, 141], + LocationName.donut_plains_2_coin_block_3: [0x09, 142], + LocationName.donut_plains_2_yellow_block_1: [0x09, 143], + LocationName.donut_plains_2_powerup_block_1: [0x09, 144], + LocationName.donut_plains_2_multi_coin_block_1: [0x09, 145], + LocationName.donut_plains_2_flying_block_1: [0x09, 146], + LocationName.donut_plains_2_green_block_1: [0x09, 147], + LocationName.donut_plains_2_yellow_block_2: [0x09, 148], + LocationName.donut_plains_2_vine_block_1: [0x09, 149], + LocationName.donut_secret_1_coin_block_1: [0x0A, 150], + LocationName.donut_secret_1_coin_block_2: [0x0A, 151], + LocationName.donut_secret_1_powerup_block_1: [0x0A, 152], + LocationName.donut_secret_1_coin_block_3: [0x0A, 153], + LocationName.donut_secret_1_powerup_block_2: [0x0A, 154], + LocationName.donut_secret_1_powerup_block_3: [0x0A, 155], + LocationName.donut_secret_1_life_block_1: [0x0A, 156], + LocationName.donut_secret_1_powerup_block_4: [0x0A, 157], + LocationName.donut_secret_1_powerup_block_5: [0x0A, 158], + LocationName.donut_secret_1_key_block_1: [0x0A, 159], + LocationName.vanilla_fortress_powerup_block_1: [0x0B, 160], + LocationName.vanilla_fortress_powerup_block_2: [0x0B, 161], + LocationName.vanilla_fortress_yellow_block_1: [0x0B, 162], + LocationName.butter_bridge_1_powerup_block_1: [0x0C, 163], + LocationName.butter_bridge_1_multi_coin_block_1: [0x0C, 164], + LocationName.butter_bridge_1_multi_coin_block_2: [0x0C, 165], + LocationName.butter_bridge_1_multi_coin_block_3: [0x0C, 166], + LocationName.butter_bridge_1_life_block_1: [0x0C, 167], + LocationName.butter_bridge_1_bonus_block_1: [0x0C, 168], + LocationName.butter_bridge_2_powerup_block_1: [0x0D, 169], + LocationName.butter_bridge_2_green_block_1: [0x0D, 170], + LocationName.butter_bridge_2_yoshi_block_1: [0x0D, 171], + LocationName.twin_bridges_castle_powerup_block_1: [0x0E, 172], + LocationName.cheese_bridge_powerup_block_1: [0x0F, 173], + LocationName.cheese_bridge_powerup_block_2: [0x0F, 174], + LocationName.cheese_bridge_wings_block_1: [0x0F, 175], + LocationName.cheese_bridge_powerup_block_3: [0x0F, 176], + LocationName.cookie_mountain_coin_block_1: [0x10, 177], + LocationName.cookie_mountain_coin_block_2: [0x10, 178], + LocationName.cookie_mountain_coin_block_3: [0x10, 179], + LocationName.cookie_mountain_coin_block_4: [0x10, 180], + LocationName.cookie_mountain_coin_block_5: [0x10, 181], + LocationName.cookie_mountain_coin_block_6: [0x10, 182], + LocationName.cookie_mountain_coin_block_7: [0x10, 183], + LocationName.cookie_mountain_coin_block_8: [0x10, 184], + LocationName.cookie_mountain_coin_block_9: [0x10, 185], + LocationName.cookie_mountain_powerup_block_1: [0x10, 186], + LocationName.cookie_mountain_life_block_1: [0x10, 187], + LocationName.cookie_mountain_vine_block_1: [0x10, 188], + LocationName.cookie_mountain_yoshi_block_1: [0x10, 189], + LocationName.cookie_mountain_coin_block_10: [0x10, 190], + LocationName.cookie_mountain_coin_block_11: [0x10, 191], + LocationName.cookie_mountain_powerup_block_2: [0x10, 192], + LocationName.cookie_mountain_coin_block_12: [0x10, 193], + LocationName.cookie_mountain_coin_block_13: [0x10, 194], + LocationName.cookie_mountain_coin_block_14: [0x10, 195], + LocationName.cookie_mountain_coin_block_15: [0x10, 196], + LocationName.cookie_mountain_coin_block_16: [0x10, 197], + LocationName.cookie_mountain_coin_block_17: [0x10, 198], + LocationName.cookie_mountain_coin_block_18: [0x10, 199], + LocationName.cookie_mountain_coin_block_19: [0x10, 200], + LocationName.cookie_mountain_coin_block_20: [0x10, 201], + LocationName.cookie_mountain_coin_block_21: [0x10, 202], + LocationName.cookie_mountain_coin_block_22: [0x10, 203], + LocationName.cookie_mountain_coin_block_23: [0x10, 204], + LocationName.cookie_mountain_coin_block_24: [0x10, 205], + LocationName.cookie_mountain_coin_block_25: [0x10, 206], + LocationName.cookie_mountain_coin_block_26: [0x10, 207], + LocationName.cookie_mountain_coin_block_27: [0x10, 208], + LocationName.cookie_mountain_coin_block_28: [0x10, 209], + LocationName.cookie_mountain_coin_block_29: [0x10, 210], + LocationName.cookie_mountain_coin_block_30: [0x10, 211], + LocationName.soda_lake_powerup_block_1: [0x11, 212], + LocationName.donut_secret_house_powerup_block_1: [0x13, 213], + LocationName.donut_secret_house_multi_coin_block_1: [0x13, 214], + LocationName.donut_secret_house_life_block_1: [0x13, 215], + LocationName.donut_secret_house_vine_block_1: [0x13, 216], + LocationName.donut_secret_house_directional_coin_block_1: [0x13, 217], + LocationName.donut_plains_1_coin_block_1: [0x15, 218], + LocationName.donut_plains_1_coin_block_2: [0x15, 219], + LocationName.donut_plains_1_yoshi_block_1: [0x15, 220], + LocationName.donut_plains_1_vine_block_1: [0x15, 221], + LocationName.donut_plains_1_green_block_1: [0x15, 222], + LocationName.donut_plains_1_green_block_2: [0x15, 223], + LocationName.donut_plains_1_green_block_3: [0x15, 224], + LocationName.donut_plains_1_green_block_4: [0x15, 225], + LocationName.donut_plains_1_green_block_5: [0x15, 226], + LocationName.donut_plains_1_green_block_6: [0x15, 227], + LocationName.donut_plains_1_green_block_7: [0x15, 228], + LocationName.donut_plains_1_green_block_8: [0x15, 229], + LocationName.donut_plains_1_green_block_9: [0x15, 230], + LocationName.donut_plains_1_green_block_10: [0x15, 231], + LocationName.donut_plains_1_green_block_11: [0x15, 232], + LocationName.donut_plains_1_green_block_12: [0x15, 233], + LocationName.donut_plains_1_green_block_13: [0x15, 234], + LocationName.donut_plains_1_green_block_14: [0x15, 235], + LocationName.donut_plains_1_green_block_15: [0x15, 236], + LocationName.donut_plains_1_green_block_16: [0x15, 237], + LocationName.donut_plains_1_yellow_block_1: [0x15, 238], + LocationName.donut_plains_1_yellow_block_2: [0x15, 239], + LocationName.donut_plains_1_yellow_block_3: [0x15, 240], + LocationName.sunken_ghost_ship_powerup_block_1: [0x18, 241], + LocationName.sunken_ghost_ship_star_block_1: [0x18, 242], + LocationName.chocolate_castle_yellow_block_1: [0x1A, 243], + LocationName.chocolate_castle_yellow_block_2: [0x1A, 244], + LocationName.chocolate_castle_green_block_1: [0x1A, 245], + LocationName.chocolate_fortress_powerup_block_1: [0x1B, 246], + LocationName.chocolate_fortress_powerup_block_2: [0x1B, 247], + LocationName.chocolate_fortress_coin_block_1: [0x1B, 248], + LocationName.chocolate_fortress_coin_block_2: [0x1B, 249], + LocationName.chocolate_fortress_green_block_1: [0x1B, 250], + LocationName.chocolate_island_5_yoshi_block_1: [0x1C, 251], + LocationName.chocolate_island_5_powerup_block_1: [0x1C, 252], + LocationName.chocolate_island_5_life_block_1: [0x1C, 253], + LocationName.chocolate_island_5_yellow_block_1: [0x1C, 254], + LocationName.chocolate_island_4_yellow_block_1: [0x1D, 255], + LocationName.chocolate_island_4_blue_pow_block_1: [0x1D, 256], + LocationName.chocolate_island_4_powerup_block_1: [0x1D, 257], + LocationName.forest_fortress_yellow_block_1: [0x1F, 258], + LocationName.forest_fortress_powerup_block_1: [0x1F, 259], + LocationName.forest_fortress_life_block_1: [0x1F, 260], + LocationName.forest_fortress_life_block_2: [0x1F, 261], + LocationName.forest_fortress_life_block_3: [0x1F, 262], + LocationName.forest_fortress_life_block_4: [0x1F, 263], + LocationName.forest_fortress_life_block_5: [0x1F, 264], + LocationName.forest_fortress_life_block_6: [0x1F, 265], + LocationName.forest_fortress_life_block_7: [0x1F, 266], + LocationName.forest_fortress_life_block_8: [0x1F, 267], + LocationName.forest_fortress_life_block_9: [0x1F, 268], + LocationName.forest_castle_green_block_1: [0x20, 269], + LocationName.chocolate_ghost_house_powerup_block_1: [0x21, 270], + LocationName.chocolate_ghost_house_powerup_block_2: [0x21, 271], + LocationName.chocolate_ghost_house_life_block_1: [0x21, 272], + LocationName.chocolate_island_1_flying_block_1: [0x22, 273], + LocationName.chocolate_island_1_flying_block_2: [0x22, 274], + LocationName.chocolate_island_1_yoshi_block_1: [0x22, 275], + LocationName.chocolate_island_1_green_block_1: [0x22, 276], + LocationName.chocolate_island_1_life_block_1: [0x22, 277], + LocationName.chocolate_island_3_powerup_block_1: [0x23, 278], + LocationName.chocolate_island_3_powerup_block_2: [0x23, 279], + LocationName.chocolate_island_3_powerup_block_3: [0x23, 280], + LocationName.chocolate_island_3_green_block_1: [0x23, 281], + LocationName.chocolate_island_3_bonus_block_1: [0x23, 282], + LocationName.chocolate_island_3_vine_block_1: [0x23, 283], + LocationName.chocolate_island_3_life_block_1: [0x23, 284], + LocationName.chocolate_island_3_life_block_2: [0x23, 285], + LocationName.chocolate_island_3_life_block_3: [0x23, 286], + LocationName.chocolate_island_2_multi_coin_block_1: [0x24, 287], + LocationName.chocolate_island_2_invis_coin_block_1: [0x24, 288], + LocationName.chocolate_island_2_yoshi_block_1: [0x24, 289], + LocationName.chocolate_island_2_coin_block_1: [0x24, 290], + LocationName.chocolate_island_2_coin_block_2: [0x24, 291], + LocationName.chocolate_island_2_multi_coin_block_2: [0x24, 292], + LocationName.chocolate_island_2_powerup_block_1: [0x24, 293], + LocationName.chocolate_island_2_blue_pow_block_1: [0x24, 294], + LocationName.chocolate_island_2_yellow_block_1: [0x24, 295], + LocationName.chocolate_island_2_yellow_block_2: [0x24, 296], + LocationName.chocolate_island_2_green_block_1: [0x24, 297], + LocationName.chocolate_island_2_green_block_2: [0x24, 298], + LocationName.chocolate_island_2_green_block_3: [0x24, 299], + LocationName.chocolate_island_2_green_block_4: [0x24, 300], + LocationName.chocolate_island_2_green_block_5: [0x24, 301], + LocationName.chocolate_island_2_green_block_6: [0x24, 302], + LocationName.yoshis_island_castle_coin_block_1: [0x25, 303], + LocationName.yoshis_island_castle_coin_block_2: [0x25, 304], + LocationName.yoshis_island_castle_powerup_block_1: [0x25, 305], + LocationName.yoshis_island_castle_coin_block_3: [0x25, 306], + LocationName.yoshis_island_castle_coin_block_4: [0x25, 307], + LocationName.yoshis_island_castle_flying_block_1: [0x25, 308], + LocationName.yoshis_island_4_yellow_block_1: [0x26, 309], + LocationName.yoshis_island_4_powerup_block_1: [0x26, 310], + LocationName.yoshis_island_4_multi_coin_block_1: [0x26, 311], + LocationName.yoshis_island_4_star_block_1: [0x26, 312], + LocationName.yoshis_island_3_yellow_block_1: [0x27, 313], + LocationName.yoshis_island_3_yellow_block_2: [0x27, 314], + LocationName.yoshis_island_3_yellow_block_3: [0x27, 315], + LocationName.yoshis_island_3_yellow_block_4: [0x27, 316], + LocationName.yoshis_island_3_yellow_block_5: [0x27, 317], + LocationName.yoshis_island_3_yellow_block_6: [0x27, 318], + LocationName.yoshis_island_3_yellow_block_7: [0x27, 319], + LocationName.yoshis_island_3_yellow_block_8: [0x27, 320], + LocationName.yoshis_island_3_yellow_block_9: [0x27, 321], + LocationName.yoshis_island_3_coin_block_1: [0x27, 322], + LocationName.yoshis_island_3_yoshi_block_1: [0x27, 323], + LocationName.yoshis_island_3_coin_block_2: [0x27, 324], + LocationName.yoshis_island_3_powerup_block_1: [0x27, 325], + LocationName.yoshis_island_3_yellow_block_10: [0x27, 326], + LocationName.yoshis_island_3_yellow_block_11: [0x27, 327], + LocationName.yoshis_island_3_yellow_block_12: [0x27, 328], + LocationName.yoshis_island_3_bonus_block_1: [0x27, 329], + LocationName.yoshis_island_1_flying_block_1: [0x29, 330], + LocationName.yoshis_island_1_yellow_block_1: [0x29, 331], + LocationName.yoshis_island_1_life_block_1: [0x29, 332], + LocationName.yoshis_island_1_powerup_block_1: [0x29, 333], + LocationName.yoshis_island_2_flying_block_1: [0x2A, 334], + LocationName.yoshis_island_2_flying_block_2: [0x2A, 335], + LocationName.yoshis_island_2_flying_block_3: [0x2A, 336], + LocationName.yoshis_island_2_flying_block_4: [0x2A, 337], + LocationName.yoshis_island_2_flying_block_5: [0x2A, 338], + LocationName.yoshis_island_2_flying_block_6: [0x2A, 339], + LocationName.yoshis_island_2_coin_block_1: [0x2A, 340], + LocationName.yoshis_island_2_yellow_block_1: [0x2A, 341], + LocationName.yoshis_island_2_coin_block_2: [0x2A, 342], + LocationName.yoshis_island_2_coin_block_3: [0x2A, 343], + LocationName.yoshis_island_2_yoshi_block_1: [0x2A, 344], + LocationName.yoshis_island_2_coin_block_4: [0x2A, 345], + LocationName.yoshis_island_2_yoshi_block_2: [0x2A, 346], + LocationName.yoshis_island_2_coin_block_5: [0x2A, 347], + LocationName.yoshis_island_2_vine_block_1: [0x2A, 348], + LocationName.yoshis_island_2_yellow_block_2: [0x2A, 349], + LocationName.vanilla_ghost_house_powerup_block_1: [0x2B, 350], + LocationName.vanilla_ghost_house_vine_block_1: [0x2B, 351], + LocationName.vanilla_ghost_house_powerup_block_2: [0x2B, 352], + LocationName.vanilla_ghost_house_multi_coin_block_1: [0x2B, 353], + LocationName.vanilla_ghost_house_blue_pow_block_1: [0x2B, 354], + LocationName.vanilla_secret_1_coin_block_1: [0x2D, 355], + LocationName.vanilla_secret_1_powerup_block_1: [0x2D, 356], + LocationName.vanilla_secret_1_multi_coin_block_1: [0x2D, 357], + LocationName.vanilla_secret_1_vine_block_1: [0x2D, 358], + LocationName.vanilla_secret_1_vine_block_2: [0x2D, 359], + LocationName.vanilla_secret_1_coin_block_2: [0x2D, 360], + LocationName.vanilla_secret_1_coin_block_3: [0x2D, 361], + LocationName.vanilla_secret_1_powerup_block_2: [0x2D, 362], + LocationName.vanilla_dome_3_coin_block_1: [0x2E, 363], + LocationName.vanilla_dome_3_flying_block_1: [0x2E, 364], + LocationName.vanilla_dome_3_flying_block_2: [0x2E, 365], + LocationName.vanilla_dome_3_powerup_block_1: [0x2E, 366], + LocationName.vanilla_dome_3_flying_block_3: [0x2E, 367], + LocationName.vanilla_dome_3_invis_coin_block_1: [0x2E, 368], + LocationName.vanilla_dome_3_powerup_block_2: [0x2E, 369], + LocationName.vanilla_dome_3_multi_coin_block_1: [0x2E, 370], + LocationName.vanilla_dome_3_powerup_block_3: [0x2E, 371], + LocationName.vanilla_dome_3_yoshi_block_1: [0x2E, 372], + LocationName.vanilla_dome_3_powerup_block_4: [0x2E, 373], + LocationName.vanilla_dome_3_pswitch_coin_block_1: [0x2E, 374], + LocationName.vanilla_dome_3_pswitch_coin_block_2: [0x2E, 375], + LocationName.vanilla_dome_3_pswitch_coin_block_3: [0x2E, 376], + LocationName.vanilla_dome_3_pswitch_coin_block_4: [0x2E, 377], + LocationName.vanilla_dome_3_pswitch_coin_block_5: [0x2E, 378], + LocationName.vanilla_dome_3_pswitch_coin_block_6: [0x2E, 379], + LocationName.donut_secret_2_directional_coin_block_1: [0x2F, 380], + LocationName.donut_secret_2_vine_block_1: [0x2F, 381], + LocationName.donut_secret_2_star_block_1: [0x2F, 382], + LocationName.donut_secret_2_powerup_block_1: [0x2F, 383], + LocationName.donut_secret_2_star_block_2: [0x2F, 384], + LocationName.valley_of_bowser_4_yellow_block_1: [0x33, 385], + LocationName.valley_of_bowser_4_powerup_block_1: [0x33, 386], + LocationName.valley_of_bowser_4_vine_block_1: [0x33, 387], + LocationName.valley_of_bowser_4_yoshi_block_1: [0x33, 388], + LocationName.valley_of_bowser_4_life_block_1: [0x33, 389], + LocationName.valley_of_bowser_4_powerup_block_2: [0x33, 390], + LocationName.valley_castle_yellow_block_1: [0x34, 391], + LocationName.valley_castle_yellow_block_2: [0x34, 392], + LocationName.valley_castle_green_block_1: [0x34, 393], + LocationName.valley_fortress_green_block_1: [0x35, 394], + LocationName.valley_fortress_yellow_block_1: [0x35, 395], + LocationName.valley_of_bowser_3_powerup_block_1: [0x37, 396], + LocationName.valley_of_bowser_3_powerup_block_2: [0x37, 397], + LocationName.valley_ghost_house_pswitch_coin_block_1: [0x38, 398], + LocationName.valley_ghost_house_multi_coin_block_1: [0x38, 399], + LocationName.valley_ghost_house_powerup_block_1: [0x38, 400], + LocationName.valley_ghost_house_directional_coin_block_1: [0x38, 401], + LocationName.valley_of_bowser_2_powerup_block_1: [0x39, 402], + LocationName.valley_of_bowser_2_yellow_block_1: [0x39, 403], + LocationName.valley_of_bowser_2_powerup_block_2: [0x39, 404], + LocationName.valley_of_bowser_2_wings_block_1: [0x39, 405], + LocationName.valley_of_bowser_1_green_block_1: [0x3A, 406], + LocationName.valley_of_bowser_1_invis_coin_block_1: [0x3A, 407], + LocationName.valley_of_bowser_1_invis_coin_block_2: [0x3A, 408], + LocationName.valley_of_bowser_1_invis_coin_block_3: [0x3A, 409], + LocationName.valley_of_bowser_1_yellow_block_1: [0x3A, 410], + LocationName.valley_of_bowser_1_yellow_block_2: [0x3A, 411], + LocationName.valley_of_bowser_1_yellow_block_3: [0x3A, 412], + LocationName.valley_of_bowser_1_yellow_block_4: [0x3A, 413], + LocationName.valley_of_bowser_1_vine_block_1: [0x3A, 414], + LocationName.chocolate_secret_powerup_block_1: [0x3B, 415], + LocationName.chocolate_secret_powerup_block_2: [0x3B, 416], + LocationName.vanilla_dome_2_coin_block_1: [0x3C, 417], + LocationName.vanilla_dome_2_powerup_block_1: [0x3C, 418], + LocationName.vanilla_dome_2_coin_block_2: [0x3C, 419], + LocationName.vanilla_dome_2_coin_block_3: [0x3C, 420], + LocationName.vanilla_dome_2_vine_block_1: [0x3C, 421], + LocationName.vanilla_dome_2_invis_life_block_1: [0x3C, 422], + LocationName.vanilla_dome_2_coin_block_4: [0x3C, 423], + LocationName.vanilla_dome_2_coin_block_5: [0x3C, 424], + LocationName.vanilla_dome_2_powerup_block_2: [0x3C, 425], + LocationName.vanilla_dome_2_powerup_block_3: [0x3C, 426], + LocationName.vanilla_dome_2_powerup_block_4: [0x3C, 427], + LocationName.vanilla_dome_2_powerup_block_5: [0x3C, 428], + LocationName.vanilla_dome_2_multi_coin_block_1: [0x3C, 429], + LocationName.vanilla_dome_2_multi_coin_block_2: [0x3C, 430], + LocationName.vanilla_dome_4_powerup_block_1: [0x3D, 431], + LocationName.vanilla_dome_4_powerup_block_2: [0x3D, 432], + LocationName.vanilla_dome_4_coin_block_1: [0x3D, 433], + LocationName.vanilla_dome_4_coin_block_2: [0x3D, 434], + LocationName.vanilla_dome_4_coin_block_3: [0x3D, 435], + LocationName.vanilla_dome_4_life_block_1: [0x3D, 436], + LocationName.vanilla_dome_4_coin_block_4: [0x3D, 437], + LocationName.vanilla_dome_4_coin_block_5: [0x3D, 438], + LocationName.vanilla_dome_4_coin_block_6: [0x3D, 439], + LocationName.vanilla_dome_4_coin_block_7: [0x3D, 440], + LocationName.vanilla_dome_4_coin_block_8: [0x3D, 441], + LocationName.vanilla_dome_1_flying_block_1: [0x3E, 442], + LocationName.vanilla_dome_1_powerup_block_1: [0x3E, 443], + LocationName.vanilla_dome_1_powerup_block_2: [0x3E, 444], + LocationName.vanilla_dome_1_coin_block_1: [0x3E, 445], + LocationName.vanilla_dome_1_life_block_1: [0x3E, 446], + LocationName.vanilla_dome_1_powerup_block_3: [0x3E, 447], + LocationName.vanilla_dome_1_vine_block_1: [0x3E, 448], + LocationName.vanilla_dome_1_star_block_1: [0x3E, 449], + LocationName.vanilla_dome_1_powerup_block_4: [0x3E, 450], + LocationName.vanilla_dome_1_coin_block_2: [0x3E, 451], + LocationName.vanilla_dome_castle_life_block_1: [0x40, 452], + LocationName.vanilla_dome_castle_life_block_2: [0x40, 453], + LocationName.vanilla_dome_castle_powerup_block_1: [0x40, 454], + LocationName.vanilla_dome_castle_life_block_3: [0x40, 455], + LocationName.vanilla_dome_castle_green_block_1: [0x40, 456], + LocationName.forest_ghost_house_coin_block_1: [0x41, 457], + LocationName.forest_ghost_house_powerup_block_1: [0x41, 458], + LocationName.forest_ghost_house_flying_block_1: [0x41, 459], + LocationName.forest_ghost_house_powerup_block_2: [0x41, 460], + LocationName.forest_ghost_house_life_block_1: [0x41, 461], + LocationName.forest_of_illusion_1_powerup_block_1: [0x42, 462], + LocationName.forest_of_illusion_1_yoshi_block_1: [0x42, 463], + LocationName.forest_of_illusion_1_powerup_block_2: [0x42, 464], + LocationName.forest_of_illusion_1_key_block_1: [0x42, 465], + LocationName.forest_of_illusion_1_life_block_1: [0x42, 466], + LocationName.forest_of_illusion_4_multi_coin_block_1: [0x43, 467], + LocationName.forest_of_illusion_4_coin_block_1: [0x43, 468], + LocationName.forest_of_illusion_4_coin_block_2: [0x43, 469], + LocationName.forest_of_illusion_4_coin_block_3: [0x43, 470], + LocationName.forest_of_illusion_4_coin_block_4: [0x43, 471], + LocationName.forest_of_illusion_4_powerup_block_1: [0x43, 472], + LocationName.forest_of_illusion_4_coin_block_5: [0x43, 473], + LocationName.forest_of_illusion_4_coin_block_6: [0x43, 474], + LocationName.forest_of_illusion_4_coin_block_7: [0x43, 475], + LocationName.forest_of_illusion_4_powerup_block_2: [0x43, 476], + LocationName.forest_of_illusion_4_coin_block_8: [0x43, 477], + LocationName.forest_of_illusion_4_coin_block_9: [0x43, 478], + LocationName.forest_of_illusion_4_coin_block_10: [0x43, 479], + LocationName.forest_of_illusion_2_green_block_1: [0x44, 480], + LocationName.forest_of_illusion_2_powerup_block_1: [0x44, 481], + LocationName.forest_of_illusion_2_invis_coin_block_1: [0x44, 482], + LocationName.forest_of_illusion_2_invis_coin_block_2: [0x44, 483], + LocationName.forest_of_illusion_2_invis_life_block_1: [0x44, 484], + LocationName.forest_of_illusion_2_invis_coin_block_3: [0x44, 485], + LocationName.forest_of_illusion_2_yellow_block_1: [0x44, 486], + LocationName.forest_secret_powerup_block_1: [0x46, 487], + LocationName.forest_secret_powerup_block_2: [0x46, 488], + LocationName.forest_secret_life_block_1: [0x46, 489], + LocationName.forest_of_illusion_3_yoshi_block_1: [0x47, 490], + LocationName.forest_of_illusion_3_coin_block_1: [0x47, 491], + LocationName.forest_of_illusion_3_multi_coin_block_1: [0x47, 492], + LocationName.forest_of_illusion_3_coin_block_2: [0x47, 493], + LocationName.forest_of_illusion_3_multi_coin_block_2: [0x47, 494], + LocationName.forest_of_illusion_3_coin_block_3: [0x47, 495], + LocationName.forest_of_illusion_3_coin_block_4: [0x47, 496], + LocationName.forest_of_illusion_3_coin_block_5: [0x47, 497], + LocationName.forest_of_illusion_3_coin_block_6: [0x47, 498], + LocationName.forest_of_illusion_3_coin_block_7: [0x47, 499], + LocationName.forest_of_illusion_3_coin_block_8: [0x47, 500], + LocationName.forest_of_illusion_3_coin_block_9: [0x47, 501], + LocationName.forest_of_illusion_3_coin_block_10: [0x47, 502], + LocationName.forest_of_illusion_3_coin_block_11: [0x47, 503], + LocationName.forest_of_illusion_3_coin_block_12: [0x47, 504], + LocationName.forest_of_illusion_3_coin_block_13: [0x47, 505], + LocationName.forest_of_illusion_3_coin_block_14: [0x47, 506], + LocationName.forest_of_illusion_3_coin_block_15: [0x47, 507], + LocationName.forest_of_illusion_3_coin_block_16: [0x47, 508], + LocationName.forest_of_illusion_3_coin_block_17: [0x47, 509], + LocationName.forest_of_illusion_3_coin_block_18: [0x47, 510], + LocationName.forest_of_illusion_3_coin_block_19: [0x47, 511], + LocationName.forest_of_illusion_3_coin_block_20: [0x47, 512], + LocationName.forest_of_illusion_3_coin_block_21: [0x47, 513], + LocationName.forest_of_illusion_3_coin_block_22: [0x47, 514], + LocationName.forest_of_illusion_3_coin_block_23: [0x47, 515], + LocationName.forest_of_illusion_3_coin_block_24: [0x47, 516], + LocationName.special_zone_8_yoshi_block_1: [0x49, 517], + LocationName.special_zone_8_coin_block_1: [0x49, 518], + LocationName.special_zone_8_coin_block_2: [0x49, 519], + LocationName.special_zone_8_coin_block_3: [0x49, 520], + LocationName.special_zone_8_coin_block_4: [0x49, 521], + LocationName.special_zone_8_coin_block_5: [0x49, 522], + LocationName.special_zone_8_blue_pow_block_1: [0x49, 523], + LocationName.special_zone_8_powerup_block_1: [0x49, 524], + LocationName.special_zone_8_star_block_1: [0x49, 525], + LocationName.special_zone_8_coin_block_6: [0x49, 526], + LocationName.special_zone_8_coin_block_7: [0x49, 527], + LocationName.special_zone_8_coin_block_8: [0x49, 528], + LocationName.special_zone_8_coin_block_9: [0x49, 529], + LocationName.special_zone_8_coin_block_10: [0x49, 530], + LocationName.special_zone_8_coin_block_11: [0x49, 531], + LocationName.special_zone_8_coin_block_12: [0x49, 532], + LocationName.special_zone_8_coin_block_13: [0x49, 533], + LocationName.special_zone_8_coin_block_14: [0x49, 534], + LocationName.special_zone_8_coin_block_15: [0x49, 535], + LocationName.special_zone_8_coin_block_16: [0x49, 536], + LocationName.special_zone_8_coin_block_17: [0x49, 537], + LocationName.special_zone_8_coin_block_18: [0x49, 538], + LocationName.special_zone_8_multi_coin_block_1: [0x49, 539], + LocationName.special_zone_8_coin_block_19: [0x49, 540], + LocationName.special_zone_8_coin_block_20: [0x49, 541], + LocationName.special_zone_8_coin_block_21: [0x49, 542], + LocationName.special_zone_8_coin_block_22: [0x49, 543], + LocationName.special_zone_8_coin_block_23: [0x49, 544], + LocationName.special_zone_8_powerup_block_2: [0x49, 545], + LocationName.special_zone_8_flying_block_1: [0x49, 546], + LocationName.special_zone_7_powerup_block_1: [0x4A, 547], + LocationName.special_zone_7_yoshi_block_1: [0x4A, 548], + LocationName.special_zone_7_coin_block_1: [0x4A, 549], + LocationName.special_zone_7_powerup_block_2: [0x4A, 550], + LocationName.special_zone_7_coin_block_2: [0x4A, 551], + LocationName.special_zone_6_powerup_block_1: [0x4B, 552], + LocationName.special_zone_6_coin_block_1: [0x4B, 553], + LocationName.special_zone_6_coin_block_2: [0x4B, 554], + LocationName.special_zone_6_yoshi_block_1: [0x4B, 555], + LocationName.special_zone_6_life_block_1: [0x4B, 556], + LocationName.special_zone_6_multi_coin_block_1: [0x4B, 557], + LocationName.special_zone_6_coin_block_3: [0x4B, 558], + LocationName.special_zone_6_coin_block_4: [0x4B, 559], + LocationName.special_zone_6_coin_block_5: [0x4B, 560], + LocationName.special_zone_6_coin_block_6: [0x4B, 561], + LocationName.special_zone_6_coin_block_7: [0x4B, 562], + LocationName.special_zone_6_coin_block_8: [0x4B, 563], + LocationName.special_zone_6_coin_block_9: [0x4B, 564], + LocationName.special_zone_6_coin_block_10: [0x4B, 565], + LocationName.special_zone_6_coin_block_11: [0x4B, 566], + LocationName.special_zone_6_coin_block_12: [0x4B, 567], + LocationName.special_zone_6_coin_block_13: [0x4B, 568], + LocationName.special_zone_6_coin_block_14: [0x4B, 569], + LocationName.special_zone_6_coin_block_15: [0x4B, 570], + LocationName.special_zone_6_coin_block_16: [0x4B, 571], + LocationName.special_zone_6_coin_block_17: [0x4B, 572], + LocationName.special_zone_6_coin_block_18: [0x4B, 573], + LocationName.special_zone_6_coin_block_19: [0x4B, 574], + LocationName.special_zone_6_coin_block_20: [0x4B, 575], + LocationName.special_zone_6_coin_block_21: [0x4B, 576], + LocationName.special_zone_6_coin_block_22: [0x4B, 577], + LocationName.special_zone_6_coin_block_23: [0x4B, 578], + LocationName.special_zone_6_coin_block_24: [0x4B, 579], + LocationName.special_zone_6_coin_block_25: [0x4B, 580], + LocationName.special_zone_6_coin_block_26: [0x4B, 581], + LocationName.special_zone_6_coin_block_27: [0x4B, 582], + LocationName.special_zone_6_coin_block_28: [0x4B, 583], + LocationName.special_zone_6_powerup_block_2: [0x4B, 584], + LocationName.special_zone_6_coin_block_29: [0x4B, 585], + LocationName.special_zone_6_coin_block_30: [0x4B, 586], + LocationName.special_zone_6_coin_block_31: [0x4B, 587], + LocationName.special_zone_6_coin_block_32: [0x4B, 588], + LocationName.special_zone_6_coin_block_33: [0x4B, 589], + LocationName.special_zone_5_yoshi_block_1: [0x4C, 590], + LocationName.special_zone_1_vine_block_1: [0x4E, 591], + LocationName.special_zone_1_vine_block_2: [0x4E, 592], + LocationName.special_zone_1_vine_block_3: [0x4E, 593], + LocationName.special_zone_1_vine_block_4: [0x4E, 594], + LocationName.special_zone_1_life_block_1: [0x4E, 595], + LocationName.special_zone_1_vine_block_5: [0x4E, 596], + LocationName.special_zone_1_blue_pow_block_1: [0x4E, 597], + LocationName.special_zone_1_vine_block_6: [0x4E, 598], + LocationName.special_zone_1_powerup_block_1: [0x4E, 599], + LocationName.special_zone_1_pswitch_coin_block_1: [0x4E, 600], + LocationName.special_zone_1_pswitch_coin_block_2: [0x4E, 601], + LocationName.special_zone_1_pswitch_coin_block_3: [0x4E, 602], + LocationName.special_zone_1_pswitch_coin_block_4: [0x4E, 603], + LocationName.special_zone_1_pswitch_coin_block_5: [0x4E, 604], + LocationName.special_zone_1_pswitch_coin_block_6: [0x4E, 605], + LocationName.special_zone_1_pswitch_coin_block_7: [0x4E, 606], + LocationName.special_zone_1_pswitch_coin_block_8: [0x4E, 607], + LocationName.special_zone_1_pswitch_coin_block_9: [0x4E, 608], + LocationName.special_zone_1_pswitch_coin_block_10: [0x4E, 609], + LocationName.special_zone_1_pswitch_coin_block_11: [0x4E, 610], + LocationName.special_zone_1_pswitch_coin_block_12: [0x4E, 611], + LocationName.special_zone_1_pswitch_coin_block_13: [0x4E, 612], + LocationName.special_zone_2_powerup_block_1: [0x4F, 613], + LocationName.special_zone_2_coin_block_1: [0x4F, 614], + LocationName.special_zone_2_coin_block_2: [0x4F, 615], + LocationName.special_zone_2_powerup_block_2: [0x4F, 616], + LocationName.special_zone_2_coin_block_3: [0x4F, 617], + LocationName.special_zone_2_coin_block_4: [0x4F, 618], + LocationName.special_zone_2_powerup_block_3: [0x4F, 619], + LocationName.special_zone_2_multi_coin_block_1: [0x4F, 620], + LocationName.special_zone_2_coin_block_5: [0x4F, 621], + LocationName.special_zone_2_coin_block_6: [0x4F, 622], + LocationName.special_zone_3_powerup_block_1: [0x50, 623], + LocationName.special_zone_3_yoshi_block_1: [0x50, 624], + LocationName.special_zone_3_wings_block_1: [0x50, 625], + LocationName.special_zone_4_powerup_block_1: [0x51, 626], + LocationName.special_zone_4_star_block_1: [0x51, 627], + LocationName.star_road_2_star_block_1: [0x54, 628], + LocationName.star_road_3_key_block_1: [0x56, 629], + LocationName.star_road_4_powerup_block_1: [0x59, 630], + LocationName.star_road_4_green_block_1: [0x59, 631], + LocationName.star_road_4_green_block_2: [0x59, 632], + LocationName.star_road_4_green_block_3: [0x59, 633], + LocationName.star_road_4_green_block_4: [0x59, 634], + LocationName.star_road_4_green_block_5: [0x59, 635], + LocationName.star_road_4_green_block_6: [0x59, 636], + LocationName.star_road_4_green_block_7: [0x59, 637], + LocationName.star_road_4_key_block_1: [0x59, 638], + LocationName.star_road_5_directional_coin_block_1: [0x5A, 639], + LocationName.star_road_5_life_block_1: [0x5A, 640], + LocationName.star_road_5_vine_block_1: [0x5A, 641], + LocationName.star_road_5_yellow_block_1: [0x5A, 642], + LocationName.star_road_5_yellow_block_2: [0x5A, 643], + LocationName.star_road_5_yellow_block_3: [0x5A, 644], + LocationName.star_road_5_yellow_block_4: [0x5A, 645], + LocationName.star_road_5_yellow_block_5: [0x5A, 646], + LocationName.star_road_5_yellow_block_6: [0x5A, 647], + LocationName.star_road_5_yellow_block_7: [0x5A, 648], + LocationName.star_road_5_yellow_block_8: [0x5A, 649], + LocationName.star_road_5_yellow_block_9: [0x5A, 650], + LocationName.star_road_5_yellow_block_10: [0x5A, 651], + LocationName.star_road_5_yellow_block_11: [0x5A, 652], + LocationName.star_road_5_yellow_block_12: [0x5A, 653], + LocationName.star_road_5_yellow_block_13: [0x5A, 654], + LocationName.star_road_5_yellow_block_14: [0x5A, 655], + LocationName.star_road_5_yellow_block_15: [0x5A, 656], + LocationName.star_road_5_yellow_block_16: [0x5A, 657], + LocationName.star_road_5_yellow_block_17: [0x5A, 658], + LocationName.star_road_5_yellow_block_18: [0x5A, 659], + LocationName.star_road_5_yellow_block_19: [0x5A, 660], + LocationName.star_road_5_yellow_block_20: [0x5A, 661], + LocationName.star_road_5_green_block_1: [0x5A, 662], + LocationName.star_road_5_green_block_2: [0x5A, 663], + LocationName.star_road_5_green_block_3: [0x5A, 664], + LocationName.star_road_5_green_block_4: [0x5A, 665], + LocationName.star_road_5_green_block_5: [0x5A, 666], + LocationName.star_road_5_green_block_6: [0x5A, 667], + LocationName.star_road_5_green_block_7: [0x5A, 668], + LocationName.star_road_5_green_block_8: [0x5A, 669], + LocationName.star_road_5_green_block_9: [0x5A, 670], + LocationName.star_road_5_green_block_10: [0x5A, 671], + LocationName.star_road_5_green_block_11: [0x5A, 672], + LocationName.star_road_5_green_block_12: [0x5A, 673], + LocationName.star_road_5_green_block_13: [0x5A, 674], + LocationName.star_road_5_green_block_14: [0x5A, 675], + LocationName.star_road_5_green_block_15: [0x5A, 676], + LocationName.star_road_5_green_block_16: [0x5A, 677], + LocationName.star_road_5_green_block_17: [0x5A, 678], + LocationName.star_road_5_green_block_18: [0x5A, 679], + LocationName.star_road_5_green_block_19: [0x5A, 680], + LocationName.star_road_5_green_block_20: [0x5A, 681] } -def generate_level_list(world, player): +def generate_level_list(world: World): - if not world.level_shuffle[player]: + if not world.options.level_shuffle: out_level_list = full_level_list.copy() out_level_list[0x00] = 0x03 out_level_list[0x11] = 0x28 - if world.bowser_castle_doors[player] == "fast": + if world.options.bowser_castle_doors == "fast": out_level_list[0x41] = 0x82 out_level_list[0x42] = 0x32 - elif world.bowser_castle_doors[player] == "slow": + elif world.options.bowser_castle_doors == "slow": out_level_list[0x41] = 0x31 out_level_list[0x42] = 0x81 @@ -552,7 +1258,7 @@ def generate_level_list(world, player): shuffled_level_list.append(0x16) single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy()) - if not world.exclude_special_zone[player]: + if not world.options.exclude_special_zone: single_levels_copy.extend(special_zone_levels_copy) world.random.shuffle(single_levels_copy) @@ -619,10 +1325,10 @@ def generate_level_list(world, player): shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) # Front/Back Door - if world.bowser_castle_doors[player] == "fast": + if world.options.bowser_castle_doors == "fast": shuffled_level_list.append(0x82) shuffled_level_list.append(0x32) - elif world.bowser_castle_doors[player] == "slow": + elif world.options.bowser_castle_doors == "slow": shuffled_level_list.append(0x31) shuffled_level_list.append(0x81) else: @@ -646,7 +1352,7 @@ def generate_level_list(world, player): # Special Zone shuffled_level_list.append(0x4D) - if not world.exclude_special_zone[player]: + if not world.options.exclude_special_zone: shuffled_level_list.append(single_levels_copy.pop(0)) shuffled_level_list.append(single_levels_copy.pop(0)) shuffled_level_list.append(single_levels_copy.pop(0)) diff --git a/worlds/smw/Locations.py b/worlds/smw/Locations.py index a8b7f7a4ec2c..47e821fc61ff 100644 --- a/worlds/smw/Locations.py +++ b/worlds/smw/Locations.py @@ -1,9 +1,9 @@ import typing from BaseClasses import Location +from worlds.AutoWorld import World from .Names import LocationName - class SMWLocation(Location): game: str = "Super Mario World" @@ -197,6 +197,624 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None LocationName.special_zone_8_dragon: 0xBC0162, } +moon_location_table = { + LocationName.yoshis_island_1_moon: 0xBC0300, + LocationName.donut_plains_4_moon: 0xBC030B, + LocationName.vanilla_dome_3_moon: 0xBC0318, + LocationName.cheese_bridge_moon: 0xBC0325, + LocationName.forest_ghost_house_moon: 0xBC0332, + LocationName.chocolate_island_1_moon: 0xBC0338, + LocationName.valley_of_bowser_1_moon: 0xBC0345 +} + +hidden_1ups_location_table = { + LocationName.yoshis_island_4_hidden_1up: 0xBC0403, + LocationName.donut_plains_1_hidden_1up: 0xBC0406, + LocationName.donut_plains_4_hidden_1up: 0xBC040B, + LocationName.donut_plains_castle_hidden_1up: 0xBC0412, + LocationName.vanilla_dome_4_hidden_1up: 0xBC0419, + LocationName.vanilla_ghost_house_hidden_1up: 0xBC041E, + LocationName.vanilla_fortress_hidden_1up: 0xBC0420, + LocationName.cookie_mountain_hidden_1up: 0xBC0427, + LocationName.forest_of_illusion_3_hidden_1up: 0xBC042E, + LocationName.chocolate_island_2_hidden_1up: 0xBC0439, + LocationName.chocolate_castle_hidden_1up: 0xBC0443, + LocationName.valley_of_bowser_2_hidden_1up: 0xBC0446, + LocationName.valley_castle_hidden_1up: 0xBC044F, + LocationName.special_zone_1_hidden_1up: 0xBC045B +} +bonus_block_location_table = { + LocationName.yoshis_island_3_bonus_block: 0xBC0502, + LocationName.donut_plains_3_bonus_block: 0xBC050A, + LocationName.butter_bridge_1_bonus_block: 0xBC0523, + LocationName.chocolate_island_3_bonus_block: 0xBC053B +} + +blocksanity_location_table = { + LocationName.vanilla_secret_2_yoshi_block_1: 0xBC0600, + LocationName.vanilla_secret_2_green_block_1: 0xBC0601, + LocationName.vanilla_secret_2_powerup_block_1: 0xBC0602, + LocationName.vanilla_secret_2_powerup_block_2: 0xBC0603, + LocationName.vanilla_secret_2_multi_coin_block_1: 0xBC0604, + LocationName.vanilla_secret_2_gray_pow_block_1: 0xBC0605, + LocationName.vanilla_secret_2_coin_block_1: 0xBC0606, + LocationName.vanilla_secret_2_coin_block_2: 0xBC0607, + LocationName.vanilla_secret_2_coin_block_3: 0xBC0608, + LocationName.vanilla_secret_2_coin_block_4: 0xBC0609, + LocationName.vanilla_secret_2_coin_block_5: 0xBC060A, + LocationName.vanilla_secret_2_coin_block_6: 0xBC060B, + LocationName.vanilla_secret_3_powerup_block_1: 0xBC060C, + LocationName.vanilla_secret_3_powerup_block_2: 0xBC060D, + LocationName.donut_ghost_house_vine_block_1: 0xBC060E, + LocationName.donut_ghost_house_directional_coin_block_1: 0xBC060F, + LocationName.donut_ghost_house_life_block_1: 0xBC0610, + LocationName.donut_ghost_house_life_block_2: 0xBC0611, + LocationName.donut_ghost_house_life_block_3: 0xBC0612, + LocationName.donut_ghost_house_life_block_4: 0xBC0613, + LocationName.donut_plains_3_green_block_1: 0xBC0614, + LocationName.donut_plains_3_coin_block_1: 0xBC0615, + LocationName.donut_plains_3_coin_block_2: 0xBC0616, + LocationName.donut_plains_3_vine_block_1: 0xBC0617, + LocationName.donut_plains_3_powerup_block_1: 0xBC0618, + LocationName.donut_plains_3_bonus_block_1: 0xBC0619, + LocationName.donut_plains_4_coin_block_1: 0xBC061A, + LocationName.donut_plains_4_powerup_block_1: 0xBC061B, + LocationName.donut_plains_4_coin_block_2: 0xBC061C, + LocationName.donut_plains_4_yoshi_block_1: 0xBC061D, + LocationName.donut_plains_castle_yellow_block_1: 0xBC061E, + LocationName.donut_plains_castle_coin_block_1: 0xBC061F, + LocationName.donut_plains_castle_powerup_block_1: 0xBC0620, + LocationName.donut_plains_castle_coin_block_2: 0xBC0621, + LocationName.donut_plains_castle_vine_block_1: 0xBC0622, + LocationName.donut_plains_castle_invis_life_block_1: 0xBC0623, + LocationName.donut_plains_castle_coin_block_3: 0xBC0624, + LocationName.donut_plains_castle_coin_block_4: 0xBC0625, + LocationName.donut_plains_castle_coin_block_5: 0xBC0626, + LocationName.donut_plains_castle_green_block_1: 0xBC0627, + LocationName.donut_plains_2_coin_block_1: 0xBC0628, + LocationName.donut_plains_2_coin_block_2: 0xBC0629, + LocationName.donut_plains_2_coin_block_3: 0xBC062A, + LocationName.donut_plains_2_yellow_block_1: 0xBC062B, + LocationName.donut_plains_2_powerup_block_1: 0xBC062C, + LocationName.donut_plains_2_multi_coin_block_1: 0xBC062D, + LocationName.donut_plains_2_flying_block_1: 0xBC062E, + LocationName.donut_plains_2_green_block_1: 0xBC062F, + LocationName.donut_plains_2_yellow_block_2: 0xBC0630, + LocationName.donut_plains_2_vine_block_1: 0xBC0631, + LocationName.donut_secret_1_coin_block_1: 0xBC0632, + LocationName.donut_secret_1_coin_block_2: 0xBC0633, + LocationName.donut_secret_1_powerup_block_1: 0xBC0634, + LocationName.donut_secret_1_coin_block_3: 0xBC0635, + LocationName.donut_secret_1_powerup_block_2: 0xBC0636, + LocationName.donut_secret_1_powerup_block_3: 0xBC0637, + LocationName.donut_secret_1_life_block_1: 0xBC0638, + LocationName.donut_secret_1_powerup_block_4: 0xBC0639, + LocationName.donut_secret_1_powerup_block_5: 0xBC063A, + LocationName.donut_secret_1_key_block_1: 0xBC063B, + LocationName.vanilla_fortress_powerup_block_1: 0xBC063C, + LocationName.vanilla_fortress_powerup_block_2: 0xBC063D, + LocationName.vanilla_fortress_yellow_block_1: 0xBC063E, + LocationName.butter_bridge_1_powerup_block_1: 0xBC063F, + LocationName.butter_bridge_1_multi_coin_block_1: 0xBC0640, + LocationName.butter_bridge_1_multi_coin_block_2: 0xBC0641, + LocationName.butter_bridge_1_multi_coin_block_3: 0xBC0642, + LocationName.butter_bridge_1_life_block_1: 0xBC0643, + LocationName.butter_bridge_1_bonus_block_1: 0xBC0644, + LocationName.butter_bridge_2_powerup_block_1: 0xBC0645, + LocationName.butter_bridge_2_green_block_1: 0xBC0646, + LocationName.butter_bridge_2_yoshi_block_1: 0xBC0647, + LocationName.twin_bridges_castle_powerup_block_1: 0xBC0648, + LocationName.cheese_bridge_powerup_block_1: 0xBC0649, + LocationName.cheese_bridge_powerup_block_2: 0xBC064A, + LocationName.cheese_bridge_wings_block_1: 0xBC064B, + LocationName.cheese_bridge_powerup_block_3: 0xBC064C, + LocationName.cookie_mountain_coin_block_1: 0xBC064D, + LocationName.cookie_mountain_coin_block_2: 0xBC064E, + LocationName.cookie_mountain_coin_block_3: 0xBC064F, + LocationName.cookie_mountain_coin_block_4: 0xBC0650, + LocationName.cookie_mountain_coin_block_5: 0xBC0651, + LocationName.cookie_mountain_coin_block_6: 0xBC0652, + LocationName.cookie_mountain_coin_block_7: 0xBC0653, + LocationName.cookie_mountain_coin_block_8: 0xBC0654, + LocationName.cookie_mountain_coin_block_9: 0xBC0655, + LocationName.cookie_mountain_powerup_block_1: 0xBC0656, + LocationName.cookie_mountain_life_block_1: 0xBC0657, + LocationName.cookie_mountain_vine_block_1: 0xBC0658, + LocationName.cookie_mountain_yoshi_block_1: 0xBC0659, + LocationName.cookie_mountain_coin_block_10: 0xBC065A, + LocationName.cookie_mountain_coin_block_11: 0xBC065B, + LocationName.cookie_mountain_powerup_block_2: 0xBC065C, + LocationName.cookie_mountain_coin_block_12: 0xBC065D, + LocationName.cookie_mountain_coin_block_13: 0xBC065E, + LocationName.cookie_mountain_coin_block_14: 0xBC065F, + LocationName.cookie_mountain_coin_block_15: 0xBC0660, + LocationName.cookie_mountain_coin_block_16: 0xBC0661, + LocationName.cookie_mountain_coin_block_17: 0xBC0662, + LocationName.cookie_mountain_coin_block_18: 0xBC0663, + LocationName.cookie_mountain_coin_block_19: 0xBC0664, + LocationName.cookie_mountain_coin_block_20: 0xBC0665, + LocationName.cookie_mountain_coin_block_21: 0xBC0666, + LocationName.cookie_mountain_coin_block_22: 0xBC0667, + LocationName.cookie_mountain_coin_block_23: 0xBC0668, + LocationName.cookie_mountain_coin_block_24: 0xBC0669, + LocationName.cookie_mountain_coin_block_25: 0xBC066A, + LocationName.cookie_mountain_coin_block_26: 0xBC066B, + LocationName.cookie_mountain_coin_block_27: 0xBC066C, + LocationName.cookie_mountain_coin_block_28: 0xBC066D, + LocationName.cookie_mountain_coin_block_29: 0xBC066E, + LocationName.cookie_mountain_coin_block_30: 0xBC066F, + LocationName.soda_lake_powerup_block_1: 0xBC0670, + LocationName.donut_secret_house_powerup_block_1: 0xBC0671, + LocationName.donut_secret_house_multi_coin_block_1: 0xBC0672, + LocationName.donut_secret_house_life_block_1: 0xBC0673, + LocationName.donut_secret_house_vine_block_1: 0xBC0674, + LocationName.donut_secret_house_directional_coin_block_1: 0xBC0675, + LocationName.donut_plains_1_coin_block_1: 0xBC0676, + LocationName.donut_plains_1_coin_block_2: 0xBC0677, + LocationName.donut_plains_1_yoshi_block_1: 0xBC0678, + LocationName.donut_plains_1_vine_block_1: 0xBC0679, + LocationName.donut_plains_1_green_block_1: 0xBC067A, + LocationName.donut_plains_1_green_block_2: 0xBC067B, + LocationName.donut_plains_1_green_block_3: 0xBC067C, + LocationName.donut_plains_1_green_block_4: 0xBC067D, + LocationName.donut_plains_1_green_block_5: 0xBC067E, + LocationName.donut_plains_1_green_block_6: 0xBC067F, + LocationName.donut_plains_1_green_block_7: 0xBC0680, + LocationName.donut_plains_1_green_block_8: 0xBC0681, + LocationName.donut_plains_1_green_block_9: 0xBC0682, + LocationName.donut_plains_1_green_block_10: 0xBC0683, + LocationName.donut_plains_1_green_block_11: 0xBC0684, + LocationName.donut_plains_1_green_block_12: 0xBC0685, + LocationName.donut_plains_1_green_block_13: 0xBC0686, + LocationName.donut_plains_1_green_block_14: 0xBC0687, + LocationName.donut_plains_1_green_block_15: 0xBC0688, + LocationName.donut_plains_1_green_block_16: 0xBC0689, + LocationName.donut_plains_1_yellow_block_1: 0xBC068A, + LocationName.donut_plains_1_yellow_block_2: 0xBC068B, + LocationName.donut_plains_1_yellow_block_3: 0xBC068C, + LocationName.sunken_ghost_ship_powerup_block_1: 0xBC068D, + LocationName.sunken_ghost_ship_star_block_1: 0xBC068E, + LocationName.chocolate_castle_yellow_block_1: 0xBC068F, + LocationName.chocolate_castle_yellow_block_2: 0xBC0690, + LocationName.chocolate_castle_green_block_1: 0xBC0691, + LocationName.chocolate_fortress_powerup_block_1: 0xBC0692, + LocationName.chocolate_fortress_powerup_block_2: 0xBC0693, + LocationName.chocolate_fortress_coin_block_1: 0xBC0694, + LocationName.chocolate_fortress_coin_block_2: 0xBC0695, + LocationName.chocolate_fortress_green_block_1: 0xBC0696, + LocationName.chocolate_island_5_yoshi_block_1: 0xBC0697, + LocationName.chocolate_island_5_powerup_block_1: 0xBC0698, + LocationName.chocolate_island_5_life_block_1: 0xBC0699, + LocationName.chocolate_island_5_yellow_block_1: 0xBC069A, + LocationName.chocolate_island_4_yellow_block_1: 0xBC069B, + LocationName.chocolate_island_4_blue_pow_block_1: 0xBC069C, + LocationName.chocolate_island_4_powerup_block_1: 0xBC069D, + LocationName.forest_fortress_yellow_block_1: 0xBC069E, + LocationName.forest_fortress_powerup_block_1: 0xBC069F, + LocationName.forest_fortress_life_block_1: 0xBC06A0, + LocationName.forest_fortress_life_block_2: 0xBC06A1, + LocationName.forest_fortress_life_block_3: 0xBC06A2, + LocationName.forest_fortress_life_block_4: 0xBC06A3, + LocationName.forest_fortress_life_block_5: 0xBC06A4, + LocationName.forest_fortress_life_block_6: 0xBC06A5, + LocationName.forest_fortress_life_block_7: 0xBC06A6, + LocationName.forest_fortress_life_block_8: 0xBC06A7, + LocationName.forest_fortress_life_block_9: 0xBC06A8, + LocationName.forest_castle_green_block_1: 0xBC06A9, + LocationName.chocolate_ghost_house_powerup_block_1: 0xBC06AA, + LocationName.chocolate_ghost_house_powerup_block_2: 0xBC06AB, + LocationName.chocolate_ghost_house_life_block_1: 0xBC06AC, + LocationName.chocolate_island_1_flying_block_1: 0xBC06AD, + LocationName.chocolate_island_1_flying_block_2: 0xBC06AE, + LocationName.chocolate_island_1_yoshi_block_1: 0xBC06AF, + LocationName.chocolate_island_1_green_block_1: 0xBC06B0, + LocationName.chocolate_island_1_life_block_1: 0xBC06B1, + LocationName.chocolate_island_3_powerup_block_1: 0xBC06B2, + LocationName.chocolate_island_3_powerup_block_2: 0xBC06B3, + LocationName.chocolate_island_3_powerup_block_3: 0xBC06B4, + LocationName.chocolate_island_3_green_block_1: 0xBC06B5, + LocationName.chocolate_island_3_bonus_block_1: 0xBC06B6, + LocationName.chocolate_island_3_vine_block_1: 0xBC06B7, + LocationName.chocolate_island_3_life_block_1: 0xBC06B8, + LocationName.chocolate_island_3_life_block_2: 0xBC06B9, + LocationName.chocolate_island_3_life_block_3: 0xBC06BA, + LocationName.chocolate_island_2_multi_coin_block_1: 0xBC06BB, + LocationName.chocolate_island_2_invis_coin_block_1: 0xBC06BC, + LocationName.chocolate_island_2_yoshi_block_1: 0xBC06BD, + LocationName.chocolate_island_2_coin_block_1: 0xBC06BE, + LocationName.chocolate_island_2_coin_block_2: 0xBC06BF, + LocationName.chocolate_island_2_multi_coin_block_2: 0xBC06C0, + LocationName.chocolate_island_2_powerup_block_1: 0xBC06C1, + LocationName.chocolate_island_2_blue_pow_block_1: 0xBC06C2, + LocationName.chocolate_island_2_yellow_block_1: 0xBC06C3, + LocationName.chocolate_island_2_yellow_block_2: 0xBC06C4, + LocationName.chocolate_island_2_green_block_1: 0xBC06C5, + LocationName.chocolate_island_2_green_block_2: 0xBC06C6, + LocationName.chocolate_island_2_green_block_3: 0xBC06C7, + LocationName.chocolate_island_2_green_block_4: 0xBC06C8, + LocationName.chocolate_island_2_green_block_5: 0xBC06C9, + LocationName.chocolate_island_2_green_block_6: 0xBC06CA, + LocationName.yoshis_island_castle_coin_block_1: 0xBC06CB, + LocationName.yoshis_island_castle_coin_block_2: 0xBC06CC, + LocationName.yoshis_island_castle_powerup_block_1: 0xBC06CD, + LocationName.yoshis_island_castle_coin_block_3: 0xBC06CE, + LocationName.yoshis_island_castle_coin_block_4: 0xBC06CF, + LocationName.yoshis_island_castle_flying_block_1: 0xBC06D0, + LocationName.yoshis_island_4_yellow_block_1: 0xBC06D1, + LocationName.yoshis_island_4_powerup_block_1: 0xBC06D2, + LocationName.yoshis_island_4_multi_coin_block_1: 0xBC06D3, + LocationName.yoshis_island_4_star_block_1: 0xBC06D4, + LocationName.yoshis_island_3_yellow_block_1: 0xBC06D5, + LocationName.yoshis_island_3_yellow_block_2: 0xBC06D6, + LocationName.yoshis_island_3_yellow_block_3: 0xBC06D7, + LocationName.yoshis_island_3_yellow_block_4: 0xBC06D8, + LocationName.yoshis_island_3_yellow_block_5: 0xBC06D9, + LocationName.yoshis_island_3_yellow_block_6: 0xBC06DA, + LocationName.yoshis_island_3_yellow_block_7: 0xBC06DB, + LocationName.yoshis_island_3_yellow_block_8: 0xBC06DC, + LocationName.yoshis_island_3_yellow_block_9: 0xBC06DD, + LocationName.yoshis_island_3_coin_block_1: 0xBC06DE, + LocationName.yoshis_island_3_yoshi_block_1: 0xBC06DF, + LocationName.yoshis_island_3_coin_block_2: 0xBC06E0, + LocationName.yoshis_island_3_powerup_block_1: 0xBC06E1, + LocationName.yoshis_island_3_yellow_block_10: 0xBC06E2, + LocationName.yoshis_island_3_yellow_block_11: 0xBC06E3, + LocationName.yoshis_island_3_yellow_block_12: 0xBC06E4, + LocationName.yoshis_island_3_bonus_block_1: 0xBC06E5, + LocationName.yoshis_island_1_flying_block_1: 0xBC06E6, + LocationName.yoshis_island_1_yellow_block_1: 0xBC06E7, + LocationName.yoshis_island_1_life_block_1: 0xBC06E8, + LocationName.yoshis_island_1_powerup_block_1: 0xBC06E9, + LocationName.yoshis_island_2_flying_block_1: 0xBC06EA, + LocationName.yoshis_island_2_flying_block_2: 0xBC06EB, + LocationName.yoshis_island_2_flying_block_3: 0xBC06EC, + LocationName.yoshis_island_2_flying_block_4: 0xBC06ED, + LocationName.yoshis_island_2_flying_block_5: 0xBC06EE, + LocationName.yoshis_island_2_flying_block_6: 0xBC06EF, + LocationName.yoshis_island_2_coin_block_1: 0xBC06F0, + LocationName.yoshis_island_2_yellow_block_1: 0xBC06F1, + LocationName.yoshis_island_2_coin_block_2: 0xBC06F2, + LocationName.yoshis_island_2_coin_block_3: 0xBC06F3, + LocationName.yoshis_island_2_yoshi_block_1: 0xBC06F4, + LocationName.yoshis_island_2_coin_block_4: 0xBC06F5, + LocationName.yoshis_island_2_yoshi_block_2: 0xBC06F6, + LocationName.yoshis_island_2_coin_block_5: 0xBC06F7, + LocationName.yoshis_island_2_vine_block_1: 0xBC06F8, + LocationName.yoshis_island_2_yellow_block_2: 0xBC06F9, + LocationName.vanilla_ghost_house_powerup_block_1: 0xBC06FA, + LocationName.vanilla_ghost_house_vine_block_1: 0xBC06FB, + LocationName.vanilla_ghost_house_powerup_block_2: 0xBC06FC, + LocationName.vanilla_ghost_house_multi_coin_block_1: 0xBC06FD, + LocationName.vanilla_ghost_house_blue_pow_block_1: 0xBC06FE, + LocationName.vanilla_secret_1_coin_block_1: 0xBC06FF, + LocationName.vanilla_secret_1_powerup_block_1: 0xBC0700, + LocationName.vanilla_secret_1_multi_coin_block_1: 0xBC0701, + LocationName.vanilla_secret_1_vine_block_1: 0xBC0702, + LocationName.vanilla_secret_1_vine_block_2: 0xBC0703, + LocationName.vanilla_secret_1_coin_block_2: 0xBC0704, + LocationName.vanilla_secret_1_coin_block_3: 0xBC0705, + LocationName.vanilla_secret_1_powerup_block_2: 0xBC0706, + LocationName.vanilla_dome_3_coin_block_1: 0xBC0707, + LocationName.vanilla_dome_3_flying_block_1: 0xBC0708, + LocationName.vanilla_dome_3_flying_block_2: 0xBC0709, + LocationName.vanilla_dome_3_powerup_block_1: 0xBC070A, + LocationName.vanilla_dome_3_flying_block_3: 0xBC070B, + LocationName.vanilla_dome_3_invis_coin_block_1: 0xBC070C, + LocationName.vanilla_dome_3_powerup_block_2: 0xBC070D, + LocationName.vanilla_dome_3_multi_coin_block_1: 0xBC070E, + LocationName.vanilla_dome_3_powerup_block_3: 0xBC070F, + LocationName.vanilla_dome_3_yoshi_block_1: 0xBC0710, + LocationName.vanilla_dome_3_powerup_block_4: 0xBC0711, + LocationName.vanilla_dome_3_pswitch_coin_block_1: 0xBC0712, + LocationName.vanilla_dome_3_pswitch_coin_block_2: 0xBC0713, + LocationName.vanilla_dome_3_pswitch_coin_block_3: 0xBC0714, + LocationName.vanilla_dome_3_pswitch_coin_block_4: 0xBC0715, + LocationName.vanilla_dome_3_pswitch_coin_block_5: 0xBC0716, + LocationName.vanilla_dome_3_pswitch_coin_block_6: 0xBC0717, + LocationName.donut_secret_2_directional_coin_block_1: 0xBC0718, + LocationName.donut_secret_2_vine_block_1: 0xBC0719, + LocationName.donut_secret_2_star_block_1: 0xBC071A, + LocationName.donut_secret_2_powerup_block_1: 0xBC071B, + LocationName.donut_secret_2_star_block_2: 0xBC071C, + LocationName.valley_of_bowser_4_yellow_block_1: 0xBC071D, + LocationName.valley_of_bowser_4_powerup_block_1: 0xBC071E, + LocationName.valley_of_bowser_4_vine_block_1: 0xBC071F, + LocationName.valley_of_bowser_4_yoshi_block_1: 0xBC0720, + LocationName.valley_of_bowser_4_life_block_1: 0xBC0721, + LocationName.valley_of_bowser_4_powerup_block_2: 0xBC0722, + LocationName.valley_castle_yellow_block_1: 0xBC0723, + LocationName.valley_castle_yellow_block_2: 0xBC0724, + LocationName.valley_castle_green_block_1: 0xBC0725, + LocationName.valley_fortress_green_block_1: 0xBC0726, + LocationName.valley_fortress_yellow_block_1: 0xBC0727, + LocationName.valley_of_bowser_3_powerup_block_1: 0xBC0728, + LocationName.valley_of_bowser_3_powerup_block_2: 0xBC0729, + LocationName.valley_ghost_house_pswitch_coin_block_1: 0xBC072A, + LocationName.valley_ghost_house_multi_coin_block_1: 0xBC072B, + LocationName.valley_ghost_house_powerup_block_1: 0xBC072C, + LocationName.valley_ghost_house_directional_coin_block_1: 0xBC072D, + LocationName.valley_of_bowser_2_powerup_block_1: 0xBC072E, + LocationName.valley_of_bowser_2_yellow_block_1: 0xBC072F, + LocationName.valley_of_bowser_2_powerup_block_2: 0xBC0730, + LocationName.valley_of_bowser_2_wings_block_1: 0xBC0731, + LocationName.valley_of_bowser_1_green_block_1: 0xBC0732, + LocationName.valley_of_bowser_1_invis_coin_block_1: 0xBC0733, + LocationName.valley_of_bowser_1_invis_coin_block_2: 0xBC0734, + LocationName.valley_of_bowser_1_invis_coin_block_3: 0xBC0735, + LocationName.valley_of_bowser_1_yellow_block_1: 0xBC0736, + LocationName.valley_of_bowser_1_yellow_block_2: 0xBC0737, + LocationName.valley_of_bowser_1_yellow_block_3: 0xBC0738, + LocationName.valley_of_bowser_1_yellow_block_4: 0xBC0739, + LocationName.valley_of_bowser_1_vine_block_1: 0xBC073A, + LocationName.chocolate_secret_powerup_block_1: 0xBC073B, + LocationName.chocolate_secret_powerup_block_2: 0xBC073C, + LocationName.vanilla_dome_2_coin_block_1: 0xBC073D, + LocationName.vanilla_dome_2_powerup_block_1: 0xBC073E, + LocationName.vanilla_dome_2_coin_block_2: 0xBC073F, + LocationName.vanilla_dome_2_coin_block_3: 0xBC0740, + LocationName.vanilla_dome_2_vine_block_1: 0xBC0741, + LocationName.vanilla_dome_2_invis_life_block_1: 0xBC0742, + LocationName.vanilla_dome_2_coin_block_4: 0xBC0743, + LocationName.vanilla_dome_2_coin_block_5: 0xBC0744, + LocationName.vanilla_dome_2_powerup_block_2: 0xBC0745, + LocationName.vanilla_dome_2_powerup_block_3: 0xBC0746, + LocationName.vanilla_dome_2_powerup_block_4: 0xBC0747, + LocationName.vanilla_dome_2_powerup_block_5: 0xBC0748, + LocationName.vanilla_dome_2_multi_coin_block_1: 0xBC0749, + LocationName.vanilla_dome_2_multi_coin_block_2: 0xBC074A, + LocationName.vanilla_dome_4_powerup_block_1: 0xBC074B, + LocationName.vanilla_dome_4_powerup_block_2: 0xBC074C, + LocationName.vanilla_dome_4_coin_block_1: 0xBC074D, + LocationName.vanilla_dome_4_coin_block_2: 0xBC074E, + LocationName.vanilla_dome_4_coin_block_3: 0xBC074F, + LocationName.vanilla_dome_4_life_block_1: 0xBC0750, + LocationName.vanilla_dome_4_coin_block_4: 0xBC0751, + LocationName.vanilla_dome_4_coin_block_5: 0xBC0752, + LocationName.vanilla_dome_4_coin_block_6: 0xBC0753, + LocationName.vanilla_dome_4_coin_block_7: 0xBC0754, + LocationName.vanilla_dome_4_coin_block_8: 0xBC0755, + LocationName.vanilla_dome_1_flying_block_1: 0xBC0756, + LocationName.vanilla_dome_1_powerup_block_1: 0xBC0757, + LocationName.vanilla_dome_1_powerup_block_2: 0xBC0758, + LocationName.vanilla_dome_1_coin_block_1: 0xBC0759, + LocationName.vanilla_dome_1_life_block_1: 0xBC075A, + LocationName.vanilla_dome_1_powerup_block_3: 0xBC075B, + LocationName.vanilla_dome_1_vine_block_1: 0xBC075C, + LocationName.vanilla_dome_1_star_block_1: 0xBC075D, + LocationName.vanilla_dome_1_powerup_block_4: 0xBC075E, + LocationName.vanilla_dome_1_coin_block_2: 0xBC075F, + LocationName.vanilla_dome_castle_life_block_1: 0xBC0760, + LocationName.vanilla_dome_castle_life_block_2: 0xBC0761, + LocationName.vanilla_dome_castle_powerup_block_1: 0xBC0762, + LocationName.vanilla_dome_castle_life_block_3: 0xBC0763, + LocationName.vanilla_dome_castle_green_block_1: 0xBC0764, + LocationName.forest_ghost_house_coin_block_1: 0xBC0765, + LocationName.forest_ghost_house_powerup_block_1: 0xBC0766, + LocationName.forest_ghost_house_flying_block_1: 0xBC0767, + LocationName.forest_ghost_house_powerup_block_2: 0xBC0768, + LocationName.forest_ghost_house_life_block_1: 0xBC0769, + LocationName.forest_of_illusion_1_powerup_block_1: 0xBC076A, + LocationName.forest_of_illusion_1_yoshi_block_1: 0xBC076B, + LocationName.forest_of_illusion_1_powerup_block_2: 0xBC076C, + LocationName.forest_of_illusion_1_key_block_1: 0xBC076D, + LocationName.forest_of_illusion_1_life_block_1: 0xBC076E, + LocationName.forest_of_illusion_4_multi_coin_block_1: 0xBC076F, + LocationName.forest_of_illusion_4_coin_block_1: 0xBC0770, + LocationName.forest_of_illusion_4_coin_block_2: 0xBC0771, + LocationName.forest_of_illusion_4_coin_block_3: 0xBC0772, + LocationName.forest_of_illusion_4_coin_block_4: 0xBC0773, + LocationName.forest_of_illusion_4_powerup_block_1: 0xBC0774, + LocationName.forest_of_illusion_4_coin_block_5: 0xBC0775, + LocationName.forest_of_illusion_4_coin_block_6: 0xBC0776, + LocationName.forest_of_illusion_4_coin_block_7: 0xBC0777, + LocationName.forest_of_illusion_4_powerup_block_2: 0xBC0778, + LocationName.forest_of_illusion_4_coin_block_8: 0xBC0779, + LocationName.forest_of_illusion_4_coin_block_9: 0xBC077A, + LocationName.forest_of_illusion_4_coin_block_10: 0xBC077B, + LocationName.forest_of_illusion_2_green_block_1: 0xBC077C, + LocationName.forest_of_illusion_2_powerup_block_1: 0xBC077D, + LocationName.forest_of_illusion_2_invis_coin_block_1: 0xBC077E, + LocationName.forest_of_illusion_2_invis_coin_block_2: 0xBC077F, + LocationName.forest_of_illusion_2_invis_life_block_1: 0xBC0780, + LocationName.forest_of_illusion_2_invis_coin_block_3: 0xBC0781, + LocationName.forest_of_illusion_2_yellow_block_1: 0xBC0782, + LocationName.forest_secret_powerup_block_1: 0xBC0783, + LocationName.forest_secret_powerup_block_2: 0xBC0784, + LocationName.forest_secret_life_block_1: 0xBC0785, + LocationName.forest_of_illusion_3_yoshi_block_1: 0xBC0786, + LocationName.forest_of_illusion_3_coin_block_1: 0xBC0787, + LocationName.forest_of_illusion_3_multi_coin_block_1: 0xBC0788, + LocationName.forest_of_illusion_3_coin_block_2: 0xBC0789, + LocationName.forest_of_illusion_3_multi_coin_block_2: 0xBC078A, + LocationName.forest_of_illusion_3_coin_block_3: 0xBC078B, + LocationName.forest_of_illusion_3_coin_block_4: 0xBC078C, + LocationName.forest_of_illusion_3_coin_block_5: 0xBC078D, + LocationName.forest_of_illusion_3_coin_block_6: 0xBC078E, + LocationName.forest_of_illusion_3_coin_block_7: 0xBC078F, + LocationName.forest_of_illusion_3_coin_block_8: 0xBC0790, + LocationName.forest_of_illusion_3_coin_block_9: 0xBC0791, + LocationName.forest_of_illusion_3_coin_block_10: 0xBC0792, + LocationName.forest_of_illusion_3_coin_block_11: 0xBC0793, + LocationName.forest_of_illusion_3_coin_block_12: 0xBC0794, + LocationName.forest_of_illusion_3_coin_block_13: 0xBC0795, + LocationName.forest_of_illusion_3_coin_block_14: 0xBC0796, + LocationName.forest_of_illusion_3_coin_block_15: 0xBC0797, + LocationName.forest_of_illusion_3_coin_block_16: 0xBC0798, + LocationName.forest_of_illusion_3_coin_block_17: 0xBC0799, + LocationName.forest_of_illusion_3_coin_block_18: 0xBC079A, + LocationName.forest_of_illusion_3_coin_block_19: 0xBC079B, + LocationName.forest_of_illusion_3_coin_block_20: 0xBC079C, + LocationName.forest_of_illusion_3_coin_block_21: 0xBC079D, + LocationName.forest_of_illusion_3_coin_block_22: 0xBC079E, + LocationName.forest_of_illusion_3_coin_block_23: 0xBC079F, + LocationName.forest_of_illusion_3_coin_block_24: 0xBC07A0, + LocationName.special_zone_8_yoshi_block_1: 0xBC07A1, + LocationName.special_zone_8_coin_block_1: 0xBC07A2, + LocationName.special_zone_8_coin_block_2: 0xBC07A3, + LocationName.special_zone_8_coin_block_3: 0xBC07A4, + LocationName.special_zone_8_coin_block_4: 0xBC07A5, + LocationName.special_zone_8_coin_block_5: 0xBC07A6, + LocationName.special_zone_8_blue_pow_block_1: 0xBC07A7, + LocationName.special_zone_8_powerup_block_1: 0xBC07A8, + LocationName.special_zone_8_star_block_1: 0xBC07A9, + LocationName.special_zone_8_coin_block_6: 0xBC07AA, + LocationName.special_zone_8_coin_block_7: 0xBC07AB, + LocationName.special_zone_8_coin_block_8: 0xBC07AC, + LocationName.special_zone_8_coin_block_9: 0xBC07AD, + LocationName.special_zone_8_coin_block_10: 0xBC07AE, + LocationName.special_zone_8_coin_block_11: 0xBC07AF, + LocationName.special_zone_8_coin_block_12: 0xBC07B0, + LocationName.special_zone_8_coin_block_13: 0xBC07B1, + LocationName.special_zone_8_coin_block_14: 0xBC07B2, + LocationName.special_zone_8_coin_block_15: 0xBC07B3, + LocationName.special_zone_8_coin_block_16: 0xBC07B4, + LocationName.special_zone_8_coin_block_17: 0xBC07B5, + LocationName.special_zone_8_coin_block_18: 0xBC07B6, + LocationName.special_zone_8_multi_coin_block_1: 0xBC07B7, + LocationName.special_zone_8_coin_block_19: 0xBC07B8, + LocationName.special_zone_8_coin_block_20: 0xBC07B9, + LocationName.special_zone_8_coin_block_21: 0xBC07BA, + LocationName.special_zone_8_coin_block_22: 0xBC07BB, + LocationName.special_zone_8_coin_block_23: 0xBC07BC, + LocationName.special_zone_8_powerup_block_2: 0xBC07BD, + LocationName.special_zone_8_flying_block_1: 0xBC07BE, + LocationName.special_zone_7_powerup_block_1: 0xBC07BF, + LocationName.special_zone_7_yoshi_block_1: 0xBC07C0, + LocationName.special_zone_7_coin_block_1: 0xBC07C1, + LocationName.special_zone_7_powerup_block_2: 0xBC07C2, + LocationName.special_zone_7_coin_block_2: 0xBC07C3, + LocationName.special_zone_6_powerup_block_1: 0xBC07C4, + LocationName.special_zone_6_coin_block_1: 0xBC07C5, + LocationName.special_zone_6_coin_block_2: 0xBC07C6, + LocationName.special_zone_6_yoshi_block_1: 0xBC07C7, + LocationName.special_zone_6_life_block_1: 0xBC07C8, + LocationName.special_zone_6_multi_coin_block_1: 0xBC07C9, + LocationName.special_zone_6_coin_block_3: 0xBC07CA, + LocationName.special_zone_6_coin_block_4: 0xBC07CB, + LocationName.special_zone_6_coin_block_5: 0xBC07CC, + LocationName.special_zone_6_coin_block_6: 0xBC07CD, + LocationName.special_zone_6_coin_block_7: 0xBC07CE, + LocationName.special_zone_6_coin_block_8: 0xBC07CF, + LocationName.special_zone_6_coin_block_9: 0xBC07D0, + LocationName.special_zone_6_coin_block_10: 0xBC07D1, + LocationName.special_zone_6_coin_block_11: 0xBC07D2, + LocationName.special_zone_6_coin_block_12: 0xBC07D3, + LocationName.special_zone_6_coin_block_13: 0xBC07D4, + LocationName.special_zone_6_coin_block_14: 0xBC07D5, + LocationName.special_zone_6_coin_block_15: 0xBC07D6, + LocationName.special_zone_6_coin_block_16: 0xBC07D7, + LocationName.special_zone_6_coin_block_17: 0xBC07D8, + LocationName.special_zone_6_coin_block_18: 0xBC07D9, + LocationName.special_zone_6_coin_block_19: 0xBC07DA, + LocationName.special_zone_6_coin_block_20: 0xBC07DB, + LocationName.special_zone_6_coin_block_21: 0xBC07DC, + LocationName.special_zone_6_coin_block_22: 0xBC07DD, + LocationName.special_zone_6_coin_block_23: 0xBC07DE, + LocationName.special_zone_6_coin_block_24: 0xBC07DF, + LocationName.special_zone_6_coin_block_25: 0xBC07E0, + LocationName.special_zone_6_coin_block_26: 0xBC07E1, + LocationName.special_zone_6_coin_block_27: 0xBC07E2, + LocationName.special_zone_6_coin_block_28: 0xBC07E3, + LocationName.special_zone_6_powerup_block_2: 0xBC07E4, + LocationName.special_zone_6_coin_block_29: 0xBC07E5, + LocationName.special_zone_6_coin_block_30: 0xBC07E6, + LocationName.special_zone_6_coin_block_31: 0xBC07E7, + LocationName.special_zone_6_coin_block_32: 0xBC07E8, + LocationName.special_zone_6_coin_block_33: 0xBC07E9, + LocationName.special_zone_5_yoshi_block_1: 0xBC07EA, + LocationName.special_zone_1_vine_block_1: 0xBC07EB, + LocationName.special_zone_1_vine_block_2: 0xBC07EC, + LocationName.special_zone_1_vine_block_3: 0xBC07ED, + LocationName.special_zone_1_vine_block_4: 0xBC07EE, + LocationName.special_zone_1_life_block_1: 0xBC07EF, + LocationName.special_zone_1_vine_block_5: 0xBC07F0, + LocationName.special_zone_1_blue_pow_block_1: 0xBC07F1, + LocationName.special_zone_1_vine_block_6: 0xBC07F2, + LocationName.special_zone_1_powerup_block_1: 0xBC07F3, + LocationName.special_zone_1_pswitch_coin_block_1: 0xBC07F4, + LocationName.special_zone_1_pswitch_coin_block_2: 0xBC07F5, + LocationName.special_zone_1_pswitch_coin_block_3: 0xBC07F6, + LocationName.special_zone_1_pswitch_coin_block_4: 0xBC07F7, + LocationName.special_zone_1_pswitch_coin_block_5: 0xBC07F8, + LocationName.special_zone_1_pswitch_coin_block_6: 0xBC07F9, + LocationName.special_zone_1_pswitch_coin_block_7: 0xBC07FA, + LocationName.special_zone_1_pswitch_coin_block_8: 0xBC07FB, + LocationName.special_zone_1_pswitch_coin_block_9: 0xBC07FC, + LocationName.special_zone_1_pswitch_coin_block_10: 0xBC07FD, + LocationName.special_zone_1_pswitch_coin_block_11: 0xBC07FE, + LocationName.special_zone_1_pswitch_coin_block_12: 0xBC07FF, + LocationName.special_zone_1_pswitch_coin_block_13: 0xBC0800, + LocationName.special_zone_2_powerup_block_1: 0xBC0801, + LocationName.special_zone_2_coin_block_1: 0xBC0802, + LocationName.special_zone_2_coin_block_2: 0xBC0803, + LocationName.special_zone_2_powerup_block_2: 0xBC0804, + LocationName.special_zone_2_coin_block_3: 0xBC0805, + LocationName.special_zone_2_coin_block_4: 0xBC0806, + LocationName.special_zone_2_powerup_block_3: 0xBC0807, + LocationName.special_zone_2_multi_coin_block_1: 0xBC0808, + LocationName.special_zone_2_coin_block_5: 0xBC0809, + LocationName.special_zone_2_coin_block_6: 0xBC080A, + LocationName.special_zone_3_powerup_block_1: 0xBC080B, + LocationName.special_zone_3_yoshi_block_1: 0xBC080C, + LocationName.special_zone_3_wings_block_1: 0xBC080D, + LocationName.special_zone_4_powerup_block_1: 0xBC080E, + LocationName.special_zone_4_star_block_1: 0xBC080F, + LocationName.star_road_2_star_block_1: 0xBC0810, + LocationName.star_road_3_key_block_1: 0xBC0811, + LocationName.star_road_4_powerup_block_1: 0xBC0812, + LocationName.star_road_4_green_block_1: 0xBC0813, + LocationName.star_road_4_green_block_2: 0xBC0814, + LocationName.star_road_4_green_block_3: 0xBC0815, + LocationName.star_road_4_green_block_4: 0xBC0816, + LocationName.star_road_4_green_block_5: 0xBC0817, + LocationName.star_road_4_green_block_6: 0xBC0818, + LocationName.star_road_4_green_block_7: 0xBC0819, + LocationName.star_road_4_key_block_1: 0xBC081A, + LocationName.star_road_5_directional_coin_block_1: 0xBC081B, + LocationName.star_road_5_life_block_1: 0xBC081C, + LocationName.star_road_5_vine_block_1: 0xBC081D, + LocationName.star_road_5_yellow_block_1: 0xBC081E, + LocationName.star_road_5_yellow_block_2: 0xBC081F, + LocationName.star_road_5_yellow_block_3: 0xBC0820, + LocationName.star_road_5_yellow_block_4: 0xBC0821, + LocationName.star_road_5_yellow_block_5: 0xBC0822, + LocationName.star_road_5_yellow_block_6: 0xBC0823, + LocationName.star_road_5_yellow_block_7: 0xBC0824, + LocationName.star_road_5_yellow_block_8: 0xBC0825, + LocationName.star_road_5_yellow_block_9: 0xBC0826, + LocationName.star_road_5_yellow_block_10: 0xBC0827, + LocationName.star_road_5_yellow_block_11: 0xBC0828, + LocationName.star_road_5_yellow_block_12: 0xBC0829, + LocationName.star_road_5_yellow_block_13: 0xBC082A, + LocationName.star_road_5_yellow_block_14: 0xBC082B, + LocationName.star_road_5_yellow_block_15: 0xBC082C, + LocationName.star_road_5_yellow_block_16: 0xBC082D, + LocationName.star_road_5_yellow_block_17: 0xBC082E, + LocationName.star_road_5_yellow_block_18: 0xBC082F, + LocationName.star_road_5_yellow_block_19: 0xBC0830, + LocationName.star_road_5_yellow_block_20: 0xBC0831, + LocationName.star_road_5_green_block_1: 0xBC0832, + LocationName.star_road_5_green_block_2: 0xBC0833, + LocationName.star_road_5_green_block_3: 0xBC0834, + LocationName.star_road_5_green_block_4: 0xBC0835, + LocationName.star_road_5_green_block_5: 0xBC0836, + LocationName.star_road_5_green_block_6: 0xBC0837, + LocationName.star_road_5_green_block_7: 0xBC0838, + LocationName.star_road_5_green_block_8: 0xBC0839, + LocationName.star_road_5_green_block_9: 0xBC083A, + LocationName.star_road_5_green_block_10: 0xBC083B, + LocationName.star_road_5_green_block_11: 0xBC083C, + LocationName.star_road_5_green_block_12: 0xBC083D, + LocationName.star_road_5_green_block_13: 0xBC083E, + LocationName.star_road_5_green_block_14: 0xBC083F, + LocationName.star_road_5_green_block_15: 0xBC0840, + LocationName.star_road_5_green_block_16: 0xBC0841, + LocationName.star_road_5_green_block_17: 0xBC0842, + LocationName.star_road_5_green_block_18: 0xBC0843, + LocationName.star_road_5_green_block_19: 0xBC0844, + LocationName.star_road_5_green_block_20: 0xBC0845 +} + bowser_location_table = { LocationName.bowser: 0xBC0200, } @@ -208,6 +826,10 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None all_locations = { **level_location_table, **dragon_coin_location_table, + **moon_location_table, + **hidden_1ups_location_table, + **bonus_block_location_table, + **blocksanity_location_table, **bowser_location_table, **yoshi_house_location_table, } @@ -234,20 +856,149 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None LocationName.special_zone_8_dragon, ] +special_zone_hidden_1up_names = [ + LocationName.special_zone_1_hidden_1up +] + +special_zone_blocksanity_names = [ + LocationName.special_zone_8_yoshi_block_1, + LocationName.special_zone_8_coin_block_1, + LocationName.special_zone_8_coin_block_2, + LocationName.special_zone_8_coin_block_3, + LocationName.special_zone_8_coin_block_4, + LocationName.special_zone_8_coin_block_5, + LocationName.special_zone_8_blue_pow_block_1, + LocationName.special_zone_8_powerup_block_1, + LocationName.special_zone_8_star_block_1, + LocationName.special_zone_8_coin_block_6, + LocationName.special_zone_8_coin_block_7, + LocationName.special_zone_8_coin_block_8, + LocationName.special_zone_8_coin_block_9, + LocationName.special_zone_8_coin_block_10, + LocationName.special_zone_8_coin_block_11, + LocationName.special_zone_8_coin_block_12, + LocationName.special_zone_8_coin_block_13, + LocationName.special_zone_8_coin_block_14, + LocationName.special_zone_8_coin_block_15, + LocationName.special_zone_8_coin_block_16, + LocationName.special_zone_8_coin_block_17, + LocationName.special_zone_8_coin_block_18, + LocationName.special_zone_8_multi_coin_block_1, + LocationName.special_zone_8_coin_block_19, + LocationName.special_zone_8_coin_block_20, + LocationName.special_zone_8_coin_block_21, + LocationName.special_zone_8_coin_block_22, + LocationName.special_zone_8_coin_block_23, + LocationName.special_zone_8_powerup_block_2, + LocationName.special_zone_8_flying_block_1, + LocationName.special_zone_7_powerup_block_1, + LocationName.special_zone_7_yoshi_block_1, + LocationName.special_zone_7_coin_block_1, + LocationName.special_zone_7_powerup_block_2, + LocationName.special_zone_7_coin_block_2, + LocationName.special_zone_6_powerup_block_1, + LocationName.special_zone_6_coin_block_1, + LocationName.special_zone_6_coin_block_2, + LocationName.special_zone_6_yoshi_block_1, + LocationName.special_zone_6_life_block_1, + LocationName.special_zone_6_multi_coin_block_1, + LocationName.special_zone_6_coin_block_3, + LocationName.special_zone_6_coin_block_4, + LocationName.special_zone_6_coin_block_5, + LocationName.special_zone_6_coin_block_6, + LocationName.special_zone_6_coin_block_7, + LocationName.special_zone_6_coin_block_8, + LocationName.special_zone_6_coin_block_9, + LocationName.special_zone_6_coin_block_10, + LocationName.special_zone_6_coin_block_11, + LocationName.special_zone_6_coin_block_12, + LocationName.special_zone_6_coin_block_13, + LocationName.special_zone_6_coin_block_14, + LocationName.special_zone_6_coin_block_15, + LocationName.special_zone_6_coin_block_16, + LocationName.special_zone_6_coin_block_17, + LocationName.special_zone_6_coin_block_18, + LocationName.special_zone_6_coin_block_19, + LocationName.special_zone_6_coin_block_20, + LocationName.special_zone_6_coin_block_21, + LocationName.special_zone_6_coin_block_22, + LocationName.special_zone_6_coin_block_23, + LocationName.special_zone_6_coin_block_24, + LocationName.special_zone_6_coin_block_25, + LocationName.special_zone_6_coin_block_26, + LocationName.special_zone_6_coin_block_27, + LocationName.special_zone_6_coin_block_28, + LocationName.special_zone_6_powerup_block_2, + LocationName.special_zone_6_coin_block_29, + LocationName.special_zone_6_coin_block_30, + LocationName.special_zone_6_coin_block_31, + LocationName.special_zone_6_coin_block_32, + LocationName.special_zone_6_coin_block_33, + LocationName.special_zone_5_yoshi_block_1, + LocationName.special_zone_1_vine_block_1, + LocationName.special_zone_1_vine_block_2, + LocationName.special_zone_1_vine_block_3, + LocationName.special_zone_1_vine_block_4, + LocationName.special_zone_1_life_block_1, + LocationName.special_zone_1_vine_block_5, + LocationName.special_zone_1_blue_pow_block_1, + LocationName.special_zone_1_vine_block_6, + LocationName.special_zone_1_powerup_block_1, + LocationName.special_zone_1_pswitch_coin_block_1, + LocationName.special_zone_1_pswitch_coin_block_2, + LocationName.special_zone_1_pswitch_coin_block_3, + LocationName.special_zone_1_pswitch_coin_block_4, + LocationName.special_zone_1_pswitch_coin_block_5, + LocationName.special_zone_1_pswitch_coin_block_6, + LocationName.special_zone_1_pswitch_coin_block_7, + LocationName.special_zone_1_pswitch_coin_block_8, + LocationName.special_zone_1_pswitch_coin_block_9, + LocationName.special_zone_1_pswitch_coin_block_10, + LocationName.special_zone_1_pswitch_coin_block_11, + LocationName.special_zone_1_pswitch_coin_block_12, + LocationName.special_zone_1_pswitch_coin_block_13, + LocationName.special_zone_2_powerup_block_1, + LocationName.special_zone_2_coin_block_1, + LocationName.special_zone_2_coin_block_2, + LocationName.special_zone_2_powerup_block_2, + LocationName.special_zone_2_coin_block_3, + LocationName.special_zone_2_coin_block_4, + LocationName.special_zone_2_powerup_block_3, + LocationName.special_zone_2_multi_coin_block_1, + LocationName.special_zone_2_coin_block_5, + LocationName.special_zone_2_coin_block_6, + LocationName.special_zone_3_powerup_block_1, + LocationName.special_zone_3_yoshi_block_1, + LocationName.special_zone_3_wings_block_1, + LocationName.special_zone_4_powerup_block_1, + LocationName.special_zone_4_star_block_1 +] + location_table = {} -def setup_locations(world, player: int): +def setup_locations(world: World): location_table = {**level_location_table} - # Dragon Coins here - if world.dragon_coin_checks[player].value: - location_table.update({**dragon_coin_location_table}) + if world.options.dragon_coin_checks: + location_table.update(dragon_coin_location_table) + + if world.options.moon_checks: + location_table.update(moon_location_table) + + if world.options.hidden_1up_checks: + location_table.update(hidden_1ups_location_table) + + if world.options.bonus_block_checks: + location_table.update(bonus_block_location_table) + + if world.options.blocksanity: + location_table.update(blocksanity_location_table) - if world.goal[player] == "yoshi_egg_hunt": - location_table.update({**yoshi_house_location_table}) + if world.options.goal == "yoshi_egg_hunt": + location_table.update(yoshi_house_location_table) else: - location_table.update({**bowser_location_table}) + location_table.update(bowser_location_table) return location_table diff --git a/worlds/smw/Names/ItemName.py b/worlds/smw/Names/ItemName.py index fecb18685ef4..e6eced410059 100644 --- a/worlds/smw/Names/ItemName.py +++ b/worlds/smw/Names/ItemName.py @@ -1,5 +1,9 @@ # Junk Definitions one_up_mushroom = "1-Up Mushroom" +one_coin = "1 coin" +five_coins = "5 coins" +ten_coins = "10 coins" +fifty_coins = "50 coins" # Collectable Definitions yoshi_egg = "Yoshi Egg" @@ -22,11 +26,16 @@ red_switch_palace = "Red Switch Palace" blue_switch_palace = "Blue Switch Palace" +# Special Zone clear flag definition +special_world_clear = "Special Zone Clear" + # Trap Definitions -ice_trap = "Ice Trap" -stun_trap = "Stun Trap" -literature_trap = "Literature Trap" -timer_trap = "Timer Trap" +ice_trap = "Ice Trap" +stun_trap = "Stun Trap" +literature_trap = "Literature Trap" +timer_trap = "Timer Trap" +reverse_controls_trap = "Reverse Trap" +thwimp_trap = "Thwimp Trap" # Other Definitions victory = "The Princess" diff --git a/worlds/smw/Names/LocationName.py b/worlds/smw/Names/LocationName.py index cc01b05ece59..847b724f7837 100644 --- a/worlds/smw/Names/LocationName.py +++ b/worlds/smw/Names/LocationName.py @@ -4,12 +4,15 @@ yoshis_island_1_exit_1 = "Yoshi's Island 1 - Normal Exit" yoshis_island_1_dragon = "Yoshi's Island 1 - Dragon Coins" +yoshis_island_1_moon = "Yoshi's Island 1 - 3-Up Moon" yoshis_island_2_exit_1 = "Yoshi's Island 2 - Normal Exit" yoshis_island_2_dragon = "Yoshi's Island 2 - Dragon Coins" yoshis_island_3_exit_1 = "Yoshi's Island 3 - Normal Exit" yoshis_island_3_dragon = "Yoshi's Island 3 - Dragon Coins" +yoshis_island_3_bonus_block = "Yoshi's Island 3 - 1-Up from Bonus Block" yoshis_island_4_exit_1 = "Yoshi's Island 4 - Normal Exit" yoshis_island_4_dragon = "Yoshi's Island 4 - Dragon Coins" +yoshis_island_4_hidden_1up = "Yoshi's Island 4 - Hidden 1-Up" yoshis_island_castle = "#1 Iggy's Castle - Normal Exit" yoshis_island_koopaling = "#1 Iggy's Castle - Boss" @@ -18,13 +21,17 @@ donut_plains_1_exit_1 = "Donut Plains 1 - Normal Exit" donut_plains_1_exit_2 = "Donut Plains 1 - Secret Exit" donut_plains_1_dragon = "Donut Plains 1 - Dragon Coins" +donut_plains_1_hidden_1up = "Donut Plains 1 - Hidden 1-Up" donut_plains_2_exit_1 = "Donut Plains 2 - Normal Exit" donut_plains_2_exit_2 = "Donut Plains 2 - Secret Exit" donut_plains_2_dragon = "Donut Plains 2 - Dragon Coins" donut_plains_3_exit_1 = "Donut Plains 3 - Normal Exit" donut_plains_3_dragon = "Donut Plains 3 - Dragon Coins" +donut_plains_3_bonus_block = "Donut Plains 3 - 1-Up from Bonus Block" donut_plains_4_exit_1 = "Donut Plains 4 - Normal Exit" donut_plains_4_dragon = "Donut Plains 4 - Dragon Coins" +donut_plains_4_moon = "Donut Plains 4 - 3-Up Moon" +donut_plains_4_hidden_1up = "Donut Plains 4 - Hidden 1-Up" donut_secret_1_exit_1 = "Donut Secret 1 - Normal Exit" donut_secret_1_exit_2 = "Donut Secret 1 - Secret Exit" donut_secret_1_dragon = "Donut Secret 1 - Dragon Coins" @@ -35,6 +42,7 @@ donut_secret_house_exit_1 = "Donut Secret House - Normal Exit" donut_secret_house_exit_2 = "Donut Secret House - Secret Exit" donut_plains_castle = "#2 Morton's Castle - Normal Exit" +donut_plains_castle_hidden_1up = "#2 Morton's Castle - Hidden 1-Up" donut_plains_koopaling = "#2 Morton's Castle - Boss" green_switch_palace = "Green Switch Palace" @@ -47,8 +55,10 @@ vanilla_dome_2_dragon = "Vanilla Dome 2 - Dragon Coins" vanilla_dome_3_exit_1 = "Vanilla Dome 3 - Normal Exit" vanilla_dome_3_dragon = "Vanilla Dome 3 - Dragon Coins" +vanilla_dome_3_moon = "Vanilla Dome 3 - 3-Up Moon" vanilla_dome_4_exit_1 = "Vanilla Dome 4 - Normal Exit" vanilla_dome_4_dragon = "Vanilla Dome 4 - Dragon Coins" +vanilla_dome_4_hidden_1up = "Vanilla Dome 4 - Hidden 1-Up" vanilla_secret_1_exit_1 = "Vanilla Secret 1 - Normal Exit" vanilla_secret_1_exit_2 = "Vanilla Secret 1 - Secret Exit" vanilla_secret_1_dragon = "Vanilla Secret 1 - Dragon Coins" @@ -58,7 +68,9 @@ vanilla_secret_3_dragon = "Vanilla Secret 3 - Dragon Coins" vanilla_ghost_house_exit_1 = "Vanilla Ghost House - Normal Exit" vanilla_ghost_house_dragon = "Vanilla Ghost House - Dragon Coins" +vanilla_ghost_house_hidden_1up = "Vanilla Ghost House - Hidden 1-Up" vanilla_fortress = "Vanilla Fortress - Normal Exit" +vanilla_fortress_hidden_1up = "Vanilla Fortress - Hidden 1-Up" vanilla_reznor = "Vanilla Fortress - Boss" vanilla_dome_castle = "#3 Lemmy's Castle - Normal Exit" vanilla_dome_koopaling = "#3 Lemmy's Castle - Boss" @@ -67,13 +79,16 @@ butter_bridge_1_exit_1 = "Butter Bridge 1 - Normal Exit" butter_bridge_1_dragon = "Butter Bridge 1 - Dragon Coins" +butter_bridge_1_bonus_block = "Butter Bridge 1 - 1-Up from Bonus Block" butter_bridge_2_exit_1 = "Butter Bridge 2 - Normal Exit" butter_bridge_2_dragon = "Butter Bridge 2 - Dragon Coins" cheese_bridge_exit_1 = "Cheese Bridge - Normal Exit" cheese_bridge_exit_2 = "Cheese Bridge - Secret Exit" cheese_bridge_dragon = "Cheese Bridge - Dragon Coins" +cheese_bridge_moon = "Cheese Bridge - 3-Up Moon" cookie_mountain_exit_1 = "Cookie Mountain - Normal Exit" cookie_mountain_dragon = "Cookie Mountain - Dragon Coins" +cookie_mountain_hidden_1up = "Cookie Mountain - Hidden 1-Up" soda_lake_exit_1 = "Soda Lake - Normal Exit" soda_lake_dragon = "Soda Lake - Dragon Coins" twin_bridges_castle = "#4 Ludwig's Castle - Normal Exit" @@ -87,12 +102,14 @@ forest_of_illusion_3_exit_1 = "Forest of Illusion 3 - Normal Exit" forest_of_illusion_3_exit_2 = "Forest of Illusion 3 - Secret Exit" forest_of_illusion_3_dragon = "Forest of Illusion 3 - Dragon Coins" +forest_of_illusion_3_hidden_1up = "Forest of Illusion 3 - Hidden 1-Up" forest_of_illusion_4_exit_1 = "Forest of Illusion 4 - Normal Exit" forest_of_illusion_4_exit_2 = "Forest of Illusion 4 - Secret Exit" forest_of_illusion_4_dragon = "Forest of Illusion 4 - Dragon Coins" forest_ghost_house_exit_1 = "Forest Ghost House - Normal Exit" forest_ghost_house_exit_2 = "Forest Ghost House - Secret Exit" forest_ghost_house_dragon = "Forest Ghost House - Dragon Coins" +forest_ghost_house_moon = "Forest Ghost House - 3-Up Moon" forest_secret_exit_1 = "Forest Secret - Normal Exit" forest_secret_dragon = "Forest Secret - Dragon Coins" forest_fortress = "Forest Fortress - Normal Exit" @@ -105,12 +122,15 @@ chocolate_island_1_exit_1 = "Chocolate Island 1 - Normal Exit" chocolate_island_1_dragon = "Chocolate Island 1 - Dragon Coins" +chocolate_island_1_moon = "Chocolate Island 1 - 3-Up Moon" chocolate_island_2_exit_1 = "Chocolate Island 2 - Normal Exit" chocolate_island_2_exit_2 = "Chocolate Island 2 - Secret Exit" chocolate_island_2_dragon = "Chocolate Island 2 - Dragon Coins" +chocolate_island_2_hidden_1up = "Chocolate Island 2 - Hidden 1-Up" chocolate_island_3_exit_1 = "Chocolate Island 3 - Normal Exit" chocolate_island_3_exit_2 = "Chocolate Island 3 - Secret Exit" chocolate_island_3_dragon = "Chocolate Island 3 - Dragon Coins" +chocolate_island_3_bonus_block = "Chocolate Island 3 - 1-Up from Bonus Block" chocolate_island_4_exit_1 = "Chocolate Island 4 - Normal Exit" chocolate_island_4_dragon = "Chocolate Island 4 - Dragon Coins" chocolate_island_5_exit_1 = "Chocolate Island 5 - Normal Exit" @@ -120,6 +140,7 @@ chocolate_fortress = "Chocolate Fortress - Normal Exit" chocolate_reznor = "Chocolate Fortress - Boss" chocolate_castle = "#6 Wendy's Castle - Normal Exit" +chocolate_castle_hidden_1up = "#6 Wendy's Castle - Hidden 1-Up" chocolate_koopaling = "#6 Wendy's Castle - Boss" sunken_ghost_ship = "Sunken Ghost Ship - Normal Exit" @@ -127,9 +148,11 @@ valley_of_bowser_1_exit_1 = "Valley of Bowser 1 - Normal Exit" valley_of_bowser_1_dragon = "Valley of Bowser 1 - Dragon Coins" +valley_of_bowser_1_moon = "Valley of Bowser 1 - 3-Up Moon" valley_of_bowser_2_exit_1 = "Valley of Bowser 2 - Normal Exit" valley_of_bowser_2_exit_2 = "Valley of Bowser 2 - Secret Exit" valley_of_bowser_2_dragon = "Valley of Bowser 2 - Dragon Coins" +valley_of_bowser_2_hidden_1up = "Valley of Bowser 2 - Hidden 1-Up" valley_of_bowser_3_exit_1 = "Valley of Bowser 3 - Normal Exit" valley_of_bowser_3_dragon = "Valley of Bowser 3 - Dragon Coins" valley_of_bowser_4_exit_1 = "Valley of Bowser 4 - Normal Exit" @@ -141,6 +164,7 @@ valley_reznor = "Valley Fortress - Boss" valley_castle = "#7 Larry's Castle - Normal Exit" valley_castle_dragon = "#7 Larry's Castle - Dragon Coins" +valley_castle_hidden_1up = "#7 Larry's Castle - Hidden 1-Up" valley_koopaling = "#7 Larry's Castle - Boss" front_door = "Front Door" @@ -161,6 +185,7 @@ special_zone_1_exit_1 = "Gnarly - Normal Exit" special_zone_1_dragon = "Gnarly - Dragon Coins" +special_zone_1_hidden_1up = "Gnarly - Hidden 1-Up" special_zone_2_exit_1 = "Tubular - Normal Exit" special_zone_2_dragon = "Tubular - Dragon Coins" special_zone_3_exit_1 = "Way Cool - Normal Exit" @@ -362,3 +387,586 @@ special_zone_8_tile = "Funky - Tile" special_zone_8_region = "Funky" special_complete = "Special Zone - Star Road - Complete" + +vanilla_secret_2_yoshi_block_1 = "Vanilla Secret 2 - Yoshi Block #1" +vanilla_secret_2_green_block_1 = "Vanilla Secret 2 - Green Switch Palace Block #1" +vanilla_secret_2_powerup_block_1 = "Vanilla Secret 2 - Powerup Block #1" +vanilla_secret_2_powerup_block_2 = "Vanilla Secret 2 - Powerup Block #2" +vanilla_secret_2_multi_coin_block_1 = "Vanilla Secret 2 - Multi Coin Block #1" +vanilla_secret_2_gray_pow_block_1 = "Vanilla Secret 2 - Gray P-Switch Block #1" +vanilla_secret_2_coin_block_1 = "Vanilla Secret 2 - Coin Block #1" +vanilla_secret_2_coin_block_2 = "Vanilla Secret 2 - Coin Block #2" +vanilla_secret_2_coin_block_3 = "Vanilla Secret 2 - Coin Block #3" +vanilla_secret_2_coin_block_4 = "Vanilla Secret 2 - Coin Block #4" +vanilla_secret_2_coin_block_5 = "Vanilla Secret 2 - Coin Block #5" +vanilla_secret_2_coin_block_6 = "Vanilla Secret 2 - Coin Block #6" +vanilla_secret_3_powerup_block_1 = "Vanilla Secret 3 - Powerup Block #1" +vanilla_secret_3_powerup_block_2 = "Vanilla Secret 3 - Powerup Block #2" +donut_ghost_house_vine_block_1 = "Donut Ghost House - Vine|P-Switch Block #1" +donut_ghost_house_directional_coin_block_1 = "Donut Ghost House - Directional Coin Block #1" +donut_ghost_house_life_block_1 = "Donut Ghost House - 1-Up Mushroom Block #1" +donut_ghost_house_life_block_2 = "Donut Ghost House - 1-Up Mushroom Block #2" +donut_ghost_house_life_block_3 = "Donut Ghost House - 1-Up Mushroom Block #3" +donut_ghost_house_life_block_4 = "Donut Ghost House - 1-Up Mushroom Block #4" +donut_plains_3_green_block_1 = "Donut Plains 3 - Green Switch Palace Block #1" +donut_plains_3_coin_block_1 = "Donut Plains 3 - Coin Block #1" +donut_plains_3_coin_block_2 = "Donut Plains 3 - Coin Block #2" +donut_plains_3_vine_block_1 = "Donut Plains 3 - Vine Block #1" +donut_plains_3_powerup_block_1 = "Donut Plains 3 - Powerup Block #1" +donut_plains_3_bonus_block_1 = "Donut Plains 3 - Bonus Block #1" +donut_plains_4_coin_block_1 = "Donut Plains 4 - Coin Block #1" +donut_plains_4_powerup_block_1 = "Donut Plains 4 - Powerup Block #1" +donut_plains_4_coin_block_2 = "Donut Plains 4 - Coin Block #2" +donut_plains_4_yoshi_block_1 = "Donut Plains 4 - Yoshi Block #1" +donut_plains_castle_yellow_block_1 = "#2 Morton's Castle - Yellow Switch Palace Block #1" +donut_plains_castle_coin_block_1 = "#2 Morton's Castle - Coin Block #1" +donut_plains_castle_powerup_block_1 = "#2 Morton's Castle - Powerup Block #1" +donut_plains_castle_coin_block_2 = "#2 Morton's Castle - Coin Block #2" +donut_plains_castle_vine_block_1 = "#2 Morton's Castle - Vine Block #1" +donut_plains_castle_invis_life_block_1 = "#2 Morton's Castle - Invisible 1-Up Mushroom Block #1" +donut_plains_castle_coin_block_3 = "#2 Morton's Castle - Coin Block #3" +donut_plains_castle_coin_block_4 = "#2 Morton's Castle - Coin Block #4" +donut_plains_castle_coin_block_5 = "#2 Morton's Castle - Coin Block #5" +donut_plains_castle_green_block_1 = "#2 Morton's Castle - Green Switch Palace Block #1" +donut_plains_2_coin_block_1 = "Donut Plains 2 - Coin Block #1" +donut_plains_2_coin_block_2 = "Donut Plains 2 - Coin Block #2" +donut_plains_2_coin_block_3 = "Donut Plains 2 - Coin Block #3" +donut_plains_2_yellow_block_1 = "Donut Plains 2 - Yellow Switch Palace Block #1" +donut_plains_2_powerup_block_1 = "Donut Plains 2 - Powerup Block #1" +donut_plains_2_multi_coin_block_1 = "Donut Plains 2 - Multi Coin Block #1" +donut_plains_2_flying_block_1 = "Donut Plains 2 - Flying Question Block #1" +donut_plains_2_green_block_1 = "Donut Plains 2 - Green Switch Palace Block #1" +donut_plains_2_yellow_block_2 = "Donut Plains 2 - Yellow Switch Palace Block #2" +donut_plains_2_vine_block_1 = "Donut Plains 2 - Vine Block #1" +donut_secret_1_coin_block_1 = "Donut Secret 1 - Coin Block #1" +donut_secret_1_coin_block_2 = "Donut Secret 1 - Coin Block #2" +donut_secret_1_powerup_block_1 = "Donut Secret 1 - Powerup Block #1" +donut_secret_1_coin_block_3 = "Donut Secret 1 - Coin Block #3" +donut_secret_1_powerup_block_2 = "Donut Secret 1 - Powerup Block #2" +donut_secret_1_powerup_block_3 = "Donut Secret 1 - Powerup Block #3" +donut_secret_1_life_block_1 = "Donut Secret 1 - 1-Up Mushroom Block #1" +donut_secret_1_powerup_block_4 = "Donut Secret 1 - Powerup Block #4" +donut_secret_1_powerup_block_5 = "Donut Secret 1 - Powerup Block #5" +donut_secret_1_key_block_1 = "Donut Secret 1 - Key Block #1" +vanilla_fortress_powerup_block_1 = "Vanilla Fortress - Powerup Block #1" +vanilla_fortress_powerup_block_2 = "Vanilla Fortress - Powerup Block #2" +vanilla_fortress_yellow_block_1 = "Vanilla Fortress - Yellow Switch Palace Block #1" +butter_bridge_1_powerup_block_1 = "Butter Bridge 1 - Powerup Block #1" +butter_bridge_1_multi_coin_block_1 = "Butter Bridge 1 - Multi Coin Block #1" +butter_bridge_1_multi_coin_block_2 = "Butter Bridge 1 - Multi Coin Block #2" +butter_bridge_1_multi_coin_block_3 = "Butter Bridge 1 - Multi Coin Block #3" +butter_bridge_1_life_block_1 = "Butter Bridge 1 - 1-Up Mushroom Block #1" +butter_bridge_1_bonus_block_1 = "Butter Bridge 1 - Bonus Block #1" +butter_bridge_2_powerup_block_1 = "Butter Bridge 2 - Powerup Block #1" +butter_bridge_2_green_block_1 = "Butter Bridge 2 - Green Switch Palace Block #1" +butter_bridge_2_yoshi_block_1 = "Butter Bridge 2 - Yoshi Block #1" +twin_bridges_castle_powerup_block_1 = "#4 Ludwig Castle - Powerup Block #1" +cheese_bridge_powerup_block_1 = "Cheese Bridge Area - Powerup Block #1" +cheese_bridge_powerup_block_2 = "Cheese Bridge Area - Powerup Block #2" +cheese_bridge_wings_block_1 = "Cheese Bridge Area - Wings Block #1" +cheese_bridge_powerup_block_3 = "Cheese Bridge Area - Powerup Block #3" +cookie_mountain_coin_block_1 = "Cookie Mountain - Coin Block #1" +cookie_mountain_coin_block_2 = "Cookie Mountain - Coin Block #2" +cookie_mountain_coin_block_3 = "Cookie Mountain - Coin Block #3" +cookie_mountain_coin_block_4 = "Cookie Mountain - Coin Block #4" +cookie_mountain_coin_block_5 = "Cookie Mountain - Coin Block #5" +cookie_mountain_coin_block_6 = "Cookie Mountain - Coin Block #6" +cookie_mountain_coin_block_7 = "Cookie Mountain - Coin Block #7" +cookie_mountain_coin_block_8 = "Cookie Mountain - Coin Block #8" +cookie_mountain_coin_block_9 = "Cookie Mountain - Coin Block #9" +cookie_mountain_powerup_block_1 = "Cookie Mountain - Powerup Block #1" +cookie_mountain_life_block_1 = "Cookie Mountain - 1-Up Mushroom Block #1" +cookie_mountain_vine_block_1 = "Cookie Mountain - Vine Block #1" +cookie_mountain_yoshi_block_1 = "Cookie Mountain - Yoshi Block #1" +cookie_mountain_coin_block_10 = "Cookie Mountain - Coin Block #10" +cookie_mountain_coin_block_11 = "Cookie Mountain - Coin Block #11" +cookie_mountain_powerup_block_2 = "Cookie Mountain - Powerup Block #2" +cookie_mountain_coin_block_12 = "Cookie Mountain - Coin Block #12" +cookie_mountain_coin_block_13 = "Cookie Mountain - Coin Block #13" +cookie_mountain_coin_block_14 = "Cookie Mountain - Coin Block #14" +cookie_mountain_coin_block_15 = "Cookie Mountain - Coin Block #15" +cookie_mountain_coin_block_16 = "Cookie Mountain - Coin Block #16" +cookie_mountain_coin_block_17 = "Cookie Mountain - Coin Block #17" +cookie_mountain_coin_block_18 = "Cookie Mountain - Coin Block #18" +cookie_mountain_coin_block_19 = "Cookie Mountain - Coin Block #19" +cookie_mountain_coin_block_20 = "Cookie Mountain - Coin Block #20" +cookie_mountain_coin_block_21 = "Cookie Mountain - Coin Block #21" +cookie_mountain_coin_block_22 = "Cookie Mountain - Coin Block #22" +cookie_mountain_coin_block_23 = "Cookie Mountain - Coin Block #23" +cookie_mountain_coin_block_24 = "Cookie Mountain - Coin Block #24" +cookie_mountain_coin_block_25 = "Cookie Mountain - Coin Block #25" +cookie_mountain_coin_block_26 = "Cookie Mountain - Coin Block #26" +cookie_mountain_coin_block_27 = "Cookie Mountain - Coin Block #27" +cookie_mountain_coin_block_28 = "Cookie Mountain - Coin Block #28" +cookie_mountain_coin_block_29 = "Cookie Mountain - Coin Block #29" +cookie_mountain_coin_block_30 = "Cookie Mountain - Coin Block #30" +soda_lake_powerup_block_1 = "Soda Lake - Powerup Block #1" +donut_secret_house_powerup_block_1 = "Donut Secret House - Powerup Block #1" +donut_secret_house_multi_coin_block_1 = "Donut Secret House - Multi Coin Block #1" +donut_secret_house_life_block_1 = "Donut Secret House - 1-Up Mushroom Block #1" +donut_secret_house_vine_block_1 = "Donut Secret House - Vine Block #1" +donut_secret_house_directional_coin_block_1 = "Donut Secret House - Directional Coin Block #1" +donut_plains_1_coin_block_1 = "Donut Plains 1 - Coin Block #1" +donut_plains_1_coin_block_2 = "Donut Plains 1 - Coin Block #2" +donut_plains_1_yoshi_block_1 = "Donut Plains 1 - Yoshi Block #1" +donut_plains_1_vine_block_1 = "Donut Plains 1 - Vine Block #1" +donut_plains_1_green_block_1 = "Donut Plains 1 - Green Switch Palace Block #1" +donut_plains_1_green_block_2 = "Donut Plains 1 - Green Switch Palace Block #2" +donut_plains_1_green_block_3 = "Donut Plains 1 - Green Switch Palace Block #3" +donut_plains_1_green_block_4 = "Donut Plains 1 - Green Switch Palace Block #4" +donut_plains_1_green_block_5 = "Donut Plains 1 - Green Switch Palace Block #5" +donut_plains_1_green_block_6 = "Donut Plains 1 - Green Switch Palace Block #6" +donut_plains_1_green_block_7 = "Donut Plains 1 - Green Switch Palace Block #7" +donut_plains_1_green_block_8 = "Donut Plains 1 - Green Switch Palace Block #8" +donut_plains_1_green_block_9 = "Donut Plains 1 - Green Switch Palace Block #9" +donut_plains_1_green_block_10 = "Donut Plains 1 - Green Switch Palace Block #10" +donut_plains_1_green_block_11 = "Donut Plains 1 - Green Switch Palace Block #11" +donut_plains_1_green_block_12 = "Donut Plains 1 - Green Switch Palace Block #12" +donut_plains_1_green_block_13 = "Donut Plains 1 - Green Switch Palace Block #13" +donut_plains_1_green_block_14 = "Donut Plains 1 - Green Switch Palace Block #14" +donut_plains_1_green_block_15 = "Donut Plains 1 - Green Switch Palace Block #15" +donut_plains_1_green_block_16 = "Donut Plains 1 - Green Switch Palace Block #16" +donut_plains_1_yellow_block_1 = "Donut Plains 1 - Yellow Switch Palace Block #1" +donut_plains_1_yellow_block_2 = "Donut Plains 1 - Yellow Switch Palace Block #2" +donut_plains_1_yellow_block_3 = "Donut Plains 1 - Yellow Switch Palace Block #3" +sunken_ghost_ship_powerup_block_1 = "Sunken Ghost Ship - Powerup Block #1" +sunken_ghost_ship_star_block_1 = "Sunken Ghost Ship - Star Block #1" +chocolate_castle_yellow_block_1 = "#6 Wendy's Castle - Yellow Switch Palace Block #1" +chocolate_castle_yellow_block_2 = "#6 Wendy's Castle - Yellow Switch Palace Block #2" +chocolate_castle_green_block_1 = "#6 Wendy's Castle - Green Switch Palace Block #1" +chocolate_fortress_powerup_block_1 = "Chocolate Fortress - Powerup Block #1" +chocolate_fortress_powerup_block_2 = "Chocolate Fortress - Powerup Block #2" +chocolate_fortress_coin_block_1 = "Chocolate Fortress - Coin Block #1" +chocolate_fortress_coin_block_2 = "Chocolate Fortress - Coin Block #2" +chocolate_fortress_green_block_1 = "Chocolate Fortress - Green Switch Palace Block #1" +chocolate_island_5_yoshi_block_1 = "Chocolate Island 5 - Yoshi Block #1" +chocolate_island_5_powerup_block_1 = "Chocolate Island 5 - Powerup Block #1" +chocolate_island_5_life_block_1 = "Chocolate Island 5 - 1-Up Mushroom Block #1" +chocolate_island_5_yellow_block_1 = "Chocolate Island 5 - Yellow Switch Palace Block #1" +chocolate_island_4_yellow_block_1 = "Chocolate Island 4 - Yellow Switch Palace Block #1" +chocolate_island_4_blue_pow_block_1 = "Chocolate Island 4 - Blue P-Switch Block #1" +chocolate_island_4_powerup_block_1 = "Chocolate Island 4 - Powerup Block #1" +forest_fortress_yellow_block_1 = "Forest Fortress - Yellow Switch Palace Block #1" +forest_fortress_powerup_block_1 = "Forest Fortress - Powerup Block #1" +forest_fortress_life_block_1 = "Forest Fortress - 1-Up Mushroom Block #1" +forest_fortress_life_block_2 = "Forest Fortress - 1-Up Mushroom Block #2" +forest_fortress_life_block_3 = "Forest Fortress - 1-Up Mushroom Block #3" +forest_fortress_life_block_4 = "Forest Fortress - 1-Up Mushroom Block #4" +forest_fortress_life_block_5 = "Forest Fortress - 1-Up Mushroom Block #5" +forest_fortress_life_block_6 = "Forest Fortress - 1-Up Mushroom Block #6" +forest_fortress_life_block_7 = "Forest Fortress - 1-Up Mushroom Block #7" +forest_fortress_life_block_8 = "Forest Fortress - 1-Up Mushroom Block #8" +forest_fortress_life_block_9 = "Forest Fortress - 1-Up Mushroom Block #9" +forest_castle_green_block_1 = "#5 Roy's Castle - Green Switch Palace Block #1" +chocolate_ghost_house_powerup_block_1 = "Choco Ghost House - Powerup Block #1" +chocolate_ghost_house_powerup_block_2 = "Choco Ghost House - Powerup Block #2" +chocolate_ghost_house_life_block_1 = "Choco Ghost House - 1-Up Mushroom Block #1" +chocolate_island_1_flying_block_1 = "Chocolate Island 1 - Flying Question Block #1" +chocolate_island_1_flying_block_2 = "Chocolate Island 1 - Flying Question Block #2" +chocolate_island_1_yoshi_block_1 = "Chocolate Island 1 - Yoshi Block #1" +chocolate_island_1_green_block_1 = "Chocolate Island 1 - Green|Yellow Switch Palace Block #1" +chocolate_island_1_life_block_1 = "Chocolate Island 1 - 1-Up Mushroom Block #1" +chocolate_island_3_powerup_block_1 = "Chocolate Island 3 - Powerup Block #1" +chocolate_island_3_powerup_block_2 = "Chocolate Island 3 - Powerup Block #2" +chocolate_island_3_powerup_block_3 = "Chocolate Island 3 - Powerup Block #3" +chocolate_island_3_green_block_1 = "Chocolate Island 3 - Green Switch Palace Block #1" +chocolate_island_3_bonus_block_1 = "Chocolate Island 3 - Bonus Block #1" +chocolate_island_3_vine_block_1 = "Chocolate Island 3 - Vine Block #1" +chocolate_island_3_life_block_1 = "Chocolate Island 3 - 1-Up Mushroom Block #1" +chocolate_island_3_life_block_2 = "Chocolate Island 3 - 1-Up Mushroom Block #2" +chocolate_island_3_life_block_3 = "Chocolate Island 3 - 1-Up Mushroom Block #3" +chocolate_island_2_multi_coin_block_1 = "Chocolate Island 2 - Multi Coin Block #1" +chocolate_island_2_invis_coin_block_1 = "Chocolate Island 2 - Invisible Coin Block #1" +chocolate_island_2_yoshi_block_1 = "Chocolate Island 2 - Yoshi Block #1" +chocolate_island_2_coin_block_1 = "Chocolate Island 2 - Coin Block #1" +chocolate_island_2_coin_block_2 = "Chocolate Island 2 - Coin Block #2" +chocolate_island_2_multi_coin_block_2 = "Chocolate Island 2 - Multi Coin Block #2" +chocolate_island_2_powerup_block_1 = "Chocolate Island 2 - Powerup Block #1" +chocolate_island_2_blue_pow_block_1 = "Chocolate Island 2 - Blue P-Switch Block #1" +chocolate_island_2_yellow_block_1 = "Chocolate Island 2 - Yellow Switch Palace Block #1" +chocolate_island_2_yellow_block_2 = "Chocolate Island 2 - Yellow Switch Palace Block #2" +chocolate_island_2_green_block_1 = "Chocolate Island 2 - Green Switch Palace Block #1" +chocolate_island_2_green_block_2 = "Chocolate Island 2 - Green Switch Palace Block #2" +chocolate_island_2_green_block_3 = "Chocolate Island 2 - Green Switch Palace Block #3" +chocolate_island_2_green_block_4 = "Chocolate Island 2 - Green Switch Palace Block #4" +chocolate_island_2_green_block_5 = "Chocolate Island 2 - Green Switch Palace Block #5" +chocolate_island_2_green_block_6 = "Chocolate Island 2 - Green Switch Palace Block #6" +yoshis_island_castle_coin_block_1 = "#1 Iggy's Castle - Coin Block #1" +yoshis_island_castle_coin_block_2 = "#1 Iggy's Castle - Coin Block #2" +yoshis_island_castle_powerup_block_1 = "#1 Iggy's Castle - Powerup Block #1" +yoshis_island_castle_coin_block_3 = "#1 Iggy's Castle - Coin Block #3" +yoshis_island_castle_coin_block_4 = "#1 Iggy's Castle - Coin Block #4" +yoshis_island_castle_flying_block_1 = "#1 Iggy's Castle - Flying Question Block #1" +yoshis_island_4_yellow_block_1 = "Yoshi's Island 4 - Yellow Switch Palace Block #1" +yoshis_island_4_powerup_block_1 = "Yoshi's Island 4 - Powerup Block #1" +yoshis_island_4_multi_coin_block_1 = "Yoshi's Island 4 - Multi Coin Block #1" +yoshis_island_4_star_block_1 = "Yoshi's Island 4 - Star Block #1" +yoshis_island_3_yellow_block_1 = "Yoshi's Island 3 - Yellow Switch Palace Block #1" +yoshis_island_3_yellow_block_2 = "Yoshi's Island 3 - Yellow Switch Palace Block #2" +yoshis_island_3_yellow_block_3 = "Yoshi's Island 3 - Yellow Switch Palace Block #3" +yoshis_island_3_yellow_block_4 = "Yoshi's Island 3 - Yellow Switch Palace Block #4" +yoshis_island_3_yellow_block_5 = "Yoshi's Island 3 - Yellow Switch Palace Block #5" +yoshis_island_3_yellow_block_6 = "Yoshi's Island 3 - Yellow Switch Palace Block #6" +yoshis_island_3_yellow_block_7 = "Yoshi's Island 3 - Yellow Switch Palace Block #7" +yoshis_island_3_yellow_block_8 = "Yoshi's Island 3 - Yellow Switch Palace Block #8" +yoshis_island_3_yellow_block_9 = "Yoshi's Island 3 - Yellow Switch Palace Block #9" +yoshis_island_3_coin_block_1 = "Yoshi's Island 3 - Coin Block #1" +yoshis_island_3_yoshi_block_1 = "Yoshi's Island 3 - Yoshi Block #1" +yoshis_island_3_coin_block_2 = "Yoshi's Island 3 - Coin Block #2" +yoshis_island_3_powerup_block_1 = "Yoshi's Island 3 - Powerup Block #1" +yoshis_island_3_yellow_block_10 = "Yoshi's Island 3 - Yellow Switch Palace Block #10" +yoshis_island_3_yellow_block_11 = "Yoshi's Island 3 - Yellow Switch Palace Block #11" +yoshis_island_3_yellow_block_12 = "Yoshi's Island 3 - Yellow Switch Palace Block #12" +yoshis_island_3_bonus_block_1 = "Yoshi's Island 3 - Bonus Block #1" +yoshis_island_1_flying_block_1 = "Yoshi's Island 1 - Flying Question Block #1" +yoshis_island_1_yellow_block_1 = "Yoshi's Island 1 - Yellow Switch Palace Block #1" +yoshis_island_1_life_block_1 = "Yoshi's Island 1 - 1-Up Mushroom Block #1" +yoshis_island_1_powerup_block_1 = "Yoshi's Island 1 - Powerup Block #1" +yoshis_island_2_flying_block_1 = "Yoshi's Island 2 - Flying Question Block #1" +yoshis_island_2_flying_block_2 = "Yoshi's Island 2 - Flying Question Block #2" +yoshis_island_2_flying_block_3 = "Yoshi's Island 2 - Flying Question Block #3" +yoshis_island_2_flying_block_4 = "Yoshi's Island 2 - Flying Question Block #4" +yoshis_island_2_flying_block_5 = "Yoshi's Island 2 - Flying Question Block #5" +yoshis_island_2_flying_block_6 = "Yoshi's Island 2 - Flying Question Block #6" +yoshis_island_2_coin_block_1 = "Yoshi's Island 2 - Coin Block #1" +yoshis_island_2_yellow_block_1 = "Yoshi's Island 2 - Yellow Switch Palace Block #1" +yoshis_island_2_coin_block_2 = "Yoshi's Island 2 - Coin Block #2" +yoshis_island_2_coin_block_3 = "Yoshi's Island 2 - Coin Block #3" +yoshis_island_2_yoshi_block_1 = "Yoshi's Island 2 - Yoshi Block #1" +yoshis_island_2_coin_block_4 = "Yoshi's Island 2 - Coin Block #4" +yoshis_island_2_yoshi_block_2 = "Yoshi's Island 2 - Yoshi Block #2" +yoshis_island_2_coin_block_5 = "Yoshi's Island 2 - Coin Block #5" +yoshis_island_2_vine_block_1 = "Yoshi's Island 2 - Vine Block #1" +yoshis_island_2_yellow_block_2 = "Yoshi's Island 2 - Yellow Switch Palace Block #2" +vanilla_ghost_house_powerup_block_1 = "Vanilla Ghost House - Powerup Block #1" +vanilla_ghost_house_vine_block_1 = "Vanilla Ghost House - Vine Block #1" +vanilla_ghost_house_powerup_block_2 = "Vanilla Ghost House - Powerup Block #2" +vanilla_ghost_house_multi_coin_block_1 = "Vanilla Ghost House - Multi Coin Block #1" +vanilla_ghost_house_blue_pow_block_1 = "Vanilla Ghost House - Blue P-Switch Block #1" +vanilla_secret_1_coin_block_1 = "Vanilla Secret 1 - Coin Block #1" +vanilla_secret_1_powerup_block_1 = "Vanilla Secret 1 - Powerup Block #1" +vanilla_secret_1_multi_coin_block_1 = "Vanilla Secret 1 - Multi Coin Block #1" +vanilla_secret_1_vine_block_1 = "Vanilla Secret 1 - Vine Block #1" +vanilla_secret_1_vine_block_2 = "Vanilla Secret 1 - Vine Block #2" +vanilla_secret_1_coin_block_2 = "Vanilla Secret 1 - Coin Block #2" +vanilla_secret_1_coin_block_3 = "Vanilla Secret 1 - Coin Block #3" +vanilla_secret_1_powerup_block_2 = "Vanilla Secret 1 - Powerup Block #2" +vanilla_dome_3_coin_block_1 = "Vanilla Dome 3 - Coin Block #1" +vanilla_dome_3_flying_block_1 = "Vanilla Dome 3 - Flying Question Block #1" +vanilla_dome_3_flying_block_2 = "Vanilla Dome 3 - Flying Question Block #2" +vanilla_dome_3_powerup_block_1 = "Vanilla Dome 3 - Powerup Block #1" +vanilla_dome_3_flying_block_3 = "Vanilla Dome 3 - Flying Question Block #3" +vanilla_dome_3_invis_coin_block_1 = "Vanilla Dome 3 - Invisible Coin Block #1" +vanilla_dome_3_powerup_block_2 = "Vanilla Dome 3 - Powerup Block #2" +vanilla_dome_3_multi_coin_block_1 = "Vanilla Dome 3 - Multi Coin Block #1" +vanilla_dome_3_powerup_block_3 = "Vanilla Dome 3 - Powerup Block #3" +vanilla_dome_3_yoshi_block_1 = "Vanilla Dome 3 - Yoshi Block #1" +vanilla_dome_3_powerup_block_4 = "Vanilla Dome 3 - Powerup Block #4" +vanilla_dome_3_pswitch_coin_block_1 = "Vanilla Dome 3 - P-Switch Coin Block #1" +vanilla_dome_3_pswitch_coin_block_2 = "Vanilla Dome 3 - P-Switch Coin Block #2" +vanilla_dome_3_pswitch_coin_block_3 = "Vanilla Dome 3 - P-Switch Coin Block #3" +vanilla_dome_3_pswitch_coin_block_4 = "Vanilla Dome 3 - P-Switch Coin Block #4" +vanilla_dome_3_pswitch_coin_block_5 = "Vanilla Dome 3 - P-Switch Coin Block #5" +vanilla_dome_3_pswitch_coin_block_6 = "Vanilla Dome 3 - P-Switch Coin Block #6" +donut_secret_2_directional_coin_block_1 = "Donut Secret 2 - Directional Coin Block #1" +donut_secret_2_vine_block_1 = "Donut Secret 2 - Vine Block #1" +donut_secret_2_star_block_1 = "Donut Secret 2 - Star Block #1" +donut_secret_2_powerup_block_1 = "Donut Secret 2 - Powerup Block #1" +donut_secret_2_star_block_2 = "Donut Secret 2 - Star Block #2" +valley_of_bowser_4_yellow_block_1 = "Valley of Bowser 4 - Yellow Switch Palace Block #1" +valley_of_bowser_4_powerup_block_1 = "Valley of Bowser 4 - Powerup Block #1" +valley_of_bowser_4_vine_block_1 = "Valley of Bowser 4 - Vine Block #1" +valley_of_bowser_4_yoshi_block_1 = "Valley of Bowser 4 - Yoshi Block #1" +valley_of_bowser_4_life_block_1 = "Valley of Bowser 4 - 1-Up Mushroom Block #1" +valley_of_bowser_4_powerup_block_2 = "Valley of Bowser 4 - Powerup Block #2" +valley_castle_yellow_block_1 = "#7 Larry's Castle - Yellow Switch Palace Block #1" +valley_castle_yellow_block_2 = "#7 Larry's Castle - Yellow Switch Palace Block #2" +valley_castle_green_block_1 = "#7 Larry's Castle - Green Switch Palace Block #1" +valley_fortress_green_block_1 = "Valley Fortress - Green Switch Palace Block #1" +valley_fortress_yellow_block_1 = "Valley Fortress - Yellow Switch Palace Block #1" +valley_of_bowser_3_powerup_block_1 = "Valley of Bowser 3 - Powerup Block #1" +valley_of_bowser_3_powerup_block_2 = "Valley of Bowser 3 - Powerup Block #2" +valley_ghost_house_pswitch_coin_block_1 = "Valley Ghost House - P-Switch Coin Block #1" +valley_ghost_house_multi_coin_block_1 = "Valley Ghost House - Multi Coin Block #1" +valley_ghost_house_powerup_block_1 = "Valley Ghost House - Powerup Block #1" +valley_ghost_house_directional_coin_block_1 = "Valley Ghost House - Directional Coin Block #1" +valley_of_bowser_2_powerup_block_1 = "Valley of Bowser 2 - Powerup Block #1" +valley_of_bowser_2_yellow_block_1 = "Valley of Bowser 2 - Yellow Switch Palace Block #1" +valley_of_bowser_2_powerup_block_2 = "Valley of Bowser 2 - Powerup Block #2" +valley_of_bowser_2_wings_block_1 = "Valley of Bowser 2 - Wings Block #1" +valley_of_bowser_1_green_block_1 = "Valley of Bowser 1 - Green Switch Palace Block #1" +valley_of_bowser_1_invis_coin_block_1 = "Valley of Bowser 1 - Invisible Coin Block #1" +valley_of_bowser_1_invis_coin_block_2 = "Valley of Bowser 1 - Invisible Coin Block #2" +valley_of_bowser_1_invis_coin_block_3 = "Valley of Bowser 1 - Invisible Coin Block #3" +valley_of_bowser_1_yellow_block_1 = "Valley of Bowser 1 - Yellow Switch Palace Block #1" +valley_of_bowser_1_yellow_block_2 = "Valley of Bowser 1 - Yellow Switch Palace Block #2" +valley_of_bowser_1_yellow_block_3 = "Valley of Bowser 1 - Yellow Switch Palace Block #3" +valley_of_bowser_1_yellow_block_4 = "Valley of Bowser 1 - Yellow Switch Palace Block #4" +valley_of_bowser_1_vine_block_1 = "Valley of Bowser 1 - Vine Block #1" +chocolate_secret_powerup_block_1 = "Chocolate Secret - Powerup Block #1" +chocolate_secret_powerup_block_2 = "Chocolate Secret - Powerup Block #2" +vanilla_dome_2_coin_block_1 = "Vanilla Dome 2 - Coin Block #1" +vanilla_dome_2_powerup_block_1 = "Vanilla Dome 2 - Powerup Block #1" +vanilla_dome_2_coin_block_2 = "Vanilla Dome 2 - Coin Block #2" +vanilla_dome_2_coin_block_3 = "Vanilla Dome 2 - Coin Block #3" +vanilla_dome_2_vine_block_1 = "Vanilla Dome 2 - Vine Block #1" +vanilla_dome_2_invis_life_block_1 = "Vanilla Dome 2 - Invisible 1-Up Mushroom Block #1" +vanilla_dome_2_coin_block_4 = "Vanilla Dome 2 - Coin Block #4" +vanilla_dome_2_coin_block_5 = "Vanilla Dome 2 - Coin Block #5" +vanilla_dome_2_powerup_block_2 = "Vanilla Dome 2 - Powerup Block #2" +vanilla_dome_2_powerup_block_3 = "Vanilla Dome 2 - Powerup Block #3" +vanilla_dome_2_powerup_block_4 = "Vanilla Dome 2 - Powerup Block #4" +vanilla_dome_2_powerup_block_5 = "Vanilla Dome 2 - Powerup Block #5" +vanilla_dome_2_multi_coin_block_1 = "Vanilla Dome 2 - Multi Coin Block #1" +vanilla_dome_2_multi_coin_block_2 = "Vanilla Dome 2 - Multi Coin Block #2" +vanilla_dome_4_powerup_block_1 = "Vanilla Dome 4 - Powerup Block #1" +vanilla_dome_4_powerup_block_2 = "Vanilla Dome 4 - Powerup Block #2" +vanilla_dome_4_coin_block_1 = "Vanilla Dome 4 - Coin Block #1" +vanilla_dome_4_coin_block_2 = "Vanilla Dome 4 - Coin Block #2" +vanilla_dome_4_coin_block_3 = "Vanilla Dome 4 - Coin Block #3" +vanilla_dome_4_life_block_1 = "Vanilla Dome 4 - 1-Up Mushroom Block #1" +vanilla_dome_4_coin_block_4 = "Vanilla Dome 4 - Coin Block #4" +vanilla_dome_4_coin_block_5 = "Vanilla Dome 4 - Coin Block #5" +vanilla_dome_4_coin_block_6 = "Vanilla Dome 4 - Coin Block #6" +vanilla_dome_4_coin_block_7 = "Vanilla Dome 4 - Coin Block #7" +vanilla_dome_4_coin_block_8 = "Vanilla Dome 4 - Coin Block #8" +vanilla_dome_1_flying_block_1 = "Vanilla Dome 1 - Flying Question Block #1" +vanilla_dome_1_powerup_block_1 = "Vanilla Dome 1 - Powerup Block #1" +vanilla_dome_1_powerup_block_2 = "Vanilla Dome 1 - Powerup Block #2" +vanilla_dome_1_coin_block_1 = "Vanilla Dome 1 - Coin Block #1" +vanilla_dome_1_life_block_1 = "Vanilla Dome 1 - 1-Up Mushroom Block #1" +vanilla_dome_1_powerup_block_3 = "Vanilla Dome 1 - Powerup Block #3" +vanilla_dome_1_vine_block_1 = "Vanilla Dome 1 - Vine Block #1" +vanilla_dome_1_star_block_1 = "Vanilla Dome 1 - Star Block #1" +vanilla_dome_1_powerup_block_4 = "Vanilla Dome 1 - Powerup Block #4" +vanilla_dome_1_coin_block_2 = "Vanilla Dome 1 - Coin Block #2" +vanilla_dome_castle_life_block_1 = "#3 Lemmy's Castle - 1-Up Mushroom Block #1" +vanilla_dome_castle_life_block_2 = "#3 Lemmy's Castle - 1-Up Mushroom Block #2" +vanilla_dome_castle_powerup_block_1 = "#3 Lemmy's Castle - Powerup Block #1" +vanilla_dome_castle_life_block_3 = "#3 Lemmy's Castle - 1-Up Mushroom Block #3" +vanilla_dome_castle_green_block_1 = "#3 Lemmy's Castle - Green Switch Palace Block #1" +forest_ghost_house_coin_block_1 = "Forest Ghost House - Coin Block #1" +forest_ghost_house_powerup_block_1 = "Forest Ghost House - Powerup Block #1" +forest_ghost_house_flying_block_1 = "Forest Ghost House - Flying Question Block #1" +forest_ghost_house_powerup_block_2 = "Forest Ghost House - Powerup Block #2" +forest_ghost_house_life_block_1 = "Forest Ghost House - 1-Up Mushroom Block #1" +forest_of_illusion_1_powerup_block_1 = "Forest of Illusion 1 - Powerup Block #1" +forest_of_illusion_1_yoshi_block_1 = "Forest of Illusion 1 - Yoshi Block #1" +forest_of_illusion_1_powerup_block_2 = "Forest of Illusion 1 - Powerup Block #2" +forest_of_illusion_1_key_block_1 = "Forest of Illusion 1 - Key Block #1" +forest_of_illusion_1_life_block_1 = "Forest of Illusion 1 - 1-Up Mushroom Block #1" +forest_of_illusion_4_multi_coin_block_1 = "Forest of Illusion 4 - Multi Coin Block #1" +forest_of_illusion_4_coin_block_1 = "Forest of Illusion 4 - Coin Block #1" +forest_of_illusion_4_coin_block_2 = "Forest of Illusion 4 - Coin Block #2" +forest_of_illusion_4_coin_block_3 = "Forest of Illusion 4 - Coin Block #3" +forest_of_illusion_4_coin_block_4 = "Forest of Illusion 4 - Coin Block #4" +forest_of_illusion_4_powerup_block_1 = "Forest of Illusion 4 - Powerup Block #1" +forest_of_illusion_4_coin_block_5 = "Forest of Illusion 4 - Coin Block #5" +forest_of_illusion_4_coin_block_6 = "Forest of Illusion 4 - Coin Block #6" +forest_of_illusion_4_coin_block_7 = "Forest of Illusion 4 - Coin Block #7" +forest_of_illusion_4_powerup_block_2 = "Forest of Illusion 4 - Powerup Block #2" +forest_of_illusion_4_coin_block_8 = "Forest of Illusion 4 - Coin Block #8" +forest_of_illusion_4_coin_block_9 = "Forest of Illusion 4 - Coin Block #9" +forest_of_illusion_4_coin_block_10 = "Forest of Illusion 4 - Coin Block #10" +forest_of_illusion_2_green_block_1 = "Forest of Illusion 2 - Green Switch Palace Block #1" +forest_of_illusion_2_powerup_block_1 = "Forest of Illusion 2 - Powerup Block #1" +forest_of_illusion_2_invis_coin_block_1 = "Forest of Illusion 2 - Invisible Coin Block #1" +forest_of_illusion_2_invis_coin_block_2 = "Forest of Illusion 2 - Invisible Coin Block #2" +forest_of_illusion_2_invis_life_block_1 = "Forest of Illusion 2 - Invisible 1-Up Mushroom Block #1" +forest_of_illusion_2_invis_coin_block_3 = "Forest of Illusion 2 - Invisible Coin Block #3" +forest_of_illusion_2_yellow_block_1 = "Forest of Illusion 2 - Yellow Switch Palace Block #1" +forest_secret_powerup_block_1 = "Forest Secret Area - Powerup Block #1" +forest_secret_powerup_block_2 = "Forest Secret Area - Powerup Block #2" +forest_secret_life_block_1 = "Forest Secret Area - 1-Up Mushroom Block #1" +forest_of_illusion_3_yoshi_block_1 = "Forest of Illusion 3 - Yoshi Block #1" +forest_of_illusion_3_coin_block_1 = "Forest of Illusion 3 - Coin Block #1" +forest_of_illusion_3_multi_coin_block_1 = "Forest of Illusion 3 - Multi Coin Block #1" +forest_of_illusion_3_coin_block_2 = "Forest of Illusion 3 - Coin Block #2" +forest_of_illusion_3_multi_coin_block_2 = "Forest of Illusion 3 - Multi Coin Block #2" +forest_of_illusion_3_coin_block_3 = "Forest of Illusion 3 - Coin Block #3" +forest_of_illusion_3_coin_block_4 = "Forest of Illusion 3 - Coin Block #4" +forest_of_illusion_3_coin_block_5 = "Forest of Illusion 3 - Coin Block #5" +forest_of_illusion_3_coin_block_6 = "Forest of Illusion 3 - Coin Block #6" +forest_of_illusion_3_coin_block_7 = "Forest of Illusion 3 - Coin Block #7" +forest_of_illusion_3_coin_block_8 = "Forest of Illusion 3 - Coin Block #8" +forest_of_illusion_3_coin_block_9 = "Forest of Illusion 3 - Coin Block #9" +forest_of_illusion_3_coin_block_10 = "Forest of Illusion 3 - Coin Block #10" +forest_of_illusion_3_coin_block_11 = "Forest of Illusion 3 - Coin Block #11" +forest_of_illusion_3_coin_block_12 = "Forest of Illusion 3 - Coin Block #12" +forest_of_illusion_3_coin_block_13 = "Forest of Illusion 3 - Coin Block #13" +forest_of_illusion_3_coin_block_14 = "Forest of Illusion 3 - Coin Block #14" +forest_of_illusion_3_coin_block_15 = "Forest of Illusion 3 - Coin Block #15" +forest_of_illusion_3_coin_block_16 = "Forest of Illusion 3 - Coin Block #16" +forest_of_illusion_3_coin_block_17 = "Forest of Illusion 3 - Coin Block #17" +forest_of_illusion_3_coin_block_18 = "Forest of Illusion 3 - Coin Block #18" +forest_of_illusion_3_coin_block_19 = "Forest of Illusion 3 - Coin Block #19" +forest_of_illusion_3_coin_block_20 = "Forest of Illusion 3 - Coin Block #20" +forest_of_illusion_3_coin_block_21 = "Forest of Illusion 3 - Coin Block #21" +forest_of_illusion_3_coin_block_22 = "Forest of Illusion 3 - Coin Block #22" +forest_of_illusion_3_coin_block_23 = "Forest of Illusion 3 - Coin Block #23" +forest_of_illusion_3_coin_block_24 = "Forest of Illusion 3 - Coin Block #24" +special_zone_8_yoshi_block_1 = "Funky - Yoshi Block #1" +special_zone_8_coin_block_1 = "Funky - Coin Block #1" +special_zone_8_coin_block_2 = "Funky - Coin Block #2" +special_zone_8_coin_block_3 = "Funky - Coin Block #3" +special_zone_8_coin_block_4 = "Funky - Coin Block #4" +special_zone_8_coin_block_5 = "Funky - Coin Block #5" +special_zone_8_blue_pow_block_1 = "Funky - Blue P-Switch Block #1" +special_zone_8_powerup_block_1 = "Funky - Powerup Block #1" +special_zone_8_star_block_1 = "Funky - Star Block #1" +special_zone_8_coin_block_6 = "Funky - Coin Block #6" +special_zone_8_coin_block_7 = "Funky - Coin Block #7" +special_zone_8_coin_block_8 = "Funky - Coin Block #8" +special_zone_8_coin_block_9 = "Funky - Coin Block #9" +special_zone_8_coin_block_10 = "Funky - Coin Block #10" +special_zone_8_coin_block_11 = "Funky - Coin Block #11" +special_zone_8_coin_block_12 = "Funky - Coin Block #12" +special_zone_8_coin_block_13 = "Funky - Coin Block #13" +special_zone_8_coin_block_14 = "Funky - Coin Block #14" +special_zone_8_coin_block_15 = "Funky - Coin Block #15" +special_zone_8_coin_block_16 = "Funky - Coin Block #16" +special_zone_8_coin_block_17 = "Funky - Coin Block #17" +special_zone_8_coin_block_18 = "Funky - Coin Block #18" +special_zone_8_multi_coin_block_1 = "Funky - Multi Coin Block #1" +special_zone_8_coin_block_19 = "Funky - Coin Block #19" +special_zone_8_coin_block_20 = "Funky - Coin Block #20" +special_zone_8_coin_block_21 = "Funky - Coin Block #21" +special_zone_8_coin_block_22 = "Funky - Coin Block #22" +special_zone_8_coin_block_23 = "Funky - Coin Block #23" +special_zone_8_powerup_block_2 = "Funky - Powerup Block #2" +special_zone_8_flying_block_1 = "Funky - Flying Question Block #1" +special_zone_7_powerup_block_1 = "Outrageous - Powerup Block #1" +special_zone_7_yoshi_block_1 = "Outrageous - Yoshi Block #1" +special_zone_7_coin_block_1 = "Outrageous - Coin Block #1" +special_zone_7_powerup_block_2 = "Outrageous - Powerup Block #2" +special_zone_7_coin_block_2 = "Outrageous - Coin Block #2" +special_zone_6_powerup_block_1 = "Mondo - Powerup Block #1" +special_zone_6_coin_block_1 = "Mondo - Coin Block #1" +special_zone_6_coin_block_2 = "Mondo - Coin Block #2" +special_zone_6_yoshi_block_1 = "Mondo - Yoshi Block #1" +special_zone_6_life_block_1 = "Mondo - 1-Up Mushroom Block #1" +special_zone_6_multi_coin_block_1 = "Mondo - Multi Coin Block #1" +special_zone_6_coin_block_3 = "Mondo - Coin Block #3" +special_zone_6_coin_block_4 = "Mondo - Coin Block #4" +special_zone_6_coin_block_5 = "Mondo - Coin Block #5" +special_zone_6_coin_block_6 = "Mondo - Coin Block #6" +special_zone_6_coin_block_7 = "Mondo - Coin Block #7" +special_zone_6_coin_block_8 = "Mondo - Coin Block #8" +special_zone_6_coin_block_9 = "Mondo - Coin Block #9" +special_zone_6_coin_block_10 = "Mondo - Coin Block #10" +special_zone_6_coin_block_11 = "Mondo - Coin Block #11" +special_zone_6_coin_block_12 = "Mondo - Coin Block #12" +special_zone_6_coin_block_13 = "Mondo - Coin Block #13" +special_zone_6_coin_block_14 = "Mondo - Coin Block #14" +special_zone_6_coin_block_15 = "Mondo - Coin Block #15" +special_zone_6_coin_block_16 = "Mondo - Coin Block #16" +special_zone_6_coin_block_17 = "Mondo - Coin Block #17" +special_zone_6_coin_block_18 = "Mondo - Coin Block #18" +special_zone_6_coin_block_19 = "Mondo - Coin Block #19" +special_zone_6_coin_block_20 = "Mondo - Coin Block #20" +special_zone_6_coin_block_21 = "Mondo - Coin Block #21" +special_zone_6_coin_block_22 = "Mondo - Coin Block #22" +special_zone_6_coin_block_23 = "Mondo - Coin Block #23" +special_zone_6_coin_block_24 = "Mondo - Coin Block #24" +special_zone_6_coin_block_25 = "Mondo - Coin Block #25" +special_zone_6_coin_block_26 = "Mondo - Coin Block #26" +special_zone_6_coin_block_27 = "Mondo - Coin Block #27" +special_zone_6_coin_block_28 = "Mondo - Coin Block #28" +special_zone_6_powerup_block_2 = "Mondo - Powerup Block #2" +special_zone_6_coin_block_29 = "Mondo - Coin Block #29" +special_zone_6_coin_block_30 = "Mondo - Coin Block #30" +special_zone_6_coin_block_31 = "Mondo - Coin Block #31" +special_zone_6_coin_block_32 = "Mondo - Coin Block #32" +special_zone_6_coin_block_33 = "Mondo - Coin Block #33" +special_zone_5_yoshi_block_1 = "Groovy - Yoshi Block #1" +special_zone_1_vine_block_1 = "Gnarly - Vine Block #1" +special_zone_1_vine_block_2 = "Gnarly - Vine Block #2" +special_zone_1_vine_block_3 = "Gnarly - Vine Block #3" +special_zone_1_vine_block_4 = "Gnarly - Vine Block #4" +special_zone_1_life_block_1 = "Gnarly - 1-Up Mushroom Block #1" +special_zone_1_vine_block_5 = "Gnarly - Vine Block #5" +special_zone_1_blue_pow_block_1 = "Gnarly - Blue P-Switch Block #1" +special_zone_1_vine_block_6 = "Gnarly - Vine Block #6" +special_zone_1_powerup_block_1 = "Gnarly - Powerup Block #1" +special_zone_1_pswitch_coin_block_1 = "Gnarly - P-Switch Coin Block #1" +special_zone_1_pswitch_coin_block_2 = "Gnarly - P-Switch Coin Block #2" +special_zone_1_pswitch_coin_block_3 = "Gnarly - P-Switch Coin Block #3" +special_zone_1_pswitch_coin_block_4 = "Gnarly - P-Switch Coin Block #4" +special_zone_1_pswitch_coin_block_5 = "Gnarly - P-Switch Coin Block #5" +special_zone_1_pswitch_coin_block_6 = "Gnarly - P-Switch Coin Block #6" +special_zone_1_pswitch_coin_block_7 = "Gnarly - P-Switch Coin Block #7" +special_zone_1_pswitch_coin_block_8 = "Gnarly - P-Switch Coin Block #8" +special_zone_1_pswitch_coin_block_9 = "Gnarly - P-Switch Coin Block #9" +special_zone_1_pswitch_coin_block_10 = "Gnarly - P-Switch Coin Block #10" +special_zone_1_pswitch_coin_block_11 = "Gnarly - P-Switch Coin Block #11" +special_zone_1_pswitch_coin_block_12 = "Gnarly - P-Switch Coin Block #12" +special_zone_1_pswitch_coin_block_13 = "Gnarly - P-Switch Coin Block #13" +special_zone_2_powerup_block_1 = "Tubular - Powerup Block #1" +special_zone_2_coin_block_1 = "Tubular - Coin Block #1" +special_zone_2_coin_block_2 = "Tubular - Coin Block #2" +special_zone_2_powerup_block_2 = "Tubular - Powerup Block #2" +special_zone_2_coin_block_3 = "Tubular - Coin Block #3" +special_zone_2_coin_block_4 = "Tubular - Coin Block #4" +special_zone_2_powerup_block_3 = "Tubular - Powerup Block #3" +special_zone_2_multi_coin_block_1 = "Tubular - Multi Coin Block #1" +special_zone_2_coin_block_5 = "Tubular - Coin Block #5" +special_zone_2_coin_block_6 = "Tubular - Coin Block #6" +special_zone_3_powerup_block_1 = "Way Cool - Powerup Block #1" +special_zone_3_yoshi_block_1 = "Way Cool - Yoshi Block #1" +special_zone_3_wings_block_1 = "Way Cool - Wings Block #1" +special_zone_4_powerup_block_1 = "Awesome - Powerup Block #1" +special_zone_4_star_block_1 = "Awesome - Star Block #1" +star_road_2_star_block_1 = "Star Road 2 - Star Block #1" +star_road_3_key_block_1 = "Star Road 3 - Key Block #1" +star_road_4_powerup_block_1 = "Star Road 4 - Powerup Block #1" +star_road_4_green_block_1 = "Star Road 4 - Green Switch Palace Block #1" +star_road_4_green_block_2 = "Star Road 4 - Green Switch Palace Block #2" +star_road_4_green_block_3 = "Star Road 4 - Green Switch Palace Block #3" +star_road_4_green_block_4 = "Star Road 4 - Green Switch Palace Block #4" +star_road_4_green_block_5 = "Star Road 4 - Green Switch Palace Block #5" +star_road_4_green_block_6 = "Star Road 4 - Green Switch Palace Block #6" +star_road_4_green_block_7 = "Star Road 4 - Green Switch Palace Block #7" +star_road_4_key_block_1 = "Star Road 4 - Key Block #1" +star_road_5_directional_coin_block_1 = "Star Road 5 - Directional Coin Block #1" +star_road_5_life_block_1 = "Star Road 5 - 1-Up Mushroom Block #1" +star_road_5_vine_block_1 = "Star Road 5 - Vine Block #1" +star_road_5_yellow_block_1 = "Star Road 5 - Yellow Switch Palace Block #1" +star_road_5_yellow_block_2 = "Star Road 5 - Yellow Switch Palace Block #2" +star_road_5_yellow_block_3 = "Star Road 5 - Yellow Switch Palace Block #3" +star_road_5_yellow_block_4 = "Star Road 5 - Yellow Switch Palace Block #4" +star_road_5_yellow_block_5 = "Star Road 5 - Yellow Switch Palace Block #5" +star_road_5_yellow_block_6 = "Star Road 5 - Yellow Switch Palace Block #6" +star_road_5_yellow_block_7 = "Star Road 5 - Yellow Switch Palace Block #7" +star_road_5_yellow_block_8 = "Star Road 5 - Yellow Switch Palace Block #8" +star_road_5_yellow_block_9 = "Star Road 5 - Yellow Switch Palace Block #9" +star_road_5_yellow_block_10 = "Star Road 5 - Yellow Switch Palace Block #10" +star_road_5_yellow_block_11 = "Star Road 5 - Yellow Switch Palace Block #11" +star_road_5_yellow_block_12 = "Star Road 5 - Yellow Switch Palace Block #12" +star_road_5_yellow_block_13 = "Star Road 5 - Yellow Switch Palace Block #13" +star_road_5_yellow_block_14 = "Star Road 5 - Yellow Switch Palace Block #14" +star_road_5_yellow_block_15 = "Star Road 5 - Yellow Switch Palace Block #15" +star_road_5_yellow_block_16 = "Star Road 5 - Yellow Switch Palace Block #16" +star_road_5_yellow_block_17 = "Star Road 5 - Yellow Switch Palace Block #17" +star_road_5_yellow_block_18 = "Star Road 5 - Yellow Switch Palace Block #18" +star_road_5_yellow_block_19 = "Star Road 5 - Yellow Switch Palace Block #19" +star_road_5_yellow_block_20 = "Star Road 5 - Yellow Switch Palace Block #20" +star_road_5_green_block_1 = "Star Road 5 - Green Switch Palace Block #1" +star_road_5_green_block_2 = "Star Road 5 - Green Switch Palace Block #2" +star_road_5_green_block_3 = "Star Road 5 - Green Switch Palace Block #3" +star_road_5_green_block_4 = "Star Road 5 - Green Switch Palace Block #4" +star_road_5_green_block_5 = "Star Road 5 - Green Switch Palace Block #5" +star_road_5_green_block_6 = "Star Road 5 - Green Switch Palace Block #6" +star_road_5_green_block_7 = "Star Road 5 - Green Switch Palace Block #7" +star_road_5_green_block_8 = "Star Road 5 - Green Switch Palace Block #8" +star_road_5_green_block_9 = "Star Road 5 - Green Switch Palace Block #9" +star_road_5_green_block_10 = "Star Road 5 - Green Switch Palace Block #10" +star_road_5_green_block_11 = "Star Road 5 - Green Switch Palace Block #11" +star_road_5_green_block_12 = "Star Road 5 - Green Switch Palace Block #12" +star_road_5_green_block_13 = "Star Road 5 - Green Switch Palace Block #13" +star_road_5_green_block_14 = "Star Road 5 - Green Switch Palace Block #14" +star_road_5_green_block_15 = "Star Road 5 - Green Switch Palace Block #15" +star_road_5_green_block_16 = "Star Road 5 - Green Switch Palace Block #16" +star_road_5_green_block_17 = "Star Road 5 - Green Switch Palace Block #17" +star_road_5_green_block_18 = "Star Road 5 - Green Switch Palace Block #18" +star_road_5_green_block_19 = "Star Road 5 - Green Switch Palace Block #19" +star_road_5_green_block_20 = "Star Road 5 - Green Switch Palace Block #20" diff --git a/worlds/smw/Names/TextBox.py b/worlds/smw/Names/TextBox.py index cecf0886617c..2302a5f85fc9 100644 --- a/worlds/smw/Names/TextBox.py +++ b/worlds/smw/Names/TextBox.py @@ -1,5 +1,5 @@ -from BaseClasses import MultiWorld +from worlds.AutoWorld import World import math @@ -63,21 +63,23 @@ def generate_text_box(input_string): return out_bytes -def generate_goal_text(world: MultiWorld, player: int): +def generate_goal_text(world: World): out_array = bytearray() - if world.goal[player] == "yoshi_egg_hunt": - required_yoshi_eggs = max(math.floor( - world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + if world.options.goal == "yoshi_egg_hunt": + required_yoshi_eggs = world.required_egg_count + actual_yoshi_eggs = world.actual_egg_count out_array += bytearray([0x9F, 0x9F]) out_array += string_to_bytes(" You must acquire") out_array[-1] += 0x80 - out_array += string_to_bytes(f' {required_yoshi_eggs:02} Yoshi Eggs,') + out_array += string_to_bytes(f' {required_yoshi_eggs:03} of {actual_yoshi_eggs:03}') + out_array[-1] += 0x80 + out_array += string_to_bytes(f' Yoshi Eggs,') out_array[-1] += 0x80 out_array += string_to_bytes("then return here.") out_array[-1] += 0x80 - out_array += bytearray([0x9F, 0x9F, 0x9F]) + out_array += bytearray([0x9F, 0x9F]) else: - bosses_required = world.bosses_required[player].value + bosses_required = world.options.bosses_required.value out_array += bytearray([0x9F, 0x9F]) out_array += string_to_bytes(" You must defeat") out_array[-1] += 0x80 diff --git a/worlds/smw/Options.py b/worlds/smw/Options.py index 60135896c86c..545b3c931b42 100644 --- a/worlds/smw/Options.py +++ b/worlds/smw/Options.py @@ -1,12 +1,14 @@ -import typing +from dataclasses import dataclass -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList +from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions class Goal(Choice): """ Determines the goal of the seed + Bowser: Defeat Koopalings, reach Bowser's Castle and defeat Bowser + Yoshi Egg Hunt: Find a certain number of Yoshi Eggs """ display_name = "Goal" @@ -27,11 +29,15 @@ class BossesRequired(Range): class NumberOfYoshiEggs(Range): """ - How many Yoshi Eggs are in the pool for Yoshi Egg Hunt + Maximum possible number of Yoshi Eggs that will be in the item pool + + If fewer available locations exist in the pool than this number, the number of available locations will be used instead. + + Required Percentage of Yoshi Eggs will be calculated based off of that number. """ - display_name = "Total Number of Yoshi Eggs" + display_name = "Max Number of Yoshi Eggs" range_start = 1 - range_end = 80 + range_end = 255 default = 50 @@ -52,13 +58,56 @@ class DragonCoinChecks(Toggle): display_name = "Dragon Coin Checks" +class MoonChecks(Toggle): + """ + Whether collecting a 3-Up Moon in a level will grant a check + """ + display_name = "3up Moon Checks" + + +class Hidden1UpChecks(Toggle): + """ + Whether collecting a hidden 1-Up mushroom in a level will grant a check + + These checks are considered cryptic as there's no actual indicator that they're in their respective places + + Enable this option at your own risk + """ + display_name = "Hidden 1-Up Checks" + + +class BonusBlockChecks(Toggle): + """ + Whether collecting a 1-Up mushroom from a Bonus Block in a level will grant a check + """ + display_name = "Bonus Block Checks" + + +class Blocksanity(Toggle): + """ + Whether hitting a block with an item or coin inside will grant a check + + Note that some blocks are excluded due to how the option and the game works! + + Exclusion list: + * Blocks in Top Secret Area & Front Door/Bowser Castle + * Blocks that are unreachable unless you glitch your way in + """ + display_name = "Blocksanity" + + class BowserCastleDoors(Choice): """ How the doors of Bowser's Castle behave + Vanilla: Front and Back Doors behave as vanilla + Fast: Both doors behave as the Back Door + Slow: Both doors behave as the Front Door + "Front Door" rooms depend on the `bowser_castle_rooms` option + "Back Door" only requires going through the dark hallway to Bowser """ display_name = "Bowser Castle Doors" @@ -71,10 +120,15 @@ class BowserCastleDoors(Choice): class BowserCastleRooms(Choice): """ How the rooms of Bowser's Castle Front Door behave + Vanilla: You can choose which rooms to enter, as in vanilla + Random Two Room: Two random rooms are chosen + Random Five Room: Five random rooms are chosen + Gauntlet: All eight rooms must be cleared + Labyrinth: Which room leads to Bowser? """ display_name = "Bowser Castle Rooms" @@ -89,9 +143,13 @@ class BowserCastleRooms(Choice): class BossShuffle(Choice): """ How bosses are shuffled + None: Bosses are not shuffled + Simple: Four Reznors and the seven Koopalings are shuffled around + Full: Each boss location gets a fully random boss + Singularity: One or two bosses are chosen and placed at every boss location """ display_name = "Boss Shuffle" @@ -112,6 +170,7 @@ class LevelShuffle(Toggle): class ExcludeSpecialZone(Toggle): """ If active, this option will prevent any progression items from being placed in Special Zone levels. + Additionally, if Level Shuffle is active, Special Zone levels will not be shuffled away from their vanilla tiles. """ display_name = "Exclude Special Zone" @@ -119,24 +178,15 @@ class ExcludeSpecialZone(Toggle): class SwapDonutGhostHouseExits(Toggle): """ - If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House - overworld tile go: + If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House overworld tile go: + False: Normal Exit goes up, Secret Exit goes right. + True: Normal Exit goes right, Secret Exit goes up. """ display_name = "Swap Donut GH Exits" -class DisplaySentItemPopups(Choice): - """ - What messages to display in-game for items sent - """ - display_name = "Display Sent Item Popups" - option_none = 0 - option_all = 1 - default = 1 - - class DisplayReceivedItemPopups(Choice): """ What messages to display in-game for items received @@ -145,7 +195,18 @@ class DisplayReceivedItemPopups(Choice): option_none = 0 option_all = 1 option_progression = 2 - default = 2 + option_progression_minus_yoshi_eggs = 3 + default = 3 + + +class JunkFillPercentage(Range): + """ + Replace a percentage of non-required Yoshi Eggs in the item pool with random junk items (only applicable on Yoshi Egg Hunt goal) + """ + display_name = "Junk Fill Percentage" + range_start = 0 + range_end = 100 + default = 0 class TrapFillPercentage(Range): @@ -197,6 +258,20 @@ class TimerTrapWeight(BaseTrapWeight): display_name = "Timer Trap Weight" +class ReverseTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the controls to be reversed in the current level + """ + display_name = "Reverse Trap Weight" + + +class ThwimpTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes a Thwimp to spawn above the player + """ + display_name = "Thwimp Trap Weight" + + class Autosave(DefaultOnToggle): """ Whether a save prompt will appear after every level @@ -207,6 +282,7 @@ class Autosave(DefaultOnToggle): class EarlyClimb(Toggle): """ Force Climb to appear early in the seed as a local item. + This is particularly useful to prevent BK when Level Shuffle is disabled """ display_name = "Early Climb" @@ -226,9 +302,13 @@ class OverworldSpeed(Choice): class MusicShuffle(Choice): """ Music shuffle type + None: No Music is shuffled + Consistent: Each music track is consistently shuffled throughout the game + Full: Each individual level has a random music track + Singularity: The entire game uses one song for overworld and one song for levels """ display_name = "Music Shuffle" @@ -239,6 +319,25 @@ class MusicShuffle(Choice): default = 0 +class SFXShuffle(Choice): + """ + Shuffles almost every instance of sound effect playback + + Archipelago elements that play sound effects aren't randomized + + None: No SFX are shuffled + + Full: Each individual SFX call has a random SFX + + Singularity: The entire game uses one SFX for every SFX call + """ + display_name = "Sound Effect Shuffle" + option_none = 0 + option_full = 1 + option_singularity = 2 + default = 0 + + class MarioPalette(Choice): """ Mario palette color @@ -255,25 +354,38 @@ class MarioPalette(Choice): default = 0 -class ForegroundPaletteShuffle(Toggle): - """ - Whether to shuffle level foreground palettes +class LevelPaletteShuffle(Choice): """ - display_name = "Foreground Palette Shuffle" + Whether to shuffle level palettes + Off: Do not shuffle palettes -class BackgroundPaletteShuffle(Toggle): - """ - Whether to shuffle level background palettes + On Legacy: Uses only the palette sets from the original game + + On Curated: Uses custom, hand-crafted palette sets """ - display_name = "Background Palette Shuffle" + display_name = "Level Palette Shuffle" + option_off = 0 + option_on_legacy = 1 + option_on_curated = 2 + default = 0 -class OverworldPaletteShuffle(Toggle): +class OverworldPaletteShuffle(Choice): """ Whether to shuffle overworld palettes + + Off: Do not shuffle palettes + + On Legacy: Uses only the palette sets from the original game + + On Curated: Uses custom, hand-crafted palette sets """ display_name = "Overworld Palette Shuffle" + option_off = 0 + option_on_legacy = 1 + option_on_curated = 2 + default = 0 class StartingLifeCount(Range): @@ -286,34 +398,85 @@ class StartingLifeCount(Range): default = 5 - -smw_options: typing.Dict[str, type(Option)] = { - "death_link": DeathLink, - "goal": Goal, - "bosses_required": BossesRequired, - "number_of_yoshi_eggs": NumberOfYoshiEggs, - "percentage_of_yoshi_eggs": PercentageOfYoshiEggs, - "dragon_coin_checks": DragonCoinChecks, - "bowser_castle_doors": BowserCastleDoors, - "bowser_castle_rooms": BowserCastleRooms, - "level_shuffle": LevelShuffle, - "exclude_special_zone": ExcludeSpecialZone, - "boss_shuffle": BossShuffle, - "swap_donut_gh_exits": SwapDonutGhostHouseExits, - #"display_sent_item_popups": DisplaySentItemPopups, - "display_received_item_popups": DisplayReceivedItemPopups, - "trap_fill_percentage": TrapFillPercentage, - "ice_trap_weight": IceTrapWeight, - "stun_trap_weight": StunTrapWeight, - "literature_trap_weight": LiteratureTrapWeight, - "timer_trap_weight": TimerTrapWeight, - "autosave": Autosave, - "early_climb": EarlyClimb, - "overworld_speed": OverworldSpeed, - "music_shuffle": MusicShuffle, - "mario_palette": MarioPalette, - "foreground_palette_shuffle": ForegroundPaletteShuffle, - "background_palette_shuffle": BackgroundPaletteShuffle, - "overworld_palette_shuffle": OverworldPaletteShuffle, - "starting_life_count": StartingLifeCount, -} +smw_option_groups = [ + OptionGroup("Goal Options", [ + Goal, + BossesRequired, + NumberOfYoshiEggs, + PercentageOfYoshiEggs, + ]), + OptionGroup("Sanity Options", [ + DragonCoinChecks, + MoonChecks, + Hidden1UpChecks, + BonusBlockChecks, + Blocksanity, + ]), + OptionGroup("Level Shuffling", [ + LevelShuffle, + ExcludeSpecialZone, + BowserCastleDoors, + BowserCastleRooms, + BossShuffle, + SwapDonutGhostHouseExits, + ]), + OptionGroup("Junk and Traps", [ + JunkFillPercentage, + TrapFillPercentage, + IceTrapWeight, + StunTrapWeight, + LiteratureTrapWeight, + TimerTrapWeight, + ReverseTrapWeight, + ThwimpTrapWeight, + ]), + OptionGroup("Aesthetics", [ + DisplayReceivedItemPopups, + Autosave, + OverworldSpeed, + MusicShuffle, + SFXShuffle, + MarioPalette, + LevelPaletteShuffle, + OverworldPaletteShuffle, + StartingLifeCount, + ]), +] + + +@dataclass +class SMWOptions(PerGameCommonOptions): + death_link: DeathLink + goal: Goal + bosses_required: BossesRequired + max_yoshi_egg_cap: NumberOfYoshiEggs + percentage_of_yoshi_eggs: PercentageOfYoshiEggs + dragon_coin_checks: DragonCoinChecks + moon_checks: MoonChecks + hidden_1up_checks: Hidden1UpChecks + bonus_block_checks: BonusBlockChecks + blocksanity: Blocksanity + bowser_castle_doors: BowserCastleDoors + bowser_castle_rooms: BowserCastleRooms + level_shuffle: LevelShuffle + exclude_special_zone: ExcludeSpecialZone + boss_shuffle: BossShuffle + swap_donut_gh_exits: SwapDonutGhostHouseExits + display_received_item_popups: DisplayReceivedItemPopups + junk_fill_percentage: JunkFillPercentage + trap_fill_percentage: TrapFillPercentage + ice_trap_weight: IceTrapWeight + stun_trap_weight: StunTrapWeight + literature_trap_weight: LiteratureTrapWeight + timer_trap_weight: TimerTrapWeight + reverse_trap_weight: ReverseTrapWeight + thwimp_trap_weight: ThwimpTrapWeight + autosave: Autosave + early_climb: EarlyClimb + overworld_speed: OverworldSpeed + music_shuffle: MusicShuffle + sfx_shuffle: SFXShuffle + mario_palette: MarioPalette + level_palette_shuffle: LevelPaletteShuffle + overworld_palette_shuffle: OverworldPaletteShuffle + starting_life_count: StartingLifeCount diff --git a/worlds/smw/Presets.py b/worlds/smw/Presets.py new file mode 100644 index 000000000000..17a80e3efccd --- /dev/null +++ b/worlds/smw/Presets.py @@ -0,0 +1,57 @@ +from typing import Dict, Any + +all_random = { + "goal": "random", + "bosses_required": "random", + "max_yoshi_egg_cap": "random", + "percentage_of_yoshi_eggs": "random", + "dragon_coin_checks": "random", + "moon_checks": "random", + "hidden_1up_checks": "random", + "bonus_block_checks": "random", + "blocksanity": "random", + "bowser_castle_doors": "random", + "bowser_castle_rooms": "random", + "level_shuffle": "random", + "exclude_special_zone": "random", + "boss_shuffle": "random", + "swap_donut_gh_exits": "random", + "display_received_item_popups": "random", + "junk_fill_percentage": "random", + "trap_fill_percentage": "random", + "ice_trap_weight": "random", + "stun_trap_weight": "random", + "literature_trap_weight": "random", + "timer_trap_weight": "random", + "reverse_trap_weight": "random", + "thwimp_trap_weight": "random", + "autosave": "random", + "early_climb": "random", + "overworld_speed": "random", + "music_shuffle": "random", + "sfx_shuffle": "random", + "mario_palette": "random", + "level_palette_shuffle": "random", + "overworld_palette_shuffle": "random", + "starting_life_count": "random", +} + +allsanity = { + "dragon_coin_checks": True, + "moon_checks": True, + "hidden_1up_checks": True, + "bonus_block_checks": True, + "blocksanity": True, + "level_shuffle": True, + "boss_shuffle": "full", + "music_shuffle": "full", + "sfx_shuffle": "full", + "mario_palette": "random", + "level_palette_shuffle": "on_curated", + "overworld_palette_shuffle": "on_curated", +} + +smw_options_presets: Dict[str, Dict[str, Any]] = { + "All Random": all_random, + "Allsanity": allsanity, +} diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py index 885f209aa74c..249604987401 100644 --- a/worlds/smw/Regions.py +++ b/worlds/smw/Regions.py @@ -1,467 +1,470 @@ import typing -from BaseClasses import MultiWorld, Region, Entrance +from BaseClasses import CollectionState, MultiWorld, Region, Entrance from .Locations import SMWLocation from .Levels import level_info_dict from .Names import LocationName, ItemName from worlds.generic.Rules import add_rule, set_rule +from worlds.AutoWorld import World -def create_regions(world, player: int, active_locations): - menu_region = create_region(world, player, active_locations, 'Menu', None) +def create_regions(world: World, active_locations): + multiworld: MultiWorld = world.multiworld + player: int = world.player - yoshis_island_region = create_region(world, player, active_locations, LocationName.yoshis_island_region, None) + menu_region = create_region(multiworld, player, active_locations, 'Menu', None) + yoshis_island_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_region, None) - yoshis_house_tile = create_region(world, player, active_locations, LocationName.yoshis_house_tile, None) + yoshis_house_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_house_tile, None) yoshis_house_region_locations = [] - if world.goal[player] == "yoshi_egg_hunt": + if world.options.goal == "yoshi_egg_hunt": yoshis_house_region_locations.append(LocationName.yoshis_house) - yoshis_house_region = create_region(world, player, active_locations, LocationName.yoshis_house, + yoshis_house_region = create_region(multiworld, player, active_locations, LocationName.yoshis_house, yoshis_house_region_locations) - yoshis_island_1_tile = create_region(world, player, active_locations, LocationName.yoshis_island_1_tile, None) - yoshis_island_1_region = create_region(world, player, active_locations, LocationName.yoshis_island_1_region, None) - yoshis_island_1_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_1_exit_1, + yoshis_island_1_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_1_tile, None) + yoshis_island_1_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, None) + yoshis_island_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_1_exit_1, [LocationName.yoshis_island_1_exit_1]) - yoshis_island_2_tile = create_region(world, player, active_locations, LocationName.yoshis_island_2_tile, None) - yoshis_island_2_region = create_region(world, player, active_locations, LocationName.yoshis_island_2_region, None) - yoshis_island_2_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_2_exit_1, + yoshis_island_2_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_2_tile, None) + yoshis_island_2_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, None) + yoshis_island_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_2_exit_1, [LocationName.yoshis_island_2_exit_1]) - yoshis_island_3_tile = create_region(world, player, active_locations, LocationName.yoshis_island_3_tile, None) - yoshis_island_3_region = create_region(world, player, active_locations, LocationName.yoshis_island_3_region, None) - yoshis_island_3_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_3_exit_1, + yoshis_island_3_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_3_tile, None) + yoshis_island_3_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, None) + yoshis_island_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_3_exit_1, [LocationName.yoshis_island_3_exit_1]) - yoshis_island_4_tile = create_region(world, player, active_locations, LocationName.yoshis_island_4_tile, None) - yoshis_island_4_region = create_region(world, player, active_locations, LocationName.yoshis_island_4_region, None) - yoshis_island_4_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_4_exit_1, + yoshis_island_4_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_4_tile, None) + yoshis_island_4_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, None) + yoshis_island_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_4_exit_1, [LocationName.yoshis_island_4_exit_1]) - yoshis_island_castle_tile = create_region(world, player, active_locations, LocationName.yoshis_island_castle_tile, None) - yoshis_island_castle_region = create_region(world, player, active_locations, LocationName.yoshis_island_castle_region, None) - yoshis_island_castle = create_region(world, player, active_locations, LocationName.yoshis_island_castle, + yoshis_island_castle_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_tile, None) + yoshis_island_castle_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, None) + yoshis_island_castle = create_region(multiworld, player, active_locations, LocationName.yoshis_island_castle, [LocationName.yoshis_island_castle, LocationName.yoshis_island_koopaling]) - yellow_switch_palace_tile = create_region(world, player, active_locations, LocationName.yellow_switch_palace_tile, None) - yellow_switch_palace = create_region(world, player, active_locations, LocationName.yellow_switch_palace, + yellow_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.yellow_switch_palace_tile, None) + yellow_switch_palace = create_region(multiworld, player, active_locations, LocationName.yellow_switch_palace, [LocationName.yellow_switch_palace]) - donut_plains_1_tile = create_region(world, player, active_locations, LocationName.donut_plains_1_tile, None) - donut_plains_1_region = create_region(world, player, active_locations, LocationName.donut_plains_1_region, None) - donut_plains_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_1, + donut_plains_1_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_tile, None) + donut_plains_1_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, None) + donut_plains_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_exit_1, [LocationName.donut_plains_1_exit_1]) - donut_plains_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_2, + donut_plains_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_exit_2, [LocationName.donut_plains_1_exit_2]) - donut_plains_2_tile = create_region(world, player, active_locations, LocationName.donut_plains_2_tile, None) - donut_plains_2_region = create_region(world, player, active_locations, LocationName.donut_plains_2_region, None) - donut_plains_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_1, + donut_plains_2_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_tile, None) + donut_plains_2_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, None) + donut_plains_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_exit_1, [LocationName.donut_plains_2_exit_1]) - donut_plains_2_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_2, + donut_plains_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_exit_2, [LocationName.donut_plains_2_exit_2]) - donut_plains_3_tile = create_region(world, player, active_locations, LocationName.donut_plains_3_tile, None) - donut_plains_3_region = create_region(world, player, active_locations, LocationName.donut_plains_3_region, None) - donut_plains_3_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_3_exit_1, + donut_plains_3_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_3_tile, None) + donut_plains_3_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, None) + donut_plains_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_3_exit_1, [LocationName.donut_plains_3_exit_1]) - donut_plains_4_tile = create_region(world, player, active_locations, LocationName.donut_plains_4_tile, None) - donut_plains_4_region = create_region(world, player, active_locations, LocationName.donut_plains_4_region, None) - donut_plains_4_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_4_exit_1, + donut_plains_4_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_4_tile, None) + donut_plains_4_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, None) + donut_plains_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_4_exit_1, [LocationName.donut_plains_4_exit_1]) - donut_secret_1_tile = create_region(world, player, active_locations, LocationName.donut_secret_1_tile, None) - donut_secret_1_region = create_region(world, player, active_locations, LocationName.donut_secret_1_region, None) - donut_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_1, + donut_secret_1_tile = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_tile, None) + donut_secret_1_region = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, None) + donut_secret_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_exit_1, [LocationName.donut_secret_1_exit_1]) - donut_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_2, + donut_secret_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_exit_2, [LocationName.donut_secret_1_exit_2]) - donut_secret_2_tile = create_region(world, player, active_locations, LocationName.donut_secret_2_tile, None) - donut_secret_2_region = create_region(world, player, active_locations, LocationName.donut_secret_2_region, None) - donut_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_2_exit_1, + donut_secret_2_tile = create_region(multiworld, player, active_locations, LocationName.donut_secret_2_tile, None) + donut_secret_2_region = create_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, None) + donut_secret_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_secret_2_exit_1, [LocationName.donut_secret_2_exit_1]) - donut_ghost_house_tile = create_region(world, player, active_locations, LocationName.donut_ghost_house_tile, None) - donut_ghost_house_region = create_region(world, player, active_locations, LocationName.donut_ghost_house_region, None) - donut_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_1, + donut_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_tile, None) + donut_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, None) + donut_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_exit_1, [LocationName.donut_ghost_house_exit_1]) - donut_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_2, + donut_ghost_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_exit_2, [LocationName.donut_ghost_house_exit_2]) - donut_secret_house_tile = create_region(world, player, active_locations, LocationName.donut_secret_house_tile, None) - donut_secret_house_region = create_region(world, player, active_locations, LocationName.donut_secret_house_region, None) - donut_secret_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_1, + donut_secret_house_tile = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_tile, None) + donut_secret_house_region = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, None) + donut_secret_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_exit_1, [LocationName.donut_secret_house_exit_1]) - donut_secret_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_2, + donut_secret_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_exit_2, [LocationName.donut_secret_house_exit_2]) - donut_plains_castle_tile = create_region(world, player, active_locations, LocationName.donut_plains_castle_tile, None) - donut_plains_castle_region = create_region(world, player, active_locations, LocationName.donut_plains_castle_region, None) - donut_plains_castle = create_region(world, player, active_locations, LocationName.donut_plains_castle, + donut_plains_castle_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_castle_tile, None) + donut_plains_castle_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, None) + donut_plains_castle = create_region(multiworld, player, active_locations, LocationName.donut_plains_castle, [LocationName.donut_plains_castle, LocationName.donut_plains_koopaling]) - green_switch_palace_tile = create_region(world, player, active_locations, LocationName.green_switch_palace_tile, None) - green_switch_palace = create_region(world, player, active_locations, LocationName.green_switch_palace, + green_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.green_switch_palace_tile, None) + green_switch_palace = create_region(multiworld, player, active_locations, LocationName.green_switch_palace, [LocationName.green_switch_palace]) - donut_plains_top_secret_tile = create_region(world, player, active_locations, LocationName.donut_plains_top_secret_tile, None) - donut_plains_top_secret = create_region(world, player, active_locations, LocationName.donut_plains_top_secret, None) + donut_plains_top_secret_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_top_secret_tile, None) + donut_plains_top_secret = create_region(multiworld, player, active_locations, LocationName.donut_plains_top_secret, None) - vanilla_dome_1_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_1_tile, None) - vanilla_dome_1_region = create_region(world, player, active_locations, LocationName.vanilla_dome_1_region, None) - vanilla_dome_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_1, + vanilla_dome_1_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_tile, None) + vanilla_dome_1_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, None) + vanilla_dome_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_exit_1, [LocationName.vanilla_dome_1_exit_1]) - vanilla_dome_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_2, + vanilla_dome_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_exit_2, [LocationName.vanilla_dome_1_exit_2]) - vanilla_dome_2_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_2_tile, None) - vanilla_dome_2_region = create_region(world, player, active_locations, LocationName.vanilla_dome_2_region, None) - vanilla_dome_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_1, + vanilla_dome_2_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_tile, None) + vanilla_dome_2_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, None) + vanilla_dome_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_exit_1, [LocationName.vanilla_dome_2_exit_1]) - vanilla_dome_2_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_2, + vanilla_dome_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_exit_2, [LocationName.vanilla_dome_2_exit_2]) - vanilla_dome_3_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_3_tile, None) - vanilla_dome_3_region = create_region(world, player, active_locations, LocationName.vanilla_dome_3_region, None) - vanilla_dome_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_3_exit_1, + vanilla_dome_3_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_tile, None) + vanilla_dome_3_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, None) + vanilla_dome_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_exit_1, [LocationName.vanilla_dome_3_exit_1]) - vanilla_dome_4_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_4_tile, None) - vanilla_dome_4_region = create_region(world, player, active_locations, LocationName.vanilla_dome_4_region, None) - vanilla_dome_4_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_4_exit_1, + vanilla_dome_4_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_tile, None) + vanilla_dome_4_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, None) + vanilla_dome_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_exit_1, [LocationName.vanilla_dome_4_exit_1]) - vanilla_secret_1_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_1_tile, None) - vanilla_secret_1_region = create_region(world, player, active_locations, LocationName.vanilla_secret_1_region, None) - vanilla_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_1, + vanilla_secret_1_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_tile, None) + vanilla_secret_1_region = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, None) + vanilla_secret_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_exit_1, [LocationName.vanilla_secret_1_exit_1]) - vanilla_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_2, + vanilla_secret_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_exit_2, [LocationName.vanilla_secret_1_exit_2]) - vanilla_secret_2_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_2_tile, None) - vanilla_secret_2_region = create_region(world, player, active_locations, LocationName.vanilla_secret_2_region, None) - vanilla_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_2_exit_1, + vanilla_secret_2_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_tile, None) + vanilla_secret_2_region = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, None) + vanilla_secret_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_exit_1, [LocationName.vanilla_secret_2_exit_1]) - vanilla_secret_3_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_3_tile, None) - vanilla_secret_3_region = create_region(world, player, active_locations, LocationName.vanilla_secret_3_region, None) - vanilla_secret_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_3_exit_1, + vanilla_secret_3_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_tile, None) + vanilla_secret_3_region = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, None) + vanilla_secret_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_exit_1, [LocationName.vanilla_secret_3_exit_1]) - vanilla_ghost_house_tile = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_tile, None) - vanilla_ghost_house_region = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, None) - vanilla_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_exit_1, + vanilla_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_tile, None) + vanilla_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, None) + vanilla_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_exit_1, [LocationName.vanilla_ghost_house_exit_1]) - vanilla_fortress_tile = create_region(world, player, active_locations, LocationName.vanilla_fortress_tile, None) - vanilla_fortress_region = create_region(world, player, active_locations, LocationName.vanilla_fortress_region, None) - vanilla_fortress = create_region(world, player, active_locations, LocationName.vanilla_fortress, + vanilla_fortress_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_fortress_tile, None) + vanilla_fortress_region = create_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, None) + vanilla_fortress = create_region(multiworld, player, active_locations, LocationName.vanilla_fortress, [LocationName.vanilla_fortress, LocationName.vanilla_reznor]) - vanilla_dome_castle_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_tile, None) - vanilla_dome_castle_region = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_region, None) - vanilla_dome_castle = create_region(world, player, active_locations, LocationName.vanilla_dome_castle, + vanilla_dome_castle_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_tile, None) + vanilla_dome_castle_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, None) + vanilla_dome_castle = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle, [LocationName.vanilla_dome_castle, LocationName.vanilla_dome_koopaling]) - red_switch_palace_tile = create_region(world, player, active_locations, LocationName.red_switch_palace_tile, None) - red_switch_palace = create_region(world, player, active_locations, LocationName.red_switch_palace, + red_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.red_switch_palace_tile, None) + red_switch_palace = create_region(multiworld, player, active_locations, LocationName.red_switch_palace, [LocationName.red_switch_palace]) - butter_bridge_1_tile = create_region(world, player, active_locations, LocationName.butter_bridge_1_tile, None) - butter_bridge_1_region = create_region(world, player, active_locations, LocationName.butter_bridge_1_region, None) - butter_bridge_1_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_1_exit_1, + butter_bridge_1_tile = create_region(multiworld, player, active_locations, LocationName.butter_bridge_1_tile, None) + butter_bridge_1_region = create_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, None) + butter_bridge_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.butter_bridge_1_exit_1, [LocationName.butter_bridge_1_exit_1]) - butter_bridge_2_tile = create_region(world, player, active_locations, LocationName.butter_bridge_2_tile, None) - butter_bridge_2_region = create_region(world, player, active_locations, LocationName.butter_bridge_2_region, None) - butter_bridge_2_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_2_exit_1, + butter_bridge_2_tile = create_region(multiworld, player, active_locations, LocationName.butter_bridge_2_tile, None) + butter_bridge_2_region = create_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, None) + butter_bridge_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.butter_bridge_2_exit_1, [LocationName.butter_bridge_2_exit_1]) - cheese_bridge_tile = create_region(world, player, active_locations, LocationName.cheese_bridge_tile, None) - cheese_bridge_region = create_region(world, player, active_locations, LocationName.cheese_bridge_region, None) - cheese_bridge_exit_1 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_1, + cheese_bridge_tile = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_tile, None) + cheese_bridge_region = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, None) + cheese_bridge_exit_1 = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_exit_1, [LocationName.cheese_bridge_exit_1]) - cheese_bridge_exit_2 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_2, + cheese_bridge_exit_2 = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_exit_2, [LocationName.cheese_bridge_exit_2]) - cookie_mountain_tile = create_region(world, player, active_locations, LocationName.cookie_mountain_tile, None) - cookie_mountain_region = create_region(world, player, active_locations, LocationName.cookie_mountain_region, None) - cookie_mountain_exit_1 = create_region(world, player, active_locations, LocationName.cookie_mountain_exit_1, + cookie_mountain_tile = create_region(multiworld, player, active_locations, LocationName.cookie_mountain_tile, None) + cookie_mountain_region = create_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, None) + cookie_mountain_exit_1 = create_region(multiworld, player, active_locations, LocationName.cookie_mountain_exit_1, [LocationName.cookie_mountain_exit_1]) - soda_lake_tile = create_region(world, player, active_locations, LocationName.soda_lake_tile, None) - soda_lake_region = create_region(world, player, active_locations, LocationName.soda_lake_region, None) - soda_lake_exit_1 = create_region(world, player, active_locations, LocationName.soda_lake_exit_1, + soda_lake_tile = create_region(multiworld, player, active_locations, LocationName.soda_lake_tile, None) + soda_lake_region = create_region(multiworld, player, active_locations, LocationName.soda_lake_region, None) + soda_lake_exit_1 = create_region(multiworld, player, active_locations, LocationName.soda_lake_exit_1, [LocationName.soda_lake_exit_1]) - twin_bridges_castle_tile = create_region(world, player, active_locations, LocationName.twin_bridges_castle_tile, None) - twin_bridges_castle_region = create_region(world, player, active_locations, LocationName.twin_bridges_castle_region, None) - twin_bridges_castle = create_region(world, player, active_locations, LocationName.twin_bridges_castle, + twin_bridges_castle_tile = create_region(multiworld, player, active_locations, LocationName.twin_bridges_castle_tile, None) + twin_bridges_castle_region = create_region(multiworld, player, active_locations, LocationName.twin_bridges_castle_region, None) + twin_bridges_castle = create_region(multiworld, player, active_locations, LocationName.twin_bridges_castle, [LocationName.twin_bridges_castle, LocationName.twin_bridges_koopaling]) - forest_of_illusion_1_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_tile, None) - forest_of_illusion_1_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_region, None) - forest_of_illusion_1_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_1, + forest_of_illusion_1_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_tile, None) + forest_of_illusion_1_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, None) + forest_of_illusion_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_exit_1, [LocationName.forest_of_illusion_1_exit_1]) - forest_of_illusion_1_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_2, + forest_of_illusion_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_exit_2, [LocationName.forest_of_illusion_1_exit_2]) - forest_of_illusion_2_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_tile, None) - forest_of_illusion_2_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, None) - forest_of_illusion_2_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_1, + forest_of_illusion_2_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_tile, None) + forest_of_illusion_2_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, None) + forest_of_illusion_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_exit_1, [LocationName.forest_of_illusion_2_exit_1]) - forest_of_illusion_2_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_2, + forest_of_illusion_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_exit_2, [LocationName.forest_of_illusion_2_exit_2]) - forest_of_illusion_3_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_tile, None) - forest_of_illusion_3_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, None) - forest_of_illusion_3_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_1, + forest_of_illusion_3_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_tile, None) + forest_of_illusion_3_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, None) + forest_of_illusion_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_exit_1, [LocationName.forest_of_illusion_3_exit_1]) - forest_of_illusion_3_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_2, + forest_of_illusion_3_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_exit_2, [LocationName.forest_of_illusion_3_exit_2]) - forest_of_illusion_4_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_tile, None) - forest_of_illusion_4_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, None) - forest_of_illusion_4_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_1, + forest_of_illusion_4_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_tile, None) + forest_of_illusion_4_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, None) + forest_of_illusion_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_exit_1, [LocationName.forest_of_illusion_4_exit_1]) - forest_of_illusion_4_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_2, + forest_of_illusion_4_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_exit_2, [LocationName.forest_of_illusion_4_exit_2]) - forest_ghost_house_tile = create_region(world, player, active_locations, LocationName.forest_ghost_house_tile, None) - forest_ghost_house_region = create_region(world, player, active_locations, LocationName.forest_ghost_house_region, None) - forest_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_1, + forest_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_tile, None) + forest_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, None) + forest_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_exit_1, [LocationName.forest_ghost_house_exit_1]) - forest_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_2, + forest_ghost_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_exit_2, [LocationName.forest_ghost_house_exit_2]) - forest_secret_tile = create_region(world, player, active_locations, LocationName.forest_secret_tile, None) - forest_secret_region = create_region(world, player, active_locations, LocationName.forest_secret_region, None) - forest_secret_exit_1 = create_region(world, player, active_locations, LocationName.forest_secret_exit_1, + forest_secret_tile = create_region(multiworld, player, active_locations, LocationName.forest_secret_tile, None) + forest_secret_region = create_region(multiworld, player, active_locations, LocationName.forest_secret_region, None) + forest_secret_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_secret_exit_1, [LocationName.forest_secret_exit_1]) - forest_fortress_tile = create_region(world, player, active_locations, LocationName.forest_fortress_tile, None) - forest_fortress_region = create_region(world, player, active_locations, LocationName.forest_fortress_region, None) - forest_fortress = create_region(world, player, active_locations, LocationName.forest_fortress, + forest_fortress_tile = create_region(multiworld, player, active_locations, LocationName.forest_fortress_tile, None) + forest_fortress_region = create_region(multiworld, player, active_locations, LocationName.forest_fortress_region, None) + forest_fortress = create_region(multiworld, player, active_locations, LocationName.forest_fortress, [LocationName.forest_fortress, LocationName.forest_reznor]) - forest_castle_tile = create_region(world, player, active_locations, LocationName.forest_castle_tile, None) - forest_castle_region = create_region(world, player, active_locations, LocationName.forest_castle_region, None) - forest_castle = create_region(world, player, active_locations, LocationName.forest_castle, + forest_castle_tile = create_region(multiworld, player, active_locations, LocationName.forest_castle_tile, None) + forest_castle_region = create_region(multiworld, player, active_locations, LocationName.forest_castle_region, None) + forest_castle = create_region(multiworld, player, active_locations, LocationName.forest_castle, [LocationName.forest_castle, LocationName.forest_koopaling]) - blue_switch_palace_tile = create_region(world, player, active_locations, LocationName.blue_switch_palace_tile, None) - blue_switch_palace = create_region(world, player, active_locations, LocationName.blue_switch_palace, + blue_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.blue_switch_palace_tile, None) + blue_switch_palace = create_region(multiworld, player, active_locations, LocationName.blue_switch_palace, [LocationName.blue_switch_palace]) - chocolate_island_1_tile = create_region(world, player, active_locations, LocationName.chocolate_island_1_tile, None) - chocolate_island_1_region = create_region(world, player, active_locations, LocationName.chocolate_island_1_region, None) - chocolate_island_1_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_1_exit_1, + chocolate_island_1_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_1_tile, None) + chocolate_island_1_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, None) + chocolate_island_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_1_exit_1, [LocationName.chocolate_island_1_exit_1]) - chocolate_island_2_tile = create_region(world, player, active_locations, LocationName.chocolate_island_2_tile, None) - chocolate_island_2_region = create_region(world, player, active_locations, LocationName.chocolate_island_2_region, None) - chocolate_island_2_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_1, + chocolate_island_2_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_tile, None) + chocolate_island_2_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, None) + chocolate_island_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_exit_1, [LocationName.chocolate_island_2_exit_1]) - chocolate_island_2_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_2, + chocolate_island_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_exit_2, [LocationName.chocolate_island_2_exit_2]) - chocolate_island_3_tile = create_region(world, player, active_locations, LocationName.chocolate_island_3_tile, None) - chocolate_island_3_region = create_region(world, player, active_locations, LocationName.chocolate_island_3_region, None) - chocolate_island_3_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_1, + chocolate_island_3_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_tile, None) + chocolate_island_3_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, None) + chocolate_island_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_exit_1, [LocationName.chocolate_island_3_exit_1]) - chocolate_island_3_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_2, + chocolate_island_3_exit_2 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_exit_2, [LocationName.chocolate_island_3_exit_2]) - chocolate_island_4_tile = create_region(world, player, active_locations, LocationName.chocolate_island_4_tile, None) - chocolate_island_4_region = create_region(world, player, active_locations, LocationName.chocolate_island_4_region, None) - chocolate_island_4_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_4_exit_1, + chocolate_island_4_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_4_tile, None) + chocolate_island_4_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, None) + chocolate_island_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_4_exit_1, [LocationName.chocolate_island_4_exit_1]) - chocolate_island_5_tile = create_region(world, player, active_locations, LocationName.chocolate_island_5_tile, None) - chocolate_island_5_region = create_region(world, player, active_locations, LocationName.chocolate_island_5_region, None) - chocolate_island_5_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_5_exit_1, + chocolate_island_5_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_5_tile, None) + chocolate_island_5_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, None) + chocolate_island_5_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_5_exit_1, [LocationName.chocolate_island_5_exit_1]) - chocolate_ghost_house_tile = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_tile, None) - chocolate_ghost_house_region = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_region, None) - chocolate_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_exit_1, + chocolate_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_tile, None) + chocolate_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, None) + chocolate_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_exit_1, [LocationName.chocolate_ghost_house_exit_1]) - chocolate_secret_tile = create_region(world, player, active_locations, LocationName.chocolate_secret_tile, None) - chocolate_secret_region = create_region(world, player, active_locations, LocationName.chocolate_secret_region, None) - chocolate_secret_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_secret_exit_1, + chocolate_secret_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_secret_tile, None) + chocolate_secret_region = create_region(multiworld, player, active_locations, LocationName.chocolate_secret_region, None) + chocolate_secret_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_secret_exit_1, [LocationName.chocolate_secret_exit_1]) - chocolate_fortress_tile = create_region(world, player, active_locations, LocationName.chocolate_fortress_tile, None) - chocolate_fortress_region = create_region(world, player, active_locations, LocationName.chocolate_fortress_region, None) - chocolate_fortress = create_region(world, player, active_locations, LocationName.chocolate_fortress, + chocolate_fortress_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_fortress_tile, None) + chocolate_fortress_region = create_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, None) + chocolate_fortress = create_region(multiworld, player, active_locations, LocationName.chocolate_fortress, [LocationName.chocolate_fortress, LocationName.chocolate_reznor]) - chocolate_castle_tile = create_region(world, player, active_locations, LocationName.chocolate_castle_tile, None) - chocolate_castle_region = create_region(world, player, active_locations, LocationName.chocolate_castle_region, None) - chocolate_castle = create_region(world, player, active_locations, LocationName.chocolate_castle, + chocolate_castle_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_castle_tile, None) + chocolate_castle_region = create_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, None) + chocolate_castle = create_region(multiworld, player, active_locations, LocationName.chocolate_castle, [LocationName.chocolate_castle, LocationName.chocolate_koopaling]) - sunken_ghost_ship_tile = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_tile, None) - sunken_ghost_ship_region = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, None) - sunken_ghost_ship = create_region(world, player, active_locations, LocationName.sunken_ghost_ship, + sunken_ghost_ship_tile = create_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_tile, None) + sunken_ghost_ship_region = create_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, None) + sunken_ghost_ship = create_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship, [LocationName.sunken_ghost_ship]) - valley_of_bowser_1_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_tile, None) - valley_of_bowser_1_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, None) - valley_of_bowser_1_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_exit_1, + valley_of_bowser_1_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_tile, None) + valley_of_bowser_1_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, None) + valley_of_bowser_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_exit_1, [LocationName.valley_of_bowser_1_exit_1]) - valley_of_bowser_2_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_tile, None) - valley_of_bowser_2_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, None) - valley_of_bowser_2_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_1, + valley_of_bowser_2_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_tile, None) + valley_of_bowser_2_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, None) + valley_of_bowser_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_exit_1, [LocationName.valley_of_bowser_2_exit_1]) - valley_of_bowser_2_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_2, + valley_of_bowser_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_exit_2, [LocationName.valley_of_bowser_2_exit_2]) - valley_of_bowser_3_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_tile, None) - valley_of_bowser_3_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, None) - valley_of_bowser_3_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_exit_1, + valley_of_bowser_3_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_tile, None) + valley_of_bowser_3_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, None) + valley_of_bowser_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_exit_1, [LocationName.valley_of_bowser_3_exit_1]) - valley_of_bowser_4_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_tile, None) - valley_of_bowser_4_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_region, None) - valley_of_bowser_4_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_1, + valley_of_bowser_4_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_tile, None) + valley_of_bowser_4_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, None) + valley_of_bowser_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_exit_1, [LocationName.valley_of_bowser_4_exit_1]) - valley_of_bowser_4_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_2, + valley_of_bowser_4_exit_2 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_exit_2, [LocationName.valley_of_bowser_4_exit_2]) - valley_ghost_house_tile = create_region(world, player, active_locations, LocationName.valley_ghost_house_tile, None) - valley_ghost_house_region = create_region(world, player, active_locations, LocationName.valley_ghost_house_region, None) - valley_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_1, + valley_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_tile, None) + valley_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, None) + valley_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_exit_1, [LocationName.valley_ghost_house_exit_1]) - valley_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_2, + valley_ghost_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_exit_2, [LocationName.valley_ghost_house_exit_2]) - valley_fortress_tile = create_region(world, player, active_locations, LocationName.valley_fortress_tile, None) - valley_fortress_region = create_region(world, player, active_locations, LocationName.valley_fortress_region, None) - valley_fortress = create_region(world, player, active_locations, LocationName.valley_fortress, + valley_fortress_tile = create_region(multiworld, player, active_locations, LocationName.valley_fortress_tile, None) + valley_fortress_region = create_region(multiworld, player, active_locations, LocationName.valley_fortress_region, None) + valley_fortress = create_region(multiworld, player, active_locations, LocationName.valley_fortress, [LocationName.valley_fortress, LocationName.valley_reznor]) - valley_castle_tile = create_region(world, player, active_locations, LocationName.valley_castle_tile, None) - valley_castle_region = create_region(world, player, active_locations, LocationName.valley_castle_region, None) - valley_castle = create_region(world, player, active_locations, LocationName.valley_castle, + valley_castle_tile = create_region(multiworld, player, active_locations, LocationName.valley_castle_tile, None) + valley_castle_region = create_region(multiworld, player, active_locations, LocationName.valley_castle_region, None) + valley_castle = create_region(multiworld, player, active_locations, LocationName.valley_castle, [LocationName.valley_castle, LocationName.valley_koopaling]) - front_door_tile = create_region(world, player, active_locations, LocationName.front_door_tile, None) - front_door_region = create_region(world, player, active_locations, LocationName.front_door, None) - back_door_tile = create_region(world, player, active_locations, LocationName.back_door_tile, None) - back_door_region = create_region(world, player, active_locations, LocationName.back_door, None) + front_door_tile = create_region(multiworld, player, active_locations, LocationName.front_door_tile, None) + front_door_region = create_region(multiworld, player, active_locations, LocationName.front_door, None) + back_door_tile = create_region(multiworld, player, active_locations, LocationName.back_door_tile, None) + back_door_region = create_region(multiworld, player, active_locations, LocationName.back_door, None) bowser_region_locations = [] - if world.goal[player] == "bowser": + if world.options.goal == "bowser": bowser_region_locations += [LocationName.bowser] - bowser_region = create_region(world, player, active_locations, LocationName.bowser_region, bowser_region_locations) - - - donut_plains_star_road = create_region(world, player, active_locations, LocationName.donut_plains_star_road, None) - vanilla_dome_star_road = create_region(world, player, active_locations, LocationName.vanilla_dome_star_road, None) - twin_bridges_star_road = create_region(world, player, active_locations, LocationName.twin_bridges_star_road, None) - forest_star_road = create_region(world, player, active_locations, LocationName.forest_star_road, None) - valley_star_road = create_region(world, player, active_locations, LocationName.valley_star_road, None) - star_road_donut = create_region(world, player, active_locations, LocationName.star_road_donut, None) - star_road_vanilla = create_region(world, player, active_locations, LocationName.star_road_vanilla, None) - star_road_twin_bridges = create_region(world, player, active_locations, LocationName.star_road_twin_bridges, None) - star_road_forest = create_region(world, player, active_locations, LocationName.star_road_forest, None) - star_road_valley = create_region(world, player, active_locations, LocationName.star_road_valley, None) - star_road_special = create_region(world, player, active_locations, LocationName.star_road_special, None) - special_star_road = create_region(world, player, active_locations, LocationName.special_star_road, None) - - star_road_1_tile = create_region(world, player, active_locations, LocationName.star_road_1_tile, None) - star_road_1_region = create_region(world, player, active_locations, LocationName.star_road_1_region, None) - star_road_1_exit_1 = create_region(world, player, active_locations, LocationName.star_road_1_exit_1, + bowser_region = create_region(multiworld, player, active_locations, LocationName.bowser_region, bowser_region_locations) + + + donut_plains_star_road = create_region(multiworld, player, active_locations, LocationName.donut_plains_star_road, None) + vanilla_dome_star_road = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_star_road, None) + twin_bridges_star_road = create_region(multiworld, player, active_locations, LocationName.twin_bridges_star_road, None) + forest_star_road = create_region(multiworld, player, active_locations, LocationName.forest_star_road, None) + valley_star_road = create_region(multiworld, player, active_locations, LocationName.valley_star_road, None) + star_road_donut = create_region(multiworld, player, active_locations, LocationName.star_road_donut, None) + star_road_vanilla = create_region(multiworld, player, active_locations, LocationName.star_road_vanilla, None) + star_road_twin_bridges = create_region(multiworld, player, active_locations, LocationName.star_road_twin_bridges, None) + star_road_forest = create_region(multiworld, player, active_locations, LocationName.star_road_forest, None) + star_road_valley = create_region(multiworld, player, active_locations, LocationName.star_road_valley, None) + star_road_special = create_region(multiworld, player, active_locations, LocationName.star_road_special, None) + special_star_road = create_region(multiworld, player, active_locations, LocationName.special_star_road, None) + + star_road_1_tile = create_region(multiworld, player, active_locations, LocationName.star_road_1_tile, None) + star_road_1_region = create_region(multiworld, player, active_locations, LocationName.star_road_1_region, None) + star_road_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_1_exit_1, [LocationName.star_road_1_exit_1]) - star_road_1_exit_2 = create_region(world, player, active_locations, LocationName.star_road_1_exit_2, + star_road_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_1_exit_2, [LocationName.star_road_1_exit_2]) - star_road_2_tile = create_region(world, player, active_locations, LocationName.star_road_2_tile, None) - star_road_2_region = create_region(world, player, active_locations, LocationName.star_road_2_region, None) - star_road_2_exit_1 = create_region(world, player, active_locations, LocationName.star_road_2_exit_1, + star_road_2_tile = create_region(multiworld, player, active_locations, LocationName.star_road_2_tile, None) + star_road_2_region = create_region(multiworld, player, active_locations, LocationName.star_road_2_region, None) + star_road_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_2_exit_1, [LocationName.star_road_2_exit_1]) - star_road_2_exit_2 = create_region(world, player, active_locations, LocationName.star_road_2_exit_2, + star_road_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_2_exit_2, [LocationName.star_road_2_exit_2]) - star_road_3_tile = create_region(world, player, active_locations, LocationName.star_road_3_tile, None) - star_road_3_region = create_region(world, player, active_locations, LocationName.star_road_3_region, None) - star_road_3_exit_1 = create_region(world, player, active_locations, LocationName.star_road_3_exit_1, + star_road_3_tile = create_region(multiworld, player, active_locations, LocationName.star_road_3_tile, None) + star_road_3_region = create_region(multiworld, player, active_locations, LocationName.star_road_3_region, None) + star_road_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_3_exit_1, [LocationName.star_road_3_exit_1]) - star_road_3_exit_2 = create_region(world, player, active_locations, LocationName.star_road_3_exit_2, + star_road_3_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_3_exit_2, [LocationName.star_road_3_exit_2]) - star_road_4_tile = create_region(world, player, active_locations, LocationName.star_road_4_tile, None) - star_road_4_region = create_region(world, player, active_locations, LocationName.star_road_4_region, None) - star_road_4_exit_1 = create_region(world, player, active_locations, LocationName.star_road_4_exit_1, + star_road_4_tile = create_region(multiworld, player, active_locations, LocationName.star_road_4_tile, None) + star_road_4_region = create_region(multiworld, player, active_locations, LocationName.star_road_4_region, None) + star_road_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_4_exit_1, [LocationName.star_road_4_exit_1]) - star_road_4_exit_2 = create_region(world, player, active_locations, LocationName.star_road_4_exit_2, + star_road_4_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_4_exit_2, [LocationName.star_road_4_exit_2]) - star_road_5_tile = create_region(world, player, active_locations, LocationName.star_road_5_tile, None) - star_road_5_region = create_region(world, player, active_locations, LocationName.star_road_5_region, None) - star_road_5_exit_1 = create_region(world, player, active_locations, LocationName.star_road_5_exit_1, + star_road_5_tile = create_region(multiworld, player, active_locations, LocationName.star_road_5_tile, None) + star_road_5_region = create_region(multiworld, player, active_locations, LocationName.star_road_5_region, None) + star_road_5_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_5_exit_1, [LocationName.star_road_5_exit_1]) - star_road_5_exit_2 = create_region(world, player, active_locations, LocationName.star_road_5_exit_2, + star_road_5_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_5_exit_2, [LocationName.star_road_5_exit_2]) - special_zone_1_tile = create_region(world, player, active_locations, LocationName.special_zone_1_tile, None) - special_zone_1_region = create_region(world, player, active_locations, LocationName.special_zone_1_region, None) - special_zone_1_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_1_exit_1, + special_zone_1_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_1_tile, None) + special_zone_1_region = create_region(multiworld, player, active_locations, LocationName.special_zone_1_region, None) + special_zone_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_1_exit_1, [LocationName.special_zone_1_exit_1]) - special_zone_2_tile = create_region(world, player, active_locations, LocationName.special_zone_2_tile, None) - special_zone_2_region = create_region(world, player, active_locations, LocationName.special_zone_2_region, None) - special_zone_2_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_2_exit_1, + special_zone_2_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_2_tile, None) + special_zone_2_region = create_region(multiworld, player, active_locations, LocationName.special_zone_2_region, None) + special_zone_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_2_exit_1, [LocationName.special_zone_2_exit_1]) - special_zone_3_tile = create_region(world, player, active_locations, LocationName.special_zone_3_tile, None) - special_zone_3_region = create_region(world, player, active_locations, LocationName.special_zone_3_region, None) - special_zone_3_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_3_exit_1, + special_zone_3_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_3_tile, None) + special_zone_3_region = create_region(multiworld, player, active_locations, LocationName.special_zone_3_region, None) + special_zone_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_3_exit_1, [LocationName.special_zone_3_exit_1]) - special_zone_4_tile = create_region(world, player, active_locations, LocationName.special_zone_4_tile, None) - special_zone_4_region = create_region(world, player, active_locations, LocationName.special_zone_4_region, None) - special_zone_4_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_4_exit_1, + special_zone_4_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_4_tile, None) + special_zone_4_region = create_region(multiworld, player, active_locations, LocationName.special_zone_4_region, None) + special_zone_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_4_exit_1, [LocationName.special_zone_4_exit_1]) - special_zone_5_tile = create_region(world, player, active_locations, LocationName.special_zone_5_tile, None) - special_zone_5_region = create_region(world, player, active_locations, LocationName.special_zone_5_region, None) - special_zone_5_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_5_exit_1, + special_zone_5_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_5_tile, None) + special_zone_5_region = create_region(multiworld, player, active_locations, LocationName.special_zone_5_region, None) + special_zone_5_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_5_exit_1, [LocationName.special_zone_5_exit_1]) - special_zone_6_tile = create_region(world, player, active_locations, LocationName.special_zone_6_tile, None) - special_zone_6_region = create_region(world, player, active_locations, LocationName.special_zone_6_region, None) - special_zone_6_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_6_exit_1, + special_zone_6_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_6_tile, None) + special_zone_6_region = create_region(multiworld, player, active_locations, LocationName.special_zone_6_region, None) + special_zone_6_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_6_exit_1, [LocationName.special_zone_6_exit_1]) - special_zone_7_tile = create_region(world, player, active_locations, LocationName.special_zone_7_tile, None) - special_zone_7_region = create_region(world, player, active_locations, LocationName.special_zone_7_region, None) - special_zone_7_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_7_exit_1, + special_zone_7_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_7_tile, None) + special_zone_7_region = create_region(multiworld, player, active_locations, LocationName.special_zone_7_region, None) + special_zone_7_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_7_exit_1, [LocationName.special_zone_7_exit_1]) - special_zone_8_tile = create_region(world, player, active_locations, LocationName.special_zone_8_tile, None) - special_zone_8_region = create_region(world, player, active_locations, LocationName.special_zone_8_region, None) - special_zone_8_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_8_exit_1, + special_zone_8_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_8_tile, None) + special_zone_8_region = create_region(multiworld, player, active_locations, LocationName.special_zone_8_region, None) + special_zone_8_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_8_exit_1, [LocationName.special_zone_8_exit_1]) - special_complete = create_region(world, player, active_locations, LocationName.special_complete, None) + special_complete = create_region(multiworld, player, active_locations, LocationName.special_complete, None) # Set up the regions correctly. - world.regions += [ + multiworld.regions += [ menu_region, yoshis_island_region, yoshis_house_tile, @@ -725,323 +728,1327 @@ def create_regions(world, player: int, active_locations): ] - if world.dragon_coin_checks[player]: - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_dragon, + if world.options.dragon_coin_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_dragon, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1))) - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_climb, player))) - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_dragon, lambda state: state.has(ItemName.p_switch, player)) - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_swim, player) or (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_dragon, lambda state: (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.mario_run, player)))) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_dragon) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_dragon, lambda state: ((state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_dragon) - add_location_to_region(world, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_dragon, lambda state: (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_dragon, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_run, player) and (state.has(ItemName.super_star_active, player) or state.has(ItemName.progressive_powerup, player, 1)))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_dragon, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_switch, player) and (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_dragon) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_dragon) - add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_dragon, lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_dragon, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_dragon, lambda state: state.has(ItemName.mario_climb, player)) - add_location_to_region(world, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_dragon) - add_location_to_region(world, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_dragon, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_climb, player))) - add_location_to_region(world, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_climb, player))) - add_location_to_region(world, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_carry, player))) - add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_carry, player) or state.has(ItemName.p_switch, player) or state.has(ItemName.progressive_powerup, player, 2))) - add_location_to_region(world, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_dragon, lambda state: state.has(ItemName.p_switch, player)) - add_location_to_region(world, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_dragon) - add_location_to_region(world, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_dragon) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_dragon, - lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_dragon, lambda state: (state.has(ItemName.blue_switch_palace, player) and (state.has(ItemName.p_switch, player) or state.has(ItemName.green_switch_palace, player) or (state.has(ItemName.yellow_switch_palace, player) or state.has(ItemName.red_switch_palace, player))))) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, - lambda state: (state.has(ItemName.mario_swim, player) or - (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) - add_location_to_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, + lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_dragon, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.super_star_active, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_dragon) - add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_dragon, lambda state: state.has(ItemName.yoshi_activate, player)) - add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_dragon) - add_location_to_region(world, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_dragon, lambda state: state.has(ItemName.p_switch, player)) - add_location_to_region(world, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_dragon) - add_location_to_region(world, player, active_locations, LocationName.star_road_1_region, LocationName.star_road_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_1_region, LocationName.star_road_1_dragon, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1))) - add_location_to_region(world, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_dragon, lambda state: state.has(ItemName.mario_climb, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_dragon, lambda state: state.has(ItemName.p_balloon, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_dragon, lambda state: state.has(ItemName.yoshi_activate, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_dragon, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_dragon, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_dragon, - lambda state: state.has(ItemName.progressive_powerup, player, 1)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_dragon, lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_dragon, + lambda state: ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player)) or + state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player))) + + if world.options.moon_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_moon, + lambda state: ((state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3)) or + state.has(ItemName.yoshi_activate, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_moon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_moon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_moon, + lambda state: (state.has(ItemName.mario_run, player) and + (state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_moon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_moon, + lambda state: ((state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3)) or + state.has(ItemName.yoshi_activate, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_moon) + + if world.options.hidden_1up_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_hidden_1up, + lambda state: (state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_run, player, player) and + state.has(ItemName.progressive_powerup, player, 3)))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_hidden_1up, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_hidden_1up, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_hidden_1up, + lambda state: (state.has(ItemName.mario_swim, player) or + state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_run, player, player) and + state.has(ItemName.progressive_powerup, player, 3)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_hidden_1up, + lambda state: (state.has(ItemName.mario_carry, player) or + state.has(ItemName.yoshi_activate, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_hidden_1up, + lambda state: (state.has(ItemName.progressive_powerup, player, 1))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_hidden_1up, + lambda state: state.has(ItemName.mario_climb, player)) + + if world.options.bonus_block_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_bonus_block) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_bonus_block) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_bonus_block) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_bonus_block) + + if world.options.blocksanity: + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_green_block_1, + lambda state:( ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_gray_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_directional_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_4, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_vine_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_invis_life_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_vine_block_1, + lambda state:( ((state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_3, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_balloon, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_life_block_1, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_balloon, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_4, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_balloon, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_5, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_key_block_1, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_swim, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_multi_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_multi_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_powerup_block_1, + lambda state: state.has(ItemName.mario_carry, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_yoshi_block_1, + lambda state: state.has(ItemName.mario_carry, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle_powerup_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_wings_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_7) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_8) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_9) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_life_block_1, + lambda state:( (state.has(ItemName.mario_climb, player)) or (state.has(ItemName.mario_swim, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_yoshi_block_1, + lambda state: state.has(ItemName.red_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_10) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_11) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_12) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_13) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_14) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_15) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_16) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_17) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_18) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_19) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_20) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_21) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_22) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_23) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_24) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_25) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_26) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_27) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_28) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_29) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_30) + add_location_to_region(multiworld, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_life_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_vine_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_directional_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_2, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_3, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_4, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_5, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_6, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_7, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_8, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_9, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_10, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_11, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_12, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_13, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_14, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_15, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_16, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yellow_block_3, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_star_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_yellow_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_yellow_block_2, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_green_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.green_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_life_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 3)))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.blue_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_4, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_5, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_6, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_7, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_8, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_9, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_flying_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_flying_block_2, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_yoshi_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_green_block_1, + lambda state:( ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.blue_switch_palace, player) and state.has(ItemName.p_switch, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.blue_switch_palace, player) and state.has(ItemName.p_switch, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_life_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_life_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_life_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_life_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_invis_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_multi_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_2, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_3, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_4, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_5, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_6, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_powerup_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_3, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_4, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_flying_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_star_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_3, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_4, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_5, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_6, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_7, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_8, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_9, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_10, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_11, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_12, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_3, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_4, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_5, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_6, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yoshi_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_vine_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_coin_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_coin_block_3, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_powerup_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_flying_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_flying_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_invis_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_yoshi_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_4, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_5, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_6, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_directional_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_star_block_1, + lambda state:( (state.has(ItemName.mario_climb, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_star_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_yoshi_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_life_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_climb, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_powerup_block_2, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_climb, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_fortress_region, LocationName.valley_fortress_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_fortress_region, LocationName.valley_fortress_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_pswitch_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_multi_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_directional_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_wings_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_invis_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_invis_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_invis_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_2, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_3, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_4, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_secret_region, LocationName.chocolate_secret_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_secret_region, LocationName.chocolate_secret_powerup_block_2, + lambda state: state.has(ItemName.mario_run, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_vine_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_invis_life_block_1, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_4, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_5, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_2, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_3, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_4, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_5, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_multi_coin_block_1, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_multi_coin_block_2, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_7) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_8, + lambda state: state.has(ItemName.mario_carry, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_vine_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.red_switch_palace, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_star_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_4, + lambda state:( ((state.has(ItemName.mario_run, player) and state.has(ItemName.super_star_active, player))) or ((state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 1))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_coin_block_2, + lambda state:( ((state.has(ItemName.mario_run, player) and state.has(ItemName.super_star_active, player))) or ((state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 1))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_life_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 1)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_life_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 1)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_life_block_3, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_key_block_1, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_7) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_8) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_9) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_10) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.mario_swim, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_life_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_swim, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_life_block_1, + lambda state:( (state.has(ItemName.blue_switch_palace, player)) or (state.has(ItemName.mario_carry, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_multi_coin_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_3, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_4, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_5, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_6, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_7, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_8, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_9, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_10, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_11, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_12, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_13, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_14, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_15, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_16, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_17, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_18, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_19, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_20, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_21, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_22, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_23, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_24, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_yoshi_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_star_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_6, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_7, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_8, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_9, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_10, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_11, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_12, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_13, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_14, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_15, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_16, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_17, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_18, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_multi_coin_block_1, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_19, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_20, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_21, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_22, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_23, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_powerup_block_2, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_flying_block_1, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_powerup_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_yoshi_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_coin_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_powerup_block_2, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_coin_block_2, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_yoshi_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_life_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_multi_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_4, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_5, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_6, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_7, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_8, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_9, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_10, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_11, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_12, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_13, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_14, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_15, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_16, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_17, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_18, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_19, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_20, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_21, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_22, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_23, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_24, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_25, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_26, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_27, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_28, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_29, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_30, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_31, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_32, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_33, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_yoshi_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_life_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_5, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_blue_pow_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_6, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_powerup_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_1, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_2, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_3, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_4, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_5, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_6, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_7, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_8, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_9, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_10, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_11, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_12, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_13, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_1, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_2, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_powerup_block_2, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_3, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_4, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_powerup_block_3, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_multi_coin_block_1, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_5, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_6, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_wings_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_powerup_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 2)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_star_block_1, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 2) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.progressive_powerup, player, 2) and state.has(ItemName.p_switch, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_2_region, LocationName.star_road_2_star_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_3_region, LocationName.star_road_3_key_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 2)))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_2, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_3, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_4, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_5, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_6, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_7, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_key_block_1, + lambda state:( ((state.has(ItemName.mario_carry, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_directional_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_life_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_vine_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_1, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_2, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_3, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_4, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_5, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_6, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_7, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_8, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_9, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_10, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_11, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_12, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_13, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_14, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_15, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_16, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_17, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_18, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_19, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_20, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_2, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_3, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_4, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_5, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_6, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_7, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_8, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_9, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_10, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_11, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_12, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_13, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_14, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_15, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_16, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_17, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_18, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_19, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_20, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + +def connect_regions(world: World, level_to_tile_dict): + multiworld: MultiWorld = world.multiworld + player: int = world.player - - -def connect_regions(world, player, level_to_tile_dict): names: typing.Dict[str, int] = {} - connect(world, player, names, "Menu", LocationName.yoshis_island_region) - connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_house_tile) - connect(world, player, names, LocationName.yoshis_house_tile, LocationName.donut_plains_top_secret) - connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) - connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) + connect(world, "Menu", LocationName.yoshis_island_region) + connect(world, LocationName.yoshis_island_region, LocationName.yoshis_house_tile) + connect(world, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) + connect(world, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) # Connect regions within levels using rules - connect(world, player, names, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_exit_1) - connect(world, player, names, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_exit_1) - connect(world, player, names, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_exit_1) - connect(world, player, names, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_exit_1) - connect(world, player, names, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle, + connect(world, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_exit_1) + connect(world, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_exit_1) + connect(world, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_exit_1) + connect(world, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_exit_1) + connect(world, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle, lambda state: (state.has(ItemName.mario_climb, player))) - connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_1) - connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_2, + connect(world, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_1) + connect(world, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.green_switch_palace, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_1) - connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_2, + connect(world, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_1) + connect(world, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and (state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.progressive_powerup, player, 1))))) - connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_1, + connect(world, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_2, + connect(world, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_swim, player) and state.has(ItemName.p_switch, player))) - connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_1, + connect(world, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_1, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_2, + connect(world, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_2, lambda state: (state.has(ItemName.mario_climb, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) - connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_1, + connect(world, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_2, + connect(world, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_2, lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and (state.has(ItemName.mario_climb, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - connect(world, player, names, LocationName.donut_plains_3_region, LocationName.donut_plains_3_exit_1) - connect(world, player, names, LocationName.donut_plains_4_region, LocationName.donut_plains_4_exit_1) - connect(world, player, names, LocationName.donut_secret_2_region, LocationName.donut_secret_2_exit_1) - connect(world, player, names, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle) + connect(world, LocationName.donut_plains_3_region, LocationName.donut_plains_3_exit_1) + connect(world, LocationName.donut_plains_4_region, LocationName.donut_plains_4_exit_1) + connect(world, LocationName.donut_secret_2_region, LocationName.donut_secret_2_exit_1) + connect(world, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle) - connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_1, + connect(world, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_1, lambda state: (state.has(ItemName.mario_run, player) and (state.has(ItemName.super_star_active, player) or state.has(ItemName.progressive_powerup, player, 1)))) - connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_2, + connect(world, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and ((state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_climb, player)) or (state.has(ItemName.yoshi_activate, player) and state.has(ItemName.red_switch_palace, player)) or (state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_climb, player))))) - connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_1, + connect(world, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_1, lambda state: (state.has(ItemName.mario_swim, player) and (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) - connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_2, + connect(world, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_2, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) - connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_1, + connect(world, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_1, lambda state: state.has(ItemName.mario_climb, player)) - connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_2, + connect(world, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_2, lambda state: (state.has(ItemName.mario_climb, player) and (state.has(ItemName.mario_carry, player) and state.has(ItemName.blue_switch_palace, player)))) - connect(world, player, names, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_exit_1, + connect(world, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_exit_1) - connect(world, player, names, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_exit_1) - connect(world, player, names, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_exit_1) - connect(world, player, names, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_exit_1, + connect(world, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_exit_1) + connect(world, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_exit_1) + connect(world, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_exit_1) + connect(world, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress, + connect(world, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle) + connect(world, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle) - connect(world, player, names, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_exit_1) - connect(world, player, names, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_exit_1) - connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_1, + connect(world, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_exit_1) + connect(world, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_exit_1) + connect(world, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_1, lambda state: state.has(ItemName.mario_climb, player)) - connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_2, - lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - connect(world, player, names, LocationName.soda_lake_region, LocationName.soda_lake_exit_1, + connect(world, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_2, + lambda state: (state.has(ItemName.mario_run, player) and + (state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player)))) + connect(world, LocationName.soda_lake_region, LocationName.soda_lake_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.cookie_mountain_region, LocationName.cookie_mountain_exit_1) - connect(world, player, names, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle, + connect(world, LocationName.cookie_mountain_region, LocationName.cookie_mountain_exit_1) + connect(world, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.mario_climb, player))) - connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_1) - connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_2, + connect(world, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_1) + connect(world, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_balloon, player))) - connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_1, + connect(world, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_2, + connect(world, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_2, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_carry, player))) - connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_1, + connect(world, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_1, lambda state: (state.has(ItemName.mario_carry, player) or state.has(ItemName.yoshi_activate, player))) - connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_2, + connect(world, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_2, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_1) - connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_2, + connect(world, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_1) + connect(world, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_1, + connect(world, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_2, + connect(world, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_2, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.forest_secret_region, LocationName.forest_secret_exit_1) - connect(world, player, names, LocationName.forest_fortress_region, LocationName.forest_fortress) - connect(world, player, names, LocationName.forest_castle_region, LocationName.forest_castle) + connect(world, LocationName.forest_secret_region, LocationName.forest_secret_exit_1) + connect(world, LocationName.forest_fortress_region, LocationName.forest_fortress) + connect(world, LocationName.forest_castle_region, LocationName.forest_castle) - connect(world, player, names, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_exit_1, + connect(world, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_1) - connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_2, + connect(world, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_1) + connect(world, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_1, + connect(world, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_1, lambda state: (state.has(ItemName.mario_climb, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) - connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_2, + connect(world, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_2, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - connect(world, player, names, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_exit_1) - connect(world, player, names, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_exit_1) - connect(world, player, names, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_exit_1) - connect(world, player, names, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress) - connect(world, player, names, LocationName.chocolate_secret_region, LocationName.chocolate_secret_exit_1, + connect(world, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_exit_1) + connect(world, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_exit_1) + connect(world, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_exit_1) + connect(world, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress) + connect(world, LocationName.chocolate_secret_region, LocationName.chocolate_secret_exit_1, lambda state: state.has(ItemName.mario_run, player)) - connect(world, player, names, LocationName.chocolate_castle_region, LocationName.chocolate_castle, + connect(world, LocationName.chocolate_castle_region, LocationName.chocolate_castle, lambda state: (state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship, + connect(world, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_exit_1) - connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_1) - connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_2, + connect(world, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_exit_1) + connect(world, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_1) + connect(world, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_exit_1) - connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_1, + connect(world, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_exit_1) + connect(world, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_1, lambda state: state.has(ItemName.mario_climb, player)) - connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_2, + connect(world, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_2, lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.yoshi_activate, player))) - connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_1, + connect(world, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_2, + connect(world, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_2, lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_run, player))) - connect(world, player, names, LocationName.valley_fortress_region, LocationName.valley_fortress, + connect(world, LocationName.valley_fortress_region, LocationName.valley_fortress, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.valley_castle_region, LocationName.valley_castle) - connect(world, player, names, LocationName.front_door, LocationName.bowser_region, + connect(world, LocationName.valley_castle_region, LocationName.valley_castle) + connect(world, LocationName.front_door, LocationName.bowser_region, lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.mario_swim, player) and state.has(ItemName.progressive_powerup, player, 1) and - state.has(ItemName.koopaling, player, world.bosses_required[player].value))) - connect(world, player, names, LocationName.back_door, LocationName.bowser_region, - lambda state: state.has(ItemName.koopaling, player, world.bosses_required[player].value)) + state.has(ItemName.koopaling, player, world.options.bosses_required.value))) + connect(world, LocationName.back_door, LocationName.bowser_region, + lambda state: state.has(ItemName.koopaling, player, world.options.bosses_required.value)) - connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_1, + connect(world, LocationName.star_road_1_region, LocationName.star_road_1_exit_1, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_2, + connect(world, LocationName.star_road_1_region, LocationName.star_road_1_exit_2, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_1, + connect(world, LocationName.star_road_2_region, LocationName.star_road_2_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_2, + connect(world, LocationName.star_road_2_region, LocationName.star_road_2_exit_2, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_carry, player))) - connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_1) - connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_2, + connect(world, LocationName.star_road_3_region, LocationName.star_road_3_exit_1) + connect(world, LocationName.star_road_3_region, LocationName.star_road_3_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_1) - connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_2, + connect(world, LocationName.star_road_4_region, LocationName.star_road_4_exit_1) + connect(world, LocationName.star_road_4_region, LocationName.star_road_4_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and (state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player))))) - connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_1, + connect(world, LocationName.star_road_5_region, LocationName.star_road_5_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_2, + connect(world, LocationName.star_road_5_region, LocationName.star_road_5_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and @@ -1050,26 +2057,29 @@ def connect_regions(world, player, level_to_tile_dict): state.has(ItemName.red_switch_palace, player) and state.has(ItemName.blue_switch_palace, player))) - connect(world, player, names, LocationName.special_zone_1_region, LocationName.special_zone_1_exit_1, + connect(world, LocationName.special_zone_1_region, LocationName.special_zone_1_exit_1, lambda state: (state.has(ItemName.mario_climb, player) and (state.has(ItemName.p_switch, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - connect(world, player, names, LocationName.special_zone_2_region, LocationName.special_zone_2_exit_1, + connect(world, LocationName.special_zone_2_region, LocationName.special_zone_2_exit_1, lambda state: state.has(ItemName.p_balloon, player)) - connect(world, player, names, LocationName.special_zone_3_region, LocationName.special_zone_3_exit_1, + connect(world, LocationName.special_zone_3_region, LocationName.special_zone_3_exit_1, lambda state: (state.has(ItemName.mario_climb, player) or - state.has(ItemName.p_switch, player) or - (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) - connect(world, player, names, LocationName.special_zone_4_region, LocationName.special_zone_4_exit_1, - lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.special_zone_5_region, LocationName.special_zone_5_exit_1, + state.has(ItemName.yoshi_activate, player))) + connect(world, LocationName.special_zone_4_region, LocationName.special_zone_4_exit_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 2) or + state.has(ItemName.super_star_active, player))) + connect(world, LocationName.special_zone_5_region, LocationName.special_zone_5_exit_1, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.special_zone_6_region, LocationName.special_zone_6_exit_1, + connect(world, LocationName.special_zone_6_region, LocationName.special_zone_6_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.special_zone_7_region, LocationName.special_zone_7_exit_1, - lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.special_zone_8_region, LocationName.special_zone_8_exit_1, + connect(world, LocationName.special_zone_7_region, LocationName.special_zone_7_exit_1, lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, LocationName.special_zone_8_region, LocationName.special_zone_8_exit_1, + lambda state: ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player)) or + state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player))) @@ -1085,52 +2095,52 @@ def connect_regions(world, player, level_to_tile_dict): current_tile_name = current_tile_data.levelName if ("Star Road - " not in current_tile_name) and (" - Star Road" not in current_tile_name): current_tile_name += " - Tile" - connect(world, player, names, current_tile_name, current_level_data.levelName) + connect(world, current_tile_name, current_level_data.levelName) # Connect Exit regions to next tile regions if current_tile_data.exit1Path: next_tile_id = current_tile_data.exit1Path.otherLevelID - if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + if world.options.swap_donut_gh_exits and current_tile_id == 0x04: next_tile_id = current_tile_data.exit2Path.otherLevelID next_tile_name = level_info_dict[next_tile_id].levelName if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): next_tile_name += " - Tile" current_exit_name = (current_level_data.levelName + " - Normal Exit") - connect(world, player, names, current_exit_name, next_tile_name) + connect(world, current_exit_name, next_tile_name) if current_tile_data.exit2Path: next_tile_id = current_tile_data.exit2Path.otherLevelID - if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + if world.options.swap_donut_gh_exits and current_tile_id == 0x04: next_tile_id = current_tile_data.exit1Path.otherLevelID next_tile_name = level_info_dict[next_tile_id].levelName if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): next_tile_name += " - Tile" current_exit_name = (current_level_data.levelName + " - Secret Exit") - connect(world, player, names, current_exit_name, next_tile_name) - - connect(world, player, names, LocationName.donut_plains_star_road, LocationName.star_road_donut) - connect(world, player, names, LocationName.star_road_donut, LocationName.donut_plains_star_road) - connect(world, player, names, LocationName.star_road_donut, LocationName.star_road_1_tile) - connect(world, player, names, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) - connect(world, player, names, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) - connect(world, player, names, LocationName.star_road_vanilla, LocationName.star_road_2_tile) - connect(world, player, names, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) - connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) - connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) - connect(world, player, names, LocationName.forest_star_road, LocationName.star_road_forest) - connect(world, player, names, LocationName.star_road_forest, LocationName.forest_star_road) - connect(world, player, names, LocationName.star_road_forest, LocationName.star_road_4_tile) - connect(world, player, names, LocationName.valley_star_road, LocationName.star_road_valley) - connect(world, player, names, LocationName.star_road_valley, LocationName.valley_star_road) - connect(world, player, names, LocationName.star_road_valley, LocationName.star_road_5_tile) - connect(world, player, names, LocationName.star_road_special, LocationName.special_star_road) - connect(world, player, names, LocationName.special_star_road, LocationName.star_road_special) - connect(world, player, names, LocationName.special_star_road, LocationName.special_zone_1_tile) + connect(world, current_exit_name, next_tile_name) + + connect(world, LocationName.donut_plains_star_road, LocationName.star_road_donut) + connect(world, LocationName.star_road_donut, LocationName.donut_plains_star_road) + connect(world, LocationName.star_road_donut, LocationName.star_road_1_tile) + connect(world, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) + connect(world, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) + connect(world, LocationName.star_road_vanilla, LocationName.star_road_2_tile) + connect(world, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) + connect(world, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) + connect(world, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) + connect(world, LocationName.forest_star_road, LocationName.star_road_forest) + connect(world, LocationName.star_road_forest, LocationName.forest_star_road) + connect(world, LocationName.star_road_forest, LocationName.star_road_4_tile) + connect(world, LocationName.valley_star_road, LocationName.star_road_valley) + connect(world, LocationName.star_road_valley, LocationName.valley_star_road) + connect(world, LocationName.star_road_valley, LocationName.star_road_5_tile) + connect(world, LocationName.star_road_special, LocationName.special_star_road) + connect(world, LocationName.special_star_road, LocationName.star_road_special) + connect(world, LocationName.special_star_road, LocationName.special_zone_1_tile) - connect(world, player, names, LocationName.star_road_valley, LocationName.front_door_tile) + connect(world, LocationName.star_road_valley, LocationName.front_door_tile) -def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None): - ret = Region(name, player, world) +def create_region(multiworld: MultiWorld, player: int, active_locations, name: str, locations=None): + ret = Region(name, player, multiworld) if locations: for locationName in locations: loc_id = active_locations.get(locationName, 0) @@ -1140,9 +2150,9 @@ def create_region(world: MultiWorld, player: int, active_locations, name: str, l return ret -def add_location_to_region(world: MultiWorld, player: int, active_locations, region_name: str, location_name: str, +def add_location_to_region(multiworld: MultiWorld, player: int, active_locations, region_name: str, location_name: str, rule: typing.Optional[typing.Callable] = None): - region = world.get_region(region_name, player) + region = multiworld.get_region(region_name, player) loc_id = active_locations.get(location_name, 0) if loc_id: location = SMWLocation(player, location_name, loc_id, region) @@ -1151,23 +2161,8 @@ def add_location_to_region(world: MultiWorld, player: int, active_locations, reg add_rule(location, rule) - -def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, +def connect(world: World, source: str, target: str, rule: typing.Optional[typing.Callable] = None): - source_region = world.get_region(source, player) - target_region = world.get_region(target, player) - - if target not in used_names: - used_names[target] = 1 - name = target - else: - used_names[target] += 1 - name = target + (' ' * used_names[target]) - - connection = Entrance(player, name, source_region) - - if rule: - connection.access_rule = rule - - source_region.exits.append(connection) - connection.connect(target_region) + source_region: Region = world.get_region(source) + target_region: Region = world.get_region(target) + source_region.connect(target_region, rule=rule) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py index 0f5ec7e4f066..ff3b5c31634d 100644 --- a/worlds/smw/Rom.py +++ b/worlds/smw/Rom.py @@ -1,6 +1,7 @@ import Utils +from worlds.AutoWorld import World from worlds.Files import APDeltaPatch -from .Aesthetics import generate_shuffled_header_data, generate_shuffled_ow_palettes +from .Aesthetics import generate_shuffled_header_data, generate_shuffled_ow_palettes, generate_curated_level_palette_data, generate_curated_map_palette_data, generate_shuffled_sfx from .Levels import level_info_dict, full_bowser_rooms, standard_bowser_rooms, submap_boss_rooms, ow_boss_rooms from .Names.TextBox import generate_goal_text, title_text_mapping, generate_text_box @@ -10,38 +11,48 @@ import hashlib import os import math +import pkgutil ability_rom_data = { - 0xBC0003: [[0x1F2C, 0x7]], # Run 0x80 - 0xBC0004: [[0x1F2C, 0x6]], # Carry 0x40 - 0xBC0005: [[0x1F2C, 0x2]], # Swim 0x04 - 0xBC0006: [[0x1F2C, 0x3]], # Spin Jump 0x08 - 0xBC0007: [[0x1F2C, 0x5]], # Climb 0x20 - 0xBC0008: [[0x1F2C, 0x1]], # Yoshi 0x02 - 0xBC0009: [[0x1F2C, 0x4]], # P-Switch 0x10 + 0xBC0003: [[0x1F1C, 0x7]], # Run 0x80 + 0xBC0004: [[0x1F1C, 0x6]], # Carry 0x40 + 0xBC0005: [[0x1F1C, 0x2]], # Swim 0x04 + 0xBC0006: [[0x1F1C, 0x3]], # Spin Jump 0x08 + 0xBC0007: [[0x1F1C, 0x5]], # Climb 0x20 + 0xBC0008: [[0x1F1C, 0x1]], # Yoshi 0x02 + 0xBC0009: [[0x1F1C, 0x4]], # P-Switch 0x10 #0xBC000A: [[]] 0xBC000B: [[0x1F2D, 0x3]], # P-Balloon 0x08 - 0xBC000D: [[0x1F2D, 0x4]], # Super Star 0x10 + 0xBC000D: [[0x1F2D, 0x4]] # Super Star 0x10 } +icon_rom_data = { + 0xBC0002: [0x1B00C], # Yoshi Egg + 0xBC0012: [0x1B00E], # Boss Token -item_rom_data = { - 0xBC0001: [0x18E4, 0x1], # 1-Up Mushroom - - 0xBC0002: [0x1F24, 0x1, 0x1F], # Yoshi Egg - 0xBC0012: [0x1F26, 0x1, 0x09], # Boss Token + 0xBC0017: [0x1B004], # 1 coin + 0xBC0018: [0x1B006], # 5 coins + 0xBC0019: [0x1B008], # 10 coins + 0xBC001A: [0x1B00A], # 50 coins - 0xBC000E: [0x1F28, 0x1, 0x1C], # Yellow Switch Palace - 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace - 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace - 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace + 0xBC0001: [0x1B010] # 1-Up Mushroom +} + +item_rom_data = { + 0xBC000E: [0x1F28, 0x1, 0x1C], # Yellow Switch Palace + 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace + 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace + 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace + 0xBC001B: [0x1F1E, 0x80, 0x39] # Special Zone Clear } trap_rom_data = { - 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap + 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap 0xBC0014: [0x18BD, 0x7F, 0x18], # Stun Trap - 0xBC0016: [0x0F31, 0x1], # Timer Trap + 0xBC0016: [0x0F31, 0x1], # Timer Trap + 0xBC001C: [0x18B4, 0x1, 0x44], # Reverse controls trap + 0xBC001D: [0x18B7, 0x1], # Thwimp Trap } @@ -72,7 +83,7 @@ def read_bit(self, address: int, bit_number: int) -> bool: def read_byte(self, address: int) -> int: return self.buffer[address] - def read_bytes(self, startaddress: int, length: int) -> bytes: + def read_bytes(self, startaddress: int, length: int) -> bytearray: return self.buffer[startaddress:startaddress + length] def write_byte(self, address: int, value: int): @@ -109,7 +120,7 @@ def handle_ability_code(rom): rom.write_bytes(RUN_SUB_ADDR + 0x04, bytearray([0xC8])) # INY rom.write_bytes(RUN_SUB_ADDR + 0x05, bytearray([0xA9, 0x70])) # LDA #70 rom.write_bytes(RUN_SUB_ADDR + 0x07, bytearray([0xAA])) # TAX - rom.write_bytes(RUN_SUB_ADDR + 0x08, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(RUN_SUB_ADDR + 0x08, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(RUN_SUB_ADDR + 0x0B, bytearray([0x89, 0x80])) # BIT #80 rom.write_bytes(RUN_SUB_ADDR + 0x0D, bytearray([0xF0, 0x04])) # BEQ +0x04 rom.write_bytes(RUN_SUB_ADDR + 0x0F, bytearray([0x8A])) # TXA @@ -126,7 +137,7 @@ def handle_ability_code(rom): PURPLE_BLOCK_CARRY_SUB_ADDR = 0x01BA28 rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x08, bytearray([0x28])) # PLP @@ -145,7 +156,7 @@ def handle_ability_code(rom): SPRINGBOARD_CARRY_SUB_ADDR = 0x01BA40 rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x00, bytearray([0x48])) # PHA rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x01, bytearray([0x08])) # PHP - rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x02, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x05, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x07, bytearray([0xF0, 0x08])) # BEQ +0x08 rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x09, bytearray([0xA9, 0x0B])) # LDA #0B @@ -157,7 +168,7 @@ def handle_ability_code(rom): # End Springboard Carry # Shell Carry - rom.write_bytes(0xAA66, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0xAA66, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(0xAA69, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(0xAA6B, bytearray([0xF0, 0x07])) # BEQ +0x07 rom.write_bytes(0xAA6D, bytearray([0x22, 0x60, 0xBA, 0x03])) # JSL $03BA60 @@ -180,7 +191,7 @@ def handle_ability_code(rom): YOSHI_CARRY_SUB_ADDR = 0x01BA70 rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x0A])) # BEQ +0x0A rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x08, bytearray([0xA9, 0x12])) # LDA #12 @@ -197,7 +208,7 @@ def handle_ability_code(rom): CLIMB_SUB_ADDR = 0x01BA88 rom.write_bytes(CLIMB_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(CLIMB_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(CLIMB_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(CLIMB_SUB_ADDR + 0x04, bytearray([0x89, 0x20])) # BIT #20 rom.write_bytes(CLIMB_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 rom.write_bytes(CLIMB_SUB_ADDR + 0x08, bytearray([0xA5, 0x8B])) # LDA $8B @@ -213,7 +224,7 @@ def handle_ability_code(rom): CLIMB_ROPE_SUB_ADDR = 0x01BC70 rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x04, bytearray([0x89, 0x20])) # BIT #20 rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x08, bytearray([0x28])) # PLP @@ -230,7 +241,7 @@ def handle_ability_code(rom): P_SWITCH_SUB_ADDR = 0x01BAA0 rom.write_bytes(P_SWITCH_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(P_SWITCH_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(P_SWITCH_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 rom.write_bytes(P_SWITCH_SUB_ADDR + 0x06, bytearray([0xF0, 0x04])) # BEQ +0x04 rom.write_bytes(P_SWITCH_SUB_ADDR + 0x08, bytearray([0xA9, 0xB0])) # LDA #B0 @@ -242,7 +253,7 @@ def handle_ability_code(rom): # End P-Switch # Spin Jump - rom.write_bytes(0x5645, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0x5645, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(0x5648, bytearray([0x89, 0x08])) # BIT #08 rom.write_bytes(0x564A, bytearray([0xF0, 0x12])) # BEQ +0x12 rom.write_bytes(0x564C, bytearray([0x22, 0xB8, 0xBA, 0x03])) # JSL $03BAB8 @@ -264,7 +275,7 @@ def handle_ability_code(rom): SPIN_JUMP_WATER_SUB_ADDR = 0x01BBF8 rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x08, bytearray([0x1A])) # INC @@ -281,7 +292,7 @@ def handle_ability_code(rom): SPIN_JUMP_SPRING_SUB_ADDR = 0x01BC0C rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x08, bytearray([0xA9, 0x01])) # LDA #01 @@ -297,7 +308,7 @@ def handle_ability_code(rom): SWIM_SUB_ADDR = 0x01BAC8 rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP - rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0C])) # BEQ +0x0C rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP @@ -321,7 +332,7 @@ def handle_ability_code(rom): SWIM_SUB_ADDR = 0x01BAE8 rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP - rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0A])) # BEQ +0x0A rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP @@ -344,7 +355,7 @@ def handle_ability_code(rom): YOSHI_SUB_ADDR = 0x01BB08 rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0x89, 0x02])) # BIT #02 rom.write_bytes(YOSHI_SUB_ADDR + 0x06, bytearray([0xF0, 0x06])) # BEQ +0x06 rom.write_bytes(YOSHI_SUB_ADDR + 0x08, bytearray([0x28])) # PLP @@ -366,7 +377,7 @@ def handle_ability_code(rom): YOSHI_SUB_ADDR = 0x01BB20 rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0x9C, 0x1E, 0x14])) # STZ $141E - rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(YOSHI_SUB_ADDR + 0x07, bytearray([0x89, 0x02])) # BIT #02 rom.write_bytes(YOSHI_SUB_ADDR + 0x09, bytearray([0xF0, 0x05])) # BEQ +0x05 rom.write_bytes(YOSHI_SUB_ADDR + 0x0B, bytearray([0x28])) # PLP @@ -576,18 +587,17 @@ def handle_yoshi_box(rom): def handle_bowser_damage(rom): - rom.write_bytes(0x1A509, bytearray([0x20, 0x50, 0xBC])) # JSR $03BC50 + rom.write_bytes(0x1A509, bytearray([0x5C, 0x50, 0xBC, 0x03])) # JML $03BC50 BOWSER_BALLS_SUB_ADDR = 0x01BC50 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x01, bytearray([0xAD, 0x48, 0x0F])) # LDA $F48 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x04, bytearray([0xCF, 0xA1, 0xBF, 0x03])) # CMP $03BFA1 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x08, bytearray([0x90, 0x06])) # BCC +0x06 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0A, bytearray([0x28])) # PLP - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0B, bytearray([0xEE, 0xB8, 0x14])) # INC $14B8 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0E, bytearray([0x80, 0x01])) # BRA +0x01 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x10, bytearray([0x28])) # PLP - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x11, bytearray([0x60])) # RTS + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0000, bytearray([0xAF, 0xA0, 0xBF, 0x03])) # bowser_infinite_balls: lda.l goal_setting + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0004, bytearray([0xD0, 0x0C])) # bne .nope + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0006, bytearray([0xAD, 0x48, 0x0F])) # lda $0F48 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0009, bytearray([0xCF, 0xA1, 0xBF, 0x03])) # cmp.l required_bosses_setting + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x000D, bytearray([0x90, 0x03])) # bcc .nope + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x000F, bytearray([0xEE, 0xB8, 0x14])) # inc $14B8 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0012, bytearray([0xAD, 0xB8, 0x14])) # .nope lda $14B8 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0015, bytearray([0x5C, 0x0F, 0xA5, 0x03])) # jml $03A50F return @@ -654,6 +664,7 @@ def handle_level_shuffle(rom, active_level_dict): for level_id, tile_id in active_level_dict.items(): rom.write_byte(0x37F70 + level_id, tile_id) + rom.write_byte(0x37F00 + tile_id, level_id) def handle_collected_paths(rom): @@ -673,38 +684,2155 @@ def handle_collected_paths(rom): def handle_vertical_scroll(rom): - rom.write_bytes(0x285BA, bytearray([0x22, 0x90, 0xBC, 0x03])) # JSL $03BC90 - - VERTICAL_SCROLL_SUB_ADDR = 0x01BC90 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x00, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x01, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x02, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x03, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x04, bytearray([0x08])) # PHP - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x05, bytearray([0xC9, 0x02])) # CMP #02 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x07, bytearray([0xD0, 0x02])) # BNE +0x02 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x09, bytearray([0xA9, 0x01])) # LDA #01 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0B, bytearray([0x28])) # PLP - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0C, bytearray([0x6B])) # RTL - - -def handle_music_shuffle(rom, world, player): + rom.write_bytes(0x285BA, bytearray([0x22, 0x80, 0xF4, 0x0F])) # JSL $0FF480 + + VERTICAL_SCROLL_SUB_ADDR = 0x7F480 + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0000, bytearray([0x4A])) # vertical_scroll: lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0001, bytearray([0x4A])) # lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0002, bytearray([0x4A])) # lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0003, bytearray([0x4A])) # lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0004, bytearray([0x08])) # php + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0005, bytearray([0xC9, 0x02])) # cmp #$02 + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0007, bytearray([0xD0, 0x0B])) # bne + + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0009, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x000B, bytearray([0xDA])) # phx + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x000C, bytearray([0xAE, 0x0B, 0x01])) # ldx $010B + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x000F, bytearray([0xBF, 0x00, 0xF5, 0x0F])) # lda.l vertical_scroll_levels,x + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0013, bytearray([0xFA])) # plx + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0014, bytearray([0x28])) # + plp + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0015, bytearray([0x6B])) # rtl + + vertical_scroll_table = [ + 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 000-00F + 0x01, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, # Levels 010-01F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 020-02F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 030-03F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 040-04F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 050-05F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 060-06F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 070-07F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 080-08F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 090-09F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 0A0-0AF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 0B0-0BF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 0C0-0CF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0D0-0DF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, # Levels 0E0-0EF + 0x02, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0F0-0FF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, # Levels 100-10F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 110-11F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 120-12F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 130-13F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 140-14F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 150-15F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 160-16F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 170-17F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 180-18F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 190-19F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1A0-1AF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1B0-1BF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1C0-1CF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1D0-1DF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1E0-1EF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02] # Levels 1F0-1FF + + rom.write_bytes(0x7F500, bytes(vertical_scroll_table)) + + +def handle_bonus_block(rom): + rom.write_bytes(0x71A5, bytearray([0x5C, 0x19, 0x8E, 0x05])) # JML $058E19 + + BONUS_BLOCK_ADDR = 0x28E19 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x00, bytearray([0xA9, 0x06])) # LDA #$06 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x02, bytearray([0xAC, 0xC0, 0x0D])) # LDY $0DC0 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x05, bytearray([0xD0, 0x1E])) # BNE IGNORE + rom.write_bytes(BONUS_BLOCK_ADDR + 0x07, bytearray([0xDA])) # PHX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x08, bytearray([0xAD, 0xBF, 0x13])) # LDA $13BF + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0B, bytearray([0x4A])) # LSR + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0C, bytearray([0x4A])) # LSR + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0D, bytearray([0x4A])) # LSR + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0E, bytearray([0x48])) # PHA + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0F, bytearray([0xAD, 0xBF, 0x13])) # LDA $13BF + rom.write_bytes(BONUS_BLOCK_ADDR + 0x12, bytearray([0x29, 0x07])) # AND #$07 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x14, bytearray([0xAA])) # TAX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x15, bytearray([0xBF, 0x5B, 0xB3, 0x05])) # LDA $05B35B,x + rom.write_bytes(BONUS_BLOCK_ADDR + 0x19, bytearray([0xFA])) # PLX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x1A, bytearray([0x1F, 0x00, 0xA0, 0x7F])) # ORA $7FA000,x + rom.write_bytes(BONUS_BLOCK_ADDR + 0x1E, bytearray([0x9F, 0x00, 0xA0, 0x7F])) # STA $7FA000,x + rom.write_bytes(BONUS_BLOCK_ADDR + 0x22, bytearray([0xFA])) # PLX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x23, bytearray([0xA9, 0x05])) # LDA #$05 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x25, bytearray([0x5C, 0xD0, 0xF1, 0x00])) # IGNORE: JML $00F1D0 + + +def handle_blocksanity(rom): + import json + blocksanity_data = pkgutil.get_data(__name__, f"data/blocksanity.json").decode("utf-8") + blocksanity_data = json.loads(blocksanity_data) + blocksanity_coords = bytearray([]) + blocksanity_bytes = bytearray([]) + + block_count = 0 + entries = 0 + for level_name, level_data in blocksanity_data.items(): + # Calculate blocksanity pointer + if level_data == []: + # Skip if the level doesn't have any data + blocksanity_bytes += bytearray([0xFF, 0xFF]) + continue + level_ptr = 0x80C0 + entries + blocksanity_bytes += bytearray([level_ptr & 0xFF, (level_ptr >> 8) & 0xFF]) + + # Get block data + block_coords = bytearray([]) + for x in range(len(level_data)): + block_coords += bytearray([ + int(level_data[x][1], 16) & 0xFF, (int(level_data[x][1], 16) >> 8) & 0xFF, + int(level_data[x][2], 16) & 0xFF, (int(level_data[x][2], 16) >> 8) & 0xFF, + block_count & 0xFF, (block_count >> 8) & 0xFF]) + entries += 6 + block_count += 1 + block_coords += bytearray([0xFF, 0xFF]) + entries += 2 + + blocksanity_coords += block_coords + + blocksanity_bytes += blocksanity_coords + + rom.write_bytes(0x80000, blocksanity_bytes) + rom.write_bytes(0x071D0, bytearray([0x5C, 0x00, 0xF7, 0x0F])) # org $00F1D0 : jml blocksanity_main + rom.write_bytes(0x0AD59, bytearray([0x5C, 0x15, 0xF7, 0x0F])) # org $01AD5C : jml blocksanity_flying_init + rom.write_bytes(0x0AE16, bytearray([0x22, 0x39, 0xF7, 0x0F])) # org $01AE16 : jsl blocksanity_flying_main + + BLOCKSANITY_ADDR = 0x7F700 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0000, bytearray([0x85, 0x05])) # blocksanity_main: sta $05 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0002, bytearray([0x8B])) # phb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0003, bytearray([0xA9, 0x10])) # lda.b #blocksanity_pointers>>16 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0005, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x0006, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0007, bytearray([0x5A])) # phy + rom.write_bytes(BLOCKSANITY_ADDR + 0x0008, bytearray([0x20, 0x63, 0xF7])) # jsr process_block + rom.write_bytes(BLOCKSANITY_ADDR + 0x000B, bytearray([0x7A])) # ply + rom.write_bytes(BLOCKSANITY_ADDR + 0x000C, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x000D, bytearray([0xA5, 0x05])) # lda $05 + rom.write_bytes(BLOCKSANITY_ADDR + 0x000F, bytearray([0xC9, 0x05])) # cmp #$05 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0011, bytearray([0x5C, 0xD4, 0xF1, 0x00])) # jml $00F1D4 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0015, bytearray([0xB5, 0xD8])) # blocksanity_flying_init: lda $D8,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0017, bytearray([0x29, 0xF0])) # and #$F0 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0019, bytearray([0x9F, 0x20, 0xB8, 0x7F])) # sta !sprite_blocksanity_y_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x001D, bytearray([0xBD, 0xD4, 0x14])) # lda $14D4,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0020, bytearray([0x9F, 0x30, 0xB8, 0x7F])) # sta !sprite_blocksanity_y_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0024, bytearray([0xBD, 0xE0, 0x14])) # lda $14E0,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0027, bytearray([0x9F, 0x10, 0xB8, 0x7F])) # sta !sprite_blocksanity_x_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x002B, bytearray([0xB5, 0xE4])) # lda $E4,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x002D, bytearray([0x29, 0xF0])) # and #$F0 + rom.write_bytes(BLOCKSANITY_ADDR + 0x002F, bytearray([0x9F, 0x00, 0xB8, 0x7F])) # sta !sprite_blocksanity_x_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0033, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x0034, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x0035, bytearray([0x5C, 0x5D, 0xAD, 0x01])) # jml $01AD5D + rom.write_bytes(BLOCKSANITY_ADDR + 0x0039, bytearray([0xBF, 0x20, 0xB8, 0x7F])) # blocksanity_flying_main: lda !sprite_blocksanity_y_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x003D, bytearray([0x85, 0x98])) # sta $98 + rom.write_bytes(BLOCKSANITY_ADDR + 0x003F, bytearray([0xBF, 0x30, 0xB8, 0x7F])) # lda !sprite_blocksanity_y_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0043, bytearray([0x85, 0x99])) # sta $99 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0045, bytearray([0xBF, 0x00, 0xB8, 0x7F])) # lda !sprite_blocksanity_x_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0049, bytearray([0x85, 0x9A])) # sta $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x004B, bytearray([0xBF, 0x10, 0xB8, 0x7F])) # lda !sprite_blocksanity_x_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x004F, bytearray([0x85, 0x9B])) # sta $9B + rom.write_bytes(BLOCKSANITY_ADDR + 0x0051, bytearray([0x8B])) # phb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0052, bytearray([0xA9, 0x10])) # lda.b #blocksanity_pointers>>16 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0054, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x0055, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0056, bytearray([0x5A])) # phy + rom.write_bytes(BLOCKSANITY_ADDR + 0x0057, bytearray([0xDA])) # phx + rom.write_bytes(BLOCKSANITY_ADDR + 0x0058, bytearray([0x20, 0x63, 0xF7])) # jsr process_block + rom.write_bytes(BLOCKSANITY_ADDR + 0x005B, bytearray([0xFA])) # plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x005C, bytearray([0x7A])) # ply + rom.write_bytes(BLOCKSANITY_ADDR + 0x005D, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x005E, bytearray([0xB5, 0xE4])) # lda $E4,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0060, bytearray([0x85, 0x9A])) # sta $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x0062, bytearray([0x6B])) # rtl + rom.write_bytes(BLOCKSANITY_ADDR + 0x0063, bytearray([0xA9, 0x0F])) # process_block: lda #$0F + rom.write_bytes(BLOCKSANITY_ADDR + 0x0065, bytearray([0x14, 0x98])) # trb $98 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0067, bytearray([0x14, 0x9A])) # trb $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x0069, bytearray([0xC2, 0x30])) # rep #$30 + rom.write_bytes(BLOCKSANITY_ADDR + 0x006B, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(BLOCKSANITY_ADDR + 0x006D, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(BLOCKSANITY_ADDR + 0x0070, bytearray([0x0A])) # asl + rom.write_bytes(BLOCKSANITY_ADDR + 0x0071, bytearray([0x18])) # clc + rom.write_bytes(BLOCKSANITY_ADDR + 0x0072, bytearray([0x69, 0x00, 0x80])) # adc.w #blocksanity_pointers + rom.write_bytes(BLOCKSANITY_ADDR + 0x0075, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x0076, bytearray([0xA0, 0x00, 0x00])) # ldy #$0000 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0079, bytearray([0xB3, 0x01])) # lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x007B, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x007C, bytearray([0xB3, 0x01])) # .loop lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x007E, bytearray([0xC9, 0xFF, 0xFF])) # cmp #$FFFF + rom.write_bytes(BLOCKSANITY_ADDR + 0x0081, bytearray([0xF0, 0x16])) # beq .return + rom.write_bytes(BLOCKSANITY_ADDR + 0x0083, bytearray([0xC5, 0x9A])) # cmp $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x0085, bytearray([0xD0, 0x0A])) # bne .next_block_x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0087, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0088, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0089, bytearray([0xB3, 0x01])) # lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x008B, bytearray([0xC5, 0x98])) # cmp $98 + rom.write_bytes(BLOCKSANITY_ADDR + 0x008D, bytearray([0xF0, 0x0F])) # beq .valid_block + rom.write_bytes(BLOCKSANITY_ADDR + 0x008F, bytearray([0x80, 0x02])) # bra .next_block_y + rom.write_bytes(BLOCKSANITY_ADDR + 0x0091, bytearray([0xC8])) # .next_block_x iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0092, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0093, bytearray([0xC8])) # .next_block_y iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0094, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0095, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0096, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0097, bytearray([0x80, 0xE3])) # bra .loop + rom.write_bytes(BLOCKSANITY_ADDR + 0x0099, bytearray([0x68])) # .return pla + rom.write_bytes(BLOCKSANITY_ADDR + 0x009A, bytearray([0x68])) # pla + rom.write_bytes(BLOCKSANITY_ADDR + 0x009B, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(BLOCKSANITY_ADDR + 0x009D, bytearray([0x60])) # rts + rom.write_bytes(BLOCKSANITY_ADDR + 0x009E, bytearray([0xC8])) # .valid_block iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x009F, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A0, bytearray([0xB3, 0x01])) # lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A2, bytearray([0xAA])) # tax + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A3, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A5, bytearray([0xDA])) # phx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A6, bytearray([0xBF, 0x00, 0xA4, 0x7F])) # lda !blocksanity_data_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00AA, bytearray([0xD0, 0x08])) # bne .processed + rom.write_bytes(BLOCKSANITY_ADDR + 0x00AC, bytearray([0x1A])) # inc + rom.write_bytes(BLOCKSANITY_ADDR + 0x00AD, bytearray([0x9F, 0x00, 0xA4, 0x7F])) # sta !blocksanity_data_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B1, bytearray([0x20, 0xBA, 0xF7])) # jsr blocksanity_check_flags + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B4, bytearray([0xFA])) # .processed plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B5, bytearray([0xFA])) # plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B6, bytearray([0xFA])) # plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B7, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B9, bytearray([0x60])) # rts + rom.write_bytes(BLOCKSANITY_ADDR + 0x00BA, bytearray([0xC2, 0x20])) # blocksanity_check_flags: rep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00BC, bytearray([0xA0, 0x00, 0x00])) # ldy #$0000 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00BF, bytearray([0xB3, 0x05])) # .loop lda ($05,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C1, bytearray([0xC9, 0xFF, 0xFF])) # cmp #$FFFF + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C4, bytearray([0xF0, 0x14])) # beq .check + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C6, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C7, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C8, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C9, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00CA, bytearray([0xB3, 0x05])) # lda ($05,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x00CC, bytearray([0xAA])) # tax + rom.write_bytes(BLOCKSANITY_ADDR + 0x00CD, bytearray([0xBF, 0x00, 0xA4, 0x7F])) # lda !blocksanity_data_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D1, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D4, bytearray([0xF0, 0x22])) # beq .invalid + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D6, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D7, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D8, bytearray([0x80, 0xE5])) # bra .loop + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DA, bytearray([0xE2, 0x20])) # .check sep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DC, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DE, bytearray([0xEB])) # xba + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DF, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E1, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E2, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E3, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E4, bytearray([0xA8])) # tay + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E5, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E7, bytearray([0x29, 0x07])) # and #$07 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E9, bytearray([0xAA])) # tax + rom.write_bytes(BLOCKSANITY_ADDR + 0x00EA, bytearray([0xBF, 0x5B, 0xB3, 0x05])) # lda.l $05B35B,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00EE, bytearray([0xBB])) # tyx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00EF, bytearray([0x1F, 0x10, 0xA0, 0x7F])) # ora !blocksanity_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00F3, bytearray([0x9F, 0x10, 0xA0, 0x7F])) # sta !blocksanity_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00F7, bytearray([0x60])) # rts + rom.write_bytes(BLOCKSANITY_ADDR + 0x00F8, bytearray([0xE2, 0x20])) # .invalid sep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00FA, bytearray([0x60])) # rts + +def handle_ram(rom): + rom.write_byte(0x07FD8, 0x02) # Expand SRAM + rom.write_bytes(0x01CF5, bytearray([0x5C, 0x00, 0xF2, 0x0F])) # org $009CF5 : jml init_sram + rom.write_bytes(0x01C0F, bytearray([0x5C, 0x00, 0xF3, 0x0F])) # org $009C0F : jml save_sram + rom.write_bytes(0x013BB, bytearray([0x5C, 0xA0, 0xF0, 0x0F])) # org $0093BB : jml init_ram + + INIT_SRAM_ADDR = 0x7F200 + rom.write_bytes(INIT_SRAM_ADDR + 0x0000, bytearray([0xD0, 0x74])) # init_sram: bne .clear + rom.write_bytes(INIT_SRAM_ADDR + 0x0002, bytearray([0x9C, 0x09, 0x01])) # stz $0109 + rom.write_bytes(INIT_SRAM_ADDR + 0x0005, bytearray([0xDA])) # phx + rom.write_bytes(INIT_SRAM_ADDR + 0x0006, bytearray([0x08])) # php + rom.write_bytes(INIT_SRAM_ADDR + 0x0007, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(INIT_SRAM_ADDR + 0x0009, bytearray([0xA2, 0x5F])) # ldx.b #$5F + rom.write_bytes(INIT_SRAM_ADDR + 0x000B, bytearray([0xBF, 0x00, 0x08, 0x70])) # - lda !level_clears_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x000F, bytearray([0x9F, 0x00, 0xA2, 0x7F])) # sta !level_clears,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0013, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x0014, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0016, bytearray([0xA2, 0x0B])) # ldx #$0B + rom.write_bytes(INIT_SRAM_ADDR + 0x0018, bytearray([0xBF, 0x40, 0x09, 0x70])) # - lda !blocksanity_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x001C, bytearray([0x9F, 0x10, 0xA0, 0x7F])) # sta !blocksanity_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0020, bytearray([0xBF, 0x10, 0x09, 0x70])) # lda !moons_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0024, bytearray([0x9D, 0xEE, 0x1F])) # sta !moons_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0027, bytearray([0xBF, 0x00, 0x09, 0x70])) # lda !yoshi_coins_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x002B, bytearray([0x9D, 0x2F, 0x1F])) # sta !yoshi_coins_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x002E, bytearray([0xBF, 0x30, 0x09, 0x70])) # lda !bonus_block_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0032, bytearray([0x9F, 0x00, 0xA0, 0x7F])) # sta !bonus_block_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0036, bytearray([0xBF, 0x20, 0x09, 0x70])) # lda !checkpoints_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x003A, bytearray([0x9D, 0x3C, 0x1F])) # sta !checkpoints_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x003D, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x003E, bytearray([0x10, 0xD8])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0040, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(INIT_SRAM_ADDR + 0x0042, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(INIT_SRAM_ADDR + 0x0045, bytearray([0xBF, 0x00, 0x0A, 0x70])) # - lda !blocksanity_data_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0049, bytearray([0x9F, 0x00, 0xA4, 0x7F])) # sta !blocksanity_data_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x004D, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x004E, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0050, bytearray([0xE2, 0x10])) # sep #$10 + #rom.write_bytes(INIT_SRAM_ADDR + 0x0052, bytearray([0xAF, 0x50, 0x09, 0x70])) # lda !received_items_count_sram+$00 + #rom.write_bytes(INIT_SRAM_ADDR + 0x0056, bytearray([0x8F, 0x0E, 0xA0, 0x7F])) # sta !received_items_count+$00 + #rom.write_bytes(INIT_SRAM_ADDR + 0x005A, bytearray([0xAF, 0x51, 0x09, 0x70])) # lda !received_items_count_sram+$01 + #rom.write_bytes(INIT_SRAM_ADDR + 0x005E, bytearray([0x8F, 0x0F, 0xA0, 0x7F])) # sta !received_items_count+$01 + rom.write_bytes(INIT_SRAM_ADDR + 0x0052, bytearray([0xEA] * 0x17)) # Ugly, will apply be better when we port everything to a Base Patch + #rom.write_bytes(INIT_SRAM_ADDR + 0x0062, bytearray([0xAF, 0x52, 0x09, 0x70])) # lda !special_world_clear_sram + #rom.write_bytes(INIT_SRAM_ADDR + 0x0066, bytearray([0x8D, 0xFF, 0x1F])) # sta !special_world_clear_flag + rom.write_bytes(INIT_SRAM_ADDR + 0x0069, bytearray([0xAF, 0x54, 0x09, 0x70])) # lda !goal_item_count_sram + rom.write_bytes(INIT_SRAM_ADDR + 0x006D, bytearray([0x8F, 0x1E, 0xA0, 0x7F])) # sta !goal_item_count + rom.write_bytes(INIT_SRAM_ADDR + 0x0071, bytearray([0x28])) # plp + rom.write_bytes(INIT_SRAM_ADDR + 0x0072, bytearray([0x5C, 0xFB, 0x9C, 0x00])) # jml $009CFB + rom.write_bytes(INIT_SRAM_ADDR + 0x0076, bytearray([0xDA])) # .clear phx + rom.write_bytes(INIT_SRAM_ADDR + 0x0077, bytearray([0xA2, 0x5F, 0x00])) # ldx.w #$005F + rom.write_bytes(INIT_SRAM_ADDR + 0x007A, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(INIT_SRAM_ADDR + 0x007C, bytearray([0x9F, 0x00, 0x08, 0x70])) # - sta !level_clears_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0080, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x0081, bytearray([0x10, 0xF9])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0083, bytearray([0xA2, 0x0B, 0x00])) # ldx.w #$000B + rom.write_bytes(INIT_SRAM_ADDR + 0x0086, bytearray([0x9F, 0x40, 0x09, 0x70])) # - sta !blocksanity_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x008A, bytearray([0x9F, 0x00, 0x09, 0x70])) # sta !yoshi_coins_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x008E, bytearray([0x9F, 0x30, 0x09, 0x70])) # sta !bonus_block_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0092, bytearray([0x9F, 0x10, 0x09, 0x70])) # sta !moons_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0096, bytearray([0x9F, 0x20, 0x09, 0x70])) # sta !checkpoints_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x009A, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x009B, bytearray([0x10, 0xE9])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x009D, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(INIT_SRAM_ADDR + 0x00A0, bytearray([0x9F, 0x00, 0x0A, 0x70])) # - sta !blocksanity_data_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x00A4, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x00A5, bytearray([0x10, 0xF9])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x00A7, bytearray([0x8F, 0x52, 0x09, 0x70])) # sta !special_world_clear_sram + rom.write_bytes(INIT_SRAM_ADDR + 0x00AB, bytearray([0x8F, 0x50, 0x09, 0x70])) # sta !received_items_count_sram+$00 + rom.write_bytes(INIT_SRAM_ADDR + 0x00AF, bytearray([0x8F, 0x51, 0x09, 0x70])) # sta !received_items_count_sram+$01 + rom.write_bytes(INIT_SRAM_ADDR + 0x00B3, bytearray([0x8F, 0x54, 0x09, 0x70])) # sta !goal_item_count_sram + rom.write_bytes(INIT_SRAM_ADDR + 0x00B7, bytearray([0xFA])) # plx + rom.write_bytes(INIT_SRAM_ADDR + 0x00B8, bytearray([0x5C, 0x22, 0x9D, 0x00])) # jml $009D22 + + SAVE_SRAM_ADDR = 0x7F300 + rom.write_bytes(SAVE_SRAM_ADDR + 0x0000, bytearray([0xE2, 0x30])) # save_sram: sep #$30 + rom.write_bytes(SAVE_SRAM_ADDR + 0x0002, bytearray([0xAB])) # plb + rom.write_bytes(SAVE_SRAM_ADDR + 0x0003, bytearray([0xA2, 0x5F])) # ldx.b #$5F + rom.write_bytes(SAVE_SRAM_ADDR + 0x0005, bytearray([0xBF, 0x00, 0xA2, 0x7F])) # - lda !level_clears,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0009, bytearray([0x9F, 0x00, 0x08, 0x70])) # sta !level_clears_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x000D, bytearray([0xCA])) # dex + rom.write_bytes(SAVE_SRAM_ADDR + 0x000E, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(SAVE_SRAM_ADDR + 0x0010, bytearray([0xA2, 0x0B])) # ldx #$0B + rom.write_bytes(SAVE_SRAM_ADDR + 0x0012, bytearray([0xBF, 0x10, 0xA0, 0x7F])) # - lda !blocksanity_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0016, bytearray([0x9F, 0x40, 0x09, 0x70])) # sta !blocksanity_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x001A, bytearray([0xBD, 0x2F, 0x1F])) # lda !yoshi_coins_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x001D, bytearray([0x9F, 0x00, 0x09, 0x70])) # sta !yoshi_coins_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0021, bytearray([0xBD, 0xEE, 0x1F])) # lda !moons_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0024, bytearray([0x9F, 0x10, 0x09, 0x70])) # sta !moons_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0028, bytearray([0xBF, 0x00, 0xA0, 0x7F])) # lda !bonus_block_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x002C, bytearray([0x9F, 0x30, 0x09, 0x70])) # sta !bonus_block_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0030, bytearray([0xBD, 0x3C, 0x1F])) # lda !checkpoints_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0033, bytearray([0x9F, 0x20, 0x09, 0x70])) # sta !checkpoints_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0037, bytearray([0xCA])) # dex + rom.write_bytes(SAVE_SRAM_ADDR + 0x0038, bytearray([0x10, 0xD8])) # bpl - + rom.write_bytes(SAVE_SRAM_ADDR + 0x003A, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(SAVE_SRAM_ADDR + 0x003C, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(SAVE_SRAM_ADDR + 0x003F, bytearray([0xBF, 0x00, 0xA4, 0x7F])) # - lda !blocksanity_data_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0043, bytearray([0x9F, 0x00, 0x0A, 0x70])) # sta !blocksanity_data_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0047, bytearray([0xCA])) # dex + rom.write_bytes(SAVE_SRAM_ADDR + 0x0048, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(SAVE_SRAM_ADDR + 0x004A, bytearray([0xE2, 0x10])) # sep #$10 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x004C, bytearray([0xAD, 0xFF, 0x1F])) # lda !special_world_clear_flag + #rom.write_bytes(SAVE_SRAM_ADDR + 0x004F, bytearray([0x8F, 0x52, 0x09, 0x70])) # sta !special_world_clear_sram + #rom.write_bytes(SAVE_SRAM_ADDR + 0x0053, bytearray([0xAF, 0x0E, 0xA0, 0x7F])) # lda !received_items_count+$00 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x0057, bytearray([0x8F, 0x50, 0x09, 0x70])) # sta !received_items_count_sram+$00 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x005B, bytearray([0xAF, 0x0F, 0xA0, 0x7F])) # lda !received_items_count+$01 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x005F, bytearray([0x8F, 0x51, 0x09, 0x70])) # sta !received_items_count_sram+$01 + rom.write_bytes(SAVE_SRAM_ADDR + 0x004C, bytearray([0xEA] * 0x17)) # Ugly, will apply be better when we port everything to a Base Patch + rom.write_bytes(SAVE_SRAM_ADDR + 0x0063, bytearray([0xAF, 0x0F, 0xA0, 0x7F])) # lda !goal_item_count + rom.write_bytes(SAVE_SRAM_ADDR + 0x0067, bytearray([0x8F, 0x51, 0x09, 0x70])) # sta !goal_item_count_sram + rom.write_bytes(SAVE_SRAM_ADDR + 0x006B, bytearray([0x6B])) # rtl + + INIT_RAM_ADDR = 0x7F0A0 + rom.write_bytes(INIT_RAM_ADDR + 0x0000, bytearray([0xA9, 0xAA])) # init_ram: lda #$AA + rom.write_bytes(INIT_RAM_ADDR + 0x0002, bytearray([0x8D, 0x00, 0x04])) # sta $0400 + rom.write_bytes(INIT_RAM_ADDR + 0x0005, bytearray([0xA9, 0x00])) # clear_level_data: lda #$00 + rom.write_bytes(INIT_RAM_ADDR + 0x0007, bytearray([0xA2, 0x5F])) # ldx #$5F + rom.write_bytes(INIT_RAM_ADDR + 0x0009, bytearray([0x9F, 0x00, 0xA2, 0x7F])) # .loop sta !level_clears,x + rom.write_bytes(INIT_RAM_ADDR + 0x000D, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x000E, bytearray([0x10, 0xF9])) # bpl .loop + rom.write_bytes(INIT_RAM_ADDR + 0x0010, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(INIT_RAM_ADDR + 0x0012, bytearray([0xA2, 0x0B, 0x00])) # ldx.w #$000B + rom.write_bytes(INIT_RAM_ADDR + 0x0015, bytearray([0x9F, 0x10, 0xA0, 0x7F])) # - sta !blocksanity_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0019, bytearray([0x9D, 0x2F, 0x1F])) # sta !yoshi_coins_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x001C, bytearray([0x9D, 0xEE, 0x1F])) # sta !moons_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x001F, bytearray([0x9F, 0x00, 0xA0, 0x7F])) # sta !bonus_block_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0023, bytearray([0x9D, 0x3C, 0x1F])) # sta !checkpoints_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0026, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x0027, bytearray([0x10, 0xEC])) # bpl - + rom.write_bytes(INIT_RAM_ADDR + 0x0029, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(INIT_RAM_ADDR + 0x002C, bytearray([0x9F, 0x00, 0xA4, 0x7F])) # - sta !blocksanity_data_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0030, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x0031, bytearray([0x10, 0xF9])) # bpl - + rom.write_bytes(INIT_RAM_ADDR + 0x0033, bytearray([0xA2, 0x22, 0x04])) # ldx #$0422 + rom.write_bytes(INIT_RAM_ADDR + 0x0036, bytearray([0x9F, 0x00, 0xB0, 0x7F])) # - sta !score_sprite_count,x + rom.write_bytes(INIT_RAM_ADDR + 0x003A, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x003B, bytearray([0x10, 0xF9])) # bpl - + #rom.write_bytes(INIT_RAM_ADDR + 0x003D, bytearray([0x8D, 0xFF, 0x1F])) # sta !special_world_clear_flag + rom.write_bytes(INIT_RAM_ADDR + 0x003D, bytearray([0xEA, 0xEA, 0xEA])) # sta !special_world_clear_flag + rom.write_bytes(INIT_RAM_ADDR + 0x0040, bytearray([0x8F, 0x0E, 0xA0, 0x7F])) # sta !received_items_count+$00 + rom.write_bytes(INIT_RAM_ADDR + 0x0044, bytearray([0x8F, 0x0F, 0xA0, 0x7F])) # sta !received_items_count+$01 + rom.write_bytes(INIT_RAM_ADDR + 0x0048, bytearray([0x8F, 0x1E, 0xA0, 0x7F])) # sta !goal_item_count + rom.write_bytes(INIT_RAM_ADDR + 0x004C, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(INIT_RAM_ADDR + 0x004E, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(INIT_RAM_ADDR + 0x0051, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(INIT_RAM_ADDR + 0x0053, bytearray([0x22, 0x20, 0xF1, 0x0F])) # jsl clear_tilemap + rom.write_bytes(INIT_RAM_ADDR + 0x0057, bytearray([0x5C, 0xC0, 0x93, 0x00])) # jml $0093C0 + +def handle_map_indicators(rom): + rom.write_bytes(0x265EE, bytearray([0x4C, 0x00, 0xA3])) # org $04E5EE : jmp check_events + + GET_MAP_LEVEL_NUM_ADDR = 0x22340 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0000, bytearray([0xC2, 0x30])) # get_translevel_num: rep #$30 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0002, bytearray([0xAE, 0xD6, 0x0D])) # ldx $0DD6 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0005, bytearray([0xBD, 0x1F, 0x1F])) # lda $1F1F,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0008, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x000A, bytearray([0xBD, 0x21, 0x1F])) # lda $1F21,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x000D, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x000F, bytearray([0x8A])) # txa + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0010, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0011, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0012, bytearray([0xAA])) # tax + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0013, bytearray([0x20, 0x85, 0x98])) # jsr $9885 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0016, bytearray([0xA6, 0x04])) # ldx $04 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0018, bytearray([0xBF, 0x00, 0xD0, 0x7E])) # lda $7ED000,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x001C, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x001E, bytearray([0x85, 0x60])) # sta $60 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0020, bytearray([0xAA])) # tax + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0021, bytearray([0xBF, 0x00, 0xFF, 0x06])) # lda $06FF00,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0025, bytearray([0xC9, 0xFF])) # cmp #$FF + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0027, bytearray([0xF0, 0x02])) # beq + + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0029, bytearray([0x85, 0x60])) # sta $60 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x002B, bytearray([0x60])) # + rts + + GET_MAP_LEVEL_BIT_ADDR = 0x22380 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0000, bytearray([0xA5, 0x60])) # get_translevel_bit: lda $60 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0002, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0003, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0004, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0005, bytearray([0xA8])) # tay + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0006, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0008, bytearray([0x29, 0x07])) # and #$07 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x000A, bytearray([0xAA])) # tax + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x000B, bytearray([0x60])) # rts + + UPDATE_MAP_PTRS_ADDR = 0x223C0 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0000, bytearray([0xE6, 0x00])) # update_flag_pointers: inc $00 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0002, bytearray([0xE6, 0x00])) # inc $00 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0004, bytearray([0xE6, 0x03])) # inc $03 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0006, bytearray([0xE6, 0x03])) # inc $03 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0008, bytearray([0xE6, 0x06])) # inc $06 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x000A, bytearray([0xE6, 0x06])) # inc $06 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x000C, bytearray([0xE6, 0x62])) # inc $62 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x000E, bytearray([0xE6, 0x62])) # inc $62 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0010, bytearray([0xE6, 0x63])) # inc $63 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0012, bytearray([0x60])) # rts + + CLEAR_TILEMAP_ADDR = 0x7F120 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0000, bytearray([0xC2, 0x20])) # clear_tilemap: rep #$20 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0002, bytearray([0xA9, 0x1F, 0x39])) # lda.w #$3900+!icon_disabled + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0005, bytearray([0xA2, 0x1E])) # ldx #$1E + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0007, bytearray([0x9F, 0x20, 0xA1, 0x7F])) # .loop sta !ow_tilemap_switches,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x000B, bytearray([0x9F, 0x00, 0xA1, 0x7F])) # sta !ow_tilemap_abilities,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x000F, bytearray([0x9F, 0x40, 0xA1, 0x7F])) # sta !ow_tilemap_flags_top,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0013, bytearray([0x9F, 0x60, 0xA1, 0x7F])) # sta !ow_tilemap_flags_mid,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0017, bytearray([0x9F, 0x80, 0xA1, 0x7F])) # sta !ow_tilemap_flags_bot,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001B, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001C, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001D, bytearray([0x10, 0xE8])) # bpl .loop + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001F, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0021, bytearray([0xA9, 0x07])) # lda #$07 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0023, bytearray([0x85, 0x63])) # sta $63 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0025, bytearray([0x0A])) # asl + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0026, bytearray([0x85, 0x62])) # sta $62 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0028, bytearray([0x6B])) # rtl + + CLEAR_TILEMAP_FLAGS_ADDR = 0x7F180 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0000, bytearray([0xC2, 0x20])) # clear_tilemap_flags: rep #$20 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0002, bytearray([0xA9, 0x1F, 0x39])) # lda.w #$3900+!icon_disabled + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0005, bytearray([0xA2, 0x0C])) # ldx.b #($07*2)-2 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0007, bytearray([0x9F, 0x40, 0xA1, 0x7F])) # .loop sta !ow_tilemap_flags_top,x + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x000B, bytearray([0x9F, 0x60, 0xA1, 0x7F])) # sta !ow_tilemap_flags_mid,x + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x000F, bytearray([0x9F, 0x80, 0xA1, 0x7F])) # sta !ow_tilemap_flags_bot,x + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0013, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0014, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0015, bytearray([0x10, 0xF0])) # bpl .loop + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0017, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0019, bytearray([0xA9, 0x06])) # lda #$06 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x001B, bytearray([0x85, 0x63])) # sta $63 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x001D, bytearray([0x0A])) # asl + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x001E, bytearray([0x85, 0x62])) # sta $62 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0020, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0022, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0025, bytearray([0x6B])) # rtl + + CHECK_EVENTS_ADDR = 0x22300 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0000, bytearray([0xDA])) # check_events: phx + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0001, bytearray([0x20, 0x40, 0xA3])) # jsr get_translevel_num + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0004, bytearray([0xAD, 0xD5, 0x0D])) # lda $0DD5 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0007, bytearray([0xF0, 0x17])) # beq .dont_sync + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0009, bytearray([0x30, 0x15])) # bmi .dont_sync + rom.write_bytes(CHECK_EVENTS_ADDR + 0x000B, bytearray([0xC9, 0x05])) # cmp #$05 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x000D, bytearray([0xB0, 0x11])) # bcs .dont_sync + rom.write_bytes(CHECK_EVENTS_ADDR + 0x000F, bytearray([0x29, 0x07])) # and #$07 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0011, bytearray([0xAA])) # tax + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0012, bytearray([0xBF, 0x7D, 0x9E, 0x00])) # lda.l $009E7D,x + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0016, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0018, bytearray([0x1F, 0x00, 0xA2, 0x7F])) # ora !level_clears,x + rom.write_bytes(CHECK_EVENTS_ADDR + 0x001C, bytearray([0x9F, 0x00, 0xA2, 0x7F])) # sta !level_clears,x + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0020, bytearray([0xFA])) # .dont_sync plx + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0021, bytearray([0xAD, 0xD5, 0x0D])) # lda $0DD5 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0024, bytearray([0xC9, 0x02])) # cmp #$02 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0026, bytearray([0xD0, 0x03])) # bne .no_secret + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0028, bytearray([0xEE, 0xEA, 0x1D])) # inc $1DEA + rom.write_bytes(CHECK_EVENTS_ADDR + 0x002B, bytearray([0x4C, 0xF8, 0xE5])) # .no_secret jmp $E5F8 + + DRAW_MAP_TILEMAP_ADDR = 0x221B6 + rom.write_bytes(0x00222, bytearray([0x5C, 0xB6, 0xA1, 0x04])) # org $008222 : jml draw_ow_tilemap + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0000, bytearray([0xAD, 0xD9, 0x13])) # draw_ow_tilemap: lda $13D9 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0003, bytearray([0xC9, 0x0A])) # cmp #$0A + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0005, bytearray([0xD0, 0x04])) # bne write_tilemap + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0007, bytearray([0x5C, 0x29, 0x82, 0x00])) # jml $008229 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x000B, bytearray([0xC2, 0x20])) # write_tilemap: rep #$20 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x000D, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x000F, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0012, bytearray([0xA9, 0x27, 0x50])) # write_abilities: lda #!vram_abilities_top + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0015, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0018, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x001A, bytearray([0xBF, 0xA2, 0xA2, 0x04])) # ..loop lda.l abilities_top,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x001E, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0021, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0022, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0023, bytearray([0xE0, 0x14])) # cpx.b #$0A*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0025, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0027, bytearray([0xA9, 0x47, 0x50])) # .mid lda #!vram_abilities_mid + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x002A, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x002D, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x002F, bytearray([0xBF, 0xB6, 0xA2, 0x04])) # ..loop lda.l abilities_bottom,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0033, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0036, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0037, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0038, bytearray([0xE0, 0x14])) # cpx.b #$0A*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x003A, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x003C, bytearray([0xA9, 0x67, 0x50])) # .bot lda #!vram_abilities_bot + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x003F, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0042, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0044, bytearray([0xBF, 0x00, 0xA1, 0x7F])) # ..loop lda !ow_tilemap_abilities,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0048, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004B, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004C, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004D, bytearray([0xE0, 0x14])) # cpx.b #$0A*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004F, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0051, bytearray([0xA9, 0x32, 0x50])) # write_switches: lda #!vram_switches_top + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0054, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0057, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0059, bytearray([0xBF, 0xCA, 0xA2, 0x04])) # ..loop lda.l switches_top,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x005D, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0060, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0061, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0062, bytearray([0xE0, 0x0A])) # cpx.b #$05*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0064, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0066, bytearray([0xA9, 0x52, 0x50])) # .mid lda #!vram_switches_mid + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0069, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x006C, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x006E, bytearray([0xBF, 0xD4, 0xA2, 0x04])) # ..loop lda.l switches_bottom,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0072, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0075, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0076, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0077, bytearray([0xE0, 0x0A])) # cpx.b #$05*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0079, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x007B, bytearray([0xA9, 0x72, 0x50])) # .bot lda #!vram_switches_bot + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x007E, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0081, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0083, bytearray([0xBF, 0x20, 0xA1, 0x7F])) # ..loop lda !ow_tilemap_switches,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0087, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008A, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008B, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008C, bytearray([0xE0, 0x0A])) # cpx.b #$05*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008E, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0090, bytearray([0xD4, 0x00])) # write_level_data: pei ($00) + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0092, bytearray([0xA5, 0x63])) # lda $63 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0094, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0097, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0099, bytearray([0xF0, 0x48])) # beq .skip_flags + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x009B, bytearray([0xA9, 0x3E, 0x50])) # .top lda.w #!vram_level_data_top+$01 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x009E, bytearray([0x38])) # sec + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x009F, bytearray([0xE5, 0x00])) # sbc $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A1, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A4, bytearray([0xA6, 0x62])) # ldx.b $62 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A6, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A7, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A8, bytearray([0xBF, 0x40, 0xA1, 0x7F])) # ..loop lda.l !ow_tilemap_flags_top,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00AC, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00AF, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B0, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B1, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B3, bytearray([0xA9, 0x5E, 0x50])) # .mid lda.w #!vram_level_data_mid+$01 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B6, bytearray([0x38])) # sec + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B7, bytearray([0xE5, 0x00])) # sbc $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B9, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00BC, bytearray([0xA6, 0x62])) # ldx.b $62 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00BE, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00BF, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C0, bytearray([0xBF, 0x60, 0xA1, 0x7F])) # ..loop lda.l !ow_tilemap_flags_mid,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C4, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C7, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C8, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C9, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00CB, bytearray([0xA9, 0x7E, 0x50])) # .bot lda.w #!vram_level_data_bot+$01 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00CE, bytearray([0x38])) # sec + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00CF, bytearray([0xE5, 0x00])) # sbc $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D1, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D4, bytearray([0xA6, 0x62])) # ldx.b $62 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D6, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D7, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D8, bytearray([0xBF, 0x80, 0xA1, 0x7F])) # ..loop lda.l !ow_tilemap_flags_bot,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00DC, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00DF, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E0, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E1, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E3, bytearray([0x68])) # .skip_flags pla + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E4, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E6, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E8, bytearray([0x5C, 0x37, 0x82, 0x00])) # jml $008237 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00EC, bytearray([0x0F, 0x39, 0x12, 0x39])) # abilities_top: dw $390F,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00F0, bytearray([0x11, 0x39, 0x02, 0x39])) # dw $3911,$3902 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00F4, bytearray([0x12, 0x39, 0x02, 0x39])) # dw $3912,$3902 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00F8, bytearray([0x18, 0x39, 0x0F, 0x39])) # dw $3918,$390F + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00FC, bytearray([0x0F, 0x39, 0x12, 0x39])) # dw $390F,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0100, bytearray([0x4E, 0x39, 0x4F, 0x39])) # abilities_bottom: dw $394E,$394F + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0104, bytearray([0x54, 0x39, 0x40, 0x39])) # dw $3954,$3940 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0108, bytearray([0x56, 0x39, 0x4B, 0x39])) # dw $3956,$394B + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x010C, bytearray([0x4E, 0x39, 0x52, 0x39])) # dw $394E,$3952 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0110, bytearray([0x41, 0x39, 0x53, 0x39])) # dw $3941,$3953 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0114, bytearray([0x18, 0x39, 0x06, 0x39])) # switches_top: dw $3918,$3906 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0118, bytearray([0x11, 0x39, 0x01, 0x39])) # dw $3911,$3901 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x011C, bytearray([0x12, 0x39])) # dw $3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x011E, bytearray([0x12, 0x39, 0x12, 0x39])) # switches_bottom: dw $3912,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0122, bytearray([0x12, 0x39, 0x12, 0x39])) # dw $3912,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0126, bytearray([0x4F, 0x39])) # dw $394F + + BUILD_TILEMAP_ADDR = 0x26F3E + rom.write_bytes(0x021C7, bytearray([0x22, 0x3E, 0xEF, 0x04])) # org $00A1C7 : jsl prepare_dynamic_tilemap + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0000, bytearray([0x22, 0x41, 0x82, 0x04])) # prepare_dynamic_tilemap: jsl $048241 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0004, bytearray([0xA0, 0x22])) # .handle_powerup: ldy #$22 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0006, bytearray([0xAD, 0x2D, 0x1F])) # lda $1F2D + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0009, bytearray([0x4A])) # lsr + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000A, bytearray([0x90, 0x01])) # bcc $01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000C, bytearray([0xC8])) # iny + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000D, bytearray([0x4A])) # lsr + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000E, bytearray([0x90, 0x01])) # bcc $01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0010, bytearray([0xC8])) # iny + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0011, bytearray([0x4A])) # lsr + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0012, bytearray([0x90, 0x01])) # bcc $01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0014, bytearray([0xC8])) # iny + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0015, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0016, bytearray([0x8F, 0x00, 0xA1, 0x7F])) # sta !ow_tilemap_abilities ; Progressive powerup + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x001A, bytearray([0xA0, 0x5E])) # .handle_spinjump: ldy #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x001C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x001F, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0021, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0023, bytearray([0xA0, 0x3F])) # ldy #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0025, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0026, bytearray([0x8F, 0x02, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$02 ; Spin jump + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x002A, bytearray([0xA0, 0x5E])) # .handle_run: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x002C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x002F, bytearray([0x29, 0x80])) # and #$80 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0031, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0033, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0035, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0036, bytearray([0x8F, 0x04, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$04 ; Run + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x003A, bytearray([0xA0, 0x5E])) # .handle_carry: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x003C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x003F, bytearray([0x29, 0x40])) # and #$40 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0041, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0043, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0045, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0046, bytearray([0x8F, 0x06, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$06 ; Carry + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x004A, bytearray([0xA0, 0x5E])) # .handle_swim: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x004C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x004F, bytearray([0x29, 0x04])) # and #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0051, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0053, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0055, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0056, bytearray([0x8F, 0x08, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$08 ; Swim + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x005A, bytearray([0xA0, 0x5E])) # .handle_climb: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x005C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x005F, bytearray([0x29, 0x20])) # and #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0061, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0063, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0065, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0066, bytearray([0x8F, 0x0A, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$0A ; Climb + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x006A, bytearray([0xA0, 0x5E])) # .handle_yoshi: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x006C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x006F, bytearray([0x29, 0x02])) # and #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0071, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0073, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0075, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0076, bytearray([0x8F, 0x0C, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$0C ; Yoshi + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x007A, bytearray([0xA0, 0x5E])) # .handle_pswitch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x007C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x007F, bytearray([0x29, 0x10])) # and #$10 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0081, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0083, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0085, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0086, bytearray([0x8F, 0x0E, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$0E ; P-Switch + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x008A, bytearray([0xA0, 0x5E])) # .handle_pballoon: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x008C, bytearray([0xAD, 0x2D, 0x1F])) # lda $1F2D + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x008F, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0091, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0093, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0095, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0096, bytearray([0x8F, 0x10, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$10 ; P-Balloon + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x009A, bytearray([0xA0, 0x5E])) # .handle_star: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x009C, bytearray([0xAD, 0x2D, 0x1F])) # lda $1F2D + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x009F, bytearray([0x29, 0x10])) # and #$10 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A1, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A3, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A5, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A6, bytearray([0x8F, 0x12, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$12 ; Star + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00AA, bytearray([0xA0, 0x5E])) # .handle_yellow_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00AC, bytearray([0xAD, 0x28, 0x1F])) # lda $1F28 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00AF, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B1, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B3, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B4, bytearray([0x8F, 0x20, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$00 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B8, bytearray([0xA0, 0x5E])) # .handle_green_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00BA, bytearray([0xAD, 0x27, 0x1F])) # lda $1F27 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00BD, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00BF, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C1, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C2, bytearray([0x8F, 0x22, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C6, bytearray([0xA0, 0x5E])) # .handle_red_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C8, bytearray([0xAD, 0x2A, 0x1F])) # lda $1F2A + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00CB, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00CD, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00CF, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D0, bytearray([0x8F, 0x24, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D4, bytearray([0xA0, 0x5E])) # .handle_blue_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D6, bytearray([0xAD, 0x29, 0x1F])) # lda $1F29 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D9, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00DB, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00DD, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00DE, bytearray([0x8F, 0x26, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$06 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E2, bytearray([0xA0, 0x5E])) # .handle_special_world_clear: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E4, bytearray([0xAD, 0x1E, 0x1F])) # lda !special_world_clear_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E7, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E9, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00EB, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00EC, bytearray([0x8F, 0x28, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F0, bytearray([0x22, 0x80, 0xF1, 0x0F])) # jsl clear_tilemap_flags + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F4, bytearray([0xAD, 0xD9, 0x13])) # lda $13D9 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F7, bytearray([0xC9, 0x01])) # cmp #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F9, bytearray([0xF0, 0x05])) # beq process_level + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00FB, bytearray([0xC9, 0x03])) # cmp #$03 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00FD, bytearray([0xF0, 0x01])) # beq process_level + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00FF, bytearray([0x6B])) # rtl + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0100, bytearray([0x20, 0x40, 0xA3])) # process_level: jsr get_translevel_num + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0103, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0105, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0109, bytearray([0x10, 0x01])) # bpl .handle_data + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x010B, bytearray([0x6B])) # rtl + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x010C, bytearray([0x64, 0x62])) # .handle_data stz $62 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x010E, bytearray([0x64, 0x63])) # stz $63 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0110, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0112, bytearray([0xA9, 0x40, 0xA1])) # lda.w #!ow_tilemap_flags_top + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0115, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0117, bytearray([0xA9, 0x60, 0xA1])) # lda.w #!ow_tilemap_flags_mid + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x011A, bytearray([0x85, 0x03])) # sta $03 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x011C, bytearray([0xA9, 0x80, 0xA1])) # lda.w #!ow_tilemap_flags_bot + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x011F, bytearray([0x85, 0x06])) # sta $06 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0121, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0123, bytearray([0xA9, 0x7F])) # lda.b #!ow_tilemap_flags_top>>16 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0125, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0127, bytearray([0x85, 0x05])) # sta $05 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0129, bytearray([0x85, 0x08])) # sta $08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x012B, bytearray([0xAF, 0xAB, 0xBF, 0x03])) # handle_blocksanity: lda.l blocksanity_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x012F, bytearray([0xF0, 0x30])) # beq handle_bonus_blocks + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0131, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0133, bytearray([0xA0, 0x1F])) # ldy.b #!icon_disabled + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0135, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0139, bytearray([0x29, 0x40])) # and #$40 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x013B, bytearray([0xF0, 0x24])) # beq handle_bonus_blocks + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x013D, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x013F, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0140, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0143, bytearray([0xDA])) # phx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0144, bytearray([0xBB])) # tyx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0145, bytearray([0xBF, 0x10, 0xA0, 0x7F])) # lda.l !blocksanity_flags,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0149, bytearray([0xFA])) # plx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x014A, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x014B, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x014F, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0151, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0153, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0154, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0156, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0158, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x015A, bytearray([0xA9, 0x12])) # lda #$12 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x015C, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x015E, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0161, bytearray([0xAF, 0xAA, 0xBF, 0x03])) # handle_bonus_blocks: lda.l bonus_block_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0165, bytearray([0xF0, 0x30])) # beq handle_checkpoints + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0167, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0169, bytearray([0xA0, 0x1F])) # ldy.b #!icon_disabled + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x016B, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x016F, bytearray([0x29, 0x20])) # and #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0171, bytearray([0xF0, 0x24])) # beq handle_checkpoints + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0173, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0175, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0176, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0179, bytearray([0xDA])) # phx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x017A, bytearray([0xBB])) # tyx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x017B, bytearray([0xBF, 0x00, 0xA0, 0x7F])) # lda !bonus_block_flags,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x017F, bytearray([0xFA])) # plx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0180, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0181, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0185, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0187, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0189, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x018A, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x018C, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x018E, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0190, bytearray([0xA9, 0x4E])) # lda #$4E + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0192, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0194, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0197, bytearray([0xAF, 0xA9, 0xBF, 0x03])) # handle_checkpoints: lda.l checkpoints_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x019B, bytearray([0xF0, 0x2A])) # beq handle_moons + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x019D, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x019F, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A3, bytearray([0x29, 0x10])) # and #$10 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A5, bytearray([0xF0, 0x20])) # beq handle_moons + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A7, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A9, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01AA, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01AD, bytearray([0xB9, 0x3C, 0x1F])) # lda !checkpoints_flags,y + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B0, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B1, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B5, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B7, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B9, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01BA, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01BC, bytearray([0xA9, 0x07])) # lda #$07 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01BE, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C0, bytearray([0xA9, 0x48])) # lda #$48 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C2, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C4, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C7, bytearray([0xAF, 0xA8, 0xBF, 0x03])) # handle_moons: lda.l moon_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01CB, bytearray([0xF0, 0x2A])) # beq handle_dragon_coins + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01CD, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01CF, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D3, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D5, bytearray([0xF0, 0x20])) # beq handle_dragon_coins + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D7, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D9, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01DA, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01DD, bytearray([0xB9, 0xEE, 0x1F])) # lda !moons_flags,y + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E0, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E1, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E5, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E7, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E9, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01EA, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01EC, bytearray([0xA9, 0x0C])) # lda #$0C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01EE, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F0, bytearray([0xA9, 0x4E])) # lda #$4E + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F2, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F4, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F7, bytearray([0xAF, 0xA6, 0xBF, 0x03])) # handle_dragon_coins: lda.l dragon_coin_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01FB, bytearray([0xF0, 0x2A])) # beq handle_exit_2 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01FD, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01FF, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0203, bytearray([0x29, 0x04])) # and #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0205, bytearray([0xF0, 0x20])) # beq handle_exit_2 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0207, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0209, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x020A, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x020D, bytearray([0xB9, 0x2F, 0x1F])) # lda !yoshi_coins_flags,y + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0210, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0211, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0215, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0217, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0219, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x021A, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x021C, bytearray([0xA9, 0x03])) # lda #$03 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x021E, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0220, bytearray([0xA9, 0x02])) # lda #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0222, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0224, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0227, bytearray([0xA6, 0x60])) # handle_exit_2: ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0229, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x022D, bytearray([0x29, 0x02])) # and #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x022F, bytearray([0xF0, 0x1A])) # beq handle_exit_1 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0231, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0233, bytearray([0xBF, 0x00, 0xA2, 0x7F])) # lda !level_clears,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0237, bytearray([0x29, 0x02])) # and #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0239, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x023B, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x023D, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x023E, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0240, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0242, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0244, bytearray([0xA9, 0x24])) # lda #$24 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0246, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0248, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x024B, bytearray([0xA6, 0x60])) # handle_exit_1: ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x024D, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0251, bytearray([0x29, 0x01])) # and #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0253, bytearray([0xF0, 0x1A])) # beq .dont_draw + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0255, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0257, bytearray([0xBF, 0x00, 0xA2, 0x7F])) # lda !level_clears,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x025B, bytearray([0x29, 0x01])) # and #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x025D, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x025F, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0261, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0262, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0264, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0266, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0268, bytearray([0xA9, 0x23])) # lda #$23 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x026A, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x026C, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x026F, bytearray([0x6B])) # .dont_draw rtl + + LEVEL_INDICATOR_DATA_ADDR = 0x7F400 + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0000, bytearray([0x80,0x45,0x45,0x80,0x43,0x65,0x5D,0x51])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0008, bytearray([0x01,0x47,0x47,0x51,0x65,0x45,0x41,0x4F])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0010, bytearray([0x55,0x45,0x80,0x43,0x01,0x57,0x80,0x80])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0018, bytearray([0x45,0x80,0x51,0x41,0x45,0x45,0x80,0x41])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0020, bytearray([0x45,0x41,0x4D,0x67,0x57,0x41,0x55,0x65])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0028, bytearray([0x80,0x4D,0x45,0x55,0x80,0x47,0x4D,0x45])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0030, bytearray([0x80,0x80,0x80,0x43,0x55,0x41,0x80,0x45])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0038, bytearray([0x47,0x57,0x4D,0x41,0x47,0x55,0x47,0x01])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0040, bytearray([0x41,0x4F,0x43,0x47,0x47,0x01,0x45,0x57])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0048, bytearray([0x80,0x45,0x45,0x45,0x45,0x80,0x55,0x45])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0050, bytearray([0x45,0x45,0x80,0x80,0x43,0x80,0x43,0x80])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0058, bytearray([0x07,0x43,0x43,0x80,0x80,0x80,0x80,0x80])) + + +def handle_indicators(rom): + INDICATOR_QUEUE_CODE = 0x86000 + rom.write_bytes(0x022E6, bytearray([0x22, 0x00, 0xE0, 0x10])) # org $00A2E6 : jsl gm14_hijack + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0000, bytearray([0xAD, 0x00, 0x01])) # gm14_hijack: lda $0100 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0003, bytearray([0xC9, 0x14])) # cmp #$14 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0005, bytearray([0xD0, 0x04])) # bne .invalid + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0007, bytearray([0xA5, 0x71])) # lda $71 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0009, bytearray([0xF0, 0x04])) # beq .valid + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x000B, bytearray([0x5C, 0xB1, 0x8A, 0x02])) # .invalid jml $028AB1 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x000F, bytearray([0xC2, 0x30])) # .valid rep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0011, bytearray([0xAF, 0x04, 0xB0, 0x7F])) # lda !score_sprite_add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0015, bytearray([0xF0, 0x03])) # beq .no_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0017, bytearray([0x20, 0xC1, 0xE0])) # jsr add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x001A, bytearray([0xAF, 0x06, 0xB0, 0x7F])) # .no_1_coin lda !score_sprite_add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x001E, bytearray([0xF0, 0x03])) # beq .no_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0020, bytearray([0x20, 0xDF, 0xE0])) # jsr add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0023, bytearray([0xAF, 0x08, 0xB0, 0x7F])) # .no_5_coins lda !score_sprite_add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0027, bytearray([0xF0, 0x03])) # beq .no_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0029, bytearray([0x20, 0xFD, 0xE0])) # jsr add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x002C, bytearray([0xAF, 0x0A, 0xB0, 0x7F])) # .no_10_coins lda !score_sprite_add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0030, bytearray([0xF0, 0x03])) # beq .no_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0032, bytearray([0x20, 0x1B, 0xE1])) # jsr add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0035, bytearray([0xAF, 0x10, 0xB0, 0x7F])) # .no_15_coins lda !score_sprite_add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0039, bytearray([0xF0, 0x03])) # beq .no_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x003B, bytearray([0x20, 0x39, 0xE1])) # jsr add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x003E, bytearray([0xAF, 0x0C, 0xB0, 0x7F])) # .no_1up lda !score_sprite_add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0042, bytearray([0xF0, 0x03])) # beq .no_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0044, bytearray([0x20, 0x57, 0xE1])) # jsr add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0047, bytearray([0xAF, 0x0E, 0xB0, 0x7F])) # .no_yoshi_egg lda !score_sprite_add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x004B, bytearray([0xF0, 0x03])) # beq .no_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x004D, bytearray([0x20, 0xCF, 0xE1])) # jsr add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0050, bytearray([0xE2, 0x30])) # .no_boss_token sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0052, bytearray([0x20, 0xED, 0xE1])) # jsr goal_sanity_check + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0055, bytearray([0x20, 0x5C, 0xE0])) # jsr score_sprite_queue + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0058, bytearray([0x5C, 0xB1, 0x8A, 0x02])) # jml $028AB1 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x005C, bytearray([0xAF, 0x20, 0xB0, 0x7F])) # score_sprite_queue: lda !score_sprite_queue_delay + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0060, bytearray([0xF0, 0x06])) # beq .spawn + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0062, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0063, bytearray([0x8F, 0x20, 0xB0, 0x7F])) # sta !score_sprite_queue_delay + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0067, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0068, bytearray([0xA9, 0x08])) # .spawn lda #$08 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x006A, bytearray([0x8F, 0x20, 0xB0, 0x7F])) # sta !score_sprite_queue_delay + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x006E, bytearray([0xC2, 0x30])) # rep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0070, bytearray([0xAF, 0x02, 0xB0, 0x7F])) # lda !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0074, bytearray([0xCF, 0x00, 0xB0, 0x7F])) # cmp !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0078, bytearray([0xD0, 0x03])) # bne .check_slots + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x007A, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x007C, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x007D, bytearray([0xA0, 0x05, 0x00])) # .check_slots ldy #$0005 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0080, bytearray([0xB9, 0xE1, 0x16])) # ..loop lda !score_sprite_num,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0083, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0086, bytearray([0xF0, 0x06])) # beq .found_free + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0088, bytearray([0x88])) # dey + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0089, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x008B, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x008D, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x008E, bytearray([0xAF, 0x02, 0xB0, 0x7F])) # .found_free lda !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0092, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0093, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0094, bytearray([0x8F, 0x02, 0xB0, 0x7F])) # sta !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0098, bytearray([0xBF, 0x22, 0xB0, 0x7F])) # lda !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x009C, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x009E, bytearray([0x99, 0xE1, 0x16])) # sta !score_sprite_num,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A1, bytearray([0xA5, 0x94])) # lda $94 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A3, bytearray([0x99, 0xED, 0x16])) # sta !score_sprite_x_lo,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A6, bytearray([0xA5, 0x95])) # lda $95 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A8, bytearray([0x99, 0xF3, 0x16])) # sta !score_sprite_x_hi,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00AB, bytearray([0xA5, 0x96])) # lda $96 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00AD, bytearray([0x99, 0xE7, 0x16])) # sta !score_sprite_y_lo,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B0, bytearray([0xA5, 0x97])) # lda $97 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B2, bytearray([0x99, 0xF9, 0x16])) # sta !score_sprite_y_hi,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B5, bytearray([0xA9, 0x30])) # lda #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B7, bytearray([0x99, 0xFF, 0x16])) # sta !score_sprite_timer,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00BA, bytearray([0xAD, 0xF9, 0x13])) # lda $13F9 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00BD, bytearray([0x99, 0x05, 0x17])) # sta !score_sprite_layer,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C0, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C1, bytearray([0xAF, 0x04, 0xB0, 0x7F])) # add_1_coin: lda !score_sprite_add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C5, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C6, bytearray([0x8F, 0x04, 0xB0, 0x7F])) # sta !score_sprite_add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00CA, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00CE, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00CF, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D3, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D4, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D6, bytearray([0xA9, 0x11])) # lda #$11 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D8, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00DC, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00DE, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00DF, bytearray([0xAF, 0x06, 0xB0, 0x7F])) # add_5_coins: lda !score_sprite_add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00E3, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00E4, bytearray([0x8F, 0x06, 0xB0, 0x7F])) # sta !score_sprite_add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00E8, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00EC, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00ED, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F1, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F2, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F4, bytearray([0xA9, 0x12])) # lda #$12 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F6, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00FA, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00FC, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00FD, bytearray([0xAF, 0x08, 0xB0, 0x7F])) # add_10_coins: lda !score_sprite_add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0101, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0102, bytearray([0x8F, 0x08, 0xB0, 0x7F])) # sta !score_sprite_add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0106, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x010A, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x010B, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x010F, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0110, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0112, bytearray([0xA9, 0x13])) # lda #$13 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0114, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0118, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x011A, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x011B, bytearray([0xAF, 0x0A, 0xB0, 0x7F])) # add_15_coins: lda !score_sprite_add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x011F, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0120, bytearray([0x8F, 0x0A, 0xB0, 0x7F])) # sta !score_sprite_add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0124, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0128, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0129, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x012D, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x012E, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0130, bytearray([0xA9, 0x14])) # lda #$14 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0132, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0136, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0138, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0139, bytearray([0xAF, 0x10, 0xB0, 0x7F])) # add_1up: lda !score_sprite_add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x013D, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x013E, bytearray([0x8F, 0x10, 0xB0, 0x7F])) # sta !score_sprite_add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0142, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0146, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0147, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x014B, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x014C, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x014E, bytearray([0xA9, 0x16])) # lda #$16 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0150, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0154, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0156, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0157, bytearray([0xAF, 0x0C, 0xB0, 0x7F])) # add_yoshi_egg: lda !score_sprite_add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x015B, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x015C, bytearray([0x8F, 0x0C, 0xB0, 0x7F])) # sta !score_sprite_add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0160, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0164, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0165, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0169, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x016A, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x016C, bytearray([0xA9, 0x15])) # lda #$15 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x016E, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0172, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0174, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0175, bytearray([0xAF, 0x12, 0xB0, 0x7F])) # add_mushroom: lda !score_sprite_add_mushroom + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0179, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x017A, bytearray([0x8F, 0x12, 0xB0, 0x7F])) # sta !score_sprite_add_mushroom + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x017E, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0182, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0183, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0187, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0188, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x018A, bytearray([0xA9, 0x17])) # lda #$17 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x018C, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0190, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0192, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0193, bytearray([0xAF, 0x14, 0xB0, 0x7F])) # add_flower: lda !score_sprite_add_flower + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0197, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0198, bytearray([0x8F, 0x14, 0xB0, 0x7F])) # sta !score_sprite_add_flower + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x019C, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A0, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A1, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A5, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A6, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A8, bytearray([0xA9, 0x18])) # lda #$18 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01AA, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01AE, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B0, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B1, bytearray([0xAF, 0x16, 0xB0, 0x7F])) # add_feather: lda !score_sprite_add_feather + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B5, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B6, bytearray([0x8F, 0x16, 0xB0, 0x7F])) # sta !score_sprite_add_feather + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01BA, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01BE, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01BF, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C3, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C4, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C6, bytearray([0xA9, 0x19])) # lda #$19 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C8, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01CC, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01CE, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01CF, bytearray([0xAF, 0x0E, 0xB0, 0x7F])) # add_boss_token: lda !score_sprite_add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01D3, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01D4, bytearray([0x8F, 0x0E, 0xB0, 0x7F])) # sta !score_sprite_add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01D8, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01DC, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01DD, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E1, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E2, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E4, bytearray([0xA9, 0x1A])) # lda #$1A + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E6, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01EA, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01EC, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01ED, bytearray([0xAF, 0xA0, 0xBF, 0x03])) # goal_sanity_check: lda $03BFA0 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F1, bytearray([0x29, 0x01])) # and #$01 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F3, bytearray([0x49, 0x01])) # eor #$01 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F5, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F6, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F8, bytearray([0xBF, 0x0C, 0xB0, 0x7F])) # lda !score_sprite_add_yoshi_egg,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01FC, bytearray([0xD0, 0x18])) # bne .return + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01FE, bytearray([0xAF, 0x02, 0xB0, 0x7F])) # .check_queue lda !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0202, bytearray([0xCF, 0x00, 0xB0, 0x7F])) # cmp !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0206, bytearray([0xD0, 0x0E])) # bne .return + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0208, bytearray([0xE2, 0x20])) # .check_count sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x020A, bytearray([0xAF, 0x1E, 0xA0, 0x7F])) # lda !goal_item_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x020E, bytearray([0xDD, 0x24, 0x1F])) # cmp $1F24,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0211, bytearray([0xF0, 0x03])) # beq .return + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0213, bytearray([0x9D, 0x24, 0x1F])) # sta $1F24,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0216, bytearray([0xE2, 0x20])) # .return sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0218, bytearray([0x60])) # rts + + # Add code for indicators when receiving items during levels + INDICATOR_CODE = 0x84000 + rom.write_bytes(0x12DBA, bytearray([0x5C, 0x00, 0xC0, 0x10])) # org $02ADBA : jsl score_sprites + rom.write_bytes(INDICATOR_CODE + 0x0000, bytearray([0xBD, 0xE1, 0x16])) # score_sprites: lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x0003, bytearray([0xF0, 0x2D])) # beq .return + rom.write_bytes(INDICATOR_CODE + 0x0005, bytearray([0x8E, 0xE9, 0x15])) # stx $15E9 + rom.write_bytes(INDICATOR_CODE + 0x0008, bytearray([0xC2, 0x30])) # rep #$30 + rom.write_bytes(INDICATOR_CODE + 0x000A, bytearray([0x29, 0x1F, 0x00])) # and #$001F + rom.write_bytes(INDICATOR_CODE + 0x000D, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(INDICATOR_CODE + 0x000F, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x0010, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x0011, bytearray([0x65, 0x00])) # adc $00 + rom.write_bytes(INDICATOR_CODE + 0x0013, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_CODE + 0x0014, bytearray([0xBF, 0x37, 0xC0, 0x10])) # lda.l .pointers-3,x + rom.write_bytes(INDICATOR_CODE + 0x0018, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(INDICATOR_CODE + 0x001A, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_CODE + 0x001C, bytearray([0xBF, 0x39, 0xC0, 0x10])) # lda.l .pointers-3+2,x + rom.write_bytes(INDICATOR_CODE + 0x0020, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(INDICATOR_CODE + 0x0022, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(INDICATOR_CODE + 0x0024, bytearray([0xAE, 0xE9, 0x15])) # ldx $15E9 + rom.write_bytes(INDICATOR_CODE + 0x0027, bytearray([0x8B])) # phb + rom.write_bytes(INDICATOR_CODE + 0x0028, bytearray([0x48])) # pha + rom.write_bytes(INDICATOR_CODE + 0x0029, bytearray([0xAB])) # plb + rom.write_bytes(INDICATOR_CODE + 0x002A, bytearray([0x4B])) # phk + rom.write_bytes(INDICATOR_CODE + 0x002B, bytearray([0xF4, 0x30, 0xC0])) # pea.w .return_code-1 + rom.write_bytes(INDICATOR_CODE + 0x002E, bytearray([0xDC, 0x00, 0x00])) # jml [$0000] + rom.write_bytes(INDICATOR_CODE + 0x0031, bytearray([0xAB])) # .return_code plb + rom.write_bytes(INDICATOR_CODE + 0x0032, bytearray([0x5C, 0xC5, 0xAD, 0x02])) # .return jml $02ADC5 + rom.write_bytes(INDICATOR_CODE + 0x0036, bytearray([0x9E, 0xE1, 0x16])) # .kill stz !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x0039, bytearray([0x6B])) # rtl + rom.write_bytes(INDICATOR_CODE + 0x003A, bytearray([0x97, 0xC0, 0x10])) # .pointers dl original_score_sprites ; 01 - 10 points + rom.write_bytes(INDICATOR_CODE + 0x003D, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 02 - 20 points + rom.write_bytes(INDICATOR_CODE + 0x0040, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 03 - 40 points + rom.write_bytes(INDICATOR_CODE + 0x0043, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 04 - 80 points + rom.write_bytes(INDICATOR_CODE + 0x0046, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 05 - 100 points + rom.write_bytes(INDICATOR_CODE + 0x0049, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 06 - 200 points + rom.write_bytes(INDICATOR_CODE + 0x004C, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 07 - 400 points + rom.write_bytes(INDICATOR_CODE + 0x004F, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 08 - 800 points + rom.write_bytes(INDICATOR_CODE + 0x0052, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 09 - 1000 points + rom.write_bytes(INDICATOR_CODE + 0x0055, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0A - 2000 points + rom.write_bytes(INDICATOR_CODE + 0x0058, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0B - 4000 points + rom.write_bytes(INDICATOR_CODE + 0x005B, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0C - 8000 points + rom.write_bytes(INDICATOR_CODE + 0x005E, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0D - 1-up + rom.write_bytes(INDICATOR_CODE + 0x0061, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0E - 2-up + rom.write_bytes(INDICATOR_CODE + 0x0064, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0F - 3-up + rom.write_bytes(INDICATOR_CODE + 0x0067, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 10 - 5-up + rom.write_bytes(INDICATOR_CODE + 0x006A, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 11 - 1 coin + rom.write_bytes(INDICATOR_CODE + 0x006D, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 12 - 5 coins + rom.write_bytes(INDICATOR_CODE + 0x0070, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 13 - 10 coins + rom.write_bytes(INDICATOR_CODE + 0x0073, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 14 - 15 coins + rom.write_bytes(INDICATOR_CODE + 0x0076, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 15 - Yoshi Egg + rom.write_bytes(INDICATOR_CODE + 0x0079, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 16 - 1up Mushroom + rom.write_bytes(INDICATOR_CODE + 0x007C, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 17 - Mushroom + rom.write_bytes(INDICATOR_CODE + 0x007F, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 18 - Flower + rom.write_bytes(INDICATOR_CODE + 0x0082, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 19 - Feather + rom.write_bytes(INDICATOR_CODE + 0x0085, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1A - Boss token + rom.write_bytes(INDICATOR_CODE + 0x0088, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1B - + rom.write_bytes(INDICATOR_CODE + 0x008B, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1C - + rom.write_bytes(INDICATOR_CODE + 0x008E, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1D - + rom.write_bytes(INDICATOR_CODE + 0x0091, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1E - + rom.write_bytes(INDICATOR_CODE + 0x0094, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1F - + rom.write_bytes(INDICATOR_CODE + 0x0097, bytearray([0xA9, 0x02])) # original_score_sprites: lda #$02 + rom.write_bytes(INDICATOR_CODE + 0x0099, bytearray([0x48])) # pha + rom.write_bytes(INDICATOR_CODE + 0x009A, bytearray([0xAB])) # plb + rom.write_bytes(INDICATOR_CODE + 0x009B, bytearray([0x4B])) # phk + rom.write_bytes(INDICATOR_CODE + 0x009C, bytearray([0xF4, 0xA5, 0xC0])) # pea.w .jslrtsreturn-1 + rom.write_bytes(INDICATOR_CODE + 0x009F, bytearray([0xF4, 0x88, 0xB8])) # pea.w $B889-1 + rom.write_bytes(INDICATOR_CODE + 0x00A2, bytearray([0x5C, 0xC9, 0xAD, 0x02])) # jml $02ADC9 + rom.write_bytes(INDICATOR_CODE + 0x00A6, bytearray([0x6B])) # .jslrtsreturn rtl + rom.write_bytes(INDICATOR_CODE + 0x00A7, bytearray([0xBD, 0xFF, 0x16])) # icon_score: lda !score_sprite_timer,x + rom.write_bytes(INDICATOR_CODE + 0x00AA, bytearray([0xD0, 0x04])) # bne .active + rom.write_bytes(INDICATOR_CODE + 0x00AC, bytearray([0x9E, 0xE1, 0x16])) # stz !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x00AF, bytearray([0x6B])) # rtl + rom.write_bytes(INDICATOR_CODE + 0x00B0, bytearray([0xDE, 0xFF, 0x16])) # .active dec !score_sprite_timer,x + rom.write_bytes(INDICATOR_CODE + 0x00B3, bytearray([0xC9, 0x30])) # cmp #$30 + rom.write_bytes(INDICATOR_CODE + 0x00B5, bytearray([0xD0, 0x14])) # bne .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x00B7, bytearray([0xBD, 0xE1, 0x16])) # lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x00BA, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x00BB, bytearray([0xE9, 0x11])) # sbc #$11 + rom.write_bytes(INDICATOR_CODE + 0x00BD, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x00BE, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00BF, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_CODE + 0x00C1, bytearray([0xB9, 0x4B, 0xC2])) # lda .reward_ptrs,y + rom.write_bytes(INDICATOR_CODE + 0x00C4, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(INDICATOR_CODE + 0x00C6, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_CODE + 0x00C8, bytearray([0x6C, 0x00, 0x00])) # jmp ($0000) + rom.write_bytes(INDICATOR_CODE + 0x00CB, bytearray([0xBD, 0xFF, 0x16])) # .handle_movement lda !score_sprite_timer,x + rom.write_bytes(INDICATOR_CODE + 0x00CE, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00CF, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00D0, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00D1, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00D2, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00D3, bytearray([0xA5, 0x13])) # lda $13 + rom.write_bytes(INDICATOR_CODE + 0x00D5, bytearray([0x39, 0xF0, 0xC0])) # and .speed,y + rom.write_bytes(INDICATOR_CODE + 0x00D8, bytearray([0xD0, 0x14])) # bne ..skip_update + rom.write_bytes(INDICATOR_CODE + 0x00DA, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x00DD, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00DE, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x00DF, bytearray([0xE5, 0x1C])) # sbc $1C + rom.write_bytes(INDICATOR_CODE + 0x00E1, bytearray([0xC9, 0x04])) # cmp #$04 + rom.write_bytes(INDICATOR_CODE + 0x00E3, bytearray([0x90, 0x09])) # bcc ..skip_update + rom.write_bytes(INDICATOR_CODE + 0x00E5, bytearray([0xDE, 0xE7, 0x16])) # dec !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x00E8, bytearray([0x98])) # tya + rom.write_bytes(INDICATOR_CODE + 0x00E9, bytearray([0xD0, 0x03])) # bne ..skip_update + rom.write_bytes(INDICATOR_CODE + 0x00EB, bytearray([0xDE, 0xF9, 0x16])) # dec !score_sprite_y_hi,x + rom.write_bytes(INDICATOR_CODE + 0x00EE, bytearray([0x80, 0x05])) # ..skip_update bra .gfx + rom.write_bytes(INDICATOR_CODE + 0x00F0, bytearray([0x03, 0x01, 0x00, 0x00])) # .speed db $03,$01,$00,$00 + rom.write_bytes(INDICATOR_CODE + 0x00F4, bytearray([0x6B])) # .return rtl + rom.write_bytes(INDICATOR_CODE + 0x00F5, bytearray([0xBD, 0x05, 0x17])) # .gfx lda !score_sprite_layer,x + rom.write_bytes(INDICATOR_CODE + 0x00F8, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x00F9, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x00FA, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00FB, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_CODE + 0x00FD, bytearray([0xB9, 0x1C, 0x00])) # lda $001C,y + rom.write_bytes(INDICATOR_CODE + 0x0100, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(INDICATOR_CODE + 0x0102, bytearray([0xB9, 0x1A, 0x00])) # lda $001A,y + rom.write_bytes(INDICATOR_CODE + 0x0105, bytearray([0x85, 0x04])) # sta $04 + rom.write_bytes(INDICATOR_CODE + 0x0107, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_CODE + 0x0109, bytearray([0xBD, 0xF3, 0x16])) # lda !score_sprite_x_hi,x + rom.write_bytes(INDICATOR_CODE + 0x010C, bytearray([0xEB])) # xba + rom.write_bytes(INDICATOR_CODE + 0x010D, bytearray([0xBD, 0xED, 0x16])) # lda !score_sprite_x_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0110, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_CODE + 0x0112, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0113, bytearray([0xE5, 0x04])) # sbc $04 + rom.write_bytes(INDICATOR_CODE + 0x0115, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0116, bytearray([0xE9, 0x06, 0x00])) # sbc #$0006 + rom.write_bytes(INDICATOR_CODE + 0x0119, bytearray([0xC9, 0xEA, 0x00])) # cmp #$00EA + rom.write_bytes(INDICATOR_CODE + 0x011C, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_CODE + 0x011E, bytearray([0xB0, 0xD4])) # bcs .return + rom.write_bytes(INDICATOR_CODE + 0x0120, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0123, bytearray([0xC5, 0x02])) # cmp $02 + rom.write_bytes(INDICATOR_CODE + 0x0125, bytearray([0xBD, 0xF9, 0x16])) # lda !score_sprite_y_hi,x + rom.write_bytes(INDICATOR_CODE + 0x0128, bytearray([0xE5, 0x03])) # sbc $03 + rom.write_bytes(INDICATOR_CODE + 0x012A, bytearray([0xD0, 0xC8])) # bne .return + rom.write_bytes(INDICATOR_CODE + 0x012C, bytearray([0xBF, 0x9E, 0xAD, 0x02])) # lda $02AD9E,x + rom.write_bytes(INDICATOR_CODE + 0x0130, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x0131, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0134, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0135, bytearray([0xE5, 0x02])) # sbc $02 + rom.write_bytes(INDICATOR_CODE + 0x0137, bytearray([0x99, 0x01, 0x02])) # sta $0201,y + rom.write_bytes(INDICATOR_CODE + 0x013A, bytearray([0x99, 0x05, 0x02])) # sta $0205,y + rom.write_bytes(INDICATOR_CODE + 0x013D, bytearray([0xBD, 0xED, 0x16])) # lda !score_sprite_x_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0140, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0141, bytearray([0xE5, 0x04])) # sbc $04 + rom.write_bytes(INDICATOR_CODE + 0x0143, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x0144, bytearray([0x69, 0x09])) # adc #$09 + rom.write_bytes(INDICATOR_CODE + 0x0146, bytearray([0x99, 0x00, 0x02])) # sta $0200,y + rom.write_bytes(INDICATOR_CODE + 0x0149, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x014A, bytearray([0x69, 0x05])) # adc #$05 + rom.write_bytes(INDICATOR_CODE + 0x014C, bytearray([0x99, 0x04, 0x02])) # sta $0204,y + rom.write_bytes(INDICATOR_CODE + 0x014F, bytearray([0xDA])) # phx + rom.write_bytes(INDICATOR_CODE + 0x0150, bytearray([0xBD, 0xE1, 0x16])) # lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x0153, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0154, bytearray([0xE9, 0x11])) # sbc #$11 + rom.write_bytes(INDICATOR_CODE + 0x0156, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x0157, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_CODE + 0x0158, bytearray([0xBD, 0x09, 0xC2])) # lda ..num_tile+$00,x + rom.write_bytes(INDICATOR_CODE + 0x015B, bytearray([0x99, 0x02, 0x02])) # sta $0202,y + rom.write_bytes(INDICATOR_CODE + 0x015E, bytearray([0xBD, 0x0A, 0xC2])) # lda ..num_tile+$01,x + rom.write_bytes(INDICATOR_CODE + 0x0161, bytearray([0x99, 0x06, 0x02])) # sta $0206,y + rom.write_bytes(INDICATOR_CODE + 0x0164, bytearray([0xBD, 0x27, 0xC2])) # lda ..num_props+$00,x + rom.write_bytes(INDICATOR_CODE + 0x0167, bytearray([0x99, 0x03, 0x02])) # sta $0203,y + rom.write_bytes(INDICATOR_CODE + 0x016A, bytearray([0xBD, 0x28, 0xC2])) # lda ..num_props+$01,x + rom.write_bytes(INDICATOR_CODE + 0x016D, bytearray([0x99, 0x07, 0x02])) # sta $0207,y + rom.write_bytes(INDICATOR_CODE + 0x0170, bytearray([0xFA])) # plx + rom.write_bytes(INDICATOR_CODE + 0x0171, bytearray([0x98])) # tya + rom.write_bytes(INDICATOR_CODE + 0x0172, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x0173, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x0174, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x0175, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(INDICATOR_CODE + 0x0177, bytearray([0x99, 0x20, 0x04])) # sta $0420,y + rom.write_bytes(INDICATOR_CODE + 0x017A, bytearray([0x99, 0x21, 0x04])) # sta $0421,y + rom.write_bytes(INDICATOR_CODE + 0x017D, bytearray([0xBF, 0x45, 0xC2, 0x10])) # lda.l ..oam_2,x + rom.write_bytes(INDICATOR_CODE + 0x0181, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x0182, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0185, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0186, bytearray([0xE5, 0x02])) # sbc $02 + rom.write_bytes(INDICATOR_CODE + 0x0188, bytearray([0x99, 0x01, 0x02])) # sta $0201,y + rom.write_bytes(INDICATOR_CODE + 0x018B, bytearray([0x99, 0x05, 0x02])) # sta $0205,y + rom.write_bytes(INDICATOR_CODE + 0x018E, bytearray([0xBD, 0xED, 0x16])) # lda !score_sprite_x_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0191, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0192, bytearray([0xE5, 0x04])) # sbc $04 + rom.write_bytes(INDICATOR_CODE + 0x0194, bytearray([0xE9, 0x07])) # sbc #$07 + rom.write_bytes(INDICATOR_CODE + 0x0196, bytearray([0x99, 0x00, 0x02])) # sta $0200,y + rom.write_bytes(INDICATOR_CODE + 0x0199, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x019A, bytearray([0x69, 0x08])) # adc #$08 + rom.write_bytes(INDICATOR_CODE + 0x019C, bytearray([0x99, 0x04, 0x02])) # sta $0204,y + rom.write_bytes(INDICATOR_CODE + 0x019F, bytearray([0xDA])) # phx + rom.write_bytes(INDICATOR_CODE + 0x01A0, bytearray([0xBD, 0xE1, 0x16])) # lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x01A3, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x01A4, bytearray([0xE9, 0x11])) # sbc #$11 + rom.write_bytes(INDICATOR_CODE + 0x01A6, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_CODE + 0x01A7, bytearray([0xBD, 0xCD, 0xC1])) # lda ..icon_tile,x + rom.write_bytes(INDICATOR_CODE + 0x01AA, bytearray([0x99, 0x02, 0x02])) # sta $0202,y + rom.write_bytes(INDICATOR_CODE + 0x01AD, bytearray([0xBD, 0xDC, 0xC1])) # lda ..icon_props,x + rom.write_bytes(INDICATOR_CODE + 0x01B0, bytearray([0x99, 0x03, 0x02])) # sta $0203,y + rom.write_bytes(INDICATOR_CODE + 0x01B3, bytearray([0xBD, 0xFA, 0xC1])) # lda ..plus_props,x + rom.write_bytes(INDICATOR_CODE + 0x01B6, bytearray([0x99, 0x07, 0x02])) # sta $0207,y + rom.write_bytes(INDICATOR_CODE + 0x01B9, bytearray([0xBD, 0xEB, 0xC1])) # lda ..plus_tile,x + rom.write_bytes(INDICATOR_CODE + 0x01BC, bytearray([0x99, 0x06, 0x02])) # sta $0206,y + rom.write_bytes(INDICATOR_CODE + 0x01BF, bytearray([0xFA])) # plx + rom.write_bytes(INDICATOR_CODE + 0x01C0, bytearray([0x98])) # tya + rom.write_bytes(INDICATOR_CODE + 0x01C1, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x01C2, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x01C3, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x01C4, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(INDICATOR_CODE + 0x01C6, bytearray([0x99, 0x20, 0x04])) # sta $0420,y + rom.write_bytes(INDICATOR_CODE + 0x01C9, bytearray([0x99, 0x21, 0x04])) # sta $0421,y + rom.write_bytes(INDICATOR_CODE + 0x01CC, bytearray([0x6B])) # rtl + rom.write_bytes(INDICATOR_CODE + 0x01CD, bytearray([0x1B])) # ..icon_tile db $1B ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x01CE, bytearray([0x1B])) # db $1B ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x01CF, bytearray([0x1B])) # db $1B ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x01D0, bytearray([0x1B])) # db $1B ; 15 coins + rom.write_bytes(INDICATOR_CODE + 0x01D1, bytearray([0x0A])) # db $0A ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01D2, bytearray([0x0B])) # db $0B ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x01D3, bytearray([0x0B])) # db $0B ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x01D4, bytearray([0x7E])) # db $7E ; flower + rom.write_bytes(INDICATOR_CODE + 0x01D5, bytearray([0x7F])) # db $7F ; feather + rom.write_bytes(INDICATOR_CODE + 0x01D6, bytearray([0x38])) # db $38 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x01D7, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01D8, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01D9, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01DA, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01DB, bytearray([0x0B])) # db $0B ; + rom.write_bytes(INDICATOR_CODE + 0x01DC, bytearray([0x34])) # ..icon_props db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01DD, bytearray([0x34])) # db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01DE, bytearray([0x34])) # db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01DF, bytearray([0x34])) # db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01E0, bytearray([0x3A])) # db $3A ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01E1, bytearray([0x3A])) # db $3A ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x01E2, bytearray([0x38])) # db $38 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x01E3, bytearray([0x3A])) # db $3A ; flower + rom.write_bytes(INDICATOR_CODE + 0x01E4, bytearray([0x34])) # db $34 ; feather + rom.write_bytes(INDICATOR_CODE + 0x01E5, bytearray([0x34])) # db $34 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x01E6, bytearray([0x34])) # db $34 ; + rom.write_bytes(INDICATOR_CODE + 0x01E7, bytearray([0x3A])) # db $3A ; + rom.write_bytes(INDICATOR_CODE + 0x01E8, bytearray([0x38])) # db $38 ; + rom.write_bytes(INDICATOR_CODE + 0x01E9, bytearray([0x36])) # db $36 ; + rom.write_bytes(INDICATOR_CODE + 0x01EA, bytearray([0x36])) # db $36 ; + rom.write_bytes(INDICATOR_CODE + 0x01EB, bytearray([0x1A])) # ..plus_tile db $1A ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x01EC, bytearray([0x1A])) # db $1A ; 3 coins + rom.write_bytes(INDICATOR_CODE + 0x01ED, bytearray([0x1A])) # db $1A ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x01EE, bytearray([0x1A])) # db $1A ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x01EF, bytearray([0x1A])) # db $1A ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01F0, bytearray([0x1A])) # db $1A ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x01F1, bytearray([0x1A])) # db $1A ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x01F2, bytearray([0x1A])) # db $1A ; flower + rom.write_bytes(INDICATOR_CODE + 0x01F3, bytearray([0x1A])) # db $1A ; feather + rom.write_bytes(INDICATOR_CODE + 0x01F4, bytearray([0x1A])) # db $1A ; boss token + rom.write_bytes(INDICATOR_CODE + 0x01F5, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F6, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F7, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F8, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F9, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01FA, bytearray([0x32])) # ..plus_props db $32 ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x01FB, bytearray([0x32])) # db $32 ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x01FC, bytearray([0x32])) # db $32 ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x01FD, bytearray([0x32])) # db $32 ; 50 coins + rom.write_bytes(INDICATOR_CODE + 0x01FE, bytearray([0x32])) # db $32 ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01FF, bytearray([0x32])) # db $32 ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x0200, bytearray([0x32])) # db $32 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x0201, bytearray([0x32])) # db $32 ; flower + rom.write_bytes(INDICATOR_CODE + 0x0202, bytearray([0x32])) # db $32 ; feather + rom.write_bytes(INDICATOR_CODE + 0x0203, bytearray([0x32])) # db $32 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x0204, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0205, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0206, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0207, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0208, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0209, bytearray([0x4B, 0x69])) # ..num_tile db $4B,$69 ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x020B, bytearray([0x5B, 0x69])) # db $5B,$69 ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x020D, bytearray([0x4B, 0x4A])) # db $4B,$4A ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x020F, bytearray([0x5B, 0x4A])) # db $4B,$5B ; 50 coins + rom.write_bytes(INDICATOR_CODE + 0x0211, bytearray([0x4B, 0x69])) # db $4B,$69 ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x0213, bytearray([0x4B, 0x69])) # db $4B,$69 ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x0215, bytearray([0x4B, 0x69])) # db $4B,$69 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x0217, bytearray([0x4B, 0x69])) # db $4B,$69 ; flower + rom.write_bytes(INDICATOR_CODE + 0x0219, bytearray([0x4B, 0x69])) # db $4B,$69 ; feather + rom.write_bytes(INDICATOR_CODE + 0x021B, bytearray([0x4B, 0x69])) # db $4B,$69 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x021D, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x021F, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0221, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0223, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0225, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0227, bytearray([0x34, 0x34])) # ..num_props db $34,$34 ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x0229, bytearray([0x34, 0x34])) # db $34,$34 ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x022B, bytearray([0x34, 0x34])) # db $34,$34 ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x022D, bytearray([0x34, 0x34])) # db $34,$34 ; 50 coins + rom.write_bytes(INDICATOR_CODE + 0x022F, bytearray([0x34, 0x34])) # db $34,$34 ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x0231, bytearray([0x34, 0x34])) # db $34,$34 ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x0233, bytearray([0x34, 0x34])) # db $34,$34 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x0235, bytearray([0x34, 0x34])) # db $34,$34 ; flower + rom.write_bytes(INDICATOR_CODE + 0x0237, bytearray([0x34, 0x34])) # db $34,$34 ; feather + rom.write_bytes(INDICATOR_CODE + 0x0239, bytearray([0x34, 0x34])) # db $34,$34 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x023B, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x023D, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x023F, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x0241, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x0243, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x0245, bytearray([0x50, 0x58, 0x60, 0x68, 0x70, 0x78]))# ..oam_2 db $50,$58,$60,$68,$70,$78 + rom.write_bytes(INDICATOR_CODE + 0x024B, bytearray([0x69, 0xC2])) # .reward_ptrs dw .one_coin + rom.write_bytes(INDICATOR_CODE + 0x024D, bytearray([0x6D, 0xC2])) # dw .five_coins + rom.write_bytes(INDICATOR_CODE + 0x024F, bytearray([0x71, 0xC2])) # dw .ten_coins + rom.write_bytes(INDICATOR_CODE + 0x0251, bytearray([0x75, 0xC2])) # dw .fifty_coins + rom.write_bytes(INDICATOR_CODE + 0x0253, bytearray([0x8A, 0xC2])) # dw .yoshi_egg + rom.write_bytes(INDICATOR_CODE + 0x0255, bytearray([0xA7, 0xC2])) # dw .green_mushroom + rom.write_bytes(INDICATOR_CODE + 0x0257, bytearray([0xAD, 0xC2])) # dw .mushroom + rom.write_bytes(INDICATOR_CODE + 0x0259, bytearray([0xAF, 0xC2])) # dw .flower + rom.write_bytes(INDICATOR_CODE + 0x025B, bytearray([0xB1, 0xC2])) # dw .shared_item + rom.write_bytes(INDICATOR_CODE + 0x025D, bytearray([0x9C, 0xC2])) # dw .boss_token + rom.write_bytes(INDICATOR_CODE + 0x025F, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0261, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0263, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0265, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0267, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0269, bytearray([0xA9, 0x01])) # .one_coin lda #$01 + rom.write_bytes(INDICATOR_CODE + 0x026B, bytearray([0x80, 0x0A])) # bra .shared_coins + rom.write_bytes(INDICATOR_CODE + 0x026D, bytearray([0xA9, 0x05])) # .five_coins lda #$05 + rom.write_bytes(INDICATOR_CODE + 0x026F, bytearray([0x80, 0x06])) # bra .shared_coins + rom.write_bytes(INDICATOR_CODE + 0x0271, bytearray([0xA9, 0x0A])) # .ten_coins lda #$0A + rom.write_bytes(INDICATOR_CODE + 0x0273, bytearray([0x80, 0x02])) # bra .shared_coins + rom.write_bytes(INDICATOR_CODE + 0x0275, bytearray([0xA9, 0x32])) # .fifty_coins lda #$32 + rom.write_bytes(INDICATOR_CODE + 0x0277, bytearray([0x18])) # .shared_coins clc + rom.write_bytes(INDICATOR_CODE + 0x0278, bytearray([0x6D, 0xCC, 0x13])) # adc $13CC + rom.write_bytes(INDICATOR_CODE + 0x027B, bytearray([0x90, 0x02])) # bcc + + rom.write_bytes(INDICATOR_CODE + 0x027D, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(INDICATOR_CODE + 0x027F, bytearray([0x8D, 0xCC, 0x13])) # + sta $13CC + rom.write_bytes(INDICATOR_CODE + 0x0282, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(INDICATOR_CODE + 0x0284, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x0287, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x028A, bytearray([0xAD, 0x24, 0x1F])) # .yoshi_egg lda $1F24 + rom.write_bytes(INDICATOR_CODE + 0x028D, bytearray([0xC9, 0xFF])) # cmp #$FF + rom.write_bytes(INDICATOR_CODE + 0x028F, bytearray([0xF0, 0x03])) # beq ..nope + rom.write_bytes(INDICATOR_CODE + 0x0291, bytearray([0xEE, 0x24, 0x1F])) # inc $1F24 + rom.write_bytes(INDICATOR_CODE + 0x0294, bytearray([0xA9, 0x1F])) # ..nope lda #$1F + rom.write_bytes(INDICATOR_CODE + 0x0296, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x0299, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x029C, bytearray([0xEE, 0x26, 0x1F])) # .boss_token inc $1F26 + rom.write_bytes(INDICATOR_CODE + 0x029F, bytearray([0xA9, 0x09])) # lda #$09 + rom.write_bytes(INDICATOR_CODE + 0x02A1, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x02A4, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x02A7, bytearray([0xEE, 0xE4, 0x18])) # .green_mushroom inc $18E4 + rom.write_bytes(INDICATOR_CODE + 0x02AA, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x02AD, bytearray([0x80, 0x02])) # .mushroom bra .shared_item + rom.write_bytes(INDICATOR_CODE + 0x02AF, bytearray([0x80, 0x00])) # .flower bra .shared_item + rom.write_bytes(INDICATOR_CODE + 0x02B1, bytearray([0xA9, 0x0B])) # .shared_item lda #$0B + rom.write_bytes(INDICATOR_CODE + 0x02B3, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x02B6, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + +def handle_traps(rom): + TRAPS_CODE = 0x86C00 + rom.write_bytes(0x022D8, bytearray([0x22, 0x00, 0xEC, 0x10])) # org $00A2D8 : jsl score_sprites + rom.write_bytes(TRAPS_CODE + 0x0000, bytearray([0xAD, 0x00, 0x01])) # handle_traps: lda $0100 + rom.write_bytes(TRAPS_CODE + 0x0003, bytearray([0xC9, 0x14])) # cmp #$14 + rom.write_bytes(TRAPS_CODE + 0x0005, bytearray([0xD0, 0x04])) # bne .invalid + rom.write_bytes(TRAPS_CODE + 0x0007, bytearray([0xA5, 0x71])) # lda $71 + rom.write_bytes(TRAPS_CODE + 0x0009, bytearray([0xF0, 0x09])) # beq .valid + rom.write_bytes(TRAPS_CODE + 0x000B, bytearray([0xA9, 0xFF])) # .invalid lda #$FF + rom.write_bytes(TRAPS_CODE + 0x000D, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x0010, bytearray([0x5C, 0xBD, 0xE2, 0x00])) # jml $00E2BD + rom.write_bytes(TRAPS_CODE + 0x0014, bytearray([0xAD, 0xB4, 0x18])) # .valid lda !reverse_controls_trap + rom.write_bytes(TRAPS_CODE + 0x0017, bytearray([0xF0, 0x03])) # beq .no_reverse_controls + rom.write_bytes(TRAPS_CODE + 0x0019, bytearray([0x20, 0x2B, 0xEC])) # jsr reverse_controls_trap + rom.write_bytes(TRAPS_CODE + 0x001C, bytearray([0xAD, 0xB7, 0x18])) # .no_reverse_controls lda !thwimp_trap + rom.write_bytes(TRAPS_CODE + 0x001F, bytearray([0xF0, 0x03])) # beq .no_thwimp + rom.write_bytes(TRAPS_CODE + 0x0021, bytearray([0x20, 0x86, 0xEC])) # jsr spawn_thwimp + rom.write_bytes(TRAPS_CODE + 0x0024, bytearray([0x20, 0xCB, 0xEC])) # .no_thwimp jsr handle_thwimp + rom.write_bytes(TRAPS_CODE + 0x0027, bytearray([0x5C, 0xBD, 0xE2, 0x00])) # jml $00E2BD + rom.write_bytes(TRAPS_CODE + 0x002B, bytearray([0xA5, 0x15])) # reverse_controls_trap: lda $15 + rom.write_bytes(TRAPS_CODE + 0x002D, bytearray([0x89, 0x03])) # bit #$03 + rom.write_bytes(TRAPS_CODE + 0x002F, bytearray([0xF0, 0x04])) # beq ..no_swap_hold + rom.write_bytes(TRAPS_CODE + 0x0031, bytearray([0x49, 0x03])) # eor #$03 + rom.write_bytes(TRAPS_CODE + 0x0033, bytearray([0x85, 0x15])) # sta $15 + rom.write_bytes(TRAPS_CODE + 0x0035, bytearray([0xA5, 0x16])) # ..no_swap_hold lda $16 + rom.write_bytes(TRAPS_CODE + 0x0037, bytearray([0x89, 0x03])) # bit #$03 + rom.write_bytes(TRAPS_CODE + 0x0039, bytearray([0xF0, 0x04])) # beq ..no_swap_press + rom.write_bytes(TRAPS_CODE + 0x003B, bytearray([0x49, 0x03])) # eor #$03 + rom.write_bytes(TRAPS_CODE + 0x003D, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x003F, bytearray([0xA5, 0x15])) # .swap_up_and_down lda $15 + rom.write_bytes(TRAPS_CODE + 0x0041, bytearray([0x89, 0x0C])) # bit #$0C + rom.write_bytes(TRAPS_CODE + 0x0043, bytearray([0xF0, 0x04])) # beq .no_swap_hold + rom.write_bytes(TRAPS_CODE + 0x0045, bytearray([0x49, 0x0C])) # eor #$0C + rom.write_bytes(TRAPS_CODE + 0x0047, bytearray([0x85, 0x15])) # sta $15 + rom.write_bytes(TRAPS_CODE + 0x0049, bytearray([0xA5, 0x16])) # .no_swap_hold lda $16 + rom.write_bytes(TRAPS_CODE + 0x004B, bytearray([0x89, 0x0C])) # bit #$0C + rom.write_bytes(TRAPS_CODE + 0x004D, bytearray([0xF0, 0x04])) # beq ..no_swap_press + rom.write_bytes(TRAPS_CODE + 0x004F, bytearray([0x49, 0x0C])) # eor #$0C + rom.write_bytes(TRAPS_CODE + 0x0051, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x0053, bytearray([0xA5, 0x16])) # .swap_a_and_b lda $16 + rom.write_bytes(TRAPS_CODE + 0x0055, bytearray([0x10, 0x0C])) # bpl ..no_swap_b + rom.write_bytes(TRAPS_CODE + 0x0057, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x0059, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x005B, bytearray([0xA5, 0x18])) # lda $18 + rom.write_bytes(TRAPS_CODE + 0x005D, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x005F, bytearray([0x85, 0x18])) # sta $18 + rom.write_bytes(TRAPS_CODE + 0x0061, bytearray([0x80, 0x0E])) # bra .swap_l_and_r + rom.write_bytes(TRAPS_CODE + 0x0063, bytearray([0xA5, 0x18])) # ..no_swap_b lda $18 + rom.write_bytes(TRAPS_CODE + 0x0065, bytearray([0x10, 0x0A])) # bpl .swap_l_and_r + rom.write_bytes(TRAPS_CODE + 0x0067, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x0069, bytearray([0x85, 0x18])) # sta $18 + rom.write_bytes(TRAPS_CODE + 0x006B, bytearray([0xA5, 0x16])) # lda $16 + rom.write_bytes(TRAPS_CODE + 0x006D, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x006F, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x0071, bytearray([0xA5, 0x17])) # .swap_l_and_r lda $17 + rom.write_bytes(TRAPS_CODE + 0x0073, bytearray([0x89, 0x30])) # bit #$30 + rom.write_bytes(TRAPS_CODE + 0x0075, bytearray([0xF0, 0x04])) # beq ..no_swap_hold + rom.write_bytes(TRAPS_CODE + 0x0077, bytearray([0x49, 0x30])) # eor #$30 + rom.write_bytes(TRAPS_CODE + 0x0079, bytearray([0x85, 0x17])) # sta $17 + rom.write_bytes(TRAPS_CODE + 0x007B, bytearray([0xA5, 0x18])) # ..no_swap_hold lda $18 + rom.write_bytes(TRAPS_CODE + 0x007D, bytearray([0x89, 0x30])) # bit #$30 + rom.write_bytes(TRAPS_CODE + 0x007F, bytearray([0xF0, 0x04])) # beq ..no_swap_press + rom.write_bytes(TRAPS_CODE + 0x0081, bytearray([0x49, 0x30])) # eor #$30 + rom.write_bytes(TRAPS_CODE + 0x0083, bytearray([0x85, 0x18])) # sta $18 + rom.write_bytes(TRAPS_CODE + 0x0085, bytearray([0x60])) # ..no_swap_press rts + rom.write_bytes(TRAPS_CODE + 0x0086, bytearray([0xAE, 0x3C, 0x0F])) # spawn_thwimp: ldx !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x0089, bytearray([0x10, 0x06])) # bpl .return + rom.write_bytes(TRAPS_CODE + 0x008B, bytearray([0x22, 0xE4, 0xA9, 0x02])) # jsl $02A9E4 + rom.write_bytes(TRAPS_CODE + 0x008F, bytearray([0x10, 0x01])) # bpl .found + rom.write_bytes(TRAPS_CODE + 0x0091, bytearray([0x60])) # .return rts + rom.write_bytes(TRAPS_CODE + 0x0092, bytearray([0xBB])) # .found tyx + rom.write_bytes(TRAPS_CODE + 0x0093, bytearray([0x9C, 0xB7, 0x18])) # stz !thwimp_trap + rom.write_bytes(TRAPS_CODE + 0x0096, bytearray([0xA9, 0x10])) # lda #$10 + rom.write_bytes(TRAPS_CODE + 0x0098, bytearray([0x8D, 0xF9, 0x1D])) # sta $1DF9 + rom.write_bytes(TRAPS_CODE + 0x009B, bytearray([0xA9, 0x27])) # lda #$27 + rom.write_bytes(TRAPS_CODE + 0x009D, bytearray([0x95, 0x9E])) # sta $9E,x + rom.write_bytes(TRAPS_CODE + 0x009F, bytearray([0xA9, 0x08])) # lda #$08 + rom.write_bytes(TRAPS_CODE + 0x00A1, bytearray([0x9D, 0xC8, 0x14])) # sta $14C8,x + rom.write_bytes(TRAPS_CODE + 0x00A4, bytearray([0x22, 0xD2, 0xF7, 0x07])) # jsl $07F7D2 + rom.write_bytes(TRAPS_CODE + 0x00A8, bytearray([0xA5, 0x94])) # lda $94 + rom.write_bytes(TRAPS_CODE + 0x00AA, bytearray([0x95, 0xE4])) # sta $E4,x + rom.write_bytes(TRAPS_CODE + 0x00AC, bytearray([0xA5, 0x95])) # lda $95 + rom.write_bytes(TRAPS_CODE + 0x00AE, bytearray([0x9D, 0xE0, 0x14])) # sta $14E0,x + rom.write_bytes(TRAPS_CODE + 0x00B1, bytearray([0xA5, 0x1C])) # lda $1C + rom.write_bytes(TRAPS_CODE + 0x00B3, bytearray([0x38])) # sec + rom.write_bytes(TRAPS_CODE + 0x00B4, bytearray([0xE9, 0x0F])) # sbc #$0F + rom.write_bytes(TRAPS_CODE + 0x00B6, bytearray([0x95, 0xD8])) # sta $D8,x + rom.write_bytes(TRAPS_CODE + 0x00B8, bytearray([0xA5, 0x1D])) # lda $1D + rom.write_bytes(TRAPS_CODE + 0x00BA, bytearray([0xE9, 0x00])) # sbc #$00 + rom.write_bytes(TRAPS_CODE + 0x00BC, bytearray([0x9D, 0xD4, 0x14])) # sta $14D4,x + rom.write_bytes(TRAPS_CODE + 0x00BF, bytearray([0xBD, 0x86, 0x16])) # lda $1686,x + rom.write_bytes(TRAPS_CODE + 0x00C2, bytearray([0x09, 0x80])) # ora #$80 + rom.write_bytes(TRAPS_CODE + 0x00C4, bytearray([0x9D, 0x86, 0x16])) # sta $1686,x + rom.write_bytes(TRAPS_CODE + 0x00C7, bytearray([0x8E, 0x3C, 0x0F])) # stx !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x00CA, bytearray([0x60])) # rts + rom.write_bytes(TRAPS_CODE + 0x00CB, bytearray([0xAE, 0x3C, 0x0F])) # handle_thwimp: ldx !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x00CE, bytearray([0x30, 0x1C])) # bmi .return + rom.write_bytes(TRAPS_CODE + 0x00D0, bytearray([0xBD, 0xD4, 0x14])) # lda $14D4,x + rom.write_bytes(TRAPS_CODE + 0x00D3, bytearray([0xEB])) # xba + rom.write_bytes(TRAPS_CODE + 0x00D4, bytearray([0xB5, 0xD8])) # lda $D8,x + rom.write_bytes(TRAPS_CODE + 0x00D6, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(TRAPS_CODE + 0x00D8, bytearray([0x38])) # sec + rom.write_bytes(TRAPS_CODE + 0x00D9, bytearray([0xE5, 0x96])) # sbc $96 + rom.write_bytes(TRAPS_CODE + 0x00DB, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(TRAPS_CODE + 0x00DD, bytearray([0x30, 0x0D])) # bmi .return + rom.write_bytes(TRAPS_CODE + 0x00DF, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(TRAPS_CODE + 0x00E1, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x00E4, bytearray([0xBD, 0x86, 0x16])) # lda $1686,x + rom.write_bytes(TRAPS_CODE + 0x00E7, bytearray([0x29, 0x7F])) # and #$7F + rom.write_bytes(TRAPS_CODE + 0x00E9, bytearray([0x9D, 0x86, 0x16])) # sta $1686,x + rom.write_bytes(TRAPS_CODE + 0x00EC, bytearray([0x60])) # .return rts + + + +def read_graphics_file(filename): + return pkgutil.get_data(__name__, f"data/graphics/{filename}") + +def handle_uncompressed_player_gfx(rom): + # Decompresses and moves into a expanded region the player, yoshi and animated graphics + # This should make swapping the graphics a lot easier. + # Maybe I should look into making a 32x32 version at some point... + # It also moves some 8x8 tiles in GFX00, thus making some free space for indicators and other stuff + # in VRAM during gameplay, will come super handy later. + # + # FOR FUTURE REFERENCE + # Player graphics are now located at 0xE0000 + # Player auxiliary tiles are now located at 0xE6000 + # Yoshi graphics are now located at 0xE8800 + SMW_COMPRESSED_PLAYER_GFX = 0x40000 + SMW_COMPRESSED_ANIMATED_GFX = 0x43FC0 + SMW_COMPRESSED_GFX_00 = 0x459F9 + SMW_COMPRESSED_GFX_10 = 0x4EF1E + SMW_COMPRESSED_GFX_28 = 0x5C06C + compressed_player_gfx = rom.read_bytes(SMW_COMPRESSED_PLAYER_GFX, 0x3FC0) + compressed_animated_gfx = rom.read_bytes(SMW_COMPRESSED_ANIMATED_GFX, 0x1A39) + compressed_gfx_00 = rom.read_bytes(SMW_COMPRESSED_GFX_00, 0x0838) + compressed_gfx_10 = rom.read_bytes(SMW_COMPRESSED_GFX_10, 0x0891) + compressed_gfx_28 = rom.read_bytes(SMW_COMPRESSED_GFX_28, 0x0637) + decompressed_player_gfx = decompress_gfx(compressed_player_gfx) + decompressed_animated_gfx = convert_3bpp(decompress_gfx(compressed_animated_gfx)) + decompressed_gfx_00 = convert_3bpp(decompress_gfx(compressed_gfx_00)) + decompressed_gfx_10 = convert_3bpp(decompress_gfx(compressed_gfx_10)) + decompressed_gfx_28 = decompress_gfx(compressed_gfx_28) + + # Copy berry tiles + order = [0x26C, 0x26D, 0x26E, 0x26F, + 0x27C, 0x27D, 0x27E, 0x27F, + 0x2E0, 0x2E1, 0x2E2, 0x2E3, + 0x2E4, 0x2E5, 0x2E6, 0x2E7] + decompressed_animated_gfx += copy_gfx_tiles(decompressed_player_gfx, order, [5, 32]) + + # Copy Mario's auxiliary tiles + order = [0x80, 0x91, 0x81, 0x90, 0x82, 0x83] + decompressed_gfx_00 += copy_gfx_tiles(decompressed_player_gfx, order, [5, 32]) + order = [0x69, 0x69, 0x0C, 0x69, 0x1A, 0x1B, 0x0D, 0x69, 0x22, 0x23, 0x32, 0x33, 0x0A, 0x0B, 0x20, 0x21, + 0x30, 0x31, 0x7E, 0x69, 0x80, 0x4A, 0x81, 0x5B, 0x82, 0x4B, 0x83, 0x5A, 0x84, 0x69, 0x85, 0x85] + player_small_tiles = copy_gfx_tiles(decompressed_gfx_00, order, [5, 32]) + + # Copy OW mario tiles + order = [0x06, 0x07, 0x16, 0x17, + 0x08, 0x09, 0x18, 0x19, + 0x0A, 0x0B, 0x1A, 0x1B, + 0x0C, 0x0D, 0x1C, 0x1D, + 0x0E, 0x0F, 0x1E, 0x1F, + 0x20, 0x21, 0x30, 0x31, + 0x24, 0x25, 0x34, 0x35, + 0x46, 0x47, 0x56, 0x57, + 0x64, 0x65, 0x74, 0x75, + 0x66, 0x67, 0x76, 0x77, + 0x2E, 0x2F, 0x3E, 0x3F, + 0x40, 0x41, 0x50, 0x51, + 0x42, 0x43, 0x52, 0x53] + player_map_tiles = copy_gfx_tiles(decompressed_gfx_10, order, [5, 32]) + + # Copy HUD mario tiles + order = [0x30, 0x31, 0x32, 0x33, 0x34] + player_name_tiles = copy_gfx_tiles(decompressed_gfx_28, order, [4, 16]) + + rom.write_bytes(0xE0000, decompressed_player_gfx) + rom.write_bytes(0xE8000, decompressed_animated_gfx) + rom.write_bytes(0xE6000, player_small_tiles) + rom.write_bytes(0xE6400, player_map_tiles) + rom.write_bytes(0xE6C00, player_name_tiles) + + # Skip Player & Animated tile decompression + rom.write_bytes(0x03888, bytearray([0x60])) # RTS + + # Edit Mario DMA routine + MARIO_GFX_DMA_ADDR = 0x02300 + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x0000, bytearray([0xA2, 0x04])) # LDX #$04 + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x0002, bytearray([0x22, 0x00, 0xF0, 0x10])) # JSL $10F000 ; upload_score_sprite_gfx + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x0006, bytearray([0x22, 0x00, 0xF8, 0x0F])) # JSL $0FF800 ; player_code + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x000A, bytearray([0x60])) # RTS + + PLAYER_UPLOAD_ADDR = 0x7F800 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0000, bytearray([0xC2, 0x20])) # player_code: rep #$20 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0002, bytearray([0xAC, 0x84, 0x0D])) # ldy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0005, bytearray([0xD0, 0x03])) # bne .upload_player_palette + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0007, bytearray([0x4C, 0xD2, 0xF8])) # jmp .skip_everything + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x000A, bytearray([0xA0, 0x86])) # .upload_player_palette ldy #$86 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x000C, bytearray([0x8C, 0x21, 0x21])) # sty $2121 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x000F, bytearray([0xA9, 0x00, 0x22])) # lda #$2200 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0012, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0015, bytearray([0xA8])) # tay + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0016, bytearray([0xAD, 0x82, 0x0D])) # lda $0D82 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0019, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x001C, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x001F, bytearray([0xA9, 0x14, 0x00])) # lda #$0014 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0022, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0025, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0028, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x002A, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x002D, bytearray([0xA9, 0x01, 0x18])) # lda #$1801 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0030, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0033, bytearray([0xA0, 0x1C])) # ldy.b #player_gfx>>16 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0035, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0038, bytearray([0xA9, 0x00, 0x60])) # .upload_player_top lda #$6000 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x003B, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x003E, bytearray([0xA8])) # tay + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x003F, bytearray([0xB9, 0x85, 0x0D])) # - lda $0D85,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0042, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0045, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0048, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x004B, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x004E, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x004F, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0050, bytearray([0xC0, 0x06])) # cpy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0052, bytearray([0xD0, 0xEB])) # bne - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0054, bytearray([0xA9, 0x00, 0x61])) # .upload_player_bottom lda #$6100 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0057, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x005A, bytearray([0xA8])) # tay + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x005B, bytearray([0xB9, 0x8F, 0x0D])) # - lda $0D8F,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x005E, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0061, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0064, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0067, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006A, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006B, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006C, bytearray([0xC0, 0x06])) # cpy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006E, bytearray([0xD0, 0xEB])) # bne - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0070, bytearray([0xAC, 0x9B, 0x0D])) # .upload_player_extended ldy $0D9B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0073, bytearray([0xC0, 0x02])) # cpy #$02 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0075, bytearray([0xF0, 0x5B])) # beq .skip_everything + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0077, bytearray([0xA9, 0xC0, 0x60])) # lda #$60C0 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x007A, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x007D, bytearray([0xAD, 0x99, 0x0D])) # lda $0D99 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0080, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0083, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0086, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0089, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x008C, bytearray([0xA0, 0x1D])) # .upload_misc_tiles ldy.b #animated_tiles>>16 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x008E, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0091, bytearray([0xA9, 0x60, 0x60])) # lda #$6060 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0094, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0097, bytearray([0xA0, 0x06])) # ldy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0099, bytearray([0xCC, 0x84, 0x0D])) # cpy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x009C, bytearray([0xB0, 0x34])) # bcs .skip_everything + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x009E, bytearray([0xB9, 0x85, 0x0D])) # - lda $0D85,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00A1, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00A4, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00A7, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AA, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AD, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AE, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AF, bytearray([0xCC, 0x84, 0x0D])) # cpy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00B2, bytearray([0x90, 0xEA])) # bcc - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00B4, bytearray([0xA9, 0x60, 0x61])) # lda #$6160 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00B7, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00BA, bytearray([0xA0, 0x06])) # ldy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00BC, bytearray([0xB9, 0x8F, 0x0D])) # - lda $0D8F,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00BF, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00C2, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00C5, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00C8, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00CB, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00CC, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00CD, bytearray([0xCC, 0x84, 0x0D])) # cpy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00D0, bytearray([0x90, 0xEA])) # bcc - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00D2, bytearray([0xE2, 0x20])) # .skip_everything sep #$20 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00D4, bytearray([0x6B])) # rtl + + # Obtain data for new 8x8 tile + CHAR_TILE_CODE_ADDR = 0x05FE2 + rom.write_bytes(0x063B1, bytearray([0x20, 0xE2, 0xDF])) # jsr $DFE2 + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0000, bytearray([0xB9, 0x1A, 0xDF])) # lda $DF1A,y + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0003, bytearray([0x10, 0x06])) # bpl $06 + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0005, bytearray([0x29, 0x7F])) # and #$7F + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0007, bytearray([0x85, 0x0D])) # sta $0D + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0009, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x000B, bytearray([0x60])) # rts + + rom.write_bytes(0x0640D, bytearray([0x20, 0xEE, 0xDF])) # jsr $DFEE + CAPE_TILE_CODE_ADDR = 0x05FEE + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0000, bytearray([0xA5, 0x0D])) # lda $0D + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0002, bytearray([0xE0, 0x2B])) # cpx #$2B + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0004, bytearray([0x90, 0x07])) # bcc $07 + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0006, bytearray([0xE0, 0x40])) # cpx #$40 + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0008, bytearray([0xB0, 0x03])) # bcs $03 + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x000A, bytearray([0xBD, 0xD7, 0xE1])) # lda $E1D7,x + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x000D, bytearray([0x60])) # rts + + # Edit Mario's 8x8 tile data + MARIO_AUX_TILE_DATA_ADDR = 0x05F1A + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0000, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0008, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0010, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0018, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0020, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0028, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0030, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0038, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0040, bytearray([0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0048, bytearray([0x00,0x00,0x82,0x82,0x82,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0050, bytearray([0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x86])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0058, bytearray([0x86,0x86,0x00,0x00,0x88,0x88,0x8A,0x8A])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0060, bytearray([0x8C,0x8C,0x00,0x00,0x90,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0068, bytearray([0x00,0x8E,0x00,0x00,0x00,0x00,0x92,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0070, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0078, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0080, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x82])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0088, bytearray([0x82,0x82,0x00,0x00,0x00,0x00,0x00,0x84])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0090, bytearray([0x00,0x00,0x00,0x00,0x86,0x86,0x86,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0098, bytearray([0x00,0x88,0x88,0x8A,0x8A,0x8C,0x8C,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00A0, bytearray([0x00,0x90,0x00,0x00,0x00,0x00,0x8E,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00A8, bytearray([0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00B0, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00B8, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + + MARIO_AUX_TILE_OFFSETS_ADDR = 0x05FDA # ends at $00E00C + rom.write_bytes(MARIO_AUX_TILE_OFFSETS_ADDR + 0x0000, bytearray([0x00,0x02,0x80,0x80,0x00,0x02,0x0C,0x0D])) + rom.write_bytes(MARIO_AUX_TILE_OFFSETS_ADDR + 0x0022, bytearray([0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x02])) + rom.write_bytes(MARIO_AUX_TILE_OFFSETS_ADDR + 0x002A, bytearray([0x02,0x80,0x04,0x0C,0x0D,0xFF,0xFF,0xFF])) + + MARIO_AUX_CAPE_TILE_DATA_ADDR = 0x061FF + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x0000, bytearray([0x00,0x8C,0x14,0x14,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x0005, bytearray([0x00,0xCA,0x16,0x16,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x000A, bytearray([0x00,0x8E,0x18,0x18,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x000F, bytearray([0x00,0xEB,0x1A,0x1A,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x0014, bytearray([0x04,0xED,0x1C,0x1C])) + + # Edit player data offsets + rom.write_bytes(0x07649, bytearray([0x69, 0x00, 0x80])) # adc #$8000 + rom.write_bytes(0x07667, bytearray([0x69, 0x00, 0x80])) # adc #$8000 + rom.write_bytes(0x0767C, bytearray([0x69, 0x00, 0x80])) # adc #$8000 + rom.write_bytes(0x07691, bytearray([0x69, 0x00, 0xE0])) # adc #$E000 + + # Fix berries + FIX_BERRIES_ADDR = 0x7FFE0 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0000, bytearray([0xA0, 0x1D])) # fix_berries: ldy.b #animated_tiles>>16 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0002, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0005, bytearray([0xAD, 0x76, 0x0D])) # lda $0D76 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0008, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(FIX_BERRIES_ADDR + 0x000B, bytearray([0x6B])) # rtl + + # Fix animated graphics + rom.write_bytes(0x018D1, bytearray([0x1D])) # db $1D + rom.write_bytes(0x0239E, bytearray([0x1D])) # db $1D + + rom.write_bytes(0x023F0, bytearray([0x22, 0xE0, 0xFF, 0x0F])) # jsl $0FFFE0 + rom.write_bytes(0x023F4, bytearray([0xEA])) # nop + rom.write_bytes(0x023F5, bytearray([0xEA])) # nop + + rom.write_bytes(0x0E1A8, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + rom.write_bytes(0x0EEB4, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + rom.write_bytes(0x0EEC9, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + rom.write_bytes(0x16A3E, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + + ANIMATED_TILE_DATA_ADDR = 0x2B999 + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0000, bytearray([0x00,0x98,0x00,0x9A,0x00,0x9C,0x00,0x9E])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0008, bytearray([0x80,0x98,0x80,0x9A,0x80,0x9C,0x80,0x9E])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0010, bytearray([0x00,0x99,0x00,0x99,0x00,0x99,0x00,0x99])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0018, bytearray([0x80,0xA0,0x80,0xA2,0x80,0xA4,0x80,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0020, bytearray([0x00,0x99,0x00,0x9B,0x00,0x9D,0x00,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0028, bytearray([0x00,0xB0,0x80,0xB0,0x00,0xB1,0x80,0xB1])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0030, bytearray([0x20,0xAF,0x20,0xAF,0x20,0xAF,0x20,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0038, bytearray([0x20,0xAF,0x20,0xAF,0x20,0xAF,0x20,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0040, bytearray([0x80,0x96,0x80,0x96,0x80,0x96,0x80,0x96])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0048, bytearray([0x00,0xA7,0x80,0xA7,0x00,0xA7,0x80,0xA7])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0050, bytearray([0x20,0xAF,0x20,0xAF,0x20,0xAF,0x20,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0058, bytearray([0x00,0xAF,0x00,0xAF,0x00,0xAF,0x00,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0060, bytearray([0x00,0x94,0x00,0x94,0x00,0x94,0x00,0x94])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0068, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0070, bytearray([0x00,0xA0,0x00,0xA2,0x00,0xA4,0x00,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0078, bytearray([0x80,0x91,0x80,0x93,0x80,0x95,0x80,0x97])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0080, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0088, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0090, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0098, bytearray([0x00,0xA0,0x00,0xA2,0x00,0xA4,0x00,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00A0, bytearray([0x80,0x91,0x80,0x93,0x80,0x95,0x80,0x97])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00A8, bytearray([0x00,0x80,0x00,0x82,0x00,0x84,0x00,0x86])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00B0, bytearray([0x00,0x86,0x00,0x84,0x00,0x82,0x00,0x80])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00B8, bytearray([0x00,0xA1,0x00,0xA3,0x00,0xA5,0x00,0xA3])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00C0, bytearray([0x00,0xA0,0x00,0xA2,0x00,0xA4,0x00,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00C8, bytearray([0x00,0xA8,0x00,0xAA,0x00,0xAC,0x00,0xAE])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00D0, bytearray([0x80,0xA8,0x80,0xAA,0x80,0xAC,0x80,0xAE])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00D8, bytearray([0x80,0xAE,0x80,0xAC,0x80,0xAA,0x80,0xA8])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00E0, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00E8, bytearray([0x80,0xA1,0x80,0xA3,0x80,0xA5,0x80,0xA3])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00F0, bytearray([0x80,0x80,0x80,0x82,0x80,0x84,0x80,0x86])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00F8, bytearray([0x00,0x81,0x00,0x83,0x00,0x85,0x00,0x87])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0100, bytearray([0x80,0x81,0x80,0x83,0x80,0x85,0x80,0x87])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0108, bytearray([0x80,0x86,0x80,0x84,0x80,0x82,0x80,0x80])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0110, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0118, bytearray([0x80,0xA9,0x80,0xAB,0x80,0xAD,0x80,0xAB])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0120, bytearray([0x00,0x91,0x00,0x93,0x00,0x95,0x00,0x97])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0128, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0130, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0138, bytearray([0x80,0xA1,0x80,0xA3,0x80,0xA5,0x80,0xA3])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0140, bytearray([0x00,0xA9,0x00,0xAB,0x00,0xAD,0x00,0xAB])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0148, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0150, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0158, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0160, bytearray([0x80,0x94,0x80,0x94,0x80,0x94,0x80,0x94])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0168, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0170, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0178, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0180, bytearray([0x00,0x98,0x00,0x9A,0x00,0x9C,0x00,0x9E])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0188, bytearray([0x80,0xAF,0x80,0xAF,0x80,0xAF,0x80,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0190, bytearray([0x00,0x96,0x00,0x96,0x00,0x96,0x00,0x96])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0198, bytearray([0x80,0x96,0x80,0x96,0x80,0x96,0x80,0x96])) + + # Insert hand drawn graphics for in level indicators + rom.write_bytes(0xE7000, read_graphics_file("indicators.bin")) + # Upload indicator GFX + UPLOAD_INDICATOR_GFX = 0x87000 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0000, bytearray([0xAD, 0x00, 0x01])) # upload_score_sprite_gfx: lda $0100 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0003, bytearray([0xC9, 0x13])) # cmp #$13 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0005, bytearray([0xF0, 0x03])) # beq .check_level + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0007, bytearray([0x4C, 0x9D, 0xF0])) # jmp .check_map + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x000A, bytearray([0xA5, 0x7C])) # .check_level lda $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x000C, bytearray([0xF0, 0x03])) # beq ..perform + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x000E, bytearray([0x4C, 0x9C, 0xF0])) # jmp .skip + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0011, bytearray([0xE6, 0x7C])) # ..perform inc $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0013, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0015, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0017, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x001A, bytearray([0xA9, 0x01, 0x18])) # lda #$1801 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x001D, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0020, bytearray([0xA0, 0x1C])) # ldy.b #$1C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0022, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0025, bytearray([0xA9, 0x00, 0xF0])) # lda.w #$F000 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0028, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x002B, bytearray([0xA9, 0xA0, 0x64])) # .nums_01 lda #$64A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x002E, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0031, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0034, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0037, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x003A, bytearray([0xA9, 0xA0, 0x65])) # .nums_35 lda #$65A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x003D, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0040, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0043, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0046, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0049, bytearray([0xA9, 0xA0, 0x61])) # .plus_coin lda #$61A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x004C, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x004F, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0052, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0055, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0058, bytearray([0xA9, 0xA0, 0x60])) # .egg_mushroom lda #$60A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x005B, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x005E, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0061, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0064, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0067, bytearray([0xA9, 0xE0, 0x67])) # .thwimp lda #$67E0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x006A, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x006D, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0070, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0073, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0076, bytearray([0xA9, 0x80, 0x63])) # .token lda #$6380 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0079, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x007C, bytearray([0xA9, 0x20, 0x00])) # lda #$0020 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x007F, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0082, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0085, bytearray([0xA9, 0x00, 0xEC])) # .layer_3 lda #$EC00 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0088, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x008B, bytearray([0xA9, 0x80, 0x41])) # lda #$4180 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x008E, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0091, bytearray([0xA9, 0x50, 0x00])) # lda #$0050 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0094, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0097, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009A, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009C, bytearray([0x6B])) # .skip rtl + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009D, bytearray([0xC9, 0x0E])) # .check_map cmp #$0E + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009F, bytearray([0xF0, 0x51])) # beq .map_pal + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A1, bytearray([0xC9, 0x0D])) # cmp #$0D + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A3, bytearray([0xD0, 0xF7])) # bne .skip + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A5, bytearray([0xA5, 0x7C])) # lda $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A7, bytearray([0xD0, 0xF3])) # bne .skip + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A9, bytearray([0xE6, 0x7C])) # inc $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00AB, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00AD, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00AF, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00B2, bytearray([0xA9, 0x01, 0x18])) # lda #$1801 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00B5, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00B8, bytearray([0xA0, 0x1C])) # ldy.b #$1C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00BA, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00BD, bytearray([0xA9, 0x00, 0xE4])) # lda.w #$E400 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C0, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C3, bytearray([0xDA])) # phx + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C4, bytearray([0x9B])) # txy + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C5, bytearray([0xA2, 0x18])) # ldx.b #(.map_targets_end-.map_targets-1)*2 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C7, bytearray([0xA9, 0x40, 0x00])) # ..loop lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00CA, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00CD, bytearray([0xBF, 0x80, 0xFF, 0x10])) # lda.l .map_targets,x + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00D1, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00D4, bytearray([0x8C, 0x0B, 0x42])) # sty $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00D7, bytearray([0xBF, 0x80, 0xFF, 0x10])) # lda.l .map_targets,x + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00DB, bytearray([0x18])) # clc + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00DC, bytearray([0x69, 0x00, 0x01])) # adc #$0100 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00DF, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00E2, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00E5, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00E8, bytearray([0x8C, 0x0B, 0x42])) # sty $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00EB, bytearray([0xCA])) # dex + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00EC, bytearray([0xCA])) # dex + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00ED, bytearray([0x10, 0xD8])) # bpl .loop + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00EF, bytearray([0xFA])) # plx + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F0, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F2, bytearray([0xA9, 0xA3])) # .map_pal lda #$A3 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F4, bytearray([0x8D, 0x21, 0x21])) # sta $2121 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F7, bytearray([0xAF, 0x9C, 0xB5, 0x00])) # lda $00B59C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00FB, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00FE, bytearray([0xAF, 0x9D, 0xB5, 0x00])) # lda $00B59D + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0102, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0105, bytearray([0xAF, 0x9E, 0xB5, 0x00])) # lda $00B59E + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0109, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x010C, bytearray([0xAF, 0x9F, 0xB5, 0x00])) # lda $00B59F + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0110, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0113, bytearray([0xAF, 0xA0, 0xB5, 0x00])) # lda $00B5A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0117, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x011A, bytearray([0xAF, 0xA1, 0xB5, 0x00])) # lda $00B5A1 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x011E, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0121, bytearray([0xAF, 0xA2, 0xB5, 0x00])) # lda $00B5A2 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0125, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0128, bytearray([0xAF, 0xA3, 0xB5, 0x00])) # lda $00B5A3 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x012C, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x012F, bytearray([0xAF, 0xA4, 0xB5, 0x00])) # lda $00B5A4 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0133, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0136, bytearray([0xAF, 0xA5, 0xB5, 0x00])) # lda $00B5A5 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x013A, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x013D, bytearray([0x6B])) # rtl + + vram_targets = bytearray([ + 0x20,0x64, 0x00,0x64, 0xE0,0x62, + 0x60,0x66, 0x40,0x66, + 0x60,0x64, + 0x40,0x62, 0x00,0x62, + 0xE0,0x60, 0xC0,0x60, 0xA0,0x60, 0x80,0x60, 0x60,0x60 + ]) + rom.write_bytes(0x87F80, vram_targets) + + +def handle_chocolate_island_2(rom): + FIX_CHOCOISLAND2_ADDR = 0x87200 + rom.write_bytes(0x2DB3E, bytearray([0x5C, 0x00, 0xF2, 0x10])) # jml fix_choco_island_2 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0000, bytearray([0xAD, 0x33, 0x1F])) # fix_choco_island_2 lda $1F2F+$04 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0003, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0005, bytearray([0xD0, 0x0D])) # bne .dc_room + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0007, bytearray([0xAD, 0x22, 0x14])) # lda $1422 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x000A, bytearray([0xC9, 0x04])) # cmp #$04 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x000C, bytearray([0xF0, 0x06])) # beq .dc_room + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x000E, bytearray([0xA2, 0x02])) # .rex_room ldx #$02 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0010, bytearray([0x5C, 0x49, 0xDB, 0x05])) # jml $05DB49 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0014, bytearray([0xA2, 0x00])) # .dc_room ldx #$00 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0016, bytearray([0x5C, 0x49, 0xDB, 0x05])) # jml $05DB49 + + +def decompress_gfx(compressed_graphics): + # This code decompresses graphics in LC_LZ2 format in order to be able to swap player and yoshi's graphics with ease. + decompressed_gfx = bytearray([]) + i = 0 + while True: + cmd = compressed_graphics[i] + i += 1 + if cmd == 0xFF: + break + else: + if (cmd >> 5) == 0x07: + size = ((cmd & 0x03) << 8) + compressed_graphics[i] + 1 + cmd = (cmd & 0x1C) >> 2 + i += 1 + else: + size = (cmd & 0x1F) + 1 + cmd = cmd >> 5 + if cmd == 0x00: + decompressed_gfx += bytearray([compressed_graphics[i+j] for j in range(size)]) + i += size + elif cmd == 0x01: + byte_fill = compressed_graphics[i] + i += 1 + decompressed_gfx += bytearray([byte_fill for j in range(size)]) + elif cmd == 0x02: + byte_fill_1 = compressed_graphics[i] + i += 1 + byte_fill_2 = compressed_graphics[i] + i += 1 + for j in range(size): + if (j & 0x1) == 0x00: + decompressed_gfx += bytearray([byte_fill_1]) + else: + decompressed_gfx += bytearray([byte_fill_2]) + elif cmd == 0x03: + byte_read = compressed_graphics[i] + i += 1 + decompressed_gfx += bytearray([(byte_read + j) for j in range(size)]) + elif cmd == 0x04: + position = (compressed_graphics[i] << 8) + compressed_graphics[i+1] + i += 2 + for j in range(size): + copy_byte = decompressed_gfx[position+j] + decompressed_gfx += bytearray([copy_byte]) + return decompressed_gfx + + +def convert_3bpp(decompressed_gfx): + i = 0 + converted_gfx = bytearray([]) + while i < len(decompressed_gfx): + converted_gfx += bytearray([decompressed_gfx[i+j] for j in range(16)]) + i += 16 + for j in range(8): + converted_gfx += bytearray([decompressed_gfx[i]]) + converted_gfx += bytearray([0x00]) + i += 1 + return converted_gfx + + +def copy_gfx_tiles(original, order, size): + result = bytearray([]) + for x in range(len(order)): + z = order[x] << size[0] + result += bytearray([original[z+y] for y in range(size[1])]) + return result + + +def file_to_bytes(filename): + return open(os.path.dirname(__file__)+filename, "rb").read() + + +def handle_music_shuffle(rom, world: World): from .Aesthetics import generate_shuffled_level_music, generate_shuffled_ow_music, level_music_address_data, ow_music_address_data - shuffled_level_music = generate_shuffled_level_music(world, player) + shuffled_level_music = generate_shuffled_level_music(world) for i in range(len(shuffled_level_music)): rom.write_byte(level_music_address_data[i], shuffled_level_music[i]) - shuffled_ow_music = generate_shuffled_ow_music(world, player) + shuffled_ow_music = generate_shuffled_ow_music(world) for i in range(len(shuffled_ow_music)): for addr in ow_music_address_data[i]: rom.write_byte(addr, shuffled_ow_music[i]) -def handle_mario_palette(rom, world, player): +def handle_mario_palette(rom, world: World): from .Aesthetics import mario_palettes, fire_mario_palettes, ow_mario_palettes - chosen_palette = world.mario_palette[player].value + chosen_palette = world.options.mario_palette.value rom.write_bytes(0x32C8, bytes(mario_palettes[chosen_palette])) rom.write_bytes(0x32F0, bytes(fire_mario_palettes[chosen_palette])) @@ -723,9 +2851,9 @@ def handle_swap_donut_gh_exits(rom): rom.write_bytes(0x26371, bytes([0x32])) -def handle_bowser_rooms(rom, world, player: int): - if world.bowser_castle_rooms[player] == "random_two_room": - chosen_rooms = world.per_slot_randoms[player].sample(standard_bowser_rooms, 2) +def handle_bowser_rooms(rom, world: World): + if world.options.bowser_castle_rooms == "random_two_room": + chosen_rooms = world.random.sample(standard_bowser_rooms, 2) rom.write_byte(0x3A680, chosen_rooms[0].roomID) rom.write_byte(0x3A684, chosen_rooms[0].roomID) @@ -737,8 +2865,8 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) - elif world.bowser_castle_rooms[player] == "random_five_room": - chosen_rooms = world.per_slot_randoms[player].sample(standard_bowser_rooms, 5) + elif world.options.bowser_castle_rooms == "random_five_room": + chosen_rooms = world.random.sample(standard_bowser_rooms, 5) rom.write_byte(0x3A680, chosen_rooms[0].roomID) rom.write_byte(0x3A684, chosen_rooms[0].roomID) @@ -750,9 +2878,9 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) - elif world.bowser_castle_rooms[player] == "gauntlet": + elif world.options.bowser_castle_rooms == "gauntlet": chosen_rooms = standard_bowser_rooms.copy() - world.per_slot_randoms[player].shuffle(chosen_rooms) + world.random.shuffle(chosen_rooms) rom.write_byte(0x3A680, chosen_rooms[0].roomID) rom.write_byte(0x3A684, chosen_rooms[0].roomID) @@ -763,12 +2891,12 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(chosen_rooms[i-1].exitAddress, chosen_rooms[i].roomID) rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) - elif world.bowser_castle_rooms[player] == "labyrinth": + elif world.options.bowser_castle_rooms == "labyrinth": bowser_rooms_copy = full_bowser_rooms.copy() entrance_point = bowser_rooms_copy.pop(0) - world.per_slot_randoms[player].shuffle(bowser_rooms_copy) + world.random.shuffle(bowser_rooms_copy) rom.write_byte(entrance_point.exitAddress, bowser_rooms_copy[0].roomID) for i in range(0, len(bowser_rooms_copy) - 1): @@ -777,13 +2905,13 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(bowser_rooms_copy[len(bowser_rooms_copy)-1].exitAddress, 0xBD) -def handle_boss_shuffle(rom, world, player): - if world.boss_shuffle[player] == "simple": +def handle_boss_shuffle(rom, world: World): + if world.options.boss_shuffle == "simple": submap_boss_rooms_copy = submap_boss_rooms.copy() ow_boss_rooms_copy = ow_boss_rooms.copy() - world.per_slot_randoms[player].shuffle(submap_boss_rooms_copy) - world.per_slot_randoms[player].shuffle(ow_boss_rooms_copy) + world.random.shuffle(submap_boss_rooms_copy) + world.random.shuffle(ow_boss_rooms_copy) for i in range(len(submap_boss_rooms_copy)): rom.write_byte(submap_boss_rooms[i].exitAddress, submap_boss_rooms_copy[i].roomID) @@ -794,21 +2922,21 @@ def handle_boss_shuffle(rom, world, player): if ow_boss_rooms[i].exitAddressAlt is not None: rom.write_byte(ow_boss_rooms[i].exitAddressAlt, ow_boss_rooms_copy[i].roomID) - elif world.boss_shuffle[player] == "full": + elif world.options.boss_shuffle == "full": for i in range(len(submap_boss_rooms)): - chosen_boss = world.per_slot_randoms[player].choice(submap_boss_rooms) + chosen_boss = world.random.choice(submap_boss_rooms) rom.write_byte(submap_boss_rooms[i].exitAddress, chosen_boss.roomID) for i in range(len(ow_boss_rooms)): - chosen_boss = world.per_slot_randoms[player].choice(ow_boss_rooms) + chosen_boss = world.random.choice(ow_boss_rooms) rom.write_byte(ow_boss_rooms[i].exitAddress, chosen_boss.roomID) if ow_boss_rooms[i].exitAddressAlt is not None: rom.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_boss.roomID) - elif world.boss_shuffle[player] == "singularity": - chosen_submap_boss = world.per_slot_randoms[player].choice(submap_boss_rooms) - chosen_ow_boss = world.per_slot_randoms[player].choice(ow_boss_rooms) + elif world.options.boss_shuffle == "singularity": + chosen_submap_boss = world.random.choice(submap_boss_rooms) + chosen_ow_boss = world.random.choice(ow_boss_rooms) for i in range(len(submap_boss_rooms)): rom.write_byte(submap_boss_rooms[i].exitAddress, chosen_submap_boss.roomID) @@ -820,8 +2948,8 @@ def handle_boss_shuffle(rom, world, player): rom.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_ow_boss.roomID) -def patch_rom(world, rom, player, active_level_dict): - goal_text = generate_goal_text(world, player) +def patch_rom(world: World, rom, player, active_level_dict): + goal_text = generate_goal_text(world) rom.write_bytes(0x2A6E2, goal_text) rom.write_byte(0x2B1D8, 0x80) @@ -829,19 +2957,23 @@ def patch_rom(world, rom, player, active_level_dict): intro_text = generate_text_box("Bowser has stolen all of Mario's abilities. Can you help Mario travel across Dinosaur land to get them back and save the Princess from him?") rom.write_bytes(0x2A5D9, intro_text) - handle_bowser_rooms(rom, world, player) - handle_boss_shuffle(rom, world, player) + handle_bowser_rooms(rom, world) + handle_boss_shuffle(rom, world) + + # Handle ROM expansion + rom.write_bytes(0x07FD7, bytearray([0x0A])) + rom.write_bytes(0x80000, bytearray([0x00 for _ in range(0x80000)])) # Prevent Title Screen Deaths rom.write_byte(0x1C6A, 0x80) # Title Screen Text player_name_bytes = bytearray() - player_name = world.get_player_name(player) + player_name = world.multiworld.get_player_name(player) for i in range(16): char = " " if i < len(player_name): - char = world.get_player_name(player)[i] + char = player_name[i] upper_char = char.upper() if upper_char not in title_text_mapping: for byte in title_text_mapping["."]: @@ -869,33 +3001,58 @@ def patch_rom(world, rom, player, active_level_dict): rom.write_bytes(0x2B88E, bytearray([0x2C, 0x31, 0x73, 0x31, 0x75, 0x31, 0x82, 0x30, 0x30, 0x31, 0xFC, 0x38, 0x31, 0x31, 0x73, 0x31, 0x73, 0x31, 0x7C, 0x30, 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # 1 Player Game - rom.write_bytes(0x2B6D7, bytearray([0xFC, 0x38, 0xFC, 0x38, 0x16, 0x38, 0x18, 0x38, 0x0D, 0x38, 0xFC, 0x38, 0x0B, 0x38, 0x22, 0x38, + rom.write_bytes(0x2B6D7, bytearray([0x16, 0x38, 0x18, 0x38, 0x0D, 0x38, 0xFC, 0x38, 0x0B, 0x38, 0x22, 0x38, 0xFC, 0x38, 0x19, 0x38, 0x18, 0x38, 0x1B, 0x38, 0x22, 0x38, 0x10, 0x38, 0x18, 0x38, 0x17, 0x38, - 0x0E, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # Mod by PoryGone + 0x0E, 0x38, 0xFC, 0x38, 0x15, 0x38, 0x21, 0x38, 0x05, 0x38])) # Mod by PoryGone + lx5 # Title Options rom.write_bytes(0x1E6A, bytearray([0x01])) rom.write_bytes(0x1E6C, bytearray([0x01])) rom.write_bytes(0x1E6E, bytearray([0x01])) + # Save current level number to RAM (not translevel) + rom.write_bytes(0x2D8B9, bytearray([0x20, 0x46, 0xDC])) # org $05D8B9 : jsr level_num + rom.write_bytes(0x2DC46 + 0x0000, bytearray([0xA5, 0x0E])) # level_num: lda $0E + rom.write_bytes(0x2DC46 + 0x0002, bytearray([0x8D, 0x0B, 0x01])) # sta $010B + rom.write_bytes(0x2DC46 + 0x0005, bytearray([0x0A])) # asl + rom.write_bytes(0x2DC46 + 0x0006, bytearray([0x60])) # rts + # Always allow Start+Select rom.write_bytes(0x2267, bytearray([0xEA, 0xEA])) # Always bring up save prompt on beating a level - if world.autosave[player]: + if world.options.autosave: rom.write_bytes(0x20F93, bytearray([0x00])) - if world.overworld_speed[player] == "fast": + if world.options.overworld_speed == "fast": rom.write_bytes(0x21414, bytearray([0x20, 0x10])) - elif world.overworld_speed[player] == "slow": + elif world.options.overworld_speed == "slow": rom.write_bytes(0x21414, bytearray([0x05, 0x05])) # Starting Life Count - rom.write_bytes(0x1E25, bytearray([world.starting_life_count[player].value - 1])) + rom.write_bytes(0x1E25, bytearray([world.options.starting_life_count.value - 1])) # Repurpose Bonus Stars counter for Boss Token or Yoshi Eggs rom.write_bytes(0x3F1AA, bytearray([0x00] * 0x20)) + # Make bonus star counter go up to 255 (999 in theory, but can't load a 16-bit addr there) + rom.write_bytes(0x00F5B, bytearray([0x4C, 0x73, 0x8F])) + rom.write_byte(0x00F95, 0x08) + rom.write_byte(0x00F97, 0x0C) + rom.write_byte(0x00FAC, 0x02) + rom.write_byte(0x00F9E, 0x1D) + rom.write_byte(0x00FA5, 0x1D) + rom.write_byte(0x00FA8, 0x02) + rom.write_byte(0x00FB0, 0x1D) + rom.write_byte(0x00FB8, 0x02) + rom.write_byte(0x00FBE, 0x1D) + rom.write_byte(0x00FC2, 0x03) + + # Move Dragon coins one spot to the left & fix tilemap + rom.write_byte(0x00FF0, 0xFE) + rom.write_byte(0x00C94, 0x3C) + rom.write_byte(0x00C9C, 0x38) + # Delete Routine that would copy Mario position data over repurposed Luigi save data rom.write_bytes(0x20F9F, bytearray([0xEA] * 0x3D)) @@ -904,6 +3061,12 @@ def patch_rom(world, rom, player, active_level_dict): rom.write_bytes(0x6EB1, bytearray([0xEA, 0xEA])) rom.write_bytes(0x6EB4, bytearray([0xEA, 0xEA, 0xEA])) + # Move Thwimps tilemap to another spot in VRAM in order to make them global + rom.write_bytes(0x09C13, bytearray([0x7E, 0x7E, 0x7F, 0x7F])) + rom.write_byte(0x3F425, 0x32) + + handle_chocolate_island_2(rom) + handle_ability_code(rom) handle_yoshi_box(rom) @@ -913,44 +3076,97 @@ def patch_rom(world, rom, player, active_level_dict): handle_vertical_scroll(rom) + handle_ram(rom) + handle_bonus_block(rom) + handle_blocksanity(rom) + + handle_uncompressed_player_gfx(rom) + + # Handle Special Zone Clear flag + rom.write_bytes(0x02A74, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x09826, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x0B9CD, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x12986, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x62E0F, bytearray([0x1E, 0x1F])) + + handle_indicators(rom) + handle_map_indicators(rom) + + # Handle extra traps + handle_traps(rom) + + # Mario Start! -> Player Start! + text_data_top_tiles = bytearray([ + 0x00,0xFF,0x4D,0x4C,0x03,0x4D,0x5D,0xFF,0x4C,0x4B, + 0x4A,0x03,0x4E,0x01,0x00,0x02,0x00,0x4a,0x4E,0xFF + ]) + text_data_top_props = bytearray([ + 0x34,0x30,0x34,0x34,0x34,0x34,0x34,0x30,0x34,0x34, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x30 + ]) + text_data_bottom_tiles = bytearray([ + 0x10,0xFF,0x00,0x5C,0x13,0x00,0x5D,0xFF,0x5C,0x5B, + 0x00,0x13,0x5E,0x11,0x00,0x12,0x00,0x03,0x5E,0xFF + ]) + text_data_bottom_props = bytearray([ + 0x34,0x30,0xb4,0x34,0x34,0xb4,0xf4,0x30,0x34,0x34, + 0xB4,0x34,0x34,0x34,0xb4,0x34,0xb4,0xb4,0x34,0x30 + ]) + + rom.write_bytes(0x010D1, text_data_top_tiles) + rom.write_bytes(0x01139, text_data_top_props) + rom.write_bytes(0x01105, text_data_bottom_tiles) + rom.write_bytes(0x0116A, text_data_bottom_props) + # Handle Level Shuffle handle_level_shuffle(rom, active_level_dict) # Handle Music Shuffle - if world.music_shuffle[player] != "none": - handle_music_shuffle(rom, world, player) + if world.options.music_shuffle != "none": + handle_music_shuffle(rom, world) + + generate_shuffled_ow_palettes(rom, world) - generate_shuffled_ow_palettes(rom, world, player) + generate_shuffled_header_data(rom, world) - generate_shuffled_header_data(rom, world, player) + if world.options.level_palette_shuffle == "on_curated": + generate_curated_level_palette_data(rom, world) - if world.swap_donut_gh_exits[player]: + if world.options.overworld_palette_shuffle == "on_curated": + generate_curated_map_palette_data(rom, world) + + if world.options.sfx_shuffle != "none": + generate_shuffled_sfx(rom, world) + + if world.options.swap_donut_gh_exits: handle_swap_donut_gh_exits(rom) - handle_mario_palette(rom, world, player) + handle_mario_palette(rom, world) # Store all relevant option results in ROM - rom.write_byte(0x01BFA0, world.goal[player].value) - if world.goal[player].value == 0: - rom.write_byte(0x01BFA1, world.bosses_required[player].value) + rom.write_byte(0x01BFA0, world.options.goal.value) + if world.options.goal.value == 0: + rom.write_byte(0x01BFA1, world.options.bosses_required.value) else: rom.write_byte(0x01BFA1, 0x7F) - required_yoshi_eggs = max(math.floor( - world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + required_yoshi_eggs = world.required_egg_count rom.write_byte(0x01BFA2, required_yoshi_eggs) - #rom.write_byte(0x01BFA3, world.display_sent_item_popups[player].value) - rom.write_byte(0x01BFA4, world.display_received_item_popups[player].value) - rom.write_byte(0x01BFA5, world.death_link[player].value) - rom.write_byte(0x01BFA6, world.dragon_coin_checks[player].value) - rom.write_byte(0x01BFA7, world.swap_donut_gh_exits[player].value) + #rom.write_byte(0x01BFA3, world.options.display_sent_item_popups.value) + rom.write_byte(0x01BFA4, world.options.display_received_item_popups.value) + rom.write_byte(0x01BFA5, world.options.death_link.value) + rom.write_byte(0x01BFA6, world.options.dragon_coin_checks.value) + rom.write_byte(0x01BFA7, world.options.swap_donut_gh_exits.value) + rom.write_byte(0x01BFA8, world.options.moon_checks.value) + rom.write_byte(0x01BFA9, world.options.hidden_1up_checks.value) + rom.write_byte(0x01BFAA, world.options.bonus_block_checks.value) + rom.write_byte(0x01BFAB, world.options.blocksanity.value) from Utils import __version__ - rom.name = bytearray(f'SMW{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name = bytearray(f'SMW{__version__.replace(".", "")[0:3]}_{player}_{world.multiworld.seed:11}\0', 'utf8')[:21] rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) - def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: diff --git a/worlds/smw/Rules.py b/worlds/smw/Rules.py index 82f22c3a34c1..a900b4fd20ec 100644 --- a/worlds/smw/Rules.py +++ b/worlds/smw/Rules.py @@ -2,19 +2,18 @@ from BaseClasses import MultiWorld from .Names import LocationName, ItemName -from worlds.AutoWorld import LogicMixin +from worlds.AutoWorld import World from worlds.generic.Rules import add_rule, set_rule -def set_rules(world: MultiWorld, player: int): +def set_rules(world: World): - if world.goal[player] == "yoshi_egg_hunt": - required_yoshi_eggs = max(math.floor( - world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + if world.options.goal == "yoshi_egg_hunt": + required_yoshi_eggs = world.required_egg_count - add_rule(world.get_location(LocationName.yoshis_house, player), - lambda state: state.has(ItemName.yoshi_egg, player, required_yoshi_eggs)) + add_rule(world.multiworld.get_location(LocationName.yoshis_house, world.player), + lambda state: state.has(ItemName.yoshi_egg, world.player, required_yoshi_eggs)) else: - add_rule(world.get_location(LocationName.bowser, player), lambda state: state.has(ItemName.mario_carry, player)) + add_rule(world.multiworld.get_location(LocationName.bowser, world.player), lambda state: state.has(ItemName.mario_carry, world.player)) - world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) + world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player) diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 431287c32bef..97fc84f003a0 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -1,3 +1,4 @@ +import dataclasses import os import typing import math @@ -5,17 +6,19 @@ import threading from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification -from .Items import SMWItem, ItemData, item_table -from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names -from .Options import smw_options -from .Regions import create_regions, connect_regions -from .Levels import full_level_list, generate_level_list, location_id_to_level_id -from .Rules import set_rules +from worlds.AutoWorld import WebWorld, World from worlds.generic.Rules import add_rule, exclusion_rules -from .Names import ItemName, LocationName + from .Client import SMWSNIClient -from worlds.AutoWorld import WebWorld, World +from .Items import SMWItem, ItemData, item_table, junk_table +from .Levels import full_level_list, generate_level_list, location_id_to_level_id +from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names, special_zone_hidden_1up_names, special_zone_blocksanity_names +from .Names import ItemName, LocationName +from .Options import SMWOptions, smw_option_groups +from .Presets import smw_options_presets +from .Regions import create_regions, connect_regions from .Rom import LocalRom, patch_rom, get_base_rom_path, SMWDeltaPatch +from .Rules import set_rules class SMWSettings(settings.Group): @@ -39,9 +42,12 @@ class SMWWeb(WebWorld): "setup/en", ["PoryGone"] ) - + tutorials = [setup_en] + option_groups = smw_option_groups + options_presets = smw_options_presets + class SMWWorld(World): """ @@ -50,11 +56,14 @@ class SMWWorld(World): lost all of his abilities. Can he get them back in time to save the Princess? """ game: str = "Super Mario World" - option_definitions = smw_options + settings: typing.ClassVar[SMWSettings] + + options_dataclass = SMWOptions + options: SMWOptions + topology_present = False - data_version = 3 - required_client_version = (0, 3, 5) + required_client_version = (0, 4, 5) item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = all_locations @@ -62,9 +71,9 @@ class SMWWorld(World): active_level_dict: typing.Dict[int,int] web = SMWWeb() - def __init__(self, world: MultiWorld, player: int): + def __init__(self, multiworld: MultiWorld, player: int): self.rom_name_available_event = threading.Event() - super().__init__(world, player) + super().__init__(multiworld, player) @classmethod def stage_assert_generate(cls, multiworld: MultiWorld): @@ -72,37 +81,34 @@ def stage_assert_generate(cls, multiworld: MultiWorld): if not os.path.exists(rom_file): raise FileNotFoundError(rom_file) - def _get_slot_data(self): - return { - #"death_link": self.multiworld.death_link[self.player].value, - "active_levels": self.active_level_dict, - } - def fill_slot_data(self) -> dict: - slot_data = self._get_slot_data() - for option_name in smw_options: - option = getattr(self.multiworld, option_name)[self.player] - slot_data[option_name] = option.value + slot_data = self.options.as_dict( + "dragon_coin_checks", + "moon_checks", + "hidden_1up_checks", + "bonus_block_checks", + "blocksanity", + ) + slot_data["active_levels"] = self.active_level_dict return slot_data def generate_early(self): - if self.multiworld.early_climb[self.player]: + if self.options.early_climb: self.multiworld.local_early_items[self.player][ItemName.mario_climb] = 1 - def create_regions(self): - location_table = setup_locations(self.multiworld, self.player) - create_regions(self.multiworld, self.player, location_table) + location_table = setup_locations(self) + create_regions(self, location_table) # Not generate basic itempool: typing.List[SMWItem] = [] - self.active_level_dict = dict(zip(generate_level_list(self.multiworld, self.player), full_level_list)) - self.topology_present = self.multiworld.level_shuffle[self.player] + self.active_level_dict = dict(zip(generate_level_list(self), full_level_list)) + self.topology_present = self.options.level_shuffle + + connect_regions(self, self.active_level_dict) - connect_regions(self.multiworld, self.player, self.active_level_dict) - # Add Boss Token amount requirements for Worlds add_rule(self.multiworld.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) add_rule(self.multiworld.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) @@ -110,18 +116,29 @@ def create_regions(self): add_rule(self.multiworld.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) add_rule(self.multiworld.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) - if self.multiworld.exclude_special_zone[self.player]: - exclusion_pool = set() - if self.multiworld.dragon_coin_checks[self.player]: - exclusion_pool.update(special_zone_level_names) + exclusion_pool = set() + if self.options.exclude_special_zone: + exclusion_pool.update(special_zone_level_names) + if self.options.dragon_coin_checks: exclusion_pool.update(special_zone_dragon_coin_names) - elif self.multiworld.number_of_yoshi_eggs[self.player].value <= 72: - exclusion_pool.update(special_zone_level_names) + if self.options.hidden_1up_checks: + exclusion_pool.update(special_zone_hidden_1up_names) + if self.options.blocksanity: + exclusion_pool.update(special_zone_blocksanity_names) + exclusion_rules(self.multiworld, self.player, exclusion_pool) total_required_locations = 96 - if self.multiworld.dragon_coin_checks[self.player]: + if self.options.dragon_coin_checks: total_required_locations += 49 + if self.options.moon_checks: + total_required_locations += 7 + if self.options.hidden_1up_checks: + total_required_locations += 14 + if self.options.bonus_block_checks: + total_required_locations += 4 + if self.options.blocksanity: + total_required_locations += 582 itempool += [self.create_item(ItemName.mario_run)] itempool += [self.create_item(ItemName.mario_carry)] @@ -137,31 +154,53 @@ def create_regions(self): itempool += [self.create_item(ItemName.green_switch_palace)] itempool += [self.create_item(ItemName.red_switch_palace)] itempool += [self.create_item(ItemName.blue_switch_palace)] + itempool += [self.create_item(ItemName.special_world_clear)] - if self.multiworld.goal[self.player] == "yoshi_egg_hunt": - itempool += [self.create_item(ItemName.yoshi_egg) - for _ in range(self.multiworld.number_of_yoshi_eggs[self.player])] + if self.options.goal == "yoshi_egg_hunt": + raw_egg_count = total_required_locations - len(itempool) - len(exclusion_pool) + total_egg_count = min(raw_egg_count, self.options.max_yoshi_egg_cap.value) + self.required_egg_count = max(math.floor(total_egg_count * (self.options.percentage_of_yoshi_eggs.value / 100.0)), 1) + extra_egg_count = total_egg_count - self.required_egg_count + removed_egg_count = math.floor(extra_egg_count * (self.options.junk_fill_percentage.value / 100.0)) + self.actual_egg_count = total_egg_count - removed_egg_count + + itempool += [self.create_item(ItemName.yoshi_egg) for _ in range(self.actual_egg_count)] + self.multiworld.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory)) else: + self.actual_egg_count = 0 + self.required_egg_count = 0 + self.multiworld.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) junk_count = total_required_locations - len(itempool) trap_weights = [] - trap_weights += ([ItemName.ice_trap] * self.multiworld.ice_trap_weight[self.player].value) - trap_weights += ([ItemName.stun_trap] * self.multiworld.stun_trap_weight[self.player].value) - trap_weights += ([ItemName.literature_trap] * self.multiworld.literature_trap_weight[self.player].value) - trap_weights += ([ItemName.timer_trap] * self.multiworld.timer_trap_weight[self.player].value) - trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0)) + trap_weights += ([ItemName.ice_trap] * self.options.ice_trap_weight.value) + trap_weights += ([ItemName.stun_trap] * self.options.stun_trap_weight.value) + trap_weights += ([ItemName.literature_trap] * self.options.literature_trap_weight.value) + trap_weights += ([ItemName.timer_trap] * self.options.timer_trap_weight.value) + trap_weights += ([ItemName.reverse_controls_trap] * self.options.reverse_trap_weight.value) + trap_weights += ([ItemName.thwimp_trap] * self.options.thwimp_trap_weight.value) + trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.options.trap_fill_percentage.value / 100.0)) junk_count -= trap_count trap_pool = [] for i in range(trap_count): - trap_item = self.multiworld.random.choice(trap_weights) + trap_item = self.random.choice(trap_weights) trap_pool.append(self.create_item(trap_item)) itempool += trap_pool - itempool += [self.create_item(ItemName.one_up_mushroom) for _ in range(junk_count)] + junk_weights = [] + junk_weights += ([ItemName.one_coin] * 15) + junk_weights += ([ItemName.five_coins] * 15) + junk_weights += ([ItemName.ten_coins] * 25) + junk_weights += ([ItemName.fifty_coins] * 25) + junk_weights += ([ItemName.one_up_mushroom] * 20) + + junk_pool = [self.create_item(self.random.choice(junk_weights)) for _ in range(junk_count)] + + itempool += junk_pool boss_location_names = [LocationName.yoshis_island_koopaling, LocationName.donut_plains_koopaling, LocationName.vanilla_dome_koopaling, LocationName.twin_bridges_koopaling, LocationName.forest_koopaling, LocationName.chocolate_koopaling, @@ -176,18 +215,18 @@ def create_regions(self): def generate_output(self, output_directory: str): rompath = "" # if variable is not declared finally clause may fail try: - world = self.multiworld + multiworld = self.multiworld player = self.player rom = LocalRom(get_base_rom_path()) - patch_rom(self.multiworld, rom, self.player, self.active_level_dict) + patch_rom(self, rom, self.player, self.active_level_dict) rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) self.rom_name = rom.name patch = SMWDeltaPatch(os.path.splitext(rompath)[0]+SMWDeltaPatch.patch_file_ending, player=player, - player_name=world.player_name[player], patched_path=rompath) + player_name=multiworld.player_name[player], patched_path=rompath) patch.write() except: raise @@ -243,7 +282,15 @@ def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, s if level_index >= world_cutoffs[i]: continue - if self.multiworld.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name: + if not self.options.dragon_coin_checks and "Dragon Coins" in loc_name: + continue + if not self.options.moon_checks and "3-Up Moon" in loc_name: + continue + if not self.options.hidden_1up_checks and "Hidden 1-Up" in loc_name: + continue + if not self.options.bonus_block_checks and "1-Up from Bonus Block" in loc_name: + continue + if not self.options.blocksanity and "Block #" in loc_name: continue location = self.multiworld.get_location(loc_name, self.player) @@ -271,7 +318,7 @@ def create_item(self, name: str, force_non_progression=False) -> Item: return created_item def get_filler_item_name(self) -> str: - return ItemName.one_up_mushroom + return self.random.choice(list(junk_table.keys())) def set_rules(self): - set_rules(self.multiworld, self.player) + set_rules(self) diff --git a/worlds/smw/data/blocksanity.json b/worlds/smw/data/blocksanity.json new file mode 100644 index 000000000000..e3737d25978d --- /dev/null +++ b/worlds/smw/data/blocksanity.json @@ -0,0 +1,747 @@ +{ + "000_bonus": [], + "001_vanilla_secret_2": [ + ["yoshi", "0170", "0130", []], + ["green", "02F0", "0170", ["greenswitch carry", "greenswitch cape"]], + ["power", "0660", "0110", []], + ["power", "0B70", "0100", []], + ["multi", "0DC0", "0120", []], + ["gray", "0E70", "0120", []], + ["single", "1180", "0130", []], + ["single", "1190", "0130", []], + ["single", "11A0", "0130", []], + ["single", "11B0", "0130", []], + ["single", "11C0", "0130", []], + ["single", "11D0", "0130", []] + ], + "002_vanilla_secret_3": [ + ["power", "0270", "00D0", ["swim"]], + ["power", "06E0", "00E0", ["swim"]] + ], + "003_top_secret_area": [], + "004_donut_ghost_house": [ + ["vine", "0120", "0120", []], + ["dir", "0070", "0140", ["pswitch"]], + ["life", "0610", "0140", ["run cape"]], + ["life", "0640", "0140", ["run cape"]], + ["life", "0670", "0140", ["run cape"]], + ["life", "06A0", "0140", ["run cape"]] + ], + "005_donut_plains_3": [ + ["green", "01B0", "00E0", ["greenswitch"]], + ["single", "0450", "00F0", []], + ["single", "0480", "00F0", []], + ["vine", "04E0", "0130", ["mushroom spin"]], + ["power", "0BD0", "0140", []], + ["bonus", "1250", "00F0", []] + ], + "006_donut_plains_4": [ + ["single", "0660", "0130", []], + ["power", "0670", "0130", []], + ["single", "0680", "0130", []], + ["yoshi", "0AF0", "0150", []] + ], + "007_donut_plains_castle": [ + ["yellow", "01E0", "00C0", ["yellowswitch"]], + ["single", "00A0", "0680", []], + ["power", "00B0", "0680", []], + ["single", "00C0", "0680", []], + ["vine", "0050", "0450", []], + ["inlife", "0030", "0320", ["climb"]], + ["single", "0050", "0250", []], + ["single", "0080", "0250", []], + ["single", "00B0", "0250", []], + ["green", "0090", "0060", ["greenswitch"]] + ], + "008_green_switch_palace": [], + "009_donut_plains_2": [ + ["single", "00D0", "0120", []], + ["single", "00E0", "0120", []], + ["single", "00F0", "0120", []], + ["yellow", "0100", "0120", ["yellowswitch"]], + ["power", "0330", "00D0", []], + ["multi", "03C0", "00C0", []], + ["fly", "0820", "00E0", []], + ["green", "0560", "00E0", ["greenswitch"]], + ["yellow", "0050", "0140", ["yellowswitch"]], + ["vine", "02B0", "00E0", ["carry spin mushroom", "yoshi"]] + ], + "00A_donut_secret_1": [ + ["single", "02C0", "0130", ["swim"]], + ["single", "02D0", "0130", ["swim"]], + ["power", "02E0", "0130", ["swim"]], + ["single", "02F0", "0130", ["swim"]], + ["power", "00E0", "0480", ["swim"]], + ["power", "0060", "0250", ["swim balloon"]], + ["life", "0110", "0070", ["swim balloon"]], + ["power", "01A0", "0250", ["swim balloon"]], + ["power", "0570", "0150", ["swim"]], + ["key", "0940", "0150", ["swim carry pswitch"]] + ], + "00B_vanilla_fortress": [ + ["power", "04E0", "0130", ["swim"]], + ["power", "0220", "0130", ["swim"]], + ["yellow", "0780", "0110", ["yellowswitch swim"]] + ], + "00C_butter_bridge_1": [ + ["power", "08A0", "0110", []], + ["multi", "08B0", "00D0", []], + ["multi", "0860", "0090", []], + ["multi", "08E0", "0050", []], + ["life", "0840", "0050", []], + ["bonus", "0BD0", "0130", []] + ], + "00D_butter_bridge_2": [ + ["power", "0310", "0100", ["carry"]], + ["green", "0AC0", "0120", ["greenswitch"]], + ["yoshi", "0C70", "0110", ["carry"]] + ], + "00E_twin_bridges_castle": [ + ["power", "01B0", "0370", ["climb"]] + ], + "00F_cheese_bridge": [ + ["power", "00C0", "0140", []], + ["power", "0560", "00E0", []], + ["wings", "0A10", "0140", []], + ["power", "0B60", "0150", []] + ], + "010_cookie_mountain": [ + ["single", "01C0", "0130", []], + ["single", "01D0", "0130", []], + ["single", "01E0", "0130", []], + ["single", "01F0", "0130", []], + ["single", "0200", "0130", []], + ["single", "0210", "0130", []], + ["single", "0220", "0130", []], + ["single", "0230", "0130", []], + ["single", "0240", "0130", []], + ["power", "0200", "00F0", []], + ["life", "0A40", "0070", ["climb", "swim"]], + ["vine", "0B20", "0140", []], + ["yoshi", "0C40", "0140", ["redswitch"]], + ["single", "11C0", "0140", []], + ["single", "11D0", "0140", []], + ["power", "11E0", "0140", []], + ["single", "11F0", "0140", []], + ["single", "1200", "0140", []], + ["single", "1210", "0140", []], + ["single", "1220", "0140", []], + ["single", "1230", "0140", []], + ["single", "1240", "0140", []], + ["single", "1250", "0140", []], + ["single", "11B0", "0100", []], + ["single", "11C0", "0100", []], + ["single", "11D0", "0100", []], + ["single", "11E0", "0100", []], + ["single", "11F0", "0100", []], + ["single", "1200", "0100", []], + ["single", "1210", "0100", []], + ["single", "1220", "0100", []], + ["single", "1230", "0100", []], + ["single", "1240", "0100", []], + ["single", "1250", "0100", []], + ["single", "1360", "0140", []] + ], + "011_soda_lake": [ + ["power", "0200", "0110", ["swim"]] + ], + "012_test": [], + "013_donut_secret_house": [ + ["power", "0480", "0140", []], + ["multi", "0310", "0140", []], + ["life", "04A0", "0140", ["pswitch"]], + ["vine", "01E0", "0110", ["pswitch"]], + ["dir", "0180", "0130", ["pswitch"]] + ], + "014_yellow_switch_palace": [], + "015_donut_plains_1": [ + ["single", "0710", "0140", []], + ["single", "0720", "0140", []], + ["yoshi", "0D20", "00F0", []], + ["vine", "0DB0", "0130", []], + ["green", "11A0", "0070", ["greenswitch cape"]], + ["green", "11A0", "0080", ["greenswitch cape"]], + ["green", "11A0", "0090", ["greenswitch cape"]], + ["green", "11A0", "00A0", ["greenswitch cape"]], + ["green", "11A0", "00B0", ["greenswitch cape"]], + ["green", "11A0", "00C0", ["greenswitch cape"]], + ["green", "11A0", "00D0", ["greenswitch cape"]], + ["green", "11A0", "00E0", ["greenswitch cape"]], + ["green", "11A0", "00F0", ["greenswitch cape"]], + ["green", "11A0", "0100", ["greenswitch cape"]], + ["green", "11A0", "0110", ["greenswitch cape"]], + ["green", "11A0", "0120", ["greenswitch cape"]], + ["green", "11A0", "0130", ["greenswitch cape"]], + ["green", "11A0", "0140", ["greenswitch cape"]], + ["green", "11A0", "0150", ["greenswitch cape"]], + ["green", "11A0", "0160", ["greenswitch cape"]], + ["yellow", "1240", "0120", ["yellowswitch"]], + ["yellow", "1280", "0140", ["yellowswitch"]], + ["yellow", "1290", "0140", ["yellowswitch"]] + ], + "016_test": [], + "017_test": [], + "018_sunken_ghost_ship": [ + ["power", "0110", "0070", ["swim"]], + ["star", "0100", "0C80", ["swim"]] + ], + "019_test": [], + "01A_chocolate_castle": [ + ["yellow", "09C0", "0110", ["yellowswitch"]], + ["yellow", "0150", "0110", ["yellowswitch"]], + ["green", "0750", "0140", ["greenswitch"]] + ], + "01B_chocolate_fortress": [ + ["power", "0380", "0140", []], + ["power", "0360", "0150", []], + ["single", "0370", "0150", []], + ["single", "0380", "0150", []], + ["green", "0AC0", "0130", ["greenswitch"]] + ], + "01C_chocolate_island_5": [ + ["yoshi", "0170", "0130", []], + ["power", "0180", "0150", []], + ["life", "0340", "0170", ["carry", "cape"]], + ["yellow", "0560", "0140", ["yellowswitch pswitch"]] + ], + "01D_chocolate_island_4": [ + ["yellow", "0700", "0140", ["yellowswitch blueswitch"]], + ["pow", "0920", "00A0", []], + ["power", "0B50", "0040", []] + ], + "01E_test": [], + "01F_forest_fortress": [ + ["yellow", "02B0", "00E0", ["yellowswitch"]], + ["power", "0750", "00D0", []], + ["life", "0ED0", "0140", ["run cape"]], + ["life", "0F10", "0140", ["run cape"]], + ["life", "0F10", "0100", ["run cape"]], + ["life", "0F40", "0130", ["run cape"]], + ["life", "0F70", "0140", ["run cape"]], + ["life", "0F70", "00F0", ["run cape"]], + ["life", "0FA0", "0130", ["run cape"]], + ["life", "0FD0", "0140", ["run cape"]], + ["life", "0FD0", "0100", ["run cape"]] + ], + "020_forest_castle": [ + ["green", "0CC0", "0120", ["greenswitch"]] + ], + "021_chocolate_ghost_house": [ + ["power", "0910", "0140", []], + ["power", "0110", "0140", []], + ["life", "05D0", "0140", []] + ], + "022_chocolate_island_1": [ + ["fly", "0490", "0120", ["pswitch"]], + ["fly", "0CD0", "0100", ["pswitch"]], + ["yoshi", "0E10", "0110", ["pswitch"]], + ["green", "0F00", "0140", ["greenswitch blueswitch", "greenswitch cape", "yellowswitch blueswitch", "yellowswitch cape"]], + ["life", "0070", "0120", ["pswitch"]] + ], + "023_chocolate_island_3": [ + ["power", "0530", "0140", []], + ["power", "0A20", "0140", []], + ["power", "0F50", "00F0", []], + ["green", "1080", "00F0", ["greenswitch"]], + ["bonus", "11D0", "0140", []], + ["vine", "1220", "0140", []], + ["life", "1640", "0140", ["run cape"]], + ["life", "1670", "0140", ["run cape"]], + ["life", "16A0", "0140", ["run cape"]] + ], + "024_chocolate_island_2": [ + ["multi", "00E0", "00A0", []], + ["invis", "00F0", "00D0", []], + ["yoshi", "0280", "0040", []], + ["single", "0080", "0140", []], + ["single", "05C0", "0140", []], + ["multi" , "05F0", "0120", []], + ["power", "0620", "0100", []], + ["pow", "0040", "0140", []], + ["yellow", "0190", "0110", ["yellowswitch"]], + ["yellow", "01A0", "0110", ["yellowswitch"]], + ["green", "0240", "0110", ["greenswitch"]], + ["green", "0250", "0110", ["greenswitch"]], + ["green", "0260", "0110", ["greenswitch"]], + ["green", "0270", "0110", ["greenswitch"]], + ["green", "0280", "0110", ["greenswitch"]], + ["green", "0290", "0110", ["greenswitch"]] + ], + "101_yoshis_island_castle": [ + ["single", "0280", "00F0", ["climb"]], + ["single", "0290", "00F0", ["climb"]], + ["power", "02A0", "00F0", ["climb"]], + ["single", "02B0", "00F0", ["climb"]], + ["single", "02C0", "00F0", ["climb"]], + ["fly", "0250", "0150", ["climb"]] + ], + "102_yoshis_island_4": [ + ["yellow", "00D0", "00F0", ["yellowswitch"]], + ["power", "0160", "0140", []], + ["multi", "0290", "0140", []], + ["star", "04E0", "0120", []] + ], + "103_yoshis_island_3": [ + ["yellow", "0250", "00C0", ["yellowswitch"]], + ["yellow", "0290", "0140", ["yellowswitch"]], + ["yellow", "02F0", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0300", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0310", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0320", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0330", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0340", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0350", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["single", "04B0", "00A0", []], + ["yoshi", "04C0", "00A0", []], + ["single", "0AC0", "0140", []], + ["power", "0B00", "00C0", []], + ["yellow", "0CD0", "0120", ["yellowswitch"]], + ["yellow", "0CE0", "0120", ["yellowswitch"]], + ["yellow", "0DA0", "00F0", ["yellowswitch"]], + ["bonus", "10A0", "0080", []] + ], + "104_yoshis_house": [], + "105_yoshis_island_1": [ + ["fly", "0250", "0140", []], + ["yellow", "09C0", "0140", ["yellowswitch"]], + ["life", "0D10", "00F0", []], + ["power", "0F30", "0110", []] + ], + "106_yoshis_island_2": [ + ["fly", "0080", "00F0", ["carry", "yoshi"]], + ["fly", "00C0", "00E0", ["carry", "yoshi"]], + ["fly", "0130", "00F0", ["carry", "yoshi"]], + ["fly", "0140", "00D0", ["carry", "yoshi"]], + ["fly", "0180", "0100", ["carry", "yoshi"]], + ["fly", "01C0", "00E0", ["carry", "yoshi"]], + ["single", "0260", "0140", []], + ["yellow", "0270", "0140", ["yellowswitch"]], + ["single", "0280", "0140", []], + ["single", "0350", "0150", []], + ["yoshi", "0360", "0150", []], + ["single", "0B20", "0150", []], + ["yoshi", "0B30", "0150", []], + ["single", "0B40", "0150", []], + ["vine", "0C80", "0100", []], + ["yellow", "0DF0", "0120", ["yellowswitch"]] + ], + "107_vanilla_ghost_house": [ + ["power", "0200", "0100", []], + ["vine", "0750", "0150", []], + ["power", "0860", "0140", []], + ["multi", "0A00", "0140", []], + ["pow", "05E0", "0120", []] + ], + "108_test": [], + "109_vanilla_secret_1": [ + ["single", "0030", "0590", []], + ["power", "0040", "0590", []], + ["multi", "0110", "0490", []], + ["vine", "01B0", "0520", []], + ["vine", "0180", "0470", ["climb"]], + ["single", "0190", "0350", ["climb"]], + ["single", "01A0", "0350", ["climb"]], + ["power", "01B0", "0350", ["climb"]] + ], + "10A_vanilla_dome_3": [ + ["single", "01C0", "0140", []], + ["fly", "0200", "0160", []], + ["fly", "0230", "0120", []], + ["power", "02D0", "0110", []], + ["fly", "0480", "0150", []], + ["invis", "0800", "0130", []], + ["power", "0830", "0130", []], + ["multi", "0D90", "0120", []], + ["power", "03D0", "0130", []], + ["yoshi", "10A0", "0100", ["carry", "yoshi"]], + ["power", "1A60", "0140", []], + ["pswitch", "1E40", "0090", ["run cape pswitch"]], + ["pswitch", "1E50", "00A0", ["run cape pswitch"]], + ["pswitch", "1E60", "00B0", ["run cape pswitch"]], + ["pswitch", "1E70", "00C0", ["run cape pswitch"]], + ["pswitch", "1E80", "00D0", ["run cape pswitch"]], + ["pswitch", "1E90", "00E0", ["run cape pswitch"]] + ], + "10B_donut_secret_2": [ + ["dir", "00A0", "0170", []], + ["vine", "0220", "0100", []], + ["star", "0250", "0040", ["climb", "yoshi"]], + ["power", "0060", "0140", []], + ["star", "0B00", "0140", []] + ], + "10C_test": [], + "10D_front_door": [], + "10E_back_door": [], + "10F_valley_of_bowser_4": [ + ["yellow", "0370", "0130", ["yellowswitch"]], + ["power", "0210", "0130", []], + ["vine", "05E0", "0110", []], + ["yoshi", "0610", "0040", ["climb"]], + ["life", "07A0", "00D0", ["mushroom spin climb"]], + ["power", "0B60", "0110", ["yellowswitch climb"]] + ], + "110_valley_castle": [ + ["yellow", "0290", "0030", ["yellowswitch"]], + ["yellow", "0330", "0110", ["yellowswitch"]], + ["green", "0980", "0140", ["greenswitch"]] + ], + "111_valley_fortress": [ + ["green", "0220", "0140", ["greenswitch"]], + ["yellow", "0940", "0100", ["yellowswitch"]] + ], + "112_test": [], + "113_valley_of_bowser_3": [ + ["power", "0130", "0110", []], + ["power", "08A0", "00E0", []] + ], + "114_valley_ghost_house": [ + ["pswitch", "0160", "0100", ["pswitch"]], + ["multi", "0570", "0110", ["pswitch"]], + ["power", "00E0", "0100", []], + ["dir", "0270", "0140", ["pswitch"]] + ], + "115_valley_of_bowser_2": [ + ["power", "0330", "0130", []], + ["yellow", "0720", "0140", ["yellowswitch"]], + ["power", "0010", "00A0", []], + ["wings", "00D0", "0140", []] + ], + "116_valley_of_bowser_1": [ + ["green", "0810", "0140", ["greenswitch"]], + ["invis", "0D40", "0100", []], + ["invis", "0D50", "0100", []], + ["invis", "0D60", "0100", []], + ["yellow", "0D60", "0080", ["yellowswitch cape"]], + ["yellow", "0D60", "0090", ["yellowswitch cape"]], + ["yellow", "0D60", "00A0", ["yellowswitch cape"]], + ["yellow", "0D60", "00B0", ["yellowswitch cape"]], + ["vine", "0F20", "0120", []] + ], + "117_chocolate_secret": [ + ["power", "04A0", "0120", []], + ["power", "0960", "0140", []] + ], + "118_vanilla_dome_2": [ + ["single", "0240", "0100", ["swim"]], + ["power", "0250", "0100", ["swim"]], + ["single", "0260", "0100", ["swim"]], + ["single", "0270", "0100", ["swim"]], + ["vine", "03B0", "0100", ["swim"]], + ["inlife", "0720", "00F0", ["swim climb", "swim yoshi"]], + ["single", "0760", "00F0", ["swim climb", "swim yoshi"]], + ["single", "0770", "00F0", ["swim climb", "swim yoshi"]], + ["power", "0780", "00F0", ["swim climb", "swim yoshi"]], + ["power", "0880", "0100", ["swim climb", "swim yoshi"]], + ["power", "0730", "0040", ["swim climb", "swim yoshi"]], + ["power", "0D10", "0100", ["swim climb", "swim yoshi"]], + ["multi", "0290", "0130", ["swim climb", "swim yoshi"]], + ["multi", "1150", "0140", ["swim climb", "swim yoshi"]] + ], + "119_vanilla_dome_4": [ + ["power", "0690", "0100", []], + ["power", "0CB0", "0140", []], + ["single", "0E10", "0120", []], + ["single", "0E20", "0120", []], + ["single", "0E30", "0120", []], + ["life", "0E40", "0120", []], + ["single", "0E50", "0120", []], + ["single", "0E60", "0120", []], + ["single", "0E70", "0120", []], + ["single", "0E80", "0120", []], + ["single", "0E90", "0120", ["carry"]] + ], + "11A_vanilla_dome_1": [ + ["fly", "0250", "0110", []], + ["power", "0400", "0120", []], + ["power", "0450", "00E0", []], + ["single", "0460", "0120", []], + ["life", "04D0", "0120", []], + ["power", "0640", "0180", []], + ["vine", "0680", "00E0", ["carry", "redswitch"]], + ["star", "00F0", "00E0", []], + ["power", "13A0", "0140", ["run star", "run mushroom"]], + ["single", "17D0", "0150", ["run star", "run mushroom"]] + ], + "11B_red_switch_palace": [], + "11C_vanilla_dome_castle": [ + ["life", "0110", "0100", ["carry", "mushroom"]], + ["life", "0210", "0100", ["carry", "mushroom"]], + ["power", "03A0", "0130", []], + ["life", "0170", "0140", ["pswitch"]], + ["green", "0B90", "0140", ["greenswitch"]] + ], + "11D_forest_ghost_house": [ + ["single", "0950", "0110", []], + ["power", "0990", "0110", []], + ["fly", "0190", "0150", []], + ["power", "0370", "0140", []], + ["life", "0640", "0160", []] + ], + "11E_forest_of_illusion_1": [ + ["power", "01A0", "0110", []], + ["yoshi", "0360", "0130", []], + ["power", "0FA0", "0150", []], + ["key", "0E00", "0160", ["pballoon"]], + ["life", "0610", "0130", []] + ], + "11F_forest_of_illusion_4": [ + ["multi", "0540", "0140", []], + ["single", "05E0", "0140", []], + ["single", "05F0", "0140", []], + ["single", "0600", "0140", []], + ["single", "0620", "0140", []], + ["power", "0630", "0140", []], + ["single", "0640", "0140", []], + ["single", "0E30", "0140", []], + ["single", "0E40", "0140", []], + ["power", "0E40", "0100", []], + ["single", "0EF0", "0140", []], + ["single", "0F00", "0140", []], + ["single", "0EF0", "0100", []] + ], + "120_forest_of_illusion_2": [ + ["green", "0070", "0130", ["greenswitch swim"]], + ["power", "0480", "0040", ["swim"]], + ["invis", "0600", "0120", ["swim"]], + ["invis", "0610", "0120", ["swim"]], + ["inlife", "0620", "0120", ["swim"]], + ["invis", "0630", "0120", ["swim"]], + ["yellow", "0950", "0160", ["yellowswitch swim"]] + ], + "121_blue_switch_palace": [], + "122_forest_secret": [ + ["power", "0330", "00A0", []], + ["power", "0450", "0110", []], + ["life", "06A0", "00B0", ["blueswitch", "carry"]] + ], + "123_forest_of_illusion_3": [ + ["yoshi", "0350", "0150", []], + ["single", "04C0", "0150", []], + ["multi", "04D0", "0140", []], + ["single", "04F0", "0120", ["carry", "yoshi"]], + ["multi", "0D90", "0110", ["carry", "yoshi"]], + ["single", "0D80", "0150", ["carry", "yoshi"]], + ["single", "0D90", "0150", ["carry", "yoshi"]], + ["single", "0DA0", "0150", ["carry", "yoshi"]], + ["single", "0E40", "0140", ["carry", "yoshi"]], + ["single", "0E50", "0120", ["carry", "yoshi"]], + ["single", "0E70", "0150", ["carry", "yoshi"]], + ["single", "0E90", "0110", ["carry", "yoshi"]], + ["single", "0EB0", "0130", ["carry", "yoshi"]], + ["single", "0EE0", "0140", ["carry", "yoshi"]], + ["single", "0EF0", "0100", ["carry", "yoshi"]], + ["single", "0F10", "0120", ["carry", "yoshi"]], + ["single", "0F50", "0130", ["carry", "yoshi"]], + ["single", "0F70", "0150", ["carry", "yoshi"]], + ["single", "0F80", "0110", ["carry", "yoshi"]], + ["single", "0F90", "0130", ["carry", "yoshi"]], + ["single", "0FC0", "0150", ["carry", "yoshi"]], + ["single", "0FD0", "0120", ["carry", "yoshi"]], + ["single", "11A0", "0150", ["carry", "yoshi"]], + ["single", "11B0", "0120", ["carry", "yoshi"]], + ["single", "1230", "0150", ["carry", "yoshi"]], + ["single", "1240", "0140", ["carry", "yoshi"]], + ["single", "1250", "0130", ["carry", "yoshi"]] + ], + "124_test": [], + "125_special_zone_8": [ + ["yoshi", "0390", "0100", ["carry", "yoshi"]], + ["single", "04A0", "0130", []], + ["single", "04B0", "0130", []], + ["single", "04C0", "0130", []], + ["single", "04D0", "0130", []], + ["single", "04E0", "0130", []], + ["pow", "0560", "0140", []], + ["power", "05D0", "0140", []], + ["star", "0750", "00F0", []], + ["single", "0670", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0680", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0690", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06A0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06B0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06C0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06F0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0700", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0710", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0720", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0730", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0740", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0750", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["multi", "0CA0", "0100", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1100", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1110", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1120", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1130", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1140", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["power", "13F0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["fly", "1570", "00F0", ["mushroom spin", "cape", "carry", "yoshi"]] + ], + "126_special_zone_7": [ + ["power", "0350", "0150", ["mushroom"]], + ["yoshi", "0C80", "0140", ["mushroom"]], + ["single", "0F90", "0140", ["mushroom"]], + ["power", "0FA0", "0140", ["mushroom"]], + ["single", "0FB0", "0140", ["mushroom"]] + ], + "127_special_zone_6": [ + ["power", "0370", "00F0", ["swim"]], + ["single", "0610", "0140", ["swim"]], + ["single", "0630", "0120", ["swim"]], + ["yoshi", "0650", "0100", ["swim"]], + ["life", "07D0", "0140", ["swim"]], + ["multi", "0950", "0140", ["swim"]], + ["single", "0D80", "0140", ["swim"]], + ["single", "0D90", "0140", ["swim"]], + ["single", "0DA0", "0140", ["swim"]], + ["single", "0DB0", "0140", ["swim"]], + ["single", "0DC0", "0140", ["swim"]], + ["single", "0DD0", "0140", ["swim"]], + ["single", "0DE0", "0140", ["swim"]], + ["single", "0DF0", "0140", ["swim"]], + ["single", "0E00", "0140", ["swim"]], + ["single", "0E10", "0140", ["swim"]], + ["single", "0E20", "0140", ["swim"]], + ["single", "0E30", "0140", ["swim"]], + ["single", "0E40", "0140", ["swim"]], + ["single", "0E50", "0140", ["swim"]], + ["single", "0E60", "0140", ["swim"]], + ["single", "0E70", "0140", ["swim"]], + ["single", "0D80", "0100", ["swim"]], + ["single", "0D90", "0100", ["swim"]], + ["single", "0DA0", "0100", ["swim"]], + ["single", "0DB0", "0100", ["swim"]], + ["single", "0DC0", "0100", ["swim"]], + ["single", "0DD0", "0100", ["swim"]], + ["single", "0DE0", "0100", ["swim"]], + ["single", "0DF0", "0100", ["swim"]], + ["single", "0E00", "0100", ["swim"]], + ["single", "0E10", "0100", ["swim"]], + ["power", "0E20", "0100", ["swim"]], + ["single", "0E30", "0100", ["swim"]], + ["single", "0E40", "0100", ["swim"]], + ["single", "0E50", "0100", ["swim"]], + ["single", "0E60", "0100", ["swim"]], + ["single", "0E70", "0100", ["swim"]] + ], + "128_special_zone_5": [ + ["yoshi", "01D0", "0160", ["mushroom"]] + ], + "129_test": [], + "12A_special_zone_1": [ + ["vine", "0020", "03C0", []], + ["vine", "0050", "03C0", []], + ["vine", "0080", "03C0", []], + ["vine", "00B0", "03C0", []], + ["life", "0110", "0340", ["climb"]], + ["vine", "0120", "0280", ["climb"]], + ["pow", "0080", "01F0", ["climb"]], + ["vine", "00B0", "01F0", ["climb"]], + ["power", "00F0", "00D0", ["climb"]], + ["pswitch", "0190", "00C0", ["climb pswitch cape"]], + ["pswitch", "01C0", "0130", ["climb pswitch cape"]], + ["pswitch", "0180", "01A0", ["climb pswitch cape"]], + ["pswitch", "01D0", "01A0", ["climb pswitch cape"]], + ["pswitch", "01C0", "0270", ["climb pswitch cape"]], + ["pswitch", "01A0", "02C0", ["climb pswitch cape"]], + ["pswitch", "0190", "0310", ["climb pswitch cape"]], + ["pswitch", "01B0", "0370", ["climb pswitch cape"]], + ["pswitch", "0180", "03D0", ["climb pswitch cape"]], + ["pswitch", "0200", "0120", ["climb pswitch cape", "climb pswitch carry"]], + ["pswitch", "0210", "0130", ["climb pswitch cape", "climb pswitch carry"]], + ["pswitch", "0220", "0140", ["climb pswitch cape", "climb pswitch carry"]], + ["pswitch", "0230", "0150", ["climb pswitch cape", "climb pswitch carry"]] + ], + "12B_special_zone_2": [ + ["power", "02E0", "0120", []], + ["single", "0380", "0110", ["pballoon"]], + ["single", "0450", "0140", ["pballoon"]], + ["power", "04A0", "00F0", ["pballoon"]], + ["single", "05C0", "0150", ["pballoon"]], + ["single", "05C0", "00F0", ["pballoon"]], + ["power", "0760", "0140", ["pballoon"]], + ["multi", "07E0", "00E0", ["pballoon"]], + ["single", "0850", "0100", ["pballoon"]], + ["single", "0920", "0140", ["pballoon"]] + ], + "12C_special_zone_3": [ + ["power", "03F0", "0110", []], + ["yoshi", "0080", "0140", []], + ["wings", "0A50", "0140", []] + ], + "12D_special_zone_4": [ + ["power", "0490", "0140", ["flower"]], + ["star", "0AF0", "00F0", ["flower carry", "flower pswitch"]] + ], + "12E_test": [], + "12F_test": [], + "130_star_road_2": [ + ["star", "0460", "0130", ["swim"]] + ], + "131_test": [], + "132_star_road_3": [ + ["key", "0080", "0030", ["carry"]] + ], + "133_test": [], + "134_star_road_1": [], + "135_star_road_4": [ + ["power", "0540", "0090", []], + ["green", "0C00", "0140", ["greenswitch yoshi carry"]], + ["green", "0C10", "0140", ["greenswitch yoshi carry"]], + ["green", "0C20", "0140", ["greenswitch yoshi carry"]], + ["green", "0C30", "0140", ["greenswitch yoshi carry"]], + ["green", "0C40", "0140", ["greenswitch yoshi carry"]], + ["green", "0C50", "0140", ["greenswitch yoshi carry"]], + ["green", "0C60", "0140", ["greenswitch yoshi carry"]], + ["key", "0D40", "0160", ["carry yoshi", "greenswitch redswitch carry"]] + ], + "136_star_road_5": [ + ["dir", "0510", "0140", []], + ["life", "07D0", "0150", ["pswitch"]], + ["vine", "08E0", "0100", ["pswitch"]], + ["yellow", "08F0", "0050", ["yellowswitch pswitch climb carry", "yellowswitch specialworld yoshi carry"]], + ["yellow", "0900", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0910", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0920", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0930", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0940", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0950", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0960", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0970", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0980", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0990", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09A0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09B0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09C0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09D0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09E0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09F0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0A00", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0A10", "0050", ["yellowswitch greenswitch yoshi carry", "yellowswitch greenswitch pswitch climb carry cape"]], + ["yellow", "0A10", "0060", ["yellowswitch greenswitch yoshi carry", "yellowswitch greenswitch pswitch climb carry cape"]], + ["green", "0A20", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A30", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A40", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A50", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A60", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A70", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A80", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A90", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AA0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AB0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AC0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AD0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AE0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AF0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B00", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B10", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B20", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B30", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B40", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B50", "0080", ["greenswitch specialworld yoshi carry"]] + ], + "137_test": [], + "138_test": [], + "139_test": [], + "13A_test": [], + "13B_test": [] +} \ No newline at end of file diff --git a/worlds/smw/data/graphics/indicators.bin b/worlds/smw/data/graphics/indicators.bin new file mode 100644 index 0000000000000000000000000000000000000000..70e7036533c98150c4b24254ea55bb0470faacc7 GIT binary patch literal 384 zcmZRuF!1r%60sy>ipLg%GYl0B=)gcBAYp<+001SYgZ~ry?XZr8_ zzvVFbS`)d@|EcoTAo+8a3=9&f>6-yfoXyV37-1||jO2h0o@ z0Azk4uYC>BHNyXK$ct;pJ7~JDjTA`3&?`~AU_iy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVW#@zquT z>p}Evxq54mJOe|ulAZm2@B7jBvl$rv1NjQfVhr*O^^EhGAo5}i(hTv8@=Wr~F!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C4p{pbK>e<81Z z4bU~h|8dBRYsfojx~`2BNJG~Rv9FL9a9WY3=9RjW==PQq~qu2Gcf!I@)elH7~~o18Rs)W^GErg%>Vr%`nMbXPG?|J)_=gvfB``Ef915V zkyYNWejjK)IsnP<^_Um9CQ&bMLKV6!#J)makYD1HWh1y4C{B8we7N9TIhZ`ce<1mli8@)AtB~ap{sYNR;<+Uf ytqGAwcOgigh3^4Vr#Wu+HAw!P zB?E&*vPu2a`X4~O5d96zALV~C|M!RJ-){6foqS0 z4nXoC*J!$~jTA^jmxb5|@@c(we6nl=7lXXz5f=`>g7j?$_f7vp-Fwb*N{~eIoZ@&L znEMd^1G)DtGAkMJLe&yl+}*RT#*9^pTbydZ-((1j3rbQgl;A22>( odH^*aoBT1ykm6tUf9pAN(Pbg-F)^`nnV%}gSPyg`Driyx0DN|Hp8x;= literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/dollhouse.mw3 b/worlds/smw/data/palettes/level/castle_pillars/dollhouse.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..06849ceaf1d56e8efed4daa1df681ef3abcb1c59 GIT binary patch literal 514 zcmZQzxLe& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}p|2rA` z8kqg)3SjO#X^|iMwH93#VqYOI$S?89vJqSi@|H(jIQ$CIw;9|w{SUMc< kz>Hh|m}5xsulm3B9J%PaA?`6Tv2vN8D#ln3bRRkZ09~Db`2YX_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/gold_caslte.mw3 b/worlds/smw/data/palettes/level/castle_pillars/gold_caslte.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f4c03d2cb17a6f69a5082b7016d2370311e332fd GIT binary patch literal 514 zcmZQzm{Z)xX~cGo$&z^v^MB^a#S9F;d0y*-=tADvatuIueHBv^YnT5K^@;XbAo0~z z|LZ~YY`J=CkURqeLO%n;|9S=n1!ge@d4_t%`AiUbkPQ&?Ve+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f88E`Wu)(%Kv2k?+?+x-RO5Z1Cs*t17-#c z05ZRj*S-em8sYyqg7j?$_f0|O zf!uS>a!Qay@|@y$9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU z2Zk3!9^Hi?`3H;-m>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYC6b0FJqQ Al>h($ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/original_gray.mw3 b/worlds/smw/data/palettes/level/castle_pillars/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37143fb2b8892a981d671d3484adc747c5a55b6b GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=D5P{ew0|iKgEMvi}3I0<#!{JVQO>d?tvz7=tuJJfl35JTpwb)nNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1;19Xk> ze;o4S8uAXBu4^L&($IB7+*8O4@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF z;&>gH`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ oj1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!X0BN&%n*aa+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/original_green.mw3 b/worlds/smw/data/palettes/level/castle_pillars/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4124e1900166d1399ba09d8b5064a1f79efbcc40 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(j^$+Sn;;XCv zgX!6F_0}MH28N?5p1NywgOw!g85lsS6_~{sDDF2iBzduC(cB9|v3``2l4}kj70m%MBUi%uL zYlQ#fkQdjGchGcQ8!3>6t{Y-sAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82XfCj%PBz; z$#aV1bzts8_zxuipHZIqKl645d!TByERXOXNM4XZ92i~@d2|D7f+HAw!P zB?E&*vPu2a`X4~O5d96zALV~C|M!RJ-){6foq*fP|1-)n|7YILU=Q>!#Qg~Wf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDuko*J22TTv3 l=3|pT<``1^tNw32M=rW7#62b^Rxa~X#Te^>?n4DivH+X+brk>r literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/original_white.mw3 b/worlds/smw/data/palettes/level/castle_pillars/original_white.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f235216f1da2e7d822ea64899b28253d7f7ab7da GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`iA-kKnN0FUG*PK z&z7sV2FWupyz%r8+7u<4ejCUJt7aBskY}i8oX-T27h{lSh-Z{%l4pj=*P6(M{!f*! z2FahZWMGg;HmRRl{{ybSf%&8SPv-yr5dGVYey1}qDKI|(>PH74`wMyPYk;m1{*Oam zTtnVL({*j6KpMJkh36e;jQyi}Y zb3ej=Ao>4{^34C4w=>uS{R{Cw!haw>NAB8O!#ZSng#SSDf(+uo@Pf#ryAUM*fbjv- n1E~4fvwiHViV{8TZJstYLNUnO9lpsWRv=-^*?}mA^IDbKg$1P{_hXbzuo9}Is=me^8=uMbO5rykk`Hj z=o;bwION4O8_2KP-t=7HRE&T>kS zMDm>CcpaGg5dH(n|7VnE{?ELf!5-*ei2D)#1MxX>*XA15A}FR tAo&N3511Z6&BrEx%rT_+SN-35j$CwEhU41pv|Hd71zK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/purple_pink.mw3 b/worlds/smw/data/palettes/level/castle_pillars/purple_pink.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5bea72483006a71241be6d52843c36883e07f1b8 GIT binary patch literal 514 zcmZQzm{ZK5!|lSKAXKugrlNLoF$2SIp4a*yx{!Ca90O2ZU&YkK+U0*leWHC9NPKnG z|9TKTTdv+3B+tNrP{F|Pzn+0Xfmw_}o}r#`J`+S9Vgty0n0&2?Ty8QeDonFn&uIm;u4nX!7^4iw` zT_gM-hrGCkyo09e+DL&kblnj93VA_(iBFb|;9`)sJmSLPSCGEV;JzuyJdk_NSxyO( zNS;$1uLE-*!haz7|BUj?|CzTl*aQ6waX-R;AU;R#+FZjrWO;=DK=OhN;=u5N$fLUu rB>#Z%0n-Dh`Pk%-IffMfs{dQhk&7-1agT|KmCO88F~)kJ`_KUZh7Wj` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/sand_green.mw3 b/worlds/smw/data/palettes/level/castle_pillars/sand_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5757d8fbfaa1584b28898d2532f44253d37fd77e GIT binary patch literal 514 zcmZQzxLdzmz(;YTZiv}7I}7j0#S9F;d0y*-=)3jxx*&3qf~szYX_x5}l^`#W`0A?v z^&on-T)j0&o`IoDR8MWA)n~U*Zw7|{K)wRA7=t`RJ>z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1; z19Xk>e;o4S8uAXBu4^L&($IB7>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 sLXi9e#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G0D^aT0RR91 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/shenhe.mw3 b/worlds/smw/data/palettes/level/castle_pillars/shenhe.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..93dc170a525e495da6d936d262e48c0a4db03d74 GIT binary patch literal 514 zcmZQzxLdzip^qm(RoL!hRC)H~Vg`oaJg@aZ^xgV;T@blQK~*=yw9E8~N{|;we0A0T zdJsKZuHG6X&%n^4tL3yIEHwKzko_Ns6_~{sBm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/whatsapp.mw3 b/worlds/smw/data/palettes/level/castle_pillars/whatsapp.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..198f46eca8a90c9fe2ebbad1e53e254daa9d12c0 GIT binary patch literal 514 zcmZQzxLd!7gPGSwaH;4sNp-o&#S9F;d0y*-=)3jxx*#%h<(PNSz@zquT z>p}Evxq54mJOe|E;8VfVdb!5e!WkI;1NjQfVhr*O^^EhGAo2>#4a^_qe=`5~hsoEP z$c6q-m9GZLpR;6OkVrPEpIZL|s28Fip&n%ZcB9|v3``2l511J+07$-&*S-em8sYyq zg7j?$_f0|Of!uS>a!Qay@|@y$ z9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU2Zk3!9^Hi?`3H;- mm>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYkR=M0!a8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/dark_lava.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/dark_lava.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..477701e86a9bda2b95fd8b41c15acc4eedccd012 GIT binary patch literal 514 zcmZQzxLcnl(joFnCD-)4t6|vWVg`oaJg@aZ^xgV;T@aZnkSdU2x>}-5;GGOee0A0T zdJsKZuHG6X&%kg(hEr8bH{W!VB?H5MAYXx5j6t5Eo^d`CM4pRbBSQtFJd->#Oup7c zF7$t@d^Je^98i};vPu2a`X4~O5d96zALV~C|M!RJ-){6foq*fP|1-)n|7YILU=Q>!#Qg~Wf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDu tko*J22TTv3=3|pT<``1^tNw32M=rW7#62b^Rxa~X#Te^>?n4Es0syKqc#Z%7 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/dark_purple.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/dark_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..29eff5aeffa30eeb48f5d158f3cdcba0f81e99d9 GIT binary patch literal 514 zcmZQzxLbczfnC$XFw1ha<6Ga!#S9F;d0y*-=)3jxx*#$|L{&G#ludPs#2H7B`0A?v z^&on-T)i~|!}cr&hNTk6HMmTD9M^|3F#NA)U{GKdV~}U4XPnQ(z+jQhz~Ijj!ciu8 zMX-zyCSPkJ7y3U{z8WO&rOCih7^WMyG3^V`EQtOF=8y6}ng9Dk^lvx%ovzILOtHg= z0Rw={*ORob`K~%k&Q>2o8f<@o$To>%3L2)}f!O7JL__6nYsDwaMsP96TOM)Y@GD5) zW^mv1KQqLB4#gfr0jDit+eKjRL-k*=sD#*Tq4gZs1Yz>X{$pU^$X%OjScfc+>OUWj z0FG@U5P5VLg8awAo508-hFgB6>E|T#?5g7P!0<;0ApIsLRxa~X#Te^>@qr4QH~`L| BZ(jfa literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/dollhouse.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/dollhouse.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..73f69240205bd32096a8b301ad63b7d04578b653 GIT binary patch literal 514 zcmZQzxLez^Ph`g1|ZVg$}d5-^mVe+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{~!~3d|3f8886I{&zC=H8A_p6~Nqg(jq_h zYc0Ag#J)makYD1HWh1y4RiKaf1c z?(GcrK>tGAkMJLe&yl+}*RT#*9^pTbyr~2zd?51ZE(FOxV0^&zfEl;^F~^YNU-f_M YIdai;L)>FxV&yVFRgAG7=st7+0M!V9?f?J) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/forgotten_temple.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/forgotten_temple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..35d97033f84735c0b0cf9a3d60736834483f3622 GIT binary patch literal 514 zcmZQzxLe;MaZDjgm(OgIQ<(SUVg`oaJg@aZ^xgV;T@blQK~*=y^r^}c2{m1i`0A?v z^&on-T)j0&o`KV@cUVE!oolli|tME`c9-{}lY3d|3H`q2T%{z6{+ z8lY>0|KpGs*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{4vLn;$QWD>p61KWg+e{F|l%)pDM;!4|E?YNEQJAM7((c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/original_gray.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37143fb2b8892a981d671d3484adc747c5a55b6b GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=D5P{ew0|iKgEMvi}3I0<#!{JVQO>d?tvz7=tuJJfl35JTpwb)nNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1;19Xk> ze;o4S8uAXBu4^L&($IB7+*8O4@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF z;&>gH`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ oj1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!X0BN&%n*aa+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/original_volcanic.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..21d82d7c84a14040cc3cd7c5df0feac6bf07fd93 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=BtAJayOV1}jO}Gcf!I@)elH7~~o18Rs)Wy8QeDonFn&uIm;Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=AhC&lR1vG_c$3#lY|%$X8$%V~}U4XPnOjkr!i-W{78$XOd@z$=905 zh5k>KuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KuZ>{o9Rxr!z1qFh2n5M+YGL3wiBp zfUXh#k3(KuL*7Btb#0_T8oF+XeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p=PaiL zNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM96#~ee7f7So3=g38ug}BGW#L8uUsu*KE(0%9t0F}Ubvj6}9 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/sand_gray.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/sand_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5b11808ae6b95c2de07c589fc05b2662555af87b GIT binary patch literal 514 zcmZQzxLdzmz(;YTZiv}7I}7j0#S9F;d0y*-=)3jxx*&3qf~szY>3P)`B4u_U@zquT z>p}Evxq54mJW#|jAnZ$;X?9pP1H*qHUx8VSL7t(WaXu47UW`GSA)Zm5NuC)dUuz;4 z`ae~^8YF+tl7T@Y*`$7I{STmCi2erVkMcj6|NBGqZ#Vj#&cLL=`~avQ9f0gF zx<>dv4ta46c?V6`wUGj8=(-{H74m}o5}zy^!NnkNdBlaouONM!!F^MZc_8z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1; z19Xk>e;o4S8uAXBu4^L&($IB7>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 sLXi9e#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G01k0?!TBm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/water.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9822a3c2eaa268eda71e8bfbd8ae929ccf3836e0 GIT binary patch literal 514 zcmZQz*j0W+ro-@+t&Urs-DR)I#S9F;d0y*-=)3jxx(q=1MGC6A8K&u)iv?cWg2Y!> z{jUenv*qfoLGnpFZMx5Gy@S>#iDWY{{I6$VP+%5gkY}i8oX-T27vZ$v{mb}|t(F-k zUuz;4`ae~^8YKV5k%7T1ePa2R>K#BnM1KSGNBN)3|NSBQw;TOVf2e!F?twD{1_0Ur zLD0VDmRz*@ZG8-Bu=_T1SV?*qz7Eul!!DmEIYTejIX+o7f{Q`k@`wwEUqSjdgZrlc zf$jyF&*QW?%q)FIajh%NeW?Dk7OLmJrynAChYco=>_1@m1p4KDtV5PZ^9a+#kh##j%G4^)uE0{~N!gh2oR literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/whatsapp.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/whatsapp.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..198f46eca8a90c9fe2ebbad1e53e254daa9d12c0 GIT binary patch literal 514 zcmZQzxLd!7gPGSwaH;4sNp-o&#S9F;d0y*-=)3jxx*#%h<(PNSz@zquT z>p}Evxq54mJOe|E;8VfVdb!5e!WkI;1NjQfVhr*O^^EhGAo2>#4a^_qe=`5~hsoEP z$c6q-m9GZLpR;6OkVrPEpIZL|s28Fip&n%ZcB9|v3``2l511J+07$-&*S-em8sYyq zg7j?$_f0|Of!uS>a!Qay@|@y$ z9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU2Zk3!9^Hi?`3H;- mm>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYkR=M0!a8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/cheese.mw3 b/worlds/smw/data/palettes/level/castle_wall/cheese.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..913ad39778755fba300dd2b321b776473ff1f906 GIT binary patch literal 514 zcmZQzxLfZlc~RBW=%&>?`~AU_iy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVW#@zquT z>p}Evxq52`hW)_|4An|@_WQl>N8it8VEA9pz@We^#vsp7&p4k6q@RHSVFOIQ)nNdBB91A|1eN&VFNA3(Jb{SC|?<$p5&_lM};ZuC2yfk}b+0W$*z0GVINYhMF& zjqraQ^5Pou4w|lOBL&hhw1e#{`=%iCK<+tbIVDIU zc}{V>4$OTB|AFNHGs-jnXWq_W5A-j@{Rsbo_#C-wa}Dc|z^Ph`g1|ZVg$}d5-^mVe+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{~!~3d|3f8886I{&zC=H8A_p6~Nqg(jq_h zYc0Ag#J)makYD1HWh1y4RiKaf1c z?(GcrK>tGAkMJLe&yl+}*RT#*9^pTbyr~2zd?51ZE(FOxV0^&zfEl;^F~^YNU-f_M YIdai;L)>FxV&yVFRgAG7=st7+0M!V9?f?J) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/grand_marshall.mw3 b/worlds/smw/data/palettes/level/castle_wall/grand_marshall.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..574d557f1eadc3cf447eff1f3bce37bad9cd25f9 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?OC+8taAKA^_iCET;rXB+n_1 z*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sVg> ze;o4S8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~NFsSo zal8)9eF*=7yYIU{sYMiGKd4i3nGv1LXi9e o#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G0L;vM0|KpGs z*N}J6bX^-MkcO@s;+{fYkYD1HWh1y4h`dNd7;gJoA6%?F{xn|3dta@E?fJk-Ik6unt)s;XjbPAcHtCydd)EE(FOxV0^&z l0BSxq`D2bD#lPzR)^p^d%R<~^Vq)boKUIve9_T)F002widoTb1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/sand_green.mw3 b/worlds/smw/data/palettes/level/castle_wall/sand_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5757d8fbfaa1584b28898d2532f44253d37fd77e GIT binary patch literal 514 zcmZQzxLdzmz(;YTZiv}7I}7j0#S9F;d0y*-=)3jxx*&3qf~szYX_x5}l^`#W`0A?v z^&on-T)j0&o`IoDR8MWA)n~U*Zw7|{K)wRA7=t`RJ>z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1; z19Xk>e;o4S8uAXBu4^L&($IB7>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 sLXi9e#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G0D^aT0RR91 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/shenhe.mw3 b/worlds/smw/data/palettes/level/castle_wall/shenhe.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..93dc170a525e495da6d936d262e48c0a4db03d74 GIT binary patch literal 514 zcmZQzxLdzip^qm(RoL!hRC)H~Vg`oaJg@aZ^xgV;T@blQK~*=yw9E8~N{|;we0A0T zdJsKZuHG6X&%n^4tL3yIEHwKzko_Ns6_~{sBm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/water.mw3 b/worlds/smw/data/palettes/level/castle_wall/water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a0955e820349da62f84748d19bd6cd4ad55b6f55 GIT binary patch literal 514 zcmZQz*j0W+ro-@+t&Urs-DR)I#S9F;d0y*-=)3jxx(p2A*^5P<$gnB8Yj&ve=z+vn zSN*RC(X-|1tr>v&lX%*6pWAu|txpokW?=YV&%mI-EXE+uP|rA@2_i4TX~FxK@gG|) zGfckLL@xAys(dv_{*5C8gIW5-@-5XnfP9Gl2Ii0QKbimgL-cPq`knqz_ki64X9f%a zvj2miea$VoX!YCr7}8+(ZRW6&^e}uKs2hh}K2LInUaWI`vTOtwgS_Pt7Y@II^lb+B zP5%Sk3o@U_X>*ua`i$aQSD5=y{bwyy&wo!pMDPw9Odi>P!0-w5%llY|ERX8HevWHA zGQ1FZbQgl`Kd5=otT`OF{7TD@N#)tQ%k{$0bwk{5Vq)boKUIve9vB~}Ac+S6-r|Dm literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/brawler_pink.mw3 b/worlds/smw/data/palettes/level/castle_windows/brawler_pink.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..69c496fc5d07949af94a1b203eb275589a595434 GIT binary patch literal 514 zcmZQzxLfaI5a>B2^hTsj+PXNJkw zn#hIzPnE9*$)B@iV30^Qsh?W^1E?0Fzk&Iq{7>fp{t*4!jee&yFexxU0P05vAoB}( z?Q4Lp5&n-uUR*=oLDO|@q(BRiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyZ1Tq(LyCXZ|E=f9MVE!R$Hc_SWqzs{V?EG)=l}o}uzREc literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/cheese.mw3 b/worlds/smw/data/palettes/level/castle_windows/cheese.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d91826e8647a061c9dcf6971e7b05f325e531e43 GIT binary patch literal 514 zcmZQzxLfZlc~RBW=%&>?`~AU_iy0Vx^Sssv(Rb_XbwT7Z9&O1S#S5y!T0fjX;;XCv z*MsQUa`o0Ac?O1RB|H25-uI*LXEQMT2l5q|#Teun>KW%VLFC04q#5EF<(cG}Ve+*m za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3H`q2T%{z6{+ z8lY>0|KpGs*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{4vLn;$QWD>p61KWg+e{F|l%)pDM;!4|E?o002{;d5-`9 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/dark_aqua_marine.mw3 b/worlds/smw/data/palettes/level/castle_windows/dark_aqua_marine.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..501f11d1a95448f15b53c23769d3654c04e315cc GIT binary patch literal 514 zcmZQzm{ZKaut4O11cPFN?g7imK;Cbj*ZLs3kaxBm15n<9qd@?s0wlh=>VG|mo-J2z z4U%VIfaq0lTM)*;@V}mcL4jF}L7t(WaXu47UW`GSA)Zm5NuC)dUuz;4`ae~^8YF+t zl7T@Y*`$7I{STmCi2erVkMcj6|NBGqZ#Vj#&cLL=`~avQ9oz-#DdhcM40Mg~e;o24 z*J!$~jTA^j*A1}`xjQ k$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYFfh0I76y&;S4c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/dollhouse.mw3 b/worlds/smw/data/palettes/level/castle_windows/dollhouse.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d3e5fe4b407f7e9f907cd0496673edce9147644b GIT binary patch literal 514 zcmZQzxLez^Ph`g1|ZVg$}d5-^mVe+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{~!~3d|3f8886I{&zC=H8A@b7#=V#m(8~{ z3xz0!x$mS!e(cv;4DDe13VA_(iBFb|;9`)sJmSLPSCGEV;J)d9pnV|soU@z~B#}I) zI9>#Z%0n-C! i-15g9LyCXZ|E=f9Mb`~+kBNzu%luR^#(JRp&;bA^{)5H< literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/original_brown.mw3 b/worlds/smw/data/palettes/level/castle_windows/original_brown.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0bcc7305b7aa91c58bc8ebdca8f486cecebe6572 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 vg&_F{j1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!T4yynFNDg@( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/original_gray.mw3 b/worlds/smw/data/palettes/level/castle_windows/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37143fb2b8892a981d671d3484adc747c5a55b6b GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=D5P{ew0|iKgEMvi}3I0<#!{JVQO>d?tvz7=tuJJfl35JTpwb)nNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1;19Xk> ze;o4S8uAXBu4^L&($IB7+*8O4@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF z;&>gH`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ oj1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!X0BN&%n*aa+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/original_water.mw3 b/worlds/smw/data/palettes/level/castle_windows/original_water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d61f6dba36f6fca7c93c3764bd9a43c16022c64b GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H*~Pb48~u4eU01F);iG@)elH7~~o18Rs)Wy8QeDonFn&uIm;87j zo*5=zYa$o=KUKaOB!AA5fk7hKq<(7s51?9z{s!ic@;{mX`$P0^H~O8Bm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/underwater.mw3 b/worlds/smw/data/palettes/level/castle_windows/underwater.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..db5c1a996ccb3c7dd95ec0caee5079dbb9474eb4 GIT binary patch literal 514 zcmZQzxLe;Mb5w=R(A_e_ai!& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&E literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/water.mw3 b/worlds/smw/data/palettes/level/castle_windows/water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a0955e820349da62f84748d19bd6cd4ad55b6f55 GIT binary patch literal 514 zcmZQz*j0W+ro-@+t&Urs-DR)I#S9F;d0y*-=)3jxx(p2A*^5P<$gnB8Yj&ve=z+vn zSN*RC(X-|1tr>v&lX%*6pWAu|txpokW?=YV&%mI-EXE+uP|rA@2_i4TX~FxK@gG|) zGfckLL@xAys(dv_{*5C8gIW5-@-5XnfP9Gl2Ii0QKbimgL-cPq`knqz_ki64X9f%a zvj2miea$VoX!YCr7}8+(ZRW6&^e}uKs2hh}K2LInUaWI`vTOtwgS_Pt7Y@II^lb+B zP5%Sk3o@U_X>*ua`i$aQSD5=y{bwyy&wo!pMDPw9Odi>P!0-w5%llY|ERX8HevWHA zGQ1FZbQgl`Kd5=otT`OF{7TD@N#)tQ%k{$0bwk{5Vq)boKUIve9vB~}Ac+S6-r|Dm literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/whatsapp.mw3 b/worlds/smw/data/palettes/level/castle_windows/whatsapp.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..198f46eca8a90c9fe2ebbad1e53e254daa9d12c0 GIT binary patch literal 514 zcmZQzxLd!7gPGSwaH;4sNp-o&#S9F;d0y*-=)3jxx*#%h<(PNSz@zquT z>p}Evxq54mJOe|E;8VfVdb!5e!WkI;1NjQfVhr*O^^EhGAo2>#4a^_qe=`5~hsoEP z$c6q-m9GZLpR;6OkVrPEpIZL|s28Fip&n%ZcB9|v3``2l511J+07$-&*S-em8sYyq zg7j?$_f0|Of!uS>a!Qay@|@y$ z9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU2Zk3!9^Hi?`3H;- mm>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYkR=M0!a8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/brawler_dark.mw3 b/worlds/smw/data/palettes/level/cave/brawler_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9197e99e15d5423640d948542383bfb75363a3f6 GIT binary patch literal 514 zcmZQzxLeP_;KVhqv@@r?3J^2{*#S`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV37-1||jO2SEMk0Azn5uYC>B zHNyXK$ct;pJ7~JDjTA^j*A20+kQd~a_+;4#E(UqaBQ6|%1?k%i?wf+l1G(p%<&+?a zz^Pi2Ml=PT3sI^QJ~lF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&Ek{G%$Kx@&cVl_czu-H(u8r)y-l&Fg2-XP|!ooydZ; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/brawler_red.mw3 b/worlds/smw/data/palettes/level/cave/brawler_red.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..e3f5cdfaaf02453750d8396e33a511a4682070f2 GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@X1@;HHF);#^rdsW>T+`0A?v z^&on-T)j0&o`GRKZ>64*WVzx`Lk5QbK)wRA7=t`RJ>z^Pi2MYWDI7aEZgc$OfXUaI z$c6q-m9GZLpR;6OkVrPEpIZL|s28HYf%&8SPv-yr5dGVYey1}qDKI}^X21X-`wMyP zYk;m1{*OamTtnVL({*j6KpKX2uziKRAiu;X%SLc9$Xgz9;qWU+-)3;%6l5OAJ?AW^ z1W6>%DUR2Hxewt#koJstYLNUnO9lpsWRv=-^*?}mA^IDbKg$1P{_hXbzuo9}db%u^R*n$^1_0S#$ZKB% zbdB(TW(;Yt`@}Wm9W-6nMhc{1%7g4H`=%iCK=S7- zrvynP&nb@Afw>RiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyZ1N784{RHP5auJ|6WRR;`E|NRcH6vu27Lzl2LO!egcbk* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/bright_magma.mw3 b/worlds/smw/data/palettes/level/cave/bright_magma.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..92e059d863c9d8cd0611069f71cf58ca5981004d GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@cB_;K9(tAjG(xv5E;KzPjpv zJ&2wyS8olHXJ9zV;V0Q9@& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&Ekd_;UAyB{IHPS?n8o7c~v&p`hG0DX~xDF6Tf literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/dark_red.mw3 b/worlds/smw/data/palettes/level/cave/dark_red.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b2cc6068059096dcbd367b40890b7fcb95dc6c47 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dNalgg3K(;>j4sHX}NUtRUT z9z@TUtG5QpGcc%W_(`@1XsE7rWnlOZKuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KuZ>{o9Rxr!z1qFh5{szyKio3wiBp zfUXh#k3(KuL*7Btb#0_T8isbTeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p=PaiL zNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM964w?^a8-ft#BjOX;{RsJWx<+=}ynY6K2Kom8j$nSo literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/glowing_mushroom.mw3 b/worlds/smw/data/palettes/level/cave/glowing_mushroom.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5b5531c7ef2d07ee1f87979af80c1999de696da1 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dM};$_np&=SL5*j)+|UtRUT z9z@TUtG5QpGcXubU9EF5Xb;((!@%$#$X8$%V~}U4XPnOjk!R&x!JBO6;@%k!ldm^GErg%>Vr%`nMbXPG?|JV1B^NfB``E7xLQI z09_;eABViShP;EO>)J?xGz{%v`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKmH&RI?g zl1QFY9Ipd&AHshi`Tvaa%>S9UGuQ+D3voZfe;__b?%G_#I%Ii-|3LDB4C27>g2LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! p|A6rU(*vma*yJ5FAJ{eoAd&PAbAFc3q|GSrqwg*Z`U(0{0CwMW-$hNhI+>NOc42t5}~p?WUI~osKMlG zP2@uVr^;7@he3GF!v+-Z>dBiU!3wjaZ|y0N-%k3|1FhJk6jU~U4|^*!2D7E zC-Z-Qi242sQ95&NA@b-h1o^L7u|+G@7`J?@p{Cc`IEXw*{WAqG%^t%Kx?8;<<{S8l Wr$=QYI#{KT7A}GU;qHv>5^{% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/magma_cave.mw3 b/worlds/smw/data/palettes/level/cave/magma_cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ca297deb25781716a786e000559beb2390427ff0 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@abhk;&oB;mZ-kVa5UyUtRUT z9z@TUtG5QpGcasZykphPaf#zQ3j@P{AYXx5j6t5Eo^d`CL_UXSlfZP&Jz{qHF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&Ekd_;UAyB{IHPS?n8o7c~v&p`hG07lq>ng9R* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_chocolate.mw3 b/worlds/smw/data/palettes/level/cave/original_chocolate.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..db2693d6be989e7dbabb14dd22f313cae46319dc GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 rg&_F{j1QO|K+VS{@1Xg>wjl^%J|aGm-H(u8r)y-l&Fg2-XP|!o@5h1T literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_gray.mw3 b/worlds/smw/data/palettes/level/cave/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2e01f09820c82ddd4c95fe1fd2e947082ee462a3 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kgZ@?6nrO9Q*jUJMNXfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 rg&_F{j1QO|K+VS{@1Xg>wjl^%J|aGm-H(u8r)y-l&Fg2-XP|!o<;Z~^ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_ice.mw3 b/worlds/smw/data/palettes/level/cave/original_ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..6d17d16efefb033235542a15d3997fd2f80ec50b GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWsU z`_Qd<-^(oPtLyjHGcf!IVg+U~26={h#`#Pjc?O1)I)09qLo4!R;qtX6a-siI<*PyR z=PVf*B$7?)r`G>~%QrB8l>f>6-yfoXyV37-1||jO2h0o@0Azk4uYC>BHNyXK$ct;p zJ7~JDjTA`3&<=KQAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82lCH3%PBz;$#aV1buj%0 zlK;;r&-|ZxJA*yYzYzZ;{0HK5k{G%$Kx@&cVl_czu-H(u8r)y-l&Fg2-XP|!oT`7oS literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_mustard.mw3 b/worlds/smw/data/palettes/level/cave/original_mustard.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..001ed133195bd4ed71a53ceedcb86de5716982b8 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%p4Kfmf-I)mUn~5d*`2AYXx5j6t5Eo^d`CL|%+RnjxN1o=Kh=CSPkJ z7y3U{z8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2AHHA02?~FXXka z0lG%`KMr|u4S5Gm*R_!XY3RBk_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~ zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 rAxQoK;{&D#Q1h|LJ7_+zZ3se`kBCoX_ao%j=^EK>^ZFU|8R#DXk$itI literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_volcanic.mw3 b/worlds/smw/data/palettes/level/cave/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..96befdfa3d55f38e0766ba31d0118b851dd59beb GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6XkF1}8;XjbCz%0ff&rr`ep9vx_#vsiQ&nV9%&kU2VHIWPbpDJGsl0Rq3 zz#x%qQa`o+2T(6We*^PJ`Jc@H{UQ3d8~sjaU{YXy0Mw5TK=v2%+SdSGBm5tSytszE zgQn}+NP#qT-4OcLsJ19Kn3 ze<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6!|A6rU(*vma h*yJ5FAJ{eoAd&PAbAD`zBsY!LRE9UTYd}-|LYkT6qv;rN$Q#dGA!nmM*BD(EqJO4|jN=MV?xemv?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV z;J)d9W{CcGu3O?P^Q+76gu>j9?7yWFk$iE=`@~HJ=PAMDk^Q$+LOpgxtacf)d;{}G z`Jc@H{UPT2D@5tcwS~x|yAb5RX2lk*RAb!ot%jOjYvUmDAob4_yfk|ZKj?1tf|zgM XC!QXajga?NU8^f(7Haibhk*eA6NZpJ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/toxic.mw3 b/worlds/smw/data/palettes/level/cave/toxic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a78538ddba77375eaf781000fd04c3c71426fffa GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dNa(#mmMfI~D}a*ZTNe0A0T zdJsKZuHG6X&%nUtv?eTDR$uRm8w10CAYXx5j6t5Eo^d`CL|%+RnjxN1o=Kh=CSPkJ z7y3U{z8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2AHHA02?~FXXka z0lG%`KMr|u4S5Gm*R_!XY3RBk_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~ zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 rAxQoK;{&D#Q1h|LJ7_+zZ3se`kBCoX_ao%j=^EK>^ZFU|8R#DXq4|G8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/toxic_moss.mw3 b/worlds/smw/data/palettes/level/cave/toxic_moss.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9afe6110309837b40676296ef6a9d03876b2d874 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@cA2kSMWO=BNsrW|}TYe0A0T zdJsKZuHG6f&v25%Pjab*yQ!xc1H*ryc?!&84Dt;1jPscw@~pDyiZ3)fl(@BF^0g*% zq5o6mt3mSTEEyOil1=KT*8c$Nh3Ic!{wV*G`M*Cz|8}F_=?qK?%nz6uFaXH?g}nAP zK-UQW$00ASA@88+x;9cE4MRKFzCvD*U*eNxBe)pkEswZx_!XpYGq`UGG7sdQbCy$r zB$DS8$Lqk{hwvXr{y(EU^MB^;4E8|(Lfnt=ABfM9yEfOb4p|=IKajj2gE%m}AoA!g r1j#>Oe8BVoYCbl32h9hz4M7O=5%G!aeuVrwT_d|~UO$6A1N{R4q3?i> literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/bocchi_rock_hair_cube_things.mw3 b/worlds/smw/data/palettes/level/cave_rocks/bocchi_rock_hair_cube_things.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0fb33b2d6a380aa40e8ca073ed8350eb9bd2ac53 GIT binary patch literal 514 zcmZQzxLfZn6QX17`Y&=`l6>~$Vg`oaJg@aZ^xgV;T@dLZF+n54w9E9P&EHUv`0A?v z^&on-T)j0&o`K;+K=ywiR$vxmkY}i8oX-T2hnT+~Xg*B7)n zNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%yS50|o%uU&w1;19Xk>e;o4S z8uAXBu4^L&(lE4x-CM{D@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF;&>gH z`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{j1QO| WK+VS{@1Xg>wjl_&eu(?A%L4!^5PVbs literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/brawler_volcanic.mw3 b/worlds/smw/data/palettes/level/cave_rocks/brawler_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5a3cf230f04df8c4c561247c61b8355424aecf8f GIT binary patch literal 514 zcmZQzxLZF>=epuwArsXbI=6Hu7c(&Y=6S6TqVLw%>w-uD$J*-cseHa~JjxS6;;XCv z*MsQUa`o0Ac?O0PDn7a!bR(6d?HL&U1NjQfVhr*O^^EhGpzy8QeDonFn&uIm;d&PAbAD`9?4QG)9M-Zx9b@g{sXZBvlxRsLp|esCW!n+iBQ=cvejmP)L`

    z*`Q1+)(x zfaD8#)v$+8h#4DyyoTsZs+(zhAh zH~r5H(f`hMOPpnXb@`o8nER3aw^SmMFHU)%xT)YgC73+2|CUOq$F7LgE<=`YVE!oo zlli|t#C(5+D4n^s5P5VLg8bL4*rJtcj9nfS-VT}%Y#V~MGuQ*e8y$e;&sk0hl1Rob F4*)fog=hc( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/layer_2.mw3 b/worlds/smw/data/palettes/level/cave_rocks/layer_2.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ff354e34fefadbbee66984960563071d2397b343 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=AhC&lLfA!vC2W82$tK3d~{*@(lHi^O+#>Vhqv@@r?3J@^JZD6S>g; zsq)nz`E!;G3=+vE^;7GA0QExjH!y#c|H=H{AEJM|(eHEyCI#jPK>g?dWPc&AeGSZh zZ1Un7@(!A=Ya<2H&}AX^74m}o5}zy^!NnkNdBlaouONM!!F^Mhf6iG>36e;jQyi}Y zb05NgAou=flxP0Wyq&=w=wFEY5&i@5Ida$L8rC7pBm4)F7i16zh8IL0-Gw0e2aFGx X9ze~w|Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=AhC&lR1vG_c$3#lY|%$X8$%V~}U4XPnOjkr!i-W{78$XOd@z$=905 zh5k>KuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KuZ>{o9Rxr!z1qFh2n5M+YGL3wiBp zfUXh#k3(KuL*7Btb#0_T8oF+XeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p=PaiL zNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM964w?^a8-j4_hqxcRJOD($c&PvY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/original_mustard.mw3 b/worlds/smw/data/palettes/level/cave_rocks/original_mustard.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8150d4687553f90c4e65aea1f8b15d570836f2f6 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%p4Kfmf-I)mUn~5d*`2AYXx5j6t5Eo^d`CL|%+RnjxN1o=Kh=CSPkJ z7y3U{z8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2AHHA02?~FXXka z0lG%`KMr|u4S5Gm*R_!XY3RBk_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~ zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 dAxQoK;{&D#Q1h|LJ7_+zZ3x1xAL4%O@&K0!ba?;( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/pyra_mythra_ft_pneuma.mw3 b/worlds/smw/data/palettes/level/cave_rocks/pyra_mythra_ft_pneuma.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8f2b2817d8054bed1bdfd8d9073a69fb4710f227 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*}`)yv6iy0Vx^Sssv(Rb_XbwMPfV54M)X_slUVyh8Ie0A0T zdJsKZuHG6X&%kgZ@?6n6k((0xOc@yd1NjQfVhr*O^^EhGAo2@&oF$hiy6Umn!{lpC z*fj9Jyd&PAbAD`9?4RxLRE9UTYd}-|LYkT6qv;rN$Q#dGA!nmM*BD(EqJO4|jN=MV?xemv?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV z;J)d9W{CcGu3O?P^Q+76gu>j9?7yWFk$iE=`@~HJ=PAMDk^Q$+LOpgxtacf)d;{}G z`Jc@H{UPT2D@5tcwS~x|yAb5RX2lk*RAcP&pzwCkd|=xUw4K2o7~bdrB!A9wN{~b{ Gc6k7U?u2Rp literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/toxic.mw3 b/worlds/smw/data/palettes/level/cave_rocks/toxic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4f98b11bc175b53cbc37e5cb633966e404599c40 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@bmPp}Evxq54mJOcxl)0(hsS$(}LZVU|nfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zT;v);a8Bp&EUT2f2e!U zSxyO(NS;$1uLE-*!hazB{~6_(|1)oAum}1V;(mnxKzxqewYi3M$npsPf#d}l#DU=j gkwNOd$DV5~no`4J%Be++>1b z^0g*%q5o6mtAYITbC$;>B$B0){wB@OhRHWDf0X~p{NEpSFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^1{^s?wEXP literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/cloudy.mw3 b/worlds/smw/data/palettes/level/clouds/cloudy.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b7d07d348c420a90492bdd105f53ec279a624841 GIT binary patch literal 514 zcmZQzkjVD5>UKKo#UC6PJvn)DF$2SIp4a*y`fh!_E{ObJ&%mHi-%$Sm2tnejtNw%O z*>d&P3=Ge8isKB!-utfg%y!hYWMBZRW)@?RXQ*eK&jixXz;IH>&+&3-MV>5>4Uw-k zkqiBwDqqb2v|rPdfuT6cr2bmk_cWM%1M^4upUnULA^Nu){Z40KQeb|-%zyzv<`?qX z*8p83{2zzBxQ4ugrt8{B0igNl0OX!RUXWknlVu~g800OFxN!Itq;E60ZwfLGB!A9w zN{~eIoZ@&LnEMd^1IhnqlxP0Wyq&=w=wEdIf%qJ`YjX|jkmV8n1IY_Ahy%k5B9F~| t4;UXXJ%E~zP5!8gr|w$aU?mBAbXkyllvR(Is@k==J@=D$K$pLk1^_(oe6auk literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/cotton_candy.mw3 b/worlds/smw/data/palettes/level/clouds/cotton_candy.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f9ddeb89c87de921311bde0f2cb67ef47e9d5d3d GIT binary patch literal 514 zcmZQzxKr}Q@k1h*xkFx3!Q&qb*x95KH4(Rg6^#CM7eq;au literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/original_green.mw3 b/worlds/smw/data/palettes/level/clouds/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..79af508740ade55e9958691a077933c26c99be22 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGtfIx8{8>v#hVK-wRX;R?RHNAkR?GIG+h5&%kg}$ItO{XhohZT)x&sF7$t@ zd^Je^oFxN;M6yZ!)cPNA`3B~X@;{mX`$P0^H~O8`=%iCK>j&rIVDIUc}{V> z4yOM=^8Xp-ng26yXRrtQ7vg_}|3G|>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK;{&D# nQ1h|LA64#(3v!RL>Jd{_yEeDye)10J^1t%|+hKuC literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/original_orange.mw3 b/worlds/smw/data/palettes/level/clouds/original_orange.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..453b717b9038d5e68d016baa245dbfd5cd5c9e22 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGtfIx8{8>v#hVK-wRX;R?RHNAkR?GIG+h5&%kg}$ItO{XhohZT)x&sF7$t@ zd^Je^oFxN;M6yZ!)cPNA`3B~X@;{mX`$P0^H~O8`=%iCK>j&rIVDIUc}{V> z4yOM=^8Xp-ng26yXRrtQ7vg_}|3G|>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK;{&D# nQ1h|LA64#(3vtgnT_d|~UO$6A1Kp1b=0^hn<}87Z literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/agnian_queen.mw3 b/worlds/smw/data/palettes/level/forest/agnian_queen.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f187d8a1abb47b6d168742b1bdfb21058bb0908f GIT binary patch literal 514 zcmZQzxLcpYW1xE3vfR%){a*FtVg`oaJg@aZ^xgV;T@d-do`K;(okD#$$Afy1`0A?v zV0yM(y){Ulf#F(Ib$$-dz3Old1_qGoa1JpBd4_t%`AiV`Y~A;^#eNdWbAY-b^0g*% zq5o6mt3mSTEEyOil1=KT*8c$Ng~&HBf0X~p{NEp<05u<*{81H8-L<;GN)q;s~#~`wQF;G?kDenE+1YG0Ihs}V*mgE literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/atardecer.mw3 b/worlds/smw/data/palettes/level/forest/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..95e07701c24ecfc33f23fff8746f0ddcdbea60a8 GIT binary patch literal 514 zcmZQzxL>Xzct-G|Qi$|y^}E)Siy0Vx^Sssv(Rb_Xbs2#2|LYkT9@Ht+Pvm}34-#Kp z^&d>nmaDf0$tUv|ivChu$@c_fn0r++K{1GEVuFUWn1`=0duWC(w|(eHFkHedD&>roS&TuRp`LL*6GVOvYaH8urYO#MEtq_*iCpOaRQYO%d~2$H zo?$(*d;{}G`Jc@H{UP!OELA+Qnh<$(7lQnElJzXx6?WY6M^!v^*Xjl<`ls^A#S9F;d0y*-=)3jxx*+m@Jp;poI)(aKGs1r$K1c4_T*EqKd4&H!@`4QF!0>{| wqq`6!|A6rU(*vma*yN9@cd>|hrpRN1ew%AW1c@9uFM84KU zF7$t@d^Je^oFxN;M6yZ!)cPMlwGjCR=8y6}ng9Dk^lvx%ozB3d!2E!j0Rw={FXXka z0lG%`KUBWUV6DYPhc7PTz7YN58uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZ zHiP@7AoD=(IcGT~2;};B9SC268R0*W{C`Gy=Ksvwf&PZcBm4)F=g3`~YgmUYkMJKz zUXVc?7+w&0bQgl;A22>(dH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG*#v F008ilqjx)G5@nhd!tWiLb8u z52k0!)mww)85ovIXlfpF=rLsvWnciQW)Br(kY}i8oX-T2&(?i!TkI#1JO`*7B42AF z7y3U{z8WN-Wy!#BEonyG?mB6pUWj}H^GErg%>Vr%`nMbXPG?|JV1B^NfB``E7xLQI z09_;eABViShP;EO>)J?xGz{%v`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKmH&RI?g z0=Yh32f|liM)(gT|DREw`9Je^2791?L3{;fg#SQ%j@-4mhIPpD2>*fP1sTMF;RTUL wcOgjr0pkOv2T=2|$sbkm)Lp9^tR!KNE(>yxvg#32Rl7E~=YH}I=<>R{09|l>m;e9( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/original_dark.mw3 b/worlds/smw/data/palettes/level/forest/original_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7c28daa0d3a68859aa714f1336a6023ff43ff20c GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=822~v$rZpe0A0T zdJsKZuHG6X&%m&rw^Hwcs*JV*GXukaAYXx5j6t5Eo^d`CL_S;hy=}3dMDiSo`l7xoIda$L8rC7pBm4)F7i16zh8IL0-Gw0e l2aFGx9ze~Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=OBPMnPct)m8uN zLGrWZ>a9WY3=A(Bc$NBCjit65F);iG^0ym_F~~F2GtOs%$Uo415L7Q3Z)EQcldm^GErg%>Vr%`nMbXPG?|JV1B^NfB``E7xLQI z09_;eABViShP;EO>)J?xGz{%v`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKmH&RI?g zl1QFY9Ipf6D=;Jc2a^BKD9`+#c{_tW(7zzQ0yDyYAU;R#+FZjrWO;=DK=OhN;=u5N y$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%2#xkp*`h^eYwo7;0gc?Wd)-}wMS#eK>E literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/original_green.mw3 b/worlds/smw/data/palettes/level/forest/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9139338c3158de0604b83766bdd3977d0b83e024 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH28Q*#m3j|UWwaHT85lsS6_~{sKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;1k6_5;mF2O#?kdF^X} zt`Yu^Ltb1%-a*rKZKOaNx^9Slg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;s4 zTpzCkb05NgAo>4{^34C4w=>uS{fq8D5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y?|cBq=7sYRqNJ7#KjRWy{1EH|p-7JF1*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l_vwKVW>o^njTG1Ay!= zx<>dv4ta46c?V6`wUGj87}~-174m}o5}zy^!NnkNdBlaouONM!!F^MZc_8$gLp7}rXb_RQ(e?fc&W`zGhe2(0;xrTMf@(BNdjt?;S@npis$HAgb3b_pbou$w0B{_E4gdfE literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/snow_dark_leaves.mw3 b/worlds/smw/data/palettes/level/forest/snow_dark_leaves.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..320256986039c9f78b33679b884188f295190230 GIT binary patch literal 514 zcmZQz*jxTgMZ?zHwLkKGY-Qf$Vg`oaJg@aZ^xgV;T?U|hrt6#7O+}fmbILarfy7r= z{jUenv*qfoLGnu_-b-xPEQ*VEn^O+q&nYjKiPDiVjh6!P85lfG&$pnd27WPc&A zeNBa&js9F?bXkc0nI%n0&2?T}Q@kFFbH UzJBP3+=)d-<<&sq@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl=ReW&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6luoTBL>}FRApbQhwrHgq#S9F;d0y*-=)3jxx*)QNV-e305miYRNfl9$`0A?v z^&on-T)j0&UQtw0>YKuLjAVvt(e9NH(dTTK@y67oxv``J?<#=Kua6c?O2DvtMn}W;( zx#yhalpu-ZImPihF!v$+2a^BKD9`+#c{_tW(7zD(Bm4*AbL6hgHLOFHNB9pUFUTMc n3@?a0x(h+_4;UXXJ%E~zO}?P+a-CWI%zAkTbXiRIqpJV_gm8JI literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/brawler_orange.mw3 b/worlds/smw/data/palettes/level/ghost_house/brawler_orange.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..750def41f99befdaf52b901569f3800f2ec30359 GIT binary patch literal 514 zcmZQzxLfbSvYJO>O$TQS4&S!$ipW+bYEaAN-YUBiyuQibi z{humd4U#`+$-p3yY*Ih9{s&MmM1KSGNBN)3|NSBQw;TOVXJArbe!$Ft0YL66 zx<>dv4ta46c?V6`wUGj87}~-174m}o5}zy^!NnkNdBlaouONM!!F^MZc_83(z-0OQ1XrT_o{ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/brawler_purple.mw3 b/worlds/smw/data/palettes/level/ghost_house/brawler_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..60ac884bf7af4d8c9496772c41d0ca1076fc2161 GIT binary patch literal 514 zcmZQzxLe;YvqI&qiGbr5pQxAE&j0O&q+0J5);7vz`tWZ4KV26@XPE*yRZ>DvtMn}W;($)B^F z5+sp4r#M~*=01e~K=S_?<(dC8Z)dOv`WNDUg#SQ%j@-4mhIPpD2>*fP1sTMF;RTV$ h=Dr7v511Z6&BrERPa9WY3=GReUdw!txhgVCfPvvZkgvcj#vsp7&p4k6B7a=srHqcMpJoBjJcxX)iCpOa zRQYO<{5eYo28m>o`l3&od9vlE)MSG0^ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/crimson_house.mw3 b/worlds/smw/data/palettes/level/ghost_house/crimson_house.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fc7369f7efb1dfae084baefc9480880b4d48d64e GIT binary patch literal 514 zcmZQzu-9W@;9%fmkY=!FsaKp_%)s!Q=e0hFzFS|f3nHB?IfBk*K}eAJ>Zd&PAbAFcVAk1uvhwx%_w9lFdIkmsW-$hNhI+>NOc41MJi3CH1tY}%ioxV- zP2@uVr^;7@Dnt$bLai z`m-uAa2rdSB%Ofrveg)~<4DOr$2igZR z|GnmQyJ+{>@ph^(_o4bPo++OBzxXZIdSRG6vi}$uR5PFFzO6%+NA=%EmLDu~tPpv0 j7lQ16&iIDuD>H6+WcOjyf6j7BkVNvF;&>f&SuRlkZV-R8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/halloween_pallet.mw3 b/worlds/smw/data/palettes/level/ghost_house/halloween_pallet.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f73c16e461017810a7ea3e7ead5d0b1767b856f7 GIT binary patch literal 514 zcmZQzxLf~#!GZAr;{vM$?}yQoiy0Vx^Sssv(Rb_XbwQ+)B}dS?EC>k_UtRUT9z@TU ztG5QpGcdeoFlG#6oXxnOk%8eqkgvcj#vsp7&p4k6BF}3g5+Ic!U-Q2PCSPkJ7y3U{ zz8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2E!j0Rw>SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK f;{&D#Q1h|L7t~#@GpnCjFYkaZi|Kw;748ZE4*`2g literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/orange_lights.mw3 b/worlds/smw/data/palettes/level/ghost_house/orange_lights.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8eddf82fd34ef08dc9bb833c177a38de28a82c74 GIT binary patch literal 514 zcmZQzxLbcrk_UtRUT9z@TU ztG5QpFOzwru}L*T>9^H9e+GvC^$ZLO%wi1k4E2ojnLzSOB#tV)($F&vwk>ys$=905 zh5k>KuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KnzR?gGWO8~sjaU{YXyz|4RFK=OsW z_BBA)2>-_+FRmf)py|3cQXmaOJJ`NLUXWknlVu~g800OFxN!Itq;E60ZwfLGtGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0 l=q?1wKVW>o^Z;r;Hu-|O%XMb;GwbCY&}A{*kE-IB3IHUlf8YQB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_aqua.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fec63947beaad9cc35209b449c6dee93b0001380 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?Cpr8iFKbrV$p$kpfK@Y#F~~F2GtOs%$e$45l+DpRZ))TOldmSFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK f;{&D#Q1h|L7t~#@GpnCjFYkaZi|Kw;760o2ea(J8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_blue.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_blue.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b46979edd84e64bd8aca975625ffd7f32daf5f44 GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H(xUKgr9Q6?U@03=IE)dy8QeDonFn&uIm;-nC?ea@xLAb1-g7- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_dark.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..eb6152098a04082fe556948c8bc4f3cd45f58114 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 kg&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;QWRUB3U06}tj(EtDd literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_white.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_white.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0d5c43f3b913762f13d854b75c804a34075a0c6f GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`iA-kKnN0FUG*PK z&z7sV2FWupoaFG6ysTMaCmYPb09MT`#vsp7&p4k6B7Z`JQ#MEQys42BOup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMlvmp8#m_N$@Wd83D(ZAj3cRB-;0`miA1`GhQzmV6y2Iv~$ z|2X8uHRK&MUDrkmq+w_W+gHd7@=JWOYy=mByyX!W4!?r*Z3g#ELFR$nbIx)~kVNvF z;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ gj1QO|K+VS{Ur=|s&a8fBy}SduET;QWRs6390KAxge;o4S8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 oLXi9e#s^Ffpyp%BBgY@QEXX~|sz*#!?b_U)`^h_?%g34n0E-uTmjD0& literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/golden_house.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/golden_house.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..45f5c6701ab330a23be3fb11143af114833973ce GIT binary patch literal 514 zcmZQz_@5mhctx^8?vGl%`hEAw#S9F;d0y*-=)3jxx*#$^piyG6%o7<-O>b3@`0A?v z^&on-T)j0&o`K;cho9tS%?dl&Uz^Pi2Ml=PT3sI^QJ~lF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&EV!Z literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/original.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3856df591aadbe01ae684091675ada83490c3846 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 og&_F{j1QO|K+VS{kBC2XS&(~_Rgaje+O@eo_mg)(mp`lm0QE6=Z2$lO literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/original_blue_door.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/original_blue_door.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b43818c578274c2bf468d43dfca69633519d56e7 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 gg&_F{j1QO|K+VS{@1Xg>wjl_&eu(?A$sbk$0QUZP6aWAK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/underwater.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/underwater.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a92bc7a1ba7400a9bb69e641009d1e3b1a58ed09 GIT binary patch literal 514 zcmZQzxLe;MaZDk@kjt$nOfP+MF$2SIp4a*y`fh!_E{IGJXp~qi^F)SI(_0lJzPjpv zJ&2wyS8olHPZnvF;WnJ;7#8&_iGksNJp+RRvlxRsLp|esCXhS>!wC^i*&NODrbbRM z`C1dX(Eq9O)gXCKO9qDTY58d(#XEs~i2erVkMcj6|NBGC-){6fUEMIvPt~0P1Axpg zx<>dvGln$S{`=%iC zK=S7-rvynP&nb@Afw>RiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyOnKz^Lze})M_KiVsj6L@+jBp82XuK=T>yexeJ20_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/atardecer.mw3 b/worlds/smw/data/palettes/level/grass_clouds/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b055f2fe658d20e7e05d399ad5d48e13e1e5bd1 GIT binary patch literal 514 zcmZQzn4j-y)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5CkiBjyM7Ao0~z z|H1TZxq52`hW$X1BEf2-SB!u3irFE2Bj&3tajf!e|JWe>T-}ehrGAphbAf6>>W!G^ zD8$&x`Prv~%wKQH!0y8QeGh53~+Mxx?nCvT9NRs%c;PD?c2Y3;S`)d@|EcoT5I$dKf39gAvU~&cNBN)3|NSBI z7g)qN>sTT3=q?1=|C;d~(t(wFPrI@RE-WFYc GpA`T}A$TkR literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/crimson.mw3 b/worlds/smw/data/palettes/level/grass_clouds/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..aefeb5d356809ad9276aa7faf8a38877aeeee7d1 GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoghI)9#?j#S9F;d0y*-=)3jxx*!rF?_{Z|^g{+DzPjpvJ&2wy zS8olHXJGJ=3~}lfxDob4hJoQfkgusE#vsp7&p4k6BA>1M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG92BWQFSPAeeju^GErg%>Vr%`nMbXPIs5oRO-=VzyKio3wiBpJ_-I( zla$Ah2D?vOL*79{H_P#KD5gBfJ%zj=zr-iYMsP96TOM)Y@GD5)W^mv1KQqKV=PaiL zfm|Q21K}$$Bm4)_53>6|^L7S%pnpMp1!jc*KzxYZ$npsPf#k&)#DU=jkw#M* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/electro.mw3 b/worlds/smw/data/palettes/level/grass_clouds/electro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8a3971982011180cb6e3848aeab88bbe6f8d4eef GIT binary patch literal 514 zcmZQzxLdzW$J_S2XGqk~tlIL)#S9F;d0y*-=)3jxx*&3y&T)g|CNC`1^dQFYIzQ@f0>VnAE zn#hIzPnE9*$)B@iV30`Wvjn*SCf~sPQT`|Me}9Pn?MA=T7xSvit=3||03iDddF^XL z)vojXXU32QyH8w0-a*r~KNRFXbO3TsAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82a-Q$ zIVDIUc}{V>4ur43jPM^w9%A=)2791?L3{;fg#SSNQyyz`4eOBQ5&i?oZ(yllk!OI& qV{;$G@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?oD`Wt47Jw=M literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/geo.mw3 b/worlds/smw/data/palettes/level/grass_clouds/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/miku.mw3 b/worlds/smw/data/palettes/level/grass_clouds/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3b009fbc0f67ca53fbc5c3e22315015100936092 GIT binary patch literal 514 zcmZQzxLcpBdPLXNZbO(!@{Hoi#S9F;d0y*-=)3jxx*#%HHO6e0A0T zdJsKZuHG6X&%kg*ubCwJY63JZAQ}b_@!{i&7Kg$1P{_hXbzuo9}x{A^pug&oc7yx8{A+LQ+ zrJ9ZSe`XA6u=~U{8_2KP-t=7HqT zSxyN8xjtS8!dGBM_zxuipHZIqKl645d!T+_kxeb;$Av|AFKM8N`9% u1(8R0AxIwLcii$vRXlaq>IN%G*rV$Pxkp*`h^eYwo7;0gc?WcPO+x@;f>6-yfoXyV37-1}32WK=aW7$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lqk{hwvXr{y(EU^MB^;4E8|(qWcfT=g3`~YgmUYkMJKzUXVc?7+w&0bQgl;A22>( pdH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG*z<3jmy9dvyQ+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/original_green.mw3 b/worlds/smw/data/palettes/level/grass_clouds/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..e896e0dce38ba4cab348a36f6cddc6ffc5476668 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&!0?iB4U<0eOy+;V3=Ck^%wi1k4E2ojnIQ5EK+tbtGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0=q?1w tKVW>o^Z;r;Hu<9}p1NywgOw!g(PcsIQC2--s%qEf_S{e20bPD_F#v7?dvyQ+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/pizza.mw3 b/worlds/smw/data/palettes/level/grass_clouds/pizza.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2be2f2db72ee79f505eab6693a29eed838d54c15 GIT binary patch literal 514 zcmZQzxLf~C)LgDYYp2moD=Fv6#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wh^MiVj`0A?v zV0yM(y){Ulfg##xzH_kXFUDvi1_rQdW-$hNhI+>NOc418x(|ZtMdOX^yZjKK0Gb8S-@yD){wMQ)e~A9=M!(Y;n1J>(GhhIa{e`^tH9*%0 z{|C$eVg$Kp9?&i+XNZ1r4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ik zka-~YoU@z~B#}I)I9>+_kxeb;$Av|AFKM z8N`9%1(8R0AxQoK;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b*7^Ye Dku864 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/sakura.mw3 b/worlds/smw/data/palettes/level/grass_clouds/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9a0d8d868733599bfcd7f029a0b2d63d8cc8a4d3 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*+m@Jp;poI)(ZNOb_Zo;;XCv zgX!6F_0}MH28NfSg4$C8rkl%_F))BtGl?8Sp<`>HIWPb zpDJGsl0Rq3z#x%qQa`o+2hc2t{s!ic@;{mX`$P0^H~O9afDvdvGXn+y*yYIU{sYMiGKd4i3nGv1 qLXbSf@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?oH>&}N;DQ|h literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/shukfr.mw3 b/worlds/smw/data/palettes/level/grass_clouds/shukfr.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..635d22c98abbfad7a277830f284640cf9738a4aa GIT binary patch literal 514 zcmZQz*i-Lm)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&!0=qb%hXWfoTsG(0|Qt!vlxRsLp|esCWt%(5csQ@E7&T)4$OTB|AFNHGs-jnXWq_W5A-j@{Rsbo_#C-wa}Dc|<&Ua(>aNudR+6ws*9~%yvg#32Rl7E~=YH}I=<>N)09|Kyod5s; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/snow_day.mw3 b/worlds/smw/data/palettes/level/grass_clouds/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8a7c632110cfc650f68e642d6947cbd6ec99feb4 GIT binary patch literal 514 zcmZQz*jv6__l2ptU5DSRIKA}A#S9F;d0y*-=)3jxx(q=1OxHKDn~E}BPnvBi0*SA# z`d<&CXUo-FgXEV=yqDOnSrixTHm4lIKWSDh6Qv_#8ZQOnGcb6Xo^>q@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl-V$W&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`dRJkW-ynEPYMyg^vTOtwgS_Pt7Y@II^lb+BP5(1P z^uKf65@(rTU4AE&f#H8W1A_vy7=t`RJ>z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6luoTBL>}FRApbQhwrHgqK zuVw(+f6kJDK_b~M?0%L#kPp${!2D7EC-Z-Qi2m(Hztb6*fc7(kOhN@9`wMyPYk;m1 z{*OamTtnVL({*j6KpLuE5D#QuAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82a-Q$IVDIU zc}{V>4$OTB|AFNHGs-jnXWq_W5A-j@{Rsbo_#C-wa}Dc|Xzct-G|Qi$|y^}E)Siy0Vx^Sssv(Rb_Xbs2#2|LYkT9@Ht+`?5c%2Z^t) z`VXdO%hg+hQp@|D0tqk3{nM^l!y~fHpzo1-Wl=-;=(d4B>A#`kk)H=F5J8odE-Y)SC#} z*WA&nH~-I%Aq_T9TtnWW)0HQ#FAuxCA+L>|y>Wc9Yy=mByyX!W4!?r*Z3g#E|1(4E zKW8~5NCN2kcpU}?P-rMHi!sPE)HBX!g2>Ndjbr=I6vY{@1(UBekqiBwDqjteZ%x(D zGpt9JZ(#l?|C9N@KSbVurHUt36C#i9LXiJXvYutT!j4=1sEViVTHRnJ343(iAoG<~ TkC>|3wYfd_lXpOu|8E8WyYGc- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/autumn.mw3 b/worlds/smw/data/palettes/level/grass_forest/autumn.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..89d75ed27fcf43223f5e976be54ab643331df81c GIT binary patch literal 514 zcmZQzxLbdkg`YK;bsFn0)_S4I#S9F;d0y*-=)3jxx*+m@Jp;poI)(b1j1THT;;XCv zgX!6F_0}MH28K?Cjk5fVl1%gY7#KjRZ!(H8$TQS4&S!$iKhS*;R4*EDWbX}=uQibi z{humd4U#`+$-p3y93NaCzCRfz-@yD){wMQ)e~AA1Li5uF7;iGx1I?Q4Lp z5&qANE(_5wt|9MmQMJtMrW2+-$i0QUAiu;X%SLc9$Xgz9;qWU+-)3;%6l5Mq{+#8M zAc^ET#ql~2z5+ACe<1n)jPlI?nYT081KkJWD=;Jc2jX+&uFW;9LzYMQ4&p$GLK@zquT z!SrmodTWq81H(xUKS_VB3cDHZ3=AOEx4|E>{)r-a(*?Ys}Yfa=r z|EJ1VgXGUyGB8Lao77LO{{hqs(ci%QQT`|Me}9Pn?MA=TSMll!Z4hI?03iDddF^X} zt`Yvvj3EtnpSXs+gQn}+NP#p=d60dDydb~CC(A}~G00mUapCYQNZ)2~-xOpXNdBDV zlpu-ZImPih5WWI4!haz7|BUj?|CzTl*aQ6w;wvyC{0HK5^ZFU|8R&jgFw-9ZG>v^% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/brawler_atardecer.mw3 b/worlds/smw/data/palettes/level/grass_forest/brawler_atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..39b73646578db2923e2f86ec21e12edbd0b9fd02 GIT binary patch literal 514 zcmZQzxLcnlaZ18hQdn%e)Lya4#S9F;d0y*-=)3jxx**b-=Oj>my{wLqq!LJcb=CiR z5ItM2-Wnv&z>v?gS9U7fed%a+28RDYzK)O>gFHh$<9sHF{0R|G*&NODrbbRM`C1dX z(Eq9O)gbwEmJAFM$tLwv>wf_CLi9H`=%iCK=S7- zrvynP&nb@Af$$ZW5&i?o|7VnE{?ELf!5-*75MO~A;Xe?cBX@1CVI8tO!haxnK?ZSP zctPaRT?mqY!1#da0n~hK@<&xXb=T?!D@oX+%Yxjata`*$)vnF$xu3iPy8Qfb0O-GY A8vpuiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe)5LJ#Ue;;XCv zgX!6F_0}MH28QD@x{5C(E4=!d7#KjRb%ewi7BefLLl?e0m%G9Ui%uL zYlQzZqsv0{i)+X`Xu7VA6iCCA2iaH13-U{RvTOtwgS_Pt7Y@II^lb+BO+n^?xjQ$0mPN#Zz~!Zm^PsJ-RH&y~?UbOjYgL+@AZ%JD|&#YXbmxS9`<& literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/crimson.mw3 b/worlds/smw/data/palettes/level/grass_forest/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..48465a548a19ed8921f65ccd00a1ad546ca80666 GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoi$UKk4|%#S9F;d0y*-=)3jxx*!rF?_{Z|^g{+DzPjpvJ&2wy zS8olHXJGJ=3~}lfxDob4hJoQfkgusE#vsp7&p4k6BA>1M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG91P{nYv&K)n$82Ii0QKbimgL-cPq`kn4Bsj1YX$AAGq_80Qn*L)KE zr6wtlAq{q)xQ4ughHjSQ=}=60kbQ-`Aiu;X%SLc9$Xgz9;qWU+-)3;%^glDiJ?AW^ z1c6*1uLI#LFeCg2(*K`Pp7}rXb_RQ(e?fc&W`zGhe2(0;xrTMf@(BNd}Q@k1h*xkFx3!Q&qb*x95KH4(RePBmm@$eYXGr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/deep_forest.mw3 b/worlds/smw/data/palettes/level/grass_forest/deep_forest.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..91517aa15e2782b262e319d208a44a90add1daac GIT binary patch literal 514 zcmZQzxLbcjhE>&7vrY53CcDz)Vg`oaJg@aZ^xgV;T@d-do`K;(okG0=^MiVj`0A?v zV0yM(y){Ulfnlk{a}{?@4qXi`1_qF71!ge@d4_t%`AiV`Y~A;^#eNdWbAY-b^0g*% zq5o6mt3mSTEEyOil1=KT*8c$Ng~&HBf0X~p{NEpB zHNyXK$ct;pJ7~JDjTA`3&*IA`?nC$wB>$gLp7}rXb_RQ(f6@I1;&bG#%{8n;mPhyxBrnJy4h%1dJh}@(@(&mv qFg<{pk4^rlil^>c-C!jNdvsZldz4j=n5x>fxjpxjcR-idFaiMJx_T1; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/electro.mw3 b/worlds/smw/data/palettes/level/grass_forest/electro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d462646a48d82dfff712776db834061c36bd00a9 GIT binary patch literal 514 zcmZQzxLdzW$J_S2XGqk~tlIL)#S9F;d0y*-=)3jxx*&3y&T)g|CNC`1^dQFYIzQ@f0>VnAE zn#hIzPnE9*$)B@iV30^Qsh?W^1E?1w-@yD){wMQ)e~A9=M!(Y+^Qz0O)?&Z_Ao~k> z?Q25SuJiq8#*hZPPh3OZLDRKA6y!d10J5);7vz`tWZ4KV26@XPE*yRZ>DvtMn}W;( z$)B^F5+sp4r#M~*!dGBM_zxuipHZIqKl645d!TyYIU{sYNx zV5wk{XMo6KbKe8T2TTv3=3|pTs^Y1;RySBl!X8}~rESI7VW D=5&Fs literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/geo.mw3 b/worlds/smw/data/palettes/level/grass_forest/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/miku.mw3 b/worlds/smw/data/palettes/level/grass_forest/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5eeaf6c571d8d219c44d366e30490526be5b9bb5 GIT binary patch literal 514 zcmZQzxLcpBdPLXNZbO(!@{Hoi#S9F;d0y*-=)3jxx*#%HHO6e0A0T zdJsKZuHG6X&%kg*ubCwJY63Hg@Q|o^K^+Mzum_N$@Wd83D(ZAj3ce;ww8?Vjr3>W}pe<81Z zO{JQR_$X|VgmHRK)KEP4DkCSl5h>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=- z=PaiLfm|Q21K}$$Bm4)F|IaAT{GWL{gFVo{Aie@K!haw>NAB8O!#ZSng#SSDf(+uo v@Pf#ryAUJ~@jGt$qbi=dYjuN_B<#_3gWRL6dc;)KuFdVapS%OQe04DZCOLoB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/myon.mw3 b/worlds/smw/data/palettes/level/grass_forest/myon.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8420ad56ec5c393fc3bbc514acca2b2ff72024a9 GIT binary patch literal 514 zcmZQzxLe;UvrOlt&0C*O5h00_iy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH1_m|FbfcvT`R1CI3=AOE3d~{*@(lHi^O+#>&vmkG*Sb!Ln*-Dhk*_t8 z3;mxeUk#GavSnb{npRY0Ts;G*7b4%l{89cV^M8Mc{_RG;(^uFkFrVXRzyKio-;3DS z{8as?_MaI;8tlHw7Pmb(bh88XvoPgB_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~a zbCy$rB$DS8$Lqk{hwvXre!3yZ?#Ti&d@y;0|3LB@u4{8A)*;Iy{0EZvR}p8BXN1V3 vyAUM5!uA2vIey&oM^!v^*Xjlg; zsq)nz`E!;G3=+vE^;7GA0L_BvZ(#l?|C9N@KScj_qu=QaOhEgY8886I{z6{+8lY>0 z|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE}K@!Px zisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{81H8-L<;GN)q;s~#~`wQF;G?kDenF8@0p0E3x(@Bjb+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/original_green.mw3 b/worlds/smw/data/palettes/level/grass_forest/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..607d80cc9b924227a3a2735a68740982286b2b89 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH28Q*#m3j|UWwaHT85lsS6_~{sKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;1k6_5;mF2O#?kdF^X} zt`Yu^Ltb1%-a*rKZKOaNx^9Slg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;s4 zTpzCkb05NgAo>4{^34C4w=>uS{fq8D5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{Ad8%aeFlY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/pizza.mw3 b/worlds/smw/data/palettes/level/grass_forest/pizza.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2be2f2db72ee79f505eab6693a29eed838d54c15 GIT binary patch literal 514 zcmZQzxLf~C)LgDYYp2moD=Fv6#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wh^MiVj`0A?v zV0yM(y){Ulfg##xzH_kXFUDvi1_rQdW-$hNhI+>NOc418x(|ZtMdOX^yZjKK0Gb8S-@yD){wMQ)e~A9=M!(Y;n1J>(GhhIa{e`^tH9*%0 z{|C$eVg$Kp9?&i+XNZ1r4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ik zka-~YoU@z~B#}I)I9>+_kxeb;$Av|AFKM z8N`9%1(8R0AxQoK;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b*7^Ye Dku864 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/sakura.mw3 b/worlds/smw/data/palettes/level/grass_forest/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5b7627fe74f241ea63801075421c77925867eaf9 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*+m@Jp;poI)(ZNOb_Zo;;XCv zgX!6F_0}MH28NfSg4$C8rkl%_F))BtGl?8Sp<`>HIWPb zpDJGsl0Rq3z#x%qQa`o+2hc2t{s!ic@;{mX`$P0^H~O9afDvdvGXn+y*yYIU{sYMiGKd4i3nGv1 qLXbSf@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?o^P>TXOo77y literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/snow_dark_leaves.mw3 b/worlds/smw/data/palettes/level/grass_forest/snow_dark_leaves.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..47bbd42b97a73fb83d8db5e553e0c5f3033cd178 GIT binary patch literal 514 zcmZQz*jxTgMZ?zHwLkKGY-Qf$Vg`oaJg@aZ^xgV;T?U|hrt6#7O+}fmC(Sk$fy7r= z{jUenv*qfoLGnu_-b-xPEQ*VEn^O+qpEN6$iPDiVjh6!P85lfG&$pnd27WPc&A zeNBa&js9F?bXkc0nI%n0&2?T}Q@kFFbH UzJBP3+=)d-<<&sq@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl-V$W&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6luoTBL>}FRApbQhwrHgqId~8@zquT z!SrmodTWq81B13>aMJqF;Od<~Hb`}FwHSjuLp|esCW!n4-3LMSqVY!d-Z1%E6S>g; zsq)nz`E!;G3=+ww%8>OpFn^T)$^73RqW@>n{B)Q+0~!F?U&w1;19Xk>e`YjEAQvPr zt|9MmQMJtMrW2+-$i0QUAiu;X%SLc9$Xgz9;qWU+-)3;%6l5Mq{+#8MAc^ET#ql~2 zz5+ACe<1n)jPlI?nYT081N{r)D=;Jc2jX+&uFW;9LzYMQ4Kvt1pwb3iaKYGRN5WW%fRhBqbdA5IS5Pq)iN83_A$>g~}wIKCI%ySfCY~}py z(?RC1H)UY>88_ESF1#MdhsX!9|KR?|zCRqo4^#V{&c$TJ{DqkT1Ax_Y+Sgbs*-Ovo z#*hY^ca6oCRoAsYQYa0(d?0VM(toS?WZ4KV26@XPE*yRZ>DvtMoBjvd2Qq)XDa7^m zsxbGV`cIDS9$UQd6%IQon0&2?TSolGj921Dx8LWnqL%eTA}NPnEz5SPf1kSQvR0&y8J#X0HbqwMF0Q* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/brawler_green.mw3 b/worlds/smw/data/palettes/level/grass_hills/brawler_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..612dcce98c7e3a8d3af8774ca257640fb30e7d09 GIT binary patch literal 514 zcmZQzxLcnf(5kUq_qA%jlYacVG|mo-J2z z4U%VI==U@ZyPg(Z{2R#r55yKW%Vf#h3xvUT6v`WhK}-$;hZ*P6(M{!f*! z2FahZWMGg;HmRRl{{ybSf%&8SPv-yr5dGVYey1@VcCuK~J7_&*ML zaSeF~P1m)N0%_YB?164V1|atq@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAo+8aQ-UOt z=M=~5K==yG2>*fP|1-)n|7V7n4dN>>Bm4*AbL6hgHLOFHNB9pUFUTMc3@=Q#f!qm_ vf57;F=>gPyWVgfQkE(d;uGI}zlCVdY1?g8-Jz}bA*XH)zPu>At{#rBuHX?(a literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/crimson.mw3 b/worlds/smw/data/palettes/level/grass_hills/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9f56757809fe1820e702f06d645f8ce224f5e40e GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoi$UKk4|%#S9F;d0y*-=)3jxx*!rF?_~Ky=7$VOe0A0TdJsKZ zuHG6X&%od#8RFC}a3k!83o`lMQd6l%j{yUK>@VcCulXeS zOHEQ9LmKQpaSeF~4c#oq)1jF1Ao~h=L4Ju(mW|+IkheVI!r@ntzRlpi>3?R3d(K%- z2?Du3UI)TgU`F^4r2jvoJoA6%?F{xn|AP1m%n1L1_#C-wa}Dc|nnBd23UT5b8{Vg`oaJg@aZ^xgV;T@VSA2dM#xude!E529zw)mww) z85mya2!!SNRHR)=WMKFYpk9c41M^4upUnULA^Nu){Z3!Zt1h=%iva_G>@VcCuL)JV&i9`g zLmKQpaSeF~P1pWVko(X9$i6~ekYD1HWh1y4ehUx69nKaf1c?(GcrK>vdH3d{)qf%p)+k>wHo1Ice-sbG<3fXHKW-vdUF-*L+y jRq@nas~fB&VUMmGrESI7VWUfF&r literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/geo.mw3 b/worlds/smw/data/palettes/level/grass_hills/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ac56278a9fc417e061935f664ead05f4f6eef3f3 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pR60_LC||e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>BobP?s literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/miku.mw3 b/worlds/smw/data/palettes/level/grass_hills/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..49c78fadba25456b64c583e363d2952b1b134ede GIT binary patch literal 514 zcmZQzxLcpBn&#vYogXBUJfnDWF$2SIp4a*y`fh!_E{KH4J6U=}dqji8S6BV72hp?T z>a9WY3=Bs^jwz^E@dHg^VE7N@zwr`dkY}i8oX-T2&(?i!TkI#1JO`)?B42AF7y3U{ zz8WNd&XR#aBH5&VYW)wOUWj}H^GErg%>Vr%`nMbXPFGQSSFXXkasZ_HO z|Idse4R)WnhP;EDC6C|6BusgbeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFAQVoaK}t zkn7`hAbbU8g#SSD{~6_(|1)oAum}1V#8+TO_z%SA$X%OjScfc+@E=HCkU<<6UJ!Y7 r7lPy=e#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuuPz1v|F?d% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/mogumogu.mw3 b/worlds/smw/data/palettes/level/grass_hills/mogumogu.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..88af74ceafcba91876e6e5fa21300926b8c484cb GIT binary patch literal 514 zcmZQzxLfb*xiQhCsIu%SoK;TZ7~o z7>>zk8gkqIEDFkGVE7N@D=>>O$TQS4&S!$iKUeX#?GG$XvI6Rc$k&?4h5k>KuLj9; znldnyqzR>6D%%0n3z2VN{wV*G`M*Cz|8}F_>CY`(!&1B%FaXH@_b&D|5rK2v;)F4z z!S1_g6J`5c$H(^y(0%9tWM3gK$S?89vJqSi@|H(jIQ$CIw;9|w{m%?>56Jx>*GoCV z+=uWVNdJ2mDW^E$i#pLpFnNUkK=RKGa$`T{BFiKE2a-RfBkX9U36aOK|oLtPn@SEqgK8U_sU$4u6MSgYF|9X)8Y`J=CkSYd-lM2O_$)*x+ z%~lKy|ABl3W-$hNhI+>NOc42O-S@V|eiF%ZfVv>^wI*_*|5N3wLGtG;85kszP3ouC z{{ZTR$Tu*5l>f>6-yfoXyV39T2aF2L511J+0LcDAUi%uLYlQ#fkQdjGchGcQ8!3>6 zp&e{rAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82XfCj%PB!1*T?I?+=uWVNd7;gJoA6% z?F{xn|DyX3#OKIen`>ByERXOXNM4XZ92i~@d2|V?QRFn^T)$^73RqJO*5?{o$x1?C4p^U(pw{z6{+8lY>0|KpGs z*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE}K_J)1>%iQH z@E=J2KchVJf9CBB_CWul`wztD$X%OjScfc+@E=HCkU<<6UJ!Y77lPy;Fg{><05u<* k{81H8-L<;GN)q;s~#~`wQF;G?kDenF5eIf0B9I`(*OVf literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/sakura.mw3 b/worlds/smw/data/palettes/level/grass_hills/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..6c65dc4ff010fd8050c2c46162422816227149e3 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*!rF4LkgXr0E_0}MH z28NfSg4$C8rkl%_F);iG@*gmXF~~F2GtOs%$fq0d#coP9&AM3xldm^GErg%>Vr%`nMbXPJh7ofaw7<0|o%uU&w1;19Xk>e;o4S z8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~NFsSoal8(M zufUA(A4vW`qdfC}=IspjK>vdH3d{)qf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDukUYfi lxaE(kc*5b`%OY)j>eY^y=?Yi*ZG zSY&@HDlOj$<}Z~vX{PLd!u-AX?{J8D+l_vwKi55JwhCw;Isn;U$ZKCyA!nmM*BD(E zqJO4|jH9OO+DL&k?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV;J)d9W{CcGu3O?P^Q+76 zgfcMvuV-LTU>0MLXQ*eK&jgW=AjnAa9RB1Vg`oaJg@aZ^xgV;T?V*3L={MUb=CiR5ItM2-Wnv& zz+l8Ui_uNcUg)JH1H=D%28NfCVhr*O^^EhGAo5NPewpkk_@S>U>)j z2w#C2;Xjc49mYFM)y&%%WPtt!@fDa6{sZxka={cOgiA s0b>JG0@QqL@<&xXb=T?!D@oX+%Yxjata`*$)vnF$xu3iPx_pHd0O>G!F8}}l literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/toothpaste.mw3 b/worlds/smw/data/palettes/level/grass_hills/toothpaste.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7c108ae348aef9e50761a7f0a7d74da10b299434 GIT binary patch literal 514 zcmZQzxLf}|Bq;T2Wd&P zAbAD`1NOb~f5&9$ZiPS>I&l40_-CUT+wQ{}56 z<})w^r54wO)kf99`=*fndx!B3Q#JE81{t7#L3{;fg#SSNV>#KG=W3DV5&i?o^D~GtNHaj>(On3VU%=SF plmIm!oBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG!^w2mod`f?5Co literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/brawler_lifeless.mw3 b/worlds/smw/data/palettes/level/grass_mountains/brawler_lifeless.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b9979692f09e9982bd6cf352a4902c41a3a9ec31 GIT binary patch literal 514 zcmZQzh$wz3!>#D6nx{EIca!PlVg`oaJg@aZ^xgV;T@d-do`FH3zM+1L)q{GF`0A?v zV0yM(y){Ulf#IU1u+urs?O{en3=AOETdc$wKuVw(+uN%j}Ffr{@l}U94P%lKjf%&8SPv-yr5dGVYey0~2ZL#|1#()7p_80Qn z*8p83{GS;^8tguC4S5Gm*R_!XX_)dL`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKnz zIm;*fP|1-)n|7YILU=Q>!h_Aqm@E?fJk-Ik6unt)s;XjbPAcHtC zydd)EE(FOxV0^&z0BSxq`J*bHx@&cVl_c!ZWkK#yRy|^>YS-rW+)v&CT|Uzi0Bt&d AMF0Q* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/classic_sm.mw3 b/worlds/smw/data/palettes/level/grass_mountains/classic_sm.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0e77cc017034d032ad060dd9c0430a989b4397d8 GIT binary patch literal 514 zcmZQzxLcngx=NB)$zSb~o~ZrgVg`oaJg@aZ^xgV;T@d-do`FH3zM-C1=|Me6e09}- zFg;tY-Wnv&!0=kcNAi;1B~?*-1_qF7UL`RGd4_t%`AiV`Y~A;^#eNdWbAY-b^0g*% zq5o6ms~H$>)t|FuV30^Qsb>n)E{DlCFn^T)$^73RqJO*5?{tV88886I{z6{+8lY>0 z|1)DqgWV^tA@88+x;9cE4O1TEoi+ z$o26$5WWI4!haz7|BUj?|CzTl*aQ6w;wvyC{0HK51M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG91P{nYv&K)n$82Ii0QKbimgL-cPq`kn4Bsj1YX$AAGq_80Qn*L)KE zr6wtlAq{q)xQ4ughHjSQ=}=60kbQ-`Aiu;X%SLc9$Xgz9;qWU+-)3;%^glDiJ?AW^ z1c6*1uLI#LFeCg2(hsuxKl645d!TIN%G*rV$Pxkp*`h^eYwo7;0gc?Wd)7ZLz$XMKkN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/dry_hills.mw3 b/worlds/smw/data/palettes/level/grass_mountains/dry_hills.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..61bdc7b82e90d8eb3d7633cc8ac403b190bd9462 GIT binary patch literal 514 zcmZQzxLYr*RIatt>X%zg_`LMV#S9F;d0y*-=)3jxx*+m@Jp+S6eM9{(w+Hng@zquT z!SrmodTWq81A{N;CEj|m3ZeT(3=AOEzud$aKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;+TozyKio3wiBpfUXh# z&x|1rcAvP0yo09e+DL&kOnH!fg}flY#3#!}a52bR9&zFDD@fmFaNiVU9!UP2<&+?h z>*IAGdnnBd23UT5b8{Vg`oaJg@aZ^xgV;T@VSASC`{-1c|S%`d<&CXUo-F zgX9?)Ug-#g<@r>kT}fnM_z&c(%ZV|_Gt@KAXM)I|Q0O%^^7^^Y~c?V6`{!ozn&;iK4LSB$x;*(_~xESOukGOF76{K%7xNizF4*fj5WA7(5&i?oZ(yllk!OI&V{_jFMv&ie l%O6$o)Lp9^tR!KNt{dbYWz{35s&;K|&;8^b(B)Ui0033aek1?@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/geo.mw3 b/worlds/smw/data/palettes/level/grass_mountains/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/late_sandish.mw3 b/worlds/smw/data/palettes/level/grass_mountains/late_sandish.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d94156adda5ccdb3b4b02e310e0ed7b0848dc653 GIT binary patch literal 514 zcmZQzxLdzT^Q*48QI**pD>?hg#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wg^MiVj`0A?v zV0yM(y){Ulfg#XxyXkk!NT(Qo1_qF717d4_t%`Ai`B7c#o0lN`S|?g*>NhsoEP z$c6q-m9GZLYZ@{z6vxdriu2x|43lqQ{wV*G`9IJ+kom&uztb6*447v#GhhIae1M>R zO_Wl-@PBg*X|R5A4S9#>787F2i!kLu?k(g6`6WJCHiC;m-tveGhhIVZHiP@7|AF>_ z6e0A0T zdJsKZuHG6X&%kg*ubCwJY63Hg@Q|o^K^+Mzum_N$@Wd83D(ZAj3ce;ww8?Vjr3>W}pe<81Z zO{JQR_$X|VgmHRK)KEP4DkCSl5h>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=- z=PaiLfm|Q21K}$$Bm4)F|IaAT{GWL{gFVo{Aie@K!haw>NAB8O!#ZSng#SSDf(+uo v@Pf#ryAUJ~@jGt$qbi=dYjuN_B<#_3gWRL6dc;)KuFdVapS%OQe04DZCOLoB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/original_aqua.mw3 b/worlds/smw/data/palettes/level/grass_mountains/original_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fda1d358f7f2b177b30d191173455e744cd288e8 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&z_6aTQtyGPjJ5(Z0|Qt!vlxRsLp|esCWw5t?t9x}KZ)cyK;01eS`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV37-1}32WK=aW7$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lqk{hwvXr{y(EU^MB^;4E8|(qWcfT=g3`~YgmUYkMJKzUXVc?7+w&0bQgl;A22>( pdH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG+<#9{`a&UtRUT9z@TU ztG5QpGcc^@t<-ye3nE`@A{Y8U zRlXV|f6kJDK_c0tero*>pk9c41M^4upUnULA^Nu){Z40KQeb`nG#?#+>@VcCuK~J7 z_&*MLaSeF~P1m)N0%_>FA@&vWg8UMnEE~baAa8lZg~P8PeVf63Q;>Ne_nfnw5(IL6 zybjEL2>*fP|1-)n|7YILU=Q>!y8l3Yj@-4mhIPpD2>*fP1sTMF;RTULcOgjr0pkOv o2T=2|$sbkm)Lp9^tR!KNE(>yxvg#32Rl7E~=YH}I=<<_`0RyRe#Q*>R literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/original_green.mw3 b/worlds/smw/data/palettes/level/grass_mountains/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..607d80cc9b924227a3a2735a68740982286b2b89 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH28Q*#m3j|UWwaHT85lsS6_~{sKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;1k6_5;mF2O#?kdF^X} zt`Yu^Ltb1%-a*rKZKOaNx^9Slg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;s4 zTpzCkb05NgAo>4{^34C4w=>uS{fq8D5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{Ad8%aeFlY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/original_white.mw3 b/worlds/smw/data/palettes/level/grass_mountains/original_white.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7ecd4e3ccb0b0228f86f96f78655c486e0fa97ee GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`i6Q1<_Gm4@zquT z!SrmodTWq81H*dWO1%fFGTI8v3=Ck^%wi1k4E2ojnIQ7ny6?Q4Lp z5&n-uUR*=oLDO|@q(BS9UGuQ+Di|#)VpCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsxNaqpW(wRMoD{?YW=41G;=eECAaqeH;J) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/recksfr.mw3 b/worlds/smw/data/palettes/level/grass_mountains/recksfr.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b5da161c2cb5e88c2ff4bc3e1f18f50f7615122a GIT binary patch literal 514 zcmZQz*i#=EH#zA_mT10taYgy$Vg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&!0=qb%k-GZIZyXU1_rQdW-$hNhI+>NOb~enAUI~?9?6vjldm2Sq0_~+#r)s0m%MBUi%uL zYlQzZqsoDJAbD{Oc?V6`wUGj8nDQX|3VA_(iBFb|;9`)sJmSLPSCGEV;JzuyJdpf3 z%PBz;$#aV1bzts8_zxuipHZIqKl645d!TByERXOXNM4XZ92i~@ wd2|KuV!F~FF$9=z#x%qQg55|FApZ)!2D7EC-Z-Qh<>}`-|4qq_Jr1^F<=0Y{e`^t zH9*%0|7XUK2D?vOL*7Btb#0^o7X2Xi6!L=n5}zy^!NnkNdBlaouONM!!F^MZc_8_7 zmQ#X2u8-G&@D-R5{sYPXXOw6D&%B+%9_U{XUx69nKMp34^=< literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/snow_day.mw3 b/worlds/smw/data/palettes/level/grass_mountains/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3b182c3b7b217548e4d21cbb60891d514920f601 GIT binary patch literal 514 zcmZQz*jpazG~MrK++;&ByIWC{iy0Vx^Sssv(Rb_Xbs2#2PL>=&=dzqEPnw;}0*SA# z`d<&CXUo-FgXEV=yqDOnSrixTHm4lIKWSDh6Qv_#8ZQOnGcb6Xo^>q@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl-V$W&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6l+IjRh&;LrLH=u2Y|%)$%WU0`5^fq_8;8;*!PD+)NePs8_vZf%=}H50Rw={GvbVA zwpOy2p3jXT4YuzZi!H0JYk#Cr8g}_W-e{%&R`JQQ5nK%NmPcGT{0h>y8QeGh&kV7D zy(z@?_NoxR5t{$x*zU2#3t!=|lY+_Dn#hIzPnEBR@cA% H@3R5`5-D~h literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/crimson.mw3 b/worlds/smw/data/palettes/level/grass_rocks/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9527a0a24af2fb9f4c4bd1a016fcf9439a3070fd GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoghI)9#?j#S9F;d0y*-=)3jxx*!rF?_{Z|^g{+DzPjpvJ&2wy zS8olHXJGJ=3~}lfxDob4hJoQfkgusE#vsp7&p4k6BA>1M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG91P{nYv&K)n$82Ii0QKbimgL-cPq`kn4Bsj1YX$AAGq_80Qn*L)KE zr6wtlAq{q)xQ4ughHjSQ=}=60kbQ-`Aiu;X%SLc9$Xgz9;qWU+-)3;%^glDiJ?AW^ z1c6*1uLI#LFeCg2(hsuxKl645d!TIN%G*rV$Pxkp*`h^eYwo7;0gc?Wd)7ZLz$XMKkN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/dark.mw3 b/worlds/smw/data/palettes/level/grass_rocks/dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..1f6aa06a19e97ac792f2b8d18b06d5ffacd3550b GIT binary patch literal 514 zcmZQzxLcnjvQ&gavByxrX-d%KVg`oaJg@aZ^xgV;T@d-do`FH3zM-B)?Lj?Ae09}- zFg;tY-Wnv&z`&;JVYtfnji-MQ0|Q7ki<%gNJVQO>d?twe1KkHf^`h}c_TDi0S`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV39TWJwmar)CTo0Azn5uYC>B zHNyXyF{Hun6W5S;&~#lJDUgOK53;Y27vz`tWZ4KV26@XPE*yRZ>DvtMn}W;($)B^F z5+sp4r#M~*=01e~K=S_?<(dC8Z)dOv`WNDUg#SQ%j@-4mhIPpD2>*fP1sTMF;RTUL vcOgjr0pkOv2T=2|$sbkm)Lp9^tR!KNE(>wbI$a~XZC*cvJ_Fs43aaw~WKMi- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/electro.mw3 b/worlds/smw/data/palettes/level/grass_rocks/electro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..684b84a6af3fb391d7cb226b51fbb370cf3bdee3 GIT binary patch literal 514 zcmZQzxLdzW=a>nnBd23UT5b8{Vg`oaJg@aZ^xgV;T@VSASC`{-1c|S%`d<&CXUo-F zgX9?)Ug-#g<@r>kT}fnM_z&c(%ZV|_Gt@KAXM)I|Q0O%^^7^^Y~c?V6`{!ozn&;iK4LSB$x;*(_~xESOukGOF76{K%7xNizF4*fj5WA7(5&i?oZ(yllk!OI&V{_jFMv&ie l%O6$o)Lp9^tR!KNt{dbYWz{35s&;K|&;8^b(B)Ui0033aek1?@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/geo.mw3 b/worlds/smw/data/palettes/level/grass_rocks/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/ice.mw3 b/worlds/smw/data/palettes/level/grass_rocks/ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..349ce8e099d27437b0de972b33bb157d0f72b7fb GIT binary patch literal 514 zcmZQzxLeO;+3w06)E_639#K5Gn1SIp&ue`UeYd_|7eqQ)as-{ra0 z|1)DqgY6gBkay5@T^lKohA9tnZy_(pFY(E;5nK%NmPcGT{0h>y8QeDonFo?TXE`NE zB6&`6ybgq~z>M%8*w2je%>S9UGuQ+D3*sv6e0A0T zdJsKZuHG6X&%kg*ubCwJY63Hg@Q|o^K^+Mzum_N$@Wd83D(ZAj3ce;ww8?Vjr3>W}pe<81Z zO{JQR_$X|VgmHRK)KEP4DkCSl5h>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=- z=PaiLfm|Q21K}$$Bm4)F|IaAT{GWL{gFVo{Aie@K!haw>NAB8O!#ZSng#SSDf(+uo v@Pf#ryAUJ~@jGt$qbi=dYjuN_B<#_3gWRL6dc;)KuFdVapS%OQe04DZCOLoB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/napolitano.mw3 b/worlds/smw/data/palettes/level/grass_rocks/napolitano.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bc6ebc6621d9ff7114dc9d5482716febe0ac4c6d GIT binary patch literal 514 zcmZQzxLf}#);euY*4;eW^0?~B#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wh^MiVj`0A?v zV0yM(y){Ulfx%o()|y-Je)<NOc418x(|ZtMdOX^yy8QeDonFsREIm;i literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_aqua.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fda1d358f7f2b177b30d191173455e744cd288e8 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&z_6aTQtyGPjJ5(Z0|Qt!vlxRsLp|esCWw5t?t9x}KZ)cyK;01eS`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV37-1}32WK=aW7$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lqk{hwvXr{y(EU^MB^;4E8|(qWcfT=g3`~YgmUYkMJKzUXVc?7+w&0bQgl;A22>( pdH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG+<#9{`FayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 og&_F{j1QO|K+VS{kBC2XS&(~_Rgaje+O@eo_mg)(mv4v#0NH?eQ~&?~ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_ice.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..041aa9edb1c368f843d5c8ba4954bff09d3e8520 GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8cd}GqK9>a&UtRUT9z@TU ztG5QpzYpD-_r1)rzPf&IJp;pkAXZ=&V~}U4XPnOjl4oEzspIE(IkX~A7A{|FA{Y8U zRlXV|f6kJDK_c0tero*>xO@ZiNBN)3|NSBQw;TOVXJArbe!$Ft0YK&#^4iw`T_gM- zhrGCkyo09e+DL&k4DDd|7V?7p5}zy^!NnkNdBlaouONM!!F^MZc_9Csvz!tnkvyk3 zUI){EAo>4{^34C4w=>uS{R{Cw!haw>NAB8O!#ZSng#SSDf(+uo@Pf#ryAUM*fbjv- o1E~4faNudR+6wsmj$^;S@npis$HAgb3b_pbou}F0RF6hOaK4? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_volcanic.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d7a19dc3bcb262498519c6c0fbdac81d3179d996 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=Jm5<{+^A>Zwf_CLi9HDDF2iBzduC(cB9|v3`{`#f%?$_$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM1p+ zbBg12VD3Zs4(dH^*an>=#(W6CS59x+w5Yjb<J^wD0L2*? z66)>6LG*ta1!j;u1H*dWO1%fFGTIwf^vg6MBx{wV*G`M*Cz|8}F_=?qLj`+?@81CafNy!JJ- zc-C!jNdvsZldz4j=n5x>fxjpxjcR-h)Tnqpd40<5| literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_white_2.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_white_2.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..596e5b93b404d142b5333e64a9dd4ffc58fc6f69 GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`i6Q1<_Gm4@zquT z!SrmodTWq81H(%OUZp-(W2x;%3=Ck^%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@fp{t*4!jee&yFahmnX21X-`wMyPYk;m1 z{*OamTtnVL({*j6KpKX2uziKRAiu;X%SLc9$Xgz9;qWU+-)3;%6l5OAJ?AW^1W6>% zDUR2Hxewt#ko8_2KP<>1MLIJpR=42 zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 vAxQoK;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1bHq-$CHNAc1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/sakura.mw3 b/worlds/smw/data/palettes/level/grass_rocks/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a339b4a62d15d843a22d44d485684349ad7335d3 GIT binary patch literal 514 zcmZQzxLfZbcv@6XYNMQq@^tmd#S9F;d0y*-=)3jxx*+m@Jp;poI)(ZNOb_Zo;;XCv zgX!6F_0}MH28NfSg4$C8rkl%_F))BtGl?8Sp<`>HIWPb zpDJGsl0Rq3z#x%qQa`o+2hc2t{s!ic@;{mX`$P0^H~O9afDvdvGXn+y*yYIU{sYMiGKd4i3nGv1 qLXbSf@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?oGwK0aD}X}) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/thanks_doc.mw3 b/worlds/smw/data/palettes/level/grass_rocks/thanks_doc.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2359c277f3764723464c94c2fa233fae98c13c80 GIT binary patch literal 514 zcmZQzxLeOF86Z1JQCzRmNhW%7F$2SIp4a*y`fh!_E{IGJXp~qi^F)S4&07^DzPjpv zJ&2wyS8olHXJGI*-R7Gewzq17Is?OhAfH7|j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zT8_2KP-t=7HqT zSxyO(NS;$1uLI#LFeCg2lK;;r&-|ZxJA*yYzaYK>Gs1r$K1c4_T*EqKd4&H!@`4QF z!0>{|qq`6!|A6rU(*vma*yN9@cP BdIkUh literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/brawler.mw3 b/worlds/smw/data/palettes/level/logs/brawler.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ed25ef97949818757bcf172f6ae4d637a980bdc9 GIT binary patch literal 514 zcmZQzxLbc(>zxt5wXt_;_>B0;#S9F;d0y*-=)3jxx*+m@Jp+S6eM9{NAOwl8uKEwA zXUo-FgX9?)PICB3Ue>IzlMQBI0IOyeV~}U4XPnOjkv}2ADVw8t-qgqmCSPkJ7y3U{ zz8WNd&XR#aBH5&VYW)wOSrGjV%pc`{GXM97=-+PiJDq_^f%yS50|o%uU&w1;19Xk> ze;o4S8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~NFsSo zal8)9eF*=7yYIU{sYMiGKd4i3nGv1LXi9e r#s^Ffpyp$fKdR!XyH+_oEET;rXB+n_1 z*MYeY;Xjc4e@1!c|IFJN?1BD;_#fdv5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{bT^TW_(fr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/mahogany.mw3 b/worlds/smw/data/palettes/level/logs/mahogany.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..80d5c2c1adfcd47b15b97c71aadf8b23c1441d51 GIT binary patch literal 514 zcmZQzxL@pP)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?d=etM#v&4$Hf{_IVAafG4Dt;1jPscw@(*+$1l5bi8`*oq!(`0-of)JS6@ct7x<>dv z4ta46c?V6`wUGj8sCq#>kb4VxL4Ju(mW|+IkheVI!r@ntzRlpiDabsK{5i`hK@!Px zisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{81H8-L<;GN)q;s~#~`wQF;G?kDenF8{wC03pbEu>b%7 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/not_quite_dawnbreak.mw3 b/worlds/smw/data/palettes/level/logs/not_quite_dawnbreak.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ecce214b80fb83396478275dfdfd2b8d58b081ac GIT binary patch literal 514 zcmZQzm=Nb_)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w+6{GFf5hfRXV2PYx^aTfdQxCf=b|1fCs3oUiwl6o(d~^VEZy_(pFY(E;5nK%NmPcGT{0h>y8QeGh&kS+zIm;SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^8f1r$9sK1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/riesgo_de_chubascos.mw3 b/worlds/smw/data/palettes/level/logs/riesgo_de_chubascos.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0a7e877996b9d17f580af8d38b7732521c6e1772 GIT binary patch literal 514 zcmZQzxRT{*)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w`O3dDsK^Z$-t|mqqxy9+=+n!teRPjL7t(WaXu47{(JstYLNMwsx1ORUMmA%NBSqhgH`%wM2U9nnEM*4z?l^RSQ;Xjc3x+3|~K2;&hqx#REV~2>eB19hDg&_TH sys2Vt^0?)Xs(9+I)eTmXut(R;zz|uita`*$)vnF$xu3iPx_pl%05~mvz^Pi2NHBVM9~ZTdKE(VDhyl za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9isTHW}pe<81Z z4bU~h|CuqQ!R`~+kay5@T^lKohA9uSuaFnym-uAa2rdSB%Ofrveg)~<4DOqP%mc}v zvz!tnkvyk3UI*qrg#SSD{~6_(|1)oAum}1V;(mnxKzxqewYi3M$npsPf#d}l#DU=j gkwTvI?MXW#S9F;d0y*-=)3jxx**a`#mlBIpe2UAu)7o_zPjo^ zSlw*7dTR!teg+0jALqpW8llQ5l?)93>lqjnn8g_68R{A5GeP7Z=spONWvdsvuLqN_ zHIWPbpDJGslJBo!U@!-g$od^^Y~d56~~dX9y@nDQX^7V?7p5}zy^!NnkNdBlaouONM!!F|*J%n!#QzBYf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDuko*J2 q2TTv3=3|pTs^Y1;RySBl!X8}~;+}Q7Mt0l0eg=I8x*ruJhyVcm1%BuN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/green_aqua.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/green_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..dcd6d0f0940e3ea2bf9060489acf768b5fbed102 GIT binary patch literal 514 zcmZQz_)(u`aID6w_I>TvI?MXW#S9F;d0y*-=)3jxx*+nPjHB)&+hoqCoNYoN@zquT z!Rltq)mt+F^)oOe3bYA9R53982l5q|#Teun>KW%VLF6CkJ_wR!s~5Yk2a~TgkqiBw zDqjtfcLdtfT;v);a8Bp&EUT2e`bh#&RI?gl1QFY z9Ipd&AHshi{r?%|ng26yXRrtQ7vg_}|3G|>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK q;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b5<~#6+kVCX literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/ice.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ec308ed93b4af4ef7d593fc85d0e2f05e386e6f8 GIT binary patch literal 514 zcmZQz*jvuPa8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T?U|hx9hu5|Flh6U-Kf0LE@{c z{?~)(*>d&PAbAD`9?4RxLRE9UTYd}-|LYkT6qv;rN$Q#dGA!nmM*BD(EqJO4|jN=MV?xemv?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV z;J)d9W{CcGu3O?P^Q+76gu>j9?7yWFk$iE=`@~HJ=PAMDk^Q$+LOpgxtacf)d;{}G z`Jc@H{UPT2D@5tcwS~x|yAb5RX2lk*RAcP&pzwCkd|=xUw4K2o7~bdrB!A9wN{~b{ Gc6k5;euS$4 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/original.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7d97ca364024b2a0a58712e3aed6d42483b51379 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kgZ@?6nrO9Q*jUJMNXfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 eg&_F{j1QO|K+VS{@1Xg>wjl_&eu(?A%L4%GRd#3q literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/really_dark.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/really_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..696f6df3bb766de8bcf1c12f27eee72c2d32b459 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T?U3^i6j981c|S%`d<&CXUo-F zGcY6xv`Dl7)qrr400YB+pm_?+Vhr*O^^EhGAo3s?RQXyHxzPWq^3@=Dh#abX1M^4u zpUnULVfrCy8QeGh z&kXU;Im;u&_<-pF)O>964w?^a8-j4_hqxb`e3AeFln-v} literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/toxic.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/toxic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..75f87d3c389a58d8c717d3d6ad48e30f0b1105d7 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dNa(#mmMfI~D}a*ZTNe0A0T zdJsKZuHG6X&%ltWyUO&mrGedMF9wGHK)wRA7=t`RJ>z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6ywOS^iF-sSy1Q%pc`{GXM97=-+PiJN=O2VZBAp3>W}pe<81Z z4bU~h|AFSC1CYG9hP;EO>)J?xG;~>reTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p z=PaiLNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM964w?^a8-j4_hqxcRJOCJdc0d3C literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8e3b464f8b538a27618dd8d89de920257a7c05f2 GIT binary patch literal 514 zcmZQz*k9~v)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w+6{GFuY_4kSb=Ckh^8ZzyMNh#C(+{j#ZxR9~(qISNEfBsh?!>T%cNzdL!mJ z3Nf~Fe)j1g{(4gehM#eBo#evn!F-T>5c?1Af9(6iA?m}_ey4LW88LrhX21Yo^EmBm ztd;Dg=W}C7gYCP8_2KP<>1MLHu zzupw$dV5uv`%(QT$99h`Uib=!ofJ&I)ngwL1RpKDr&EZ@NVQT`|Me}9Pl z1r{;RI#!50x(h+}zh->L^obd_ypQKgH$t9jlQU4EYx E0D~WSU;qFB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/greenshroom.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/greenshroom.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4ad23210af6238afb1f4a3287b7cf3e064fd2379 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?2N)Tc5||X2A22g8fK@Y#F~~F2GtOs%$Uo415L7Q3Z)EQcldm#%n0y2CNBN)3|NWu*nSZAzRS z`=%iCK>j&rIVDIUc}{V> z4$OTB|AFNHGs-jnXWq_W5A-j@{|Ntq_#C-wa}Dc|9Q1sGdU;wLT7GscSsArtd1d)HB`yi-ZG~USG8zx_CA{Y8U zRlXV|&knT3Ff7~medH9NSrGjV%pc`{GXM97==ak69(P!x-LPGc0Rw>SFXXka0lG%` zKQo3j*nQ#}@(!A=Ya<2HFy%q^74m}o5}zy^!NnkNdBlaouONM!!F^MZc_8_7mQ#Wx zlIIl1>%iQH@E=J2KchVJf9CBB_CWta+>h`dh|iI`HrKEYSsvj(kh~y+I54~*^5`y{ s1q>sI-?7Vs^k)a=`fc{#Zy;-qt{dbYBRyWP4goL4T+Q>g=SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1bCKm$$(k^{@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/original_blue.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/original_blue.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..268518d3a692d63ee8d59c5997feb5d8e31c4c25 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?FBy21`dE#nwi_`pfK@Y#F~~F2GtOs%$Uo415L7Q3Z)EQcldmSFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LX9wo`ZT8=9AZv~;3v!Q<9SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b=0^hn(k^{% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/riesgo_de_chubascos.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/riesgo_de_chubascos.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d515e876d315ffbf7ad43ee00777345b4cd0488d GIT binary patch literal 514 zcmZQzxRT{*)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w+6|#2%HePqN}60(JDvtMoBn5p*uUNs z;(B{knEO%vC&zY=EnfHvhn*BmzScx8^na>+HH6QX*`I4#hb-T~{89cV^M8Mc`~?;< z&N^0zJh}@({(H^%j_DIKcKQ22`J*bHx@&cVl_c!Zbu%!44|E>{)r-a(*?Ys}Yfa=r z|EJ1VgXGUyGB8La#|PJk?@xxwH!y#c|H=H{AEJN0(EN0MCKKiv%nTR+WPc&AeGSkx z!vAr|i)+X`TvRPHyXl0X9qishUXWknlVu~g800OFxN!Itq;E60ZwfLGtGgkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0=q?1w sKVW>o^Z;r;Hu<9}p1NywgOw!g(Pbg-S*L4cx6SKk&}X3gQNizg0JY3}s{jB1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/count_shroomcula.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/count_shroomcula.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..74e336cf6f76a2511149f2f9101d85f2970def4f GIT binary patch literal 514 zcmZQzxLcph(;~7|;+V`c6?V^suztnviF9`*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l~HZr6{t9pJQgg03iDddF^X} zt`Yu^Ltb1%-a*rKZKOaNhIX)hg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;rX zB+n_1*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUrr+_O&C$Znh0&!Eph_oIR&0RUyLeP#dv literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/cursed_gold.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/cursed_gold.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f0c7d6124fc6488512235418850c933adb91bae2 GIT binary patch literal 514 zcmZQzxLaS(T`aa%(M$5Vz*2$9#S9F;d0y*-XrPQPi2PsA!0@0>p}wL1K|M%(b=7|` zJzK8c8YIua;IBAC?waf$w)y-F3?S7C%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@^ZFU|8R&jg(82=%xU+p2 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/dark_green.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/dark_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b1f15cbb1e0c83ef37f037f83936f374a5a7d8b GIT binary patch literal 514 zcmZQzxLd!N=ZS!-Xqx15*%ykFiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(j^$+Sn;;XCv zgX!6F_0}MH28Lq{3XB|#3m6rc7#KjR6_~{s%5Li9HBHNyXyF{Huvi)+X`TvRPHyXk}}53;Y27vz`tWZ4KV26@XPE*yRZ>DvtMn}W;( z$)B^F5+sp4r#M~*=01e~K=S_?<(dC8Z)dQF`yb&ykUU53+FZjrWO;=DK=OhN;=u5N x$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%4LxObhdk=-`0pFy92?nebloB+{eyat`0A?v zV0yM(y){Ulf#H~lmnMf|kKs8>1_qF71!ge@d4_t%`AiV`2f7b}>P6#??7d;~wI*_* z|5N3wLGmD763JWYEb6O)dLjB7m_N$@Wd85Zz)%^_z>py+>V8_$PivzQ0|o%uU&w1; z19Xk>e`XA6u=~U{8_2KP-t=7HqT zSxyO(NS;$1uLE-*!haz7|BUj?|CzTl*aQ6waX-R;AU;R#+FZjrWO;=DK=OhN;=u5N v$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%4LxM!WNk=-`0pFy92?negz(7Ak% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/original.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cab6dd134869338d9efedbd2ee146f0b759046ca GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(j^$+Sn;;XCv zgX!6F_0}MH28Ndmyh?qn#!}mj7#KjR6_~{s^suztnviF9`*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l_vwGcYMIKVW9S03iDddF^X} zt`Yu^Ltb1%-a*rKZKOaNhIX)hg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;rX zB+n_1*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUrr+_O&C$Znh0&!Eph_oIT}`2b;#eINh; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/snow_dark.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/snow_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cf3390d5f186777ea4e124579d9c2da93929412c GIT binary patch literal 514 zcmZQz*jxTgMZ?zHwLkKGY-Qf$Vg`oaJg@aZ^xgV;T?U|hrt6#7O+^qAB)+=pe?5qv zEmv<1l3yzEUShjuiK4mHuOJBjq*<{{l#YyPycCGfz~E_m*0nI~d)gcz8>IfE+06{w zQn@1AY7l>|?NSMg>`z6dXUqKN5NwZ>^C>uJSCWXt%+Rd|5SOXf7D}F z#A=rz%QrB8l>f>6-yfpiUm;4T))FF*?n03NniX5LQjKxTA64%P8)CkG S=!e{iMMmY-K=-49s%ijA+mAm0 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_hills/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_hills/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..19d9d32451714d3386e12dd46192f91856524237 GIT binary patch literal 514 zcmZQz*kAlSwmZo%{ZDvtMoBn5p*uUNs;(B{knEO%v zC&zY=EnfHvhn*BmzScx8^na>+HH6QX*`I4#hb-T~{89cV^M8Mc`~?;<&N^0zJh}@( w{(H^%j_DIKcKQ22`J*bHx@&cVl_c!Zbu%!4ZDvtMoBn5p*uUNs;(B{knEO%v zC&zY=EnfHvhn*BmzScx8^na>+HH6QX*`I4#hb-T~{89cV^M8Mc`~?;<&N^0zJh}@( w{(H^%j_DIKcKQ22`J*bHx@&cVl_c!Zbu%!4 s$MlIAyZn8i{81H8-L<;GN)q`Ih*dS(9PVg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H%%L)u!xKuLj9$ zsxmMX1&Jm{WX}eg3en%d{89cV^M8Mc{sO-_#p8_2KP<>Geg|-)^>B4dHS5< zcpaGg5dH({hq!IMs)!y;9^pUi@(BN-%cHxH82O_rp1NywgOw!g(RD-IvrgB@ZkyN7 MpwB?}qk_CB0OTBb$N&HU literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_hills/original.mw3 b/worlds/smw/data/palettes/level/mushroom_hills/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3e4854584838b4d79557679d1f869b60536b2d6f GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H(%OUZp-(W2x;%3=IE)dy8QeDonFn&uIm;MVg`oaJg@aZ^xgV;T?V*3L={MUb=CiR5ItM2-Wnv| zB5*?Fims00M#FF?28RFj3=9g)Vhr*O^^EhGK=KR>S(fiSi=)10%_(PK*bmfc#5_kK z##YYHJ{=^lsoEkC)jgwi{KIbBL`};?ZWn03i8^ zvhmCjYO?w@${5mM{Z}n!hd$HrbX*gPUA~YPgH`w;#EyHBxNPe%HJh?N>lzScx8^na>+HAH_`BwyO6Dr9+7|M_$5 w5Rq1d$fLUuWIn|2xaE(kc<99VnCB?O z*vk3Yr-S4*Ra*ptyjBLjj`UB0>2F~EDF2iBzdr**Re6iRcB86t4zZO=JlYHx03<(A zHl8^`O;*1~8ABSZ|Ek68&}SN+j%z}(%NO#3{1Trm8^OgOZ+XOp!>=HHo56k4|I85k z&sk0hl1QFY9Ipd&AHsiN_bFEE$w*%iu~LJ{*P6(M{!f*!hUo8#ZO4FBsH7!;Vr7~~o18Rs*B2II!InqwM8JvYh~c;NdF|5{s!ic@;{mX`!g_9mA43NH>xV<5L>Clqs@Q;K=Ko1 zy8QeGh&kV8u zoaK}tiR3xO@j5W~A^Zn+pJKJ1jPwN&D>ayWt%+Rd|5W*Ei2klfzO+wO$nvQE^XJ$h wBCQCKM|UB}e2Cw1%O6$o)Lp9^tR!KNt{Y6DG#!*kQd~a_+;4#E(UqaBQ6|%1?k%i?wf+l1IeGWoDw9FJf}Eb z2j)J6|3LEp8ReP(GjC_G2l^M{euV!(e2(0;xrTMf@(BNd}Q@k1h*w&pKTryKP=SgFXY@j|yZI0WX(%HUIzs literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4540f32bf586fae9a2e499cf2a0db820a10a5a67 GIT binary patch literal 514 zcmZQz*k8=dn$F727Ra`ht(tvuF$2SIp4a*y`fh!_E(1{he?0@kgF1!!hWZEfAo0~z z|H1TZxq54mJOjf^h5)HzRtdRVW)Qv+^Hr8OR(ZC6Y!H5~?nm2FKgr~|K(!$CM$B^* zVr=F7?9)N~^`;CAKjY>)$%WU0`5^fq_8;8;*!PD+)NePs8_vZf%=}H50Rw=|>O$TQS4&S!$iKhS*;R4*EDWbX}=uQibi z{humd4U#`+$-p3yY*Ih9{s&MmM1KSGNBN)3|NSBQ???Ym=hV|x{-)1>0YLT_^4iw` zT_gOT8ABTEK5-3s2TfPbtWcm@bO5rikQd~a_+;4#E(UqaBQ6|%1?k%i?wf+l1IeGW zoDw9FJf}Eb2j)J6|3LEp8ReP(GjC_G2l^M{euV!(e2(0;xrTMf@(BNd^suztnviF9`*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l_vwGcYMIKVW9S03iDddF^X} zt`Yu^Ltb1%-a*rKZKOaNhIX)hg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;rX zB+n_1*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUrr+_O&C$Znh0&!Eph_oITz#Qy8QeDonFn&uIm;gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 vg&_F{j1QO|K+VS{pBYEXX}Zdc0m80$z%_n&)lNXiy0Vx^Sssv(Rb_XbwT9+dIkoC`iA-kKnN0FUG*PK z&z7sV2FWupyky{2>SHyQ+HS(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMlvmp8#m_N$@Wd83D(ZAj3cRB-;0`miA1`GhQzmV6y2Iv~$ z|2X8uHRK&MUDrkmq+w_W+gHd7@=JWOYy=mByyX!W4!?r*Z3g#ELFR$nbIx)~kVNvF z;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ rj1QO|K+VS{e^kX&cdc%)l7v0FEW|zQbdBt`dHoFf40Jy#_?-^`5!8Q# literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_cafe.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_cafe.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c8da0fa178818e329cfd38ba3fe45e3033c9cc65 GIT binary patch literal 514 zcmZQzFfZoT)iupGz33?HH7#m#F$2SIp4a*y`fh!_E(61rtpD{43<~uP^$&m$B)+=p zKbW2^S8vV0P*vU{a6;sYu8!hH!*C}C2C!;oF$Q^tddB%o5cvnX4}$7NW}p|3q2) zng}&n{TgKqX|VgQTFef8rs3(hCKS7TAuq@;@yW6gTnzG-M_f4k3evY3+&BHt46*;5 z<&+?arEvn>G&TYRqo literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_negro.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_negro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..868945e9f427713dd200f8dfebd814fc4f7484fd GIT binary patch literal 514 zcmZQzxRTW(vQ*-^il%P1=~~Ch#S9F;d0y*-=)3jxx(q=1|Md(E3iS>34}cIPzPjo^ zn4T?HZw-=f5jY`oMOR00qhYuc0|Qt!vlxRsLp|esCW!n4-3LMSqVY!d-Z1%E6S>g; zsq)nzc}>+8fgrDyfv+R|lVI`<%pc`{GXM8yV5ll@5!h~2Rn8%{Qi(^K0Rw>KC(7E_ zM5xK?*C=C1gY{pvm>v2|!_#q1D0cZmUXWknlVu~g800OFxN!Itq;E60Z~C7ZV*feI zDM1p+bBg12VD3Zs59~h0YCRe03nEr(FnNUkK=NIYd}*JmkmXVR=g+Z2L|PFdkM2T{ qeu&?3%O6$o)Lp9^tR!KNuA6}&vRGO5h^eYwo7;0gc?Wd)=NbTG*MEZm literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/shuk_ft_reyn.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/shuk_ft_reyn.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4fa3e7600a154feee12065d5cf90573876021953 GIT binary patch literal 514 zcmZQzxLdzeghSQKG)VHMY_;0tVg`oaJg@aZ^xgV;T@d-do`K;(okD#>{eyat`0A?v zV0yM(y){Ulf#IdWcUvFd4~gI77#KjR6_~{s^suztnviF9`*P6(M z{!f*!2FahZWMGg;{^K;ye}6hmzJd9p{7>fp{t*4&<9??zFexxUU}nGoAo~k>?Q4Lp z5&n-uUR*=oLDO|@q(B;mcCdR3c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{r}Kf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! s|A6rU(*vma*yN9@ct<8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d64a4bb2298285ab8361056cd7ae64d93e611e7c GIT binary patch literal 514 zcmZQzkoTXTFYgb7lZzP`e)GK62hn%y>vb6z=I1jo$W_m;o>~8`{!cwfe09}-Fg;tY z-Wnv|BJh%dS4l^4qhYuc1H=D%1_lLYF$Q^tddB%o5dFEjA8kwhe#O;iGcfE2nqb5{ zMm&d)v_B){I2f#GM|Tqn8kdLSPnAH@EH`yc!Ma0ow4?RPpClM(Y5W(EuZR?ivF zY^`K3J)avx8f@M*7F$+b*ZxSMH0<($ywOVkt>TkqBe)pkEswZx_!XpYGq`X1A7~%Q z{Pm`#ev-*^i|tim?z_qo$12bEj}5|?W4p%|FMNf=P6{SpYa$o=KUKaO!spBE&o!+> zmTzGGDF2iBzduC&0*e@D9VIN%G*rV%)#K$^a RBfD*0KZ8E&pv%kq0|5WQcJlxL literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/cool.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/cool.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..326b693e141bde3e41e44aaa8bfaa10153da21a9 GIT binary patch literal 514 zcmZQzxLePrn_>ILw=n8zR%H3)Vg`oaJg@aZ^xgV;T@blcLaus#_00Nj^?&L?;;XCv zgX!6F_0|jwd&`$fyqDOnS)yoe^(zR%KWSDh6Qv_#8ZQOnGcb6Xo^>q@`<^xj$OfrD zX?8Qiwp6ajwi?7=Yr9m!BKuQOY57htA0)5rf5QB|`0sFt`t3%))1T{}G+PC<4;_Ha zE98x5u8_0QpKFXR3(-H*M8;9mb#0_T8g}^&g14mRImaikMcj6|NBGq`zu80)LKI1(On4gU$bJ1R;n>>`J*bHx@&cVl_c!Z XbwkY85B-okvB;>r8t8shkm(Bmpze(RERxTNO;cf%&8SPv-yr3=BWh85kZgJz##okie(_G!Gqs z>@VcCuK~J7_&?BmbP$!zz#y(6@6e*~+{7yoT^3?rAuq@;@yW6gTnzG-M_f4k3evY3 z+&2Z82lCH3%PBz;$#aV1bzts8_zxuipHZIqKl645d!YLu{zv!^#OKIen`>ByERXOX nNM4XZ92i~@d2|z^Pi2MWH2SN3s@kaLEF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqW{0v@AOPtcRzPy1`GhQzmV6y z2Iv~$|I8TDVE2h@$UA7du8kB(!;}ZvSI7(UOMJ3y1Q&z68_2KP-t=7HqT zSxyO(NS;$1uLE-*!haz7|BUj?|CzTl*aQ6waX-R;AU;R#+FZjrWO;=DK=OhN;=u5N x$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%4LxM!WNk=-`0pFy92?nebnB>d&P zAbAFc7Lnt+$5gT{-v=@<{0H(Cn8g_68R{A5GeP7Z=spN4m9f=~vxCXkn#hIzPnEA` zVAxZC&XR#aB6&iZPnAg&?rz+u=Es-J}^53;Y27vz`tWZ4KV26@XPE*yRZ>DvtMoBn5pxaXYZ zlpu-ZImPihF!v$+2h#taQJ(of^L7S%pnoCmNB9rK=g3`~YgmUYkMJKzUXVc?7+w&0 wbQgl;A22>(dH^*aoBUA~Pu;b;!AcVL=&}&^tkX5J+vfE%=rhp$sNksz0AXHy#{d8T literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/midas.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/midas.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9adffb69bd5f2a5705eb84d423da9e6d5a32daef GIT binary patch literal 514 zcmZQzxR?H#=L+X+E;;%6=KsAX7c(&Y=6S6TqVLw%>oNf485rcM=U30H|5pE}9wffH z>OYvCEmv<1k>_CDEOe1`x>k%k1H=D%1_lLYF$Q^tddB%o5czeoLPn)dKf~3ohTl{89cV^M8MceoO5+{%=^nuvM}%U;vQ&@;U8m zq^16H#Yc6Wj=B)cTA@b-h s1o`hI>qWLQcHHtuRXlaq>IN%G*rV%)hQF?n-8QeEL7#!{M+K8P0m+4a^#A|> literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/original_green.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9abb79150620ce048da970ef845b24068098187f GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%p4Kfmf-I)mUn~5d*`2AYXx5j6t5Eo^d`CME-&9gP?lRcq4mnn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ng&_F{j1QO|K+VS{kBC2XS%`bq=^EK>^ZFU|8R&jgpdfp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszEgQn}+NP#pA?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! s|A6rU(*vma*yN9@cfp{t*2$>&)t3l=@U{sA9kXAo~k>?Q6uvs^#t& zVMv4BC$1sy@Y+PrvCtP&9^~FaUXWknlVu~g800OFxN!Itq;E60Z~C7Z;@)$XQ-UOt z=M=~5z}%1UA4vayMtSD{%-b34f&PW~AK^a`pCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsv0vvrgB@ZkyN7pwB?}qk;qx0FbA6`Tzg` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/riesgo_de_chubascos.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/riesgo_de_chubascos.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b17323af65559ee0197747e00867e0adebd6ba25 GIT binary patch literal 514 zcmZQz(2vv9)Q^L~$;Au|zj z3{~YV0xubOm2?z08iqSDF#HFqS6~)nkY}i8oX-T&|3LRa&~4QkJKJEGe65LG=>Jst zYLNbH-4=ltBd4%IO2_BDvZE}tt~W@P0bpDY`}#UO8a#D&AJAbp#`ebfKU5c|(rP6?7o zo>LsJ19Kn3e<1s}D^}~tNM8`KQiI7O`>#cyE0QnmQx&p2s{i~sc8EwTLgdk12r|Eo rH&x6{9=kj!ypO7Q>aNudR+6ws*9~#cI$a~XZC*cvJ_Fs43iRUuum^qR literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/palettes.json b/worlds/smw/data/palettes/level/palettes.json new file mode 100644 index 000000000000..9690c2069db1 --- /dev/null +++ b/worlds/smw/data/palettes/level/palettes.json @@ -0,0 +1,358 @@ +{ + "grass_hills": [ + "atardecer.mw3", + "brawler_green.mw3", + "crimson.mw3", + "electro.mw3", + "geo.mw3", + "miku.mw3", + "mogumogu.mw3", + "nocturno.mw3", + "original.mw3", + "sakura.mw3", + "snow.mw3", + "sunsetish_grass_hills.mw3", + "toothpaste.mw3" + ], + "grass_forest": [ + "atardecer.mw3", + "autumn.mw3", + "brawler.mw3", + "brawler_atardecer.mw3", + "brawler_green.mw3", + "crimson.mw3", + "deep_forest.mw3", + "electro.mw3", + "geo.mw3", + "miku.mw3", + "myon.mw3", + "original_aqua.mw3", + "original_green.mw3", + "pizza.mw3", + "sakura.mw3", + "snow_dark_leaves.mw3", + "snow_green.mw3", + "winter.mw3" + ], + "grass_rocks": [ + "atardecer.mw3", + "crimson.mw3", + "dark.mw3", + "electro.mw3", + "geo.mw3", + "ice.mw3", + "miku.mw3", + "napolitano.mw3", + "original_aqua.mw3", + "original_choco_volcanic.mw3", + "original_ice.mw3", + "original_volcanic.mw3", + "original_volcanic_green.mw3", + "original_white.mw3", + "original_white_2.mw3", + "recks.mw3", + "sakura.mw3", + "thanks_doc.mw3" + ], + "grass_clouds": [ + "atardecer.mw3", + "crimson.mw3", + "electro.mw3", + "geo.mw3", + "miku.mw3", + "original_blue.mw3", + "original_green.mw3", + "pizza.mw3", + "sakura.mw3", + "shukfr.mw3", + "snow_day.mw3", + "volcanic_rock.mw3" + ], + "grass_mountains": [ + "brawler_lifeless.mw3", + "classic_sm.mw3", + "crimson.mw3", + "dry_hills.mw3", + "electro.mw3", + "geo.mw3", + "late_sandish.mw3", + "miku.mw3", + "original_aqua.mw3", + "original_blue.mw3", + "original_green.mw3", + "original_white.mw3", + "recksfr.mw3", + "sakura_hills.mw3", + "snow_day.mw3" + ], + "cave": [ + "brawler_dark.mw3", + "brawler_purple.mw3", + "brawler_red.mw3", + "brawler_teal.mw3", + "bright_magma.mw3", + "dark_red.mw3", + "glowing_mushroom.mw3", + "green_depths.mw3", + "ice.mw3", + "magma_cave.mw3", + "original_chocolate.mw3", + "original_gray.mw3", + "original_ice.mw3", + "original_mustard.mw3", + "original_volcanic.mw3", + "snow.mw3", + "toxic.mw3", + "toxic_moss.mw3" + ], + "cave_rocks": [ + "bocchi_rock_hair_cube_things.mw3", + "brawler_volcanic.mw3", + "ice.mw3", + "layer_2.mw3", + "original_gray.mw3", + "original_mustard.mw3", + "pyra_mythra_ft_pneuma.mw3", + "snow.mw3", + "toxic.mw3" + ], + "water": [ + "dark_water.mw3", + "deep_aqua.mw3", + "deep_chocolate.mw3", + "harmless_magma.mw3", + "murky.mw3", + "oil_spill.mw3", + "original_brown.mw3", + "original_gray.mw3", + "original_green.mw3", + "original_mustard.mw3", + "original_volcanic.mw3", + "pickle_juice.mw3" + ], + "mushroom_rocks": [ + "atardecer.mw3", + "brightshroom.mw3", + "original_green.mw3", + "original_ice.mw3", + "original_volcanic.mw3", + "original_white.mw3", + "riesgo_de_chubascos_cafe.mw3", + "riesgo_de_chubascos_negro.mw3", + "shuk_ft_reyn.mw3" + ], + "mushroom_clouds": [ + "atardecer.mw3", + "greenshroom.mw3", + "oilshroom.mw3", + "original_aqua.mw3", + "original_blue.mw3", + "original_yellow.mw3", + "riesgo_de_chubascos.mw3" + ], + "mushroom_forest": [ + "atardecer.mw3", + "autumn.mw3", + "count_shroomcula.mw3", + "cursed_gold.mw3", + "dark_green.mw3", + "lifeless_gray.mw3", + "original.mw3", + "snow_dark.mw3", + "snow_green.mw3" + ], + "mushroom_hills": [ + "atardecer.mw3", + "atardecer_naranjo.mw3", + "atardecer_verde.mw3", + "future.mw3", + "original.mw3", + "riesgo_de_chubascos_azul.mw3", + "riesgo_de_chubascos_cafe.mw3", + "riesgo_de_chubascos_negro.mw3", + "watermelon_skies.mw3" + ], + "mushroom_stars": [ + "atardecer.mw3", + "cool.mw3", + "dark_night.mw3", + "halloween.mw3", + "light_pollution.mw3", + "midas.mw3", + "original_green.mw3", + "original_night.mw3", + "purpleish_night.mw3", + "riesgo_de_chubascos.mw3" + ], + "mushroom_cave": [ + "argent_cave.mw3", + "glowing_mushroom.mw3", + "green_aqua.mw3", + "ice.mw3", + "original.mw3", + "really_dark.mw3", + "toxic.mw3" + ], + "forest": [ + "agnian_queen.mw3", + "atardecer.mw3", + "frozen.mw3", + "halloween.mw3", + "kevesi_queen.mw3", + "original_dark.mw3", + "original_fall.mw3", + "original_green.mw3", + "sakura.mw3", + "snow_dark_leaves.mw3", + "snow_green_leaves.mw3" + ], + "logs": [ + "brawler.mw3", + "evening.mw3", + "mahogany.mw3", + "not_quite_dawnbreak.mw3", + "original.mw3", + "riesgo_de_chubascos.mw3" + ], + "clouds": [ + "atardecer.mw3", + "charcoal.mw3", + "cloudy.mw3", + "cotton_candy.mw3", + "original_green.mw3", + "original_orange.mw3" + ], + "castle_pillars": [ + "agnus_castle.mw3", + "cheese.mw3", + "chocolate_blue.mw3", + "dark_aqua_marine.mw3", + "dollhouse.mw3", + "gold_caslte.mw3", + "keves_castle.mw3", + "original_gray.mw3", + "original_green.mw3", + "original_mustard.mw3", + "original_white.mw3", + "pink_purple.mw3", + "purple_pink.mw3", + "sand_gray.mw3", + "sand_green.mw3", + "shenhe.mw3", + "whatsapp.mw3" + ], + "castle_windows": [ + "brawler_pink.mw3", + "cheese.mw3", + "dark_aqua_marine.mw3", + "dollhouse.mw3", + "original_brown.mw3", + "original_gray.mw3", + "original_water.mw3", + "red_castle.mw3", + "shenhe.mw3", + "underwater.mw3", + "water.mw3", + "whatsapp.mw3" + ], + "castle_wall": [ + "cheese.mw3", + "dollhouse.mw3", + "grand_marshall.mw3", + "hot_wall.mw3", + "original.mw3", + "sand_green.mw3", + "shenhe.mw3", + "water.mw3" + ], + "castle_small_windows": [ + "dark_lava.mw3", + "dark_purple.mw3", + "dollhouse.mw3", + "forgotten_temple.mw3", + "original_gray.mw3", + "original_volcanic.mw3", + "original_water.mw3", + "sand_gray.mw3", + "sand_green.mw3", + "shenhe.mw3", + "water.mw3", + "whatsapp.mw3" + ], + "ghost_house": [ + "brawler_cyan.mw3", + "brawler_orange.mw3", + "brawler_purple.mw3", + "creepypasta.mw3", + "crimson_house.mw3", + "golden_house.mw3", + "halloween_pallet.mw3", + "orange_lights.mw3", + "original_aqua.mw3", + "original_blue.mw3", + "original_dark.mw3", + "original_white.mw3" + ], + "ghost_house_exit": [ + "evening_exit.mw3", + "golden_house.mw3", + "original.mw3", + "original_blue_door.mw3", + "underwater.mw3" + ], + "ship_exterior": [ + "blue_purple.mw3", + "doc_ship.mw3", + "grey_ship.mw3", + "original.mw3", + "reddish.mw3" + ], + "ship_interior": [ + "blue_purple.mw3", + "bocchi_hitori.mw3", + "bocchi_rock.mw3", + "brawler.mw3", + "grey_ship.mw3", + "original.mw3" + ], + "switch_palace": [ + "blue_grid.mw3", + "brawler_brown.mw3", + "cafe_claro.mw3", + "color_del_gato_2.mw3", + "color_de_gato.mw3", + "green_grid.mw3", + "gris.mw3", + "mario_pants.mw3", + "monado.mw3", + "morado.mw3", + "negro.mw3", + "onigiria.mw3", + "original.mw3", + "original_bonus.mw3", + "pink.mw3", + "red_grid.mw3", + "verde.mw3", + "verde_agua.mw3", + "yellow_grid.mw3", + "youbonus.mw3" + ], + "yoshi_house": [ + "atardecer.mw3", + "brawler_green.mw3", + "choco.mw3", + "crimson.mw3", + "miku.mw3", + "mogumogu.mw3", + "monocromo.mw3", + "neon.mw3", + "nieve.mw3", + "night.mw3", + "nocturno.mw3", + "original.mw3", + "sakura.mw3", + "snow.mw3", + "strong_sun.mw3", + "sunsetish_grass_hills.mw3" + ] +} \ No newline at end of file diff --git a/worlds/smw/data/palettes/level/ship_exterior/blue_purple.mw3 b/worlds/smw/data/palettes/level/ship_exterior/blue_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2e098de76404e6c399e3b5e06fbd264d83c28b36 GIT binary patch literal 514 zcmZQzxLdzU=d6X0FPmkS@7c)7#S9F;d0y*-=)3jxx**b#CsAaP3POH$)&F{s{A{^; zYmhtx14NcT=}H*`!+#)Ofmw_}o}r#`J`+S9VFFCP)nNdBB91A|1eN&VFN zA3(hj{SC|?<$p5&_lM};ZuC2yfk}b+0W$*z0NLLlU|++a_`p^n9YY%IK5-3s2Tj+t zkpgL$@*w*Pc|m@OPnM0~Vvx5y;=%iQH@E=J2 zKchVJf9CBB_CWta+>h`dh|iI`HrKEYSsvj(kh~y+I54~*^5`xE$v)Z^(Ve+*ma-siI<*PyR z=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886IP8MGK8i)%pq`~eJ z*N}J6bX^-MkcKG_vagUA*fj9JyK@zquT z>p}Evxq54mJOhK5=2}xjw<$rE=?o12fqVsKF$Q^tddB%o5c#Dd&t)KLVDhyla-siI z<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886I{%pnn>PA}E z&8odIq`~eJ*N}J6bX^-MkcKG_vagUA*fj9JygnDPLa{Cvs) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_exterior/original.mw3 b/worlds/smw/data/palettes/level/ship_exterior/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..20eec6ee80bedadf37912970c3927695055d3c49 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf?4nN7uniY1k!3+%lfqVsKF$Q^tddB%o5cv}#oU%EZ=S_{AVDhyl za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886I{z6{+ z8lY>0|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!^nlm`H@hI|kJ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_exterior/reddish.mw3 b/worlds/smw/data/palettes/level/ship_exterior/reddish.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..674aafd0c0a6a2dda0b9a295989935373b814a51 GIT binary patch literal 514 zcmZQzxLfZka9TlFH$&u|hMDc;Vg`oaJg@aZ^xgV;T@cw2Ygun@XlT04^_MS5e0A0T zdJsKZuHG6X&%lr&@lK`K^rvH;9|OaGAYXx5j6t5Eo^d`CM1F+~x2m7+2Gh^BF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pXNH7* z4a5Z)(qQ+AYsfojx~`2BNW+u|*;mL5@=JWOYy=mByyX!W4!?r*Z3g#E{{!s<$)B^F z5+sp4r#M~*=01e~K=S_?<(dC8Z)dOv`WNDUg#SQ%j@-4mhIPpD2>*fP1sTMF;RTUL vcOgjr0pkOv2T=2|$!7=V`fc{#Zy;-qE(>yxkshyChk%!2uI71Q_@e^=o!@%I literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_interior/blue_purple.mw3 b/worlds/smw/data/palettes/level/ship_interior/blue_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9ac0f022af3db2dcb4559286a7c215015b18d7a7 GIT binary patch literal 514 zcmZQzxLdzU=d6X0@Akx~ESciT#S9F;d0y*-=)3jxx**b#CsAaP3Y%q?@7YL@`0A?v z^&on-T)j0&o`C@(%b#?mjDg`lkgvcj#vsp7&p4k6B9AZuCSPkJ7y3U{z8WNd&XR#a zBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2E!j0Rw>SZxFDrVNiTvtB{T%4R)Wn zhP;EO>)J?xG)#GreTBRrzr-iYMsP96TOM)Y@GD5)W^mv1KhQpq{5i`hK@!PxisN-) z?nC$wB>$gLp7}rXb_RQ(e< a05u<*d_mpiIb3@`0A?v z^&on-T)j0&o`Io7=9qz|V|M7;R0f9sK)wRA7=t`RJ>z^Pi2O+bZc#ni0>$ZmF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&EJstYLNUnO9lpsWRv=-^*?}mA^IDbKg$1P{_hXbzuo9}Is=me^8;oE3;?pfkk`Hj z=o;bwION4O8_2KP-t=7HRE&T>kS zMDm>CcpaGg5dH(n|7VnE{?ELf!5-*ei2D)#1MxX>*XA15A}FR hAo&N3511Z6&BrERPz^Ph&-FDhhmrJ8$&-Un0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ig&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;R>RR93Jdw87y literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_interior/grey_ship.mw3 b/worlds/smw/data/palettes/level/ship_interior/grey_ship.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..786802304f335dde42c0a687bff76a19fc2d42ce GIT binary patch literal 514 zcmZQzxLe;U@KPpE_p@!8-_N+o#S9F;d0y*-=)3jxx*#%J^Sx!U*R?3i^f|>K@zquT z>p}Evxq54mJOhK5=2}xjw<$rE=?o12fqVsKF$Q^tddB%o5c#Dd&t)KLVDhyla-siI z<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886Ye%1f#=PZNV zzDHq5gWV^tA@88+x;9cE4O1RuUm-8ZFY(E;5nK%NmPcGT{0h>y8QeGh53~;?f6j7B zkVNvF;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ig&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;R>RR92&9Dc(9 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_interior/original.mw3 b/worlds/smw/data/palettes/level/ship_interior/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a208db211f51dd7967c5d1e2d5702488d0b9d7ed GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ig&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;R>RR91#$av}i literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/blue_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/blue_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d613b8061deb3310e59c1ff98d2adeb268aa28e4 GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#TlaXJFv=71xk=K-Godf$S^f1^FdDSvG=;LEiF+3x{7p`Zk06rXce`^5-n41W6>% zDUR2Hxewt#koc-C!jNdvsZldz4j=n5x>fxjpy842Aj804FSX7XSbN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/brawler_brown.mw3 b/worlds/smw/data/palettes/level/switch_palace/brawler_brown.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a3073c31adca276933a73da4fe5187fd14cb73e7 GIT binary patch literal 514 zcmZQzxLcnj;%0i*uv>FuQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0)0PHyo4ptq{sYx3FpDwBGt@KAXM)H-(0ve8FB)%T?+uf$HIWPb zpDJGsl0Rq3z#x%qQa`o+2T(6We*^PJ`Jc@H{UQ3d8~sjaU{YXyz|4RFK<+E#wXXrX zM)*Gtd2tPS2V=WXw`qPD+QIe}@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAorZJoDw9F zJf}Eb2j)J6|3LEp8ReP(GjC_G2l^M{euV!(e2(0;xrTMf@(BNd}Q@k1h*xkFx3!Q&qb*x95KH4(RgpqX8dweTD!4 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/cafe_claro.mw3 b/worlds/smw/data/palettes/level/switch_palace/cafe_claro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..90ffc5cf73f8caf0c9e92d7ce04a6b688c9b556a GIT binary patch literal 514 zcmZQzxLcnj;%2HNyHR&zQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE1IUM=b{N{h_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^7Eqswefr( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/color_de_gato.mw3 b/worlds/smw/data/palettes/level/switch_palace/color_de_gato.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b5ba743bfae9cf2bb8443b692f0b4f6ae023912d GIT binary patch literal 514 zcmZQzxLcnj;$|AlSj)6Asl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@DvtMn}W;($)B^F5+sp4 zr#M~*=01e~K=S_?<(dC8Z)dOvnh$Y5!haw>NAB8O!#ZSng#SSDf(+uo@Pf!=bKe8T r2TTv3=3|pTs^Y1;RySBl!X8}~rE=SKqoqbPe_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/color_del_gato_2.mw3 b/worlds/smw/data/palettes/level/switch_palace/color_del_gato_2.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9c52a56a872a27b8dff95fb1439873ffd3a28293 GIT binary patch literal 514 zcmZQzxLcnj;%53?pjvWcQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE1IUN}fc`-TAo~h=L4Ju(mW|+IkheVI!r@ntzRlpiDabsK{5i`hK@!Px zisN-)?nC$wB>$gLp7}rXb_RQ(eIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{Ad8bl6{{5 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/green_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/green_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3a8ded956b435a03b03666b57f3ea4cd54bcc57b GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#Tla2dZWi*N}HW)rH`J>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=-=PaiLNhHrH zj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM;5h569{#y5As literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/gris.mw3 b/worlds/smw/data/palettes/level/switch_palace/gris.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8e8df8008bb40d0a7d1988bb227c4fa1731010db GIT binary patch literal 514 zcmZQzxLcnj;$}J}NFsS-QhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE!#PVsw<$pw+QIe}@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAorZJ1clF> z;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ sj1QO|K+VS{e^kX&cdc%)l7v0FEXX~|sz*#!?b_U)`^h_?%g>Jn0OBZp0{{R3 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/mario_pants.mw3 b/worlds/smw/data/palettes/level/switch_palace/mario_pants.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b18aee2e184171a3b62dc46b3b9f725efe844799 GIT binary patch literal 514 zcmZQzxLcnj!tKk&AaA)bsl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK;{&D# nQ1h|LA64#(3v!RL>Jd{_yEeDye)10J^7EqsawvPU literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/monado.mw3 b/worlds/smw/data/palettes/level/switch_palace/monado.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c37acf989847366753986709d39c92b78c2dee1b GIT binary patch literal 514 zcmZQzxLcnj;%1sFpkcZ(sl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@4{^34C4w=>uS{R?qF!haw>NAB8O!#ZSng#SSDf(+uo@Pf#r wyAUM*fbjv-1E~4faNudR+6wsmj$^;S@npis$HAgb3b_pbou$w0C>QBCIA2c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/morado.mw3 b/worlds/smw/data/palettes/level/switch_palace/morado.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ab495c8b33b658f4539317aa5dc7da23923d8cf5 GIT binary patch literal 514 zcmZQzxLcnj;%2Jn8xp!Psl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@tGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0=q?1w tKVW>o^Z;r;Hu<9}p1NywgOw!g(PcsIQC2--s%qEf_S{e20bPE6GyuHnd?)|_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/negro.mw3 b/worlds/smw/data/palettes/level/switch_palace/negro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..dd9db46ffdea238a8bc241c3948f2fe143f2385e GIT binary patch literal 514 zcmZQzxLcnj;%0hGB3pN3QhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE0|UcS5lvML?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cG*(EyN*d%FMt literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/onigiria.mw3 b/worlds/smw/data/palettes/level/switch_palace/onigiria.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7632dc08b226eca8998e5e06798767184ed6748d GIT binary patch literal 514 zcmZQzxLcnj;%2I;tLwQjsl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cfp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszEgQn}+NP#pA?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cy8QeDonFn&uIm;~8`{!cwfe09}- zFg;tY-WnqRl7UyLkJVUeyAcD!f1r8=W-$hNhI+>NOc418x(|ZtMdOX^yZjKK0P2P4Z(#l?|C9N@KScj_qu=QaObW~om>Dnt$bE&p_BBA) z2>-_+FRmf)z;FA_=WYy!cCdYgydb~CC(A}~G00mUapCYQNZ)2~-xOpX$UWyQrvynP z&nb@Afw>RiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsxNaqpW(wRMoD{?YW=41G@bDXaI5gdhP%K literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/red_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/red_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..1336dc83468039d90876692cc112596cd79bd549 GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#TlaX8^iHTtnUgRTqK>vagUA+_kxeb;$Av|AFKM8N`9EgvcXY24R8Z rA22>(dH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+vuB3iG1@=e&2` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/verde.mw3 b/worlds/smw/data/palettes/level/switch_palace/verde.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..92515d0c32f5169b0017cd26063b42750298e4be GIT binary patch literal 514 zcmZQzxLcnj;%3^;`;vEKQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszEgB$M(UM@Zi?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cfp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE1IUL7ei+)p_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^7Eqs$>Mz4 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/yellow_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/yellow_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c41276492eb0ccc73add33198c80b001d1874c60 GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#TlaXJDuo7T1t>K-Godf$S^f1^FdDSvG=;LEiF+3x{7p`Zk06rXce`^5-n41W6>% zDUR2Hxewt#koc-C!jNdvsZldz4j=n5x>fxjpy842Aj80BACJQ2+n{ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/youbonus.mw3 b/worlds/smw/data/palettes/level/switch_palace/youbonus.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b213f566079581f1d272ed2ce0207c6749b38fab GIT binary patch literal 514 zcmZQzxLcnj;%2ID$q}|Osl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cp}Evxq54mJOjf~6;It`I?HVH^B5TZ1NjQfVhr*O^^EhGAo5}i(hTv8@=Wr~F!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C4p{pbK>e<81Z z4bU~h|8dBRYsfojx~`2BNJG~Rv9FL9JuvF%l%5xn}&z{i9#S9F;d0y*-=)3jxx(q=1hFHsbzPLpaZl=q9LE@{c z{?~)(*>d&PAbAD`4!?qQR?TIOE0Y)){?{`wC@_mL$TQS4&S!$ib2IQV7zutAHIstL z*P6(M{!f*!2FYuxGB5;rt<4k2E(e+g(ci%QQT`|Me}9Pn51PNz(?vCv)@U(c0FeEK zy!JJhWp}CBm}5wT-RCKjZ*a_n!_zAkyZmd8O}rI+@yW6gTnzG-M_fSu*=BIx^glDi zeofULLjk8^zsn9V_o4c4n`niQ75`b0uaYo%WdAWRyl~+RU6+Y0kLo``2613`LFCb0 r2=bq+=n2IvE!^_ifw_L0{r4NlV)eg~9lVAo0~z z|LZ~YY`J=CkURqehhIT@8_z3=Tulas|Md(E3d~{*@(lHi^O+#>+zh-7MuJ~O&7@%R zwI*_*|5N3wLGqfa3=BbDYx4xM%YkM=^fxenl>f>6-yfp?gXZt_bWu&EHChZ90Azn5 zuYJvB*gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 wg&_F{j1QO|K+VS{pBYEXX}Zdc0m80$z%_n&)lNb%7 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/murky.mw3 b/worlds/smw/data/palettes/level/water/murky.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..57ea1ce44f5e72698c17e239141e7b54c163b17a GIT binary patch literal 514 zcmZQz*j2t*f=y9PHBd&PAo;~2shY% zUuz;4`ae~^8YKV5aj}S5`o!`r)jNQEi2erVkMcj6|NBGqZ#Vj#{!sUT-2-O^3;?qK zgP?uQExBm*+xi&NVE1k2u#)sJd>yD8hh08Ta)w^4b9}OF1Q&z68_2KP<> zGehj>aoQYamOi7n))nSHRR38E)$`xe4-ve>29rnj-(r!rK)<|?b;$Cl{_E$s#v{WE wkwF7s2x80&%YfeM-h096fzpa1{> literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/oil_spill.mw3 b/worlds/smw/data/palettes/level/water/oil_spill.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ac1ffed27f62b1137ec363aebc34115fd4b19d87 GIT binary patch literal 514 zcmZQzxLcph1A#39EdrB^85n-^yw(TNckAnQL1aU$W&JUU7J;QAOGQB9tE>LkgXr0E z_0}MH28I)n=Zabcj!9^$GBErH@)elH7~~o18Rs)W(dH^*an|yX)uHR<={RXn;=&~U980qnPbqIJV=4zg|MVEKt002gCeFp#l literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_brown.mw3 b/worlds/smw/data/palettes/level/water/original_brown.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5f5366cebd110b6b7a55f1fbf5a4f1665ef1d4db GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf?4nN7uniY1k!3+%lfqVsKF$Q^tddB%o5cv}#oU%EZ=S_{AVDhyl za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886I{z6{+ z8lY>0|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!@6mv4v#0J7tJ?EnA( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_gray.mw3 b/worlds/smw/data/palettes/level/water/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b5087eccbed5ca09f590dc7d4c7288b950064b7b GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjgt$a6)fEe-58doeKl2l5q|#Teun>KW%VLFC04q#5EF<(cG}Ve+*m za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3H`q2T%{z6{+ z8lY>0|KpGs*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!@6mv4v#0I1}AC;$Ke literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_green.mw3 b/worlds/smw/data/palettes/level/water/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..6697f2839edc0baec036037589d7dc6746935572 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf^#x+d(%rlw)1v4=G2l5q|#Teun>KW%VLF5^Lpx@?3VtpM+HAw!PB?E&*vPu2a`X4~O5d96zALV~C|M!RJ-){6foqtGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0 x=q?1wKVW>o^Z;r;Hu>zpT))l!`we8x(PcsIG1BAp>Jac!%+)+^i!R>~3jj0+eLMgF literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_mustard.mw3 b/worlds/smw/data/palettes/level/water/original_mustard.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bf14a8cb157e0ac7a87163cea6a81c71e0e9d307 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf^241B;R%5B{Mhp!9fqVsKF$Q^tddB%o5cvnX4}$7NV@cUVE!oolli|tME`c9-{}lY3d|3f8886I{z6{+ z8lY>0|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!@6mv4v#07%k(Qvd(} literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_volcanic.mw3 b/worlds/smw/data/palettes/level/water/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ef22bf7bf6a0f10c77cd5cc8c7e52859bfb75d04 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf~6;Iu@y1_~k_6!XFfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 wg&_F{j1QO|K+VS{pBYEXX}Zdc0m80$z%_n&)lN0F>W)X8-^I literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/pickle_juice.mw3 b/worlds/smw/data/palettes/level/water/pickle_juice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a27ebcfd89750ea2e47c9b4979b175c517aa673d GIT binary patch literal 514 zcmZQzxLePoR;kTzo+(u%7UDm-n1SIp&ue`UeYd_|7eqG1TGmI#rwFpky;K8ByERXOXNM4XZ z92i~@d2|Rd$>ZUW8WVR;fJaHPUm7OX8*#>fB``2jX3RVtd;Dg z=W}C7gU!3fV#})Q+8-&DhFw08H(KexReZ8+1Q&z68_2KP<>1MLHuzupw$ zdV5s}--sF2e{yX1*y4q+aM($~VG|mo-J2z z4U%VI==U@ZyPg(Z{2R#r55&Lg#Teun>KW%Vf#h3xvUT6v`WhK}-$;hZ*P6(M{!f*! z2FahZWMGg;HmRRl{{ybSf%&8SPv-yr5dGVYey3l|`d$BknE?ZU>@VcCuK~J7_&*ML zaSeF~P1m)N0%_YB?164V1|atq@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAo+8aQ-UOt z=M=~5K==yG2>*fP|1-)n|7V7n4dN>>Bm4*AbL6hgHLOFHNB9pUFUTMc3@=Q#f!qm_ vf57;F=>gPyWVgfQkE(d;uGI}zlCVdY1?g8-Jz}bA*XH)zPu>At{#rBulJG_-`$;6v0qTOt*P6(M{!f*!2I)U%$-p3y zY*Ih9{s&MkM81Lfqx?_i|Nap3w;TOVzpN=6{D7GO1Axpg3^VoAo+8aQ-VOQkJo{@58*$M{C`Gy=Ksvw8SI%L z@(Rod|AFK=a@Xb>)*;Iy{0EX3WDsYNXMo6KbKe8T2TTv3=3|pTs^Y1;RySBl!X8}~ WrE8)5+k)PX|) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/crimson.mw3 b/worlds/smw/data/palettes/level/yoshi_house/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..e334904a372b04e6ddeb0cc1f681ee7283c5e8f1 GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoi$UKk4|%#S9F;d0y*-=)3jxx*!rF?_~Ky=7$VOe0A0TdJsKZ zuHG6X&%od#8RFC}a3k!83z^Phg; zsq)nz`E!;G3=+vE^;7GA0QExT8<;=J|78B}57EEf=y!Uzzz>-oJq8Q_vcHhmzUGtQ zFEvSd3~8|Y#5Lp{G<35ZPlsa4gX}Bh1^FdDSvG=;LEiF+3x{7p`Zk06rvI5C?m1^U zB?#pDcpV5|ff?aHkpBOS^34C4w=>uS{R`qNFeCg2;&bG#%{8n;mPhyxBrnJy4h%1d yJh}@(@(&mvFg<{pk4^rlil^>c-C!jNdvsZldz4j=n5x>fxjpxjcR-haAprnZw1M9M literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/miku.mw3 b/worlds/smw/data/palettes/level/yoshi_house/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5d3b9e81144a649d6b51d4f089eb7eaa7bb8cd4d GIT binary patch literal 514 zcmZQzxLcpBn&#vYogXBUJfnDWF$2SIp4a*y`fh!_E{KH4J6U=}dqji8S6BV72hp?T z>a9WY3=Bs^jwz^E@dHg^VE7N@dqj&d$TQS4&S!$iXY0PVE%uX0o&(eck*_t83;mxe zUk#E!XUV`Ik!(^wwf+ZCFGRk9`J?<#=KuZ>{o9Rxr>j_bL~o8~zyKio3wiBpD%EVn z|1)DqgWV^tA@AU3$>X;%2~!?qUm-8ZFY(E;5nK%NmPcGT{0h>y8QeDonFo?TXE`MZ z)*;Iy{0EX3WDp027epT2 ug&=tqr8i!i<8jL$Rq@nas~fB&VUMmGrEtBV1!;Cz7q literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/mogumogu.mw3 b/worlds/smw/data/palettes/level/yoshi_house/mogumogu.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cf76396c8fb0360e684214e03ce05fded5fa2e92 GIT binary patch literal 514 zcmZQzxLfb*xiQhCsIu%Vr%`nMbXPJeC@l$qkqfB``Ezjv{(i3ptQ z7AK4$4R+r}n<(4oIzGNvfbK&FAo~h=L4Ju(mW|+IkheVI!r@ntzRlpi>3?R3dqD07 zxn9ZIN%G*rV$Pxkp*`h^eYwo7;0gc?WcPi6Q{FsD)|( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/monocromo.mw3 b/worlds/smw/data/palettes/level/yoshi_house/monocromo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8e1529f5149896fd2b6552cde7e7e59dba5fb36a GIT binary patch literal 514 zcmZQzxLdEOnyvfZw%AW1c~0@>0W$*z0NG#2YhMF#KZZ1h`_NT@>?`C2`6WJCHiC;m-tveGhhIVZ zHiP@7|AF>_*fPAv{nxLF5&f5&pw2kMJM5JT~_sn~zQYsEViV dTHRnJ343%|kb9I>kC>|3wYfd_lXt+B2LNGMh~NMK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/neon.mw3 b/worlds/smw/data/palettes/level/yoshi_house/neon.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d3e183a770fd5c5a6e7721f0ef3212d8030f2ea9 GIT binary patch literal 514 zcmZQzxLeOq!vF$ca&j>P!*8C~`XKsleZ4M-gvx^zfy7r={jUenv*qfoL4pd*U@15u z4`)Fb=<;%*$O`V(pR;6OkVrPEpIZN;9wgI%V&2_)1?KHWztcf74^U)4A|UtuXD;Nm zhxh>^ghry|3wYfd_lXpOf!&m@77fJj8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/nieve.mw3 b/worlds/smw/data/palettes/level/yoshi_house/nieve.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..07db0b1339ddcd516ef120e3783183d9ac0b80cd GIT binary patch literal 514 zcmZQzxLf}|bZg%CGRykv`n~m&iy0Vx^Sssv(Rb_XbwMOV{(b1)`tM~R@zquT!Rltq z)mtOvA^I5@{?{`w?5*d>%`QAwX;=r5&(?i!TkI#1JO`*7B42AF7y3U{z8WNd&XR#a zBH5&VYW)wOUWj}H^GErg%>VsC@(c{yjee(pFWX!HfSCaUfZSKeYhMGiA6)^={kY^o z_7(Di{1Trm8^OgOZ+XOp!>=HHo56k4|3Ldd^5-n41c6*1uLE-*!haz7|BUj?|CzTl z*fT-o6_^qJ1IcsbuFW;9LzYMQ4#(3v!RL>Jd{_yEeDye)10J@(r;7;SHAz literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/night.mw3 b/worlds/smw/data/palettes/level/yoshi_house/night.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7cc0122339b0e41c8d372496b934ffcdf9743ac6 GIT binary patch literal 514 zcmZQzXpupNlZzP`e)GK62hn%y>vb8BKuLjAVvt(e9NH(dT zTK@y67b4%l{89cV^M8Mc{_RG;(^)m$-5xMAU;vQ)g}nAPTU;&ND*Z5|!R`~+kay5@ zT^lKohA9uSuaFnym-uAa2rdSB%Ofrveg)~<4DOr$2igacKW8~52;};B9SC268R0*W z{C`Gy=Ksvw8SH`n1@RS_5&i@5Ida$L8rC7pBm4)F7i16zh8IL0-Gw0e2aFGx9ze~< lCVy1LQ+KUyu#$v5x-7^&%Bn|9Rqfi`p8Lr=pvy0j0RS0@dUyZ; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/nocturno.mw3 b/worlds/smw/data/palettes/level/yoshi_house/nocturno.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..de764ef9e823f6e40030de28d50f132a76c5ef1e GIT binary patch literal 514 zcmZQzSf;Z~XQ>K|oLtPn@SEqgK8U_sU$4u6B;RbcOa~;sy6S&Dh@LH1Zw*q#z;IHb z*fQBv!mZhgf#E-p-)tquAkR?GIG+h3pRN1ew%AW1c@9t)M84KUF7$t@d^Je^oFxN; zM6yZ!)cPMly%6~Z=8y6}ng9Dk^lvx%ot|vkZ1sSd0Rw>SFXXka0lG%`KMr|u4S5Gm z*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~1af`64ur43jPM^w z{y(EU^MB^;4E8|(g7^x|2>*fj9JyG;SV_VjT^8gXWz{35s&;K|&;8^b(B+q^008FUeqaCq literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/original.mw3 b/worlds/smw/data/palettes/level/yoshi_house/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..051d266fbd86097ec67979f677f41cccc748400c GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8cd}GqK9>a&UtRUT9z@TU ztG5QpGcc^@t<-ye3nE`@A{Y8U zRlXV|f6kJDK_c0tero*>pk9c41M^4upUnULA^Nu){Z40KQeb`nG#?#+>@VcCuK~J7 z_&*MLaSeF~P1m)N0%_>FA@&vWg8UMnEE~baAa8lZg~P8PeVf63Q;>Ne_nfnw5(IL6 zybjEL2>*fP|1-)n|7YILU=Q>!y8l3Yj@-4mhIPpD2>*fP1sTMF;RTULcOgjr0pkOv o2T=2|$sbkm)Lp9^tR!KNE(>yxvg#32Rl7E~=YH}I=<@TU0RyRexc~qF literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/sakura.mw3 b/worlds/smw/data/palettes/level/yoshi_house/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d099ba93531275332326a6a5b04e9d7d76a46d19 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*!rFZx|?B_9GS~zPjpvJ&2wy zS8olHXJB|KDyTgrV7j?%83V(AAYZmjj6t5Eo^d`CL_XbsFLqO^Y1Yjmn0&2?TW}pe<81Z4bU~h z|8dBRYsfojx~`2BNW;($wy%&EkC>|3wYfd_lXpOuFRupxL+*fT literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/snow.mw3 b/worlds/smw/data/palettes/level/yoshi_house/snow.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4fea26a690b26e3b84314d2871e9fc2a1a769840 GIT binary patch literal 514 zcmZQz*jt|I`X+W$(fKsf;#t*`iy0Vx^Sssv(Rb_Xbs6CDnXYrnHx+@zS6BV72hp?T z>a9WYOC{b*Y}YJ`i*}n+4&l!!FP4eYkui;z0`VCbJWbEK7KVLKL&)FEuq~AqFPPwxG3G?^jzr!KsZ8!RzUKBT{d==0>bO5rykk`JZLe55i zt}(hSME^_^8AnamwUGj8*yT3}-jbT<9G@&3!NnkNdBlaouONM!!F|*J%n<$WT(`to z=2w^B31wjTU(djxz%0ff&rr`ep9vx#$rq=*Pux^+o)S#H)%P8)CkG=!e{i OMMmY-K=-49s%ijSagbO5 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/strong_sun.mw3 b/worlds/smw/data/palettes/level/yoshi_house/strong_sun.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7c671aa368ed47023734baf45ab897f7b3cf5f5a GIT binary patch literal 514 zcmZQzxLeLF$2SIp4a*y`fh!_E{KH4w;Qf!*2@Noude!E529zw z)mww)85llu-Zopw6lc7inStRykiVW;j6t5Eo^d`CL_S;hy=}3dMDiSo`lBY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lm1&3P}C~$^U1RXa3K;oxvXHUl3n`8R0(=pCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsxNaqpW(wRMoD{?YW=41G@bGYyjEIe}(`6 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/sunsetish_grass_hills.mw3 b/worlds/smw/data/palettes/level/yoshi_house/sunsetish_grass_hills.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c51bdfc6a7d3c0784d33fe3b73dca7557de88146 GIT binary patch literal 514 zcmZQzsLpm1d?~qB>AjnAa9RB1Vg`oaJg@aZ^xgV;T?V*3L={MUb=CiR5ItM2-Wnv& zz+l8Ui_uNcUg)JH1H=D%28NfCVhr*O^^EhGAo5NPewpkk_@S>U>)j z2w#C2;Xjc49mYFM)y&%%WPtt!@fDa6{sZxka={cOgiA s0b>JG0@QqL@<&xXb=T?!D@oX+%Yxjata`*$)vnF$xu3iPx_pHd0O>G!F8}}l literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/atardecer.mw3 b/worlds/smw/data/palettes/map/forest/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5ce855399b9857bb393bf4cb8002786baa090204 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3OrBfJ#bm_%g&D->QtH*i)c>D(f3bWGNZwkhnBA7uh|`O4GaHBx zF~45dTmPdw1H*oxPYQYKng9F8C(A}~G00mUapCYQNZ)2~-}FDwe31M*rnR-7n6Gfy zNrCjE0+75IXB{g??p@vZWK=m24Kp1G03qq}3=Gl? Sx^AE2TE!s#!88GXd;kDyk!$P# literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/burnt_forest.mw3 b/worlds/smw/data/palettes/map/forest/burnt_forest.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..58b0648f18e36da4365a6d545a70ecf5a73d739a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K=gg*ZFCx z?h=uZi!tW}+h-W`Ep1M9tC+X`M|Y6=1TzJ1P1Ptrt6*gP|C#Sq%h!P9%fmEPx0v>u zg6skDA?DZXLiB^&P{>=){NF!5SvG=;LEiF+3x{7p`Zk06rvI6Np#hYCz__-yfq6TF zJrhVjDgeogF~~D;K=ggTb6T} zu?I@X#h7z~?K2GemNuulRm@xeqdQ1_f|-Ihhgp=LRWP#t|IGKQ>+4b>ov!Kp1G03qq}3=Gl? Qx^AE25aEGt2LAW}0O1;G(EtDd literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/halloween.mw3 b/worlds/smw/data/palettes/map/forest/halloween.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c3899751bf31b65cccd242e0402f0b9ea5c3c3be GIT binary patch literal 514 zcmZQzKn0VF85sUEU-p??+-1rP4qYC^f3A~l``))W z?t9++Pu#k}=Dx`X5s%x)&USBmnp3P#rd zpZQ+3d<{rG-u-66T*LcnAR9n@i23!p5d9$c6!O+H|M!nimW|+IkheVI!r@ntzRlpi zDabsK`~$|dwGGVMf$j$BM+G2xkZ(A0@9M@UqsoDJAo&KS2h1F~Qfildu**NFQ>bsK be*lD}%QG-YGw8Z~jzfe8x*4ePfi4RGbnI~U literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/ice_forest.mw3 b/worlds/smw/data/palettes/map/forest/ice_forest.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..32d62829bd5c4e0a1a2f6e8c2b46efc9275d8f9a GIT binary patch literal 514 zcmZQzKn0VF85sUE7spL5e(#%Y`&W3-_OWUx1ad|^PhT%ytd=|(BPy}sh#z~)gaaWk`;D#;RK=ggTb2hW z1y@VR#h7z~?K2GemNuulRm@xeqdQ1_f?06Vd!;Bpt6*gP|C#Sq%h!P9f#xR7H4Lr> z*#qK3%&*sl=m)u>khh-szkhtPYy=mByyX!W4!?r*Z3g#E|1$$a11SH1acyk_^L7S% zCXjwq0FoDDkZ0h?y{j9aj4B7>f#e&Q9x!v{N~smcVV8eUr%>Ne{{RR{muFy*X3%x} N9ES)GbTjbB2LNimaKZop literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/lost_woods.mw3 b/worlds/smw/data/palettes/map/forest/lost_woods.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ba9486b627ba4eeeb266c7448b718355e9a0e136 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3p>D%45QYyH3>X%a6ciW)7z7xE7#J8B94Igt2{14SIB2jJ2n@_C1okxo149b~ z3pbq%CYMY*$+CWZ|DArH0qW8bU?v@PBrnX;))aur_ZQ!M(7kikr|YPMneg1R^S$z* z?9zsWZqJ2}>gdR$)6q$Gr)I*CR;}z7ScgR3No92uhPAS<&zV*|A*TRXT4l*U?sfm3 z_gwNn?&O#D=r|So%ly-|^mB$$>A2@Sq^AE6=6PB5xlaxGnE;r}+tCT*Bzf+yi}~{+ pZ%(4iL(N!~`RJIJytRk9vgjtl57*L$T>sYvI9T99gg$qGBWaD}=Dbq~P$Ez934 zCDcmD#h7z~?K2GemNuulRm@xeqdQ1_f|-K%d!;Bpt6*gP|C#Sq%h!P9tBu|(Nh*G0 ze5Sj?48(_+U#|<%4{}2xZ$0yW|M+Ct2rdSB%Ofrveg)~<4DOr$X9k7_Q2qhq+S&%@ z?F{xzApNKSBrnDw&%lv;S2sQxRSv`h$u}@PVCKk`QnL=nF8`oTp}wL10T7Zd&%hwf QpzHQI4iO&cX5fzx0My@Y{r~^~ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/original_special.mw3 b/worlds/smw/data/palettes/map/forest/original_special.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..57fcf7dea59b1949ea5d66e58b10ea4d7744f92a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3OD(0>K(H*2d!A!yXy;4<>RWP#t|IGKQ>+4b>ov!Kp1G03qq}3=Gl? Qx^AE25aEGt2LAW}057X;AOHXW literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/sepia.mw3 b/worlds/smw/data/palettes/map/forest/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b265f978060dd60e3aaea39f9a833111d1e074a7 GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX+yDt^%|w+pwkjt-CnsVn^eur%I$W#q3XY1e!qMZ*!?@*%I!9*el?r~bQMTF z#C$$ch<=b8CMnv)-!HGtE|kq+5jA=%a9LA2yim5>Zf-UM14v$9^oy>jS%74P6G%TQ z0Li}*(3cF547aNVsz(P9^TUBY2)Bc{7gYsF-pnlAZid@UKZt)xlV@Py6BRYO>4yjp JRQ-hF0{}_CVnYA` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/snow_day.mw3 b/worlds/smw/data/palettes/map/forest/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c76540325baea28db53e1f9d91a6201488a4219d GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3%#7e1I1Ko=bKwOrBfJ#bm_%g&EAZ`WaM{jjsPc^ZsJ_8j$?GYU^;2{v#Hj!0I99 z*Xw%ge{^SH*bnqcA#XkNfB*Po*$6HMdCMa%9DW7q+YIiT{s*cD$-iS-TlkK7{ouACg6_`0H{H5f&c&j literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/brawler.mw3 b/worlds/smw/data/palettes/map/main/brawler.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..731696fcfc8204cffabd03f308427fc880a45460 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3o!k`;D#;RK=ggn-_dg%K9GvBM0uK~&5tM<(U=|5uOAq?U} z%&*sl=m)u>khh-szkhtPYy=mByyX!W4!?r*Z3g#E|1$$a11SH1acyk_^L7S%CXjwq z0FoDDkZ0h?y{j9aj4B7>f#e&Q9x!v{N~u|gW0!wWr%>Ne{{RR{muFy*X3%x}9ES)G JbTjbB2LL%pZ?pga literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/cake_frosting.mw3 b/worlds/smw/data/palettes/map/main/cake_frosting.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..aec0fd7e40c9db6eeb5d2ebf27bebc231a6535a5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3Vfj+|CvFi83uhzn^WB?=B@wH9mJoK6jgi=B#)y1Kl8n6`5KTsO#cxJ4`C1=Vt&0Y zL_f$4g}n95|NY~WWh1y4Xn(W@*gSv93Ol=Sg}MiI55W5GRa=K^ z)B)wq|1*P3GYtBcHmAB(%v=AXJBY6#v_i;37+wE==6luhH6VGI{v#G1!XQ4x{CZu8 zevlgqdFz?~`^P8CMsP96TOM)Y@GD5)W^msWWFJWW0pr@*2IlPy_DmrCr~o7{#vsqY zk$YD+J{eUG!~@AUFg;-A$dyvF4#zJ4piZH_q5c67k}l7{AkCob_Bjp_9_VJ^j}HK& Crfz}& literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/mono.mw3 b/worlds/smw/data/palettes/map/main/mono.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d4d0f72d29550b6f60468a93058340f9db106760 GIT binary patch literal 514 zcmb7>ACtr|7{-GO3&BEVVWCh66ap)OLZI-%LSUs32owSbf&BuD#eRk2;^|?r7%eUy z?#(lSn^bP@oylaL`MrOhykRU4X&IZmw9@*HtGUT3V<7K4ZXD8k4t+R11Xm*!Ju>$Z z2GP!qain8Nd~RMB($DMX2Uo~;^+?7oQnagqj)62+&+FtT4=vj7+vFEH5?nCGmG7k8 z*T3&EB>yo;i~6r3`Az+|GS$PwLr5D#7+l>Sx!TG_7QL%|hk4-D`E}t+SH8o%JixcI pI_rvb7;*-c`jZAve)Ak~(UKl%HR-+otNGnWUjNsB`~ZFMY90Up literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/morning.mw3 b/worlds/smw/data/palettes/map/main/morning.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b2fe88e2cf89d9ad5e4dc2e3e714a09ce015444e GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K=gg$Jg(z z3@8oP+;9G$86D|DTroZU{TGfLzf5fpX+4XzV|JT z`=0l|9#j5f{eDJ_wGSIbk;rOV_AOccz;K8K z#D|z)uj{S<(Vc<8GM#~;khh-szkhtPYy=mByyX!W4!?r*Z3g#ELH2>J;i5>K_0h>GBK=(hRz8 QpW|A^ApXHL0e^e|0GO{{UjP6A literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/night_time.mw3 b/worlds/smw/data/palettes/map/main/night_time.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..13a5af30c2e5e2144df1c975df6449f31d7bbdc7 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3FZHL`v$J=4gEkLUMB`fUg!WHTs)I9*}H_zve zdv3AWaliRSA&|Ub(6_WX)vaRQ`XAjvd~L%`uH`}K`u{WEtCp_;$;0#?)zCBp@ge5d z>q7K{+)&6{&-~v%K3O(`i$UJ`=SKPmvp zi!sPEaOB?AjZa3E1Mxue4NMQ1IdY}cti!R(Kd4ivZ>WC&grv(eFi11#x_ypAga^7A H_~Qcra8Pkk literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/original.mw3 b/worlds/smw/data/palettes/map/main/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cb655d882e715528ba6092ec5ea9ca66a0d39290 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3ZBBKon795%cMxC5sMO9n99{o^=6luhH6ZzW)z;x4{YNZ3gh70W z`SrRG{UA3K^42r|_m5AOjo@OCw>;v);a8Bp&EUQ%$Ucz#1ID$r4b0ma?3qCNQ2|I^ zj6t4(BloUud@`yWhzF8yV0ysJkt?NU9gbc8L7hTwk0y@gXd9{r{QoRm<0aq7K{+)&6{&-~v%K3O(`i$UJ`=%iKK=Kb5*VZ;LZ)dP)0_jHuAbBwc zc?OQ$ySnkosB$14NWOvT0W(Lgl$v!ocKHW&3iS>34}g$#c?Je)23@z$aft9hHv@ls F007$Aa)1B; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/sepia.mw3 b/worlds/smw/data/palettes/map/main/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3a6ece7743e941b78e5aa7daba2310af6678b62a GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX+?tA5c9x7+EquUOOwq`FXchFeVXWlb})n|>hv{qk@-QKOx1 zbF=4WgZO4vzk=rFU)G%A1abufgQ!sjkcF=Qe);|KO4ugxx$&0rBVdMj{QQ#!m*w%iV6A4pzb^oy>jS%74P6UaVP0Fr+rpf4F9 y8E#h#b|0qvB*kz$kUYe_nDS<3;dV3JZu&v|OPV|b1D~j<(M>-@c%bSh6dwSAuw_>O literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/snow_day.mw3 b/worlds/smw/data/palettes/map/main/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..1ad307f078dc13fd67a195e16bc3b035c4f607d4 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3D^c_q_l0nDQUtVt?vE^2c>P zWP((gqycRP%lS)I*x7|E)IF$s09OlAQP2F}9Yh-jeM_5D-74m-{}IU7ar}_EDeY>y zJW%&>9gsXk|9|Ft)$%nUK15!}@v7CFXoP&dE<``b&O%#}bH$E9v4#We=H!wY5=E#*&!<2=Z iU#C#tQ2zi3F;#%=$0N_cAkCob_Bjp~9+)QJj}HK$18D^S literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/special/blood_star.mw3 b/worlds/smw/data/palettes/map/special/blood_star.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bc778b202b99d347451c6490fd02d5bd54e698ff GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K*{)U3mBz zBpKp0?Um}8|GR@sGYtBcHmAB(%v=8>kgwzTA#+pO)pU7=|MkapK=Kg%|C#Sq%hy26 z2g&O=UbUJNjgYU`h3E&_S;z}C+dn>8HiC;m-tveGhhIVZHiP@7|CxcI0hE8hxVE-| zc{_tW6G%TQ0LhCn$TM){-qnpyMwJ8cK=KVt512V}rPQp$vCBWGQ>bsKe*lD}%QG-Y RGw8Z~j)R2f#e&Q9x!v{N~u|gW0!wWr%>Ne{{RR{muFy*X3%x} N90v;zOcU_O2LP5hbAD^c_q_l0nDQU%)s!~}EAggC z{iz4ZAJ_Si2~uT})^86{?=M+lXBVze_n__pSiiE^6TKGiQ^xho|J^|<4THX=&8cn` z^Va_e+JfBkVCkUT{Hf98AD@--knL|(`7s@0rmgnYd&L_f&RLSCTR z{_)AO5nK%NmPcGT{0h>y8QeDog#<|c0pr@*2IlPy_DmrCr~o7{#vsqYk$YD+J{eUG x!~@AUFg;-A$dyvVl!cmKr%>Ne{{RRvRe*bm2ng6?kR2l|-OPf>OD(0>K5y;nZ{E)dR?P|I_!~go@Iv{z7{{PJPs^x1S=7Z#Q z9Islq7K{+)&61G}}KuSvG=;LEiF+3x{7p`Zk06rvI6Np#hYCz__-yfq6TF zJrhVjDgeogF~~D;eRLT9;MN{NEj9o?+0pv^mwSV&3{6fqWgu51E_NuBOW~{I5T*1Cod6|Id7{TD}IP zA0n^gc-3l7G(x^!7os0zXCW`pZ2$OV*$6HMdCMa%9DW7q+YIiT{$~b;22lP1d#! z-;kEz%D_<1{NEj9nqknlv^mwSV&3{6fqWgu51E_NuBOW~{I5T*1Cod6|Id7{TD}Hi zK1g22@v7CFXoP&dE<``b&O%x4WRr3#{R1E*U7mqK RnnBm?a~v!@FipT89{?(kc+UU; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/blood_moon.mw3 b/worlds/smw/data/palettes/map/star/blood_moon.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..42f155ef33e2d6114d8d27e32bbf88c02410d803 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3-GjOZV0m4Z zU=Ax5dlo4Md9Y!ILEqBmRJV$G>wk1-VDRIB=$B`Zbkk%(=>N}ruUftaVg6~It5$QO z5%TrA5d9!G6!O+H|M!nimW|+IkheVI!r@ntzRlpi>3?QmXaMCOFs`j_VBXGP&jfY= z3IUQA1G{R1E*U7mqKnnBm?a~v!@ JFipT89{_h-X&?Xq literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/mono.mw3 b/worlds/smw/data/palettes/map/star/mono.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..15d33bdf3a2378819ebd2e249a570bab1abede53 GIT binary patch literal 514 zcmaixudc%&7{!qjS0E>9CMpmJ!~|ld0)ePNT!EOWKp-j*6^Ok+Wo2Js^2)wKW#w$; zAkPbK9HVoN_wLXOS`B93ATn2M zg}NTCH~JRA*Fvbj-`IwF>QcPtgRy>=LM;}2cetOX?s1lG>X1#S$9W9191ObfJo#Dq zysQOy@=JG%=>MJHb5!|op^_gpdx7ZKdfxodYAt5DZ#)n67zhDp<^4n}%|Z?TR%r%S nYG!oG|DX55XmIqK3x1)D9?W7fZ?10~`=-@m-}}h@-~QtTOEYd; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/mountain_top.mw3 b/worlds/smw/data/palettes/map/star/mountain_top.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d2b96b0e3dd2731c9f1deaf3a467747982cc250a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3yYJbTJJG*d&x(9U+!1C&@ zetCh_GwO@-XHLek|K7^E3=-9E>` L!UNL;{P6(*Ay{!c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/original.mw3 b/worlds/smw/data/palettes/map/star/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2107a5555ebabc965facd1a3db5227009de6fa6d GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3$8|vR5dHs|?^Vm!Ak5csylOQk z8X;e=3(*gf#e&Q9x!v{N~u|gW0!wWr%>Ne{{RR{muFy*X3%x}90v;z JOcU_O2LQx1c!>Z2 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/original_special.mw3 b/worlds/smw/data/palettes/map/star/original_special.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d2bd439c97b52518cc44a75d80215d2e25a6b840 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3O!=v&&H>Q*st{f|ICOuszC|N7@LAbE)X|IGKQLek|K7^E3=-9E>`!UNL; H{P6(*hNO1x literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/pink_star.mw3 b/worlds/smw/data/palettes/map/star/pink_star.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..55f68ecf6ce2615a7bcd845c124aba707d75d654 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmxJWr3YeMgbgNA-x7$|?QC}!K!!0KHvZk5YO+S!4pXe9eefht` zY~uHogG@8C`V}-U|FY%`Cy*-&Wg+_YCI8pI6#&UY^jk;YFW&@oKg|3>*_~ds*$8bx5gV6t<`Cher4MP67&Q+^9(Fpl^U5I{=8wz>r zng9F8C(A}~G00mUapCYQNZ)2~-}FB-Ff@Sj4;a_hHZX5zuxA44M+G2xF$Q@Cj@-Mt z@yV!iARb7*f$0G=N3N8bbvSnU2XzYd4fPLzkaT$l25AOex6g5~@W3-GjOZVEy~U?n}rp zvDvtMoBju?2gyHRTwB|~yq&?G z38WttfaJv(XT7St3JG*d&x(9U+!20i1+qy+@ z0Oif=bwMf&gTAHBscseX*8k`Z;=4++D?L{N>5t+7@ge&EGvBM0uL1GzRa=K2Gm2%a zXRc>P$k*#a^n=_`$Xn0+-#S7c6Q+kbr0$ufc4+2{;z(Y z2`Fz~uM1KEQQs=&t^d&-#D}N{>A%l};Qv>@S1n%y;@_*b4nJlj!BEd!&y0|lV1Vcc zxuKA^p83Cje6nl=7lXXz5f=`>g7j?$_f7u;)q~_8Fs`j_VBXGP4>liU1_FTO#Teun zICAgm#wR1hVJwh*1JeU$j$A1<>u{JTf(Dykr%>Ne{{RRP;xHB=c?Je)23@z$aftAM K>4eev;{yQC=xv+; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/dark cave.mw3 b/worlds/smw/data/palettes/map/valley/dark cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b6c0f57cc614371d8f7671f472885839564eab9 GIT binary patch literal 514 zcmZQzKn0VF85sUEU$UHBtfR`Mcw7QimVse%@g>Xu^&pyo;gV%e9lAV-|6C{A_PuX$ z-1j_?N_6nCem^5a-G1f=%zx@3^2;v);a8Bp&EUT2f1rAh`~$|dwGGVM8SI%r`cVN$ zUW`GWfg|^>ZhSJT9Eb;!Z(w@B%#kal2J<1ZDzN!=3iS>34}cI^2EryJ&%hwfpzHQI M4iO&cX5fzx0DOOM5dZ)H literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/dream_world.mw3 b/worlds/smw/data/palettes/map/valley/dream_world.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bcf8d9514213ab9280377dbbff6b92a1e9de38d9 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3S7c6Q+kbr0$ufc4+2Hc8U; z1j?J&>w;7m27ODLQ{5`&t^d&-#D8NTr(y!|Y;zRWRXTDc0UjyRbtF{h5W^}@! zp1GbGAz!Zx(GPM%A#XkNfB*Po*$6HMdCMa%9DW7q+YIiT{s*cD$vc%Id%7J(w`39y3%pAE=YS!V{W07BB`85pD)blpD3 MA;JUQ4E*r{0L7wk*Z=?k literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/fire cave.mw3 b/worlds/smw/data/palettes/map/valley/fire cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2980210dd233eaa0bfa804389754055f6a03dcb9 GIT binary patch literal 514 zcmZQzKn0VF85sUEODRn*mKD9lvxNgymVse%v6RyPdJxUPAf=R3hb|A|KiA2&eeYWw z_dO4!5*>W3-_OWUx1ad|^PhT%JeOpy;zdn87VB_`T7St3JG*d&x(9U+!218!2MbEF z=&|fKuh#{sFbw*ZHmAB(%v=AXJBYu5;TwY$BS^m_3y2TV|G)TNwR{bT&&?vp0@7d4 zT+fV<-_Hoq4{}2xZ$0yW|M+Ct2rdSB%Ofrveg)~<4DOr$2dW3jKVV#2+rYe?!JY}E z9~FS)#TeunICAgm#wVl7fp{SK2Brth9Jx|zQcBq6AJi$-H`G4>Lek|K7^E3=-9E=5 L!UNq5{P6(*j&WwS literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/invertido.mw3 b/worlds/smw/data/palettes/map/valley/invertido.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37cf2e9f50d24ce7db77c7a9d4869aaad1eecbc5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3w;7m27ODLQ{5`&t^d&-#DC+d7xpm;q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlmp>o6^6h)t`s6eh*Q-MH4AXXq^5C}vBq5@G9fj~qcRv`KVk&*d@l`HobA|qp0 zMpm|Shw1FFrqegWFqiW#T!5%96^Q1ff(of)`f{o%5y5b}qfKnVgS{l4kW-*|G{u3qB`lpr!fxnA4&rhZIDDk8*F} zXBxWrZ|5zS{J(x=e{jKz9~nle!j|(8oBoyQ`*Bf+J~pYF6A`%Jd&{&Hse`{R@cU74 oE1bmG1voSoe2%YFaH@!m|Y{_!5Q|F8e}0n&(NlK=n! literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/orange.mw3 b/worlds/smw/data/palettes/map/valley/orange.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c9f6ac2ff2e90232ae61bc93765512936b7f356a GIT binary patch literal 514 zcmZQzKn0VF85sUE2gpt?)|2EGJuZMM%fK+XI6!u%9f)RN2$0RGLzf5fpX+4XzV|JT z`=0l|o`K;m5N}hwW7W-ZiQ_v9&~zYQfmw_}o}r#`J`+g&WWn!(d*s#%)H#EA3=9yJ z3Uv?a9st!cFx;!Q=M-RAE3n_ZUKhlFD-s}jSrnoR#D}O~D{$8g;#>&-Kl8n6`5KTs zOn-{3hcH5Yy{@S7c6Q+kbr0$ufc4+2o*J%E z2b4Fj*9ECC4EmNfr@B?lTmPdwh_7bYY5T?#q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlm5v0G#0K|vr|Id7{TD}IvzgG>C$<}Q$ z@DN7G*Xu&`gWOQaThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcnSr4Jlz+guwzh$JJA*wF zNIxn7$%`?_GjQbI)s0U^l>_lW@(oN6m^pH#)U3m?%Ri`7sBfr$0EDE=GcZUq=(>H5 MLxcyq8TjJ^0H*kEoB#j- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/purple_blue.mw3 b/worlds/smw/data/palettes/map/valley/purple_blue.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2342f0acd205d0d9fa52a0f1a66488a4d74c4eab GIT binary patch literal 514 zcmZQzKn0VF85sUE_XSKYo@mjl(J6r{%fK+XxG!K!6^LeF=nKfHLzf5fpX+4XzV|JT z`=0l|o`K;m5VLZw;7vAjaqkQVnhxYEFxQ&Mh5k>KuLh}aiD577EN=Mj$m=JmQDzPD|@@7F{xo4$Za5ggi7=t_mNA6wS_+(T$5Dz5Z!1REbBUehTF95szgF1!! ehWZCUNV+@&gEWJ#+vm7eF^GRKO+bwgba?>wk8^(j literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/sepia.mw3 b/worlds/smw/data/palettes/map/valley/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..aa5aeb51d38b312a1873ec1334fd247f52534e2c GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX;g=$e`B^wJaER}4{GC_BR~Ci$|anb}Q0kjVXVn|M(pJ<++@ zW@aG1nbohLdHI($XE=de!N8y=I#Kb9E=a$q5r_}bzc2rO`6jUYVfsOCN670-LiB^& zFiFuS{(gCFcA;zri>T3CfypDVoIv_f0Z9IhfWBma zWVl@|P(3<;m>+Hjl84xjssbc$W)^NY!|kRY#J{A;GcfRpiW=SYLxcyaenRm90OQJG A%K!iX literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/snow.mw3 b/worlds/smw/data/palettes/map/valley/snow.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..185d0d42c77a14450bf662802f4189397e97f64a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@32sC!WN04)E% z{&u}VeYD$)MDHq)d|;S(_TFk9`+V<9;UNC{ywdtRK>7X224M9N{r}_d)yvm_zV)i$0y51a52bR9&zFDD@fmF zaNqPlGeo{cYi(_+v8muZC6Inp0MZ|&Q){^*_O5O`*aw*MEn2C@D`KV8tby)D2cR%` kP^VDeQ2zi3(Pe?LK|r2?L7G9=?Q>kK7{ouACg6_`0M-+IhyVZp literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/DOMO.mw3 b/worlds/smw/data/palettes/map/vanilla/DOMO.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..595638077050ab67064d7ce25977b4cd92cd9e71 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3-GjOZVEy%e|7Bzt z+64BSFO3DMFbw*ZHmAB(%v=AXJBZ)Pb3%k$7NlPW#ASf!|Id7{TD}IvpU+g!D8s-l zThCn2jF7L_h3E&lp^&$p`M-aBvTOtwgS_Pt7Y@II^lb+BP5%ScgXAADuB~lg-p*jp z1k#TRK=NV?@(digcXi{FQRP59kbDEv17?m~DK+bG?D7xl6zUu59{?fg@(c{p47zTg N;}GG2ZU+AN005Z&YH0uf literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/aqua_marine.mw3 b/worlds/smw/data/palettes/map/vanilla/aqua_marine.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b382964de02c670e2c72b816639e5ed58d968d19 GIT binary patch literal 514 zcmZQzKn0VF85sUEzi^#gyxh=L^@$9sECa*j;uo&}>p?UF!wc7(I&^su|G7@K?R($i zxbJx&mFVDO{eDJOD(0>K(H+EhWNGGkA_3CxZVKW<^#5nRS1n%y;=}aUGuJaC z=HHo56k4|3LL1`3H<^Ya5uiGuShM^rHfh zycmN#14r&%-S}iwIS>yd-@x>MnIl(9?S(6L`3H3h^$qn8fRJ=~1_o&cUAND1i10u+ I1Alw~05OSj4gdfE literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/dark cave.mw3 b/worlds/smw/data/palettes/map/vanilla/dark cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b6c0f57cc614371d8f7671f472885839564eab9 GIT binary patch literal 514 zcmZQzKn0VF85sUEU$UHBtfR`Mcw7QimVse%@g>Xu^&pyo;gV%e9lAV-|6C{A_PuX$ z-1j_?N_6nCem^5a-G1f=%zx@3^2;v);a8Bp&EUT2f1rAh`~$|dwGGVM8SI%r`cVN$ zUW`GWfg|^>ZhSJT9Eb;!Z(w@B%#kal2J<1ZDzN!=3iS>34}cI^2EryJ&%hwfpzHQI M4iO&cX5fzx0DOOM5dZ)H literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/fire cave.mw3 b/worlds/smw/data/palettes/map/vanilla/fire cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2980210dd233eaa0bfa804389754055f6a03dcb9 GIT binary patch literal 514 zcmZQzKn0VF85sUEODRn*mKD9lvxNgymVse%v6RyPdJxUPAf=R3hb|A|KiA2&eeYWw z_dO4!5*>W3-_OWUx1ad|^PhT%JeOpy;zdn87VB_`T7St3JG*d&x(9U+!218!2MbEF z=&|fKuh#{sFbw*ZHmAB(%v=AXJBYu5;TwY$BS^m_3y2TV|G)TNwR{bT&&?vp0@7d4 zT+fV<-_Hoq4{}2xZ$0yW|M+Ct2rdSB%Ofrveg)~<4DOr$2dW3jKVV#2+rYe?!JY}E z9~FS)#TeunICAgm#wVl7fp{SK2Brth9Jx|zQcBq6AJi$-H`G4>Lek|K7^E3=-9E=5 L!UNq5{P6(*j&WwS literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/gold_mine.mw3 b/worlds/smw/data/palettes/map/vanilla/gold_mine.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ad5460a75e503f52ea46f721ad81310114e94831 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3BMkT7St3JG*d&x(9U+!20i1PYqx0 z29!6i*9ECC4EmNfr@B?lTmPdwh|eQAQTCf6NdIy-5FeudKl8n6`5F-aUbS`jF(XdV zdggj&gnYd&L_f$4g}n95|NY~WWh1y4#YMZEXYdb_RPU zkbYDEk{4r;XW+=as~ew;DhJ|$E!XJC+K&~^J9 MhX@aJGw{a;0KFq^6aWAK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/invertido.mw3 b/worlds/smw/data/palettes/map/vanilla/invertido.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37cf2e9f50d24ce7db77c7a9d4869aaad1eecbc5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3w;7m27ODLQ{5`&t^d&-#DC+d7xpm;q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlmp>o6^6h)t`s6eh*Q-MH4AXXq^5C}vBq5@G9fj~qcRv`KVk&*d@l`HobA|qp0 zMpm|Shw1FFrqegWFqiW#T!5%96^Q1ff(of)`f{o%5y5b}qfKnVgS{l4kW-*|G{u3qB`lpr!fxnA4&rhZIDDk8*F} zXBxWrZ|5zS{J(x=e{jKz9~nle!j|(8oBoyQ`*Bf+J~pYF6A`%Jd&{&Hse`{R@cU74 oE1bmG1voSoe2%YFaH@!m|Y{_!5Q|F8e}0n&(NlK=n! literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/original.mw3 b/worlds/smw/data/palettes/map/vanilla/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c165e81b818b54b39163774ae200a6a48ba7ac84 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3S7c6Q+kbr0$ufc4+2o*J%E z2b4Fj*9ECC4EmNfr@B?lTmPdwh_7bYY5T?#q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlm5v0G#0K|vr|Id7{TD}IvzgG>C$<}Q$ z@DN7G*Xu&`gWOQaThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcnSr4Jlz+guwzh$JJA*wF zNIxn7$%`?_GjQbI)s0U^l>_lW@(oN6m^pH#)U3m?%Ri`7sBfr$0EDE=GcZUq=(>H5 MLxcyq8TjJ^0H*kEoB#j- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/purple.mw3 b/worlds/smw/data/palettes/map/vanilla/purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..db0008bca7cd3f70e23859b48b89c3b4b4578b05 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3@0M8eH0?l}YKU5Y$qGBWaD}=Dbq~Pu_o{E# z8`NtD?l+e)1*tF$`j$4Qx>d|u|D!udzjvfS+LSEqz{+X^uze8y|C#Sq%h!P9?^Roe zA2ZSptY@xg2I+^$*Xu&`gWOQaThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcf$Bl>4;a_h zHZX5zuxA44M+G2xF$Q@Cj@-Mt@yV!iARb7*f$0G=N3N8bbvSnU2XzYd4fPLzkaT$l T25AOex6g5~@W3E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX;g=$e`B^wJaER}4{GC_BR~Ci$|anb}Q0kjVXVn|M(pJ<++@ zW@aG1nbohLdHI($XE=de!N8y=I#Kb9E=a$q5r_}bzc2rO`6jUYVfsOCN670-LiB^& zFiFuS{(gCFcA;zri>T3CfypDVoIv_f0Z9IhfWBma zWVl@|P(3<;m>+Hjl84xjssbc$W)^NY!|kRY#J{A;GcfRpiW=SYLxcyaenRm90OQJG A%K!iX literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/witches_cauldron.mw3 b/worlds/smw/data/palettes/map/vanilla/witches_cauldron.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ef6a81e5d49de8b637df7da1cb7c326ffc509a0a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3N&?W&-qGP}GXYW*cE?Cio7>K@cR0PDY39pJo5 zYpKkB^D0M>3d5jpX>+Pu#k}=Dx`X(~R6I;i+Jf}2(gN`z`u{WEtCp_;@$XezhaWRq zDpSu~&y0|-*M;Z@xuKA^p83Cje6nl=7lXXz5f=`>g7j?$_f7u;)q~_8Fs`j_VBXGP z&jiws3PAE=4Dt*dxp#HrlTqbBJdk_?(*tIXTq!l{aP0CA>J;i5>K_0h>GBK=(hRz8 OpW_hWfo=x=_y7Qrxo`#m literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/atardecer.mw3 b/worlds/smw/data/palettes/map/yoshi/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a75c898cee98948eef7a1a386a507995b6a208b5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3OrBfJ#bm_%g&D->QtH*i)c>D(f3bWGNd8{6bvQ`>5sOb?^$_#x zb-ndJx-&5B2l}Ltx1RaGe|)lR1Q&z68_2KP<>1J#4%-!ZMN{lt8Q!%hlh z9x4FIi*eSma^&9CjZa3E1Mxue4NMQ1IdY}ctiv(oL1FNqPNBY`{s9n@F3-Rq&7kY{ PIj&U<;vY;C@W%%L&X;cj literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/gum.mw3 b/worlds/smw/data/palettes/map/yoshi/gum.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cfde2f53bba4254934a8accd29a5a692f093e3bb GIT binary patch literal 514 zcmZQzKn0VF85sUE>jg|M*0#|!P?bTIWnh?GtQWAR4n#9B=mq2eS(xCtPPXlP-{QFM zdH?G%Wk1&MXJn|`&-{S-Pd!9l!LuP%rS5%5bnzS;ka~Z~3Ol=Sg}MiI55W4Pi!JKr z*t`$fZ(gqplArEj>p7`=e|!J{C?0N@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/lava_island.mw3 b/worlds/smw/data/palettes/map/yoshi/lava_island.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..570bdee3aa9cab8df90100b5c9a771a876d82314 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K=gg-w)m= zHdo+`#D4R7U68zC(6_WX)vaRQ`XAjv{G}q#WwLeu$JOT}>;KPuuUftaB!55HI($Lh zH^vo09>O3##Qb_)h<=b83VG|9|NF-$%SLc9$Xgz9;qWU+-)3;%^glB&G=TCC7}wS| zFmGqDX9DR*1t57b26+aK+`GE*$*6K59!S1{=>aoGu9TW}ICl95bqe(j^$&oMba@5_ SX$D=l&vA(GKsN(_d;kE?AaG6q literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/mono.mw3 b/worlds/smw/data/palettes/map/yoshi/mono.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..62c9761b4673734aa0e4edc80f8ccd0e93d2af97 GIT binary patch literal 514 zcmaixp>o6^6h)C0704B9DpnvO5D|zN1OgH9#0o@B1OgF(s6g}wRz~s*kt_EXA|qp0 zMpm|S2hp$$BdV<4aJ*s;hya`aCx7s>TRhXKmR zN~1J$>jK#=6+X9nNA`NYy;%WuXeM%DQPQjq{g%jDaXsfK4SH!lUUOX3L`cEd-bq7N z`5b@xmETgyza{0l-aDKEeVH#!@gu`ULY>CY49{HOkBd4Cp^1HtjDd^O-f?Y3?BTBq s{C*TCE6Ap?X6VX%+yEJW=g2?N>dJj<4g7y`yf@dY`+kqw|KERn0Uyk0>Hq)$ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/original.mw3 b/worlds/smw/data/palettes/map/yoshi/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..eb9451b1fe5c3a345c8fe04cefd208358367b772 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3j9wUd2>*|(&qvn(pZQ+3d<{tcUbS`jg1T>v zAX`9ui23!p5d9!G6!O+H|M!nimW|+IkheVI!r@ntzRlpi>3?QmXaMCOFs`j_VBXGP z&jiws3PAE=4Dt*dxp#HrlTqbBJdk_?(*tIXTq!l{aP0CA>J;i5>K_0h>GBK=(hRz8 OpW_hWfo=x=_y7QkXmBI| literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/original_special.mw3 b/worlds/smw/data/palettes/map/yoshi/original_special.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..269b45db617113eea999a51d7ae3edc8b8b95543 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3cLJ|9{Cf98AD@--lNp#E@>{uM$V!XQ4x z{CZu8evlgqdFz?~`^P8CMsP96TOM)Y@GD5)W^mv1KQk~ifbtI**VZ;LZ)dP)0_jHu zAbBwcc?OQ$ySnkosB$14NWOvT0W(Lgl$v!ocKHW&3iS>34}g$#c?Je)23@z$aft9h JHv@ls007S)aQpxO literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/sepia.mw3 b/worlds/smw/data/palettes/map/yoshi/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3cbf6b0390bf10ab608d5765f20df390d3d9d083 GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX+?tA5c9x7+EquQ)&wq`FXchFeVXWlb})n|>hv{qk_T0Lh(h zbFvv9i^Za4iP{zX*-(vMG`fq_p{)aa%k LB0Ny_6N(Q2DI#SY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/snow_day.mw3 b/worlds/smw/data/palettes/map/yoshi/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..464b32bad17cfa0135d89d3cfaedc70423a89f08 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3;v);a8Bp&EUT2e`bh$i`LrORAW=Yc}nQM zfyhVc)LO2Hy{j9aj9tD(E7f>KtdyEH(7osY6b29K6zUu59{?e`EHE|*$TKiVGw8Z~ Pj%yWz_y^Mj{P6(*tigW< literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/sunset.mw3 b/worlds/smw/data/palettes/map/yoshi/sunset.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9477a08cb8e69974c2be867e9e0d61af7474a796 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3v zD}+3RL41h$^}62rAKihz1=>-_ThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcnIZBI7}wS| zFmGqDX9DR*1t9%m4Dt*dxp#HrlTqbBJdk_?(*tIXTq!l{aP0CA>J;i5>K_0h>GBK= U(hRz8pW|A^ApXHL0e^e|0OlcVq5uE@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/tritanopia.mw3 b/worlds/smw/data/palettes/map/yoshi/tritanopia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c90b7f9af0d2ad8b9261edccc53041384e0ac809 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3=HHo56k4|IEP90LnjLTwB|~yq&?G z38WttfaJv(o!k`;D#;RK=gg->Y7y zrx8J;87bI^O^et^pb*q@S{zrEZAEN(%9IF2R%=fD0Ye4e%s;$Ep)O}-IA+(

    gxP-|z;$w&od33yyym=P27S&6i!*`MW<-ue* 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%0A`sd6u(QWc7HH zVIqENpIn8s<>XOJ@olRqI5Q74llgaZA^GbO?i3v-S%)OEpc16Y-Hv()&HL-94 zzvRwVzR89QO;!UbO!36qtTI+I^$K%_DLTME4l{dOuoiM7pVO6YtQMpsTomg_WBZTD zPz5gl;C{ax1jqx*tlr|j6`VudyZB}v=)f@Bcj3KoM9M}&4bdq+{D$gi^zQ=yH{Qiy z<4-SLRHY6KyFl@P#wwoW7B?6riS7A~g}`c48BI;^l0%N`cb<&r0s9#M8*$(>5%Mse z3QOaErH~-HRT-jo?_-fZp(rhkh+1Y5WCvrU-;U^y_()5o0vD;rpX=B7JGHEFd_7}i zo5o>Ne2XVV%DG@mt1IF?pi>;<uY@8)8 zVDkx4layLs@259^7NZ3@WwBdLMcM6D>8Wfdh|eC+lUG(I(QU8=&fi?8zw6+c`De0G?y@00EMkK07JyHAT7SY}VF_ z#)C-Kxd*O~{(7ozGGeO$0q3u34UzDpYker@F*5#GjhB%xxYlWC20IgXlSkz{6t)s^FwQ@A1>2#Wv}qSPb^j@0W< z3@4Q>U|<=_6r1ahwID%(uv1)Rjlv%Eu_UV{Z~k3q>zbCgW-mzkf_(sMAqx2Xw1@~S z|18JDIb;u1UHDbEWx$+IuIw8Wm?%Yn-;M9tBV{?_CQ7x+>fZNixyrC-G!la;Y1IUBDl)FK&kow-M0My0E2j-ekMp_c_ zp44c&{s^jsm|i~EK?O%SSkm6%z-y>0+3}sNwPYAxoR7tj?HLGyFzj=d=6_bjBDYo; zJR*0Yz^otx4Q+-fF*oUOu5hM)N)%5amHs!LVA?wX`KS-1k|?q0gon_k;h3rs+VGMO zqzbS|mqQQ`WXJA)RVA1YY>{gOAl~L+B8Gs?PcN!!0WTGoJjz=#kD*VVy(L*bKoV>g z0~hIU+B1V6TAcpg>1sgcL)6sJ{F~uv}6Nmhoep zyD95z)ob7>@#pKy7x-)&FXN4ULc#XWh{vcB#jHpNBu;MS{D&)wEv`!V38tZEK`MY1 zFRCt#0;ByBCn59C0PRMt0N z6)3Jj(18uyw9Y*Pf&g!1k~LNl;HY96?P;Y|*a%(ilvsRHbn`Cba=Snt0msyViTORm zRW|0kCEj_4Oz3u=uAg#d+uEQrWu(?wTH;xMDkJp65+P_{j)G0tA)4N1OWoW$wvit6 zpv(oWauo%92xeBN084D$)`D_{{>}VPqB}(_u+Q4YWbd8i#il}mRR`p<2?;csnsLR{ z`Uv)em`zWrP&#Kq_Ksf=^~* z+1NS$1aLp17!{%uZMs)LqfbRyHUMe@KMp4k&OcfLUL}kIF1#hQi>egNI5*c?F5o>X z_V|z?a#F^os!;sQGJEIZEv2}TJL?*7Da~Q&fliH_Qme~t-?)XpIIh#uy%s#iWQ{!# zv{fIS@)w+}J&ygvpkF`yqNBNET5&!(a7lRYN?)Ye4~e?7#P)v8MK3?J!6)ARp)bFQ zFAtJ~6%MvmQ-?Y|c4S+*;dTlS?vmwLHoUpY&j!AQv`-xUu0Jn+dFr|II-KK=&`=MC zWyb22ALk_pSe)kK(DbTc@%>)ZHs%#kTBitH0gSZmrJ?NnOW@p#vGXtFhxleHJuYLcuBwW*&Wi}kd zw#X7v*^}H!<&`Pnd;Tpkx6Fw5j1Yy%b;z>Lgi(rPjjyTzF{4$jCwEUUv1n#+Cpq|q z?In`*t0&#p=)T2t33IzSVSaV$2RR-feP8z+q2(=LE|BZ>e(h0b8Qv>Mw?!8B*DKU> z-e!h-?GNT>UmwJ9I*(RBGcyLTDQ?gXboz8^E_=>K*qBMI#hp*NXB(mW{8>^@;#>0oDgQj;eb4bSrp}M_O#G~+IoqKb*E`z0Rff}zdl;AV?$52G!duPvZOWd>WwDDZdbwX9 z4U>lQ6(GgKD?b)TXX79K>1OW~&c7Mm1E%+M?=T7TV=}*GXt4NIweV57!hr45-)NS5 zL9aUJQ3{EdNrJZ4?3MSQn2(Blo_~-tvA6%S)q!Rkon@O5YX0h2A#uZP^vf>f5Kwwq z!fUKoZ*TWJG)umM8Dje;;i;DwQM~1~nM;yr+C)CsJ8BxTHNP(QYQ7z__CO%;`_}g0 z!MYHC?4T5T4YF`)8>XAGcH{E4pX}t%7bhQVi%8sDtQ`Q{eWr3OFFZ^(m?q>E{U~#q)>`y!+jG!ZY=B$-FETL zle_(=MIqp7hDDyfly&G5zm5JGnbIwldm9(J8#8ZDgwJgM_#Hu_v7#)d&mP>gb+flL zDf|ds8wcVm@GGD0=fcx#7p`4pMRS~2*zVT2)?RzC-Sfaq)-m_A*)nRYW6k5ly|6Du zY#rW`y*#80x%{J>YA)yR{F71sZ$Ts{z^9+cDk;P@REcW3?kj(^|&&%uAR>mC2M zKqKV4yMuOj{?`bShsni}Jg(&N`{(Ds;38@O7n%AuI-E<)ftu_?v%#3&PwfikM@EpteTBDm7ZJAC-xd3uKI}T!bNy{U z;i@q8ApUi_YtGp0uUEI+GJ&SO!l~Z9S-U1(SGqYs7wDGhF`Y>MCiaFfQn}WG*~dqL&Tb$Y&nF1B=HX_jlR4oR^9OtG z4OT2;YV1k)+jh(ES)3X59H|5c$9nkarGYjtLkmZaT4#n_yppnr+z76^Fy$h)L-CW+ zmof}LN;yQ-ixy(^wg`#J^>%)!q#T=A1=K|B`Btj1uVGQ!3xt7_6&8FeSr5m*m}kHj zV8~0+hSw5?{ErD}%e)lzoMcuCs2OTem*wbp)p4tJ>ylk9xbek){~}#Z+!^Kfa_80y z=35fu+P2jxKmSvyzjtl8MPU*S^nRLKZ0_>o8I3&De3HRHzvsb!S3qnnnpm zS)x!t9n!KE@XzCSit1L+7}@%u$UDJOcJ(9!IGhO|Gmk+6EmS`EduC$N016D{@Gvvb zj>Rds+kf)6j)b3E2OJt5d@?)K8jVXfx7pZcQ607jL7D(wN1rrGZ0Cs|2@K}vFgSde;OsSK#R zgs{pRYIDEo;akz7i%zHe)L}n@SW9nMjpq)RC^2hsV^8M6EMl-7yS9xkmb^mxHly)r z;1Lt}EiJ|ZP-b(63JGEhDYr-~NY-xBhhb**+l7{}dB#tjd)Z1hC~wJe5BFF61*<2( zcfJ7fhXo=GVUr1{xGx1Rr_-OkDgkBlDCeIE1jRuAO7I zyefhL6;7)PLqlsdfn}~@R_Y}*^k8QmV*w;5H?Z$l$v8&LBFLrC(2E7SmbjH7!2CR{ z!&XeX;Khm>LZTK|SE4?@3#66MX*}b7&!gg4WnxP-7mw&SGp1VRBH*j~FWy2S57LM| z+S=kqKti0{S8$P^uF<7*4O)gbrUVuGfZGWdpIYbDf`~R*PT79(X{tMteAr?&5!or()z0HvW@YEtJ)OqD8rz64}A$W1&(j^QUY>o#hLb<+}7{ zv_~3#WY~hCTj>O9X2KtTN}72Re)ztFI9ElDdtOQnd3G`$Bu@z$D5~W(1^MgO*(kVG z=T}0`b8wO;$Ok;cTkjq+IZJ5)58hQl>psiI94>kT9mK>IOPL=g zl82bX7t=v%lIep)a*Ab6I znZZUdqab^Dq9>Qa*RN}I2cI&|Y^0rYj%s68o*w8?kms>czgZYFN0#(a4=@_JKb4jF8(|C;AcJDaMY?B_ZkI-Hw_2IJeQbs za!ldtT@uido#O^+xcA8k9;=QkT7ONSpd{`3>ph0+@nQ7oh~>(h;MVtOfMIt2eVGfbR3wX zT*%rWo2Iu{WgN-|$g1-c0>N)!?NLCcR#gg&8IM9uooGL5-Y^+tW5=GI@!(l#^o2~j za{*&ZQ|$}MCVLPYAf-U{mWi-wMXTPmg;kLnrOOLBzOTu4CL7Z(|?Ei(Ro5~>}oD|Ws~zWDYe z6!;N2I!W3bQ?hCkpY{L6wnALuAKkUAslR!NBd4;jJ@sCkVn#y3H=!>hmHm z@xVVznQDA&h8$vB@oPlVhpFZ@sA&zmITeUg7-WS4 zEcKaH-c+B;BfL!4mRxjdN=->Bl!g-Y1O8oa1u_e3T$c~MFY1xw7hwK?mhToutX_b= zW(qT^8cHoIq^%`>;0qyJFLihWlJbj-5a?-Hz*MU!z3CYS@GhP|q!_fgM5|SZf#yCY zd-Vn7a=dW@SGgCYV%u2uoGVv*6&S5rAf|pLSi&wpz1-Y(kumUgKs3R?6&~#szi6aj z)_SIbQuqtK0W4arQI+7<4tS;H>00|3aXuy!&!%cpU9!@9fw+0rsd)t^oHi3cNa|Pk z=|sirMe5-!0j?k-xW$Z)42I1l4X~0Q22eyNYZfS)GUr0>>YNIhY5EW(T#iDOq-;CBJ!`5b~wulthg#t7mmTZ^8o5%@DD4_4GPYm!t)T{w@>xur0EA-Ajr{4!IFme;+4Rc2>p)**C zBRbyP1M=_vQQCS|$Y?-Vt^u-hn5ZOzz@UN+%oyBBUo^H2$U-~Av&uA(UO-7)-I75i z1PInsK_wSQv%G+Ck@{K=0!7!O*k{H`;5U{0NHA1j<5JMDfS^Cba z)iO+NUkoEu8}4UJd0m?0e7PswkSx9c@J>`DZ?u;iLe&q2WC?dy6AZ`W-6|F&04v;UFOdjOC zEYKV$mdf0CM8Cx>NB8F%HVA}f(Xhro2 zg(eu{#rPMDeIGnat5;mTHD-&-E?YF3m-QQaeA$)j?&?(g&jJZHn?9++!q7oD!X*Nz zZ6({NEx<%jyMKLN9uC*hl5!GhnZgJ~Hf6(LH>87Y+LfO2kSG|UJzyx@JU0hbMOK$~ zWr}g7n3$(wi(IIR*L6m<8bL)-ouNb$EbkU zmZwu+jTr5rOt=qZ94i`F#`LwR1q2p~Oi<*Yw+|j}9o4D;5WxlpepRVfz$EMW-q;K3 z^g3^lPOG1{a)(mHM6(g9LNwaI4xYu;sGr*?)%nz}q%0|(YM45G(tSXV$!V8#`;QBPAmYQ=qLFYl;ZuaBY6!3`fJ_kq;&877GDPl5?7*#p#7~|DctO9lrPv)fafyF41>u?TdwvI_IbZ% z^TE-Hzq9H{P9WhtKR^TRls4=>#Rl&5s`u__LsU)}q9%s1 zyTfy8D^>6OCHA8hRd;#s9UG)a3?8pT$2)OF=-vB-YWuhVz5WX zsCJwj6%zF{o{*52;HT>dyjChy)JpO1zNJW}18K|3yx=v?O~E1@nt=UCE?iupM*w64 zGW^u>f|!3!IIt^tgiC8-8jxN4-~t#!mn-I7Q+uLH<%9n1hwqcM?*I$_^@IL&x@Pkf8`T+~1rgQee?=Ck^AKTSfLByqQImMuwqXXxCW81t9&-MVC z;r#j?SWIXASZwD-NQx1(`a$~>eTCz-0F$7N8W{u$o1FVpI#;9LckDb*>8MLmT<7BX z4FlHM!Fv0zBQ7#SBh5iaPDU>te*G0Iai;eh@>}1Uahm$JNYpFc)bBnf%#wx&dPx!a zQp?ZxEDk^WbLdW|0W{?0U7tTKTrztz{<8f=yE?OK+f#t-*F*m5E(V&+*%Rv_E`6Up zkv;Y&tgqV1$U*i^V-1u=DZ6K(_Gg^?H`XE}k32fej|>`n-Y36+oc%cR6v_UByIYDq z_r0svY(rhRBB`BHBEI+}GglfP>n0qnUyCE{beo26SiNz1q9GtFOCqjsRmC3>?49}@ z+Z3PQ&(t_jh#7v5jEsFL8W}I+WC1%_?|*J-FZbqE@iykt(b^JAk_M*D#Z%78vEEOQ z^|6cTC#vue_J~aQ+bE7DCu${isA!+<#KC zs$=EN&I5W$F30vRcCVSWMs3A@;U6gS2)ZIsec|nWsio^*G@eJL{Jy@3+PJQ%&G-CE zM)!*%3+{+X8|m$#S=6i5X;}p0LUrswmEK6ZZ);z1^3n13;<*~l>!?Qa!5dH7!{^SZ zD82gmeSdL4=kd+o*SOymFFTx#v-rv1*E19|tN893dX{*PZE^PAL%BaNt@cBwWTBSe z-LLWg4|{JN6!rfvY?A^80tyJyp)`u1OG}sZE*+vE(k!KvEFgj)DGSnFOG%f6N(l-A z0)isQ(xs9DqR(qR=X=g?&fI_8&vRz(dG49Rj3c$n?kC>yitBP>T%sTQV|sS*o460Z z)Rtm!lv^2B;hk|FP#lHh8)NnkO{ zt~sLylCpoXzjz_{egI7X!f*2@eg?10CjUUj?_fKxXk0fOe~~?K^ZqY|AAi`fSw|&D z&TPN!B$nmxI2;6&Tzu-jekjZ^m?y)miWYAd&Y0$-4wVqweiiwKm0@>~kEzHZ$vDTP z6>rMGu%)fqV9wLx=T>^(uSEGo_t&1YqSUs79T zPU3V;y+-uU@uV3Gk!|>GMVoRSOe{Qoed*j3GOLzPUKjt0xiIb9sYea_AU?tBT9psh zv-D^2LR=J;ZT_VzZ*=eM0W!69+(=Z`q!oE?jO6u?RI0Yv-eesgA9`(jhh4gN1^)au z_QccO0<8DlY%GKj@?U@(^D$85+vj(7NxV-5(~6rk8Ad{Hl=uz3T8Yh5&8l*FrKQp@ z-%yr)M@;jz!~DjU#EtV}^iIa*M>aQu5?}uwGeuO(T}Vk}$+?s@@k^k!O+aR`^_R%w z-oDdcB&+i;Je_||V)^W~FE^$%J*Kt0l3DIBsF9L^>7VHGe=AKwh+sSWdU@FV^KAdS z9VGvmB~kq6p@ir1v2k|zn~(U9l)yhf{Tl>#@bvs&fG%O%a4DcPsHt$n*1O`&9^8oam~nyz?vqj znpg!fHc{)JEl08bMTdK z8+P59eLXH(`4{I&-m(#T)T_;bDmDS<81S=_-=p8(ca6xu_U4Zpyz}F&>96;Q40j6P zw9tG(`k%VB4lf_`bO2B7BFq8JIUyLmGKyoK^Qm54Ar!i2-Qm1EBU6Bv8Oc#>ks0ev zSsce5VjtDi5BtAo*?@!qea#Gy<=73Bj}j4>4fNj$c^fZs!QWv_InL1$p(tv%Fq7s1 zMWU>rLdA)2fZ4h5JZ(uNy*eIgq{hYlx<^0N(Ko|@U;_P$C-lPj+yL4VpnAY&D3Xmi z(S=IICDO)!hqBH#ImQ-n!Kx99$i!}?dqLO(NscIkPs|7|Qr(=mu z@DU^k*ltd+o(6IxKKGex^B?Lh40U*sTq56sih!M@lp97XzVV+l`IOn`~ zURgw9X_q|J4jSW7uu0PeNCBHX$4Z5=>jj=?S#!3~Z5;%9N$gXvRsncfeVPEh1F8k= z)uNR?e`XtfQ`ApjT);gwO3U9w@StuyE8iUUm`>EPt5F5~^auh;ZlYve2Sdb|{!*%D zW>f_6$qrVWiP_21R+Pq){pMEUnp&AeC57T=vl#Lu zrBggcBX?5@70)L#xGoU^=UZ=7V!7;f(Lu3DuwtS(O~n&xMCTa0z@9^LF8XA}Nj8}& zC4lXGkfM3{GRn3Ac$%0V?XQd3}x zOArR+BIn&9uyp^ilOkvjULlta3_f&(!$@S?tbc$r2@WCc>(M6;FE+-#ou_3USF{Q{ zM$sXn3ZpZz#m~1nGzzSNTg!|0wMDZO4Y`6Ed z@`^J~x-$V(mvD-3LwDbFMdphjI4&3wIECqFrG^b`Xo)}P*j=jwYsW;iSWUuA-;%h9 z*l!>*k>x}YN377o!Cx}7{k6<|93fz+vc5dyp8@V0u2j=>dJkeb9lJwhAJ)|CCGije zA_L>Qn15$nLSz}+5P^E60!At5H2~ejWe)-ykHh9{D&SdQGV2W>n~@1m0MgRiiu$rSb7EEEaffutlL#rPVcHp&s$IAjjvu;IMNnP>0LSpaJhHG^ zc&G&GMwxUjUjZq$2qU;2p5dC)R5Y5K69f?IKhRz& zO176ko(zzpC$X!!IA8A>GsLs^GaNH*t`QazGQ_*-Mv6!b+<>OBPv5^O(rU^F&LM`@ zj@Mg8=Qwd8R33kIPughZ{dQ!NGwlnmpNa!IvYGnsfaMKcJVUA{u zV<0fD@?B$5{4EC%FiK0v1zCqXT?i?RGhW1CnYKe>^(ym8sV~b%IA$4Or)XpI zD?hFNouTr$|7{eHiE$QoM`8gu1QWNXe{-?pllE9xBSDQnWYqz8l>9C1eImcHuXn)~ zMrJ_v=c9h|4|wDOfBvCwDu`Rh7sRE-_sxLy5vqw6y{Z`Zcj4TguK?A}Fa( zGT-AHRN)(w#OEboH6~`DjjOkp-vK7Gb1xN%)vy#Cz2va=V;p+u?ACeh&ZHBKldd85 zdH7}4jA+8EAB8-J(i|~!$m<-L za86%uvbwjliH87h#<79*u-+tivWSmO4Hc^-zDB!)kTRS~T0(Z{U)YCL#oJ(j9NwXh zVDbk(o!5wsRP@$6;8Ze4Gz(e5C^>o0wdz97%QMfTXt@-wBV%7}ezy9%7IM6eAsc(C zNA`m$M?cw@i;hp2!B?{@SL+gO3u*D%vIa>^AC}XVgMeAR&GX`*H-&<-@jj`(GPFs@ zr#RWL(noKVljoQjddca$LWx3W!t(P=@8gxukab#6D4qPyFNR~$e<~IRt&ncS|51z;$P;k2^47r1}nv#cr-&bj=)l#1<%6E;NXE*=J$kSj$m?yOYhyT#-!n+ zfgX)w@AtGfmHP^Klf&=P&~${w;uJT+A7yjfXx9-&>M-|CmA;IURAY*Yd95saWOQn~ z*A(x$DVOJFM zTSb(8_h$8VJZOEA`+1&EJL>}9(E7$Qby=6 zRkT|CJv0bC50xO;%xEr;mE=Ef!wS@L{(sAoOD7qNZ`#arZWc$2VCMVUNY}eVvNr znQTIb=W9j{3odev^FaoqcxZUi?*%Bc0VXdB_KLbqH1En7zC-e- zF`ZZe!yih@H9uQQbOKtTfWO+K0W(PF*o<-oAJmr8TGwJi@ zNMv3-&!Wd%B=Sa$_9iTXLV*iLXf%2vKJCqsIid;tS$5WCi3rqbi+ACp<;Hb|Tehsl z?=B&CGg|J9!DzF@9OA9#m@c}i2k_Pu_WTbBey?Z&g}7>YZlKFK-YIbs=D5I$EX+@E zbyFixPZW%W*D$LpQ*42a?8!Oax`J7~lMj;mnzaSM+8MJAIav65Yy8gaVB+S+4Fe6& z0=53d0+@gRD1&Ji7E+<1m9a|nVkH37{pHId;F(QAEs^-)d%<8g^0Z49SD&lbo=jUYS_wGc&N)QUH7~rwou}QIe%!hc&6xC4{?`}Wf%)aV$|nbPkDbM$z(*wyC#ZfjN!FZj zz~7@_Sy2^$#w=LFKNBF0mG&AY*00Pzk;ymq)hYgD$39Ds&tF}D4}<% zyXnMo(4I0PEo@D(u2!m+{=1;Jav1I5wsT9Kt!ILRDQVgbwDaw)lSb@&r6)JhZG0Q$ z4<75s^O)|ZQE$HC8F=(&lXCC3v%b((oecXeF7`@Y_txj(Zs|e#Zxg;s&%4dNxR+Y6 zCC(P~)c(!8dQPeAJLBm1fTlzjeCx(XUpn2?CyxXQU!8quUwxa^!6!+YJ+#z(X#u1G~rJJL7x?dSM*F~NrTg{QVhc8}h?Wz+vH zcJ$)8wNHKFZ8wpu)WNMc_m-FZe*5h_bv5;tJCbU`1^Tvr@x0kNkv#4zr`1+#Ey`vx zyw>(vYu!4*!6_l{MWgAT^cdM5mDI}E{V-FK-?1uUX+c<-KLRPIBE43M*XwCpjiojn ztZuCAe={%0%RGg%=WqXg^iJG#k@oeRfYp=5^Wo)wQ+&(-Z_uJ-jn*7GaYra0rkXBQB;0S~ERgo8epWzwu_LeGNpk3FwX6_%>_ySb zKW{tR9W*vP@O`s*D9;n>dmvNtx=P)uv+H}w(buh8=F1tc7?#(1rYZ;8qH5UNtYZ4Afa+lZc*)lh@Tp9RmR&mrRrdYGuH9Cse7swDEsh>DTCf_bS(_*V9G`v5) z%rhl*H-))So>ylgsMT-8uSwU^wq!KPzMo~GD)6Kn%dR}rgCB2-e#}Ib{-)5Ci;@2` zv!yG~OpX7lmL<>hB5@;mqqSzP5-48Cx&5tzoF@sh z^GR(t$_p`hzkSQEhDU_Zq(Y&Q?vgcx`!u>69ESRJ>+sD3y3-Z?x>AW*i?>%#Zmr+O z|9Th<= z8CHc)9Ud@pBZYmSc#tLvEY z`%?KSrWP`v!J-=bn3G7B?nxJ^R{#272W`|Xd>K~$f@c!q?{@-U`6#jYW|FP`LK7{$`#8o`d$@m7ai{J&*%G=GWx@3XZd2@(60{b(mz%0(4>;FawW2oQQo)zGmB)2 zoT2~E2Y9o1*|@VR>37_i_DotH9ldDZXLkGM5k2xqDB4N+dxOrshp0Z zQk61RFf29-pKh%wsmHWa%nUPBMTl>BarQrQS|cW)%cV*72JV1YMU+`>8Lx8{^algQ z2VWMPz}lb-YJ#2oqnvvruKf~o#Noq;hp|to)A4sIhMKDFLS{-$%t@woI$WqaHZf5r zk2NNhso%XrPE;YcaXh{GoO}BZ+we$YXg0a@yQ5?qI<+B0g#vZ!PJ{w}LvtKq=yUzM z%#_e`r?qHrb>lLLsO@rZ4ujp>_3C7DUUMX0S5VDjWEo%-ImtlSx>wbPyvxTz#l7P^j~s1*PJE-$al#q2gijvK=BjH3f46VJvBv6i= zsE;Cvg-tvyOFi;x4D5zwfvKbcL4C4wU-5jQNRhIp2Cjy^1Bn{EThi6NLk>>hb^K>FrPNxD+V9x0(m|FEGD(Q(^I7Ge`HMM zA85_+*it8k-RMLbwcXMF>%9pFSAzo!PAa#8Gx`}xFc=#!O4`AC#;gkOX`xiNjEMUN z%F>jo&I$#)aiI`H2V8{T=cI6kR1Yd(JApc)%oAY2&~OADMeaYV6NqMLb`Y9UT%7Z{ zBM6NeY%#oqr-Kt^w0cIfLbhygVv<2JgDu6aN&Y-Wp^PLr4SET_m+^8>_s4a41oZ>b z`ymhyDvz=uYcLq>Mw^alh~o?)wM!7SIj#&zrNHV3GM88tc=v6z7K64ZbQZmiK=Go| ziC+?eVu#tP&2*8(j~c=@<8SuB06>4Una8^C#1Ghh)CHwC z&(JXaS)uGh7F^tqNo(qMDPO+(J=orea%RbbNq~30l#Qx5uC&aUIdqxCF@o6t&HSjH zQ3RR7{IEZNrAzi2$(R}=3(3_Q0{fjZBpbs1!4P4Gd=-}>Y}tIH&I0p2k|RCPo>-1L z2TdrJ)V=Tml0}L6dv&J(t?#pq-Wjr^{;61LX1ridHUX#3G6fSh3vhliOE62@O=y!0 z7Le(@O1Rp*LK|68^7%o59CZmA`1q>ba>J0^xrD;nUAt{d*dpaa=TdPbUkhhQ=(b;h zuE<~mi|X}N@==BC>fYX;<~&^Y#Y}iUV>$1Hyf4TvK4Kar4o{`Z<-GqYkj3!=rKNju zY(*W-^E%Ak-60TxBC_wZWPq4x6XFOEs$8$mB~*wzzf^(P>}XSQViaKMPb}**GhW5{ z4ag3+EUFN$s4J}CAu#4t6L#8QI7gr{ncY;z62iB<#7WgUk3cD3{*(ZjRz%koc=B6{ z2oPepJnUEq5M0bD>%5NnHu)U9w3cBO#|LG>O)zBIEYs1@T$qIAH1Gjxxf$KWkg2Et zO#R({bgBSOs3c)P^F89&Sk)a|o7X9zwFFuOLP!IwI|mXm#`pk;JSif$Z~(B_LHAbk zWOQr5<@S;oE}uEcdXruJZAS(;<- zZ5*ZCsV)_zoWJH6rA!%xY(P~dHaVTFa8|i_d+voK;I1B)xB_av1RddRBoL*{Hv=YB z5(L&uxo_d4msdXj5ND|qQcgr{iotdD*1EbH=rFwB$$jWwf!|x1dIe>Vl_kKuOH2tB zWmWj=%SbCJ4lvEQY>BjF6x4IFiAwR7a~)BFLQuO30Z!a-iU5pBWl$J786sx9<1)q} z4mTnAG>91G2xmj=*vS0s7MkF0@NTLt8wg9rejp*JW5}L~dBy?r9>@I`-~v8SQ2baM zDWWjTlc976IIww;Af%$7)}EGxLv{DGK`IWc>Ry{I)x&T+4GHF0my7I|ea&DvxSQTL z?E66k#%N*Vf?0I@%V)SpyV-;pfLs@Or%r`GMzv~8$Ut*CWdN)39+9i6a{g;) zpw*|RWg)~HFp^g&h{J9WFefqD7kEq3u(5qC$Tp-gaLouan4t!ScYRHVoW(H>;k^Uu zgo0|>kBP;lF=Q|jg6E+pr`ck~prZFC<@4t1gnV9eWCy3_BB4KqC6_ZAz#@>~!$9K< z(i`Lsm}d(GSB-bbAu;+@0_-yhU&y%#w|qV~7^HW1Y};NctjEOA`B5i&btd<45^`-3 ziiLes17I)EG*nqL#X!bOi~@`^1``OKndWh~rcSO>BEaHlz|bRL>*N2j1>`()ZgA!G z;{zn9^^I~z8Vi-fpFnZLz9S2qNJH!_o-YqBMQ^f3JQ^tey!(#RiHlA+bGmK3#OOee zMAz{|j}q<8RXm&#dKMahyy?@|tr<*-iz40iQY3-Qw6X#stNAq#-vOh@CqWn4!Q~4! zjS0=}-Wl{sJKmmmptPsqA=f?D6~d^1yg&&B5KUUgNDw+>ocWZq(rHl%i(@^UrD{ky z>bmVO$%}C9=K9qZV9@D>fW(}~=Wijh$P}hpWN9xG*~P;v?>R+z;Z80B6WJwWiV=8QB}a87|?WdS3Qp;g2UGmfe; znF75Ztygq~qDU9^vI&oj!pB_U9w%Hx;@Nmpn44i;A+;FfzuLBJ4~|f;H!E>{Q4w z4k_I1uoq=0ab=~=wGVr)-h7V3?)WJ?&dgFG}kKzZl^xY;)Hiqv_ z5m!e?65$BnaP2c7q#%-d(-k3IgY6GElNLa z&+*yHYGaDiTEUuXGNB8)?+!yqg$wxIuGENi%hxE7Zs~Cdui$E5Ic&dJ#e@i%c8D1*zvMM}VVQ`VuVGn<{QVNN=Z*L76lKUa;xr{uj+7|mdH zx#M#h%AD^0=sHGN#zU%#k`DeRVb!2~M`aPO=NbFK#d)F?InFy4(?M8#RcJD@1ORib zftEX0fXmp9k{bSGi8c+?V&z(CsEyOGk_~WC-c;hD5=}pOn&5L|y?y<}s7Ex+?8IvD z^FrKJUhFGRkXO8aI+0tqiMdkviN?XQj5U-L=tFi!X<05&=T}U63d4;g@e}9XifguY zQISmc!-`9s8k+!%tnZ45+#Qgy8PxL;*2gH6&fV*_MiCa7P~)-vz};K66bh;kf$51U z90GwXeaI3_)Qx11@+f*fV-yogzX{ijb&CISn%eOS1%G8j-Y|l{W(o%XcxY6}J3(!b zR67I~AF~z=$pzk=5G9it@B)$_r-p(r6l=WLshETw?xdSWwDIYjFBedwG#n z$#WAD2`EK>!tH&Yt0W3%05YhOtGp|z&=S)Tz&}olQd`2bG??BT|9Cz&kA(2bjWBs# z$%GwQ>nM;yx3nS4{S0ykDWo^xGF3$ZM->+S$O51&aXuxK1}VL#10viIoO!99 zy1sJy>)Es8Rj$y4#hL{@N~$QAJK`-dOoAAt@_7SeBSkU_B*@(78kChmOJ1Q_0?Q*l z!vaDY{=Qc8hm;jKJt=cqB}5~T^h-X0E!A8^6V}I87EA@VMqwJn5E21TP{*RqrX;|K z9=V9KW!=dsvI4c8UQ;+bM8Y>Car5KR25YOH|;|Ku;_Y z+J^la4o41$iq`Tv?&zk`o+aCrJF39z}v$C4DcmDv@ zNHi6gY=+7d$S7cat={wjq%R`L%_U_Z4*@tKuw$jbO_y_Fs#q|CTU^JEyb%_Bd6499 zlZo+^tz2~G{Sc-2vvBp#3s@jC_ehezIs+RfG~LW6(nZ1$@{X(ItX6``BNt&JA-b2I z`;i<(Gb?!L82o^kw#f7>487={a8&|~NZq}BgdV&h2@tG>kV1aED4&S^UNc;AFu?U8 zQOCj$yRGqv@huXJQmt;F;M~cMDT(C8HB%~e8k$HIMj6_Qlkxo{gyTR4OG;KzcLM_h zoCRAB8aC_bVTsntBai{x6!9EuJ{F4;NDGYrF%C=HVRDKOoyfLwDxwaJyLW~h((_kn zQ4`vI3H9F#SLmE1%cOR;Os`sAH)dz3`t5AC`&7tO`mW76o}(8LMGl$eto3^=F|+-3 zqzobb+db%wUm8-{-8z!0zSDu+hZuZ-p2%gZSr_@`jt5&$S4IR6jF)*z$okp`D0b}c zo66qzo|5mH$I9;8?D+m#dV|-Uz5ku3>D-0(!?ni~m&z>mONHKvlsX-2tUTJ%b*&9} zB|Y~F#KuOQO)~>I?_Mf zzt3VHI`@trpTAV}qi=M}*=&y6Kjg^C>K%@~?DH+7#5XfGM>)=yuf7|0V2*7YsF`ba zh+-(+6c9-eU6?KZHI^x@J&R5|njNZA6IHFT<-Te(*UzOTid)smprdk(>^}5O>{x0ooP@~ z)QjTGI*ZNfy#;ISqWzzrwl`Pa+2$QQ8(9t5M2UH^S;fQ(F`#$5P?^bZ7)M9Cjv{40 z+56`mcp6zw;O(p|ME|5FsX}^0bZ|c<1%I?>YM|48!|Fjw;t^MdNzA9(>{v&8x&ElC zG|F~s=~dsLBY_?Kw6$)6o#JKnM?aj!EhSwP4%*+Sjtd6W?C<~B5f6-R8O6QLm8|&P z+3#s}@tEN0t<{wgX@3;Y5u-i5hdtw)iRXQi$LxK5uC{9S*No%5lajEnPCsaQkaqP5!YkZRERd(U_=o-f9!N{^& zHTk<1Eg{c6eh&^0ZP&dSz}__@Mov{x>-$Qar&fED>>#N-*GGTyfuHBA295S-1tXBB zn?yKd3Khydw*XspD2b&sD@^8wyUpb0W?3?~Xt6@m<(_q{4Q3 zWqRUDxUc?%;nAmi(*gcg6Z%IHt@w|>ALF9N+Z&`l4~mMYj=BkmPQFl)7rF4JFRbiR z64%~ogb@L}%$yb2v0WoxrU>xI0&+O37A zA6?!}By)%TqJzc-mNEN~AQ2U1G~L+b-w@p}WEvShoHA#TvSh*iatix;v+K-sK7NZv zT2i;?>5KcJ3o?yJwt(~-=l9HiH+Tp$q{pUtNOf;i%LEAb=l6;Xx?mS%(xUgc432WK zTkQD*@2fO!9tN7pHKgBsXDu{UINGu(_)Mzrte~;wAqeAdORh~fM_#STEf)K3CtSFh z(X}nZo@q?Hts1l_Zc9tOIEbq0Soo_tDLK=jqR?Xns*~y8?D2sNgwBA!AOEck2_by# zV&`$&!RFtejp%<>W_Du z!h7%12q3Su*7?I}!6`ux(T_5*Pq*7j*w61Zwg)&j^|ah;3iqE4IF!v5-a~wPu^rU) zQEaPAsAz0=s=eLq*%&LP#pHLD9iY8AI@TL|h%tx$uWL7mERg$m4v89+%r=D3-`v{6 zWgbT*>AI%4Pa!DX6X}fWQx1t1|zEYzmn(4!vcs-0E;(oD0Eg{W>D&Kz& zwDB>8l6XqPM>IkLi;r)yMg;I;`CBgip5T7SpNDUvY_5MTm+|B@rWDuLyYhm7$;Kj( z`$#)m-h$)mMNr@*DxA$FD8Lr}94)Zi>pNfG(-(K;V`HF_!C5(^usE#z)X%!xXipTK z8aaNJ!=2$!PWn?o22JC4y1lGN4&(x&o<Pc8LJMdy3TOo@=Mcut*sA)YokytU0?ByZW>!- z738?2UzvI$IDrqGK9CxUsiotRR%Ddqz<~c$>U_AmAg43L3&B6q3qEM@t)hUPOhec6 z22aMK+npk{_xU+FAdk%7oYqx);7NF6$`|b4aXLIR;&`kykAI~~{m11QLwu+HBMUmp zOLlSJ1knO#Qb42{YY4V?4IbX zc`SC^#1o1jUkHy%X)it|bf~z;#~aFFE8^W}J`C4wejKMLb`DiiHVRm@B;pxJuHd}o z+sqS@FZ`~AuC+KW@!K{rvE_b*Nha_kIQan=0kL4tS~vkfQCWW!L14wM#@lw3nKvPC{taRs<|UhUV7C%<(3%^oet{< zD}ZqWuW*2OM@cO@;#RaK3R^vL3P3uYIDud#Ns2b*JD>XeWM+5}6wMs$Rqq)gJ%Sn? zFXe*j``)n0L`_Sz!o=QS9TcyNrV1~*_)4G|N}?woA+<=u6twspua#BNsi9$OFbdv~ zetiWwEZh-;*NGUdP{jVg9l};pG>%*-Al(Ia#>IA;AQLqSiR_ zPH(}QnHE#RffNN0TnxdGE={3~A#lD@Pukg{*m?-e7tRC@%1TW}Np;EkV1rpu?!Koz z#@66Qr#!ESez^64%Y@>x-O2!Iyg{-t6-!8kKFQ*F(;T{1VEobcf6F#2I-igfo|*c@ z{u?b9RmgMn*Jbr08#7`TmT3k<5-vd)rnhl))SZO$I6zFy0>L@;^yX7RG{WN2Ya`22 z6kw^H1sETslM)AB8lwn&5P(adx$Vj#`R(GGMDxS;h3q^7=D$eRn(0ar7%Ruv6#ai8G zSWnZ>^x=_3L~TM}*z7vf+^7R>0d|SF-fFHsY06 zZ8^<|z)sM9&(iX!`VJ`8nzU@acg+b8>hl>q|ai7I6>Q|aZgYMAe^>B z**#*(!9|!5AUrx+^JUT}V6-dnj3Waaq0%U*rD8N^^$x3xEhuKwh`;7Z?TtY11*MbAlsnR`Nnww5)DOVvAgC#Bmno?OL)C4&2KJ?UU2>Tr$<|{$U2%=yG)ZPxU zqc#&4wO#|u1+m|9L@@N6zN_m{ng&aot@7s__eGR;Y1yhhFFYm^R&B%eG5Pwy_#07? z14y>3)Y#SU!rJcnga-8=L1rZO}}-oUYU|SLtdNEY< z$qA50PHD%BVpwhSb+&F8n#zEpHxbCK`!M>1D6rvT^(N~6uH}q4q@wy3HH&J#6CKaN z!PRAhpDdSu`-!yS_45;TXhneI^b*r1YR3~6Ig>JePukLOK?|bePhu)KjTV7*sV*rABQI?M z`NOUJ+pH0B$ob&(cn9;YYeg!Fvbi8nOPtne&o8fw2*Qo1 z4BodPUNKr+BxMui)OK*N9Qg|jV!}`(J`}pGtPB`%Q9!ptt3*J2DCSf=4N63lZ4uT6 zzL0rYj_6~c1dt)j9&*$}D^YO>Y)1)@ge;B3w@z4&Vs#&U@U7VMX^&($-t6H6E!T9 zytcWx2ETU@`^EB*QGP|$5vCb>G8j65Cun}{()Z7&&WH1fmYSb9$8^Kj)Us;uT+m~~ z3B^!76qW#QO5$~q)1dLDXMh?CBDJFU;$F>+Eubh0@KRS1ortf@*9uXWjL&@O>-rHv zbySqtk>QdB3;p=Dc%sTkJ(TA8iwY?I8a0>6a;fs`0BOZ6--q{qd!KdKBQ>7%7G4)# zi3SVMu$EB;`BTM639;W906tP%MA6a#D{7gC8YPDsw|=~`v?H7~EJ*>(yr!yxM3-at zKI4fxS_2J-tD;Pihise!x@td(nu$2Y&dq$Fuc6^A=^G}|oc&NDz?Ed2!l|~+R#GOd zcFfRB&sW^Ym*oUPb5W1YrA_Rn1I$PTIG7%lUuNpe=5LPyxeZ1Tn2YEx<~&nbMDemB zNhFZamaytQqcg*#=w`uArD16-o4 z;!d1liKG7$MxyWY`b`)zdL#U#VT<4FuFh4jyl7jw={UK@%M*wv?E$n9aX={rZZ z%HeR$+uIjO*Eyazbtj50AETS?#w8lqZ@vM4z^1hhmA z>Rka?_mHp(Z&t26JF^53DF>~29rL|<-9}fIPRWOh=*(8Nh+bkWF|{BX<{iSk3Mk7> zkO0QV53Q>*mU3;PBXlVGPCkEVf(5!(7cXPIvL2X=X7tqL9T4L6&_S*0)Wc!>Xa@PwwHI`*_w~66=_OcD} z`q4_1UR7$7jA|ZwlwZ6l_Db>+8Rg-ygfe|fj5h~a)D>Eho2$o!nW+XBah9WBVd&`Z z2V5N2bvOY?p~~uOrP?9LB6_}R1M@W){E>3WB2>fKT;6%nEP7sKUs3$QTPR?|(SIo= zp}wtzplB0VChS0$P*AKiDUzy7;63mO!}+>M!cx%5>60)Ug(4nMAR(q^m%L9>)UeM+ zlwL^~qoA1L!+wl=NgeuAn>bgwG62QmNx6Cn%(#i?j=_d%7{Y31CB3hdInR4SS%Hklb3Ct});^$0nraEyRG3M1Tfn*dz%8sHTB4VyK2YQkSz z|2bSl1>oiqGHTc)Buga^m**$MWIIv|P(9at404r;#_uu}u4biQ^_K0nyy_oT$fOtV zEYvs*7W&{-D3qpmt7S5IO`CsIAHIFb$L9KChU44?`RJO-2A(U@6&XEztD`}2havbf zc}trYuRh5pwD~XCcjZ0Zt4Me(d?7LL=}!OjSJaQLsKeh9nO03v>}O+(VzXoe&-NVI z*GoUolJ~t+Ptco4X%E`~a5Zc7!S6Tc`K@>ESPQYEHydQm^Kkpu2UMMpovpP$oZn*{ zVcKbDMRxTZ3A8#VzT2wdEo@Cg2nP*WU1jTWm=+N+v0(e;kX|_@t!3|f)q}Bddec$v zfoD{g^uqp!UG~KGgc?PS)VB%ux3!Zd({LB0@(Yd8uI&~#T-RGSCR3t&v?rr_x{pwl zfl{^RL%#PeSBdM^W3~ovDF(+$UP;5d8;r2NyEo&Jk#9)Vh+Oae>W&W5Uf8#-Wo5b- znmiH9MYfPR!4gi~{YL5D**z=Qt}e^??jxQgq8n3)^b6VvUeY4$^s%e^cWD-rpF-EQ z&pNlxm*K{C*R{KemToG&mbZGY>iW@TQ`tMGcc1ihN(2FHc8WE4^Tb%c$qgw z5Y@d9vtZ=C{B#&QSQB6|7$z@dxs%)Ao4mOy|M|w6K;hCS z1sV2tVMP@sEso|)V`c!E2ioE;C$P!Ro(g6pO>|UNveaR?VV~o$72>p z@)zZP-n!h-{{@v&E_{I{*4k-uZz8~F=lhQ=<4RHWz&*83X}p!rteB?On62Mivy6kA zxDV2g-#uOKU$4|@j~Ypqf4k+iVs%vP@r|TG(L3v6-b8tY$iq8E#jZad+eH)_Z@s)} z+pbv{_0gsI(aXVUi|-lh^INvN-eNiejKZ_8=Ps`1|7z&JR{rSlgKz5DTbsG<2gK}w zHteS|Coc{*AE{m4kgcAQm-GF#y^IeIyw!excVe-0@Aq))J;!!yodZ$dq*k-Lz3(dm z+rV=orrrF8FJ-Ds$k@$xTq$yOmZGh>>(x<8{*h3>^;QpsTgCm%Pq*3}zTJUJg7!~h z53Py^dlroB8I90qlY5sno415ElCwOGsjFY;cN-C zXwg+4wJ!hTC(Mo_tkQMYCZj*wR^W$<{p_yk9=xvGlMcOBhu-H8cO9!io3-BNbPrt# z^W{A7-Ff5sqsAJW<;B4fX>HL<_ABHh7os_P~ zICW1gZqE3KJe6U!YX1DzPKuq~R&zd$VYe;ogI|=(87scY?Jzv)`3tx0Br`s@n!K*P zAX()z9!=;X8Fg>>c0Ey@aB6<^Oh$JN22)8WB-pelF&kqWafWz zB>%TGlKe-GnR@f`26eN7%UuW`=1e5URA*IqCcQ%UBYiahE|etel($#r1;wso=DT_F_)Ts zd%I`b*Y6dV7nWT2vts+kUvGM6+K>D=t>S^UiTc4>BlAVjS~jh)=JanPFu{);{W+W$ zjILa;ZnFOp=z(mzu(vqfZY^m%C-+>1_Tq#rp1sbqG3Z76Z!|nTi3>Je7xqk=L|%YAPOeL!Jc{Oxr5piQCM!T_#Gl4Xtl&vxVrAvG=0-a-uvXiWq%KJnzuJ_`& zuo_Xvl%>4Q^vB0tQ&*ysf}VBc@Fja0?^wLO=Ggv@`1{v`X`g40>~7p|dPJvCtjLr* zfHL)FNpq*|Y*n-b=N^q;f#x0h6!$14^uEuICUJ*LZ$x+siv1i@Ik}XG+mk0{nu|C} zT3e-?@;7F<&TQZ)p*9LdTTXt~GtL!#2o2?A#9!kn%R{LfZ=VsN%b_G;tr?zJJ9K$n zYc{({$@v1__&xZ%y7BOxycwVSwT8w|7j$(@H|5oK2Hl>?6g;f<2|hG_T^r%&x9&d& z$K*TP<0W5B(Y>!S8U+y-RO$=P4AHE5j%qUJtmO5Hc-X#5xfoJCLcG_}o4w|BnHp75 zqD3817U_0l6Qj|%zj1>)6z)8)u|yw z%Ezd=RG>*6LC!94n>lU*e;gsfS! z&Jc|j`%)N7r6^>}nq^4#ogqTXlBHzd_Y_&i5>nZT>ba)x{d?~F-2Xk#ea`co`<(lq z<{ZuNna}6FUf272y<`PShR#rF-hYwMw_5chIyH$YjEewo0e9j{q*za2ndONUJT-rU zDR*&wu#Z@~riv+>_9;8s@_bwsWQ3^E5iRWWQ154ON)P*0T$XV-?)t!ocJhzfgI8w8%jT)Q^!E5%4- zE2+6e$Vn;`x5%#)ZGjL2_?7prXX){u$Zju|&=z=9+pm^hB2b5M3F$Gm&@z!0x6qQR zGg-!uR-B@I`gTrv ziuE&!!akLpP^(Li_ZxP3Y*}2Ym{?t1c*uWxYq_A4pgE~I;%Xxk1nBwzk0^tmLcp0i-Fu%qR;asUB&2$-rZW(Df zVn-{=6_a7W=s8L{85(cI<59%UW(B!}@#hql>E`+vp0}ls?9su&^$W`P<6 z$|Hh4J*1Rr&|rt|6xh*cS8@f-54YBI1dn6Gw;!$5yUgSWy?2rBoA>y5`gs!PjyT)2 zppJ?_+n|ZG=Gn+$H05b3siFN_sg~m1a=|yS=Gg4N0?#*rE|;zWffLV)V#0Qo%l zJZ>Man`cPzu+Z`$1)NW1?HTK+$YX72xsh4{7Ee73tr4vF>p~h9HRHrcJmSp7P-@Tq zCt~yA%BN6WZc`5Yjn3717Wm0wewkT)O=AqMYc48IP{IUSYwH4xGbSV@3;0p&m6?%S zO+g)au88p#$pDj;aK*9+(Ezk3n%I3X2W6lO(benEwvIdxNkq%2+?A3X40?)*8fe?d zt_%&)PVH?3IEbO_Aog6mdhHjRv_!`bfA7bg4fMG@#xwNQw}ZvobbUWc=a-H&Gy3P= zrpM`9f@So@6Cw!6NoN>~){Xu3gg$M_rm^CLv{5p-apjdlo|I3PX?39u%#mg(N{nZ| zW$=F`)4MF7P7JBoAneWQe9#m?g{QB0b#aY*hiOYsF;b&gdxhCkY&USg9xbMR3J~mC zx+`@7&`_pbq?EXZEub z}~)FUks)$Ha0$GX!8R#V54&Pb2U zox9vUdy zNO9|Q|8S`DEt)juMKNXHvAe21UnLZb$;O}b`q_#o1!Q9$)r*6>-Ibf4R%P!T9rM5q zH3rc-PrB_l+&)$eIhaojO~+3rJ|3xGewJ;Tk*Wj6{o|& zu1I@r97~)~WJ*FoqN@zr=wRxcO8W|1`@G)@6)uefF6q}u>zczQU@+H27ZUsqG zrl-9D2r*~)F)btE%Lt>Gz9x(nZxJXfjGdj%63mO|qCOgL^PXH#ufbbU?)CkoIcPJ) zKv+-nKEqpL%T?z#5lh!!E*YwYJ0@Goy$&mlH=+68ZpXsV*1374Ib^Ihe7}hL9QYcfB)5VAa;nRT%vLdc0j^C5gtX59Yr|ESGJEK)_-Z7UGgH5%IBti8At7hWy5^6kz=oqz09Za{7q zK8}XU@D}$tPOxP7V31b6>g`b+6Qzz(OpU zmW7Z+tt3uEG|WWL9OYQ6!sOE0j}Fqz z%99wrYdI9%#?YK+8piqt-+MW$&0AI?&?VVj{W>p+*uGj`i*&tw>-N zNv66PE@ZE!QQw?STn3ckH_9mCPD!`vT4@-fN%VE;y?qciH^g%g*^$_IJ7FJISeGR> z^RFO%R;XPHg?x6&^lHyWN-OXsg=fW+;}n@v49)RICHbD3unwSPAN9%-*A_U@D5`7E zEn@$RFCE_S!&B4VT+6tOPVU3o%m2(eEjg%I#h@38&y%cZ!|{)xBciF)`3tL%C|9+X zh|ioJ+0Q8<-Go6e-0C&WxUT7lZR;@ZI9}U_PF2znJHK=yilS6`j^f+P8&+4yz~Fe) zUfi1H?E;4<2?80cqp7WH(r;w3FFOaz(i?D+fJ4wRg^$I9+5-JajT$C0;rwoVxYiZ% z1PwboW9d2=9;}Qx2n?)YqS{|1=_?wU275j>gPcUf{`@WCI!`aPE+dNB@^Q?(Ach4F zjj$J1(uywhNfPt18ns0j^gm;Y0n)>Tb|w@$`t1S|v#FF2;s?pt841R~$Ses3AT6nZ zBbHhjj-haO#v0tMI${ztV2PNsB%_}BD6$_gs+Y}Lc)Qxv$sE>?b&B+HXbo!|+-lAt zICEl$rm!Ghc77=}l%7BZ&$SzWyY`QhMmXh^0Y$dA3(M-3BTBH9gU&9prN>8oEZe}2 zJ}VLnwfbl(?y88WDF0W>HVB(m77sukaU1NqVbw z62q@utPP6=r}Ri@rDdEhSQi>~pUE*a`tuMgY{De2h&^ z#eAek37;6AQyNPD(S`jeUJ?1JJFq$XoThpWVB6q}ltTUyHVB$*Ph{HolV~=ku2aBn z8}r~%Dz~%;-HDxO^4Sd%$O{R2H*1|#YJ*+5wL;GBNj+6_E#~Hx0GN?7j#-;WKNI=N zbv_cih8HB6$@_vub*godlZ(~5_;}yLM0P@Q(i-}bG!B0JP9JVFP0Gp&QYisSOrc5N`+|b|H{l|>8KWw<#}Rq!S}-}i)!G<7z575oh#r_wX3?*4VzUPK2}@O32{;)i4PNYOPXeV_FK9)e=G1@t9!(Y zD;J5)RN0q}IWSt=Z+@`TYF6b?Md*?L=10wyOdmbnS4A9JNCMYd@@tK;TiM# zQ1RwX)A{AO&2DGk!=e4c)(<R+;VSCP3fKb}IO!zJLj5Rm0weTaIyv4aooAS{EQSs|Sy27diTKUM?jpd>a z?N?8QZhwtA#HB3F3h{nyjpaye8e<*h^~zvdh~++YwCcEn`=yinKkDb_HmtHsV=VpC zV;f(Qe{YXmuOJuf%ec0<|3T%{!>f}L-HyA2vgZ%Y&6A~l z!+J}8yH{+ZjoJgWJ%=Tv0axZ$q9N;)P(=|SV3pZR>|AE)~X^VQpG zRv+ZCgAYwUs$MPq*iu-9={tZ7Y zDMq)n5cMG~@zg;p;d9}`yBqVHr8;^)8F?&ul0lF#`CYH z`@s<}(0{GpZ}FY9_4JK(MGlcA-Dd`6tjFFU@vOx{8du|6NvYv)ylV9a-ssDbh>ArcL z>Gp{`t=R`$IFa(k?@=|$nVE$Pce-@y?mfPWiTXP9c+&3kd*`})^S*Igr;-}Klg)f+ z8-0zq?c3irvBIJ6#Z}64IWhEFX``19Ys%+TnmA99?$6HkRxM0O zw|(t56=GxaDyZLMY2RE;{QQzS;LalNRQf|y=m(~j-=?P@Xm>g~-}X*ArtOQ3UX*;B zsOIM+A~AmD(4FJT7>#j>?EyIA&nKJ2S*I!FFG{A;-;v2*(e2)wHhME*I~6>2(7Bgi zLhC|m%P5~d)qr_7B8ZkuuiiOMe$Z}H)8uobJ11YXYFIs1?2+Rcg>EWGdwPGzrYcjr z3`WPMlDWW=77lNYVst$@!Oe#AF(kBE7^a#+vz(k7~HnU^>n8X zcztIYOei(-Jl|B>8Gr1%M_;}#I@Yqyd!Aw^I!1Ktq09S^!vc|aZi{-zeXL~j_!dh{ z5=39V9O?Js!CH-c_TDjghgn4gfnjEH^*Pv1R=R$h3XWxzl_Kd$%hav}*Hx-Qy24P} z_^p|uM_8Rqu+5;Q5a6PjOG2|c$iL10jOHdgXaiBH21ybX#_k>9xqSv&1GKm0*SNHA zaIX!^1+=k*W%QgNNeiZk2=r<8b!)kF2gD$HC5hAow*lvF$8~(H zxbL^rddi*XToUbeFUD;*pg;h}e*4>zTgy?g^{WDL-hMM;=f#g_Nic-~$m98=m@;LV z*-^P_8ZrU<?>xPAqhNj3#ZuGXmaT-Ihdr^Clc7$wXaf%9kh^ zWSvGH^;mPv;2jAcWwIvRWFC1uY8n@wQ1+|-VySWm(Z|MOdYAVuo$W*lMMk7c zsLPiZ2;pRHX0;+uU)$44hSqxG!bsx+Oe;2d8?#)iq=d}!O9Ofqx}!LV9NZ>`XP^=u zDxBAA!nWa74I1F|2whB5!ra+p0Ngski8>P2cD&674{MC2i|ba64y`Og}iAq0)JcbQemXm!#yY7;0T08ve}X6(D%peETCbNu!L^D|s5k>5R5#=`FLfkWxiqsleunET#cQPh; z?ZgP6b-Z`rA7Y`sTCNFD3C=K=OuW7>BVZ41eF!596Wuzu z);v&uWK^mwSBCs11Neh-O(#Tt@lG=ictPD}&s>~5oOF`wEY0I9mneljOf&5C?D3}0 zEY~cw$>cSGKHT?@0Oyx*!XC7^7Cc-QyN;W#Y_ zSNsS7K9)x6SXyd;KoT{I>pNJ>@wcf)kzRCyQWXuM{UW0KDBGlZwjg8?A26Yp4Mv`l zSH~x_E|w+%)7wI8k9eR>ESdQ)#i+ZL13_URiApKSx%~!ZL7KF2T1t;hk{>w$m^iX1 zk{CmRdY~&``BZ5P-?^JaR+SlUqghpthYg;op$fi~S#dd3W+7fRU|Ck$s;O4vI(+H= zz_L=IR&Y>I3wWuH5WY}*OqZ@dt`_lV9Zbf#-N0wRC1nHv3$=}x39f`H9|h-a(-QL+ zyg5Oit9~-SjsIxM3J5J`!P~uz8B(MYkJh;L;axj{%uzzul4AUU2Rn=r{oG=7H&Qen zJzRl1wYJ#0M4%|O&_Zh$9ZhKqKB%EOGW?`q7&i3<<4E9#G30L^d5$9vihm(}0SO!I z=&qIUU z)H-*Vm{n37>AUh|Kn)Oiz^qE)(@A;VY`n@~*`L3OEus(VS~xA&^wJ$6j6Y|`3lc0- zhn zW#%Jeq`O#ZMRmwbRA+%g;ghqVAHrHM;zPFD^R^JO!gvz>3FpYNs}t^r%O_lqL!7B1 zCcLj5LLLN&K~!_Lvmr=~oD&8pZ%(72|EvK#&>^XACH#!cNnsNQ`qsfh%xmZ?JK*V7 z(b`6+)10w6nW>CfSVX8HV z6fwYWfar!a$(TqEdCyD@+#6c@U!giG6;|RB#s1P+!M`My{QxG!2bSD6{s7l6Y~BeZ zxr;GHCGFd0e10~Jmjbg~k6*ml7B2;VZ3b(#7;_gg%r5JJg?T`wk*t&)v^MW3J}JyU zcHZmR3iKQ0K#7>f>H!&RT5E{S67ouy@(pw~m-_h1fygiM zc%f!fzc0NTmsUPd+6+mlJc0uBbNz3d$J|RGJs7Zqi^r=DRAy?^qlIp~A4*!lM?AOCYuaz)rPcXly&_N#T$JOqzYZTJ&XQQfa!=u@W)$ zN8!AH?y@KGvz+a!0m_gH&i#SWi`4vq!PS>;tqoURzE1-Eky}mdQuTCwsU$1os*{c@ z@bN!>h(9IF@}fM}Y9?KOa0AE6BGvs^e0gTIE5Blg&`(kv36&byjb`eo2 zu#=TPQZqMj0zr}x|80%+gS=u6me^V5u;G|${sV>^_^>{2<9Z42A}xW-VO@!+TOn^w z)#F-S#ExX$mBzJk20BthzjJdtI8+ zM}4^LMV@t*$H4|p#6{a2dTo2SXX;B z7n)KjKl*GZan)mLNt2BQCSY*1)IpJzvbKD;UigrLI1(%=IvWbPcoN<)y$x8$*n~86 zNv$*Ls^AsXVd8pqGHV{2y$v`a5N}a<(8V;Qa*Eg=ut3pFiP2~odE5X`9sz55t|jX% z`u*AL5yL?01#Hof0y;jnh$Pn!d=*e+`>I?#kYsp}5fF`V_*^WxOQ9J?X%HlowDSjO z$hj8b1@C1J2=ZBI?IRU=#zpq+i4O=6mjw}g&1q|In==a|VTjH_QF`M6q-`q+n41j6`tIgB+jScIB-OpSw_!Ax^Rnf;31*+^g_S!+BM zw`Myn!yau0XRO=T^Yh4(K9{6dSRwoyHdr|=5MdDic$XbR_b=FTlPOY%v#VNA#Km_Hr--|3Ud4IE5?A${ zhi3B203StP932n{w=&bx935C;gp=#N_n}Y+-udvNk5?>%O4)2dJqKEF*gfBhr6gbd z6d5uUvn?$mdwRyqq%^}PvDKv$#~;C$SW2Fa^^4VFBtbYPqg1c)0PhJTL2+W@YFTr& z;nL23R}l%7^RY}wO~&Bz0oo6{I%K477x1u%7Fg*t9*Q%LHk+VKth5Dqk^l6BhoKw5#w545%ppkb3Z>V^qAH6c$Y7Db<0`Wl9k0Q7qiTW*6WgPX{iS z8P#P11D&&v7+X+G(E^q!2gZVgt*G1LGsX#Sz=(?9A-ePAqNe2CRx|6I@gQ&~gE2;E z3k38UvaZ0hqolI^NhttB+fa@Cs(W23fCMw=cg|8Zdrd`X7*XCehRvP{4BR7NC!yF= z`=wCFfzG$1hxK?7Q0NfG=UMTSR7hE5k^jt$ZO%`&76a|61rm!>!Dz41qw|@lo?Z^*Iym5SBCkdtqEE~HKHKu%u z0>x{hB>)&NF)^2SRA`?|Ly7{hR5RF9B>_tA);SGQE&<)ABV&zi@PJ&36@y^(f`b;^ z)O)gqs$Ka&yz6&5P6uC4L>QKv7|R?Liv~)MY$bY_x&olQ7sqn_{h=A|{(t~p5csor zNZWas-Ktr3GuLM5euhr!%O79_aAqQIr=wb+1{E` z9@*Y`fscFum;6lp-QR5x6w(_cmk7v&eQt+(PvYhl%0;ZDQC(V4m(#s)b>&rIoM}fM zd0BbP9$fj8Vpt9Ja%x_y_E#kPz(7NaxK_1DoPh+I(@xNmHnX%_n+JTNqlaCOcrze@ zfl>OhPw_bHVOm~(jK!0H$%?w{7ltJZ%T%CBTyc5Kq&Brkgx ze~Cf;tOV*u9vM9#4xRCFaWJqE`#I?R-ji{{%Do`s^1bqqrFXOqMH#?P@Mp}EG}i}W zS`X6<0a+MM5i{qt}fp1BdrY?j}RK5$4f zDop|!#$-}Ljb#or3^vRL?1BSO2G?pqZ-hS$^E}&b8i=C1?)wXWG z09hCNjbB3B4}>j0Jhq6zDsEltn3@sd?v-bE|8cwAZvONBjo+MhEhTmiUV%g9LniAy zSLfw3GIwLw54*Sz-G1LYTu*4{ZCn5HSh?v%)BgP9i8E=K*it0 zc1%Xv=RVn;%|+fykI z10RHuk7wrThRNrO5_V-nowXEp-%?kQ8C@8MvBq;pPMxMb-y8qj>kbe5?(+U!%ktC1 zcV7RvQ5fAn<~Y0{*LzVp3)Zgc*C#Jw$V$Q za}8;QmJiA|=_KyEZjaf-88+%lmtSRzjIh-z8=gqsd3Uhx@aFrA@WRY%`OA|JGN!GS zdTOicUyna)nj5KAxLPo=y*TbIq}Uhl?bQ?8d-QwLsWrd#5dZa%^y5`_i|lR_0iRDH z_GNodKHxmOJzM11vSJyL`S8ASRjRj*-Kn{SyA?hko^3o-=Dz0s%b|ooSk;p8{pEA; zPVslW!W`Q4g^xs2&$UOx`!=mKh9b2?=^rtSUUwG@;Rz{NV3am*f%K&Ug$HcsuHJmS5|b{u)H(2YX5`_s47&1&m+!?s z%Qn^~G%Yr#Jy81K_hxQqX?kK-!1sp#b%z~$>CNH8aiglrx%2tG@%x)onSD)<3;Ua_ z9foTUw$g`cy-2(=e#mm1x|*`87sy(>y=WmRAlqaG*@-o_PCwkc-q(D4gT18FA8x!d zV7+!SAQ zx1Z}f7goc&WdHl%>cqIoptA{M*mk?o*;Lx|+*iLFf18VbKKup8-QakY<9nAx%v61< zpgdd8&|Q>YKeun3!_LL1ONLu3Q&p3p5t%g-hLc8#nQ!(Q&1P@hd!W+SlyHAwLpT(B zuIh@2=esvi?SzfEB}DM&o9(9{TAJQ zt`M3lb;>Uxy!_qjSILx~p|5h!N}A*Ee}8P9SoV5fepiw{Ei-9hE9#fM?=DWV>gGM) z#g(&;!wVokVKI7k{oua`P1rT zdY3wC@hx>cbF!km_F(Vk_AeWXZ;wQ~-yv(=SF}MzA5HbCfBoV%^Qc$NA78Hng&?V= z8Z+gshqrIW`O&EHXbJ9q&*Xalk+i0HkNnQ-{enAW->z;d(34C)1Q8-B5-jqO%{~xJ z3G4avslVc^hvTPyEXm9RU&s2(=Sg<2Z!fY2uD0EBha!$YP7JQvGhNi&-R)EENpNn4 z4t~QKWMaEYb2V}&J-~H6VI!y|B@UdL&7+u7H`KS@2B0L9tFW|+YscA&nRhG9ld`ct zjJ&x%t^TNx0eql}b;mQNYGqK|s*Z6B74FB3Q!snx8E|gueL#|D{gUFVDcKx5$8b`r zK_s0|vzp#*l`;(-57ig0sO64{m1rrl@#se$+;GzED20#7b|Ao6aX9iI>O1AHUu62!px$ry?A_5d@kQX;D36e*xqD=lpc!dS3m{*nX`J*} z$QD5!)~S`o3^De_uQt)yFp_Gs5esA921?@voP_(Vwx{WAd{7Em;XQ7FT(aS#CS*}r zeajyh#b}5n)=GxAlDm^*%{oakIgZ73S}0HtA~iiAE_qFb9xR;A`@k%kQPoa&*Yhp~ z_W%;u+(D!T6lF?3cV?tq&|+TNMFF%?QALnM;$b(?u(IZHjCvS1ZAvVGr(QuoJV8q( zh}66@MkgC58tDg?ZGd@01@`mcAK1iZoOXYVw~j>dHa6TLx-On}f#ByX46oT(NDV1_ zWK`jd-v;)hE;uoify>5)`H)+78-v@RVd1PY>AMX?=GE*tJeB&@R z(}us2-q7M?!0L>*J(;+K=9n4{!bbg3SC^h%hW-M<_|xzfr(+B`f?SHGeBsK=1=&lZ z>ZJvZ4iTnmbDC1)N%gQswr6 z@xWAJ)uSU7iYu1<@w9Mn5^;m17CZ+e<2h1=G{oTNF-xj9j{RIy4B$+cyn2^B|X6-&%R%R;>`g)eZ`T$@HFuT&7tbL7dk_>r`w{RiF)n=Fx zLbiPGs+G)o!urt@!wD}QQCc5U^^QY(atFq}E%R~5gB7XqOhAGVrz}w}09JHS2KFGw zR+RrH2B(S4(f{Z?IJwwRl%=CGYA)Vu`{r9P!c6KDfqg0 z+0(yqED>MOppS~qmCyy5PtH`{Yu24!uPc)Soik>gihdGC%pTrlJ>vqPX1GxN+^{s} zZ}O5hgJ~>&$Qj`>SJqXl%_IwXIX=HME2j6!)Y^ZYO;7klbCSE-vu|gc)RO3{FQ|4} zoSXEXLS9DY1Kj)i?L@S~iTCo{CbVgwgJJgs(Mb^k^@^1Na3YgA$4nIdT3#sPCgR^@ zI4tljoLTnvn=H`0Cr;IzC#1>Ko*Z+GI7Q)qj^V=vGC{Sfv=9rUxWkqJL|%!*_WkG* zJrntRAT{`VusnJb!e|mEKOsmgp1h`)jVpR4S444n%1YJtZ~`31XEaBjMUF;iD67}X zXdcU|(h+-WcI(){Oqck})Zj5P`;1SgoIf$UYDaI%p>h46;@&-h2R$N4j&i$c$4WR6ghYWA&pj73 ze3@&vDF&^+G^WVG9Nb>CLYvkF49EErgMb5Nr`7i>qOoS(?YQ=eXL8=lI@j$+@ZJS3 z5bsfj`V~2Gd5^InQRWvZ5jH;3Fz6Vtn5Yt!W*ICw^e_=>ddx^>DJ}7256Lx> z-5B6A*S0pry%cg8mVfU>T0F!krYzA#G~c3C^hpMjwVjD!QYCt=pn(*8a8XzD5>IeB zYKibuD-pUoGs5a{xRarQ#jN4A^-SI1uH8|1hKBZJQ3J^8w`%H&>K5}PC>9*_uv)!!-_)l{!CZ2dd}yk7(VJS*}0^x@KMLEI*m z3NN2dI6re~y7?L!chDm}Gu%6pJLOoi+dwC(QG0Zmo03YiQ%gHNH)HLwXl+ptxd3ss zKs1QNqG96uN{#O*ghuu@@Dw-Wi!nNgwF0Q{QC&2b$?;<-P&;I-bvDP0{9QGzHJW@- z(W)UHUO}6O&pal^&a+tuoJ1LRAUVL^8PNZ%xvgVX5oV$vwMC^-;9psm)G#-HOlrzQ zFrdQtZoQplg+U-xM;t6{OmOR6KsBLi3{8xi0{1d6QTqa5*~7a?aqT9gj>ifn3O%!H z3)s_xYLl4Df;b0$LNqZ;SOTQ?%fNCpe8waN@BVdo>3I*KRy~2^>mu^`3H+ z<^1k21+f7li|hmZorN{yrkSq8L5LeteB2(U<_=)Fe!3QV8eIoQ5yFT{!R4T0d=y^F zXVIB;zbKw|@JRs~{h$RJJW)jv*R^5(dA}neeqyG1OX~reXyY44nT#l~kl!@8Kvg5o z8dnU12{cC(y>~S}@o@-b$>L%li(-$GinUF`pqfF8mgAWvQOf{Mi1LsA9wa^Ha9W|U zR**Q~&U+Yfgsrg;Oi{qCBk(^s1&5Pi!Ktlrb9&;XTn7df@pfOWm zXp?V0gUnPv8I&lj7xtl5oX?#8t)&9S8`gbTSuW%WCAOK|$`%sF%RF6a^mha;P$Vh! zj*k5pBRfY~RM~fE_Be^JjF#Gwe^f4+t;x%koN#R~ZAs?WCg$aa51!7Qm>7u~YTqX- zth{l`K7two^pzY(tUE)VE(ef+&WU4q35%{!Oj{l>dL!FDjI1mA=rQb*dyd=3cT)w%Q(my^0Rwtx&{U+f0RE`bgN;npbBz4G??kC zk1!<8uR;XUZov z441m{Zpt=iU1o{gx+TRIel;|MMd_}Y(yGIJBggLs{dmKa_+(J6*e~s*9JgK6^1#!gip>Uy8}Z_xAS+ zw+pQuRJ+*&Q7CMqDMA=)!8*0^ zZQwA9^zGYm)Tp7PW}e_mk{qnbiIH{j{BA8abpC)IM#w(=KWY16XL`` zcoq2F?gwM>WpCEUPZNVZ?7f#cZ5ouIgMfe1PncUn+FsidWNMWGq0;?y2JU!hYIdqq%6ORbcCm=U^w|0T5kYQ7DgdMCWUohEAv^(vY1C0CGg z4jAK4aKgxmqKCRvYTm*m{#`(LeWeBrCC!VZ(ZU0!hVZVH#yF$kmiy*b%(PeeUQTXx z=DD`N)X}q;emyPUy5dxD?47`J0PQgcu8X)6sf^2|b@AMT!nbs*4bNI9RNhO0{>5rc zEszF_`qOXKxOIb_cKB=P6$LO|iV}*^t9P=Cp;s1J@w6YMPxOTisHzV`5zNf=Wa*HJ zUy36|L5fpioI%)PSMxg+Y&Mw@3@_vLW{hzfuiw5sfgEuV_rsB^68v(-Mb48ErB)t0 z2&KTY7u$l5zzSm&iTj&#EHaP-muNQAb#XJTd_lizarneABT<56*rk3C=A`>uo`#KV z3p zc*pfv4B*1ds%LF7j^_&D{k~jh*J_caUBW&?5sOxg-j#Gw(Fc|6g*jUt;O_EVa$R_u z*YcCLmMg6w_f2z6^QVTnxcf`(J8~VNx?vRuYMWLZGsbkEg??!{Eg-kc|Mo%U-J{)k8B*xmfD+%?vh zd2&-Sy(V|^yKlpbX{-7Uf$rSF`h`!P(n^MNz6ircy@W3g!<^=yRjwVa*|EO! zRq1HqCDy3+_Nj>lA;g0w7XCZ$54F z6Hw4M#oWTeQ6-tKHK7#ad1wAqk2pp9vaGn9!_DuvmGPK@l!l2nIz6AQ8s#zzhu!B& zRxSKYd?hzmy?5+o%YN0ce?Ncv-TdaNY;SJ=+FFQ4s!Dz8{rq3uFF!t;M?aflzEIem z+ahTgY5Tw>Dd3Mk^Puur>6;&SY zYb-3y-ek4xWlwMK+&@%Qyq2*f-}-nGO?Z#LxrM_?Wn7vQy>HomxcPb^(m%QFv6ed` zWK&LUs$v-QAL-PF2MJp?cdhB04Zp{G{5%@kbop{0@1L^0yD5{`9)rwhGJGzs6EtnGBasrWk*CRtUv7&F@v_Vrz@@%@vWc~u_BMCBNTB`3%7MrTHyYMI^Z%r)YD`18wbOTUUlD{_S5vln3f@AMjHVZB7@-KzT zU-7Q@b1YH6=X~*dO7g4vt%!sdiLcq>koqkq`wjWsx@SwyE`SbY`&)=y_J+u}2(w+T ziG<|oJBmsMr%s9NoF9AScGZU6p~`7Z)9@+VtX-w=ygh5OXM0XWckYYBq!wxJi#IBL zFP69wddbh0>`g4DCYvDNNH;ATKgcjFy`yjXGoXgD<9~yX<`Y|fTSoV-l-uHHI z(hYM)*sT3yjJJWLN#xDS_|{JJ+m_2qDZQtL)k(~KYK;mW5LA**%pDl2bL+BV|fidLEicn5jMt_^wG4HgJRB>tN= zzSZ)vK$!OK=45w@mu=64?|SBLy!M*iQ~S0{(AgU@$b&dO# z?O#hubsA(p{6Cz2t{hPRp{A;yY&^?>__nP0n#fV!^CvO#S1o#X$i8Jb%W_bWlug4M ziQuDi-^u~qF|2@U*s>0AHK}U21+k6Y=wmbQO7J2c)LctfR{}JO@b??44iJUKcz~0| z)O$#m1pY)&ol!>JPlCeXWOO~Ug~r^x4|h=mRW=-$P1dod-zN7_sln<;~3iq6{vLO z!e0ENKM|7IXP?o6XR4z^Btii4D}*kb?(mmB!1^MpOltG03p+h2d>5+);Uwo z5r7noUmQ5=`9>VhS&ARWCn9NXUG>&giHIdc5-4P+_)x%>&-u8`$fRvgkdAt|7?6eg za&$xc}6eEpTuf|e8&V7kxLg9wmboph9g$zxt(Xd&<;2<_}cQh$g<Y2vFrzej+iPDBp^m@#1HVU=xP_c$QoD zrSkKDbB5_&s=oT26vx=c@p;dNpYe1tNA+bp9yZ|h;BX!Rc?)s< zzRYwFsWA2i`St$Kg3N>6$@70+gW)04(yzbsE}2M3$1&a*YJixhkh_q&si5gBdemn% zT6s7GTYODj^4x>6DvySXO1;#H9PqeAaz8JgUt2jKzigMf;y$ILp7?cLKTV{%BOx!* zXc<-Qc$gZ`a1|HBugzR#RhcskZP!8zPU^uW*+H1QkJQ8pSlUH~R6Z%>0wm_CB`c+5 zNADUIc+r0%LYc9Euh@5>4<~^ga9JDM1gPYjjHj|Y=U2Z zfsoi&ojoM6n@J-k*x$_|fkqDR57J+FMo+C`n2y(5?g9vsFxKxXTGc`&h&#I*VO(!f z^woR=jZOsjZJAHOr6LqwJoJyWY=-s zUx@#rr77ZyWk`}tdGg2&O&3)mOxmFd+7u|nIP7^nXkgKKA>%zi)V1`N2xRcU|G-Yx zsiD}Sk1c&-Yklcm^& z*Vc3?;c$2cQSJ-U4cZ~oct5?BN%1?U?N|p6-2Loh=ab2_k>`o2v*ce zOwuF3O?cXaBF;19I1#~n6y>FAN1r$wUXIU^p%PyTxV(z7FN>HVjGUX&DJlVaJ%tYx z{te_~sBcr?JpZlNTrV)F+k7LT5>64(XQONREFdOH^M$K;Ej@)tky3!(T;85e-HiHC zT}>K9kkFj%IE7t?l>F30jkSEVi&GXEeYJLdorh-E?>CS;(GfsV!f`{fPYEUkm_}?G zYrK;_+W!_p9Fc8`rpSzpN8(A?8DscNrS%|W`i#g3Cq6DrI2RRQHG+G_tYo59jL2=RmYbo@+@FXK{y@o11`cs;T#e#r7Q7CY! z7RJ`Ly-!G7cR3%REztRq1cp8}dy=Rk&upMsp|yTsT^{#@!erB@kcG#%WF{2H^p!M* zzE5Av@R+(grL^gdW>J%HzP!;u98Y|>r=6e4P(a@+*CcJwSniPS6Gzq;a9TW&1q)8VLqy(h* zmQa)?MT)dYZ&F2pgbt#BfHe8t@q0e!eCO=`v9o7(cXsBN83`s&$W!j;y36~z-Y*c< zBFz+S!n6SCB;r%qXMiDJ$=Q*3y$(x$bqPdg;EN7OPUsouH+pSpF^xj})TbdCG(17l z^5a$|To8a5pxyidZ(>)WI`22%AO#ltLAe{|LY^OysSQYm9XTZWygLYldfH>)i?17K zJvriN#tKuk2Y9K1nqhFef`*EO220CN!6aYadhkHtHmQr(L)CHmW54M;0LT?BS@0S7 zEh-2dh1=!kl4$e_d%Dw+Sz1X!I_9MoxFU@Zd#~KIYp#REf6$!z9(5yIHsj#{D06%P zL_L*^-jzt-2M>1rz30h(vE;T>Hx5iu(G*v-f|T_Jep6VmQIq=8qf5ZO1ZpT6Ee72_ zSHbNqJP3db`+= zUf>4tN*w6Sv$SSy+~xlf@8=;STNmyZa|-EU<`k|#Biy@cYtd!ZWR+h zCa(bKHUQBD=wJ7_LiGzDAv*Ca{EmS5Yw#fj3~Jbr03}pwlm*xrQnf)Y5b(cuIy5Ap zu_f++Ol4TGg)&ttX=W#Tf}c#KK7IYf z(sX)l1yWO}ZOrBC$^dTFtX@M~J&z=0jW{i{z0k{hgiwTnpdh&t{ZA`>9QC;lnwXl$7WR zg0Q0|LW!t&w42@6@pJiwV7B|ZBoQ;FD%O}vXPYoKU%qG1V9!VT=aNP_0BlRA&ict) zq!;&nEkzjWL7sYQc-Qy>)0)}(kpMLp1&4{{>-wfNG2ISw1k3QPKX;^)t`PbWo%F_* zbF``jVA)z~BIcs)TF@@#B$YNf7*R|sMhfpH;Sk23O+i)bH$fB~85wmfIKFu*zlz=+ zF%np<0M%+)Uv{K^VGXi{j{*G)AT2nE9uVT80xE@C(b-^yXjM8v?pHYZ-|K>&$+Ia2 z1B1eIKufPlsO$s9i$Pio}hC@(>C_(~-k8{OJWSRUh7+o^R$)xKJm z>Ui4cbKa*yX2A0ph)tsXB_Gaf8J{oNU`^6}TMW|q`yE6#i`Sr9eua|P-%I3MszSN% z;nF2a1Z{Hbl(zQ>h_esCwy)6y0Ve|03Hz6emfH?qTVP|MS77EVIbuW)Knz>6h|Nd4 zn}GpnGk@@>fgVM_go4ja zstkT(A`B?+F@_xwgCW^D!EAKFSFX>Q*MfGDS?iH5p}UHq)j~!>B=t=flY7PCpjtEqkND5^zc&CZT;ig8zxshN@#EsNlh)inMvp_dsJzQcx*M+OiuKYwpUR# zQfI%lh_u4?m{_Zge5uRU44xVoP3ueo#2+ z6&NbdxfCcFY21oUtZE_$Dlk_pn2&{Wg33`GOwor5%p|!X+AO+vWMEp%adim_hA(v3(rRF!2D@fK3_w&8TBQy{pX>w+0B2KN1hC6` zdk+?-se{l*Hjs=sQ<9)v>mY3;KpyI0-+kZ*d>0i0v4|MV&-l0@BkuWPOFJGAFpadd zs__|F0`u~0iJFq>wHeq2RT(f%e$dTNMd*xLtKl+(lieO>9?uDIr?wH**<}DOjRnN7 zPRkUE^KWSC*I_qE)eF(S_Jxi1sE)Ovo5lsZbil)F%f0^1P`G%lF>524gH?nf%K%>R z>(P{{TZZDKF)NIs0-t>YRNvzoGCvP;_Jr>-xZZ{JJgBC@(>NbQDQ{(FGgJo1e0W)0 z_eN{~nK=4eM>VcHdiM`s+SmG!IRP_szoVbgUdseOt#4aqOq&4xs8ez6U z8;#k4NATW0yOlrdbX|di)yRyg>hXvW-j}1f%!pQVb)IYfcfZlH9$+q4a~Ut%Vp^S^ zC0Gq%-b?o1NaSUnUSi+E!kt!pYCLxYGgl0C0s^XeW6hh|IkKlmo{v9wwc*vk`#`zp zY)YHI>MD_sZ{Fzrp1-s?(XtVn`NeLupe2IXw((Q(%V%V^PGeHvhzGCtT{tJd-2XJL zgAFf}*}XQFSzk{&s?_qDsIQ1w0xGD%RN>}mj+QU~P%y&#tx)~`3yHg5@Gfna6YT1Z zEfk3fD#(+I*fg^r;-Ww3D_5eepiBB=^*=v)e&^ssqNOMxc*+7&gsr%>IpmT64hk0@+K>H6hXP$n2m)Ir9(%5$op;C=mcsO1m%$Z=fLlfOfH)RrVRC3~dZQ)8l zO^44(|HMM-mnVPnv0+43heb3n;z9iM7^og;i5o4J72I)Omhtz(&z|#E8vY5>SQgRs z;#1g_PmC7OqfYxO*p2TDU>-_-Is23{*!+X!{PTPL4BIE^-Zak88g+VncG=Y<^U4-o zh}XCh6NApKRqD72edQ3jx%A0sfYfhqtJbRQfWvXykZlG$`4t&fY46`X*nAOLzSd)b zgRiNleGa36{neY(I*%*9|5ZV7hEkD<-T}sve;z-{{~*#v}G(Z z*UW#r^yp<1uJaV$)r0wQkB`MBvAcGu& zfB!rP>+G)9w)W0W|MueFHwh+VZg#HD9#+;?RR3&FcqSY-sAjOWCVYbM`DdWY{;4(i zh|9tOXTM!We^a>Fj_(wPsk1gH7x4ZqDH1l^W!XGBQnx^_95yrU&p%fKGTeT+cw-seHe!#5nzr`po@W;K_#FvGlar=D zbWUGFPBu4s-ZuKMt$e_Grh0Z(*)+-9Sby}%ad|n@cjn-^U+1s?A3gPtnyMrw_5y)*lEE zU?G?AB)D8a+^ z%~Qw{y3({+Jj3!z+iA-aU;un@w0t4umCpRP89xA2**&wHz>)kl%yj|w;dz|vCbgFB zrsJGf9gqXI2)P){@4~+BZ&eP}WQ_CZE~HA2k~Q*~D{s19NP-%-OKwl@a^8E-@hJHh zfCOz_7}XZ=p@&`9m0d7aLxOEJGO)Sf5DH-6k~d~?RAK7Cg0-HX1EbSjKU@~21JO0~ z@OTgr1qvusOJawC^=cMrVHfZja_3;B##2?mRh8w1AlwZFr20G7*7@c7a0el(nH z5Zo&y+ZrUCvgR%rx@<;t!CleXkvJEsJAtP%qC>>xlW0WgJ!>f=ds`2@eTiaqF!b15 zU@C&)sN%O>J5fnK0#w7;k5u!+7jK%zfKjU$OftI3_9lHXI723=>J_#E$-EatDSn32 zQ)3_(AY0i}!OrLWyeC4BU%yG!@C`ggin^Qu#=;c(O4}y<(F%KIy503!LUAsLMqlnHscifh9U(hAXGh_Gp#%`x zqOrX=t?0VX+x4**yb!w2Q+pBG*NxGFzu;OB_lhVZ^HO18l++wqHscp&67)&AMiba5 z2NQ8VfrfN_H)2-~6I=UT#yy0C(Qf4Q5d)Jfj6O%NQ>4~}lnWreMk&NqMS=DX+LlpP z_2$0P6r4);RC|i!; z%Y*{=@|3#9I?3|zBvda*Z1j%#C+gX z6z&x>%RyE4&+L&xL#CN}3M8Oi$5q>M+n@5Gy4chFBg^2aP7R5}_bM6F)bN7_uu2h3bhi>gI?2M$r9Yr_379*B zHv3ryWIpSIDtMIc4otU0<~C*&U@xw}U0qc3`=5(|l+tr$fPf5`u>o9~(jWzUkUQTBL74!mi+-GA zqQZ1dT?~@O8z?B>Kp|b%E$~j)m)Y!`j&WqyoeY zx)rOGOAQ43sv%U>8Czx-JGiUmMK~k?DjIk`3#yNEsCpGT^7@rr##pv1r1nl+%9}3M zsp1rbbh)hs2|%Q!?z+6B&dcg6T_RSC;c{2KW`IrWQq|H+O1OO+03r6n8qr49bVdZf zZy;_mMrc8j${8*I_ZA5@ikol=EnrEb*0ly)<-%QaNWm`ivM2)!X`$^kiks-yr2r6c z{M6B2sLar$#8ocRPg4Kc!3W-S`nuGxn}X;4CN5)OM)ORx6p>Il9@RprTWZg!6|JX* zKI3Z2-p$?dnF!6r7gU#UKZxX!(gx1zDJ->E@GA1HO*;^Z1%4MpMunJqtLm;~4Q|X_m{h_jo7I?W7C5j+mzfY=RJKJg*RB zjzRt3$X%^fj*tyXG_&SdXj0re+LGCt1Wc>#UB77KxBR1xKnz4LvPbm|*# z5)o$h8$~XHnqK)DUjDh*oe9?rGRAisl1~arOD)wDA2vlBPK@a?%$^6wW7lxq1Dr6$V@%l zqd&crP}Qvc(1xDaq$dUSij(ix#VH(woodxJC<)k%^C}2^(0$KCk(XMS;E|IhQd#vw zaLGqemp)=@m*l=O03SJw$)OT-Ntkm9a4a-^Jr1Uun<9+iSVvJrJIVkkmEy&7v5B!6 zdg>VV9z_1FxLe(-z5$Y`Ps*S>l+fZQzc93((~r(2GXSoC;`RC%#G!XaP|K}gB5*() z-Ru-^;n5iON^n*kFj}Yz{ukI0YKzai9~HP6K$xND&aDBi>dc(^lJd4l&{P3_xrZGz zVs%I);1GPL04O3*q0+{_<)P3CR!X&`G&;~H2_gd59Vf#L29&UpG4a*cO)h0fXvEPP zk^vM4t?8o`^!Fkg>oEtFK1`WD6KDRWo?EOm5&0A!-)96hNp6w!JMwuALJxcEQtlFH z^X)jQfwE;&gxs+%=)>&^9X0PSbeVo0y&2jVv$+_CvpwXSt?H2+9WE;T3GB@jav4$b&LL3rpku~&_Y^IoZ$#?+hYGN6?5E}c@ zoftU%0}=};UcCM%r!gq9>bfblB1v4n8ie_(nI{G`KYL_J8M>-=ST9bmcw8&Y1ep3< zlt7m$(&1<1ou_M?&&TY(8!8VPC0p947ne1<4UeVw#{czIn}jaaB56z#52 zh~O3yEI?z+2TRUXe3ZWdR)!fToz%fL;oeYr%;*R;pfjyfFcT+p?5sU8!G4+=4hP{7 z6bq*&Rq%6TbhcrkdI&>FWnS{Zb(MJs?#~LchfikhyK3qdkzjSWg&zQvULhS46^x0^ zGAEKNqCls2^$@h{w=hYPGA~LOx413s>2r$GqQ+mvV4=yA?UJ5*Dk!h1{7+%wTCEK8 zpTooy&09bRezpGih7>s)a8Ls@_Ojmnsw5lMOskYCj0oti48K3X;;DM>KdTn02?1tO zU_jymz-X3M!_>Wuz|LzF?JhO==I3G%%^|qehtUq)`hYcI2t{G)yt0!YV%Vb}^$~h% zTUVmZB(7YRNhuf5kQ`JN0eEFy!d=}vMuAuv1uc;JlBj`!`;xqpTnZvwJjwHJO%dmN zYB%8IsNBoi?3LO|*wQj8mQtMrxc$^v16uTrZ=vMGh$!R4izzrl(xvIf5YxDK2FuHt zL$j|AcP($a>xC#+gjIb>=P?oAPax$rRJJQu5Hb~rRp2I<)W7o}TrLN*0&f_Kij8G6 zp#!()s!EeGq1lh_WSHs@12PN5;nKqTR!T}$UdjuiA4X*NxisO8^bQ6kS`Si4<$Z+n zsJ?wrCaJp%!dA7fJ7F$iS_^~lsTQsT-HFDqEn8cP&@1y$>DhkXnbju}zTI6)=rA2# zrC%68%h!AWcfw4E0Z6?Ns;u(ow@!x>*dOm^UM0ka+8|JExiiJ<-cW}}QXce^;9xTV zFC^)eHc+Lx$WfHf!E&$WT96}ySu&Ug>7(9yg!HbfypogEkPJ334tWb&TLf?BWM?tn zeLVIfFFB~J^_6y-L_zoyi<4f-uDKFD(YFJxVqhR0#TL1!YKttPYhKrrWJ4>LFit+* zxonbT1>6If(#W?!Uuk?JLTjFiURhb0ii8+Inh5=QGDup92NZC{#;V{ZezY%FbM0FU z*R%JfCfQ2UyaUARNp!&;IZ_G2N$fQrdJBhXfaBTT>@Z{T1THH7+5R1aRV6FL`Ns2c zND3iM5ClM-TjgaBgi!CrZ$@&!@R#By&httV0c1CIat#ny0Bk1QggY*g22U8ADbV|_ z0q_$he3V);dIPjSEw>RuJd-bKZ0^&#vaQvy@s2FolE`V&hCa_sm7%v_xOSORz~wu2 z-sZA-u7Gd4Ei-6`9O&0$_CC$@JZ#-yLIc|W#b$s(!n{`NL4#R=hpcxxwBM->@X1l& zPPq6m1zIq>bsr?A{D_&MPU0ub-Fkhw^M<31j1^YUkrVQEdWF$QUdg-)`B)bO8FMaY zTjcjV?KXgeEGdIq6R0U+{gzujY?q^jec(nHoSjy!^bOKcyySK@%DB%iGf)?Yh zD1V$3OQhzLb=lfg&I;jiJmG%XX5~5MUT^bHm`C5dsSYKABoBbO z(_PB|v9hgHP=I|M83~B@v+FSs6m*r~-AL#VbAhw0auD|jwTB_1+!gH5Hmp(phP*2n zut4gE30-F-kVe3?RUcIQ>yAp*Xll}jg4Dw3`*Har=dlXn&0i)OiQio_h&^3n< zjoAap3z+i~8S~Obu;}}@f@9c69EyJ@A>w&~y`!lNrrW@(&Kh8}6v`#_8Sq=FKN+#p zOC+5j1SMg_hwx-p2owM`p);h(+>C5xEiPnzY3pes8K*rXbjA_*0$+1?Ky+|>zp{SiY=`tfa-gxp z`^neP(aJHiZ6>q&OI|Cd9=Dl~LgtW_5v{(zes=J#OINMV3sGskjH)qqWVrm9Op(nw^g@%lJT)lO%k_F!r(M zZbKL|u%Ar&^)&g|U!*rH?%Bkv{KB!~9ziwbKWt}ikd-Y3wK>tDqPhc(ZS)74xI}My z<^0OAPO~z}d~!$07_nn-(bmGDBTHLxCVh#UEG%bRhjvbPari6m^J^Hs6uQk>xIGum z4i9Rup1=kKZJ(v}SND`KrKw6~c5z-9E<2xiDvy6+Emjh4!*r#}`*4#ycv-`8__v(s zr?O@Ut>dRz(|&;-T%j8)#ha>kvKE8N+6O+|H&s7>VGN18J^0&;2U&G3<^9)R_s!(QUR(`3*B!F3xwq%sXf?UEDDXwZ{~qfPpPPA$+czH; z@4svMP_pre#Tk4mHWG2`9iUA!ZzF7 zKfjgH#4S&qb{~qSG+L}Zk#VV%y*!q%y`E9>$z8`(bD%~m0e zjMACPuNk-=-n!{R-$Sfzm&kA7S63s9<{nfRUO2ME?%K~bXPNmOZVF?&i=MTlg@?Iz5D6lW%*N^7^kS*Nl^RoUM5m z-g_x0OeLoE{kK`!`XeowllOy(Q`3E8VQCV`O7i^DT)zPD#QPtDe+2ZjNW zcN}s>4u(a1lb>%;mhw+ocZ_T@IeYMG*LpC*K z#IDA7u>l_wQaE({_HM2AwkFF=jbpymUd-Uk5r*7maI1P;HtS2tJ;6UC`6nFA4R&uT zSgvQ9tEU9{37p}lcb@LP`)yd|eY0NYy;uLkzP`XXw<4)cy19MlhPL&an?hmp*RTCi zIB>mep9ERn-;_G-o4(D$K-FBDhpXUdnuAG9)P5IsWrsQ^RkFtvM+c!c_Z|L^q3^D|nB+0VN@jQWx}O-g`fJ@y!P%QSY;;Z1CuBP{ zrTWUo+_3Gi^IhK1Ot($Xc0Gr0{bQT?F$tDD!p59UpaS9#@ zerOh-)^&%z-Cm~V+*MX)r{EsT4Yd!?VAD^ak{Ktb-t9dhxNEwhiTbxtVVl_*4U7pk zlj@b<$|?h9uIR1kicY*b8YjsvgBsF@_sVpyzWriiEF%w{xD-*W7VhPzk#;3JF5^vR zIpQI`;ZoJrm7(qGgrPsII5_0{KTGZZBiZPGW;_M~`=IpL&E4A7iS2>C)8qfrT?j&m z=i{gLf5n>r)Jy)^<-gK^y_?(riO@s%;4^0@cRRv|2-|r$IXXFe{ioJI5Q1Es-Tq1@ z1Xbn%P<4j>?F;^&myn+QoALp6;P2yqOGt#%KsXClk9`O_+ZlKeULrgQ|F^gNm!?OM z&%m!p4^T^EPAI4Wx)@;_!sEYWB$j`@@b~9`lacZniHKVMO-5>smgZTlm}D_JF0G*}ML4vPY% zw=1JLvSV8Xt1*}wXm(@QUQyuQ=z@#qeC5emsB`#PKwoQk&%lX*bA1yh&!0ONBIe8_ z^?9HwePTU3cTIMUB$U1JjvGVU%XKRyeO2zUgK2IVWdXm=PK-DY)z9H?e~&mjvyV2g zigJ{|LTD#%=y7#r)|vm(i~8ac%gnpgYScNSgY=jTjKsBXY-|j@L*d#3I}sVf2v!l( zX$9JY?vvVODBQI63kMLJCS6BBl`!=FtsiBw*vDY(*v)R@5i-91Xcm8nouHVlVWPvP zAc*OC9zseO54=w$e(_b@I>R!(NS}DoMjeDJ=y@Dc3w3gR-5yWrzUGn`0v48{z#2|u znS)rjwC7G)6aaBn%pi9d-?P7EE$YgBLrOyQXrcan=3gElDb)V=yP|PB106tRmb$ll zcM^Q1Cr$6F)Xuv^P)qFi(WzO7*yL8aRH4Z?L6gL80GNFu$Ncl}GC~)a6GcvtnCgue zz<}mKWRZj`samg6^d$P}W!xE+1cfyP8AcmW^QF|7U7N#wdqypw<2p~)cJ{o=SE{Ou zz23pE>oiZPOeirV3By7AegS$<%jCV1;L~Ory580Kw4}cLi_PvXlp~wB45dfqs8-R+ zgO7dnbfTlPNpX8+TICFABV}w44V?J$qu}6PH>n3Gn zd&3Q(LFj!sApT%Q=S{rHiMy6c!9ow&LfHzx)@*pROh03B>E;7+<$@>$YlLLi_#`#< zYY2uVwWG=)-X&vNm5Q%N8LVOx1$1?YK<7QNQ+xKt(ym&KM0;qp(lD%;E0VXn{2S^0CCP}D%8-zV@o`(Ub=&CDwJ%1)H zV@xz&l>tw6{4~GOL^leEiMB)cKepVtaVeFV2WV03i5f`kDS1j))aNBhaYwyEJEMCq zk=t}$XZD5Rtd2(aE?FZRPFgd{Sms^@z_blL8-1n3b{CYiB1i*Sa`;Fl1Jj11 z+H*=2g^cs&taCW}^s)l2E3pb|n+0d$Ti)VU`UNhoMxSNd+C0+}Ps(^HKDvk!k2hZ) zQjMrgQ)MTK>(Fk#ad1tbKv3MhYZH}IP*TN(!bxgmAs!}wt?7iFhhHRbvss8dzbtn_ zd40oR_0`m-06$b(BIIwPZqEB=S1R@ndk4^e6)`N)>-FhC?uR z5OD`J2-;e0dQJmnK0v;CcbMqWRdhry#08__-Cx7Xu|Z=~NDtxD)w97ek8OAlhp z;UhJW0>k_u*HpnwPVqld-Sr5*w25~sL1=&hhA+=YsuIOGOuT{aqAUHofEWsPKsP48 z3f8m-Tc}BJ+gS`k6JZC?U+Ld*#LB(-5^zqbp`T9dj`Xc)I5P63dSXZzmVEp6OS%iO zY@o$(vF@?4gm!7i`w7MUEU1Cm>nQg~TC$Yk8l7zPVAR*aXtRK?!zZQ0@VR|u4wltg}q<>AE_0oxjv|Gjk<+$_B~NTzs@Im zyMM8vfTG97`Bndou*q%E_w5(h-H$cL55v!nXhz!MTeSYgzS8Us%0fbd#@i#!H|EZ|J!Q1$wy3gkL&>~SNAHd`R!9b#0+|Elx$EzqUAl&f}bShmt@!_S87qU?9p7 zbX>L7RmQVg`_FkW*dM8+1uQ+OQrUg9#Y-0qK)?l!g!)UdpLFWg)Gqt!VrfvksNu$* zW*E6n&CQL%nUI;}Z;*gixD0f2e@7THBUqF`7j_$uO>A=TPW{lNqW~D?xSFT=%C^YC z#}E7mz<$4fs@>?||6M?$6C9(Hnvwz3RP+4E+ZA1?#U<`i91t1^cbsShB}4gV9Id)! zPA^>7r2xOkHOECXp%a(yh5)evg(`7@w&Tp-cf72gezlEsP8hI1bPXbbM}aPO38Fdu9W2Pn_z`k9a-!EC5#Jo;;W-z-3hX9+Z+q<7dYi zv{q64u~)GkpqE!{=ohbEcg!Yw!{2QWs=`2@^OdUS1tgY@79^$Un(WobxhERL*Q4nY z&W0~afW=mO1E!;$ZAvxR@O%bK#&vyUYcl0+LA4r@jO*Z_4|;0a3W_|~qK@?&x~6BR zb{K>Rchld_(KA>Qho7CRiKHv#2`SOgEW69I*+zX1aMXByL7nZN=ioxGTqnfmFe%}l*0YXoJ(Jp&*syP( z4Cy{XaW;qrEtYHBp^-tPQGcfZEv@ON$*2tr)KaY2sm6~41s zMXF^gN>tVKJl1*{O-p=l+^1Yd;E5r@jJ8$su`@zfK%fV~tf-;?yYD8{=0TbW=3+zw z6%ph82AlxoW>TmYbgq-%LQxUmQLOJ00<3FSF8?9`Osj=S1?Zh-gV>-8hBUXjyPgU0 zGKSzgk!Su@z9(6tkX%>`BY7KAry}YTdRquGk7jzHxT@0Prsr&{(>^p76t9TKf#ZZ$U9_bj}Lf$%7fIKQ6KDMT3e?fk|uvdTaAwYG+!ndn9d|% z>d2OgJj{8K>W~d#Cha(I3Mry*N%W$Z0?fk8X27lD$a9pttI|0^7AeeT3USfE>dFAM zh$_?)Vn8pT!$37+6v>LGjfrGiM{S$#2^b#JBFGjTg`mUVOx#5^*3s89X*4W$@geAIG;|(5QF5?B(u9&a z2)z*qcJU-`0{2TP=nxVn1o$j7!d0n&u!wYxz7>sMcQhXmYy^oSJqti@)2b>>O(JB? zBnuS*^_>I5@rG2PdFKQ@L+4<3vGtENnq5A^Ca??iM-sklo7jC*p00 zJGUKic-R6dDRuwjkaWHQbi4Lk?AT)@I2m7DqsOT5xTf{f#14h)`;m*+<~YNum3f7o zV-tkc+zSdqlahd-aD4RU!>vAHZp6(si>lPZwT2*F>${!H&Ac{BOK4z8tQrME$P;K3 zsPTb5d4j4=JvT+As-X93&TFeKvbGRcU`Pj49~Uz1%0+j?x#~ODXE$At8Q}}AKxc3# z)+TxbzGIx4Y$>lSRz+xVF_FIZvY8GTGuY5uyS@gs97F6fZolI|jNHq7n2gdgL^(T> zv}q-TXDRq~@p)go7KQ0X%m(&8P^^)<8U-QZ^o5j+`j|iuHpa&B@t0D0iJ*5e7cDdB ziJ%~8UBAmg^tPc`-+lI7UoAgDZRNE}Yigp2hpLM^QUQz`K^4wDf9Vj-dC>%Q70aw; zj_SqL)R6P3t$-N62*#s8A!)f62jmWWyDQyTB%v4K18F9!^AvgRv0!M^^U41O&x9+*$_oxmGdT&cS zt+y)_)#%xblvoaWLo9B{3_O;#Oi*y7(G4-sEAV%x3S@d^#EwdsR-Zo?G++pg!ld<~ zdAn4dg^Ja|{w)az@C9*FEK|u!5bf(F?1K;A+2VBsgKSQHQzHv~lo52r$qdO__$)R7 zWUo;OYbsM^+}k3n0jH3jC(QANz#;|QA=!}KWIUsb9n97$W8&OUS~_mVj~zHDke0I| zgbs*`u>aLZ@NaJ?T{isvqOZz~DPM|>uB;D~F_jL`gcj#+(#uNl0>Ldd+8NpEKpowW zD4nNhM-1#+6@Y=bOrb6`SmMpbz5FV)YL}`&8yMLa!-V2e3wzO#>0}R+2el2yXKypC zPds;fuF1~gV6Ou_hB zEDC6lR)|nsY|rC8C?R|r*HJ}*>C7=&Nl~WMfrowKpK?!C6WQ!h~M}uf}H8!85Epnd? zj?}G)vQu%Jz!P2ZOH4QcLl|A;kGjb5vUyJaOf8FQ2}mtFS=6E4O?8_Zzx4 z9GEY+)&4F~D1m+U{IpD49L6@xP5#j9^%ysKDTZU8^)|?sfXT0*jl^$Qu~7*89px#3 zp&g@gVg36R=L+h2`#bdae~T9*+Mrb9oRYm^^&wDg=3KXN>z6Ef^^_B?Z=%L_F1y0@ zQ8&mH4PS!PF{4WqU8$W&@Mp0IYH&%2Zi3V`UkF7-FBP6;re8h>BH1yzGjA*Tc_AaV z>Ut)Qiq5sVe|^fxfPQ1D-^hr(r2cos43}Q5eCHP3n4g%wssFjw)rUNIv$SFXa?)Er ziktJ)Zy3f`LLsd_66zIt5HdOwYgtyhSLQu8)=IQvACEZx$#r>=pC?=4ZnAdoLsfwH z%KHG9pKNj^1k`v)KnjiQ8;`zpww<8zE;6{AHH#OIAZfc&wN;U~{63 zO@EtzXh$G$kfQbapOZ;1Ju}ormLYT1{lu#J{_wS)lX)dE^&PXB%D}nkTbpOUU8nup z%?tG}N81~&z5Q_ftsfFK!rODaw(0%*7p#fDy6X|cVS!-y0_lRL#G4}#!Hhto*s(u% z*Y#;jG?Q%;1+N8|Ev^kzah_LtMXs6}Aes1Vp=qHy@%FV^2@$h93#(l&X%*uSUA_nC zv@jN0i>OVkuDIWL#I=-BKVEx%DKyYjvu|U;ckhG5RY8&Ju*4s#xBA{$y-#aUjp)E3 zFg7P)W`&n{<`QjK8o>4gp?xikH<}sBqk0lsAhr%hP0_a}4O`4aG*33h-Mv$jRwh$5 zJNN-F{p|%CB%)|-13LDmNQX6|I_vyEC#5(`%98#@$4@~WTfD0H=bW@6yGP}(H7omY zl0rO&l922Zg}ZS~hN5YF0+A!xM}0y&yN`EV*)>dP3tJbFVS_C)&mG119++F{hx8a@ zTD>k+MQ^YcrM!y3?3L#vaGkkp(c*tS3XE4BvRrj}S!rq!UfyhOMz{2EPnps}lCL`N zsQ*V(>dcvMS}ad?p@^&+wppwRqT)53*n&7-tj>ep=~(b`$oBnaEUk9>JLT@Ay^_Gu znzb0S2EB}U#2?wgh`W~p8>d$i(^Wk&e}dcu_RN#+myeS>OSHGs`D0uExN2oIe~Q+} zSL=sZxh60BU&$S++MZ?;8vCJd4v|e=`sOe;)NC=mcT=s;_{Q#)xyrM)p(w!vbd!2w zPjJ#gb^aP`BP^Q|@|t0#`Tkdd;I*Zm?@A|ifu|+BJZ7OMn}*3e(Tqj)zYS;7twhFd z_$RMn3N~eT&ub<12~+jI@9!3wU}X-BUleJ{NIrx?{j1!aDy3L)S=Jpr<@mv;cDU22 zvnQsH@E?EexV{NUer5aA=|fJ`T=Q`ogMDR&O(LsJ{?j#uTitI?r6?bqWge!W=>j~` zY`-~V4V4U}1~yLy&TO2nMXZr)9ye>A1O`lPw1Tw$kT~BTdo2ea$A0+h_#a$p_rk83 z_lNzduHrQtgo2u$-={gzDbui~PvO&xMNvPSAEzaaD+ez$?*y)VEEjHRx|(%bJs^H$6wh!q@dggr0D8fq9^a-%b^8v-0@d>CA@L zP&3bXT&OuTtTjQ_=k(NKpc#jIvf7I8SliM3m9Qge3e%=qMudo1Vkg|w*TmMwH__Fd{Q>3?D!)u*pk20iZ^FbheTuSeFL;l1`c7@ z#~J$s=YHGCz%dD*(zhx|ZjIp=YxkDc+;whL?wOZ|Yeum7EQOSkbFQdPU!S$FUQ$~q zdM84@_Do7LJ*=4OM#y8^tnBQEQPo;kVCk1nn(`NqG-n3a@~y7RIo3C3FSEl}D9sHH z_dU_s)5`QD3lBY95MNPjY9wI_vObNf$w&77X&bhcuL>l;dM2FLSFac%@A)m2R4}1t zoaw6C7kK;w^QLG4jEei>%8fTFp3w_phe+FuFB~IiE>q!^yB0UeeMFm?GNi9`)mI;E z*c$N53Z1mK+rK+2(NpQNUXQn1qgGhblVMIy+{Nw8D&#eJ-|k3p3%b?RZKPP9ToAo%yiJLivy_{5_g=DGj`RWFKgFk>W7ZXh zm;^*>;Dn%tX#V7TIUUr<{|LOPfCuB$!L$~vmr#2DA)GLUYty^iA_*1{o zbarLeLg2?g0>=MHoFiyiZuYjW_E!J=IfDA-r1kW%kF~2CL00)U;p?x=;`I30-%_V5 z;5`jzS9iPr50$xgpy%NK*>Ujvzm>UvI~fGs^&cHf)rxQ_|NTL@eE+=r@Ue^Te-yA0 zq%bRMo5vnc|I>%=>i=;|b$d7Wf0bGN*Pr~qpv)0U)&BWT^C!-3ZvTAxzhpVqe_jI5 z|4o)t2i0Br2mkt7LUq@(IK1Aq=Ht-;4f7l!3f&8+!x*w)r!UWB5&3$TP)2h8)PC0` zUh*K*-;v*&w&q9tWcW>eLJ>eVci1cP;(7IUEFR{0H0#ZI5NQ(zjY!rzQpK&HR^I z8V(m@{RddfTF!D@DqbA3sgJQm{YYPbhlCukia5(+)tWN!PNx#*jR%poyTfPG@2r>A zD5`Eko6gRx2WpH+2Aj>M17b6Snp%CINkmA^YsAR9P%;S7*1vZsswH&^8AW|3&3KMq z*mv6`%Xl8ri(pNgX>}imMPeH7Y5>pi6ItaD z(iBZpfy1J^@5>t=OSFx>Wnl{J(77<%1L!?hE0|EZ>LJQb1{q`kEA6BuyMRVgfhk$m zl~=MGE}uR$o zPyq>r^bu$Pn|O3rNTbr1iwLICtV0Ayma_qx59C0muSAZ~rIwx-g9y!-3`UR zj#9vfAQ-D{1Pvy_I9^G%buR-m-Hb}fV8BauC$mPbPsHY=-C0krf*Z1eHP*;+c*dv% z5$u9g#f#BTz|+IZ!O*~9+Y*7V`}WziW2ey3=<=m3QZ0HOW*BSSrkW>!Y-+hDJA5Be zUBh5^K83F%Zn#{j)2w%Z7FapYAzCX__0%pg!H##%DpK{#$&(&X*9U%9$8eXE7FLWr zi(I^p*P>VErcnhaUkajaSi0G#HX=E*f@G#{Uo)Y*A2i+#Gbfpq<8A~~7?Kz`Wc?Q$ zWk`pd42J6~StEd4!cDHY6jXxvqOVNW2}+Hk=8=>WqYpv$d8MvBk_LGBVRPZa9+w^! zZYF`$@xEI#DcHTX)GwqF^FWl1;%(4vHNr^fO-d++4K=52t*@)-eO)2NQ|_J*R)1PW+s5;jwo#ImYdcji(+Se7LoH{ zo&%JtdxV&0>F|U5#hWBB#S{$t$4|gdC;10xm?{u{XR7;xyAJiMPBil`K7!;?GDdAz z_9sx&a)PaT3WB^)V1v#=OcG4=fQG=^z-u~w7n4V8fiB~>?x2?>5#+|yWkaR(1=!)j zp~fP6b2Gs8@~Z2ff8jB?1DSBki300$ zZX&2lmmFuy9DW(n)sm6W;Y)%NN8NHBDHlA=z7HCY(p~-+*FT%PkJo=!@=C-4SGV^K zt8KrpTA&=2!F@{-Q$is1mT>Za2_eO2Bed5k5PB#ME1gDu`M5DTW&VIN2dMgL;m)TQ zYtA&Y8G33}>BkX`(4bY4#uUkDP;w}ar1TAvnx|Ti?_FtR2b`Plk3iAP$Q?8zkZ2Y4 zp3NbMjd@kQzbPHNFP7PWYpD;##{8XAXM?-aTF4xV&Es@8tTi_=yHlPVq)?BBlawW3 zbN8tb*YJMT=w4wqd3I+lanRZ#36*ArWn6_k3WY=mm>o>fI1Tgt5>EFDhnNguhRm+R zQED=mBvgoV&-i=tySCbQAbJuiD@Yju=FbQ{-j30?`>RvoN_6iEX2=8FPU%uQlxEQ(-Ms)o6qJyXMi7whMj9kUX#pwekWQ5rML1*me!Q;j z>)Dt4+h?Es`{OsROXp(FF~_*act$_-dF~o)L?6L0549mZ;6n|;pR+U55Hh=jcX;Ik zrJkY;DuvQhEiZSBtiwkOuJJqrbpigM`#_O1%V;eWl|<-FO_;$4#!^CA-TBb5#R=5< z;E0LYW&aS@mINeV*rSa^2Q$9|e9bOnN9Yx6Q}i-0miDQ{fk9BLSdlExjw)xjoAoeD z8kk5NA~0#=ViD-qL!4J`%KNJ~9Td2aoek6!+13D)+*qXNmwe4m*-*6x9|EJGtYs;{ zSJU%~MA)EyNG~-oHk`9q$raM5Pw0^zaXGyBOJ$L(LH4>8bKC)m%SJE?vE=BHg|9TJ z)H<*{tdY~7ZNC-hlcT*ZTdfMY^tiidGcA=SXiZ~J2z^Jhv4nMnmI;U~=10SoM+*W@ z`2#x&9XSXdr;(uQ#xLLUC+E#lTlgWKm_hVZ|05Qd9m!){UdY_junM7?es&X>v9M*& zN(zM}?Dn)YpR$v8xGykNZ~CoKWm6+o?(_WCOFE z%g0!|g(*Iqc8np7gn*Dh(lSE2p$L4kjx^#t*Cfo5l|Tb*mESFq7>>!)t{K#0++9s} z-jgBG{s!HF^_x}y#r2d-4z^{OZN*-Qp%SdiHOep31sW&ZYogW1VEs0rA#j!h%%5VB zfY7wG?aWi1SNtpLgWs~_2*smim0^Uab*qDebeER0DYXo*>&0|ozCaNIhO_rr;EQ#q z9Pq(7peP8afhKmHY`U@<5z6{QIPpLa+oXCEpns#=i@w>P1ZGXgqY_rEafxX~0=H<{ z|88J@uRb9d_x$aJz*6I~oZP+WfkoU+w4&v#EP5aE;k4oeTt;H+d!Io$ICh0MsK|&;)b&-#wlxGxYA{>5qqcLfJUVx#X*LN$EUW<+La;J&7T?YOv6!ND1RstKP z+ASGhqXhhVV@ej)V#XkfS4-8d&x;(dDr?=q+Cdn1jHhEJ96Kt)sO4vz@ zOVvUYIepz3tnG{Fiy2vr2qigj+1fYQ2NDf4-gBq{t;ZG)kVbHsG@6`^_L%rDH% zPzfdUAj-;*jujyV*m4yc_yKkc$dzXy%V6n3YUnYa76FWmqi1oi2Z{VljSE(u=T^&k}%dia?ZvSV102< z&(ZB>w-Oo^SU^X_WCIGR$HPmbdK8=9+N@)E^J#=q+F9B3u@;OTvJ$MF>UuM6-S3vb zcBNJouSN_>g09IQj2^m=FE2zkECg`Mr;+$+=e?(A4CL*-v^jT#uJa_az&_1B>OcZx z4K|Vu>#=}u)If8v$PWiA7|m(pBaTTtN@S&&Zo%Rrqv{omRHYcSp>0jJM_-EU3-lq4 z9Xken_PYwyzN0bW4|nscn7gvGs$xH4COEk^%h$5o0?Qz5AsKtk(wq-{`ZduQ^+doZ zw71s~3{sVmEw45BODW?50()T-#neJ`g~%(!(nWv0n+Hgv1PMY@fUF{=8XW7Pppdp> zFtamGwL{)3MC^<3l;rSUFK8?H&mA+eq&QUCRySFX<0Y$&$!1EL$~JF}t0m+gh}@wU z{-RHzgqF`bsSYjmg1A$JXCqRto(QcBujmae>wn^`u5D9BBMCo`*IGb)gVCZ5>4 zn8V(Z?+zJlj`giOMV}Z)jiHztXG5`oAj2$Zf&H?lWB?c9YWx5S+A~&f3bQ5E1gX`w zFHA5dB0VtMj1|(Oq3Gs9Bf*xz>3zRG;SXOOq)QAA4vLA8Trr7QAm^SHV_(L^wP1w; zSwY}Cp-#W>8ne!mfNmvd8LOqdT5yRzY;#0IrD(0YLV76v%m8>=@BM0&`C~yy_vqc& zp2j*EaAIfYeOX7`YA@$%sM=<-l1TVFcSx-Kd=QgaDh_dLHr?D;2`LF|hMU#T&+#=~Hy! z^ODcNoL!dmPHs1eyL34n4FUVs!7%40A5~yhIkFx1F^0TE`cVgL4>X__OhJPW`>3u! z9$5OuzHa++Y=KLM23tjD4FjhH>26L%%m`qqG5m3g3~>_=Y`K9y(lfRIclzl){16EwYl z#@U_sabbLC>$`l^{J95i1oh+B;h&!$Q>Adc?q~BRlHYxkRuwew;dfR{&Nk>i*t1C} z<99nQ+=ggQ@4gVGm895}u8vRB8*e-P>X}obz7b+3-RC0#@T95l{?cLn$$^7_QJhHLoVn~RG4n9KfPmtp2gr0BA3l2OnG5%eRc^(a1{*V|PgOummJLx=tI zFY%MulXHf?Hbf!66|WM;Mt$p2JK@ALV6<&HHxZ9*IMZ52v@upf^-PFF{vonOQKxCN ztF^LoOC4gL{n4QVPZQekesOo+9lYQX;~dxXMr`Y<{x-r9g;TaRbosOriT}p7 zw?~DixbyUQdS1^)o$DUE=zRgK)qGR2+T|0zUX^@PvIp^gli%+U7^Gm`s2d3@vridpW_O==9NO7 zFwzbenT8zBW*ifV`09)u{3=7V1dI#~-?pn}Ip}p++N+&@S@q{?S8ohiO_xnn-^j2m zCAm*566bgCFxR)<#OO^-%`??R+5W=&&HJxVqo|wD*VRc{zI|Ad&o)8*u;fL3!^io{ z+~ZFz=U)V=C3;)GX&%qApStmH%;zcYK{q(#Ty~BriBz-M$>geR9y~{_9Ad|Ok9KhV zn^#&e7aD|UUgmti!#u;+{2b)Rq#VlD^BEiTFpto@mLi9@cdbNx|o{kWAMx$cU4 zEz8F0TeG%&+Y9#^EWJv2!?pG9#+l5~a>wy=mZ;bdE>^AP^tLBVa*{rTKRo8?ai=6i zQRthA>wRm%xUWY-Fz@#6S~hn`pLO+nPjw0I8qf5n^Ay3aZ1jCcGEe!QO@<`JM_~nz zPm)EKuR5od4+>EC-MB@je65)7IITh5Xy%iyb+5F^uK95cRvFRjU44?4!cIR6lc#b6 zu|0HR)YtsP2ebMv?HcBhk#}>wHbnRCnL*>aMX_f4pwhKIDgPA)ux>0}MeV*4qplW}8M##sM;bg-zrO>@& zVbhn)VofD4{AB&c=%$4Jpp8?p2BA8K^=nidg)3px?B$iX z>id}-Y4u-RuBu94KcARJjep)*iPSsacvWqVB~4QHX2bJBc1!SN<1l%9$9eUGDFH*S6y;eWlA7PtC(#nfPEKyW$EQrmkj z!F}zEY=_r5PBtsf;>Ah(e@lM^v!#MsI=R_ir1oD?A89&!{I9Byuz=2j1g5w6SNOl` zql=mV*Z=d>NAQX$6L(8f6LV)rGtlt_wSekzfSqL#WnO4D{Z zy|!oDOe#!ic)kkXj5zdZzIubS-KkTrjhs9ME4A*jL6OtYYa-Dcz9oS>qG#x5cxS2) z4!IPIMi==QHRAKO3ZPM(81L@rXLT_`pSuqgyqDDI{9!dv{?Dvk3oC&$e!U+{g*sUgjD4BlBkAe z?U1q7x%mf?aflx)C2r=j+;cu$eTwe&BtS@2WFf5-dz$w{nJca~(G7q61tiZT+Ds@G zf}zcFgLJ=2Cu+67^n_60n+~i=#FA{`NK$VNMMJY?1q$F8{j(w1f5g|Tfn{oEvo&{H zA$?mYdOm$BAu8874rqlwvJkkN5rTn0)HZv16N`g zvfR@`%&5U>@vfoPMi7?;F#<)qu-YZ!WFzUI9IXLFMA)BufJUci+Y|j3={v zQ>RMl_TBy~w64$?q^ya$0420s`b&T#zObl(#suaD4sZYg_eQQ@LlXvb_dPV4H(X4Y z8qjM-ZI{yft{UHnPP3QD5{l1c+kk5{qU}!62NW}pbtfMKx|eOVEZywbNQUoMKa!QY z)AR;Fi@v@M>?%s_AVUOh3VZT_ccb#6*e*u7;G$tgkP)QeeiRQc5sCU9;Dz5zMarVT zs_q^#xP&mPq#?!08Y(S=ojMuQdVHHL_D!Z#Sv@}S1e88k_v*Z`n5exUm9X1Q1bJd% znwP9}%Vsofl*C(PsD(XhB7>_!PjNDq8Z_{m!C5XEF3D|Dv7usto9}wuQ3{Hti15&# zV`JhIOO20?mEb~^OXZXRQ3{eu7)}P@!sum}*$7fujEET;y2a!g`f5U`<#6EACFF!j zdKrbVg$Ce{Tv8qoBKWK(_f{K#4LQsUxfWZsNO@Rybg}Y5uvL`SdkeKmJPC5tyQ})l zKr>?Jy8t@f)YW=5a!^gI5J~};Ps#7o1UUJYHMS_EyiKhR05u_g*5fZ!((lJN`#Oc%7ok0RCHQv_EJuU69$+G|}tDLAWt*C{0e;Rp0F zY=Cotwk^JhjI0>wBETPtY%qy_DtWPbCjxWrCa8p%p%_JU!vgn(ZAv@@pfBxObM^0v zQw)JD?boXhR?`Wi20qD-0AAgJVu+G5X0L*|F&YEXqf(H}z>4f(*3;u+`mf z0uT0~(hR(KCqg1rf(nM?)pU?Obx8?AmxAPg#_ew44PI8Bx!5pM!0B;yYMFcVJ8F4U z3Nzb=DVkQ{$d0A;_3n3Kv;2T84g&(IiIHY@5q0{?$Ec#jXp-xGGbds37R5q?lL({J z=yh0}!=i%d@ZL_WHVcZyV?)k2y<`b7 z0r6&7l!$WG-h~WpA!Y70l|_L>Xuz}5m4?w$_vk%jJdw~_5fL8PP%%!PBuwu4BhpK$ zNPtj8Yb?n$2zpOH#tfq68F)o#bSZaqQb;CxkhJ(x8ZyYSQ)dh&7fX3ue$-x?8>5|o z-T??E9x+eJg03;avtZJ;h&4+rM1gGxxatA~!_)zEI3?@CqzC!TG} z&H5tK6ajKAC@%O6k(mc>4w~0y3EMGH(4OS%BZdf}6W-A{K|r>LY>25($|p3H1YS;J zWn&NVkYU|Oj=x_E(;G<)BPNpEo+2_`DnP3sfaF54GYN?jYz<_V%Jf6y@ZE+c>p_Qg z&)6@D1y&-At{Oy4Z&0x-Xf@m^uGZH}KDZN(_b8Hv4)wCg-UO!bC2A4Prz8<9n&Wt! zxn55q*XZncS-o!aU(PNf^F=U(KmeD*+D+>v+`g8YcvWdKGQNcoUQ1P=c(5cobjYI4M>}wBUUY6sa~fAP{y#QNK+d1vFh|`@bn*#BhFrdmJH0Z&+wlTr!k6Fg65$gk zKa~OegVD1|F6CjdLLPuYX>Ax#uD#5RM8GkwvDc4rppaT5xjVxnts%1ZjD*xNFCMDg zu0Q>Sw$Oh?OLY6#_7an=hai}4#nEoR4mRgl`)mQ4U1#VrTTr#S-9$e@TDc6Eiv&1% zc%?KNb*Ui6+6@CrQsCnU(8F)y-f3h&%gRIqP7hO&+B;$Mu&}~jkrw{r?nq44cE`ai zdkO|=6=Go$dh@QtkR6=gY(V6}AUD#<502C+5{l@;_(QhQESGVUwKaef zHEB*jHH5jBvZUJvnw(730ru+$$!(0+#FnLY0cRTS)+=><`4tL$`q#qXV*$Wu2WeRM zmtI~P<*<$h&DISYjViykL2R_M@GSy4efC%)Y>3`xzrq?CH{7d)Zs<-C*EQrl#x%_| z!;k$GF%OB8FD5ivs1dM#vl1I^QY2_==;$QigG0H*B$qXR@Qp{CqPGJkAY23dNS=Y9 zkdrB})9Xz`M*tadHG*QU#h`G>%xH;*bnnZT2(YvWEz2Bs(jp7heOh$nEgDjt(kN3- zu&~a6eJJCTfIm(lZ9!OhB}5I<=$o5^@UZOKRecm01o<_J2-5fawfTgFNkQFIL*5WY zWkoIEt1hHB+J2pSJDEuRBz^$qaFo?8W62et4i)hD#0p`deCx96RtNSmx)OXhZ`v{> zut+mXj(R*&n3N5S^q`T_7+}rt@4k{*1dU0^X$Ez5ukn660Bf9}u?RfYDJ`x$qy}~a z_ize|O&$644@DVC>FM1!s+wSp_s4}%bH}FEWu8V4i__TLeirHvn5ow_aHW#+h@-EvU1*$dX z;n#l{k~4zIpZ0}3S?z+4gTH@R>sF@gzN+^!zlKS?VUD88srn) z3_YlCEK9S~U=&TrkSAQOmpr#*-qlcI>w+WHZi*F(9MmKZh^$t|C?b>MqE?X|Gi7Eq z_~HaVxfaYhXjEN_{wRF1MKfYEB)2PdrGr)~6>$k9w15r0=;RI>FO1peR?+PyprE5m z?^{5MoIR$S9j&{q=T1?T$7EEYuSkcEqh$WBn+O<;qp`YE0^%&;TU{c!-)Hs1GC9_0 zOnZ0$Sz+Kd;QYxi3PC^wqfHxaqavP|3Zsi%_QUx(=(fdztM5+}Q#ba_)M#V|VuQRCh z(m&nMSPSVkeVqI}57v@l)Kg51)POmS0nC^XS@z?b9k9Mz03)IyK-AVLQN-*lF1lr* zG$nYe@yS-b?zj-zpk-UWWpDD5uC1U*S~#~QtGPF_3o|4)P6*jZQ>Q}y9lwk}7#!R& zZR&H}@~teW`MnpthNyISk$Z@)&zUlg3t&QC6d8og zLyYZsigY66fWc~k*%6E~YR|-D_tczwyIJ}0%6Ojwej4N`b2P;dwt|6g)Qdm~g~9j% zoa|1>GrAVY4;C8Sv3Ofyss$!I29;Y`!~;uOA9sQ$9^JBxj=F0#51VtzA8)QhNQDD0 zJqF8G2SYQ90#+jlz`G779L3=N3Z$dlf^g`4sM`8E0OX#EJ;FX**I8EIbtlAXno7*LoaWb8^Jwb_6* z^#otQv7^*23*2s6$OGSTHKYq_5ZQRt4G=k0n!tYD0kG6uUZA_wMJkMjwg;)b$#5+d zIKAD~Q2K<%7=@wyycn|qw=I()lC@ht&@cqELg~_})46i`wJ2$JMUBED4Zsy!MCT(9 zTdjbF01Vt}MM|it@r1)* zl#${DJFz-fKplxr?gCbApwq}=HfULa&ytRB0RqO9@}g-r>Y?7ofWvf7mwq1Hg*q4P zAFIi}$P6lnlD*9I%t<^@F&KNf+r3KpMWN`QC?zL^l-6*sq)u@>lJcg=TD`eOfh#(j zVFm%i_==@u%h=f|({P?<;W zYUru@m)H@70GDD21#W_`NV&-N>KN!6KM^mE<%@3^r#YCT9)TUS!og(W!2mjk!7~BtX74$AJO190~XvR z*Vw&;O$HUm@b2jK+0^%Gz)|6=G_WE>W-Kqmhxm zl*JF*z>*()QHM^^-dX65t#LFSp0165Qyv@(Y$$>3v+#vv=;Bl$x$GG*r;f@8fcO`T zOxtKhfL%2;{_rIfSy!q5A@Bt|Xq8j#ni+!x_J9XJgy{T%BBy$5WMzo~u&b>_Cw-p7 z8?a17AkVL&0n2HhyW$kYKm&kclYx{kyFG|23!L=EY6>x9+M?3*?QW~28MBkDehAl zH;@C@fXCIzUjteO32O@9Q%h2@aWKC9!cl~fFgZU@92@D$`2nwq18V23 zG4|)(YI$X@LWWLNlb%Lj+nSBJ_KK@t*7v896VVFpMm#y+@>HK&71in`U36eaOs;>w zI1oWljds~JHYbPs*RL8h&?gJJ>&1Vx zDo#$iZ+`OL*LcSFnUU(+r`mGpV0rko$yD(`)Yh&ej-1Fhs6Ii$_t9Bn5zm9k`5E#J zlVQ93*l}5Jo%7T*`Fu<5(JtRXcEt1apwiX!NuBgbJ436wv}GsOp*nK;7Utd*r)#GV zmI?;DcB=SpoNs=7{~oX7jd5Hb> zk9VbyzxG!P`3bWb>#UXMZ=B8yT#LM(ZasD~`t)e+F!6iPi)V2f--1$9JX5S|U*Z^@ z9k2CcoYr<*VOOxF42-X?Mm6hcO-`5`O}beTFWHW)aptGYsm@mI&kHd)2#fl~^bdPn z*DySLAGtQkHjQFtYm&dR9Q180+)Z<_x%#Qzq1(6ovBZt-6`gQ5?Gx1-6GC;b2X;Ra z>q)eZkK``AeLmI5Ch)K*FTA*T=D|})-+HOO^kNeZs;_O=3SG)MZ&Qx|w!U1*Gg~9WmWFyDN4wuKM7}ckjc# z;M~c#v!;aagJU^<>O>vIxlawJYqsvO8(Ts=pRG)umU$j5&E(n7e6IQSEdN;d3w55G zbt%OQL1k|3u9wWEDZ387b+Ru~yy~uGCn_CqxR*EFN=f?WwMB1F$`uYB_8p}5hQzD* zCVzVHy}yZA8LXx1OV52M>6cXtE3Yii7Hah~(%a`~>Ixi&*l^391ZS^da- z9GfHG4f&6UW218hIquHsFP|N_J#Wa^_cc!ahTL}IyE)gt!yX zRL{@NT*qDSZZ&SspB~B-r5SW-awNs)f%9Dx*xu{*}j{BYG0ib$ENMBddrFf zBfCG@*CzS>vgTIR%K-67dy46_(+!L59{K*O1rtjZ+a07;aKrGu%pv$C@VxbY*vMCb{Oy@01g z1ylDQP7z@)^aQR83UHg6S_oR2Sy}S(3;q@Ppa1_*uz0xJnk!md!c7r{IQy~7UzJGlOCIzT7>@KW^E&{BvL!E!71OMzI&VL=v z#hb9}U()|I-=ApUasG-17VFpWUq=I9rk`lw)BcGDz8-%?1OMy-)qfq$uld^ku!@6E z`zIRsxWA&gKmdRJ9UA!h{nK^>|ISY|unu_3o6E4C} zG_bMcBK*&w`F*>AjjsVd>7#-8hDJqqJhul;?3Vl3O?RH z(OkRzoNN_4S)Ter2ca>@K0gC|3U+sZ!f}6H1P8KpFsoPs(zw@5iVYYpJ?Fa_wS;C zm&HFN1(}r*Nx|3ppQ3@!_a_>Bxa|I2 zH1M_lr)c2w{fP$l`O-!BchSI0`=4lj--_Y${fUO?0t&ePiUz)~Ttxewq~LqPpQ0hS zNDy3qqJeSaUW9)a4g4tar)c2w{fP!1hT$3ijSb_)|3S`Tj%$e53yz{#`UM1Q+2?(ZJ{X6AkG_GT{2}qJdwP{1Xj) zU-&60_OVvCr;>s%=bxg1=Ro)~G{4ph?C;;z z4g7rkPc-nU!1DcT^n-W(@)Hd_#$S6leBZoC^LJ?A=i@&`LvxWJxc(U$Sgc>e@6f={ z$NxkFU#6dRLkpMipP~8Fb^|}>{1Xj)DnHTC{T2;;+x}Hja4-D*^LKRvKOg@o8v4Ie zgqPHRh6cWlFJAr~8u)d-*_#GPf`S_n`;8TJ9{;N+h!X^9@ z4SY}gRZ{R0zj*m~XyE7LKSguxB0+HdGc<57{QdKHXyE7LKSjgzcZz?C2A1>xTY#-1 zT>kQ!1}_R&blAew-4ZyyvM_b?{_B(+cKag0-UN@K&Tc;rp)YR3uCRg2-Nwz?!@|-6 zIO%d%@o+S={Bh_E9}8Ass58{mK@)2Fm(Mo%r?BMzb-WKdTZ9J(lf9R-n*%IunA~Ae zO+DO9oor3Pum2EWMMs4F2OBS7Yg81uw?*GFhSXEnCx|_S%xxteo9wOn+)ZIIXQ|%)Dvj9K2~Xoq%@44VFJFz}7xiKv(h)a4@j8 zKa?Fb;Fmccx2b?7zZti=g_V$ig@vg(_jNN~K>-0_9&lPr&j{qx@?O#=*2CB^) zmI|!iVEwSF=aJH6PAgYCc@tvwcIVM8~L&=haD5Wi0>Kun!gnl^A0^0pA)aXP1O)P(~vrYbsT%%eOKqhWh+d)^RR3u zVN52(@jC@B@g#`{B;|KCO$~X;i|&p3OD4vJleI(Fg|7)(s`bciki@?(=(ZrVdYpQ7 zfhnssQUD#`^ zWsUc8-}V@4sq_r2NpD{eD2 zEX<(=zloV zIxHwWWbF|o)p4ZJuO{60N?GMF3avDU@QngY+4WJ11r`m_vu_F|a@tu9^^6?RM!!_s}a&9ipt9vbU zcb;V?f&3bYPB>4*(!#7A3F}+zfQ08}VNZl~9+?9E6xfYhs*EI^h;AcUMj%UG>+BiZ z0C%@yHg`fEtt86Ci4?aoIhW}Gga#a5F9~7mrJE%zYj!y(2C@1MF^>z>l$?u74TxyZ zS3=X~BC`@Wme$Yt^bCW(lv_60q;hqaeryu@k^5>~mp(tw z6fuW^>C5Lb71RO>l1X^f4he|KMY`y6H%|T7RGL}V2CVOwuk_mBT$Lop9%AWSXUBsU znHO+R^H?jBF6*|fx0H>!ZcGHPYzZ)blEzyN)BGaP8Z*ZFYTcC1hON^kZ0P>UW<=Jg zM3M|>HjX>eP%F0Y+mY@98uH|OX7t}nO!>8vzL%m+afgX%Y})h*O{!Jq zrHZD{w+y+FRER5j8Rb^Hip}0H8GF1G78~-ao^_~iW-9pOqymb*b+kvZhaCDo={+TK zlyWl0#`k8WPUaC3Ig$L@v)_9jA4;j3Sd@Jm9SVNivr|%3&d;rtv?FI-`bC=@F#=@( zF=BRM2{OzVXnCB09gG)XcPHfXamU)#GtZ3ty6g;#KB2E;>4yi#A!d0!5#I)HD?6iX zjaO5d(X+XPZE#0ct{nMj#X8&>{cOVe)}4rYnwb=8z|0c!neO22JNJD^kHDss+x2B> zLQKkl0bRA)AoqJ6_8X7G^wv^KxlU_bZr&jon_8H8ielaRwHUN7sR+=%bo_=#?!|bI zeOMoH-r8t>_f>@A{&_lR`B$RqBC>Jj)JKYsH#~}wYTYi8>lt}{$a=!+ulaIwpvA2r zFp{XfBQG@ogTIA%ocnx}6H-MhxkpqN?LxBUEQ<-ov$5!8{89OV(93zXu5E`Vo zFXPjcbRw-n$Xf60VL{WRM9dY8PyQrwQrm$$>J69c*EFK!ZsS5lmad0Qep&YCDs4L29LR`ByCo*aKs4(;) zle}@xo#@k&*ksXbquU}WW|1#V%y5o*$2n~~n=|XJYR~C$8oEwadOV2w&$^2F^m6X> zJ+P9r%UY;oJQDLs&R2SR`x$G}p}F6-4H@S15m%ZJyjnXHOr4g_Z}KzvF(|96!4i4) z>=n-2X8KkspzRi3oXV)(|qDN+{N#X^&%r7d|O{a*-^bx$|dJ+G)hBuHyb4z zBQ2A}!)4msh36xK;TrKU{TJ#@m2T z;iKU)bw4xKXFL;1w}$b@HKnC@D6m|-uID;+?nmmHzmJeE*7mTk z`_fAV0d{YBvXkf+C!J&6J1}0eCaH&n&vB!|xwNtF#R%OX5$L>1;B9KBi(e(S+iYpK zwYY0T8cX=i+%X@@!CoAFttR!;-WiYC*+RgpKvOZ7R}v@zq*h<@?qcTANDynCCS8>x zqI%^(c;)N^G4BC!{FhYos}b!45N=N@ls7@>$L7hwA2Cl-YWl1;9OrI?uF~V+d|YDL zeT7-FvL?zM&9wgt@30~3G1vMU#Tv?ZKTXYGB?{^NhqKYC3){7b7^D)i4yVz#&@A;N zQD0dx2)&=jN~1mhBKzhP;bY6QSF4zv6MhOWU2?}Nqk{~4P9k_!1N8Ww-Ti`yzKFoD zz_XK*f;d>wM~DAFX_etRbD3i_*RHYDIR6IKTe6eDzLD$+JM6oyPb4VjCF#Kaur%Fc%qXjgk3 zgjS3QLiN6sGV{6goE4JYj*gyJh$~T4 z5D|HBJFVp-Nk~;S2Lqm9a>sHZA{BsceF6O7PxDkfZj;JZ_zeh@*YlI)JF@@SXGyn8N5XZ8Ax zWV@=9eDF*I!fau*^p%e{aW(fR&l$&WdXfg0F1Ot7zPv)WOHh^1W4lKu-Mmk{b2!RO zBSfSa<>cU*b_X$umgMrBzWg+6q<}_inGAIn@ALIFZEL!3Dx$3+p0wZWx2xjr*Yn>i zTwHF5_WshRF7j-ieXISQy#*>~HcFKA=?1Qf)Z+(y#M!-;5|djF_>j@PcFwwMu?XjW zks}F-Eu=!ss=LsrAZZcO0rh(H+7)G!mDLvBB8}~O!&_YQuFR&$BoQ4&r{bwG8*c;l z!bl99L}KqJ+9&fqA$^TE-b0d|FI{+9k6S_$eT09XvCCGNV0>v^we|L{-~#Uf!sgU{ z?AdxO}87GVGAgdHqF81G5(+-oGs0;o3m;3qO-B zX@9|dkzOJ}dZSqqmGs^_>dEeJtvEUeyG zQ+nm$EOun_s@#P6F2pI<6vX{-Sc@_sW3yZ(nz;`j;M0Sd*LWr{( zNOt}#ajiRh1Pw?2pzgK*TVi96<_MFUH^XH=im6=1NZjWpJ(c)WCy@_z+ke0C7$@`w z)!SYFFGyFI0+4Xh?{JZPzE8^T((OEZs;*4LnkQg;f1o)W+G~isRX!1RZ>rTrm7|_1 z0eM6J>M^qQf@0ChCGW1iY=iCRMM3G>qsze$lau1AeX!8y-1MgRC8!_0l<=`91MZry zHwW>g=b~gpi8K+L=-#PbH9{&az2@#}8rHNw)`W|p=n|2wv>&(j$h}58P;We$b<~=e$q}vPkX&f+CZu6<- z*7MTcO8OFyXSQ$oUfti-CB~21k07{Pe4yg+-eeOF>GK;jpUbMkHR+eU9Ao;3W1b^2 zM@#rzYn|sRzkPnWZ>%NSZ%oloinob}6MbBg2-j2U5t?^8t(=;xV}l?$^7-s|3Ad5R zEx!=P>BVaELmo>1`dpGdKP|RK%Zsfd?0f-i)UAKFL;kT^A51#tG`U`Zoo2{MO$AL$P6{Rpsq{C}SZ`(KIuxWHh+6JWzm z9j5<2Nciu&*@q!^hn*c=WD2V;=)XMB|ER|Q%YO}OXI$X(=!@$8dE@~n z#9!U(zXS(q*-L-D0**Wk`=S^GNy{f^-)>*$GNwfhUcp{um4QdhF74}g^q$>C_ix_E*u5{euNJ{C1PgJ9Ax(#bGX|bQ>ydsat zCwcsNKU=Gxt0bGqFcd>|%8fkqBf)c{oJvhYBb(IXxmyV`mdV!9ami>6=U8K>o3!KF zE-$P4^2Ez|SZ`-&%2ws6l)b8qY}_^49|-0ncKdhW)^sxwq=m~vhHyUPpgwtRb%5p^%x^;wr>`}Vibn7<{fDU}Oj z5%yn3c~r(CAFkF~cS%y3j5V1^6FZT?x~|$x%0OA@5s}k`isS@}{5y@C)`anv*2H8S zn|Q3rK0Pe{CG=)9i+9tmGv-|`)Y7qW)MGvTX0mlsbD(S8$MAJdZLmBy$pZWepD#M8 zN!3fL@E~UAS6@;`z0`wJhm+g6cE0bF#h6EZYX5@#v)ymWJAg zRcwscnl{b^RxI+%-&nM#K5I`ECvIF#g^n7bHBg{Uzo{)ePSY2O8&OYn&;4jG*vXw6 zZ{+1jSIn)IYOlt!5!S%bx8-+2FWzchlP`dO|KnBgi^P$B=*FpZe|g8}!T8eacxR#g zJ#7tEpWnIIQ5WhUJ>L$?;27y4mVoTrSGKBMW`I^TE$k=K*R^-8x*rA z=Q!*8G=^;kYrj83^=cUAPqg zOloMru;VKYg|SCVb72LZ;<=%*VG{y`<|UK2jCv52Z^<4HhUI0_vYC;S)i8`dy6n7& zlrev*KIp8BN?Ut9)Q9U-d9a+ZsooO*{_0i!R-z^I!7Zw@XGIvHrGC^#mC2EfRfILc zz6CY{XsJy;WaY`hHT5n}#H>xoh&&G*OUgjr#Cx5z>wH zWTImU`e)(3u=C++2wt14mdtY-DHG_9pLeIy?_{)o6l}EB9BdAXn*6?<;E<4quRE!+ z^Vr}ikI^>;0Eyk{nasI}&c40GHQP)v@nF}KC;hjRK9{|4c=b3e&rh5VEtHo^<1?f2bzX5NGa9|-?cA5 z@HRF2+BtT~bg209SFL6f-VAY88@eE0vzg%H_u{vHS@oez^|3}HzIjqX$Fb9=re)!4 z0!#wO9)mKO?_@l!S#@482}>ANB}e*jc~_!u9J4lX%92z%DzWUKnqCXTF*1GiY~)?R zbJ9sjFHxqB(63^Bi- zfwapoS3abWl&yNW{HC@i=N{597~$5@^j&6_t|Oq{3d(CA$0D$U@Nulg6Y|wo2ip|k zTV3M9PP#s~Fqnf3rLqp%e&dPWxvEFDK$1@Kvb_u!l~0m1b2rrVu9ZO8yDx7LKHs=T ztx~v06#JnN(3@W_4yqq8V&;s$11(#yt^3lzRO+oaQpa{^q<&xoIoKhf=(|sx)3P7d zf)UmtE<0Z~$`pyhm$e`0&J&^L()L)AfFa&FaNeA+!v(|P6c4*=!Y?II_gXprPGYzv zq>Dv5+1Vh?%a})XHTH%x*%pl7?_O3jP3?1`I-ceg+BNyyW&Sn6C{L;2;H)IgVkp-;KWkO zt*SvG!Lf&|%gED%7WQ$7gc`{_lN(z@h3ReVOm73*EQb5_h&+fyeYRoiLg^#qH+TH6 zN|iYyy4Ssws`<3d`#>G3ww*%VAU=QdQu@ zyGF{KebB<1DHU^GQA!R^`yl^%#PUg-i~#Lj2G2(&RmhZ!HVW#Yc#Q1Af|FuBbnKcp zVppmYy{@skQpB()-PI_5a&(G-ACXx6yzLvIg!!AdZ?0UzUq<8#iGRP2^Qn>U6JzGaaS;myIfsXjzSCx62-dV+Qqpwgl1{ED&6^Q6 z>}h%tp+fzrz3``?j=HncndUd4$i}z6*(PI&!5)MP7e_>qo_drtKcX9ACTd=sDZplJJ$sYHoax z`H=n^^%1pS7wqDNO3i1EkC;+P77+Af4_yU#364B+H`P_ouxRtRe-f0V%p`zRsTqvk|@5 z!zJaOvZWNPxqPHWWJ;RG}yG6wC@iy50s2d${xARF?5DfPA#1on}I}Xzs<}oGa1x9$rXO|&rLFcq`=@Vabvf}*^WUS z{2Zl?^)vVshTwQM6ISqdl-g1L9?Y#!bN+EyJc7wAtXoXQ@iDs0BLxrjzLsHJn}A&u zDF13;Ym*hh*?4_9@#26Ly}_Q}a@xHBU*2|}xg9H$swHk-8~Q3Wobks9_SZ^7{Msue z_5IjTR*KAHQpk7GE||DED953!h^cB@RC*lLYIGk)PDVW)s3%%5I)Q2^KI*EaUAj(S z7Zh005dqU^xKYAuAaVqU#oy8T2I#=-3TVfvWqUb*o`YXI%3OEl-TA`(Oz7KX; zVE1E0UQxe8S)db=AEZC>ciHh_k7FPx(kAl{_=odnSu(O9dzc+1g=53p>{?yDlhF(! z-win)MX4L%-$hStY|#hj&DTigE3P%ez^2PELjd0ob0E&{8~{ls@yQ}w8_Pp0DlAKL2>S^G^{i&M?7qbSz>6k&CO3yjk|F%gp@Oe|$cCRS z$LQz@P(u%X&P+Hew;*SrZKgeh#N`>4i+^_7+Z5~*WXC{YOd+>}aUWFLTU?Dpf3Qye zy$k$CkpwL1^;Eg~V7=}hQPtfL^2yt`e;9GzfKoF?*U9d+v(B}efX80U-3 zQ6n}uv4GU>hjh@n#-;i2^8B-fM>|XnQr)){(U=~U7C)?>x}d&v`CaUk>ESJ)P8k%` z_!pb@n4vOf8Sq4q^8`wz2#c&i+?5B*2_2~H9JF$U&LG_P!2EGdJL;%xR=4-JzBMSk zwFyoWw|jwjW=(`1V4AV3uLZHmJw*~gye{Q%A`u6zI6YXFAEHucjX z-;Xr>F;fUy8oq1IrwMAoUVqX*HKPTt$Q)g8ws#G`D*;Tt=I~%vOgGRyy^+P4P}fPi z_NFgO^k)seIukrSB5#S}M!wfK^s?i7m%J<;cxizKoSbND>Yb=Lq*iJ3im|b*L?>+v z(prh=uAV5^dLx@_VHr>H|{iXVqp=nQXbs2u!KXdZiFsG9P$kxSg1n~^BZYH*lqonR} z>qsW$U&r(CIaeGO@irlot$9>Bo~)z>xrP3%w5)awfH`B~1*8qgDK7 z+vE7O9v-+SC`8M6g<+vwzo}cu_f`3}HzZ+L8hR6}b^QGNsl0&p-y#H6pXKV^Mugng z*glsf-8KD8c2o)JA5aM*esE*vLg{$xeZNR02sB%-5-GdMVM2<@8|)D2+`o*a`&b5+ zbCYM-|5P}*qyGnX{BI$;zvSy*jyPXH?Vl6=uR^-Nks<YXk^aAY-afzDKl1r2K=UsrslN*OD{F%<$GCsDkpDn* zf1mx+{)<-<LP2`ViYD za!?E#N8I%!6k9w&(ms*M-8M=2laF=%QPydJ7CU#%rv9}4_1imycHyBMuDRHRoENO< z{5_suGc#^B`{B52cH$Pv)GaMuFF{R@Gh8(iLc&p)-W&zm43mzJR`6H}f9%E+p4QXe z_txqjv{>VZ;f9qg)B29z36LLM0VHPb`+!W3s`L#-(T3myiOCV6u4ws_qvf74b~{tipJ38}77_#1#Z1ldrUfng z?O%XH7S6S89I1KEI1N1M0x4;1j&w9;@)R5wCizQi;jKxHeTRM<<9WQc{OWiFQpK4c zYwZn}_4>K;jV)vVO7m)9IQA&zz-he!rO;3$+Xf#@A71u53MFe@U1}5;R-MOV^lV&&WmAM7y;CJ>QciP?(=;p3_S5EIX=Vw1GEL<2LwKsD zKhp>zs#+MaeM~ci>NgV0$C`OuSrkdC`%!j!(Wd(Cs2k;e&#N@DFLXweLjD5jX ztAM+_NV=U-PGp$rEFDc(GVXpV$kq)}vFdkqPd009*um>al6QR?t=)0CxSvju$om;! z0XSSOZaN${f4{_G>W&_LaV(zs{1Bb8ZHbC6o|KYVL~Ms}76@cpQR0Uaw0PX`5&dbJ zZ^Nxy8~4dls^Sd5Q%Q$Q@RRX~yP%ZDx!6#NwkT*RjJ|MeXf7Mdrna6+ZP-4vQeV{< zmZ;IPK)L5oP(6WJnf|pFogVdTI?hzok>xFO?X0pY$1*g9<-U{S1%XDhmW{f=3KJ4g zTl$9I6CA&OZ)7br>ZlU=d4K5PgC{EIjO!-b4318xVa-nc@%$@oGu(eypnyHf$5Hf4 z+FC_x57X>Fx?W}tZn#p+Yon;As7hv(gPRZE+#qzm>;$0MZk6b?`Pc+ZgVR=3l|4FV zo_J!Hh;(A+Vwq^2{|Em>GXWA_5z3j(g8l+agOiO#z9pMrc~tRF^Qx6oJZ^Cfr$s6} zsu76Bcj)qY@taX|r=)MEW{XFjjn1YHeS8J=2UdF&Cr6*080;$|7E=u@@lBz9ZQ1?b^NIpSW?9 zyW)r(yBA%Z)fXK!2bf{2zs@Q+UN+6wO<@#vU1BcysbqXx6Dcc;-bhsgS>q+IiC-lH zXE8{oVRBv?Fe_z;T;5N){fC!|DSK(*V8l*q;TtsnW0Li6SdtV?1`94HP3!Qd28nxfa& z3>OU*6^hBF6WdVR>K|QZidH-yn1ZDq*E^QpcJeLi+fI%ZOrebx;*pyOt1LPM57XhU zn1#B4LlekkC+rgU_SAAfU)`;!7j=$sVdl5atvep#8CZ->ztg}+Ti+{qqTMwoK;Gqg zyY(uzY);Nk>dbG_5U=?RG%S-s2Im%o(E2&hJo^0QJdN2%P+~`O_sw)Aj{0IlIr6%s z)G{Ml_bg?oTu64mz+;5Y)qp`84o5+PP|6bCmaL&RDl1c z_;7Ip4YhS3G8mE-vfPAMV{Tz9K5L|Dq|Cgy<**Eq*1Wi>-H@y#bci$Ar7aOYROS_( zdh`SMDI{v2!+Zyb2ZN&mM0StB6&M;r0aD+2Nm{r_WQ~9nG#hp=v~nsE2B8J%`*Xyx z67KD@X=o1B8fR5J?ysj*nZ}lVH?$=FzFn+CByRBQP=q#VnO49d5kr zwP1N(CV+Z)cE!0M-9Y)>`1T`=#{ z&IY3t0K9qv-1dnT<_EVJ^hED*^VJ!FyFGG*&203>dFc9T=l5n-9{xX3+8zo`UbSS>SBl z#YKP)t++L#`%#Sx#gcvSmAejSM(e{5dXHyf=#LUt zIzn5koZm?3&R%G|w3wDBE50Ay!9bHyW28g4m$(f4?Vr&&{0O}{^W;R`u|hYVH3%sQ z4sD7pc^8;J97lGUOdrzsel~vKdf$jv=Kam(0(%%r>M4_jmR2Kvq9T2($<63AU*cQc z+8U}P>i@yMtjnbZlnS))T}rolqOlzt_rAK>F3NaiV`;lRtsVg-3?XcJQQk6i>DV+C z5PD1P5sEdRT$5mL477xUH6E<~d!V~F29}7BXfsYiwhS35wC$bajS#{>RgOynQXClv zm++bbEvLA$OknW9N+7yqE|7sF38pgIdLNQAO;oE*{Nk-;tvJ=L}`?uHnDaJJq z2oL&H-w7r|ACg6|Ww>Sad(&DC-K8+^U=Ek)ej}cMhJyqmUFuvbSXL}c<7rMRM5zL} zJ>NZZLS44sC6HGq43{Eut3iE_>E=}=m$o2>V6rf{h3)V~g1p{UrMnI4=b>jIfG{wq zQ59367Qlv;ZO1&LxC#RleEiRh7XI(2s@xdTVR@d-tKx>9ocWk&sx=ECOt}Q5o@Xu? zXwC(Y(m`9Mg8N;fBQJfNz1oIr?&SqhqP-hRFZI55nSxH%bUW&=ghR;~y{h5zQbLhP zdNPSAO654}T}Y5kRTxnSak?dC_&*0DLA}zaw?jXDFzcR)OE-%(y7aZEn>8APiM?7n zM$kWDt21=%c--F)wyMaPkh6sXp)kyseED~e?pww}=Ns-Q3)(GIZ7~nt5)VA(*Ep2B z4_6c+tW}vW-qq8PlvSl1!|cTf8nHqa+nRA2;)*gXRFoV| z=ge@@oOCxh8iTbb+>{Vp%*8VKSL=M%sFtjDtF4?;^aw*@7|al!jIPAsR=v4~WfUSk zL@fsH*X@p8u4ryfa2pg7uM0^%)+{L+{l8Z~clvCfIp6X})8a{X8DMQ1e}h)l_4Jo& zY3yG95UXH%SPid}&JV%sre7T~Tv=}R^B8SEpwJK&Up`8)7bhdpe^l+&jcFE#asBellnvsw*S%U_UONL`}#Bn zEk^E0HFxQROb|rj0vj6$7PB{nwXjxh^y8C^K(Twd2AMKEKiWn}9;q_ap-Cw5hK}oA zBq*he3$;ArDsu^ik6V@a+sB&ebvvWrpp1HA?3N?(l`B^(ABLXC_ibakPe)Np;pK_6jO)4w&^sEWC|Wg z7Ge_h`uqf)L@9g;vJTCklkpU1x;&!7d?tE#%QyJCS-aJjDjWMk-71ZD7Mzhk^VHA0 zxFUAX|NNQ!-vT3l1AP9*T>Z~cO8={9S9}k>0OA z_S}~UoY0b`%&|iCKSQ#&>6?u0?ZadPYgZ=*V zsOazmtLtw1o3MXApA6VbOrckSJ95a5ooR4p%=!`VjRx+?geUJWI-cl8S;&>Ol+R9D z-J0R0)Sgjt~w_5En9abp@!sHq6`r@q6USD z2qiTe|KN`e1quKkLfNh7H_kp6e8p7{>vyhSaZ33LF0VneP;cHt4s=X*U=%vDzpVp) zNOh`D79i(`tO`X9$t$xf<>3&&;Ei=r5|=BW2z8H?1%+I&OS`2`U-g2B3*RB=ri^I< z&)_`kg`twQpM?5+EIGJS5qb!@gvJx+e=@du!wEsz#}uf-&gg!wQPce?$zQ6VR27<+ zLr*QnnXB7(N%C8pBbx`3IaHt7qn4>WBr5nFG%u5;$%W{wiqAYFsvvz)yzK!eME8S` zk|?~zf!{Eum1NZqB0l^GuKzxLAhxKIDQ<>EYfLiLjN*j~;|$+wzE1B^9#)%NW$h~} z3%Jh4pAzjXyO}Q*l-!iTM89|_Pxs+yBnVL@7BET$AYgIz#(Wn3)(V2$M z+XzkIO-cNn1X|}ZJlE~6VAL}6F&M=~SG1=c^#kP9s}rOUn;Aj7k+l}c1B!>Lt!l(O&lYv@^=mA`00 zS5I_nLRq%_3yELKg*T$xF?o<<>F_D<6}3+w@THNQ?_VnB23qx(ehbkaFE4Gc&|Bnz z_dN3z2wug}whXjgmVdabh5|411VU<$no%z zV4W=CjLgGbRlHGlqL1!hxMIOoymt?^rjIB_NXkNg>3``X{xIB`;p2AG!JSirkLQ7y z$8Adw4quEi-!u4>*?PXIg)8TAn{fyG9f{f=u z3Z(o>x8X>u4*woW)H^p%A*RFvAHUp1cwUs|m}==!HwNFFz&bq*d+)f|x_raK5#0Yv z^^0KjN^MCe&H|l4hiWyiY$Uha{TN=?#mM<^D(L2Zp)bQs)%P4_bPx)koVnOY6Z@@V zMIYt1W2!uy6~AzlCau-~Ck_#F8RTp_^5(&YqVeS zb-FHrJ2B+D{q#@Kz{cRcwio<9S(vR{m>BSP>B5f=y^XF7(Uul|hKRit5u7tqEg6X~ zX%-)yN)A#oSN)>tmQ^l$v6(IwyixPs{VU?6ES~hAJQ}TVZiMc%d)i|%1T5vQWSoD^ z?j>3HP;wp>LZRET?7G$&9onIE9#MX%#tR?mkuCg7YxdTq?sgZz^QVI4KVM{z&vWXU zXD7}G*mR4JNGNsS6z}d4l@Kx#oBQ^G5C?j((;LwpUPVz$lEmc8be3TL{L--d>a{F1 zox1GcInbC+6>0{?C!`7)oBNvYCbD!ukz);5`^r(|UxK;68VgItiw)B-M?ok(yOSGc zHACr0%%arZARbv_#ZW#$nfXv`uOYa$+WLc+&J=%$7h6H`a-^+8;!`e}=cr57}08uX%zh6$+e$Z!%{&|8Tq<1p(B^NvF`42q_=j=jVXD zzMdbZ%Xqbx)8W5Ebgw(6V8K~2T@;k;o|cvrD~a#w z?=UrL)!bD2XO?TA>^(F=NypyS*$?$%b4hVg5sQX;YR!|g2u}`O2Mb4+4=;rJR-dm^ zp?_YyH)?HYN81D(>AHk+Tzwhd`&!PZNtG7I-Wb~igEEwYRI5}%?4Z*rF|LG^*|2ad zBfhP6aG?(}*jyD%?oy&R^C47udrc=g7dz=1=%09inEAG3eXgd*uUyaPNIQ{)Mk)E! z9t93o3#@Cq0&o)m?4Exv@Tdi{%mVcuk}mYT#59x+D7aO=CShkx;Il>`1R0!EJ?`G) zT*qVaAEVK`5TjR$EWrpL;q@)}T{H2PBt>KSq(H2xoMieblgZ~oJ)~A-t8XM%k&B|l znRQ@EA_KhpmU_MLvPIXU3$JWqCiGa5dx4La-d8$^QWrDVP+g9XIxAKB_s{C%J7s$- zz23b)F)|Q?n_ZYNzU#??@#@QiV?Q0Rg$A(wS)Qp_QNx`ZVZsjoF1eI@zE7>dKO}h! zt?2^VlAJD-Ku+#)SDS8w4*ukY{~LvJ8tOnPi26tEfjy;LP%)S0C<5veov;8o7(}q` zXl-f{btun1!~AAIg`t=m z`$AmfD= zjK_b@)ArK4QsGD_^7w)e8iv2C5AOrz`?lU^m#hZ2ml$sU1}X(NsX|V zna8gYi*PWR0tGW8Ysdp_%HPS)FTJFvhZAQfl1w$EJ56s~veMYnU~X5l>m(BMo%Z&2 z7eY3=jqT&fb%=tO$MAZM(knn>DVr;PvG*r_>vnd)ce>FUvZ;Wlf!m@`l-6>dmv5cl zTN4Lk)Do-zurt*vd9-vEgc~qwW)DL?#eP>`&p_4lFk~W{0?nyqpCdDFVtx%bz!6CT zy=ae2Yb+X42ezlyb#k~&A-~*gWBqmeu|5*wdPHaD_ISaD+5OnNjzI1jpu27A3zQ>$ zWZE{}jh2~XvH_r3?D89+kX%j`rsmdF?T(AwWcwbG@1Si$MP2{mjx}^zUPInK2$Q06$ZrBXn0Y;c11 z1>evm7*&UQmj!w7MQ@lGCr?@+jpGY7pRc42jJ*%kYQ-5O1-zuph{{0xxC#f(Qp-dm zMv%y$kMRWvaOdFgF=qL- zL=K=V%IRGRANwAg5k-k>GzlLCt_oB%7^gNRWiyxTKL!&-y0N0@!cz;XGg{L$`MW34 z5-4B=&`-t>$(>paaEwC_-P|Yq85ePP2u=7fm^x714)fqvS`^!<)&div7joSny zU1}qFJlh+0=Ogij)d)XwHm&Oo|4LQHt{`m5*?5;hNPamPM&3{9V3v(74> zYkSHLbs$flrcQGN)%6F3S*&83Uv*bcN#TT`KR6H)7faiZDnyj5{)k|zXgQxnsNyzL zK}n0QxNrR6%T=D6LFb?Dt1~bqnSn;+(dz&0c5pIBL@qWgez= z_=}L8)J3=Qr6xkEuV2x~Q^nr~Mh)HMq~50|6^17j{9KY1yu-HWxz`#Y(+Hlu5@$nc zTWbpkL4wXctdKPqRmsRQdnYKa#%l{*(mwY;I>+_jp{6(>)+@!}^ zkp-QgNJ-tfCX$xF8a5atfpNbCHxP+HhgI^oF83fsP^Yv2EVa}3`MDICq@J6N5M+zo z6QwYc^Z1KP3y&jJf8-Vx?mZiq)189Ccgh0ESgJrz$*+^xzzu?Lv*=BgO0`$2;S(aF z;FlDjW@{(-nplldzur=`3VsA#3;{xCSMD?GfQbacbsuNWoy!|L7h(`h!XCxjx^z&P zd(_Wi-nqX?=N;ipB)|Nqghzv?m2|C?9opJDz7 z>-y_4^iTUQtZU^f85G0+fSUj23WPJ_Ts^ZZQIIkejZ6*>Yj+dCO%HxMZB}R$H!hYe z>X(E>x+0xbl(y z6FX?{zPq^`;(JT;z^|lqajNtQ|KEipLb2gf^8mq z52lJ8?8OIIfL>KUuNqag20SLS59Q9M;F)VNmH^Mp&ujHtP@Z@KsL#OYubamen>vF7 zuvHZ0Hx*;KtO^I|r@&dwdmAm-ROB~zG5ho2x0VqnvRAY~2=Jsp;czQTJHYi)><%_5 zw9%ZJvIo~70gV9Hq$fZXh{-6or+txrFNDxs!2zT@ioNyUj*cFPY1^mG)(@;C$*95f z*_yf9FB2j93L&Z?vFXdgIobvBO7)b#hLuV6^t9md!%RuP1`dv9LVX%BaC3?R@AmWK zm7Kmin?H;T{&?WS3JC;~AdSZ4QQv2chDPh8_+bR5=MU9SjA$yeW27WyHtQUf@rlqTr@*#i!un8MYYQ4N;8D%LaT@ z!e|h5R5>4E6!@|)oWBSJ3>m_J40F-)^ik=7mVVEI2ewAKB{xzlA5^49rwAJjSXSyS z@XQ_NBt5{jjj>n65e@)vsruvM1+wgP`^g;_L@T94NewfnJ!`WFLWGsF<3L0}wyVg|Lo;)7S-uK^*?p;eTX zy5?c5Cm#UnL_ucD3VbS;cL{u;Zr7?Y*7~#s?Bc7sg~Ie5lEWNA29xeZ`2{;PPwB?> z3sqnz?ok#E%q}J*t1ydDRW?BagSj~71_uzUF($+`!H`I|>ED(J*ethNqhIO*8hqdJ z$89;u4o?z-oQl;eSlBhe@Y0OEub1e%@$0aN*vxZ%qnMU{h>BSk46cO{!C)gw$FR66 zq}er+2W!=?0Egp6Zp4Qd7n`>|5XVsSbHVHRTuZ}ONF<8#-T;Y!J1=NCGi3ad1Brq^ z0RAcv4G=Onho>07ucUKQmzp~Nm`uXi(iDYA7 z*heiYMn;4w;-odn?4YG&@k&g2o?-l(n_Rh9DHfNq?)~zieG_417EpJWFf2^pRR%au zBfwOD9 znex1*zau7|Za2$eIlIAR-QmHPqDrFtNc!=8b^2>c=v|Gl-%q;reYg#fVW_9ud>}=v zB|??ynV`y7mAf{h(<=@(q^1}B_Lb*nPW^*RSLLUJqi(^5C^e@m*wR}8NBTq zNNP6HY~uvYeWR+P8qRe;u1oR<_8-5~`{txHuidLawY?DlE;MJD=eMwaPVgsbhzJG@ z_Wbo$g+aSc1J4;}yBhJaW$w9(pP{`2lfSd~H(a|d9yRj-7#JLlm~&M&g0a_1TEBcSxE4L(!6y0j}0HpgU6bwh~x0D*REztBq7pv7JhWS!>m2d!CLGKm~H1h~@RMUoc$F(C@nu{248nAGlk6DQLJh{~KS^MeUtOqt` z^0dDaI4eJVfy#}mVx?0S!S)D_Nk$TkMSe>ZI^w#MzqII9?pQR zQ@Hh~SvfNVTUa<`djKT6m{5?JhZVcQ+i)b}Bd^-!qo@hoWNyCA>=he+iI6Rg!8|Xf z8wN#_hYbxr(EcMil3D;$q>Ie!_kXG52)h0V$0^sKFZ2=9Cs04z=T-a0jNefss}CIe zUmLvfM7RM#%?Ah2jqo@MhQ><_LjYj~$4Q7))-#ML=6952MT69m?#bBHYPG7bnb;-x zNr!<3uvJQukaWy3mmdqSNimOy9@$g|aCIyjJ3^i7j75*wyKAy(8hTrLte zd0FWw-cW+4J__1ZH=fmxS$Y6y;W&Bj-tJRf?nE3+HAMv_yP#3Aq z7f`C9Gcj)i1ityJL}6NohJtDR9XcHmcVgixuc!@WR&+B#6lUG?>(=il6k68V&`(m`!(65;}SKIg#a@&W;XeYux2ha`&fl;J0={w^bZ`{YHYbnpAd7o)CkL zvF(N&=)pukpHmUY2ZIYO=0{ouEBQ??DZ`2)2?HJgXvCfpPOvp)1tM+SOKO1xx41 zw?Kp?)bO~E-@+K!?}66I+vAIM1Hys(N@Jd@Q~l-C42U}2*aX@yAa^%MA3pf}vDksZ zx=z7Jlk{BYd|i5>#fOwi_%!6-K8t^DCt_OTx!-{p>ZPivN{|Wi5T@g!0E<{cjvc&t zP>G~P=dr`axHhsgR5!?`(k2*Gak|^lqtQli6+*3dxF>iI0;C#pLuZXHS%yRt*itXn zx87t6bjRmzCJCwsP;!LRb1K2w74woY`1v>E#O>81Zo&3Fp21DYVJ1b?0ok+LY}}IN z`IL)o=jK_O*^6z*=2i!sw6^098JHC^H5V6UWfNIM<>*gL`8;=G27=i<_e> zgS7wn+X#Pd#_##%39xo)7f&hcN}|Qt7owL@=hGZ|ajG)&k z-Kz;3A)W>45i2S14HJAjLW~Y3hCpr4U4{a){o%=X3^SS|_bmhw6jYxO7R;XwQdqzr zJRIVttBS_{SoAtRyhGD4{&-~Yx4RbB+?3k%G~@S~kuD8=Jd?1gbdy9I;|%wdc_~EMh zxyx~tXBQ(b<*I=XRGoB=L4TBA$Lj_u=YZh&X3SU(V^MGx;8xZF)=^E;(>L!~nfR2-O0)E8|Whe)I$e z*I=w<#g{Dx*+8gS2>Zs6J&^KSF?G%gpY%rg7i~mh2>`!{pJ>y)qf=0H%uXbNGS46l zP4f{&6lyX+N=F$z&72CScJq%0=i3C)V_w8R`2T%1yp zNrH*YnijXy3bR-^Zc-Y zRY_t&h#|rdHT}UtJN(AnkOL~&zK+SdOZ?p!Ip;D|brNAR!t5Ssba-kfrnPIee*@_d z>$Njo9(-yr(1}rR+WE?2kl60yW6P87NOkN-!82c96EEhOk`#zZ8wi$xk8b6_b2zfe zpF6Bw9*y5f6DsP*be}>8hll2g^vtqma%Z7^hm$GpsL+>^v-)fa4Oy8>)z$-uE$(^A zavYNcMNo9xx<$N(n?3`?e3KZfTw{!}kL6>=RSSiEi+^ zHrHj;hI$D^Z%Y}&YO+PUbN}FU*L{Tcs%n!VHSCrcuj1EE8F96J(nDVOUTp_@K3+f& zY=yKNu64$pW)fL<-=T ziDt^gz60J3OGdM1f@H+=`|DXBqtO=nnTAqmMkbJ=d8}^CO$n~~`2a_p3HaIVt0fao zH44)wT?C0>at_;Oo0M!hZ7b15<tlanJB41G zc%YiL0!G-@2hY5N!Fr+ly>>itViC;eN8Y{CK01V~Up!}zmwC(&2@JHBKY|~&t#UTg zy}``uF<3tF@|&Nl)G+*Y^f@btQm5YM!@pSDWpw+<;(_|$g@eM2;*j&dmTdvS-tOpY;<8>&2a<*urf==ta_}7wD&+p zPabZ8E8629PsP#~oHzt^_q)a2IQJ6a2WHas>&iQS&wCd|;GICfmh*aSa0AuU;B9nH zn%5~$Qm0QEY^bSLl%x-h&-df#%~5NU?QR=j9Xh3wUF$5tKH-!5vP6V)4>e+Bll#Y- za~Jf-j--HDr2YI8%!p%T=-sU9-Npf;QW*YOF5*Sb()w_4%$A;sUDm2Evu)b(eb|k6|aL8^Xtyd;kW{2V~4mOgE}$h=}$CNYHVo8ReQhO2PCQgu@s$A z5`-5Ed2mq6*!I<0+!7>Sg*Vv7+fnwx>DcwQ5xQN9kCF!Y246*QGYNE z{i!QnkXq)12vNe1mnXP`wlQ;n)%J#TRmvN1s1*(Eo3CSp4OSkESJVBAYfaEyFIFam3M!^lU+w4$g2hn69-=59ZLtOH^p zhRZJu-ETvYa%z=2prJ6Y?urGtEAw@~Sf75J!cM0*-!1MzDciS{9|5~{xsM0Fx;bgk z3HkdPzTw2%LLPEiL>{)v{zW5~DQU4;Nr?{k0XGfRuY(HJX{vqi!c>j-bX_+k{5p7a zm~|V?g7_uJXa46DuM0nd&2fkX>MR5@eiq2d07n(e@4J~%j}X4e4t4;@K%-cUV}si9 z&4uYA30HJ6@kqfX#z#-}F}pI|8&Xy`3LFs(e?F>rWF{Q@Akdc#NK2|#*dt+iVt3w( zB|0%e5zi`7n`|oC{QVl-{;W+-pvguo8joa5ZD%rcI~;_NdAOxqy443*@5-mpS>p+P z1b(f}Po@4Qm)Bzn%fPAZvCT7$VUf!9@ocJ);_+rTVuD~MY#Se|eG8o%JNIBNJMR6< zh}iD=rXg1a9kuCm1f(u!1b5>e0=?&5qTm=y1pM}nA|As{b%y3DjVJjYQro>c8@;>H zMfu8K8GDNIcxDdvukV?>$*e1CgaI-&$YP>+J)*-SWBok;N8n=z7e0B zdY?A5|7qc|vmW?+9Ph-i&r}bR*0h}VESEWs{<)X3_lU+) z{OZ>wItfASvtQta5e$&boZ)9hpQRgx2|=F~aTpVlr@wMSJJ^ua-!)?HRW6{;t@oBI z&cNxVnWGuA)~QV+5mLsVPE~#StcjnQG^r;bg9k%DTdC_l1J*2&d5d~vT?gM*w*O`ZqC35^7l6wwd)Jn-7yzO|Pd z=_^^)0!+^UqbdAvbA%BAFXus%Vrt$rgy}&$+Kvz@a-Cvwq5{vSS?)psSa`raqJ_{O zS@CksBmgK*8S+X0lw z4hR987IMt|-8bTAj_CU>gQI6PIzFk!R#;ce16kK3@&tDpkkP=(vH>uvg{D{Tv6IUR z&cn!d7HjXd>lguxP$UqcEcK9l(ln@G;&CanFKOSC%dgR_b zYZI5kIB>3Ccd1itHtknrvD_2oiZZH4)b{xVxMgzrF+2#Q`f!BsIOX(b_`fF(U32$y z8a<+&RVB$nB*eP@*u$6~Lr%Wb&>{b+UwhPvTVH>V=w@nBdiuLgU1^D(X0x)%W1BnY z1)qL@iF1Y9@Sio0|Bn{%{w}rrtr#L~WAx8R{_i`fbA35B)Bhh_w)^WR{gs8_p9}cA zl=R;jHvhdw$yd!8rT_OCHvbxki@DWTZJ2-0fbbuSc7ILfpY~rC?d-qm`IN){TO`8Y zMLXGqwPv&-k0sxoH^*7245MNuiIR}{xyj!yRwX4e218ZkBfodgNJH=es9@re6aqwL z>ozj)#N2m_$9W+eFIXN&DMw}Rpxi}ns6_sVd;vg^@*NRa3oMZDp^sM|ZyO{@_{2f* z@W-E>{Jb@@Q=z0|XKYc$ikT*=a?V;sIfz5{_S-dgAe&sdIhJ=Q5T^U8(W6wVqZQ9h zUvVuP&aHlPfY5jnQUV27A4p8z>AauzdSSg}ntH?Z%*?J}_kxaauhWBOW!_i^_!iYg zU1HXgH3t?K#R8YVHLa~Ddv=XeX#lE@ifh{D*(Vswh#yty&aR) zw$()Y)T>j=6sBIvmpKQd3VnmIs4WMjN;>| zI$`KcK7LiTdf<2Tv7Q9;zL#UXkM+uZ!K;|&9I4{`yH~*k5rl%SaMMG+axu=H8&>fU z* z-xe%oJ<~f)aG2bCnBe+_)@^hBj9o;LrPG||{n+7@g<~hEBq5 z9(-$7yMLk!UF-uGrDFC#h+f$azxCHl5E)%ofpd+56&(2=STPBK1iR$SCMN;(?h^kW z_TDl!t}fUXjG394nb~b-W@dKG%*<{xQ_Ku8#>~tZvmH|mF>`Fk?CH!m-+P*?8R==R zt~5`&=SO#|PiybqyH?dXy}DLa`jUxv0DlU}%WiFJwq5`6>25D#Q^d^8kc6A|nODtn zg?<0`!*FCyn5_Knd!Q>V>+lfU!1sk*FlYj2!69$Npm!#Xt5x+wUMu{!H{pYqdt4mD z=1l{4yw^VqhId+?Nl3GzGrMwb%-QGfd_|S-j`Z$0M4=FGWvy~rTifPCsU-P?a`>~a z*;>Zg%FKi>3v>jIM{V^TCEiXYqYZLKhm*;lm-C zY{m0^zT%D@PnF`jd9u)g+1@a6!A=(lXAX$O$}U^R;$E@=`C-X2s8^vE3jP zW|qpupxP-reC6^*b;VK61tQIse{@h zAaH+?J2x6Z(Ju;Q!Zm4>>Kk#>kYJZ(u*g>R)?pAHDc_5275?!&2+$IwJx8e>N_i_=c)=R;~bFMw*HWVgc#2%L0V@U>LCJH|sbaBd;|GMtq zS45;?5+-`({A#HE-jivU3n1IA-Ct_I zJOhixnjrhB2(v4dpGM>-7#Ww?qMj2|vO~xq2T4UuNN;^;< z$#lCUI9G-$+JyVJhToCps@$HLt&P2za_q_O6Z~wM*~1Zx>`3nuI3&bRYQ=Uuo-UO| zVLQl30zgy#YGMA-0=uqW7n!uCKC`^37~yzEwWo9FaRV;?8`QC>Q(SRAD=Yb{YY=0a zdAj=4@l%hvSj#IwW?5TM5Sn7X|l2f=&Hi*we* zwbI+@8*K#75Am*EyGy^cy>Q_&H}8ng3>Y(SPZieL(gkKc)Y`E&!#Vv8e9|*S#XrVt zwmh0=YC$_Q?Z^>6)|=VK-m~A^f;}d3kK3i56x+aj=&K0T!pbe|+i)RwchV=@VO&;1 z+hGGXocL}xD=7?A{2}8IHQ%HDP__H3So64T!geaxAe=n>oU1M9Ef+eDnJ)bTZjx27 zTqP{h*H~|pU14tfbnp09?=2+iX81+J9W7quas*r8DG3w(;Ah~B8%7o zdD%R75r@lgzBTqwF}2vkwr}{k!>lB}Bgj#DmL=i27_lBoFb<26ZM|)a(qUhPpN=8; zH_(PsO+5)_^*Es&zAPumQ)_=&fD&e9WfCSL-Kbp7%wBMU8J84ur@BX;RtJS8^+s8O zDx`DEpvv$f-DVl2mm(lEqSLU+hh7A~z4v-&C*oq$)y46bWCsA*&?ZNYTRQ5W>H>3A z<5HrCY>Xv(A_7)l9lpj1Z2n7$7w8v|do%;WZCmQ!ia(DrB_gwjJLKBYe(AnPMFL%Y zY-eO;is4H!Zg-2NeoFsQIvH%%yKJx3y<m;kw~%M)~) z_M@99``QK1-EW%9oQ9EC`w&q=?^td_sR%*-57<#1 z^z6s6)poF(F~rdhJ}(NF^Lv<9XY0rl%Ue<_uYG^0ETKZ%IdW3P{2w6%N6~G@6eGD~(b}9Zgj`(v&mUS$SJ3!5v3*(z*CxHIS2SHQbmYe4C#bYG5hOmCp zRb#wyc%%4wGXm!q*Ygu&ba|8i^)flHqVrb@G{3RijE0OEBALTFnL}hMw>_JoP0fj1 zYt3`Ldy5LXQMj&dRhT|JBO=zISB21SBLK~Q92?jmCg>!0o@6IVoLLFp^v+`15v0ils3dn_`c zb0*s6mwc7HIjTF}8ukwSr28gD+SWSs_e{-I;o!Cq1UQGzd$b$JAtS6=G?cp)VBys9 zHXg0lrL;1xf#}2KYAm#3Qb@`MD4DYo$2gLeyH*GV@Z~xNfH68Rc7+tq>|vcO7+H4o z#1e;rCMaYH+%H=~Jl9*+_X2BivRI0yDvboQw+l8C?=JT{HdIa`_+qV3V9 zs#sM*YoMb+F?SxTo0TF(&6EQh!t+A|mnw+VM&5#6%Ld9XTA;B{2 zU61Wt>%V76$oBrQkkr%Ruh@Y5vO!~kI~!_IqMpnfkR4~9YZ*32uX;h#0Kf0@5Jjo+ z^EJy~Tr|PPV2*yXQs$(@$t6pg#(Q-Vf?tLWNo}8IXucF+3@ zHJ(90dKdiKk{m6XANt4>Ju6mz$|A4i)i{X>x4x2fR1l%1q#b>A?sxqhC`<5Hfo$nP z@^(M$fW(289oj#m(3>7#B$nv%v72o~guQPE@+R_LP^e2#E;qYANO6@f1HE}(k5@_J zNj5uhgbZjDc~>kW`{;SzHqw$($r%-@sc<*1C?|_mF{PjIVt<<|%tbNL-GT`ou18jY zYTWBuP3h;HDXaPV^1>mIs#u^FKwGUhH0I;f+e2fULJV7lq^1D)rnK0-lw?^Ck4%1r ztC&}ncIKR|lf`4b{*^Z%$Atd={nLsJFTQf-h|@f(4Yn$8j|tyd?j)++RkQoH3H6x6 zV3)m61bxpopJjMxnbVk6?9`!(7ce+LjZ{xTA8qqWv-iWqJwp@o2|kBV%WF!?h0%sH zFT>*bms$+LuYgMVZz6M$0*oy48e$&2b;I#UXF%9c^HXrR8PRjsWEq>rZ1a^h%R;-0 zUvwDB)GwGclfA*bpK_?vT`by7qu`f5Zg)fRF) ziPY&Z27Z`8*z=4yr*THGZICWe=I`F=e&aG4H<&S5YtJHvB4Acvr_Rah=`n?-wa3NY zHo`$Z8H!N*^ir2{;t!v2B96@ZE&X)YM zz7!-OJmD%-Y^6vo-l(IZmS=z4(K_!}qWsm={MLygdIACHZF3Ee5PD)F8tuhS^cgo1 zxRks0_45>qvfcxv99OGiJ_)zyr=SmbryqNKRtcR?Fa-Km$QMP7C<}zI9>mUyM(^LX ziCQG|q;v|r=hH&0e4+kWt+vfQQ&A#eZCv;UdWD+W140O!3?FU49dODBi_%kS+F?A_ zXxDMgW}Y>VM2i)c*E~stYR=Ms*g=$**$zTzBZ*KoZpRnqe7o(9+-RRF=N^E_<{C+L zEV8SHB|n=tl`u7Dh%#WH#`gX#F-h*-%t1m*xKA53;T{d7xCNSG&)|I1?V}!;55489)Hc&O1S@_Jq?dX6a<&94jmWz0JAW@-8L6EX>f*2quu1q)d z)Zkt3pws<*2;1Q-i>RpUQzd?@KUOHXB|#G@!Jwa{ZL&nZ-sSzJaJ=LXGST#IoF!>T zaD8fn-^&CPbO2hvqPW}d7J2+FfMfoT6gWDP*3XD2DwCV7UpdGdYv?+klXp#KRxhf_ zmu7_5juWxKIf}JlU2Je?uMbCr1wRkhUzYcVXgp(Xj~?_bwe4?^@r2~U!RQE_RUDMI zzF1wQJ`?zPwS4oK>Np;w(%mOMi*z9RnVj#(ptRQD!rri>{8NWHA+rS`^}11V-cY%U zDfNkUjLj&ic{C1->XX9Gp+4#>cP&07`wOe_=dr{mQ}fJao}?F@^dBv)YoDrV^JT*f zRMLiI8;qeKPyElQ#G~*`ll()4XMdCWlT|(t-JGVTO_L1C*Qc3jXkpj4Ly}=hbwivN zzP3d0dQWIMEtS*t#NXWqslsm8b;G-0XzrvOMW_hBozCDH;4ojTKbT&>xdy(B@Q)v8 zypQ+rptBZ}r51Ys=ACRa5;gCLzzGhbJlz6whYF6*lUQeu>O)rZUhZLbi;`~UfYY5_ zN^Vg46zKg2J^fSvZytlrTX|&SDxO1Vjhv6Il_w6KDE+TltvH9&hvsaw+JT*_G@^fG z^H2~|KG5zmo|EppM zP{qnmARg7;(#`Gvs{irt2>G0yAOE{_Z2k)fYfH!fCr(d~k%+(7LJs zg#+abGxu>3@Ez+7!}ql4LmH*yJ}v(l1rzjM+ENsMT4WmSErnz}u%FW?=K1DDDA38K z`~|kCC*W4NK6%dJ7LVqJ-7jB?hK6^huJDwXv;cMPXe1&i8KuW9(Z4QvPN1FEsYB1G zL(OQW-X*BPMTX@kCsOevJ$fQiT+ipYf^qAS%HO47-?a4$gUJRp(gA`;duc-aO8&;+|!q)JX&r^v>6>xfo9vOZ_US|f2)j7vpQ-3+f?S-G{;d!fmNZ6XYFAmdMURzH2Dw<#27pQ{ zI~(vci=yl?JB0dUkHF%X0Fz{#E#VVu8r3SAc=(CRk-yvqAGIjC0x%G{IB@mn53Q=g zHF>d0RpadMZ7rF1hdT#%my};A=21XP@Vt&KM~^t2z5m{iZpvp5NeY=daz`3~R=&>& zUA(Xd{8+{hpB1&7MRn*hT_%LRUm(5e*j*oNK_oIb(sMWNc(Twuf{ZVrP7*3G?}$yS z{9=a37ry0=+PJ5-UW?qrBm8_PO4Onf4q%5ksod*^N4a2ugcDY5d{VR1-pbG51lGlw9aY*ru=p zQoWkj<0iQM0MQyd71)7;?aR2vU%VE_TBk;^6CLWc*mai2TE1@OxGU;#TNap;``1Gs z8sKDZ%b_Z<8JNT5q?CF2M_Tsme!RA^^s1P$owloM~!qzJWT$sw64FowXb%aPD7KTK6;P<#9 zO2FqJqGJ#|G6;pAZ~uU@Nmz;W7{uZi+Lo6?Dt#ogJQ=93*^lYj|G2?+q!CJ#TBH)u zwidfN{{DfDlv#8|AKKbZE6!84rYCuQ>p{U)c168=PxW+M^owE8oeuQf(Ya@@%hk* zRuh#TBnPzzq%pK!DdIC-6W?nk{E*rllj0UGh&q}wDvUj576(+&8=;{IUe()4-kHk1 zOak&V82g7l6;;J{`ao#>UUqmJLP2m52{*5ev@I;@V+`zT6_FNE(e;NQ!q%ALR9V3} z{fZr~ip&5I-=f=2X~r(50~_!_*^)vlH=npqnidZ?MWgm74X#|^(iemF2?82SHK+R$ zakY}BQBVm-GLwQVUYr-EVr7~7?US7>>=WAdngt9Z+GpT)7)qWPt?($yOA3dF5~`@- zBH)YJI4cnqwEoI0AV^-p38*o(6ob~dEvn&o#0iOq;#bdPurWQb8M(qt2MP?1GlDga zP!fq)gB{|{s6ZM()K?TxV*fmC9v9W$Iq(o3Tu}_cMAwMLn7Jx{$>e+|%2?rU)J{rK z(^f@29Wk8HalTW@;I_gK3^SAsD;&(>pC~%qvN-=6-*=Y2f{U|r{z4(0d^B#plDRkF{e+-8q@;b6Z9d}f2tcg zM=X-6FWY=PCi@Y?%Kzx6K4HkxmSRFHk1Tfd$u-U}zD(bo>7B8BwFpc6Q8+wuCER?9 z3S|6&rdGQdcUsxHU)mwBNx7r~4As~Y-o#qg%i|nHw}vBODO+D@3!x7|>oc*Jr=%-J-IO10!6xUvqE;Vz_rAYUJEwVz=LPw~VvCVl*QTN;qGQ9|`gmHv0EclRL5DRMK?TOu5{+?%}K9S~CHeOgA;}#DEQ`=J720=M>c?j8{ z``T!aQ6MEQ`^1;Z41N;G`Pzu%W?N@L!XrquE!Pkm z22!sQz7F>+9pFmgsVk6=~OU{^SWa$6BccKMDn{gzk>H{7f%&3nVRGCl*GS z%yoB=YoVPViMELW!cFWg)UuMQEyUsi^AS7IvsC}K;C`Y2`KD< z>=E85J7(Y%((;`0Xs2=4C>j(Zq>$laG?_rcbKGK?AHh3Hfa8=r2MLtLKTt(?GAQrs zh*Q|y9Vaz&Tcf~v6>gv04T{Ewd37S=B`Q)xCDF*N7oPzlr? zL1G^XVNjnfgg*>|{W@He73k$=;{>z6WQiE9;ZnZw#+!9!{*mp>ULcUOvSqn{g8j~_%NXh% zs*df6&5Kl02%b0WlRbR`{)2XnfBnyt6c;tjYcL%(k? zwYeD<+B(_e?vM~BsgB8!c3N#s^ckWUlGpMKkn5B73rsjjXO>2GT3J0wR?fYWU7pGV z9m-`^S*;arwE~iiB3oP92Thu$sar@MIUIwRf;GO}%^;`3@30A0`AL@ks{qpMwSO)o zmUEHPH^bkS*>f&LZ{4$prfgqUI_OrUe0arMifX>zoqG3nmx@$YTMhU(3Uy`IErcN# z!*^yEE480}jnSLiKKZ1wq@9#de95^GDTRG2&(^!?AWr4<4ND!}dlvD0P>4;~J!Z_vDvW*MHws)9ZGK7b%P+YLf z=jjw%&$OQT+HO_F()6;PNnEP)!x=Cq=XE)0svxkL`~qEK)*IpSp{IO|bhWU_=JuW> zBYMmNoRg&)Zg8TpaU8X{gG>NJ^6^eE2$U9mC&J4 z{c>oYJ}y|NLtcPN*ufVZRwq25APnbdwdPb@P-P|O#_jcvbx@p$Gb_G)q%)=F%ai6c zEZYxyCQ{yRlf^t@_zrL7ei3R<%+8;fy6=oVZ1;4dOC>m{jJfFh>6@Tx_5%*QP<6t)a6V#R1F;1RO6+g z8O*>`Dj=LfH10gL#~Z`xCY?M>R8y#o9|DhpXV1bQ8c$$2^Ldz*lGw#I54>55tZ*(|Svcprwu}`B>Snq&0j>&2;RTEWpV?{N1*__j zMsai{lceo$+vS)DM!uEKPsr-pZ!vbX8(pgZV6J}DVUl7=RuQ$}t$K{|&}?@-TOwuA zEQ(5@#AIT%qhtGGILWN6Yw+>vn78!oEHhPB)Pe*rPrgd|!&;_FRW5+h1^nqoK)3pd zx)!lPiox*l(vp}eMf)O>yg;v|J=+%N~ou65#{CRjwb8EIuf7HdRnKD1GQ z28bgT@B{W0oFN%VpP@J7VyUbNuN7LQnbC8j&t`Gal!EX2OiVRfrh5$&c(yft3;BWD z!}+2_Sz-EPWuL{f)l#xx2j@ncMm-*zk0_V835IA4FM)-l#Islq$#0*;0kU?CIWB!@ zc*&aN^ei1Zemn5m{w^@5$PhVJYixA(yJsZ#osJe;!vbBS5Ks^^v8Jl@y#4IUYK+;>^X3Xwr?j|>-+Xu>yjs)6lNuuq`z zj@hqCA1(h@^?57YgTMt^I@fv{qAj(k7cD@jOKl;;VOlvHNuA=w9gyc(l>ALpw^X(L zC$M+DIdR3Q0p?_{n0TkME?o4L?wxWj>{?RsYu{JnfHdk@;N3ERiiVaJcH(PT607P? zh?;Y@h5LZ%6PgP(OaIxyZ{M@D*Nsa2JD*$ada81|b!u^NSf{M^h_Cg_S*!yHLv$D~ zu$Pp+=AjAvA_~_-$QUnX)X1v$8?*u#m<~j^)mLl3Re)TdAN96RqL+*T)qdNolPpt+ zotKQ083V6pq-9RHJUwU^NWgm*CI=E=je1KD;YuIw>KAd%<{A^!rW@IDXR)s)3(~cGtYI~+_X+l)u1T`L=PQVI_n42Mu?_@ zv<&grrHQMS>7i!Mi^>&?laZ1IdLI-FJC7*exTir4uGZnL4G^SkjZKX8<%iW zs(Rli5WvXUDR49^(k`I13mexpDe(qrmdx?)M!lfq-%+?7Z2q>v`ah5V6yf~;z5EU~ zXmU{pGY>awOAAsPM^blCCa>cEt&`P%J@Y&R%Dz4PZ|3=b7v}kIafU7y*uHZFiT%>(UCqn!s0z zAC0nOSrAu+rLd)BE9ad|+N^awEov)1ddGtDJ>U5*|555rJdzx_bp7HY zleGu4GY5{|{7+-aksjbYS*T0*PH4Z^hE!4kxH}W7F-1bn1ElwC3R&1xUK|~aZt%p2LHz=(XU;?oiTh0A7r8I0pFJXQGK}<{Qh)OCKt3p;rQ%aG} z*{-Y-306mI7Uz+Jv8YJ+Pe^dYl3`~n*cMC?4UO8?low_^0Iu$gU8t(8D-!Ec@C%r^ z`s0i62kW9=u%i;mke8cIPbu-Uh@q}aTZ-NOCQ7v$ZFHc|aUQM)3NE$6lt+MmfDLXw z!&mQZZ6-;TxX|Dvqj3Adn&g(2_%x&r$J3`INDVf?(6N-!w}bP7KAuH8He?e@E5Z9H z#VjsLE4Gn-+64kwKw`utWVlh7Sqsm3%hJ+q?pDlqCN|(M#I4S8U8z0B0V`x_qUeu& zQIYard1ZPJoR#5wGH(E2du8M&<3k2bC-D-T1U7gFdRu=%gkbn!q3R>s(+YyzZ!G&VE5<_y;j+bla_jp zk8fPwVwfcSZrbrip*s$C!yrx-r>l?;LFgR?9*znKi21;!+8=qr8Op+t>?wDXzU01HZ*pZixgDs-oFyS2c zxMi*9w@~D>sQ;K&xcbwahHoJnY*~ZbL*4}@MlwVPi|omWqr7#eoCGx)Z>H(xMwr^hU|(OFp|Y_H~@ zZembPm@*mgu&GclnNBf+yRrw7l6l50guB%0x)3>!-o`*0S9(E7QljRf_YYA2?gyi! z2dK;4M7oIv^V!7-p_KSRRCCa%QK+i!(Yc`cSi=fBh`5?F#Uwz=4gn!Gu&(xim3%lI zxd@40D)IvQxP|pxO0jiB#JcL&(c7`ZCW98Lf4)O$YQFt~Ue$Wx!OT$eUEEtt)n>uF zWqDiiB-(<{sc8$ekSs*-JeQ!7&MKU@o&0Zqp$fP1XS(C#Pu#H|nvX6;+gYX~&3|S1s$3BO(%|SfVlI z<~b!^zX*0g4tJ&@0zF7SQ50g^ritk%1Y6>^q9StKQ=IZH3X^WGPvSm8KPKG#f=46u z0V9qBSLG0b%eD-ff_0FV4n^7ZnfI=H$LN4|oe71gM`_sFoRe1+ques@tVrqnX&qiF zy^earIY1*$wQ7ZN;J9#QI11H)YPF&Am`x0IA!oLQjtp78g>|VIY!%ls3U1L1A_~)7 zN8Wqc0tpf0Gg8TLk^#!ZDVDR9j*GqJisk6s(BUS?Tc=2>0x(-cgckIgZ|lN^MaS;9dX5G3^mw>E{%8br6yIG+Pa>y;y#oV69Qk%A^GPPfa{#0h$PXSZ=ROtS>67RW0gybP+=cnWE+pE1DEEd@|NPT!jq4WKDj zN(TTP+TKJ3j$Z7aa{=V8L%4??jT}z&6P9ANtIh&EuvJ1RryO2YB&VtUtD!MincEVL zwL9QO8^Otms8!jmMd{3hMg#!>M9N21xBxSOYnMfT_sj=74^mn62m$mrxce zC-qhjzTL7j7&?nfV5r~eJNbeI+K@E4$s&(?Xg@|rFG1&$-()~5^w;;=4mjQk5n?L- zSXuc1oQ#M#=2$at`8@wv&iRQ{i~Nc?D3Nk zQX|-Qz5L(f%)+!(c%Y0GtAlExX20s?#kV{MQ|Q3;nvuKfe(HQ*41bND;sImOViFBY zOIB)6s)43g*o>_hOkybrQ?jI}f7Tk;Hg5QG(~57xfP6oPoeOiW0FtVFaV__bRaeDt z1mK+2&Q5Hm!e>@<2~&UT?u*tz6*9v~)cFbP1r=?oh3{m;K;l-_Hp+Rg>9CAp6$Vad z*q&d37uS34-(zOGo;G8B5K(0*JNR05RjYnJa(2S!&a;>31~UU=e|v(-Pcm^PNy)s4 zzzvUM4@oVMqTtP$0%VH;$6J*C344H+*pBP4DSe$E8h&ADOwZ~^AMLK4KQ&BU^nB)Y z@6dNx$5I9`=TuNdw99nGk^WLQH#RHAgDMCHURoittKMnUP^Ndw&1hjJ-L6=(p1w+o0tsOXos^A-QVqRruU;zP^73Cb3`Q#OLv=rn+3@04i zRd3@3rcaDz{%*dCz|%{nwW`ty-%%0>K0ux=3w$0&+T&x8iGGysf|9j(8Z_+uBH7^g zGjPh0hJR=Aa-c5xN^Zs6uBcUVT%hM(ekWURlo@G3qaRF-7)r&V0+D{Khy0oA&2(hK==a zoH}pF=k4KPDLRd@s(i6Eq}RaokxfbLZ-EVZE}EM}9g$v}U%u0?Wc2 zritape(;jZVxX;0z?F3Aw0qcXrYdDwtu9RP$-v!easiaotEK!I2lM7qDL;$`NAfI4 zY}6(<=%Eu+^cM=`@s!kC`>Lh-LFKZtgO$BJduuS8pR5_=#2e7O|hML0z>BB~ksM^ObM@+4iJ(D&KxtC*vGRbNMaHAAkm6huGk^ zh49XMSIdVq-A2k6Icz>U(~!WMI6I2Yj!#uV2$C0O&nSXDR-$6jTZW6t`cXfGVjFx3 z4=qhck-C7ythtAWF=w{mPCfL>DHeZKAqh6{P0rWF%4utYeBb!|$fD z=cJK?s&`_qRgfg>1R8}RnFcol~>wlUJ1JFAku0?KJ=&a?za%!zoB8@g3Sq^`7xf1Ruv_)5v}lfibv%qa@ofwt`s&0l4JIn4aXzbD>{`C#$TH&o ziU4YU=1&Axu>Eq|2~d8Qg|vjGgUs)(FbXp2CNHK2V^=`x(3m-repL-1@N zN!o%A=L1t=e#6giR%T$Pl>S7m!lFG=&4gW9HO2FzY326`FO- zYC-+fBs>io6+#j}?VgT$pjq!qgfbQiZA-PxPu9chW0xeFQ1FB)G#P=omxq5_#pJWP z(I0qSczqR|3VHXQr-%8fZol{G{i40-TL%X&w8;Ja5f02)KT*p2va%+$Qsm$#)RZ?- z6=DdHwFm-R$l- zef~>R{o6qPFB$d!Dnb7XwttWRz*B?wqj-U+)%rmzj9gUe4?Wk zL1+epui0Nxs*4$pB!G}LU)!Mcf=C$tVp@Ynlh(X3q4@Hti#QBQ0_XMb?EYhl{T<~)oPwvO;x*l2fEF;LMurD7~@`g2y9 zcGzwEX#HOey7>glBjv0Acs*|I3W<+Yr(1;adeL1h?%W{KG%ooBM~LtF_s1PyYfz^V zt|GD>S6Oky_5w4)ft`;hsTr2@MX?f5Nehg4qbf$$`O);k2Mhgq*v>_jdHbr4>H${H z)qZKa3$N0m;&nAQ?UmH*tRx7(&<~_^rhPF80j3wRXShbO;hQ@MD z;dVErxvq;!<{Q)I1D4eJXsG$&kGc^%7<=ul4Kh%Vbi@eXHD{dqwCoq@l7Tju#QqEu z9XWP|s+u(nLK>X>77nd>=s3)$dOb_EoU0<{!B2GzM@Hi2!P#zTqym(fwVX#hba3Dl zc3eo1%zA~;Ro|2#6P3+vc^#MS!$=OYr!?G%Y$~lizvl`lh+D@$=<{sYvssCVOD>wj zI`li7ea@*peB}|c=UPSW)Z-Eg^{vw962iAeY~ZV9=A1VOd(gL;E!{ekCiHw7o47H1vtcL}d zdO(C}+a=qDHuq7nEmkVGTGw&fv zCEBpUKcn?Zw(3+bZGt#YJqSAE=8q?KDt$Q#&!}Is-MHn%>wgK@cxie*d02^8jv_JE z*4uVlTFEf3o@+_J6?;ZHcGn&KXAJi(TQ)_nnTO*mRxIL)+LF8P$Ep*@TYwSVDSb6`= zCD@lg`TTd!Uu8jiKJJUGdXr0{*KVEGK52^7?6mtXAU-9DbpWn+i`#oRTpGV5s#4vd zCD;y)KU67dcz{8L4-~BhS-mbq3S~Xlp>ePHYTm@AmeY%=G9YBp$PNCeiehh*)=*@s zp5JawQF_aV)ESJaZ@DWLy_!tf1|(1q!;e&b*qilLA_P?hN4(Wr>>YcToFrvsms5x8 zc>H7YNUPMojMusBp>sKn+__l=0GB2gfPE&)Dwz&r@gm$Q0rO@?M-O<@#XoVDy5ChOA!u0X>r>l}*-q&$MB0Q_yaNU$Az0PUv`1&of zu!Gdutd_`@aRb(6z_x}ZunGhqJFDV({tvC+8)U`f``!7UaTlz018z<_il{}p&oh{WreUe%F z7k>}(YSTZ&7!8?D9It3%*1=h25qHMZ@zV##?R{wZpdzu%YDziKZV!lMYZJ--+Nc0; zF{34q=-V+{4<{HuhG)6kO<(pIVLg`H39KNC^%;|9UmbI7SfTBv>qrKMnoDYW%V zYP5SA>GickERX(keri@S6{edugBuU08%#_GeNRqy$ElT*)%p_F2nv2w+bnbk_7o4YiFk#Ea;Z5Wd8g~dZm1Vd#uY{pQ6+Ym|r)U1Q zt8IW7ZD%Zv`s?i6Zd z5i0rykJ0aN2{f3Nw|Pgg{N5=*w_Hs)OTd&F;~e-^MO2jzfNH%iWXLy=)#dJ_8K1^B zKn@M|I5ZZ`lzI&?vrlJ6wZJ)n8_)A&oT>xi86fYXaa(WVLrI1wdPBoGz+l3Vav3(L zUZ>?>|I=ZW$Go``VJCyMqhsax8G~&XO>Cu^;kq9tpW1?(g~-=*-#W6I!3z!Qm5n{# z#aM=cR1-@EOqflN!hy!@+yqI>qJmOZzPU{$%uTFRqDMGaGZw)oxG^)1nQaPI2`s<~ z9t=+K_X(@)VEkumbDy~jSHZfN& z>nh`^-6f6n-d;7f zmPC$ku42=0o5n`nr#?S^_?^^5dgYr`uEjo+w0z7%egFY*wsL1GAu5lTV2ah=5EFaiG7N z6A{pdZf_1Vw?t{obe^D!IpNmeDhE)J`*!sF*>gHxuNQ)!`DZDwiw|)eO=&6|jO*x| zuSaYWP`?{P{nx2tO)e{XIt7#W0cPf)pmkDYJE1wk4Qsv@4!fgR4IcokE13piRoeWr z(#C@+yJjJU+$n0p<%>RBjQQ?^u)ft~@sf{Y4@w)|ZgNC0%$fe?ksFZ|l~se{$pF*O ztPPRDu!wfUaDHM{-)^1lMXyTDz1Dl_kZc@UchdLu| zZt_+i6sD>>HXMC#3Wpl+w^?q^ER0Zqn?ByKs-pWwvT3&?1Z%tNScwpFHs*OAer_`=Ng&d55o;gmUSQD-_s9 z-CQ)&nzRR7D6|RrWtxgM46-u%`=u^dbJ|)4_7;9<6$0NA+%wnuhI=cOkBqB0J8-gs zD88EqGOSRoajgLu9E!5mOSi-$pS>Q_4`Shj?f8fmf2GhYPjKhALHD%X$@18fe!Pi* zJx!7sf%j718-0&b(B20~xwKY!>Q!vdAUBOdJrC~9+AJaBEGuM_WrQf++L9*2Woqj2 z*eihRcn{)GHUZ*G=4(kNNVL`lKA-pVIzPnX@_Rb#C@|i%?lTee>k_c4W@xM&18tfQ zEUnlHhBCoNH^0Pa&IAU<*Amdvahc=j-!=eTAD`g1g~)97Z$bkD(aeuZi}pLd#X+SI z`{?Gj6Ru<{6UN+QTJ7)UMY`NThv+o>{S0*v5~|?P7$JZoteZHj(HFm;32Oc+OWvuh zNR-|=lf&1qHowG1Sw+B2-TRw*9Mypkw|v3o<>8kn^esdpG`PcK)1tmrb@(AyRae*7 zA?UD%!=}p)kMDCVW{5BmBSTrgaJOj*fNtFZD%(s?XDrH}Xl_xry;6laoA=$a(Vvx3 z^&-jJ;K1*|$wSTfrf$5&f&`T&%8r`ga&{p(&=YL(M~<6EfQ7Oi z0%N}_W=s4Sjr&svu(KC!{tx!vIyjDIOY{_27Be$5v(%EsELqGfS&V}lAJ1{4&glYQl3v&2yL2q?H9t_C_jzXAPyLY?Z#>8P3@@GQ>P=3;j3wH* z4a<~kNP5gdzas2=r3GL9nsN~0t+C6bjQh$~-AmseB-{v>a|&h<^g%5YB1|_Yzp?si z0G;Y0DQGrw-k$J_U~=x2xoBgx+9jX9lRe?4@&R&LL!H|(Optb}g-8hgQuVO6GTV7M z9Z19+rs*DWM3wDE#3c&MQ(5~1ZT7&Mp!}Qea8|7kITc~##M`RYN#wP4BDXF2x0_qk zYN3*@OfoGw3E=k~5G)>I^XQ#vB+?Q-^GbsGR!OSZB{89=-2iTqYn5^iu(LztPOKyP z(u>f~q&={H*h6JtkMQjAAe;fev2Y7OK!~5D<8(5v^-03jOHjZ{tS#GaIJ-^)kKaX% zw$!YIFgFY|L`(w?yPtz*ymsCTVH{cB+w~l9E1&?LGZ92$*Imo2uG-rHwzSDFguk$W zX>9&&`!9i_k2oh+XDbr}hrf&a&yu2#=ZiU*o0=OD2|9c;Gj{m@KGhfuFi94Ck3jR3dVhl_X z5AsjS1rK~xNYfcWzNbpY=Ey1)T=-pt&_RMyt$uiO7V`FAog zVCt^_-828sOEvaUBS&EBI1^h3W5@qZx-sZ~w(K9mv^KC4F>o^YFi3Zx+%i`GasC4v zN!R0wqZ9l(ZQkvGiYb6em4}AV6+q!(HW23KNNKUFskJ3ff*{w-PhM!vH!n0j&`qQ% zdxU)X>so(6&Y_9=JTO!EZWIp$z?9%uBV?7Gvn&H4H6_>#AnozDsH zaGm)55W-vMP%Znjz1zfyS)hj7meEfLZ}*jlglN56SU&e?JlscC*mWuf#*(k5A1pi!aH6>o}rHjjBXy;0zS{<$2}` z8edbXP>XJ~uQdTwtvGni6s_UFU+$Bh@=BZ4rG#cW0zXy<>?W;57f$J>?9*UNKErRI z(5-r+&Xm=jz)RaqS^-WfC2YWx<;3d?_RMFp{UMWnQYcjvsaI?pDYMb8 zbTf!pWY$Jlup&&;Kf0)}#(EfRyZcRivU<>NO8MHg z9n1h#@&`5jrth6Io3vq-{-WrDt4f5=pWrmT@uYqDM+H>Drs#vNG&RVGcdv?rWkZp3 zF5F%PEjezwp&$y?4dNKZ-Z%E7j4%}*S;zIY;f%1y>fl3pG#WsvL#q53z9W*)v-V`Q zs4=@jMVViIlYbI@c|e+kmc?04^nq@~shK^$@~!2ped>(i@T$bMifFz6YdJA@#6=yw z@kZTq$!@Bjswfby(F&BCdo9>=D575o;H?L(kJ~pf2bB1=!W>jo?bquqT9tY7U>J71 zsIYfqph67SW~Hyud$I*-!$TUEfzbh}SH2b$S`lG1*`Xs$0fRzSN|taYE?wqgXJ*w^pO_9yNh{`VM7@ zEhj?yW1)0aDVd-r3Ov7!wu4{-js~IrEN;|(_WI+J>a9Z9-VmisMD(u_pqIfkX80vg zm=$fI&s7*ds=V}oidGEUwGsh8FGhk9uOc-R5^#p?4xW362OM)vA~$>kJpH*Wd1}PE zNUJ$78dLbqdzz*$Aim*-M`xp2FzcOHL$Nidz==R#oEn30kVAo27~Ameu4jXS}-?9n{9dT2A3AK7~djHyW2v{V3FPsW>86^HG2ye?#&jO4zOdmlbc$khnNKX*ESvW?6 z!#d(=rjo|A-aB?AX6CzwYl!eFo76?L5?9K)^wyGgHXXJY8ANw%HZu>S!#Zj=F%7E# z5A{rtGl`j^yWBuR(}!AkdRN+f7u7eCDaSa#gi@uj5-uH%oim-!7xlv~4-Qe}&_fRf zuIpX;VRHG_Nu@(u?y$XRYr8A~J=gci&rtiVewi&>C2D-Wy#m`cwcS;ec~}Mh%Ocm0 zto7!^FA`WaXk9XL7;p72ZT)U7JqNXLCQsPmNW`SV)95dQ-)i&=wxo4Ev#QeJ6Q5U}8MmbowzKk+-~<_!mhc^Cl5kS>(G>*rM+|svqqn>IoQL`NkZEfuJP< z_HCdjk;h@G{xMd*4%!42ZN$}JGc;G}?14IbFN;WyUV3E>|OwiG45=Jc*bW=&U^*JyR}0JrW{I?mX)Y31}G9 z4Sx=37U-(q0-_LHE86u&VEtkZY`&zz*ZpuM2S#aFipxngGVp#mdoCToaE28EiZ>l} zQON?RZ#oMg=sR)5h7r7Cy+E0+ca|1mF(m=SOoTQ6}T` z41$=nk=F?oFVMTeTu9tCz8u^EWvZ5pmg)BRCC`e&1*J?T6q#n|`=n&StNFyPpXcKp z0m2utA|$y(V_?#wn@|U5WsHKXII)d~17&^D;(wBifH)(e}5*ZHGzq6As>E)ubqJ+IzAJY%clC`PWe1hM45YMvrBGI)@+U zQ6K zJ|jtS3wm5nGV=ud$yO+7d|gK;D7zsU@9aZxjDnp5UOiFZ=BXm%%mHq=y`9L5nT-I7 zym$sNUw#MuHWvYc*Y}t}$qn|2k=Md~-v#V1M5yt&i3=^GrjLezdc3;6OM3w>?@P+f zrTt~C5}(s^zlVc!txH3;4(C5Q3*uhu?{S>`_!={LHA#NP2NudHIn=6SvOR(WuB zT;#;ZsM#X05Cn6x{oCSH-x#L3s)pQ<&jam|CX6%+Ud1DANf9~%F)E;*OwBNUPd&a( zXJ`ks&KbJq?J;smQ;zk6)D2|G^NGz0bh$EbCrgF}U;Vso|5CN;fH=YB*SRyW|0hR) z;QWC*nbc_UmV&vIfUyOk=G|o%xbN<9(FI@=MXtRLtLjS5hsRqGN|BJv-W9Yu^ zZ|0k!o5_JUg={U$G{T7IdM45l(}79dWUbkFYXc)Tss5Vkg7TJ(ss2W(RO2Um5GVWo zX=ARDl>p-osASMT+OS|3%p$Hkw=I@G^sl#skaQ#%4GqKA8CmfvjFQ?uH?+cH#iqdp z7;MpC-524>;4MPP=#@+~@TR3-~o_0eu`SzH8>tqr43bc-z!KUTBqFh zf~rxS$h=}>=+>1sSd#W)dD(L*7}i0%!XLXU=m7Ce{ZT#0F9LqX0;e@vPwWRjMtlub zFAi;aLqh7+LA*9!s?cKxCYjA({hOxWUq9La%k3NT&vLRgiB~JZcNKBO*QCYs+V0^l z3B}>;GO||mRG-1wZ{DpHUx4W6$6<5q6kXmCgD}3(5K-3MlkU7hjBGJ)2}1H)2is9) zkX+l+r<-4PXtC4QI|NrapL(s?_WfL$PI{?p2tpFD8VR0`Wyix_+nQk9TiJU-U*0~M z(x#vOUN=8PoK^jeM{{TpsQ&30AUMm?p2+E?`wo+6F0zXcna)f!Qk;+V@O;PF87osz zted1*1u~4RMA$MK6&w^}=Zk&qAxxOno%}fC`s6(P08P+BDcMNlgxqfSikJWC}39FddB@?G@r+FLJ&;)6v&&`~zQb9%<1<2M>FsGNL3K zNFWzYD;afNk+f=|23BsM#CliO1NsieH%R(lguOTH`=RoCBj5G0zG?%sv5t{%ya4dM z0@G#_+9IDlVRagAoZHM-g@l7%soAF&x8itr{lN_+@m21s^LE4YMOa3j$Vg3pw0lrg zXwoc$xScCVtFct?UhRc^DmPL6*6~9swXMklZ$P8{X2_rlt&_^cPiD!fH-}d#2YnOE zlTw?!=*y!}SQeu}%fz{hS64MpJO^e({i=vuv1CX{+-)^vrqgDPo{Wvm^$lGrdjP`p z4G93}sU`5)tc+M?tqi&>nb2wO)47VfwPoIzorkol@dGo$wQepxxd@zy*=@ZN&BclF z${(mo!9WU2$6>lSavr+0A7)#!pVx7AYt862QD)nWc+W6%!jqM{Ch{$Leq5=~+k5^{ z`Oy>;AO3Z{Ipam`_j=IpHt`WeR?|NZ$O-jIDy}~kI|s{oW}BAcvt%P6e;Leac6`IQ zgwM)XDTc<6-LBvq8i~P(L6+jT*YYu`*+SFbg;Q8W>v#Gkq}J%QS!nxJna!(p;AizX z;hMQhMKCT^x6oN!M@U&phn)x<&AB}Sx~0{|Zo%*o2D4IVfM}#*o88N~PZALNt%(Tp z;I6E^5X7!pVAVJYSFvEkD*Gufr(xkCt@l$StWRhOwV65lTGCjzvOn00IB$rRX+;@u zfDNaP+QbQ$94xh6SO(>JEeXC)43hQ`uso%-Mx{nXL4f#O$&W}*#Ij@?v5ewwbO zQMc5cSsmeB{i75az9*%_d6!6sVnnAS2Dx^Wt{ZggmKUufiRaT1Ui7G&2@sJxtz91r#O86iMvZ)(C!>C|+2s7`!559iR{ToaCmq6PG zNwRTvaQ?fn|14(vn9lfbe2qv3xQZQ+F8y7^|NB8%N- zV*}v+ukAx|=0+ce_d(wN-u^>CmORkF6xaT7@`J4D#o#t0|7yQ|!W%YEXFJL!qEgO5 zw4Q;aEIr?&mauAJMk4jI-n*~SR6uboFYMFV*uGJ|+r2N0dF7%uEsSeHOIaf?MK3f1 z0m5nruG`&MR$)a?Qw6llj4+=~^laCG;^{h#x8V8N(5n}=vbokrGP1AZknb3TX3+e< z(6iaPbl6%+zi=aGgAx4FLL<`Y0q=8{sppByn!-3wD&vQpQ>k=vt$SQ>S`Hm9)iA0cW(M|>p^kG!}l3M-<55sqdndRl;=^e5U(y+ZD%=B~HtOslYn&N=Ln?w6S6 z^s!k7P|Zm0T5h(&KgsbUR8EfYZv+A-41)J&JZK*JJ9fSL$Yg@S!4Ih-AF zEI+<~8xk^&`vdSWNzO;kJ2WBi#DHyGE2(yStx`7du}1sZI3r@2ADr54-*M5q4Cz}E zqtQ)#W8QJOZLkFCOVwnV8|I=6$BT36IbsxcWDDoTE}$GVK|v5j!vB;LgNlzhtMq5V zjJ}xdtm|kb#5(Z1*a6nIup_L9HLDe!RjyOc=)vc&0O_c_kqtC@HzJ5+oF}_uo;5I_ zc+at&Hc3;WgV=f7c<}(S3s5lMtvU$7-G79ff*x!Wg`aDh)uD@A&kG?G^dQ|Ee>{;NPxRvFk%H5N>B~^~%z<5%Nsxn^3s&g!8YbXiJ?>D0a0Fd# z`1=7#-Iys2ueYu|JfgGoY_NE~LhY6x8R>KP`@;B7CUBO>^5`a%OsyiZpQ-l*4iv%% z)ui+|EY5CjP3=agRDoJ{3QjUnIQjPcnv|8`4x$VGkaM6#VpKNpF+-(sAx85?pq_zL zXdOD!`S!6+SfPeM9pWLgTlfQ;ohP~*YPM&;l%2W{Y_hnXt^2|*=~=@UgxV3@Sfeex zX`T zxrhtss>daefJ_BEuV~%u`M&ATxX1K4&^c`zFw;o*5qqeW>eolxY|TO`0L_RcFPah~ zcj6hpauE3X5@?-R)qh$N$P&&gT<`C*WEW3Zi1atE2Kq>x7e3t-F-@Jmd7x=PO43XR z|B!`R-l~}i3ZWFwuZ#P$mXAgi{<5-TP#_@ina6goZJPAqLzZajJqYOVmp*2ZB{EH+ zO?!S$zP&TSI=_ZMRip_d2m*5Dk%IKJe+`)Sf>Sf$4!SUysRsuCgki-0CGR5AK!&X_ zJYVlArS!8t3twcLPm#HRf?6I{1Q=mvMvL6Vh?`{28G?s7tE(5Fg{y&Bzu8|K*UL}f zSY)B+dGN!it~JQ6NV!IY&6lz5aqYs%>oUtiyZbDi1(#)Y=yD^$+{d&NQDjk~iu6Nb ztht&Dys|%UfmZ94g;71rdnB|h zcErosiDrki@7LBTF7b)^@ai4}u1Rhcw-Q>bX)f-I6RF6T{rhhAyG15bmdMlVGM&ci zFE=t4Q}f6l{s3g^$M=<^;FM0Q2*~(IGmz!fnoL-nE*1?$4iBP38C-^An+Tvq9mrT% zStTt0QlUAkHoam0rbEgk*X~XLyg4KMamBt#;I2SJg$$buaafWMf#1c>Zd(`uC_1IN znQi-cL`b8SmL)3oa1zE)4~Z!4n1mV~*so3TIHKq7L=XqQAMYA2R_;5^G2>F`i$MIz z^cZxC@Z0V{z}&5tg%T>=IOVdaL4dlcGHWRk!{CiYm=UW5DqeKL4=pw7SzTO?)D^dJ z%WsdF{pP+6Vp(L?FnvSZBWRD=^agob%M7K?^yZ2J&|k4UtLi{;FERR?i#jrcI}eDiA$k!=Xa$+IYvbJ^uZg5imwP*GB zvnDrnas29^f9e#LHGF{1)qZLr9;m1zzZZ7wNAlR}zz$nr_df=9<@!OZdl$zHW z%*UuLaH(<=SP-5vx^65)l`ixGam8_$8ywm2ag7VQ87VqqrrrqP`$XlcV77`b7S4g&1UPlg-D}U%EQr-z z57MG_0Eyo#g;g>-tc)>OMZ88FB^jwDqQNRoY~XcE4xN@KT*M|*l(;3LKsEBKd0)d< zGeHVXZpGvy9xw<>t5{MyD3b3-AW}V1-$xUeGf{|rLA!fzC#eTmzl2jHZ!fYQk<3Qo%Il>I~X=Cw;yuM@<2|ISpfH#-&C5*J>m))f! z9=GvPNjApD?jV8Ao;8GC8}%3MmGnuDl*~QVG>b>*!Y@mP1W&6;oaE}8R?|tQ7@$bt z!i~M*Zs;=PpMm+7jGj?=g=MABi@Hd=Zpup;AoU$$n|9JnaQBXUe1yIRUt$_``#YZF zO+#YE9L5O-SypO1wYbbLEq$1|;l58$!_O$$i3g%|DSC8c*yDSK`Yc<0{xpTY3!y_A z#F+kTLZ_u$v9Rq*t=h&;Y*lPjrgFR_7L(B1b3{2a83;;w)YHXes;2?p2UFv7wV`S- zc)KXfvj@=y8Z0hO75t=3@bUfUD7d`64K;6CUh3A0) zHK(A<=s>fWF_(hQzLb1Dh1IAb#G3rHDU5=No|$uDlsy0G&}E1r!9JrhmUc1nkVbf( zQ^>Elq{p&O8%vt<=er*nL?^`t6=MEin7ZWW=^$p7DgaEXV8(&kz1*#uy$gAZ2{-R+ z;~kn?L@gPar|i68mrLSr?a)GzJ1Rs0zAX4b|38rj#(~LdNZvjw2HWmkxu|8zH2DJ0 zsIBd>(p{fGI;U7@WFADZtL_F46|#BX&aSr-e0aa#T;~sG^WpHhKdv4cJ;r{2vAcTO zN*G@G{V>BpS&b?5p1|s&BfE)QdWYzxJD>5}Tq;6KJ2FAJ1-3$wVXg1`kR#oeJKuPn z7Ix_1?V&at&1if)G6;4t=e4!BMk!-?b7<1|D10+*vZXsrp*kuhzUj39LqM7^g;mK; z%mt$~<6vZRwm=Lj7FDk{Lx_8PjW<8K^*~;gOB@t9xuxur*Z`=-BwukrS$Xh2m)<1$ zr&#iWlSt`%iA79dQHBFFHP}C7x=&;KZNrMSQml)n&I0(?Dv<7EvJ`!#j|9D0BC zmvpWNjMq@=pmAmzw$Sx|dZ#@>pm z5W(ulwh`S#e&-Hg!qj+JYoAdmuJN~jCFPAuVcgq)o?~mbeI-?0<+pvGOilnq9H9GR;~RVyKVI z6XtM!v@z#MzZ+Dq-zHC&6_wAdd~yGle@XYfj1)5=gp;%CM+Zx&ctCY65fyM~xSA46 zv6FD1o`ANrZh9G%IGwUSHQU%TcBlpZQO~Y%F3yR4YsS+}R2jv67I!E*0h?N#puQHC z1nJdvnPff>b%+dEk4lO#ES)l%N~Wf6?A7wMq0>GP?Gggddo)T2cck68H?gAW0{^qT z?hj5xqP=t`-~FsT<817FJXgI%?Ha@be|q6lmWz>_D3&y{jfpkCds(utTybfpEW4A2 zY?n|=J*{yzM?-pdTP&kJ-rfENPx})rw8Nnx=Y8BJ($jM*%-)ZBnQ0ylQ4j`dt_Hrh zTOoBnh*9AB;c}-Q&V?KQs{JZ)=tP0nD1~b1r;Cbp}tf$t9GS&Dt}Ndv2D zd*gYb3A7N4{AF_4jvVfv4-bX9q$VBa`D6oOEZ7iGWHhRHA`;nYrz=;ZydjSai*W=H zBj&fOB{ML-l@R^1OjcHAv>m4rN=R3q2pbQ?u!N*+i9HL+I*GXAJcVAbHs_0&zx;-P zNp(^salY(ZuGxTl^*XE6*|RJ=t)EPE2M%E0IxK=2S$M+HZ0=dGrTk3F_d?|ZDKzLa z*5Yz0m`SxkGvi{M_&~KzA=T_bh8Mj7Z=4Lml+AE2$ z#HOzQV1dRxrMl>I=~$^?Qu4gPpRP-p>VW4=XcfqPghLx=dGgqyT-lI=3)|vt@yy5D z)6t9wo12p0G1B}eco(zhxT3LlP#Law?Z1C&|CfQ0rLBee--Z2Wfzbzj`N$qCZ077> z0}LA?Qu=oy@ZUp>J_v%djU_OW$L3!vXJ_m9S0vI0s^QsG%C2Lo!he!%ac`_~{hp-W7gjjIS0SNnG*x_{np&j3W zY!IRFSfMY?q-0U-u!b!Pz9_c)<^yF~~JPV6f zK%l%%@_cL)bs`eBrm4NHCG_=e_w9ZqlYqq4dc(|GQoU%cn_W7iZ4h)_L5(F225lsj zgw9TsdbckgZx*@9jftrSZRPL5p`O;KI~H2w&b8X-jQ&j<2bLHOM^3#KBo~?c7K;a#s+K=wF)Ju` zfb;pR7cxuHrb#ZWhqTp%(CCTZfOE^W3>5S719QN^2wJ6}pb;n4MOMp=?QLl>e!bG_ z=|a%eHB>G++}4!zJJ9#D1V76o7_wTrJ zfvIWVOXjKA(3xC~M2U)WyYbRiJdM&&>M0?$H1#sol!6OaZAd^{z(&7Kp>C{ zqANm3wj?-k(vTIOFfM)bmJQjL=ZL}h7G0Q^{1bWty^}}?8r}t56Qxpg-4#`HErJnH zLSzriL}@-~Y|m(s6B#vTO(c3qq?ns0yehn(TbOUUHojCyFta8>q+d#Pz~W{_G)$${ zJue6mrnNP7qKM4a=O9|oihogo{CN$k!KK=04w#sg;lPQ_!FAf<<1y_;X)>0`4|QQC z(q;1asXsfXLJ%fqf%mt%QEO#M0SgpLjH(3G#hYkWS@BVB&(6)4kNV*aelBtDsCWg=tNSy&4PhpWAFb9n) zfi?P_Rh?gG_L`pEAGQXvsNlg~zdo)0bwH=kc*gKujpIHi00w2b)vS_hW1~|nEP6x` z5}nMZD4RBIiKHQ7W^VT3oYYbkRel;|A|F!vrL+9N*USL!9itS%F_of_lB~< z9Xg(!UB8VqB+NFPuWD(_Aw<+i}oA4}GwV?&Xcia;Z=4mK{? zfMI`0S4@6z!c`eX#m>2Nh(>M$dLaKBC}!#~4XLY%!SC;Kz1lGkj8mg-XTfy!~*HT?Y1sDVSA!GosR+vDZ27|Hh$PBoG2NHm1`)l;+|V|bq1^a76$b{gJNY8(jl-(= zsq=e>XwN>zq$1-M5tpw&fR&#vL}(l&pdoZ5=jD(;bWx8qxZa}yiusj9s6BLWiZOV= zaP|ol_`#{3BqFn`ixNfzij$MK0&pCp!(e<(V6+|V#7b^*W+dsYH=!e0xX)9-i})@6 z-JNy<(|M5r;K8;yWR?t|%R_vlNN&#KX-n3)drBF&ae1o?6=n4CW2|1$`#l$NEy2W| zV&eo{B8TA;I1DZSQBnViKJra#hQ44PqCBj{vN% zCtnVEfcDZUMN>%%KQ$=~-hieUS@i`;n)z+v5ZEt$W3N`wqAh01PUbeSFnm0+fUO=e zHrJ}80jLLyg94p6ZfeCs!M3)&XMyk4`OB3+caaF@rn71zV&G5P;V4E_7)Hkch^#=0zy@7*9{|h-j+Et18c&iz$ z*xcn<<^WsuxLU}aoVclm5~?(R!%>dEK7K>PeiPu0BIV>gnGHY00ais2zRg|7lsnn; zWxLtXz1^xRRFwaZZEc4RyxKtC;rn1pb6@mrGQicpLC8^Hb{oUFagTMcL_DEd22>&C z&E8o@K5T&GVy%%cz~UCOi3(2&Un2`)!a5g{J`ZyATN^B8sMRovwoFbBF`7vdEl(=84wRjsmUizua_DE9*xY{$yl? z*lqo&l=?l6cR7o{#buF64APBjXu8vlNE3%?D~l_cm6(@S9aEo4uN^JCC4@u)?lk%^ znx3bDK)}FkCHPyF>+`JLg#FbK14m~N`XgG-!lqJxdi$pt5tC=)lQEN(bZi73nA6W$ z_L1kgmY>ejZ?|#lTo2G7CB=ICV67&i{dFmWhpWcQWa!LdbO|XtC2;f{$2DHT0lC?B zrXqXDx?k-uHc5t7ox!FXx7qRTj{RqujJmJB^R|t^+%dV+4a3m&cbx+s(N?^J{Fu~d zDNP2Jg=Y=3gIYm3OfoZ#hD9ccP-i$W_3 zRlBz>F$U?X`=14V8&I#_hV`^nXFhnU;W6Ai9Ascj$oquL^svtT8w~c3?~$DN4kEql z@yd2cnCT;5`S{FzXc^yq9%f%Ik)5e4D0hc1CI!~T3kHFso|3;O=Db~%oX+y_a+pi7 zkCRRipe-j&q z8z$D%qSiey9DJ>&NjzAWwb^65d2>=;4h$EhbPQwBYM$TbGM#o^_BiU&w;|E7+{fbu z)J0P%NP(No9R7UQFt7UIf28@h9-ya+UzPl$`7(6hSrcI^%(y?*KXSy4r~SnEo&3IY z6w@_WbysA`-5c*JBazCZ^_VOjmDbuJIZ5zN;yoOJvXX|JgUjMbDPemlwR z^(YB_vXS>3e3%*s)Cc$#ZFY2Ez3B#;^OVz%`rqx&_brzv%GHMZbNz;=@jJdR;^C<2 zdD!3e!S<^hfPKf{>%xS7O*?EG-X_l2T6xxw_%_}goNNtIydas%!J&Fhd)H~?T4Wh= z#E56wc#XMy2~8H-5RJ_OCXB-&7|I_yX}TD3lBoQNd=vw_ko9=`TKn5Tbnis%X&mZU zG+zS_er&>Mmmj)mjY1$d55jQ2Q;0CwqHoLL9ts6?1sA%o9F%$jYe*J*xVWlaIbZbP zccvW69XA>H#s2G9oL~Y6iH^N3B;3>3<{Q1LuN{@?8J2Kzuc^d@MF9aV4T*i$*A=5= zQZ9@ln!H$*!qI5X<;uOCN9gwJ1Ht(#_IjrKAhuQsq+3Waa(W>O5rM(jMeLNuZFRa^ z3h0tX_cirv(Ac21JALA+f_{X8+fT5c9lcVuC5L+n;I8|&*wD~l1?Yd ze`l#-lFUn5vp{f6vhf@7XR>+XBo>u=0^o>0?>&A0R4<68v@N$I{+SHKZ@rc-sI&_t zwDz9k5uCiID#U;Q=;|3#c@h+yLrRr;1v6w2V(Ee? zd^2IPensJ#3dVej7M_)H`MxTL54SO)T=_yg#kevr-K%;pn99rDr94C@mH%~8J%-4n z+dt^ACUW0!PY+5{KF)Dcw}`+79pG2p*F@#aH1km{fc0UYAGfaFlFES^D3{$B7;wy0 zvZ}g>9^X;Zi9~rURQ^nZglE&&ti6KhEFs7)JT_=I1Z9Vke1)%0WsN~+aZS~+oQ_K= z+ah`A<>50$YLh78bL^iaTf0K1+*=f0Z)kbCk6~VCr59Jx@35p{H1x8+kAAy>ioE~p z*ErQljyoBIgJ}TBPqzyXY##cSWWQ}%Ix&{Jl`(YLA%t+M^PS(Z;r5J`_i!6O^;dfg zR)?*nQzS+@Qsf$WRTNUf@_=8&Mc`UaJ0S^bja?1?nA%V6y)}VSiorKY?<8iQyl+|MO;| zeyHdFZWb!sztsFc5YKxw5Ri4fe2c%`s>=) zAlZ2GN2BY?@lAp^AOkgR+S}T(nm1N1JX>)^VRw=s^CTpw!V~w%-KB1DsD0gb$dToU zu7kJ8ZQTW+phf)vHfpY`I{V!;YCpReN?i|=$A=Fy0EayMF3r=Vfe2Ic?;SDTSw-1l zmz90Ppg$(ErCYyw5L1Z;_uGn|hk4S%5Z%SvUW ziR5Id3GJ7N)?YrVQ6!XhAH&~S?G8&Z=(!g|!e?x-#U0|Kaaqb|uq1_izFxEm)+aDQLoT52({zT(9V%(=6e}-zqqL z3oM(&CrL;XE=is|pQoOrDKY7FYC>BG*3^cy_;GHyu&~|*p4S>Fs1`sMB4$3hKVw#U z0C%9-7OzlmU;Sl2aulPygPT;pB9XvjDd6czFAUheh_WUuIA7J{gUD64lKcjavxFBj zezV1uxutu37|leMlQkYYh&DR(-xKZXxX;-DGqbr2UIL4d`z(mDLCF0V1kQyMv%AO3 z{TH0F6py0W_s6h|U&OFeaJu$BudvMB!NLs|Y9%IZ`DsewW@GGX%X_gD+mEwR61)RZ ziS-1Tp-=4UY6}y^rx?0cb$$CrDYh33VbNm^$fnk4lMR@7ZEVF-`PZMIexMHuQ#n7*P3IWkPRK zgjvx~`7|qPWjIdGGE!nWTy*gzWhGg4kXUg!qX@0h|6olX1O3sKdJ^5NyD)7$ZY{Mr zZBg5=c(jC5QS5MrDloPa>mkUR?^GpfyvnU(9 z3yEb>yUODXXfFIG1pLgAaJ*9~H#C}17_($jZG!u*yj3}G(t~CF>nRGA27BUcu~O|q zUor5hvivgJmO3VAN8#eHOm3#H)9>iFmMt|iv+MifmH=xiJuYnLb3S*_QlHsk-{7`` zljj12o>kPVQ8R>`By3(Y?vVCjc-L-cvPJ-4IFqVCs?Unl98U?(4I@X=l2yA|x`m%hbD>+1;$PMRhI@=~Lr7A8GYJ~K-!(@S}uv4)>3A#VKX zsz<;aKR9xZ;B++TDvHSbYNt3#q%!|ahhNJGp%d|@Fj=8mNLc)SNpbS{sG__{5{4yZ zPO_DoYu>=|%mD=lb9`}Pwy$WJZ25iV%@KYhTgOyi5zUTFjuWko!ZF8B5d$GJ6TMXl(p)M2Not% z{;DS6=DN6O1*9q!y&*ssHLpnTxLOdtz@AeAnZ}bXm+$n4F+}Y6cXq1-Ms6qh2zl-Z zoCW$PR-gtz6~qDlvB}DY5|*b$jXoTxx0W-Mur8tja%E{zQMQQ+vAN1Uh8tL0clkzYlcX-PJ= zHxe5t8%q(br$2$J{8B$y z{Zo^E*}_nuBG#c6spx0r47^WwV($ z`G*ON_dj|-f3&G1o}cZdGEv=|R~@Xz9v^NY)e}-6&g7QxXNnT&lo6<2m=>Iu>?y@W z>iz3aj-F2K8iG&7Cewr^@DGp_n=y6d%^AW3%_I!vao+jJ-AxsP$ix;SfHsRs_K*#? zlNXI58seIN!Y0B-J7B1CS>-4Gr9~?*A8ppavCZS{uW=#ueNo~bw{_YnRK@~<_)D5) z{|3q}e{n^>mWp*1(J6gl>k#W>p>|g5*vE3F=waaoK10Bv)#5&_H zV$0C;{;Jyo_zRPnV3b*vR!L!b%Fjygn&^`a0r)jEcsRmFrfgP*G-?%Cw8>_j~?o#HBr3{HGnAWiu#aok7hIq91B>JVI^Ldf3r81!L4N= zOdgMUv@P;4#hJTKYpptrE*dT19#q`UguwD&>9plN0gBq7ywfS=g6YneS-7S*V)bUX?x=r26 z0z?;WINT`6?=uy&wUvGY4$?L$euSpip77AibG+aP86C?eM-UEuu}z)FJ|&4jOdd#I z#O3B@OhZi*!D*Np2*+jA=af&&9dxPfBtLjkezXH$kfrj(XLt%SxhgQZQnR{Bvbd79 zTqI3stMa+nr~=pS`mN)GUQ>glAA(QE@r~#SiiefgBa@3uTDw7kP>R9hYY*9kanfyr z@3y6X>ltUrN*j1@+u1sML%YJVJV3Xm^^8Ifv!b0L2hvevHFehHur3|WQRAC$>$pF6 zoNWDR74(q~s`i6sF#p@il9|99^dOo`16GFq;4UOsUwRVkFKBF4)*h|-FA|S!_$!We z18Al%oUc)XU~KqG0!4P$fWa^7FL1Blx0j^#+&1enQm%}`ZMz=h&*pNd-UmoGBj?!c zbmSi$aJ{iR^d!}5M*@1XQ8-m`tX2jb=%+UUM~!Y%w>7s&KYPPPh|BX4QHl;!YwoCB z6th+(>CPDKYHE1BF{*GOmY7{AyU?%28HUBLjEtx46Uosk{YM60=ti6kAzoD6<4Qm4 z99Eih>>L;>;KjW_?635Kw%1v2&%h`acrn6!$Hee$o>a>Sy~Zvl8kl0`6_=Yuyp|B5 zd-(dRzqlv4A0LuESUKiVc0%h!TOHTHG7hr_-Szf0Og4d48%0c2b(u-d^+)z+gG^sd za|;Y77SiODwSGLF)>J&eWW4uI+$ygqxr>%%a6PT-Bpey^$ikrilU-QlnTI|}8Y=HT zW#cN1Jpbs4q<&9&BUzp6ntx#>4wa1dxOX4I<%%;M{co`NyiRt@8t90E5>C_kx`SS0 z<+&nSY7-3oj@8e(hC(CkYh_p*J!~m{-2;Lmc2FlC90oo@FV3H|r7eSq8OH|@OG=0% zc+_@FsctY4dzVH0xES_Zh~2;5qa)zOIjX^4*&nivFn3R`7?l&eg_ElegSAJ@wAOhaMRwapYWs(jb%l*5?+#ok*7R}Lk6nr3EZcA1%(8OqGe%v5G7Gc&VYW@acem6@5DnQ3mlp6-e0 z*o~Oox3dv1;^`k-_ei=aos+4nPkHip(RN^)-1F-@(TQ$UYT>Z6k-&=4dZ;1%ki5t6 zlLa#h-{V$38G-T@+>TYITtq|G=u$0yys?g^TC1l&6({#oDfWvH`+dLzB$+pf0M2ar z)99Y+_D_eT@7DqB8{iQjPhq%K8~n`!VFf^4BsEtKzcXgBoKk}w(Fsb>UIQ)y$x?m` zVURUYUKS*}-xvztP`krbx8FjswZA!@aug6AC99v8v2;D`R;B8%y828HGC_|9x00=TY|Q6&vH4JBFJvhMr8`r$ z-ge4~`kHyY>-kgl=lJ}o9B}$xIp>h2H6@eQ$Zs^|O}Hv9m615B*_`l0c3T@kz#YpO zSD|I!yvD|Ccpm?S)L{_(9G!e}DrnQzk2&8BM^ScCudf6m++8UeHRIMqT^?k&2oTIQqPZEn2cpqCRk$)&*IG`#KZzvQC~=Rq#b#g) zC2obb44OYhvNyu-R25Dp-Tx?21g)wJAv>wYJ`(fV^IVDHnsU6M-?6md#vniwHxhZn zf&nHij5Q0wBa<5gK2OeZjhGD^!XS?xD>`*PtyJ}hae`o%eu}j_!^lbRQ_oS;$+mRR ze&BpAJX!8e-q4Foq*EGHsYECZFx`73(rr8DXB_f}HwjaNBZ}nC7n;Nn>m$%s*?cgK zQZJJ7lJzwrdyg9^oVJvVvl%WwTR0@A2U5cjEcR$382e<21aN@%( zGzMPRdm5YY8jeOw%e&+RI?~ESvk9Lqkww<>aWL}K@j`z-(+lO~J`FZ5D?oo+GoK}J zY)K5Q^plJ&FOsmOwjn;hJDE|K#t3|8zBV&{j+kH8w#})bhrj)SsBAJ^?RY0*y0){0 z)~7TzJAXAroGh@sykhQQMi*5{g!49c)3-|y&D^zTW%v;U(a*hZseiTS0pAx~(s#hM z`Ca8FoBb~l@hQhpM5@6|m@8GbYVS>A%YDg93 z#knw%XbW@?-W?CU)4JaJJS+HPnZJAS^ z6(0{cC4F!kT6-!Vx6?|`C^g*lgcVscD)@-3pPAW~XRIsc_O;9Rb$QzxKKp3Cm)TCd zh*Kw6oxs=DUgI-}~H+Ir%a{-fx2s zpa;ow(1N*k*ay?~RCx5#(6uq#((Vq|8Q{YX;Ja>2OL>P3yt2$PK%1VgdbG|oIaRT`+0r^?LrciME`waK^TEO0lXqR~`<-!YY zvE>os1c9#FOAyX{dPBE$L_e0`1f9HnZ2vMxx=OzsOP9&i3NoD~$QkTvgs{J9oD3Fn zgMvDBq3@x7mZS)MQJs%||IV%vy( zg9zYfEvPWxGY1(E&~nVQWj>>g9L~9U4qrbL>)3|ZQXg)F8?v_@dynN!wuGmyL`S!1 z_=V{l&txdG_02^>YWHxWO99O{6Xg2ldeQuB^WQ%S<4rI}wzj`?2;L8uq#8&_Ca&t; zFB~VPOB;tk5-2f+n*cUAf$vQa0HJrsQ28ek{ZDcIztGj#(A4?=d8x_2NLJL;)PYFF z!qV8<^j|Zl|G!CR{^!a4{rm4+UlfoDOZwkj|G!cm^Z&Jw?%zt9|6Kl!>MtP!0lgyp zi|Xr4!~%qLb03DRhwD(i+!LFDm23iie0y3miQQgb}57`iZyt6JXC!6QCF9giHjW*u1jV|Vt#X03o$79F1dA*#Dc z5qy5pAYfNF1^;*w+Dq$;_oEOwdRK-_fW9GC#z&y*TKzGA=V|%K-^bfLXX94Z?Lf(m zjceo*^QO8797ik$lUHP3B)sm|u*aVewkQxi6)fuUh=e_T){c{DXDqK36V&Rbh?CM_ zVr!L-1e$z}(BHz26AgCY{erL5<2Z|};MpC){)XyKs60}wHr6z%b5L*e9x&6ESzRGg+WZGLzQ9kyr=IM{s=O@@0y9BMOhf+F<{Lu8D75UhJU?H{^&mDMbJE9f?tT9A@PM*>czw!!dJ-lX6)uG$!8}2Hjp}r2T>h28uNEOt>lp zO_T0Xs16RMpL{hrD_}AmCkfaYOL(!)U}npmSSjT?kd`Y7B0|W?8G2px8IM6s>v*~I1|!Wjfiu0+5eEe@e>>V&q}AUDaCOQw zfbo;b)%|CL{r~ocT}Sm*={}=06x=A=gENGj0D8$_(SZMMwMr|s>=<{bVsk{{TUSwn zhe|L#ohse1f!zc#nn+WvRpmtanM^yj*fHiWZ-&3!y2;GUSjwgjR+MIh&)vRXi{ti4 zLQXtE9=a1zTf`7^sZEnxIoebvv(D=t0{AFu)-Gmdo^mH=%rgoX3M-k++&}-X;xiU` zBBv~wd4AWtP1(0*FD2$z_l|s=(6l^j|MzF7VK-0PLmbiYn29 zX25qb$4cmj0(0BYgdXH}aiNeO(YjXybkR{^II6u>5(H$iaXzjow_6dJ$=o}inrU#p z)+5RnN`&iic2rWPWs>g6(rTj4s2%rx$!9TJmslKLPbv8?haNE^*uESy5s|d>3xKCV zkNowtNqv-$$(nh9GBDIY-Q;J;#-xI>ahGgHi}kL(4_32YgsF2YZvaXB7$L+i$TTE zSe>Fs`M{x@MI%wixU%8D6hVl@sDH!4XO)gRKW8587BwN^3ZhSdnKN1qNnXM*P)X1U zzvoG7HZ0PCENW{?F=$j!HVRg7Y`lgMA|J&*2E?9hqXx?u!?a(GxyDGd*)am*B z=w|SvhGd^?RC*FQv%Qi(9CMRd7r36Cm~xZPUl+N?FHOv~skth}F9#FX<&TomQr`a& zvRKE!3KRC>7@z&m-m}xL{Iun22K7+Xpl4Jtp{eF$5FQhrO_Zyd$jn<{&Et+#X))EC z_fRt~sFSbAu}16=NE__{K6~2@@O4}g?QgA_-P3a&k*(iQd@ck$J>5~*;*Y03f0Zm5 zksa!1NP-rsn|6m-uR87|rnFrO!iUCxupO-C(L~K;s{t}4+fvAX^_69ssHXhZ9St|u zW$?eibk^m{-yDc2Ot3<($LL?It2HEU{I-N;sPS{9+UkIj{&0`rw@yPQf{)k{{pKD4 zoXlD(h9uS!E81djPhNlZq-t{lNCb^kH5h16492_8Ts``yqiU?;OTHMe_yLq6DqvhQ z*>)&JDZcoX(Dsy)ae>02mxyI|WY`w>mcDm`{q2z~;1GnlS<)doYvzxG_>4^)wRIIt zrU=){K);3zS**c!LoaznFt=LLzlzjUyUlwhX%5^f`; zc051Tp{Y;#0y(hj?_Z139gu|=CJaPI$x2vsLlbBI?&Qfx5>H!deUkbwfcp~D{h~ht zz#41vK`gag^%Z~`vCi4F_Dt%;t+NJqC}28*R;O-u|A;jG_#W0=@Duf@JBZwCD?m@$ z%ZiOjB@M&6Os41uWG7f*z2fSbzp)FcOi{O`fJbS3bGR`DSEgK30)v2rB%& z5^d)5KS{=XZ)aF-`z`iy9HJHNjv3vjDr9rieIAMd_p+)4x#>ytObF+C3Zu6d|0!2fX@ZPIDgM;DB z1*D-7DY%%%Y#E}VMpZG_D*-`QT#?e{<7ZEd#nnw2-YZ| zcI`EpEA(Jh->lGhk+4D^)c)&Dxij18A# zuBeKl+5@E*NILNU89*D)j~L4{*phOoWzT-tYI9lQ7LyX@729#bCdi8J0#WQuCM*{N z>$W}Py}^~@ip%f*?EPaZIUMqvMXL?yKDLwLTmCHuLp~}wzV9(T$LbWn1LOeG@LL`s zMag%F=lN z(p>R}c2tK5Y|hM^`9@o$gX?uhmtqrLw*(R}S~2L(3|5CGoq+x52G?OTxEv90GH_pa zXjjKtPv2tRg7qPi?;z^G{I3F7@*QBY2$r<4y>n@E@f^SpH)YMl<&eBHmA4S5)TmJS zLM4$IZ!SYA1m5+~om#8;7=WF!JBrd0N4tvC6R*08m-hg66{Ybkj*q?(P#wmgH{t=z z0vJGftw&UGnX5l`HcMH+x}BOzL9t1jgEuaq_U`T6=7fS^1c0oFRoVLXD% zWYh2Q#iB1UE8$%!q9O4483LZz-9b|6n|=7RVL`Z`!`f%YWiW)-#)uNa9?dV)zLi&D zQ+#d*<>Lk29l@$^v^8*Uvi=E43`d1K?sGc{7SGA-M+hQ=@u?=>TQPx-w_}Vn;vPv~ zaYb_3CePXHCWLT7;6V&NBii>Er+w9v*16Qv$W$;_N&;a+qv5d5+ceY?B>k<|bHjH? zGWbHX;*ABd%jyqDQX<;6GM~arAvqvrkm<;03dN+%_k&L-BQmkCiHw>px*>hMVBiz zyzvpj+dFRw#iZ0$xcGe&YKd3>w4W1-I7K>i7TFH8jPCl46mtoM&D!Yu{sv-qzfb@W zg8{efmbrb!@kMh?u!bn^{3{T+1qd0D<7&8r?^ue65Dh?>G%Vp@41mrz7GLWY} z$_zPFZq6TA&oZQ65VVCZNL#R=7(j``JHOx#n4GH}tDB`rt-n}Kzim<_$D$43Dh|DD z1JC6!CS-F``s@#WzgORj&DtK_(?lC^ngXj$zt;4IcK(jeXt+4#hDMGPYc6s~ zdgbM)Wi-SUkI#T~dk4A(vG_tU@OUkNkEXRx2lB#8t=#|6FhIi{TI0RPb@{ zBoL!Xji?m$M^gK^*urfA;|$nqqMqsEm*@0)iMt(C3~|{v(jWDRZuIgcG1T|z+VBTW z@_qzIU_Y|IKiisxJxto>^3ChA}NHnaqIAuYF zO0zynox2_|>IuGufQJOsb`9r@qzx8l)fkm^1+7i5A@G2QGJ8zDe|IP|tN?Qs3^&kt zJ|(e?v#c1-Z_Ri)YUg9buVB0yL?pJ5+M1%59J@7+ANM2Btoqb?@bcN3xBQ%C)8A;ipck?6Pz zdriAODqd&e${gN7P`@&2|LA~CMmAcoD*Bd!`X#u^2M>$l;A%vjOOOz`T}I$$PuZODA7)s+#*vBBcN9v!8Mu`*<#2fKHU7lqUJWqdy!)}NiksKsW&M7X=J79TRLrIqUEtUcmB}d$6WjpuSRix;M|ANY5lbWsU8bnv+vkc5bFar6Xf zzVTExnxcUjQM$VtMc2AYOSRQIU@c&26XL+$6U#R zj}-3*AvF2RelH%{n~5q}LP>6VV{!WWfIv&E3Oxrjt~qybZjs;Xsi>m@e!r+9mf4vU z1((f*y|Xn9gnc`dOh~+1Bs_Y{5@|;mO4Q7EA_fAVH3rM6@ugEuaNWC16yHG%f9df$ zzkkny+Vr5g{e4Ru-cKERi-?_pHMW|zEx@SEl<%&QJ;Q8=M14zRnO+S_LpZ(42_~1I zpS|d~ECo@Wcq%b31<`{YiRfSgF|gkp2JEO|>W{_qEVItb8Gwu_K3 z|0nVJPjgFCTT>@X<9{CRf43CF-`brb_D)W&4lbr9L{fG(|9_%O9{{>!{%>^oUj<$M zg_8fbg3ABWdID_pZ}hSPP(D=qfA;f#RAHD}7st-spLmk69FwqAEEWS%aS|-D%zRX7 z3Ku5DVIxy?HfB4^-f-P}u(7q*Yo?ILL&E$7rKFe#1ql{}A|pczgb4=?ghlZKLqWkr zGwaOR(7cKg7>XTk$aE)593uF7A7_~wzWQ+v4+)iT40-OCV>B%024}$;dnNYiAzSdw zxr#@zvJ+1v$?zV4|2}Da&N&zZ#89s-Z=xP!uAZp8;)j=5D}M(|7Ew!>BY2uW5NG3jmTb5<>wQu#7RPndKc#fq^mR6l{Sf)4k{}Bs{EGFA|hYNYv{#1{2A= za-j&-TtTWLoXs$E^%FwDBv2Ehg+7@y8chiJ>xa^A)6t@NP&wRjYU2e|Ase!qx2*W> ziSm{PIRO4Czrvc8Y@i|!h6FdI2lG=BY}X`F71u*+I2@=?`wl(gq@1|Xe(5k{mc8`; z2x{>4_eW(xsL~mmmdrOM|kJL=h4{344Uw1WtYLV7u+0g})^myg8D*CP`>vvDh41go3k|(4Z6NDwIDZozAsd$G(W{65tYC z+N{YFOy08%j(&?=>+&Ql7z!+fQut7H&k~kV(+&QOOI9hKyqQ#dshRxw@mo(%27L~Y z40zENf{q!AOaJtFN@tfAEt2_NlZnBA zQ0a~fV3l`P{MY!yKX}rin}5m=W(+xbNJdJ^Tmu$v7sLFJ^laj^yd%bm8<~P`+gq65+-{hOjG8X zsSM_bFGN(w!BsaMVlLc;l^@e`x(qO$XGh9 zl9qB}4f)=~Kg{SV4b|Wu=IyrUGddSBt(^y)+>SzyMW+y6q6iz9nQD_1l+74cY8sHK zKAW}SXWLs_!s;p=R%>Ul%PlJt{UNb3ZLptg6sRcW41r~IeYSg6f5lgrvgCvd8fe5i z(O1e3u-qxlIbgA1w~14$*xi5uX;s}&>l-fbAY-WunsyfT; zq_o8huOYsWsfvT9G$G*GfOvx~uDhU7HoIz>*Tx4`AH8Ia^B>?ZgBQB;n ztUzs9y|2$-S=5FC$AM!aZ#79TC05Fab~>GjK~{yV+hK?=?2I&8NhLn2g?FzOmP0Dc zNFLb52|1$_r12ocBwlQ13mu{w157ZP$9SHuHah2Wy!}oCUQZ3Rm?#v>Q8J}bm*PxVXowx5FkArA?6#<2xq^vOx(V+LRVVv+P*F@(=5bHNOw3ZQHQkBcA7` zs(G&h1EXp&cP7^>5DBFW%kYmQeWTqf%Kj25cp9D8gLAA%Uq#P`r92v^e<%u0dFDO(cU4gSRqW zjj0mI&LC3Kw4Rk1R0;Qm!2%LbX3mYK5akL#^jX-Jh()#0{@jLD{FcbG9weV5dWos;m9!}s z(iPX=D5WXz)_+xcP}TdEX{HctU{lwn76#~Nct6an1$r(TO~9>!R|xAK96zrK5HuFk zH+FWi`lsVg?e3ZA+kUOoGRM|&=i2mHu+{Br{KgIE;$y$H;}Mb4ie3@kBL=xYux^{$x~8g~Bw5XVCy6KYr(T zho#Z zM^a_#@dBu#G4;d8K;`hR(`oI|Mh1Hf{T!0}}4T`f3)SW7knoHp# zxDN>3xP0JPwn;H=*(fAgLJHv^QPR55bv-yvrHmU_!RZ-lXTQ~htlQ$o@u1Eipl6J} z!x$C1?7{Kyaw8_nirsRK)l4KVlM=7Ad*tAY?9M<@@WaoI9g%6i)RRrgj8;K{kgSEVN!nI z?(b>cf-;*kf`Eq%kP&>3w7>@5H`i0h>Ij?vN|k=kk^tq7jWKn?mrpagx@Au*)3%Xp(9& zdns#7U@!1c=W{bM!}-PQ&Lk?AM_3c;BD*DOPU&Bv;gU zqwnpgGGoPK>i;hHV%>7_#CqLHGt}~+=a^o}$b)L(PYn9n|{po(%JrJo0rVEID=ov67^=Ith3K!tG7@SZLu3gK)oobY~cLO>22 zKjn0S=tyQf25amI6LJ-Gn`FYX2?uh{7uV8LsQhguu5$bg*k@|Xzr+w_Mi5B$a8_99 z{Rdj&PGY_2`qKOKcW=D)0%A>v<$RZH8XB`zk|m`T_DeYyklr~G;bn)Z;5Vw49?1%0 zaU3=2m09Xs!neH2U7mOePE@b=)Fe69G1VeReTlm$4# z1^6BXh}Opd9>?^oe3U~)S6-APjj-1fR#QKR=;R_{*F_hKRSI&xF>>R_xt zHKax-zQvdzu_Jw2Ka$cX&GR_ThBxSPEAHzb$#cp|B4q#K&a{-baCZH%0l=Avkbq6=d(6Zw!#_DQ^QgSGJPWqFl5| z{CFqRmS1ep&FhaCQ&E`IX|IVo1`4?6b{zV*mXhMV_@UEagFSSWi>aayqN$L5FoD}= z^-#je>l<<79U@_mA%@Lve2#Hp#tT92x{CyihXyL!1ubq)^jz}~R%mHc*_N$j$8tWP z-E^b}%_$BE_A0fttAJ|enC2|wa-a4xvjl7hx#Pt=&!2_O%syy$ZUr>)P8#m}4>t1{ zl`MPU9ru3FeTu=^bpVQ+kHU}`;wGgJ*rII6izQ;FACCrO#r~3T3{XylJLWdChouE_ z2DuAQRh!o>o)Nz^AG_y@oJs#&JdLsW8hI3h=l-EzJepnmaum0DxHQJi>=u1N8>y~0 zR;kfAR`Mz37JPT&f5C?TaAGqg>DoGZlpMW@<1-um?c0}T5#!7HHv;U=p}=QLUgH5C zyr&m?skia0fl8;bDYw_MP7843wf%#c0fcA2E%LJx^JG4Wi+8c8D!wD#P|jY(z7N-k zL|0cT``7w5bm}pv!I(&Uxboqs{8&)#oWxIm-wMLFIZcJ`ed)|?5B4=H4$4FNNUx2v z_Vs=96~`0);zF4&%+0h%yGI9m*^^q1yvU_)9T&f@`l>&r)eEMDHqwL%6A4z@*|!ud z37mNDV$yF1f67}le*4FbCqhArHCT&Yo|f;(9i=&NH+%Q5=RWUA_3%`Bk{{fubS+@K ze0Lp8tKoz#uK)+6JBk(m5qgO9qeHdeYYr+0WcmmEIJ3m2ejDY5G1ag*-Lq4dL`NI9 zjf+fj`w2(vS|}B=AEE8bHq+5{+0dRw(KfHLdJC~qM?fg8uQ`5gxSaZAx3)V;a#Y9} z$JerpKZcWytSz8}+8p>YhR~J7{72l2HPQTaJp9s0B>u{EB3dgy^8qhH!Z8K*#ogS; zUU-pE*XB)S#=>5p&2nR3e{htetvP-Sk=Y!q1~0D{VsQ`R{Q+XD=e~=B1`8(Biv6Y6 zS*CY!C#9vWuT^EJpsq#K3XmW+NR18(vb$iQ;i&q{;+d%o19l!Ec<#U$nT zdDDxFp27ori#t_Gua96b6Y;`81FdO1>MGyW z%2^!Qx#&mAk;9fzoJgH$j0t5p0}?8TmWW24p`{1Q#&zf$P<}i)%on8Jx z-p!yatK13wF65aL*IYk=!}#d*lP8&F9n_CRCoq{7C_TF>p1oUpO;aNC z@oSe}C7eM_^8`GGG8b1V#RTV&$)nIt|B0o|%i=fZ|5aG@-}f7~_O5m= zhL-;tB>$@<@ZYx@|6KkJMn?luX|W#v(FlOicxy4ZEy*3L$%}Q_+V&O!AWr%j&-o6E zEuHAUd%|b1-0(@oXCMP`6lVtt^ZT$b)Pxl8Az|+GV|`B4?;&3|zmC5b?Mo(c&hJ>z z<5@L!fF*e#a)ln@Rh2*N~Sso^wU=_aj4L{pBTcIei<7C+rUG z_NA>PebAZCA~i60YjXf--%0KGicwYDhYkdOTUArurZ6Y0WVI+!ZH~r;zSALRaEk1t z8x|B&{obUT!o@Pw0g`+uaihtfgIZcPTYY^)l_nVl);}kJU%C>YA6?NH!By-bJcW%j zLxL-4M=)Se$;{{YOG>Ad70%e4j}uzX?EE%hfv}6utxx7@dc*k(;-+OWhb@k_^BUy5 zZi!XP`IE#+RBh`EmJ4Y#0pJdToUr>p9u_rB?{tG7>7Hs8Ax;u#jlC{qsD`8MT<86M z?s9rfTpP>VgD`to=Iz^|k?-myBWS8FQ0M1-!#IVtZ$g`8M*(dqFOJb=^N*=_ z+-Lqn7or=qZ__wg4gr!RCjnaJ8d;oPtG&d98=kcWf%Io{B6!1dZA=_WY$PIgYz3%3 z-dm>@VtPWL@1Fb74_hsZN4pN$`DrS``jEQ?{YpPa-Fc459XdXsFd(79(2{+%=}ldG zxxAG5e$jN?RzBybQdOydn{~$pjhJ4e`8L+C?|yOZH*LZVXO;7YE`HK@Rt0M=b8|%n zys=TQB8d%Y<7yn9a5XNl+iQla>!Mp$(~1%ut{zR$*}5JLs08R+20IxA!zetblT>xE zp$I3w^rZL@xfJ{M2zeVFXU5KCdf|m!Mgg)S^M30;eq{Cko!+$I!BQaq;froym2_ElTmilK+)YsdROxmVrro-Z!i1vM3z~^^*j&rlV z%@J06SO&n?MyFZR2&qZjk%ua{W;ZA<+wK%YjI!nLSdOW;)0x|{=Gq*~EQkw2a}#BO zlbQEVt%M<+w(>G0o0ybv&~A^|-e54Bg!)z|%Q*thsyDvF@>Jf5{ZtdWvacyx-d_)d zy?e)!G*@Iq-mJlI3H8(=De{7oMyNDt^1xX<`y7;#%Pa zcf#RTgSx z0RGNh$+lM7zK?f*U{#t{j$;bB5}y?fXWw1!!uOl&RBkyB;EAm)CZzAC)A2ktu17~U zCpxNEzjL|G3t^geHgI>3#-kB&`I;ivk<-86OO|}O&Bvy3a<4F?0%Vt~%wL)_DBe`F zSC7rkp&*0j^7oPIjV)FC8{Sa*#JK@ib7B3|#(IQ77 zP0UVSQu)}O5GO1e*y(ce#fm}|3I)`TjXdF!rQ6}6b(yjHqLs`M%&xz~WqQ-<^xEo& zPJo~SIniqER>?>{#$72j5j3qo-~-SaTu<-`~2#>A%({yMMZr1(i`jBZLy^io^> zr?13IM)*&@@+#Zy<;Zseg(@A6ZWIlFF5GAiK;K*@NGs^b7;kl1sU~N^>qkd8ZveTQ zi8%30r$Vr5%IssAk`@>B*6LQ@FW6Goky~;YG6a&U6V;k7Uy_9?Tx8ekBs9!Gul32W zxKvjvrWjQBxS=29#ppKEl()P7!$1WmZIJlU$8t19?6rmMta39rI$I&l7QF(O%B@G6 zM%(+Jrd{q3G40&1-?C!t{CzbLKoN8)O&#_VbfEE^!hXC+FZ+E$&YjgFe+y78GAw*) zCkM5&$Tf$Yu^&dB7neHW43y3@?0|rP!bP4}yIaupjIF0)H*6u$BA@o}SIw1(LH)cr zrj1IiR~}|#;)o=T4K^`W@&@%18Q>MQ$=2p{-i z5qGp+2L_)d5S&-=EEiY-QKT=1Z5g=s63M>_D9v~Kg4`FseQtVavz9LXOai1Fd;w3`R$9L!%A)ZhxZ_6+D80;mIey0RYuSOeA{ANcRqAk(uD z2E#z-P=JFj69e%u_PA)`gVbUG@qhq6u7vO5209-Anwx3??O6pn$NIY$pbg9b5+bcS z5z<209TA@KW98pBKRE-cWe4JcFpvPM1n)tZDY+oTGHBo81T&xml^u3B@jsa&0Ih8{ z%#Nu6tvmxdllOO}=xJvM5~0LjDz0=%Bls%TaY@sAl-zVl%R!f3by*Gvb}Q}W29ll9 zBd6n*t!X5;<(8cy0BnoJJYDd169ZiGwv1|<&3!cEKbgkMBo5)EgtU!#|E}dQBHL|i*|+6IkZq(n@G;73GsbvH$N82v%D!-eG|q@Dzo3J~Jq z%aQg8FRWXC?cD%S83SGq$yX64&#!z->mAGEAIG7jF>}4Ycfm@|#`UPTy=0VXzanPx z36V~U-#B~nTQJ_=plRVox7lu%$74xP%uOosjMh zzB^V<>d2T$`W;3~Rgy-S(;|eypnGQc_izJg6g7_W}^CRd@$BhpTftu=tG3`2$eMebU znu=&*>P@V~Im7w$Bjcy&9HJRbynUEBtw1ig$T(a*m+Y}48J05Q*lz>YZm>Ut{bcQF zPBVKwTJ*a$CsA@iHpy+31?h=Ic}MC6Goqw;76LsvE8zNOqfuDI71K`S*yOA~oI9ri z<3&tFl*NAq5AufxxB7;si0{maX?0A4;)zi+`269liI*j;p~&MbNnHB0=(I{GC$)5# zJyAD-k{`RNivvfG_E1M4Uyuo#{f{V-x>vSDzEA{U*%?(gkhf z^phUV9MzS=CM)86R`~<5i?8D59c|U?lsu&qpJO)Wgf8&P}F6ZDq?VTl=hY{ zT`aWmDB7_#B~t5KxOdKy1nH^|amM1Ze%&KWT|}JFeQ-$UblBE(3*IQMU48try{nTH z_}o{DN1dOz4U}LOBq?t?A@AO)czVtW42_}h{Uk5$P2bf{pzH&G-iRmLgxU1urfF=9uPP#huzIHYsf4PScU~g)oJMUVUA-{M^-D5`0 zHVp7ekTrG34W<*m_y}C@CA%V=93NcUuHxo6)8W)aW?5i_IICiey_iQi1d7#T>v)Cn3huA`W;%urd+YHsF ze#DGdH14-iq2Xa*m`I~434#-ggGy4p<{~oy9r_E_S{bCcIAz!cKy#!W%TgHCTZJ*KAR^* zu7MRs5|?>IiCKV{oQ7)1u4zi0Bx)c0T1R~Ytbw`F&V}kv;BZ@ki;K~*F(doI#=-cm zveqZ^Fz=M6T3~6P4AQVl@n{_S;bf(&_+h)>PWDgB6^>;|Ug>eNnw+QMsV!bRHYDl? z@{;@^9;{Jv4{8$0L6~2k8AWRaa*^W&Rmg*BgM8fQat@u%q$^)vLUY zFE|pa9Pv9)&aW^)mVFUq1*vvA_PDAW=b@t!0i44>vCL^z9Dfa_v6UZpPXA)Ccw&0o z3~%01=2ptxJUmeH!_#;ZiGjbs1ibn_&_ix^7^jwzPtw09N!&9UeWUM612d7<6wgY! zqD@xlTAct04dC?DN8gQNVfJ%_U2@dSQ_u&Ap;^bjhxtM9BOF_Zd-}Zo(%b)@q>k)_ zD|#!8m?O{Oxz@8fVLcQzNRr#}5e9GXF7N`H?HcNWfzXcXbT(IM?yNQB6v<24EsB}S zjOy78Ra+6`e7UVP-+~64F{q;uuee|d)l0rf(iS#4+beEP?jUA@qxzoN8taizD#4+& z>26U_XiecyrPLPVPPu!SCX;1K?7G$h{tOlI_ zW7UHp^gd(8Ugme;@L8(sMFIatV@F!{MjN#15q9=-S9lJ`x3d}tG@i0oyV>9XKFscr zeE#{yy@pjq%0-qBAUXub9}k^NztS-;LYVVYmd1SRh-@xmAWS6tENG%Njg==Npk-Lg zFxndiY1+`{<%-t(FNZW?K743n4nVZh@%rM)_#Ap;pRHtItGX>?S4^shH67^*5C{_` z9HYA2Gc^3Fr}&LsZs#$#04h3GrnlU@kDREl2!VlA(RsOT3S2DR>kC-7;Nhn=S*807 zzrPJ(ZG964g2{$v!*%>4N)ZIj#PJ9jJlS*EIoTh;e3b-w;zmSpdp09Ks?JeUAi-n2 zE1F52CxJhY6tw6EBlgsoLs)Z30A^_J%i7<|k@n)Ds4Tbxv+H2KNFU;<^pi{Fp&}kH z!Y{)}nj8Ulug)|eE>z^U>~^r2>uE@qQ6$4-U~F{4)q1(|s3X#qu3$lQr7$0aYG`%` zlq%?g@yOcMnoM}b;_>|ELoRL?D~fv4blE^GeZ_!kNw$p6hg7!=spCR2_tV}fVwE0F zcm-FmGX9dET0_(TZlZ(;N=UlYSB^dg%c+AWGk(&i4`y;YqSAPgZ;K+~XbUl>KnWG& zIz-AlA>I!goPTnf{}gWg%Lf5i{F?-;9lPF) zUea}AiqA7qd`xZ|K&6mZnyb@sQ-Ualw6;9tAKf}h;)CZvHVJ1O&6&^IkSrc@D=&^?$ZTA@5}suT7}}N6{ID~^ly1FqFjBi4+2>tA>t(ogU!SVW(20$> zhYP;xFudarGN=SxKNL*fA96YzgXr6?uOmDs*=Hvk&t|Vd5$>>dNRv$PTSBk-gPhF9 zo#-N=j#aO-<8DilQj3OZ%}cP7A{wS#rG?PWl%dAzDJ2p#l+w_K-|o8-m4~O%ixHf^ zx!{7-=L^}=X@#C>IKg{Q=j>aZMq(5ykFvSSJM{@J!^2`FgUthpgq|#0qbpSQpN!XT zV4F%Yvg9vuok=H%3}WeGHzPD?yO%JX{{gArd9fOTLxn{LpJzW39mGm5)LD}};UBJz zuUd;+zmBT!k(NF78L70{=jN6TMJJ&iawu`mmZ_HeAMCvaP+ZNr_Zu97Ly+Ju0m9%8 z!QI{6g1ZHGcb5QzyA#~qAy{yC37R0`F5Z3KeRl1-r|P@++^_0>Ri~?1GsBwI-K)FT znwsDIpXYzHq}GVW9VY$Va6zFGYCg#(`L%P)OWV%LbMzm;q>Y%(!MRIlzm z)+P*B%L16%F}SN)WGU41ftZ*B_8}Te*7WI0>a!-$DqB0AnizYperD4|&8lYKXbEa~ z7K)RBE&@Jj!}Vze$^g|3x21OBClYF8++;{W1Toagd9f=+A@RN4K3$TUJSTPvH}cRq z-;rC<1KZrY1ue4YvIHm@@dU3-6euyh%1i^52sC8SuHPd%DgV>&Sl{C#m$bM3wU4B$gR8L15q;;4nK>EJWbd$yIlT}$+TlM?TRB!LMz-5AX#AKnS{*zp2j}}8 zv&M7B>1eN5P1luWN&?}%+AG2XfF!`CfvG~e3K|^BO-*g3pu=(9KHE~Hqn?t*-8%7z z{<+#ImHy&o5lCx5sPz;5!*@}9#$~#_XG&bc@3}T~Mbbr)zV{P1+@pbv=^`%=Lh{u+ zxNGd-k3A*tEGYun4yO&dD@O24u-HA{v&$)jYLd4d2{X*rD9(zy?zATsXs@UHajP;Q zJBgCSbfR+CgeOzHP~FJkQQ9!!S>yqO8I7SL6B>P08Ops_V{xXNI2&@dOtU`k(7oLu zW;6{qf?Pju1~&Y@u(LZb#~R&?Mr)OnWJBpTA~pIFb8~|-9ub=E)i5ur5;dc`Za|)J z0wO%o@$cj;Pg^}E5IS?IqHqL80y&#*s}RacReVU)V&rjqR8q1hvnB@{I+cdjA#1X< zPtk2`5aAePb*KX|K;?W=H7o{6PHkmy2qHYrLaw^bWNTc2j4J=65HQmh(iQ#5;6xbB z1&N=ht$;8dqe}L0q3+t^M-{~`n{v_B`ynltSdsCG+p_mZ$wp}M<&P2}{F2~D zR}=hs?l(?-wR~L5AzUh7x$sfsLXT>oO!{oCON)6%@{A&6551>Tp^B?1buxj4`-jr` z=5in#*-ErTfGeb}nGPBArASvCz~hBaL?A*JzxAR%zxc&nT8d1Bd<^hE)Q;F6@Kd=r8Vj}9f8ZV;Ci_|cx?D#b_WTN$cb8CKnB!gB zA#8@xM;8T%kSU$hKp%*bj%FgL%*fjtRT1&eyi~rT0B1j{NEjOBSg`6kk z&6cN)dfcR!-BO;r=h$ZptOoAj%?_gMs}D7hWM%RW z=FcD9WDi4-d-$DyubPxPw)lfEJ*g=hqSKh@!3}v@ePc}4ti%9^?!)VeR{Vjc0JR^o z)%dWzAdR=5Y-6XPSq2PwZRJ&cWD(CN&MPguIm7N@o`U}z1O4nZmM0!z%IZAZs z+SGA%4^EsY#8pOn*3f~A$djMo#35ABo&2zg6p@c(020)p5e_M`IV1q7?8nElGxX(K zIua8Cj_4+fSV{cg4!C)^G8p z@fUW%zKiDKr|t>b(&%u?S|v?P=Ad>UqS5=wNpeho>EhqgT0UAAXRztZHDSeW;6ZOd zhk|SGhEdVZdkrW-yaE1pS-a%<6YUiO6caWPct%NV*M(3gamg zw40D>_Dyy=67S+72qqLGIHa2!+Q?ky5AP6wBZf3J*)LZzpds2oQU_u}8pEZ6oQs$N z1d?K@He(P{&AZl;?lO|lR}Y#}e}d{@;@ihLW5|+I5q1LRCA5y(MpOvu2dua=?Xt_b zF^Wf17>8B7v|~^Ou{pnRo70oY!?-PBcCz+qZyP(vm_=3Z83rg1WHGnw!_mawH`8GZ zOb!2L!SKlX*t4@&_%pBWDhUDny>*~%wCrFV7KrC9_O}-kX+E{?pm0o=FLKA~PdBxs z_{EI0@cu60)}3%J`qvh;@RrE#nQZQG&9$;a$2%1f@M~?~r<937U``X*7^y14ca$W=ez(uL`tvJVBu5Zb zqPYQ7SCm*WC;V;(aW7tckchk{O}p!t!9O%4lR>6pH7j>t66`_0L)@A8S@S7&X{2>7DJDC!>dO{Gc!+^1NW+-(pZ?ei;_>QD!%Q$p&9XfZwxrLx zsJFniRSA7yJ@ddI6yNsTeg5J5YxAca5kLRW=Mn0zTkn@S@vk|&Y1>D;yE8j*#|RZ< zSE^Ds7hJ{%;gu5-X1+_;Sz^vYPVZU;B=lWfF0M%>I&nw(tVuJ=NoStDZP(UV5bY5k zWuW*G!`9ber01yciv%8!D+W9!e6Vq9bK)iiS$Dv&vsQp@A&%2XK74Lg&`Kf=z8?c` zqdYzalAhp_KCj+eQw%NLPYqAnYxCi1>WJz(cXic+b_o50dS^SxYw&k=zzUXL)q_6-kiWmes8o zF)Mlui}~Sa#HBid<%h&tE{3?Bm$`huO%Y0{rt9TLXz6Z(o|?-)>~M>c4avV-l)Q(H zWXigR){@Wlk2));4mOS{xZty2aMN9Uh8B|R@SV+-LxdRWC-NUFj)}wRbQq|%E0&#s zB-ZgNp>6JZnn&4%@Ymb%w??fy9arS&8m@KQpWcr#8aJ+4go$yRFZ4XjWLGU=Ah*u`_QjG^ zj&C)h;wt>*c!S&Z7F?Ma=sK`Zpj=*Lt-85=>T$q63UAd}brGoWT2XQACuZfhw&q&! zlkyg%?aKIGPTL*v(s!s07S0E%U4-Z_j?&QejHhc@4~OznOo^eR`930s)uS z&^?5ymS)x5JaQc5-A-z)y-B%jhnqaB!Fo4ekyCR{I&d~bM^x6mXQHwJk5^GzO}}15 ztcP)Pk3cX+*}%-z9Z#XlzU(O=MY<(8j&t&*3TbqE>k{ENZ%pky74-t~!9|W^le^ef zVYoOmU1Aa69W>@|LcajYNZb5u#%Tvw3m%ry&Rn%T&pxCm9$2$awfomD*}sp)gRASpOE&ial)&#zhv8gXNU3Ban)!?K$Lg7K~*x5cZA=^8(Kz@<;8M)f?`Fme|Bm> zu-`RhvrmO0=JSW;p1tb9KxPalf)UpaJqL}#Q zY)ayRup8y);o4@Iv~9EZm>mf8L$plXR3F6+TB65&%2gew=|ok;`Ta8?sPU*0;@hEq ze1SloT3e+5O^j#9A^Z3DxBnK8cugeeYG`d@>iBnc|EoaaHIbl-sjZ_WwG)Y$p|$nj z$N%rg5ySwFIR76U@qY@A0Q72aoJ{`-y#5nH00O6fmVbi~aR8tgf%?ZBze0$$gq0?& zLC;0kZBEMsvxmg=NoyV(M&=HQ(|*Y+?r>d>oN#K}ZQvfNvG=h+{e$f8HVOj_*4Q_&mJ6yz*j0J0altEcpSHA0;i{(#vbtdtA z^5Bu6qGe zN-ORAcwjP=lLH2EtvI%t*Llu-*dB2yb=56 z=|P~fkBid#NX;sif>mlXotFN2$~|!;>KkqTxonozjo^!(9b^PYAU=isIwT@K4Cq7-Xba7$adCJ1iogaC(js5g5nqh`$E?KvNM+m~3y(bmf0GJTH1)Ahui z1Wo?vFF*{@-zW8Ar+o6iFBPn>o#rI2iJbb^2^xm{^ zzNmL)?~O)wxFk+xEs7+!shr_3cbwd~5Yh8g5^m9aP2mTE)L!D!FEF_BYroid4*yJ< zdwTHg`CAPhqF$8k4a7QXs8))4FM0=)rdUDG<$=Fup$9n8c)1VIiJfdVpW=+Y2K5?$ zSLKn*&Eb)Xsv`!|t##=UkgvB9nR6I8cV~&JTAe6xoFuc5l|X=@0zk~BLvqW?`#LO? zr9+p4S?*s;g9$#2+h${Q^w`#DlkYzfGZ?)tpeo6Y>qHhMMqni+vJ4SAV||c`bwB}U zP`l!;ab$Xv4$n5+1(Gl=E49cSzw@|x$;LpM>pecyHYx$}HwaXdGOk!74fs@yokKIev#_7HO|` z%}VG=9c}0)fa(x%PRsNw&jTAc>^v zNX+D<>uF_GC1gm4!7h-9TR8ZLOIQPR;CQvgm}Q6-q3y>?){zOY@m?Ai7Gr{Oc-K8MDK`x|91jvvVWlr_XzOeJ+TU=EoYtVzk zZ~={)KAZlr1|&}z9V))CNBDQV%$QK%k{mPx@{`@*F33w()^=D2Kt$R$&EWyb5RYn; z(7N$}j>@fAMZdmfI4kYD!C{(mP`>(j>fL2f1w`L)0A7S5p&$>hcE5$6E#|%ZhM(cC z7d8;P9}CBayu1*`RdH8O38X6%jo!+jC2d~p*WAJDV_Vk-Vruz@yrT98=C$Lu3Azb3 z5NbTAH@jXG#!i@=Gzf?rY;4>XrEd4EYx7vyk6Rcd)C@hXh04p8DZ*fpjPLHNX-eal zO3(%+(16Cq`HZVFB=7q6LEBk$PQ}SE>=WuxVgi%cGwFeLpCdGOn&$0DPk#s*FsbiU z4vzoknHuz@e$%I|%Mt{P-JJ1?=XhmA@zHebc-~T$D0od0yzrYeF~4xmzd^DuhR_5R zWrhCBlp0q>j2_W5BPIP5$&X>f??oI94}A3c_zmoz=kP(GrLH2;r9YJYAxpW!7eQPe zc??brK!Q5X@y20Cx6jZ=^GD_7mK+L2J9&x6<-m|QP3g$UG)?1y2^0;_w@QPo?CZJ} zfP2sR5@`x3w&RuvL`|jJF!-Q5S2x|kn_P27dw-GYfN3Necf!#g!Su(3hFdRZZd)HXNuD;RDs0XO$C>CaNmJA<93>9K!J{HkR~{~$4ky=hFt zkb>cHx-zQ-H=1+6s-*M-d!sduauV|{U@wr=f2#iwkD++#IWd6$i(R1+336 zGi;+0Yx=AS5geLZ`b<#3rsTJ{i{FmUzb$tR$~IeNm49roX}xC@cFxj{_AG_Hd)-9k zFsmCk501|d8;)mnN#U0JR>v=W12%o%d^fuK1_%j|To-aae2R7_f#)?)HD~bR20o_{ z9iz20ZXsYF|Kww*To`6)8Ps&?v%6(}_lX(#l{&LvfXih5I^r6}1ZFr9hEAa)1Y#ys zw;05aZk7F^%U#A<%0{;rJSEYpX$;$0R*a&@Ofk=GYTF?l>}HdVh__y5L8=Ig2F|Jj zM$Z1N5CY8@b)pH?-xA?OzS_r|lLB~B_LoZnIbI#>FnNmg8Y~^&P>;z8?65$1=U1jZ z>HMt8B*%XGRbC0_ujb`+5|(eS-PC{OCLxjS+kV)edIKP*WeNvuL|ffeT`w;n0d6ct_heT%wvtFvkq!@rqBiEJt;V(clR378^uK-P-QdT(-OI^KI}46AuER7_Zc$|I{{Dk6{|XQwPa9D(<= z=%;WjiIhFCX$D;$HF+AN?lqSNj{3_Fn zt>xx;^WR%9R1eSEhoEZ@BKkl~fzWzxxA*R9=g6Ey4tQ*SU9=&QLP0BY@S&XktpD=iUu5V=wk_fIK7E zk~Q?ZlIf1D<(~0irF8n?xm0;hko3FhL$U~uPx)o0Q`P$_^!PIlHDTAYb*iJ!WZ~6P z$52=0?2n}I)df#RKzFUKRZgdVScfh4TkM#4EH`v$sH z;*eq%qwy!hWG+-dvB7-1?94`#3{7fd;f&F=5pzGpNSoHIV z3U`islO83svI%BMvPKg{Z;Y5?8!KAbarI0o$u1&B1U0poB5WkVBPObHYnqlnXOFp7 z3(G{GkB+fXSF-vPM^-poh=1`#w;)pRu1BtjPHu-Aiiz@&e_A}-fuI&;%y?rm7j>u5GU)xf(`?&V;+EKdDp{zLy zYm(c`(VKd{N%De^>V;S}M|AWV!W`0wU?6d*-q*{HuVZVxQG*T%zNoNr!UD^kAB^4P zMF63eDX!ux0zWz}n2R?`(^e5PeKQYje83AcOKeT`r*e^I3ah8}r7ubPBS!4IUK4{* z>|d|*xtDjHjKTM6{6`^?BQF6oGM#nd;(?1AXfkk4dhXPT5Klcb5FFICWy@FUJPdBP zvaf-bTSjn~x9ZMLA3xU}6lB`^cb82S& zF1i|48L$7h79=2@;}Tt<;M1QmAA9)>EB4a5uDbq0$!woK+qz}+WM6kh?T~Z7ezBYS z;fnzuw=+Cb)0a&(CZ+?d@QN^wm4g|SbxFb?WxP%_=^r*KZcIqL=LqdnbX_HOf=Dh< zdu0V4P+Ju+>@}R>#7LSp!54bP>4?wG2H=Kaz^U_?rEQ8m?h*LiK1oN>IB;kQK?om|L>_!7qOxs{|Hc5(p8 zaANUn<=yNAhmrL%Y1nuHi-(;WysoH2OyX78s99jvp!4KxhCx}Wx?wA4u6lS&xg2hX zNpfhUW{SYs_aVsH%5V&*ihTEbs2fP_z-+Xy9Z0jwsxHzbPQij#w9gb%ftRAeKlIfu z_~n*Q6`}3SLK_-XQGL2g10ir%JJSzo=sA6X^}$q;w2zk9{3a9nklI zrjz|`%$)Cc7>xmPrh*>)S}%Q@$)k$A%f4O?OSWCY>Q~)Iv++<9ILy$Yko(YQ>hjjl z07tP2e!Du5_3aI9p1QVkG1;!3ZG0)@D1(yAhYo4VD|-Yp9#x?O$(rCf3z20Z&H7s^ z5@CUlzPxJ->aI-)^E2ty!+qr;mxUkAA%E{rydJ0Sh;Zc4Kt zLA}5m4xzSslTIxkh=_-(nX3Zk;3|4hC!TpKQpe*qomt1>ZVq36Hx*%88qat}kEh~0 zT%IAwf*ng%A7WsRP*SGXm^8A_8t41HkUJvQjo$aXlrvw2{g|VM-!nX!TEN$Nx9f?Ull~*jm}zx&1R%?G*?9t2?(f zz;BRVk1*Tv z?A)t3#>mjx(AL;g-PF?D!uho~Z{GL`{Ph5A{(6HWu;0Sc)?Cic`9W}Vd6pBC(3-VTwbAybE7)|hL2 zUmQRXL{dfWFwoQLzT3DxS-5TMs`6xCVxa4fpL9(krAPiy8%PT1M;QB;B5Olqv~UZ4 zx*{pPR)cifP_Aq54jE9Y7eOyhKC@&6CL3Q?H*2xn7lq~5-yh|iOKB>?HMkv zR~5{1x0bGp+@H^xczW`| z!qel-KfcXJ0Q53yQjuFOQXohXVhZsL<3kYK(&A|@#e$*dcdm;3ia^^)l=_ziEf%bk zZ^{A57e<^v*B3RKWI2T%l!H*FtPD-maP`LQ3Qi>)n}Ek;xw3SYJaD<1_)!?j99R(} zfV^dk^3QQibdtBdIb0!3M|cwMdn!`SpQ0 zn@7fo*kZF%pW8!Iu|E`bHcWn>8RMLmI-l|HbYA6NagqaijTh2UJ*!>%4s=s^uZiC} zi5>t3>>yHs2$wOpsHG3oNVt0p)*w$j((eP5NQ829E!qLL(Mqz3*?5=6=~Noc1pso4A%Yz|K-`S2qwiOQCl&}MM62AHT)s*tU2^DYU9mr*DQHvZ^ov@@iloP98TCwp*SxP%nknZ%h%4-OwkR1l|XE-h-ur=F2 zjVeRb(@{-Eix%4b{#S zshdJv;uM~~>6Vzcp1|a@hYV;MA7u?z-U)v~e)5(j0=-*k=%B`I)6#*B`T^nKJ~_fh zlJL2TQ7$+vM~VPF>plZZXYnIp=sayd4I#rfSLc)c5QD_5Z*QOTUEj{V93Bl+x9m;A z26U*#{2?!~Ds%bta2-u`Y_XB4`g-%~>7M3_GHMSH2dQX1O_PP1TMZnV8Ax>Hou7d4HOje9>4KZ|*bLv*QH`r- zJ7gB@ZV1fIoh)lGB_yQinhK11-V{J1_SyLzN|W(cs9~sa9~|HAy%PQGmHJGCj^u)V zp@YvM?sH(a=vmc9_#R8XRy;~2rS00P<`NsN5x2jlgoZ1!elS7jQ7n9`5j5)ddO^e& zAA(jUm{`TN9t$Ib{X(`Dm}F$+S`B_pG}c&G2fl6F@sog;ODRLq3GAhTky$~di5{XM z+%thfA~b6wtW$ftzix(vHJ(20b#(Du{PVbm!4LG=Dyk|-->oRdpY7=xmr|?`ksMh; zyc+s@sE#yHb$b?}U`6havc*}NIt1vBph2{_0l^~>euH+eqnAaAAqE&=%tm#Ll@qn<*aje(u6>OPho99pCthX2x!j)MD9`rfORAv#Pq|%@G*q(yyXW=TAu6NR6;noUo^nY(6J1hb3SqxS6nZew=tGBd zBR-n(a_0R^gGu*1(DDh5QQyikGtIRgs81`a?_RX*p4U}P35MPoNu8@RgHjtdYTCx| zXxScf+STz|7DD68)0yQ#_~q0ii3_96Q$w*sA0nC?be-I5**OtW?0l6XwS?>-Dw!lI z_yi+;h*$DhoS#WDp%RwJKQ6iIiM?RPs-f~b+p1tck3Au-X2HZP1&v-3{64m6W~$JM zws=^GwM$jUTz4j^qmp}*J~fMhc8V+`pxZ}~J(MG6DPS})>nDkVpmjI|H8;e7m~TGLaOTx0W|JDdqR1?oXm;Z5G=NBCgrwQUBsMnN%iK)7WYV2AA9 zznk$jPG^0i+9HE5pE}k8@5%xprkXz5Eh#xRy+*dutsMvw#IRNH11Hj8^M@a&@M$xa z>agd{8DN=fk45-hnqpKpFO4n2ozVXXBbEc9cZ#g9_eV0g`p{O{LGiz@J#uE<>J_It zvPgdG%l%eNXljRMA^?@*jWsrg zZOh44BO)K%m;pt~IU3};2(Qs(&c(;=8u2}x#sWGr)GmVPMRHQ744F^xR)&Q3C#<9U z+~M60_8)i;c5A2E2gm+EM+J!=IbE;aSjO@CHf>A3b(`1MhO&0hNTP}bbFO>~)iGq* zV(5=KE-kp8cgC9&SIjWSI7(UC;L%qQ$({m>s%Rx~@aGdBD%M`(kMb?KB#Q`feBCv4 zp{SBSUvKexTsV`bnu`sLwUa;0-jEf@Snc$>Sg%A4^deqssmz)EP^gb1;ye1DXQa|e ztoaU3GfTvhHD;SeTlxZhkag2hlBl4+bb_PK@A2q^+$k+YY-<}=Hg3f}ivcH9KBjfo#l|CYF%+k>n;}O;X zaf3gc9sySS-CZgnV=yy9Wfh5*&zzih{``Eejk>7_aw?WrpnO|;A|sVro&>*Jj26^- zK>)KP+CKOOVONf)Q%)>B{+o|#$+82w^_Jb1c#Nn!WNY&*4&@`9=<1K^0b)S&wf8PY zDTulhU#6sAJ;^sYv4}Z69TR~|Pc9NNz$rOuxn*jdQUfvtJ|vi9YSuMdBzt@+^;j6& zSm~MYT<_=)-CFAH!IM`u1zg_s1!n!^*ZNzjW#ra+TAjQUuCZ5@*F^-KCc_}0^14Z5 zNF^(ko80lRDZEeNPY;P4eTlMA>2waOez%@02qL5x-Oe75*>Zvy63X5!d(P0$YS_oW z0)9o`o8QWZyS%7Aa$RxUNKbV2i8DSJ&lAWogP7{p!x41V(=7|_w6v!D!QA`1U+}Cy zyw56b$6l`Qz;Wc(W=Jk6B!7I+;aG9T-d=L`Ms{xB^+VfFl8fUcpt@J zA0FSt?CsSumv__Xzm42X%Mwlb;s2B4Wwy4IxT=N3*#%K{gF*mZTh%1MU9ut)uQ$Uk zqxf?{Hu3J#j4(+$t@jAG77bibeft>fYBwEQ+@0c2gKnG?$10?CQltd98=n-It?L98 zU%k%9wE6*ZcT4ia6|HoRvp|I!g>?uTjXD({wcQLy>Ai8Cx2YrQiMrLh#PMg0OzI<{ zzoW8`@iqQ%DG11d?d=N#puTl^ST3VtkG^|H@8{HFYhZZo0XaFqPCN7ICIZ3bno36J z2oW4Idq;K zG!btwRcMFFn~)JOasUJ;gsSW@^E5s=?Tfysh1e`U3TpVWHn`Pajs+hx^cIuF#oY~( zigAIJ`Y;JW*$9f5TZx;Hhxnnws6=_lexO?cb8%I(=}U52CFJy^yvd_*k?OcJ9*wL+ z(Wuz&u)dlN{|<8O`gZ_6o%Xd${r&y?zXhLO;f0BzqZMGlU~Fmlca{IEg0FYL0fHn# zfYi$ppf`Z(OUV=nF*5%rn|TE(fRxPgFWB=>-tu=Ve}M%{C#U}#Q1p7Bjh(Ht#lIi) z@9gOYc*E4caGw8RPk)at@c18O%E86d$yw6m6`cKRc}+BBY-jt|URgtXmH*&tud`xe zYG&wS{ZAuczysmm-zfu3_`eMMU-iBcI1@vU*LGfj^4I@>&FgDRprO~Pe69Syv|)H{ z=I`=v(5VLyhWU{GV=??C{)z!iWQn|t?Fe`*F&bzeFh9{c#zQk~E{$;8G6wXA;6pi_ zEyX-Sy@M2u2E<>baey!*GJny=pZAmE4UFe<+xiXi1BEDYPqwf_!InjcKwlqe;TnXA zS7@KzB$v}4+A36d(gTzw;_BL|u~U|}fu>KKS4GNr0|$~mItIk44?dn-Ur(Xja*V%5 zp6>8aFEcQjs80g+3z5qLD9{zc-#&qde+J-$d0HC|-$>w2E!PAn_C~%H4%Sq3q<9nr zj0Xv`tvpx|b_kY}(miZPSpK$_RkAaMCDgiI zsmW6K4rz#G<;rVlYBU4fLQZ->P_~k+gE{~CgUPGe=8%vm#CwrZD#)@U4Q|&H%7-%H zYT??q#cU&3m$Q8oQSugmV`r7f0G;%t-Y0sl6EbII2t#*hk1CUGt#o))7k`wM!eO^x-j1`dW9`FJE(tVpY%k=ad$_UV&RIk%| zk(V27z)P>Clk|@^BX1Uh>Sj|c_D?l2>z|qdV*$UUB{t3`kVU3)n zERqepJDHxrFQ9aBpmZ~9ZWJMOuJy8^yLLPz(9Z<$<$x%7HeY#;pGb6i8IbE zas%$dz!x{FCu?=}sk4z(xI^?QT1ZtPAljNN!KR#R^I|S)EDfgW6wh z4kyFGbg$-+$xk{!OdY14P{xUL__|s`mj8JR; zP|~3^<;ayf9jvs1`>CKEQtM9IAj zj{M+fPpd;Rv4eNuR!s31Vq;4l4t(omeXPA*Ww$_=P}e79x`ephosL*M7LGAA z=mI=m*wmCq(4mpq68eTGK@_2em4I!pOk_L;`Os#fEa>!sE2@2AdWsE!3WlP2b26h0 zJ4Nwa4MQx#Z7%Ns^eYG!Y6oXf{qqwMMrKzl1i3fm?s#@%&Z5!Yy{i*aRC`n4xyxE# zlh*s&Cg+BO3u)K6JPy8DH8M)t{lc={H<0R$QaQTAS1dgx6eLhc{Qc%Mc7Nn$tM^VD z-}Gq-WPS(aPFhJ>hFVs5*1t`n&zx7Zk&?VE--H`;;C&)g;o*xT5QDZUW2AIuWwl{WGS52J()$+@V;{p0jUe#TAg;nx^t@ zab6bYtR!)O1=HoFEoMh@h(UY-#Ze;) zz8bKahBN*~=5X^e0-;~ETnXPXCQf%3g#nh7l&~>ALtxt9%>f?9&kiN8`Fpkd|Tl_$I> zV^TpZFAnsdIZjOGmQ+N4=!6~heNqJ`;@lQ4TN5c0P?c$;=5n}jml?aHrR0oUq+|I$ z(E%o{74B0M54)`ydSgsl1SwigHhgWR09>Cfry8hCW@wwArzmLYG`P7xb!FqBUyyn9 zf(@x$@fn)sMY`pcR^vvh&l2wh7XW!Ezc)qAe5+MXUsUx@h zEkPgH&ZIyOq+G#<~+@rMG_xXTzpyn%+0c$V!bZQJDNfb6& zN@v3&#_ZZ>$wm-#5T!|5bWKr$YUGK!K$P`;_}$Z1L&w6tT8IpZeCxp<+eZ~cd}r3YMgI1@6+9DSe#CZzQ4qr^ z;z8rmx9@?c*Ggg$JA7fi;RnS+P-{e7otWS76}u89VIT+v3Tzv064Izu518}Kyc^Op|`8WWjXiaK(QsA7)O> z73dU*@37D--z@X0BL&?iZe_*u7NKuEEd5-T=Q;&RcMn_w6!XD2yz^36 zY1v{)u$X2rS~EHF%e55hx?_3rj>%s(Q#6$Co1;>^bOvhYvJO8AqHg*x}(Cx zn<04*;<(`Vwz`}Yez={DwBU7b$dzL)?rHm?O)`U+76Z7-qT6kGv1%1E`#xz*<7wBK0A%+6UKF1Oo@Pd0?y^VAv#^;j<#Vpr2Q4=6I-AA zc$Og2N+l{s^eMQyB(vrQ$ILgD3e0HSiaq7`{%Mm4%&3R>c9K1x9xvtsG>=TqrQ|mt zs`sSg%jR1XoGg*W z6MbQeZSwKT$?jY-auz%29vQ~Tn-3g~y5#cSU1sK@sC3rPoBzT7=FdF7_+pj!ubzoa zadC0u{RF#K;+FH7`B?sM4o=y~J?}pnKhTgrq$=VP6&LXrwmchl88hCAf`T4A+UCP6FM5pa^*Hni;+)>C^dta_8+krP-0 z9W!y2yea~oejn*nlVFK<4YhqLeX>ki@B8!Z>97^M1CjM8vN(CsCW5jKVB)&x^&1H} z%ww=Ov)%EX$ByN>kNrB6OFu1|!Qt$ejI=uOBN4qcwUn#fFz$1iESV0ILzb^ZqgL57 zl3AF$So~b$%46c5^zj_oW(YTXQIRRhJdFnxK?Dl;n!$Zt&J ztxj#QF6QB3L&pe&dL`CNp4bZs6cR{vGQ2A>QjN9(twa%7)=wI&oL76=aW;Z0Y{r?hd9Ezv5eVSOX)foqEo{3`C`@fx- zGo6obe~8hIa`3Ke{}R>a^VV30&%4&dfy`B}LD5vk?}W3Rk9cR<g^kk^9ov*#wt@7}4zV|^CZ6x5v^!830 z*PmVYVHuu;v71nGB5y0xumfLULhhOY)I=C_yR{`Nf-eCKtYd&vNx@MAi|0UjXZbyWluTG~r7G3#qJs${VBoq|@uoMX!~N)&eAckj zja;KT!6>7^8FA=UxzgiM-jk7*4T>tQc{OK}e!Vb{@UpI}XUI0wI|rKe^-S`FjV9}} zFmA+QM^737&m0e9ypPGTIbL8hg_N+1#ycJsZUvT~S`e6VY&Kfx+tNyFb!5sUHTf)$ ztp}ti z`GA2h7ey6z@{QJ$ZPS92C4y}>X!g|AX}(iXAAB)wZZV^GL-~M4TZ(Q@gHj50n>c%# z;fKLE@cY+`z)L-An>c)4Ao<q-5m>G$Z3(HNmZq9@@rO`s;Y-8rH`vpp$5n!CcPkQ( zDY)lshiIxq;#jSf1Rq#|v^XTtV4Cw)=pc1wn+rL`RIQMh5dx#2N{p5Y30EBgg!d|32t8tV`kaW;GK&MhwrGeVpd=(=TSJ?i zm{nh{s0E}A(k@X`2L~nSsP{E>Pa9@X7Au-yh9y2300}U{l46dt#56yY4dRCQWkJ^q z-f89ja7TL|7_7a43L(fzU9#J27t9%C@~+pa)aCfH16yeAnj*+xRK82JIe2mzRs?Y$ z)*uP3(FI0q=SOS#E5gvCuSXGs!~hjjgmlEkMxhr68)LmceC`Np#pA2qAPyakK2%-SNZCvHxm+W zS#e#D1%)>vV08PO6o>HgB0zL<6nqg!y33nxxBBKsS4Cy{?&mD8oi|(6@PtY`#w50r z#(syiV;3#l!J}?#E*PXr#+XIL6=wmW(|a%DH@#xhh*aND|CAOl+ITH4Op-901rdZo zXio$j%KTsKy=71x{hKYC1cJ-P2^Ju@1lJHexVyW%ySuvwcX!v|?(XjH667}jx$iww zQ};>Ozqt(PQa-w&h>zfFO3PSn>Y?u<}glt|-twX;HB(l475bBHj(P#ZIzUJN`r#_qj z6o*4so&yO5S}PELjBt2WbpUS)m76*^$cbBj8=Tc*jQJPE&b5WNVI}y-^>SVP&HV=P z^eftLsLn=)pbJAJv6VzIspx7*U!Ouz`y^KdGu|D{S7OM?{sV#nUPySK} z+vJZA?U)d7@?^}I;Y5bs1P~oGYs`(d~}hN z?Ov%Vr}FD)2j(lV5_GBDh_W4E0HP)^Ng#EEBlMM)0w6ei>$Hpo+F7O&-a;nPF=-CaiW&Yz(9 z9Ah1_(2KmSiaI+nijcDJOWNo(X*W>Zw5M17#gO{9zhK9QbH(&Wa$uv z1tswGYBKun6<@RMkimB@T4aoG_1w#qEtInG7_%SmmwK)ozhWr-Fyv3V9lT_IJkVQk zm|#@XSu2a6ba4FI$T~U2L4p~8L{*gWo{u1#wMQefGLLWI{7ZB4HvNSy88wwoxCXFk zzD9!Vcedd~`)`u0135H-bIz0TeBrfk@uByYO_4$d8}0G?tcraz%KPsRqVDX|`)1A3*}S!&W<#Q{kg| zYNQ^q&nWdWoL~oVid882*&($r(ShGr^KCca2@CbG{){t?T;o5ld>+2$e2oY87@b23 zd$Jgkr9yU98Qz438Ln#~$_{jOUv^Mu&?~G@1_K4hwv)-y{MNB!3Fo3?K)Cm!7^r{!%D z?=2^kg(PB;nH>y4-=*=o&ox8FztCdI!!Tin$HpYQ`x+OCZqeT~2(R!nF1y$R*04`y z{Uc8fPItD%w6;qrD*jBiKyP4{%0|4;FHtPQp<608GDnCk8g;t63MPasnuNI}+Ic$n zne7^1Jo=LK-{v6i4_(2qK!k+z8o~Dg^tf=kgM=TU^&V{r92~_8iAO^jr#x~3APZdH zIw~0~ToNGw;KZurlkKW5N!x5FS_@N034bn)>S22AOXB zt=95mRe%mkb1W5Fm&%K9IY~ETmSY_<>D&=ZhagnnIFHpT-?DrlbcP=f6GAi0q9qjC zFg(y&uoK?oEyMx{%HPnd!czzYFY|-_yF#2Mv}!6P`qab_Z+qNPrCX(-XpV>D`%Vf` zFyz>@-wg{)1U$Uf=p>h8=CAbgj9mZ^FLhS=VU=*wgJ@)cS5z<#d^tQKM3m8jj#z2r zD@JXR>)Oo7r9FH2&b<$jh{8=~=CRor8A`*W!Y02w!mDi<27lqIu9%FNyb|sl`GO1t5SkVox z$z0Kz{D`{rUIgQDdG=LRXcM2l1U01}+>HG^e1`n3(#mo%dVvE@8Dk3ZIi)GO=H*pW zh>AWO%jB6b9@gFw`5qUwqTLr`qXaJMX*Z^V$DNvW(+QSPj6ou_?@|waOpF=MzUp76 zweY?}+foc-4D!1rQ0|c1YK*YB1x&MPKBKqbaXQ^x;Pu4{>TwX?bGhj)r5x+opa))t zX}c^W9*^5#NB8ioQVDf#0xYp^vPA6p61oU0oT$-y%N<*5F%lXfZbj{cZYc3x{!u!0 zk`Zb8l-|IePHHIi4W;zmzXqroF-EByV(qryI{5lZv?S=!$W$lINTr)g81Q`C$Q zVT!k>7Ec;0HjD|Q5*uC}f)Dy2{UpHFOpQ(oAif|PGeQpjdmZ1S*WMkHuXmLohT<5m zjblI%(JrK8GY0)z=)MVGmPGi9wV?cFk&+GGNje^Pg&Tgnrpk)da2pD;BCLAzSW>u| zs202(ZA@DtigfT`s~A)5IDt_+&VWF;snz2DywIwbRu%+k;d+iqCq09yTQm~15{c2TeQvwe#S{b zOkBK8tsUyojIO*A8BvUxn=u8{#f)pw3Y_9e@+Pr`%TvXMoml?1c31)<*hs6@iTk32 z3E~5&71^b=r9%7dVL8{xu8%Hm>Hemd`)9wMk*rWox%dfAf)IXMaj)eN7N^6dDcgGJd@cIS#>|1xuSc?* zO!cpq8YROX@n&aa~(I(X8Wsr@Edap;7C#t*SxaSQ>eK+Cry z-l0;z;#gfD-zJV^*9sz?c4q1MtxIPWIY6}u&I;j>E6kJZr7!3lUF8t@y8mQMpdN?f5jbEM+Xg2O1yRxeh8L*w4C0d z6^aZGn{y090o2UOx^opt1>%)l+a8L%eor*5gSx6jT(eBwz8^E8LM4 zqd?~%uYsq2$697ZoE&Wwf+(XE2+UPk?PYmixXe?R2vP`{C` zqJT6ehF;vjOQ?_2}R0cH<(=ssyj8U z7%Q60mH+m&FV9zc=4?6h@Hs3&dCh1c{o0zk{UGl`FMa@B(+7>RJLuDTKTJ{RV6v_U zw`f*!lh@>1zwh)CryZRdW5Z>VDEa6=<_(cYl4I{tKj|OWgPpj@%H?{l34h4T&b9o# zZA1N}ZXQRU5^K0}LSD``+l?hjTNYYkN}WyF6xtYzo9Fs6)hy!(k6@V5av|gXvr2g3drVvv=V;iTPiK=*P zc9(^3dgOsiY;Rx9vbpbdcNaj<;f@GWZ!6?z#=##aOuw9;-bI8zec)adYI*2hCmgcn zAL7-i#?&Vw9QIS-=i@70xN`qcgzMwDzFH;u{Rh>w`$!E4_h-O#a>h$_eqG1LO1O5S zW)X|htpO{eHJw+Bf{vB=;4)ZhMMhScNO=cSC97hjU(3#i{*0)|8aT)b)X7)5w!d*C zp`-+!e`)da;vbhSS+zzA(a;ODl5?+)kLl~+D8KPq+E8GG?qohZdJkn#!Frf6rV8-8 zpsB6Ra4ieE)LW`s)yu==PA9N94%a_?iqTTV(^y1LjDW7`$Af!Rq!`fjZ1R?=7Y|_s zQJ}OHt(nWcx^K7+`mx`|K;Jf04~y++-UyG~SdJH2hR?fRt! z9Kr}{>SApj5lwdtVsA||=`lN}7h0-n0gHVS1ks&!F*=8n!m*~sD%XK~Hd1ikm-U2u*T%r$!7u*8 zPb#@!>H!&_JJd~yG9SH&y%v*%xtSLc*;$B9x}yWBacdcTnkESHW7N)!pWb610W9Ul zGZhxF+mHR+T(w7peR*#w7xd~PW?6o2Wz)ey92XEfs!O)3jzj!Wb}*5$`U}RqDP2(q zOJP+K#I!?enb}#x+nlPByyvtu3Kyxs>uka;joYT^xd||H05sj${15o_UqVseAy2#a zPJRFE?LW)-dgCI zgTsS=Wh({8{|P8bNl$6Qy|)uDb}hHB(Sh#p!Yy66g5?%^{-|(*TAz_nH$aZz>C-h&7 z;atP8>8;;M8gB+4$9 zL~Vz_XMK?}fVHcSr}ayBGmlEqj#J8L(ix(6F<3-2gZ8#4J({dqPg_Y(C>|?Ra zQqT${LF+z{$%ywu{Wj4pdZ>C{@ld8zcwOh&$7wdN#6A;^tGLqM%qqoEr^Onq1`EKl z;?g#Q@qF90XK14~n_@EUo+?GaL`a$C<<=Df?%(n8XHl0YTxDQYNhUiWkbGiIZdXjq z<4hjSKNRgItePDh#B>W`nmB#YrsGUthA7(GbP{m32CL#N(RBa3$f=-T-csX#0s8fbtZ5>AFgL_&8an@^yPHnC<4_ zY8#;FT)5^dsmz*cBSZeI5%4v%ftjYg97r0t{4GOmE{0Wr<^KBj@_Soe%|fSCk1{P* z0j5WqLSSDYWI-LqSW9Az%-8Y2(`Xa8ie6S;mvus^W@cPODyi2?76X^lW+N+_e7YqT zViN!-hMunZJsn6|4tb`dHM@lwj3z*ut2kIkE7C19Z3BlVAf>DeE}~dY$IkmVF;&|t zJbEn=%}r<-+gJ4Ne69rsiVt{bzeA&2n6(fPfmcI2<;t}9soQU{%T)U&Q~lN-lv=9W zVM9HYzJ(k1ZR_mZ6!4X<-N^Y*2W<0Y*As=XPO@1&SU3~|*gc3$h7^2$5r32WaoP&3Y1RMIlw8(&ymiXaNX98BS0PAdLik;BnB7QqUHXg(`81$ zrkM7Ff|^WQP2i8w3bs+OHqemEO})I&h^PrGOd#gi3wUEelp-|0WQT$lOSGIAFqy5{ zX(mm8O*c9(wgSoNj@AeBp6`c!XJZ5{%(yR}W@V9C?H{_LQrC4*zTvx&p<)+QpkaQq zI|f4w`6l#CH)IEwW-IRf&v7?F^dO+>9+vUtnoS;*q4y!;_@ zap@MT=-2z4bEWX^YX22caN<$Z!ca`61MkDFznUqBxQ6=PS@o{bU$OnnRMtf43jiQ> zLFr;=G@_W?Vny0k+>~7_iB^@!)>X4)SoF`f@-3YULrn6IIU-CH3nxroBNpevP7M$kuP?LngE? zl?q|$ahz7iMZQ#Rl`?w?Fs8bly5EXEZF76BLw>`L$U9voh-T1UA~vKgq=+KW9BSyP ztlB6#%_0F5y}47SObC`9=Hs)b@N%tT8@=8oWAJ*$m_=#u)3IZ?O4!?LZzne{F4k6B z$Z5|LViktReMaI3x_>wxR+?LmwnqPmou@OA|JYmbFqua9WHT5ks7ZPgv#ZTG24u%> zKNIFpp|ROJAtnLfnTea#l8_Cq#Dw3W^gcU(dA&T(x539UDk$rDwy>3)fQUYi_iVC7 zBo3SmpXz_m+}FPOSZQP6$*vfSwm31_l89NM0JX?6sC~Hd!Ns(zR&*??bcx%=`WGg2 zMaND~fGIY6^A0U?(hC*Z4PBfxGN3v(8Eb}dC(Dro;8C-r^y(V0W4IUA`f1lS(__NP zb;SwS@*H)dr@;Hun{?6IP<}4Q*n_57L%zg=aZf|QpX*+rd#5nz?pdvys2eRiwHvyb zd!1Jl^ZAI-6uV8VocjrHg!ftn)O9+MID{z{?Yk0Nb~RhE$( z7e$kZS7_OK+9{+(osM8)yj(SNgK#vHiloksm$8K83Xe3A4<CjTqV z7?t=#3CUzgQHv_lf$Mq51~sfpyS!ys1`__cu31aMH~f;nDrCSK-e*$`X10 z!k;}dNJuLm^qp7RY|qam$-8mM^0TNVBo*P za>C|_nQU;AFlJj<$~UMks^Qce_EUjMXvO7*oC!#e{6dHm=ctSo5XrgPwRCuN-2uYn z=b!qMe$#Rt*Srpa;tHM6qQl&WBDqM@hTav1atAF(RHD!V(~$=H7%DTA55`T%!_+o= zB#*qvKlOhTQ*BL0p-nia!r*m&y09aq3?DWNjTc(D(N80*e4|B}juQBKm6Y~TU-pz! zh(0=7@1EfbX1r@DZt|DBl`V&qKJ+7sbW*MyM#H6@3M`Hv6GoW1U)?uLEnSgb*&;R` zAy{lPeGj_r>DWKwv#D6zr!s)z+jsz(HQ;LtTF1DWa!yaPu6${ z<7?n`?97f)O|2IVmlngeIg`d`<%x$^=zZuGnx-7@5eS%8edSN$i>aXu_FGZtQb;y+ zd)P#=_ZXB3xrhsgU7BL2MqJMm-j8)#7@%i&%+RUkav8>j<>y^tN1FG~j&h1RW$&_mSK?Q}OWvQ&>rO(uHy)wK_(>=vNtB zvB6GV#MSAd=}{%wRZa)W_&1WZ;7zz8kw~68#zq=H-wcZM4CbYN!BtK7vI9fxCR1N_ z&-X8~1G5oSUl|-UVGdpJr5&VS&cz?Eu*BGDKk>CfNI^CYqIO`@b5VhYx7`NM=jb6Z+rD;zx~d{L~Q zIfjGQ+HGUn8WMU6k1&f6^2z7}%5^fzp&NF^j}lKt-53eqAdj&p3bOo-0qzRgB3o zWGO63t(~0py zv{s>aE_8msXpxvq31v|GN{W$to!y}QYApKjRa<9PYjiXXse-qdtYgH|hqe5|20;1T zy@RyF|Km*JSJd*=6HmE8zca|cBkwC)LKft~_grF?v=1Rl3l;reXFjZ=!9~a@2#T;Y z$_4j}af}H~CySjz`(TY1{QB6LEF$1`T;m9>;6?1Cy@~xhZiPsi^!hsQDQaLr&NHyZ zc9m=1U0yNWO*>{sQJ&c`cDJ-x0KecSslXhl<|m18}isA zC+u--%C?4^L1s0%SJj@(t2{mqMW;}RPDb^io*(I7yHziBizH0s+xGTJUu2SNL#j%y zo~$Op9_~ramUql-_Hr}6&MdIm8hI0uqmeHh`{XYl$#C7+cT+GYASscSYy26MjbtG- zb=#_Vo9-!hZe(XOQY;;(YdDIkpisj}cXW^zP`WmD{mTFJ$PJPTChw!=R|e8{LtPR^{A|^f5#jF5fgufz}*=`8WL2pM4iUq8ad4M_=KU2 z^(aNA-VlXow0xoQOmC=UFoe1@LRQB@SM$0c0M#VU3kOo{@A7+U9Owam{sA>DUIYf9 zaDyQN88S#yb(ZwR$Rr523Pp+t@ieC+S);;oNyAj^aQR^ayBo}tvR~$)1@@`)kVz>7dU=mg*{{UcyQZ)=`~7aBbsQg zYN4Hc6IMc=y*kf707hsO_*XCo>Su|0C)_W@72(4A79l%Hs5X5tNw_lHLsS;X6MULr z^I{H|8((>3)Ci>PkLs)~Ze_qZQXgxKeTN82ZH!7!D;Vkl)jmz zp7H<3Dg4jkY5$tx3Fxl@g)Y9R#e4I)e-`wA9#eY*Ftv~WMpygSm;Mi?_K)p>?SDT^ z?OjI=G>J6-XOZuGQsaNAy3zl63)ub#C`}s>PKQGMFHWLp#f|`4`45Ksv2U?a;9cL4 zWxiEmRYAq}|A8T9fD4YJ%^8%BTBuz)J31R`RL5O*7yL=-@sXETCvc(0=QAbx1sLc8 zY@t#s99$=uH-33m-9d3t{?6Lo-j>qFz@q7SmaSlHxqVRHZ(>mXu0nC)mas*DQ6qmJ zI$6RxMuTV9dZ-*E2oMxmPPQb0C!k^K(v=i=O$sYbGApBFAz$@ckDnnVV!V3hqg=@7 zByKP>Y9@oMWt1n-a{@5XfWGolHt>KfS`-t?K2c2``#EtXUPtC#P0hP6F_O3xwc=|s zOIyI#(0Rbeov?gZe=f;wp6qKQamEOhbv9O%2XVW#@qwsc8!k;&artj6CRSK+zlEJ!x%f-or~A5ZsW~|f-1R3SQv);ID~b;5tURxbOjnsOC0?@cy}>wE{kuL0Ey0yF z5_rsJeJw%r{99n+LOr!n-W(KFhj5~mnza;LhUJV6hggO=tIHjyArNCK-F`?5uX~w? zmRS0r_$CK{E%~eUs60^l^IP?(-ed;5RpXq&Eqb44&-3}tf7v8FZSkZu6d<59*3|>O z-&GFO0I2AKrj7CI%8{C{=VX?n_@Qek=VKPs|jd9H^sX9r;EBTMsY@KNU>j zu`&}TtFre4O_wW+IDRRAZT~zEI`@)Gf}EQ->#Xxvoi#rv!^czRx#+*2y*ZMXuof$x zRNW&-6l_C5%lBtFR%!b&*13{@529w0i`*PpZFskmkOZE2_*7t@6fnIczZ(@81$`rb zisN0!TT3VP6BiHSQz1kY$C8zMStBE6UtZjgG+z_;yTllmOqHvJ`9yqBXODR=c$SK5 zaV5d07#I}J%XJi6L8&ml9Y`sGL+1X%O_jhk*m)+qMmTWvm+*PEggjnEGS zk$fJHYd$~|{%$u6!dl+iw5Xo}FxU910?j^|d<}Ej@d(yW@R)sQvCCs1zthvK5-=ox z`6PtD*sep^98qec7VLHxuMqr>MW1mg!~$RO2xWM8F)hC3aoi>A&P;4-hku@Y@Tz8n zYKsqin}g9d22BYLY_t&tt#QKwDrE!)ZGcO4T;l?lBeqahM_AjSQtT&< zCW$@Bq~)Z<$G!U&INQO0t9Cr$NaR@D-w5xejiXNm3{;4)KNC}#P_SXb?2D=C0Y;7v z`9+pq|9#dpaY3v^sSF&F@IS1iKv_1Xm2e~|r3)jPe;+U)Wdr49T9~BT4O{tUy3vgA zqV%$^=~L&#M2vhkSm86uvvljb2cVN}c zs&KhB3$6=ITVwap{(DrccQ%&y)0WSf(1y9^0V5rzwFA8*?M^|AT9}3dXYAloNL`Rz z`|XsovnmyHx|oXk>Urz$i>ajW9neudS_xj1SyW39A_G>cQ+ZCb-Fwd2azxtDQFLibdu*{S|P}q)}@HJ|t zfQ{FM0f;hzh5Vuq4Clq=O{#txbYJq2D(*aP??C3aiYze4e-JOnm?+_~EC+1>$6tEn^ zLt@ci)dHbQ5C!MG+-scSX$UZHH+c==YSaHn1O8D9h?$O1|3e$2dKJ}^K;H0%|d z0{%O9;Mo(gmvsEJ*JjpEPwwa5dOOVdKqgGQy;PkkpRF|)xi*EljhG+RpDwxHW~W^j zOA6tXt+?LGQ#1n4D*TrZ%j_=NQPxFsvM2>Y!=wOH{=P2yb^cxXumdq)saW*q)3`~m zji^Isjj!y?iz5pbEk_)VpEG_f24q23t(62VNxtc|S4qLL00D0qI5=Jn)&VX3&=KA3 zd7C?}NRZoTnRNCOkUz{-H5KI;exh>PN5u2>v_2-VcnY$2-0X($C1VOH+YyX_&C!_S zJxUPH#_(yeoDO;Rn**f4|JeIxFo5crma5KrL1|pWc;E-z{6M3^8kFH zZw>;e;>_@^P6vDVjqNIt+B?965e_ktcZ0Og7z48WqgsRa3^?o{o_CelcbjA@v_tr# zLLGzbtVTk75c{?G@HT%<(X1d8ETAX?w}qL)k_eKnsMD*kHT+W^R02BCJOj7cwJ<_D zyo5PMEfnauG2ONLH)@Xe_UDc@Ci_0$dbfEZ^3XVs(AkAuxoHJFVHsL%1d#Oz_7;)} z5Y|3G#X;)gF*}6?(nwr#wB!bj0Q1+O%RoV)YsNmUY;gK$clS+Qo~k0I@1&>i^GrG$ z6dW$>1#vCM5>#QcGWN&1_ws85;&*Hg*!mWa!`}frWU6e_wjT#GYg@(#DY=~mgWoV% z$Z@A^H$MRc1L&yS79JB_*`z555 zg#rV4h(5)(_$jnZc1{cJyzlkPoHp!@R7{;A;C(Dk;N#2e78`tkgbmETl|j3m=h$H> zV5j`@STH*jfiUb7m_zz87^J~%l8%OvDNw)D{vPZJ)^-#LkAP9D?eAe7OpMe>#0 znX_O>P$*zugq;w}f(4n!hs!!t{i8FZNDY42#jsEeLgKHe4enpOLf@LjBw(VXQT-%) z&D&GMN<=z6BYfHRBCWE5aVdvwn#<*xJ*yUKpUxDY-DHwW0<161?9ZBY&JuLa^7oYG zl1nHuGIvGv%a00s@MfD-jaYJnT$jN%euQQ33dE>B4l@p4?Ng3@rSxK<+_y`GiA9U!Ra1itO+#MtS$-d?br z0_)8gD$JpugmAYT*tqb-qK(l2BlGEsH46pVJk9}?p9g1aw4P?y);g?T(kh8c7~nR_ zZ}X4(=#setBp+sBXC3d;~W9K6SjoG$siQcQ-! zLpeYAt%65bj2@Z)>m+6ct2S3`Tp}#JeoW83?UBg%RoxeaJ$oJ@=k%uS-}DI6acX9{ z1)P)9yygA~{CY@@g`HQBq+Fp%B5Ka zF^F_5kV_KMD$tdvu(yRP!>XsM2~C|*1xo-{blt;sX4lH^#x@BPkrTHJ^{+M4 z5fGI5kiKk+37tWCu)c;wzu~LD;DPILWX5(rhHb#6dYJwh z+qj8JDq?&Mk`_lt>Ks(L^tk$&93e>_oa{u(Iv_%IQFpB`!mDjhha14KsJA0EIJ*gr z8(z=(M)~}`Z~KNbTzOz{>@aendw--PH}8ZzQa?xX%eRrX8$xIg8E69!kLM_ILh<77 zQOlCNQw2{ghq3q`F70AlaqI5F&BIlTZ1neUQT&TbwmZ$Kbb?(DagyKCs7b?WA&EvA zHfxE_Xv{HC@oxF(N#lRH-N$kC8}=>^vOJa#C(+iu;7fjjIC0UX#|mF_p~Qi_UxPaj z$k`BoMQnGyPaAYx>&t1_tx?H;*!DU2N))HD6Y!(=og(>NNJM~$a8ZO6Js}MuSScn@ zE?<h?=$nX$GEx}cjS~b*AC&&N#7^m*BsqiEq}}0DKk1uCVx~}v3XwRW zMKyjIk218SL_W5%G=h-KH0<~gVe26Urkvm?5_oGisJ#0*uj-R< z5{l5iaV7YP75p-Bs@d2Qs6u2Q-wgTeo1J7hmOf~uW&^Q|lK{2}P$WmP{TkB*+0iBT zlxCto*?nuHLZYe$ILA(;f)nr&qbsjTwM0o6nj^2(n?B?y(??j@y^zRz{KF;5Efy|y z7VjKM*puei*m05s0@3(*G{es;`Cyr`_!*>!kQ`-8m-d9sa0|DiyrTiXaP~aux#;z@ zg8`1DS+0sdz>)00^91=GTlhRmSHp-OK_xJMB5y%qejes7t0FlTs< zO!a{h{Mz4<`AeHfHqbIL{^*)Ypy27piu%!O-dX6B-CeRs4A3&M;#=F8>;&)=$6<<5Mi8{w!6V8pm4v&(Rn*v_T3Q^-wvDN5j4Bb)SOA?uFOvPt$cQ#M?0 zWN6^77e^;mEa7shtyMu!hAF!lP& z7v1KOfX*&c=7&7npOCR68b#V&B;!lJw~JrX1#WT0L~`h)Jz+P)2W@$GG_5|-In!OS zzca>vKkV?mHRJ#9z+H@gx9tA_9i{O6TO*UgFV%8~ z!cWK|cpu|e*#dfIi}0oJ`|_VdP$m%?4Xzv_81n~FqT|<9sG~| z5Zfti0dTUiwp-s9jLuZ14+)v*6IDVwho>v{9F`1avwTI{e6?yVgR;72YMaoDmM0W3 zEmQLUfJT=f_(W~dF-s?piTfCy-rimX+cm&c%@(OwOv&UN377m&fnEH+OG=uR2xRBf{EwvXpt#$|a>^ zb|<9ayxmzoy8I*wijtvR_i;lJL8j2}{`qxWzTvI_GKfRVRqo1j1iG^~b0LUp6n;Yo z4An`F(8)z*naA!g`^*hm8&fhk|I89S$>sd|;JoQZsT+224|_W=0V*VRCgaB}_b>T8 z&o!*MEpbW&yu|B1BbjZi`uKw4;}UEiwy~6K6C|Lmmkx=M9$*d48g-b_noZpfiIeen zejsu8um#cgeZZs_i)5bn-W&zy%IYOSq}YyvK+aXVxH{aGoltzX-*?lk{TZ0m=zhbr zB}y@thY0nXQQT-4ae_Zy6tD$d|BwR(W_oy(Se38Al$X;8G23e{eT!N?=xQ=!(R$0HNE+K

    $ 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 z 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
    :` on the textfield on top and press enter (if the +server uses password, type in the bottom textfield `/connect
    : [password]`) + +Don't forget to start manipulating RNG early by shouting "Heart of the Cards!" during generation. \ No newline at end of file diff --git a/worlds/yugioh06/fusions.py b/worlds/yugioh06/fusions.py new file mode 100644 index 000000000000..22d03b389fe8 --- /dev/null +++ b/worlds/yugioh06/fusions.py @@ -0,0 +1,72 @@ +from typing import List, NamedTuple + + +class FusionData(NamedTuple): + name: str + materials: List[str] + replaceable: bool + additional_spells: List[str] + + +fusions = { + "Elemental Hero Flame Wingman": FusionData( + "Elemental Hero Flame Wingman", + ["Elemental Hero Avian", "Elemental Hero Burstinatrix"], + True, + ["Miracle Fusion"]), + "Elemental Hero Madballman": FusionData( + "Elemental Hero Madballman", + ["Elemental Hero Bubbleman", "Elemental Hero Clayman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Rampart Blaster": FusionData( + "Elemental Hero Rampart Blaster", + ["Elemental Hero Burstinatrix", "Elemental Hero Clayman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Shining Flare Wingman": FusionData( + "Elemental Hero Shining Flare Wingman", + ["Elemental Hero Flame Wingman", "Elemental Hero Sparkman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Steam Healer": FusionData( + "Elemental Hero Steam Healer", + ["Elemental Hero Burstinatrix", "Elemental Hero Bubbleman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Wildedge": FusionData( + "Elemental Hero Wildedge", + ["Elemental Hero Wildheart", "Elemental Hero Bladedge"], + True, + ["Miracle Fusion"]) +} + +fusion_subs = ["The Dark - Hex-Sealed Fusion", + "The Earth - Hex-Sealed Fusion", + "The Light - Hex-Sealed Fusion", + "Goddess with the Third Eye", + "King of the Swamp", + "Versago the Destroyer", + # Only in All-packs + "Beastking of the Swamps", + "Mystical Sheep #1"] + + +def has_all_materials(state, monster, player): + data = fusions.get(monster) + if not state.has(monster, player): + return False + if data is None: + return True + else: + materials = data.replaceable and state.has_any(fusion_subs, player) + for material in data.materials: + materials += has_all_materials(state, material, player) + return materials >= len(data.materials) + + +def count_has_materials(state, monsters, player): + amount = 0 + for monster in monsters: + amount += has_all_materials(state, monster, player) + return amount diff --git a/worlds/yugioh06/items.py b/worlds/yugioh06/items.py new file mode 100644 index 000000000000..f0f877fd9f7b --- /dev/null +++ b/worlds/yugioh06/items.py @@ -0,0 +1,369 @@ +from typing import Dict, List + +item_to_index: Dict[str, int] = { + "LEGEND OF B.E.W.D.": 1, + "METAL RAIDERS": 2, + "PHARAOH'S SERVANT": 3, + "PHARAONIC GUARDIAN": 4, + "SPELL RULER": 5, + "LABYRINTH OF NIGHTMARE": 6, + "LEGACY OF DARKNESS": 7, + "MAGICIAN'S FORCE": 8, + "DARK CRISIS": 9, + "INVASION OF CHAOS": 10, + "ANCIENT SANCTUARY": 11, + "SOUL OF THE DUELIST": 12, + "RISE OF DESTINY": 13, + "FLAMING ETERNITY": 14, + "THE LOST MILLENIUM": 15, + "CYBERNETIC REVOLUTION": 16, + "ELEMENTAL ENERGY": 17, + "SHADOW OF INFINITY": 18, + "GAME GIFT COLLECTION": 19, + "Special Gift Collection": 20, + "Fairy Collection": 21, + "Dragon Collection": 22, + "Warrior Collection A": 23, + "Warrior Collection B": 24, + "Fiend Collection A": 25, + "Fiend Collection B": 26, + "Machine Collection A": 27, + "Machine Collection B": 28, + "Spellcaster Collection A": 29, + "Spellcaster Collection B": 30, + "Zombie Collection": 31, + "Special Monsters A": 32, + "Special Monsters B": 33, + "Reverse Collection": 34, + "LP Recovery Collection": 35, + "Special Summon Collection A": 36, + "Special Summon Collection B": 37, + "Special Summon Collection C": 38, + "Equipment Collection": 39, + "Continuous Spell/Trap A": 40, + "Continuous Spell/Trap B": 41, + "Quick/Counter Collection": 42, + "Direct Damage Collection": 43, + "Direct Attack Collection": 44, + "Monster Destroy Collection": 45, + "All Normal Monsters": 46, + "All Effect Monsters": 47, + "All Fusion Monsters": 48, + "All Traps": 49, + "All Spells": 50, + "All at Random": 51, + "LD01 All except Level 4 forbidden Unlock": 52, + "LD02 Medium/high Level forbidden Unlock": 53, + "LD03 ATK 1500 or more forbidden Unlock": 54, + "LD04 Flip Effects forbidden Unlock": 55, + "LD05 Tributes forbidden Unlock": 56, + "LD06 Traps forbidden Unlock": 57, + "LD07 Large Deck A Unlock": 58, + "LD08 Large Deck B Unlock": 59, + "LD09 Sets Forbidden Unlock": 60, + "LD10 All except LV monsters forbidden Unlock": 61, + "LD11 All except Fairies forbidden Unlock": 62, + "LD12 All except Wind forbidden Unlock": 63, + "LD13 All except monsters forbidden Unlock": 64, + "LD14 Level 3 or below forbidden Unlock": 65, + "LD15 DEF 1500 or less forbidden Unlock": 66, + "LD16 Effect Monsters forbidden Unlock": 67, + "LD17 Spells forbidden Unlock": 68, + "LD18 Attacks forbidden Unlock": 69, + "LD19 All except E-Hero's forbidden Unlock": 70, + "LD20 All except Warriors forbidden Unlock": 71, + "LD21 All except Dark forbidden Unlock": 72, + "LD22 All limited cards forbidden Unlock": 73, + "LD23 Refer to Mar 05 Banlist Unlock": 74, + "LD24 Refer to Sept 04 Banlist Unlock": 75, + "LD25 Low Life Points Unlock": 76, + "LD26 All except Toons forbidden Unlock": 77, + "LD27 All except Spirits forbidden Unlock": 78, + "LD28 All except Dragons forbidden Unlock": 79, + "LD29 All except Spellcasters forbidden Unlock": 80, + "LD30 All except Light forbidden Unlock": 81, + "LD31 All except Fire forbidden Unlock": 82, + "LD32 Decks with multiples forbidden Unlock": 83, + "LD33 Special Summons forbidden Unlock": 84, + "LD34 Normal Summons forbidden Unlock": 85, + "LD35 All except Zombies forbidden Unlock": 86, + "LD36 All except Earth forbidden Unlock": 87, + "LD37 All except Water forbidden Unlock": 88, + "LD38 Refer to Mar 04 Banlist Unlock": 89, + "LD39 Monsters forbidden Unlock": 90, + "LD40 Refer to Sept 05 Banlist Unlock": 91, + "LD41 Refer to Sept 03 Banlist Unlock": 92, + "TD01 Battle Damage Unlock": 93, + "TD02 Deflected Damage Unlock": 94, + "TD03 Normal Summon Unlock": 95, + "TD04 Ritual Summon Unlock": 96, + "TD05 Special Summon A Unlock": 97, + "TD06 20x Spell Unlock": 98, + "TD07 10x Trap Unlock": 99, + "TD08 Draw Unlock": 100, + "TD09 Hand Destruction Unlock": 101, + "TD10 During Opponent's Turn Unlock": 102, + "TD11 Recover Unlock": 103, + "TD12 Remove Monsters by Effect Unlock": 104, + "TD13 Flip Summon Unlock": 105, + "TD14 Special Summon B Unlock": 106, + "TD15 Token Unlock": 107, + "TD16 Union Unlock": 108, + "TD17 10x Quick Spell Unlock": 109, + "TD18 The Forbidden Unlock": 110, + "TD19 20 Turns Unlock": 111, + "TD20 Deck Destruction Unlock": 112, + "TD21 Victory D. Unlock": 113, + "TD22 The Preventers Fight Back Unlock": 114, + "TD23 Huge Revolution Unlock": 115, + "TD24 Victory in 5 Turns Unlock": 116, + "TD25 Moth Grows Up Unlock": 117, + "TD26 Magnetic Power Unlock": 118, + "TD27 Dark Sage Unlock": 119, + "TD28 Direct Damage Unlock": 120, + "TD29 Destroy Monsters in Battle Unlock": 121, + "TD30 Tribute Summon Unlock": 122, + "TD31 Special Summon C Unlock": 123, + "TD32 Toon Unlock": 124, + "TD33 10x Counter Unlock": 125, + "TD34 Destiny Board Unlock": 126, + "TD35 Huge Damage in a Turn Unlock": 127, + "TD36 V-Z In the House Unlock": 128, + "TD37 Uria, Lord of Searing Flames Unlock": 129, + "TD38 Hamon, Lord of Striking Thunder Unlock": 130, + "TD39 Raviel, Lord of Phantasms Unlock": 131, + "TD40 Make a Chain Unlock": 132, + "TD41 The Gatekeeper Stands Tall Unlock": 133, + "TD42 Serious Damage Unlock": 134, + "TD43 Return Monsters with Effects Unlock": 135, + "TD44 Fusion Summon Unlock": 136, + "TD45 Big Damage at once Unlock": 137, + "TD46 XYZ In the House Unlock": 138, + "TD47 Spell Counter Unlock": 139, + "TD48 Destroy Monsters with Effects Unlock": 140, + "TD49 Plunder Unlock": 141, + "TD50 Dark Scorpion Combination Unlock": 142, + "Campaign Tier 1 Column 1": 143, + "Campaign Tier 1 Column 2": 144, + "Campaign Tier 1 Column 3": 145, + "Campaign Tier 1 Column 4": 146, + "Campaign Tier 1 Column 5": 147, + "Campaign Tier 2 Column 1": 148, + "Campaign Tier 2 Column 2": 149, + "Campaign Tier 2 Column 3": 150, + "Campaign Tier 2 Column 4": 151, + "Campaign Tier 2 Column 5": 152, + "Campaign Tier 3 Column 1": 153, + "Campaign Tier 3 Column 2": 154, + "Campaign Tier 3 Column 3": 155, + "Campaign Tier 3 Column 4": 156, + "Campaign Tier 3 Column 5": 157, + "Campaign Tier 4 Column 1": 158, + "Campaign Tier 4 Column 2": 159, + "Campaign Tier 4 Column 3": 160, + "Campaign Tier 4 Column 4": 161, + "Campaign Tier 4 Column 5": 162, + "Campaign Tier 5 Column 1": 163, + "Campaign Tier 5 Column 2": 164, + "No Banlist": 167, + "Banlist September 2003": 168, + "Banlist March 2004": 169, + "Banlist September 2004": 170, + "Banlist March 2005": 171, + "Banlist September 2005": 172, + "5000DP": 254, + "Remote": 255, +} + +tier_1_opponents: List[str] = [ + "Campaign Tier 1 Column 1", + "Campaign Tier 1 Column 2", + "Campaign Tier 1 Column 3", + "Campaign Tier 1 Column 4", + "Campaign Tier 1 Column 5", +] + +Banlist_Items: List[str] = [ + "No Banlist", + "Banlist September 2003", + "Banlist March 2004", + "Banlist September 2004", + "Banlist March 2005", + "Banlist September 2005", +] + +draft_boosters: List[str] = [ + "METAL RAIDERS", + "PHARAOH'S SERVANT", + "PHARAONIC GUARDIAN", + "LABYRINTH OF NIGHTMARE", + "LEGACY OF DARKNESS", + "MAGICIAN'S FORCE", + "DARK CRISIS", + "INVASION OF CHAOS", + "RISE OF DESTINY", + "ELEMENTAL ENERGY", + "SHADOW OF INFINITY", +] + +draft_opponents: List[str] = ["Campaign Tier 1 Column 1", "Campaign Tier 1 Column 5"] + +booster_packs: List[str] = [ + "LEGEND OF B.E.W.D.", + "METAL RAIDERS", + "PHARAOH'S SERVANT", + "PHARAONIC GUARDIAN", + "SPELL RULER", + "LABYRINTH OF NIGHTMARE", + "LEGACY OF DARKNESS", + "MAGICIAN'S FORCE", + "DARK CRISIS", + "INVASION OF CHAOS", + "ANCIENT SANCTUARY", + "SOUL OF THE DUELIST", + "RISE OF DESTINY", + "FLAMING ETERNITY", + "THE LOST MILLENIUM", + "CYBERNETIC REVOLUTION", + "ELEMENTAL ENERGY", + "SHADOW OF INFINITY", + "GAME GIFT COLLECTION", + "Special Gift Collection", + "Fairy Collection", + "Dragon Collection", + "Warrior Collection A", + "Warrior Collection B", + "Fiend Collection A", + "Fiend Collection B", + "Machine Collection A", + "Machine Collection B", + "Spellcaster Collection A", + "Spellcaster Collection B", + "Zombie Collection", + "Special Monsters A", + "Special Monsters B", + "Reverse Collection", + "LP Recovery Collection", + "Special Summon Collection A", + "Special Summon Collection B", + "Special Summon Collection C", + "Equipment Collection", + "Continuous Spell/Trap A", + "Continuous Spell/Trap B", + "Quick/Counter Collection", + "Direct Damage Collection", + "Direct Attack Collection", + "Monster Destroy Collection", +] + +challenges: List[str] = [ + "LD01 All except Level 4 forbidden Unlock", + "LD02 Medium/high Level forbidden Unlock", + "LD03 ATK 1500 or more forbidden Unlock", + "LD04 Flip Effects forbidden Unlock", + "LD05 Tributes forbidden Unlock", + "LD06 Traps forbidden Unlock", + "LD07 Large Deck A Unlock", + "LD08 Large Deck B Unlock", + "LD09 Sets Forbidden Unlock", + "LD10 All except LV monsters forbidden Unlock", + "LD11 All except Fairies forbidden Unlock", + "LD12 All except Wind forbidden Unlock", + "LD13 All except monsters forbidden Unlock", + "LD14 Level 3 or below forbidden Unlock", + "LD15 DEF 1500 or less forbidden Unlock", + "LD16 Effect Monsters forbidden Unlock", + "LD17 Spells forbidden Unlock", + "LD18 Attacks forbidden Unlock", + "LD19 All except E-Hero's forbidden Unlock", + "LD20 All except Warriors forbidden Unlock", + "LD21 All except Dark forbidden Unlock", + "LD22 All limited cards forbidden Unlock", + "LD23 Refer to Mar 05 Banlist Unlock", + "LD24 Refer to Sept 04 Banlist Unlock", + "LD25 Low Life Points Unlock", + "LD26 All except Toons forbidden Unlock", + "LD27 All except Spirits forbidden Unlock", + "LD28 All except Dragons forbidden Unlock", + "LD29 All except Spellcasters forbidden Unlock", + "LD30 All except Light forbidden Unlock", + "LD31 All except Fire forbidden Unlock", + "LD32 Decks with multiples forbidden Unlock", + "LD33 Special Summons forbidden Unlock", + "LD34 Normal Summons forbidden Unlock", + "LD35 All except Zombies forbidden Unlock", + "LD36 All except Earth forbidden Unlock", + "LD37 All except Water forbidden Unlock", + "LD38 Refer to Mar 04 Banlist Unlock", + "LD39 Monsters forbidden Unlock", + "LD40 Refer to Sept 05 Banlist Unlock", + "LD41 Refer to Sept 03 Banlist Unlock", + "TD01 Battle Damage Unlock", + "TD02 Deflected Damage Unlock", + "TD03 Normal Summon Unlock", + "TD04 Ritual Summon Unlock", + "TD05 Special Summon A Unlock", + "TD06 20x Spell Unlock", + "TD07 10x Trap Unlock", + "TD08 Draw Unlock", + "TD09 Hand Destruction Unlock", + "TD10 During Opponent's Turn Unlock", + "TD11 Recover Unlock", + "TD12 Remove Monsters by Effect Unlock", + "TD13 Flip Summon Unlock", + "TD14 Special Summon B Unlock", + "TD15 Token Unlock", + "TD16 Union Unlock", + "TD17 10x Quick Spell Unlock", + "TD18 The Forbidden Unlock", + "TD19 20 Turns Unlock", + "TD20 Deck Destruction Unlock", + "TD21 Victory D. Unlock", + "TD22 The Preventers Fight Back Unlock", + "TD23 Huge Revolution Unlock", + "TD24 Victory in 5 Turns Unlock", + "TD25 Moth Grows Up Unlock", + "TD26 Magnetic Power Unlock", + "TD27 Dark Sage Unlock", + "TD28 Direct Damage Unlock", + "TD29 Destroy Monsters in Battle Unlock", + "TD30 Tribute Summon Unlock", + "TD31 Special Summon C Unlock", + "TD32 Toon Unlock", + "TD33 10x Counter Unlock", + "TD34 Destiny Board Unlock", + "TD35 Huge Damage in a Turn Unlock", + "TD36 V-Z In the House Unlock", + "TD37 Uria, Lord of Searing Flames Unlock", + "TD38 Hamon, Lord of Striking Thunder Unlock", + "TD39 Raviel, Lord of Phantasms Unlock", + "TD40 Make a Chain Unlock", + "TD41 The Gatekeeper Stands Tall Unlock", + "TD42 Serious Damage Unlock", + "TD43 Return Monsters with Effects Unlock", + "TD44 Fusion Summon Unlock", + "TD45 Big Damage at once Unlock", + "TD46 XYZ In the House Unlock", + "TD47 Spell Counter Unlock", + "TD48 Destroy Monsters with Effects Unlock", + "TD49 Plunder Unlock", + "TD50 Dark Scorpion Combination Unlock", +] + +excluded_items: List[str] = [ + "All Normal Monsters", + "All Effect Monsters", + "All Fusion Monsters", + "All Traps", + "All Spells", + "All at Random", + "5000DP", + "Remote", +] + +useful: List[str] = [ + "Banlist March 2004", + "Banlist September 2004", + "Banlist March 2005", + "Banlist September 2005", +] diff --git a/worlds/yugioh06/locations.py b/worlds/yugioh06/locations.py new file mode 100644 index 000000000000..f495bfede22d --- /dev/null +++ b/worlds/yugioh06/locations.py @@ -0,0 +1,213 @@ +Bonuses = { + "Duelist Bonus Level 1": 1, + "Duelist Bonus Level 2": 2, + "Duelist Bonus Level 3": 3, + "Duelist Bonus Level 4": 4, + "Duelist Bonus Level 5": 5, + "Battle Damage": 6, + "Battle Damage Only Bonus": 7, + "Max ATK Bonus": 8, + "Max Damage Bonus": 9, + "Destroyed in Battle Bonus": 10, + "Spell Card Bonus": 11, + "Trap Card Bonus": 12, + "Tribute Summon Bonus": 13, + "Fusion Summon Bonus": 14, + "Ritual Summon Bonus": 15, + "No Special Summon Bonus": 16, + "No Spell Cards Bonus": 17, + "No Trap Cards Bonus": 18, + "No Damage Bonus": 19, + "Over 20000 LP Bonus": 20, + "Low LP Bonus": 21, + "Extremely Low LP Bonus": 22, + "Low Deck Bonus": 23, + "Extremely Low Deck Bonus": 24, + "Effect Damage Only Bonus": 25, + "No More Cards Bonus": 26, + "Opponent's Turn Finish Bonus": 27, + "Exactly 0 LP Bonus": 28, + "Reversal Finish Bonus": 29, + "Quick Finish Bonus": 30, + "Exodia Finish Bonus": 31, + "Last Turn Finish Bonus": 32, + "Final Countdown Finish Bonus": 33, + "Destiny Board Finish Bonus": 34, + "Yata-Garasu Finish Bonus": 35, + "Skull Servant Finish Bonus": 36, + "Konami Bonus": 37, +} + +Limited_Duels = { + "LD01 All except Level 4 forbidden": 38, + "LD02 Medium/high Level forbidden": 39, + "LD03 ATK 1500 or more forbidden": 40, + "LD04 Flip Effects forbidden": 41, + "LD05 Tributes forbidden": 42, + "LD06 Traps forbidden": 43, + "LD07 Large Deck A": 44, + "LD08 Large Deck B": 45, + "LD09 Sets Forbidden": 46, + "LD10 All except LV monsters forbidden": 47, + "LD11 All except Fairies forbidden": 48, + "LD12 All except Wind forbidden": 49, + "LD13 All except monsters forbidden": 50, + "LD14 Level 3 or below forbidden": 51, + "LD15 DEF 1500 or less forbidden": 52, + "LD16 Effect Monsters forbidden": 53, + "LD17 Spells forbidden": 54, + "LD18 Attacks forbidden": 55, + "LD19 All except E-Hero's forbidden": 56, + "LD20 All except Warriors forbidden": 57, + "LD21 All except Dark forbidden": 58, + "LD22 All limited cards forbidden": 59, + "LD23 Refer to Mar 05 Banlist": 60, + "LD24 Refer to Sept 04 Banlist": 61, + "LD25 Low Life Points": 62, + "LD26 All except Toons forbidden": 63, + "LD27 All except Spirits forbidden": 64, + "LD28 All except Dragons forbidden": 65, + "LD29 All except Spellcasters forbidden": 66, + "LD30 All except Light forbidden": 67, + "LD31 All except Fire forbidden": 68, + "LD32 Decks with multiples forbidden": 69, + "LD33 Special Summons forbidden": 70, + "LD34 Normal Summons forbidden": 71, + "LD35 All except Zombies forbidden": 72, + "LD36 All except Earth forbidden": 73, + "LD37 All except Water forbidden": 74, + "LD38 Refer to Mar 04 Banlist": 75, + "LD39 Monsters forbidden": 76, + "LD40 Refer to Sept 05 Banlist": 77, + "LD41 Refer to Sept 03 Banlist": 78, +} + +Theme_Duels = { + "TD01 Battle Damage": 79, + "TD02 Deflected Damage": 80, + "TD03 Normal Summon": 81, + "TD04 Ritual Summon": 82, + "TD05 Special Summon A": 83, + "TD06 20x Spell": 84, + "TD07 10x Trap": 85, + "TD08 Draw": 86, + "TD09 Hand Destruction": 87, + "TD10 During Opponent's Turn": 88, + "TD11 Recover": 89, + "TD12 Remove Monsters by Effect": 90, + "TD13 Flip Summon": 91, + "TD14 Special Summon B": 92, + "TD15 Token": 93, + "TD16 Union": 94, + "TD17 10x Quick Spell": 95, + "TD18 The Forbidden": 96, + "TD19 20 Turns": 97, + "TD20 Deck Destruction": 98, + "TD21 Victory D.": 99, + "TD22 The Preventers Fight Back": 100, + "TD23 Huge Revolution": 101, + "TD24 Victory in 5 Turns": 102, + "TD25 Moth Grows Up": 103, + "TD26 Magnetic Power": 104, + "TD27 Dark Sage": 105, + "TD28 Direct Damage": 106, + "TD29 Destroy Monsters in Battle": 107, + "TD30 Tribute Summon": 108, + "TD31 Special Summon C": 109, + "TD32 Toon": 110, + "TD33 10x Counter": 111, + "TD34 Destiny Board": 112, + "TD35 Huge Damage in a Turn": 113, + "TD36 V-Z In the House": 114, + "TD37 Uria, Lord of Searing Flames": 115, + "TD38 Hamon, Lord of Striking Thunder": 116, + "TD39 Raviel, Lord of Phantasms": 117, + "TD40 Make a Chain": 118, + "TD41 The Gatekeeper Stands Tall": 119, + "TD42 Serious Damage": 120, + "TD43 Return Monsters with Effects": 121, + "TD44 Fusion Summon": 122, + "TD45 Big Damage at once": 123, + "TD46 XYZ In the House": 124, + "TD47 Spell Counter": 125, + "TD48 Destroy Monsters with Effects": 126, + "TD49 Plunder": 127, + "TD50 Dark Scorpion Combination": 128, +} + +Campaign_Opponents = { + "Campaign Tier 1: 1 Win": 129, + "Campaign Tier 1: 3 Wins A": 130, + "Campaign Tier 1: 3 Wins B": 131, + "Campaign Tier 1: 5 Wins A": 132, + "Campaign Tier 1: 5 Wins B": 133, + "Campaign Tier 2: 1 Win": 134, + "Campaign Tier 2: 3 Wins A": 135, + "Campaign Tier 2: 3 Wins B": 136, + "Campaign Tier 2: 5 Wins A": 137, + "Campaign Tier 2: 5 Wins B": 138, + "Campaign Tier 3: 1 Win": 139, + "Campaign Tier 3: 3 Wins A": 140, + "Campaign Tier 3: 3 Wins B": 141, + "Campaign Tier 3: 5 Wins A": 142, + "Campaign Tier 3: 5 Wins B": 143, + "Campaign Tier 4: 5 Wins A": 144, + "Campaign Tier 4: 5 Wins B": 145, +} + +special = { + "Campaign Tier 5: Column 1 Win": 146, + "Campaign Tier 5: Column 2 Win": 147, + "Campaign Tier 5: Column 3 Win": 148, + "Campaign Tier 5: Column 4 Win": 149, + # "Campaign Final Boss Win": 150, +} + +Required_Cards = { + "Obtain all pieces of Exodia": 154, + "Obtain Final Countdown": 155, + "Obtain Victory Dragon": 156, + "Obtain Ojama Delta Hurricane and its required cards": 157, + "Obtain Huge Revolution and its required cards": 158, + "Obtain Perfectly Ultimate Great Moth and its required cards": 159, + "Obtain Valkyrion the Magna Warrior and its pieces": 160, + "Obtain Dark Sage and its required cards": 161, + "Obtain Destiny Board and its letters": 162, + "Obtain all XYZ-Dragon Cannon fusions and their materials": 163, + "Obtain VWXYZ-Dragon Catapult Cannon and the fusion materials": 164, + "Obtain Hamon, Lord of Striking Thunder": 165, + "Obtain Raviel, Lord of Phantasms": 166, + "Obtain Uria, Lord of Searing Flames": 167, + "Obtain Gate Guardian and its pieces": 168, + "Obtain Dark Scorpion Combination and its required cards": 169, +} + +collection_events = { + "Ojama Delta Hurricane and required cards": None, + "Huge Revolution and its required cards": None, + "Perfectly Ultimate Great Moth and its required cards": None, + "Valkyrion the Magna Warrior and its pieces": None, + "Dark Sage and its required cards": None, + "Destiny Board and its letters": None, + "XYZ-Dragon Cannon fusions and their materials": None, + "VWXYZ-Dragon Catapult Cannon and the fusion materials": None, + "Gate Guardian and its pieces": None, + "Dark Scorpion Combination and its required cards": None, + "Can Exodia Win": None, + "Can Yata Lock": None, + "Can Stall with Monsters": None, + "Can Stall with ST": None, + "Can Last Turn Win": None, + "Has Back-row removal": None, +} + + +def get_beat_challenge_events(self): + beat_events = {} + for limited in Limited_Duels.keys(): + if limited not in self.removed_challenges: + beat_events[limited + " Complete"] = None + for theme in Theme_Duels.keys(): + if theme not in self.removed_challenges: + beat_events[theme + " Complete"] = None + return beat_events diff --git a/worlds/yugioh06/logic.py b/worlds/yugioh06/logic.py new file mode 100644 index 000000000000..3227cbfe67c3 --- /dev/null +++ b/worlds/yugioh06/logic.py @@ -0,0 +1,28 @@ +from typing import List + +from BaseClasses import CollectionState + +core_booster: List[str] = [ + "LEGEND OF B.E.W.D.", + "METAL RAIDERS", + "PHARAOH'S SERVANT", + "PHARAONIC GUARDIAN", + "SPELL RULER", + "LABYRINTH OF NIGHTMARE", + "LEGACY OF DARKNESS", + "MAGICIAN'S FORCE", + "DARK CRISIS", + "INVASION OF CHAOS", + "ANCIENT SANCTUARY", + "SOUL OF THE DUELIST", + "RISE OF DESTINY", + "FLAMING ETERNITY", + "THE LOST MILLENIUM", + "CYBERNETIC REVOLUTION", + "ELEMENTAL ENERGY", + "SHADOW OF INFINITY", +] + + +def yugioh06_difficulty(state: CollectionState, player: int, amount: int): + return state.has_from_list(core_booster, player, amount) diff --git a/worlds/yugioh06/opponents.py b/worlds/yugioh06/opponents.py new file mode 100644 index 000000000000..68d7c2880f03 --- /dev/null +++ b/worlds/yugioh06/opponents.py @@ -0,0 +1,264 @@ +from typing import Dict, List, NamedTuple, Optional, Union + +from BaseClasses import MultiWorld +from worlds.generic.Rules import CollectionRule + +from . import item_to_index, tier_1_opponents, yugioh06_difficulty +from .locations import special + + +class OpponentData(NamedTuple): + id: int + name: str + campaign_info: List[str] + tier: int + column: int + card_id: int = 0 + deck_name_id: int = 0 + deck_file: str = "" + difficulty: int = 1 + additional_info: List[str] = [] + + def tier(self, tier): + self.tier = tier + + def column(self, column): + self.column = column + + +challenge_opponents = [ + # Theme + OpponentData(27, "Exarion Universe", [], 1, 1, 5452, 13001, "deck/theme_001.ydc\x00\x00\x00\x00", 0), + OpponentData(28, "Stone Statue of the Aztecs", [], 4, 1, 4754, 13002, "deck/theme_002.ydc\x00\x00\x00\x00", 3), + OpponentData(29, "Raging Flame Sprite", [], 1, 1, 6189, 13003, "deck/theme_003.ydc\x00\x00\x00\x00", 0), + OpponentData(30, "Princess Pikeru", [], 1, 1, 6605, 13004, "deck/theme_004.ydc\x00\x00\x00\x00", 0), + OpponentData(31, "Princess Curran", ["Quick-Finish"], 1, 1, 6606, 13005, "deck/theme_005.ydc\x00\x00\x00\x00", 0, + ["Has Back-row removal"]), + OpponentData(32, "Gearfried the Iron Knight", ["Quick-Finish"], 2, 1, 5059, 13006, + "deck/theme_006.ydc\x00\x00\x00\x00", 1), + OpponentData(33, "Zaborg the Thunder Monarch", [], 3, 1, 5965, 13007, "deck/theme_007.ydc\x00\x00\x00\x00", 2), + OpponentData(34, "Kycoo the Ghost Destroyer", ["Quick-Finish"], 3, 1, 5248, 13008, + "deck/theme_008.ydc\x00\x00\x00\x00"), + OpponentData(35, "Penguin Soldier", ["Quick-Finish"], 1, 1, 4608, 13009, "deck/theme_009.ydc\x00\x00\x00\x00", 0), + OpponentData(36, "Green Gadget", [], 5, 1, 6151, 13010, "deck/theme_010.ydc\x00\x00\x00\x00", 5), + OpponentData(37, "Guardian Sphinx", ["Quick-Finish"], 3, 1, 5422, 13011, "deck/theme_011.ydc\x00\x00\x00\x00", 3), + OpponentData(38, "Cyber-Tech Alligator", [], 2, 1, 4790, 13012, "deck/theme_012.ydc\x00\x00\x00\x00", 1), + OpponentData(39, "UFOroid Fighter", [], 3, 1, 6395, 13013, "deck/theme_013.ydc\x00\x00\x00\x00", 2), + OpponentData(40, "Relinquished", [], 3, 1, 4737, 13014, "deck/theme_014.ydc\x00\x00\x00\x00", 2), + OpponentData(41, "Manticore of Darkness", [], 2, 1, 5881, 13015, "deck/theme_015.ydc\x00\x00\x00\x00", 1), + OpponentData(42, "Vampire Lord", [], 3, 1, 5410, 13016, "deck/theme_016.ydc\x00\x00\x00\x00", 2), + OpponentData(43, "Gigantes", ["Quick-Finish"], 3, 1, 5831, 13017, "deck/theme_017.ydc\x00\x00\x00\x00", 2), + OpponentData(44, "Insect Queen", ["Quick-Finish"], 2, 1, 4768, 13018, "deck/theme_018.ydc\x00\x00\x00\x00", 1), + OpponentData(45, "Second Goblin", ["Quick-Finish"], 1, 1, 5587, 13019, "deck/theme_019.ydc\x00\x00\x00\x00", 0), + OpponentData(46, "Toon Summoned Skull", [], 4, 1, 4735, 13020, "deck/theme_020.ydc\x00\x00\x00\x00", 3), + OpponentData(47, "Iron Blacksmith Kotetsu", [], 2, 1, 5769, 13021, "deck/theme_021.ydc\x00\x00\x00\x00", 1), + OpponentData(48, "Magician of Faith", [], 1, 1, 4434, 13022, "deck/theme_022.ydc\x00\x00\x00\x00", 0), + OpponentData(49, "Mask of Darkness", [], 1, 1, 4108, 13023, "deck/theme_023.ydc\x00\x00\x00\x00", 0), + OpponentData(50, "Dark Ruler Vandalgyon", [], 3, 1, 6410, 13024, "deck/theme_024.ydc\x00\x00\x00\x00", 2), + OpponentData(51, "Aussa the Earth Charmer", ["Quick-Finish"], 2, 1, 6335, 13025, + "deck/theme_025.ydc\x00\x00\x00\x00", 1), + OpponentData(52, "Exodia Necross", ["Quick-Finish"], 2, 1, 5701, 13026, "deck/theme_026.ydc\x00\x00\x00\x00", 1), + OpponentData(53, "Dark Necrofear", [], 3, 1, 5222, 13027, "deck/theme_027.ydc\x00\x00\x00\x00", 2), + OpponentData(54, "Demise, King of Armageddon", [], 4, 1, 6613, 13028, "deck/theme_028.ydc\x00\x00\x00\x00", 2), + OpponentData(55, "Yamata Dragon", [], 3, 1, 5377, 13029, "deck/theme_029.ydc\x00\x00\x00\x00", 2), + OpponentData(56, "Blue-Eyes Ultimate Dragon", [], 3, 1, 4386, 13030, "deck/theme_030.ydc\x00\x00\x00\x00", 2), + OpponentData(57, "Wave-Motion Cannon", [], 4, 1, 5614, 13031, "deck/theme_031.ydc\x00\x00\x00\x00", 3, + ["Has Back-row removal"]), + # Unused opponent + # OpponentData(58, "Yata-Garasu", [], 1, 1, 5375, 13032, "deck/theme_031.ydc\x00\x00\x00\x00"), + # Unused opponent + # OpponentData(59, "Makyura the Destructor", [], 1, 1, 5285, 13033, "deck/theme_031.ydc\x00\x00\x00\x00"), + OpponentData(60, "Morphing Jar", [], 5, 1, 4597, 13034, "deck/theme_034.ydc\x00\x00\x00\x00", 4), + OpponentData(61, "Spirit Reaper", [], 2, 1, 5526, 13035, "deck/theme_035.ydc\x00\x00\x00\x00", 1), + OpponentData(62, "Victory D.", [], 3, 1, 5868, 13036, "deck/theme_036.ydc\x00\x00\x00\x00", 2), + OpponentData(63, "VWXYZ-Dragon Catapult Cannon", ["Quick-Finish"], 3, 1, 6484, 13037, + "deck/theme_037.ydc\x00\x00\x00\x00", 2), + OpponentData(64, "XYZ-Dragon Cannon", [], 2, 1, 5556, 13038, "deck/theme_038.ydc\x00\x00\x00\x00", 1), + OpponentData(65, "Uria, Lord of Searing Flames", [], 4, 1, 6563, 13039, "deck/theme_039.ydc\x00\x00\x00\x00", 3), + OpponentData(66, "Hamon, Lord of Striking Thunder", [], 4, 1, 6564, 13040, "deck/theme_040.ydc\x00\x00\x00\x00", 3), + OpponentData(67, "Raviel, Lord of Phantasms TD", [], 4, 1, 6565, 13041, "deck/theme_041.ydc\x00\x00\x00\x00", 3), + OpponentData(68, "Ojama Trio", [], 1, 1, 5738, 13042, "deck/theme_042.ydc\x00\x00\x00\x00", 0), + OpponentData(69, "People Running About", ["Quick-Finish"], 1, 1, 5578, 13043, "deck/theme_043.ydc\x00\x00\x00\x00", + 0), + OpponentData(70, "Cyber-Stein", [], 5, 1, 4426, 13044, "deck/theme_044.ydc\x00\x00\x00\x00", 4), + OpponentData(71, "Winged Kuriboh LV10", [], 4, 1, 6406, 13045, "deck/theme_045.ydc\x00\x00\x00\x00", 3), + OpponentData(72, "Blue-Eyes Shining Dragon", [], 3, 1, 6082, 13046, "deck/theme_046.ydc\x00\x00\x00\x00", 2), + OpponentData(73, "Perfectly Ultimate Great Moth", ["Quick-Finish"], 3, 1, 4073, 13047, + "deck/theme_047.ydc\x00\x00\x00\x00", 2), + OpponentData(74, "Gate Guardian", [], 4, 1, 4380, 13048, "deck/theme_048.ydc\x00\x00\x00\x00", 2), + OpponentData(75, "Valkyrion the Magna Warrior", [], 3, 1, 5002, 13049, "deck/theme_049.ydc\x00\x00\x00\x00", 2), + OpponentData(76, "Dark Sage", [], 4, 1, 5230, 13050, "deck/theme_050.ydc\x00\x00\x00\x00", 3), + OpponentData(77, "Don Zaloog", [], 4, 1, 5426, 13051, "deck/theme_051.ydc\x00\x00\x00\x00", 3), + OpponentData(78, "Blast Magician", ["Quick-Finish"], 2, 1, 6250, 13052, "deck/theme_052.ydc\x00\x00\x00\x00", 1), + # Limited + OpponentData(79, "Zombyra the Dark", [], 5, 1, 5245, 23000, "deck/limit_000.ydc\x00\x00\x00\x00", 5), + OpponentData(80, "Goblin Attack Force", [], 4, 1, 5145, 23001, "deck/limit_001.ydc\x00\x00\x00\x00", 3), + OpponentData(81, "Giant Kozaky", [], 4, 1, 6420, 23002, "deck/limit_002.ydc\x00\x00\x00\x00", 4), + OpponentData(82, "Big Shield Gardna", ["Quick-Finish"], 2, 1, 4764, 23003, "deck/limit_003.ydc\x00\x00\x00\x00", 1), + OpponentData(83, "Panther Warrior", [], 3, 1, 4751, 23004, "deck/limit_004.ydc\x00\x00\x00\x00", 2), + OpponentData(84, "Silent Magician LV4", ["Quick-Finish"], 2, 1, 6167, 23005, "deck/limit_005.ydc\x00\x00\x00\x00", + 1), + OpponentData(85, "Summoned Skull", [], 4, 1, 4028, 23006, "deck/limit_006.ydc\x00\x00\x00\x00", 3), + OpponentData(86, "Ancient Gear Golem", [], 5, 1, 6315, 23007, "deck/limit_007.ydc\x00\x00\x00\x00", 5), + OpponentData(87, "Chaos Sorcerer", [], 5, 1, 5833, 23008, "deck/limit_008.ydc\x00\x00\x00\x00", 5), + OpponentData(88, "Breaker the Magical Warrior", [], 5, 1, 5655, 23009, "deck/limit_009.ydc\x00\x00\x00\x00", 4), + OpponentData(89, "Dark Magician of Chaos", [], 4, 1, 5880, 23010, "deck/limit_010.ydc\x00\x00\x00\x00", 3), + OpponentData(90, "Stealth Bird", ["Quick-Finish"], 2, 1, 5882, 23011, "deck/limit_011.ydc\x00\x00\x00\x00", 1), + OpponentData(91, "Rapid-Fire Magician", ["Quick-Finish"], 2, 1, 6500, 23012, "deck/limit_012.ydc\x00\x00\x00\x00", + 1), + OpponentData(92, "Morphing Jar #2", [], 5, 1, 4969, 23013, "deck/limit_013.ydc\x00\x00\x00\x00", 4), + OpponentData(93, "Cyber Jar", [], 5, 1, 4913, 23014, "deck/limit_014.ydc\x00\x00\x00\x00", 4), + # Unused/Broken + # OpponentData(94, "Exodia the Forbidden One", [], 1, 1, 4027, 23015, "deck/limit_015.ydc\x00\x00\x00\x00"), + OpponentData(94, "Dark Paladin", [], 4, 1, 5628, 23016, "deck/limit_016.ydc\x00\x00\x00\x00", 3), + OpponentData(95, "F.G.D.", [], 5, 1, 5502, 23017, "deck/limit_017.ydc\x00\x00\x00\x00", 4), + OpponentData(96, "Blue-Eyes Toon Dragon", ["Quick-Finish"], 2, 1, 4773, 23018, "deck/limit_018.ydc\x00\x00\x00\x00", + 1), + OpponentData(97, "Tsukuyomi", [], 3, 1, 5780, 23019, "deck/limit_019.ydc\x00\x00\x00\x00", 2), + OpponentData(98, "Silent Swordsman LV3", ["Quick-Finish"], 2, 1, 6162, 23020, "deck/limit_020.ydc\x00\x00\x00\x00", + 2), + OpponentData(99, "Elemental Hero Flame Wingman", ["Quick-Finish"], 2, 1, 6344, 23021, + "deck/limit_021.ydc\x00\x00\x00\x00", 0), + OpponentData(100, "Armed Dragon LV7", ["Quick-Finish"], 2, 1, 6107, 23022, "deck/limit_022.ydc\x00\x00\x00\x00", 0), + OpponentData(101, "Alkana Knight Joker", ["Quick-Finish"], 1, 1, 6454, 23023, "deck/limit_023.ydc\x00\x00\x00\x00", + 0), + OpponentData(102, "Sorcerer of Dark Magic", [], 4, 1, 6086, 23024, "deck/limit_024.ydc\x00\x00\x00\x00", 3), + OpponentData(103, "Shinato, King of a Higher Plane", [], 4, 1, 5697, 23025, "deck/limit_025.ydc\x00\x00\x00\x00", + 3), + OpponentData(104, "Ryu Kokki", [], 5, 1, 5902, 23026, "deck/limit_026.ydc\x00\x00\x00\x00", 4), + OpponentData(105, "Cyber Dragon", [], 5, 1, 6390, 23027, "deck/limit_027.ydc\x00\x00\x00\x00", 4), + OpponentData(106, "Dark Dreadroute", ["Quick-Finish"], 3, 1, 6405, 23028, "deck/limit_028.ydc\x00\x00\x00\x00", 2), + OpponentData(107, "Ultimate Insect LV7", ["Quick-Finish"], 3, 1, 6319, 23029, "deck/limit_029.ydc\x00\x00\x00\x00", + 2), + OpponentData(108, "Thestalos the Firestorm Monarch", ["Quick-Finish"], 3, 1, 6190, 23030, + "deck/limit_030.ydc\x00\x00\x00\x00"), + OpponentData(109, "Master of Oz", ["Quick-Finish"], 3, 1, 6127, 23031, "deck/limit_031.ydc\x00\x00\x00\x00", 2), + OpponentData(110, "Orca Mega-Fortress of Darkness", ["Quick-Finish"], 3, 1, 5896, 23032, + "deck/limit_032.ydc\x00\x00\x00\x00", 2), + OpponentData(111, "Airknight Parshath", ["Quick-Finish"], 4, 1, 5023, 23033, "deck/limit_033.ydc\x00\x00\x00\x00", + 3), + OpponentData(112, "Dark Scorpion Burglars", ["Quick-Finish"], 4, 1, 5425, 23034, + "deck/limit_034.ydc\x00\x00\x00\x00", 3), + OpponentData(113, "Gilford the Lightning", [], 4, 1, 5451, 23035, "deck/limit_035.ydc\x00\x00\x00\x00", 3), + OpponentData(114, "Embodiment of Apophis", [], 2, 1, 5234, 23036, "deck/limit_036.ydc\x00\x00\x00\x00", 1), + OpponentData(115, "Great Maju Garzett", [], 5, 1, 5768, 23037, "deck/limit_037.ydc\x00\x00\x00\x00", 4), + OpponentData(116, "Black Luster Soldier - Envoy of the Beginning", [], 5, 1, 5835, 23038, + "deck/limit_038.ydc\x00\x00\x00\x00", 4), + OpponentData(117, "Red-Eyes B. Dragon", [], 4, 1, 4088, 23039, "deck/limit_039.ydc\x00\x00\x00\x00", 3), + OpponentData(118, "Blue-Eyes White Dragon", [], 4, 1, 4007, 23040, "deck/limit_040.ydc\x00\x00\x00\x00", 3), + OpponentData(119, "Dark Magician", [], 4, 1, 4041, 23041, "deck/limit_041.ydc\x00\x00\x00\x00", 3), + OpponentData(0, "Starter", ["Quick-Finish"], 1, 1, 4064, 1510, "deck/SD0_STARTER.ydc\x00\x00", 0), + OpponentData(10, "DRAGON'S ROAR", ["Quick-Finish"], 2, 1, 6292, 1511, "deck/SD1_DRAGON.ydc\x00\x00\x00", 1), + OpponentData(11, "ZOMBIE MADNESS", ["Quick-Finish"], 2, 1, 6293, 1512, "deck/SD2_UNDEAD.ydc\x00\x00\x00", 1), + OpponentData(12, "BLAZING DESTRUCTION", ["Quick-Finish"], 2, 1, 6368, 1513, "deck/SD3_FIRE.ydc\x00\x00\x00\x00\x00", + 1, + ["Has Back-row removal"]), + OpponentData(13, "FURY FROM THE DEEP", [], 2, 1, 6376, 1514, + "deck/SD4_UMI.ydc\x00\x00\x00\x00\x00\x00", 1, ["Has Back-row removal"]), + OpponentData(15, "WARRIORS TRIUMPH", ["Quick-Finish"], 2, 1, 6456, 1515, "deck/SD5_SOLDIER.ydc\x00\x00", 1), + OpponentData(16, "SPELLCASTERS JUDGEMENT", ["Quick-Finish"], 2, 1, 6530, 1516, "deck/SD6_MAGICIAN.ydc\x00", 1), + OpponentData(17, "INVICIBLE FORTRESS", [], 2, 1, 6640, 1517, "deck/SD7_GANSEKI.ydc\x00\x00", 1), + OpponentData(7, "Goblin King 2", ["Quick-Finish"], 3, 3, 5973, 8007, "deck/LV2_kingG2.ydc\x00\x00\x00", 2), +] + + +def get_opponents(multiworld: Optional[MultiWorld], player: Optional[int], randomize: bool = False) -> List[ + OpponentData]: + opponents_table: List[OpponentData] = [ + # Tier 1 + OpponentData(0, "Kuriboh", [], 1, 1, 4064, 8000, "deck/LV1_kuriboh.ydc\x00\x00"), + OpponentData(1, "Scapegoat", [], 1, 2, 4818, 8001, "deck/LV1_sukego.ydc\x00\x00\x00", 0, + ["Has Back-row removal"]), + OpponentData(2, "Skull Servant", [], 1, 3, 4030, 8002, "deck/LV1_waito.ydc\x00\x00\x00\x00", 0, + ["Has Back-row removal"]), + OpponentData(3, "Watapon", [], 1, 4, 6092, 8003, "deck/LV1_watapon.ydc\x00\x00", 0, ["Has Back-row removal"]), + OpponentData(4, "White Magician Pikeru", [], 1, 5, 5975, 8004, "deck/LV1_pikeru.ydc\x00\x00\x00"), + # Tier 2 + OpponentData(5, "Battery Man C", ["Quick-Finish"], 2, 1, 6428, 8005, "deck/LV2_denti.ydc\x00\x00\x00\x00", 1), + OpponentData(6, "Ojama Yellow", [], 2, 2, 5811, 8006, "deck/LV2_ojama.ydc\x00\x00\x00\x00", 1, + ["Has Back-row removal"]), + OpponentData(7, "Goblin King", ["Quick-Finish"], 2, 3, 5973, 8007, "deck/LV2_kingG.ydc\x00\x00\x00\x00", 1), + OpponentData(8, "Des Frog", ["Quick-Finish"], 2, 4, 6424, 8008, "deck/LV2_kaeru.ydc\x00\x00\x00\x00", 1), + OpponentData(9, "Water Dragon", ["Quick-Finish"], 2, 5, 6481, 8009, "deck/LV2_waterD.ydc\x00\x00\x00", 1), + # Tier 3 + OpponentData(10, "Red-Eyes Darkness Dragon", ["Quick-Finish"], 3, 1, 6292, 8010, "deck/LV3_RedEyes.ydc\x00\x00", + 2), + OpponentData(11, "Vampire Genesis", ["Quick-Finish"], 3, 2, 6293, 8011, "deck/LV3_vamp.ydc\x00\x00\x00\x00\x00", + 2), + OpponentData(12, "Infernal Flame Emperor", [], 3, 3, 6368, 8012, "deck/LV3_flame.ydc\x00\x00\x00\x00", 2, + ["Has Back-row removal"]), + OpponentData(13, "Ocean Dragon Lord - Neo-Daedalus", [], 3, 4, 6376, 8013, "deck/LV3_daidaros.ydc\x00", 2, + ["Has Back-row removal"]), + OpponentData(14, "Helios Duo Megiste", ["Quick-Finish"], 3, 5, 6647, 8014, "deck/LV3_heriosu.ydc\x00\x00", 2), + # Tier 4 + OpponentData(15, "Gilford the Legend", ["Quick-Finish"], 4, 1, 6456, 8015, "deck/LV4_gilfo.ydc\x00\x00\x00\x00", + 3), + OpponentData(16, "Dark Eradicator Warlock", ["Quick-Finish"], 4, 2, 6530, 8016, "deck/LV4_kuromadou.ydc", 3), + OpponentData(17, "Guardian Exode", [], 4, 3, 6640, 8017, "deck/LV4_exodo.ydc\x00\x00\x00\x00", 3), + OpponentData(18, "Goldd, Wu-Lord of Dark World", ["Quick-Finish"], 4, 4, 6505, 8018, "deck/LV4_ankokukai.ydc", + 3), + OpponentData(19, "Elemental Hero Erikshieler", ["Quick-Finish"], 4, 5, 6639, 8019, + "deck/LV4_Ehero.ydc\x00\x00\x00\x00", 3), + # Tier 5 + OpponentData(20, "Raviel, Lord of Phantasms", [], 5, 1, 6565, 8020, "deck/LV5_ravieru.ydc\x00\x00", 4), + OpponentData(21, "Horus the Black Flame Dragon LV8", [], 5, 2, 6100, 8021, "deck/LV5_horus.ydc\x00\x00\x00\x00", + 4), + OpponentData(22, "Stronghold", [], 5, 3, 6153, 8022, "deck/LV5_gadget.ydc\x00\x00\x00", 5), + OpponentData(23, "Sacred Phoenix of Nephthys", [], 5, 4, 6236, 8023, "deck/LV5_nephthys.ydc\x00", 6), + OpponentData(24, "Cyber End Dragon", ["Goal"], 5, 5, 6397, 8024, "deck/LV5_cyber.ydc\x00\x00\x00\x00", 7), + ] + world = multiworld.worlds[player] + if not randomize: + return opponents_table + opponents = opponents_table + challenge_opponents + start = world.random.choice([o for o in opponents if o.tier == 1 and len(o.additional_info) == 0]) + opponents.remove(start) + goal = world.random.choice([o for o in opponents if "Goal" in o.campaign_info]) + opponents.remove(goal) + world.random.shuffle(opponents) + chosen_ones = opponents[:23] + for item in (multiworld.precollected_items[player]): + if item.name in tier_1_opponents: + # convert item index to opponent index + chosen_ones.insert(item_to_index[item.name] - item_to_index["Campaign Tier 1 Column 1"], start) + break + chosen_ones.append(goal) + tier = 1 + column = 1 + recreation = [] + for opp in chosen_ones: + recreation.append(OpponentData(opp.id, opp.name, opp.campaign_info, tier, column, opp.card_id, + opp.deck_name_id, opp.deck_file, opp.difficulty)) + column += 1 + if column > 5: + column = 1 + tier += 1 + + return recreation + + +def get_opponent_locations(opponent: OpponentData) -> Dict[str, Optional[Union[str, int]]]: + location = {opponent.name + " Beaten": "Tier " + str(opponent.tier) + " Beaten"} + if opponent.tier > 4 and opponent.column != 5: + name = "Campaign Tier 5: Column " + str(opponent.column) + " Win" + # return a int instead so a item can be placed at this location later + location[name] = special[name] + for info in opponent.campaign_info: + location[opponent.name + "-> " + info] = info + return location + + +def get_opponent_condition(opponent: OpponentData, unlock_item: str, unlock_amount: int, player: int, + is_challenge: bool) -> CollectionRule: + if is_challenge: + return lambda state: ( + state.has(unlock_item, player, unlock_amount) + and yugioh06_difficulty(state, player, opponent.difficulty) + and state.has_all(opponent.additional_info, player) + ) + else: + return lambda state: ( + state.has_group(unlock_item, player, unlock_amount) + and yugioh06_difficulty(state, player, opponent.difficulty) + and state.has_all(opponent.additional_info, player) + ) diff --git a/worlds/yugioh06/options.py b/worlds/yugioh06/options.py new file mode 100644 index 000000000000..3100f5175d6f --- /dev/null +++ b/worlds/yugioh06/options.py @@ -0,0 +1,195 @@ +from dataclasses import dataclass + +from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Range, Toggle + + +class StructureDeck(Choice): + """Which Structure Deck you start with""" + + display_name = "Structure Deck" + option_dragons_roar = 0 + option_zombie_madness = 1 + option_blazing_destruction = 2 + option_fury_from_the_deep = 3 + option_warriors_triumph = 4 + option_spellcasters_judgement = 5 + option_none = 6 + option_random_deck = 7 + default = 7 + + +class Banlist(Choice): + """Which Banlist you start with""" + + display_name = "Banlist" + option_no_banlist = 0 + option_september_2003 = 1 + option_march_2004 = 2 + option_september_2004 = 3 + option_march_2005 = 4 + option_september_2005 = 5 + default = option_september_2005 + + +class FinalCampaignBossUnlockCondition(Choice): + """How to unlock the final campaign boss and goal for the world""" + + display_name = "Final Campaign Boss unlock Condition" + option_campaign_opponents = 0 + option_challenges = 1 + + +class FourthTier5UnlockCondition(Choice): + """How to unlock the fourth campaign boss""" + + display_name = "Fourth Tier 5 Campaign Boss unlock Condition" + option_campaign_opponents = 0 + option_challenges = 1 + + +class ThirdTier5UnlockCondition(Choice): + """How to unlock the third campaign boss""" + + display_name = "Third Tier 5 Campaign Boss unlock Condition" + option_campaign_opponents = 0 + option_challenges = 1 + + +class FinalCampaignBossChallenges(Range): + """Number of Limited/Theme Duels completed for the Final Campaign Boss to appear""" + + display_name = "Final Campaign Boss challenges unlock amount" + range_start = 0 + range_end = 91 + default = 10 + + +class FourthTier5CampaignBossChallenges(Range): + """Number of Limited/Theme Duels completed for the Fourth Level 5 Campaign Opponent to appear""" + + display_name = "Fourth Tier 5 Campaign Boss unlock amount" + range_start = 0 + range_end = 91 + default = 5 + + +class ThirdTier5CampaignBossChallenges(Range): + """Number of Limited/Theme Duels completed for the Third Level 5 Campaign Opponent to appear""" + + display_name = "Third Tier 5 Campaign Boss unlock amount" + range_start = 0 + range_end = 91 + default = 2 + + +class FinalCampaignBossCampaignOpponents(Range): + """Number of Campaign Opponents Duels defeated for the Final Campaign Boss to appear""" + + display_name = "Final Campaign Boss campaign opponent unlock amount" + range_start = 0 + range_end = 24 + default = 12 + + +class FourthTier5CampaignBossCampaignOpponents(Range): + """Number of Campaign Opponents Duels defeated for the Fourth Level 5 Campaign Opponent to appear""" + + display_name = "Fourth Tier 5 Campaign Boss campaign opponent unlock amount" + range_start = 0 + range_end = 23 + default = 7 + + +class ThirdTier5CampaignBossCampaignOpponents(Range): + """Number of Campaign Opponents Duels defeated for the Third Level 5 Campaign Opponent to appear""" + + display_name = "Third Tier 5 Campaign Boss campaign opponent unlock amount" + range_start = 0 + range_end = 22 + default = 3 + + +class NumberOfChallenges(Range): + """Number of random Limited/Theme Duels that are included. The rest will be inaccessible.""" + + display_name = "Number of Challenges" + range_start = 0 + range_end = 91 + default = 10 + + +class StartingMoney(Range): + """The amount of money you start with""" + + display_name = "Starting Money" + range_start = 0 + range_end = 100000 + default = 3000 + + +class MoneyRewardMultiplier(Range): + """By which amount the campaign reward money is multiplied""" + + display_name = "Money Reward Multiplier" + range_start = 1 + range_end = 255 + default = 20 + + +class NormalizeBoostersPacks(DefaultOnToggle): + """If enabled every booster pack costs the same otherwise vanilla cost is used""" + + display_name = "Normalize Booster Packs" + + +class BoosterPackPrices(Range): + """ + Only Works if normalize booster packs is enabled. + Sets the amount that what every booster pack costs. + """ + + display_name = "Booster Pack Prices" + range_start = 1 + range_end = 3000 + default = 100 + + +class AddEmptyBanList(Toggle): + """Adds a Ban List where everything is at 3 to the item pool""" + + display_name = "Add Empty Ban List" + + +class CampaignOpponentsShuffle(Toggle): + """Replaces the campaign with random opponents from the entire game""" + + display_name = "Campaign Opponents Shuffle" + + +class OCGArts(Toggle): + """Always use the OCG artworks for cards""" + + display_name = "OCG Arts" + + +@dataclass +class Yugioh06Options(PerGameCommonOptions): + structure_deck: StructureDeck + banlist: Banlist + final_campaign_boss_unlock_condition: FinalCampaignBossUnlockCondition + fourth_tier_5_campaign_boss_unlock_condition: FourthTier5UnlockCondition + third_tier_5_campaign_boss_unlock_condition: ThirdTier5UnlockCondition + final_campaign_boss_challenges: FinalCampaignBossChallenges + fourth_tier_5_campaign_boss_challenges: FourthTier5CampaignBossChallenges + third_tier_5_campaign_boss_challenges: ThirdTier5CampaignBossChallenges + final_campaign_boss_campaign_opponents: FinalCampaignBossCampaignOpponents + fourth_tier_5_campaign_boss_campaign_opponents: FourthTier5CampaignBossCampaignOpponents + third_tier_5_campaign_boss_campaign_opponents: ThirdTier5CampaignBossCampaignOpponents + number_of_challenges: NumberOfChallenges + starting_money: StartingMoney + money_reward_multiplier: MoneyRewardMultiplier + normalize_boosters_packs: NormalizeBoostersPacks + booster_pack_prices: BoosterPackPrices + add_empty_banlist: AddEmptyBanList + campaign_opponents_shuffle: CampaignOpponentsShuffle + ocg_arts: OCGArts diff --git a/worlds/yugioh06/patch.bsdiff4 b/worlds/yugioh06/patch.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..877884d5c946fcae2186b3ddd1b69470e1225cd1 GIT binary patch literal 2959 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 P9lRPqfdvUVP*?*1t)h(- literal 0 HcmV?d00001 diff --git a/worlds/yugioh06/rom.py b/worlds/yugioh06/rom.py new file mode 100644 index 000000000000..3ac10f9ea496 --- /dev/null +++ b/worlds/yugioh06/rom.py @@ -0,0 +1,161 @@ +import hashlib +import math +import os +import struct + +from settings import get_settings + +import Utils +from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes + +from worlds.AutoWorld import World +from .items import item_to_index +from .rom_values import banlist_ids, function_addresses, structure_deck_selection + +MD5Europe = "020411d3b08f5639eb8cb878283f84bf" +MD5America = "b8a7c976b28172995fe9e465d654297a" + + +class YGO06ProcedurePatch(APProcedurePatch, APTokenMixin): + game = "Yu-Gi-Oh! 2006" + hash = MD5America + patch_file_ending = ".apygo06" + result_file_ending = ".gba" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +def write_tokens(world: World, patch: YGO06ProcedurePatch): + structure_deck = structure_deck_selection.get(world.options.structure_deck.value) + # set structure deck + patch.write_token(APTokenTypes.WRITE, 0x000FD0AA, struct.pack(" bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + md5hash = basemd5.hexdigest() + if MD5Europe != md5hash and MD5America != md5hash: + raise Exception( + "Supplied Base Rom does not match known MD5 for" + "Yu-Gi-Oh! World Championship 2006 America or Europe " + "Get the correct game and version, then dump it" + ) + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + if not file_name: + file_name = get_settings().yugioh06_settings.rom_file + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/yugioh06/rom_values.py b/worlds/yugioh06/rom_values.py new file mode 100644 index 000000000000..4fb310080de9 --- /dev/null +++ b/worlds/yugioh06/rom_values.py @@ -0,0 +1,38 @@ +structure_deck_selection = { + # DRAGON'S ROAR + 0: 0x1, + # ZOMBIE MADNESS + 1: 0x5, + # BLAZING DESTRUCTION + 2: 0x9, + # FURY FROM THE DEEP + 3: 0xD, + # Warrior'S TRIUMPH + 4: 0x11, + # SPELLCASTER'S JUDGEMENT + 5: 0x15, + # Draft Mode + 6: 0x1, +} + +banlist_ids = { + # NoList + 0: 0x0, + # September 2003 + 1: 0x5, + # March 2004 + 2: 0x6, + # September 2004 + 3: 0x7, + # March 2005 + 4: 0x8, + # September 2005 + 5: 0x9, +} + +function_addresses = { + # Count Campaign Opponents + 0: 0xF0C8, + # Count Challenges + 1: 0xEF3A, +} diff --git a/worlds/yugioh06/ruff.toml b/worlds/yugioh06/ruff.toml new file mode 100644 index 000000000000..8acb3b14702f --- /dev/null +++ b/worlds/yugioh06/ruff.toml @@ -0,0 +1,12 @@ +line-length = 120 + +[lint] +preview = true +select = ["E", "F", "W", "I", "N", "Q", "UP", "RUF", "ISC", "T20"] +ignore = ["RUF012", "RUF100"] + +[per-file-ignores] +# The way options definitions work right now, world devs are forced to break line length requirements. +"options.py" = ["E501"] +# Yu Gi Oh specific: The structure of the Opponents.py file makes the line length violations acceptable. +"Opponents.py" = ["E501"] \ No newline at end of file diff --git a/worlds/yugioh06/rules.py b/worlds/yugioh06/rules.py new file mode 100644 index 000000000000..a804c7e7286a --- /dev/null +++ b/worlds/yugioh06/rules.py @@ -0,0 +1,868 @@ +from worlds.generic.Rules import add_rule + +from . import yugioh06_difficulty +from .fusions import count_has_materials + + +def set_rules(world): + player = world.player + multiworld = world.multiworld + + location_rules = { + # Campaign + "Campaign Tier 1: 1 Win": lambda state: state.has("Tier 1 Beaten", player), + "Campaign Tier 1: 3 Wins A": lambda state: state.has("Tier 1 Beaten", player, 3), + "Campaign Tier 1: 3 Wins B": lambda state: state.has("Tier 1 Beaten", player, 3), + "Campaign Tier 1: 5 Wins A": lambda state: state.has("Tier 1 Beaten", player, 5), + "Campaign Tier 1: 5 Wins B": lambda state: state.has("Tier 1 Beaten", player, 5), + "Campaign Tier 2: 1 Win": lambda state: state.has("Tier 2 Beaten", player), + "Campaign Tier 2: 3 Wins A": lambda state: state.has("Tier 2 Beaten", player, 3), + "Campaign Tier 2: 3 Wins B": lambda state: state.has("Tier 2 Beaten", player, 3), + "Campaign Tier 2: 5 Wins A": lambda state: state.has("Tier 2 Beaten", player, 5), + "Campaign Tier 2: 5 Wins B": lambda state: state.has("Tier 2 Beaten", player, 5), + "Campaign Tier 3: 1 Win": lambda state: state.has("Tier 3 Beaten", player), + "Campaign Tier 3: 3 Wins A": lambda state: state.has("Tier 3 Beaten", player, 3), + "Campaign Tier 3: 3 Wins B": lambda state: state.has("Tier 3 Beaten", player, 3), + "Campaign Tier 3: 5 Wins A": lambda state: state.has("Tier 3 Beaten", player, 5), + "Campaign Tier 3: 5 Wins B": lambda state: state.has("Tier 3 Beaten", player, 5), + "Campaign Tier 4: 5 Wins A": lambda state: state.has("Tier 4 Beaten", player, 5), + "Campaign Tier 4: 5 Wins B": lambda state: state.has("Tier 4 Beaten", player, 5), + + # Bonuses + "Duelist Bonus Level 1": lambda state: state.has("Tier 1 Beaten", player), + "Duelist Bonus Level 2": lambda state: state.has("Tier 2 Beaten", player), + "Duelist Bonus Level 3": lambda state: state.has("Tier 3 Beaten", player), + "Duelist Bonus Level 4": lambda state: state.has("Tier 4 Beaten", player), + "Duelist Bonus Level 5": lambda state: state.has("Tier 5 Beaten", player), + "Max ATK Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "No Spell Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "No Trap Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "No Damage Bonus": lambda state: state.has_group("Campaign Boss Beaten", player, 3), + "Low Deck Bonus": lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and + yugioh06_difficulty(state, player, 3), + "Extremely Low Deck Bonus": + lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and + yugioh06_difficulty(state, player, 2), + "Opponent's Turn Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "Exactly 0 LP Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "Reversal Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "Quick Finish Bonus": lambda state: state.has("Quick-Finish", player) or yugioh06_difficulty(state, player, 6), + "Exodia Finish Bonus": lambda state: state.has("Can Exodia Win", player), + "Last Turn Finish Bonus": lambda state: state.has("Can Last Turn Win", player), + "Yata-Garasu Finish Bonus": lambda state: state.has("Can Yata Lock", player), + "Skull Servant Finish Bonus": lambda state: state.has("Skull Servant", player) and + yugioh06_difficulty(state, player, 3), + "Konami Bonus": lambda state: state.has_all(["Messenger of Peace", "Castle of Dark Illusions", "Mystik Wok"], + player) or (state.has_all(["Mystik Wok", "Barox", "Cyber-Stein", + "Poison of the Old Man"], + player) and yugioh06_difficulty(state, + player, 8)), + "Max Damage Bonus": lambda state: state.has_any(["Wave-Motion Cannon", "Megamorph", "United We Stand", + "Mage Power"], player), + "Fusion Summon Bonus": lambda state: state.has_any(["Polymerization", "Fusion Gate", "Power Bond"], player), + "Ritual Summon Bonus": lambda state: state.has("Ritual", player), + "Over 20000 LP Bonus": lambda state: can_gain_lp_every_turn(state, player) + and state.has("Can Stall with ST", player), + "Low LP Bonus": lambda state: state.has("Wall of Revealing Light", player) and yugioh06_difficulty(state, player, + 2), + "Extremely Low LP Bonus": lambda state: state.has_all(["Wall of Revealing Light", "Messenger of Peace"], player) + and yugioh06_difficulty(state, player, 4), + "Effect Damage Only Bonus": lambda state: state.has_all(["Solar Flare Dragon", "UFO Turtle"], player) + or state.has("Wave-Motion Cannon", player) + or state.can_reach("Final Countdown Finish Bonus", "Location", player) + or state.can_reach("Destiny Board Finish Bonus", "Location", player) + or state.has("Can Exodia Win", player) + or state.has("Can Last Turn Win", player), + "No More Cards Bonus": lambda state: state.has_any(["Cyber Jar", "Morphing Jar", + "Morphing Jar #2", "Needle Worm"], player) + and state.has_any(["The Shallow Grave", "Spear Cretin"], + player) and yugioh06_difficulty(state, player, 5), + "Final Countdown Finish Bonus": lambda state: state.has("Final Countdown", player) + and state.has("Can Stall with ST", player), + "Destiny Board Finish Bonus": lambda state: state.has("Can Stall with Monsters", player) and + state.has("Destiny Board and its letters", player) and + state.has("A Cat of Ill Omen", player), + + # Cards + "Obtain all pieces of Exodia": lambda state: state.has("Exodia", player), + "Obtain Final Countdown": lambda state: state.has("Final Countdown", player), + "Obtain Victory Dragon": lambda state: state.has("Victory D.", player), + "Obtain Ojama Delta Hurricane and its required cards": + lambda state: state.has("Ojama Delta Hurricane and required cards", player), + "Obtain Huge Revolution and its required cards": + lambda state: state.has("Huge Revolution and its required cards", player), + "Obtain Perfectly Ultimate Great Moth and its required cards": + lambda state: state.has("Perfectly Ultimate Great Moth and its required cards", player), + "Obtain Valkyrion the Magna Warrior and its pieces": + lambda state: state.has("Valkyrion the Magna Warrior and its pieces", player), + "Obtain Dark Sage and its required cards": lambda state: state.has("Dark Sage and its required cards", player), + "Obtain Destiny Board and its letters": lambda state: state.has("Destiny Board and its letters", player), + "Obtain all XYZ-Dragon Cannon fusions and their materials": + lambda state: state.has("XYZ-Dragon Cannon fusions and their materials", player), + "Obtain VWXYZ-Dragon Catapult Cannon and the fusion materials": + lambda state: state.has("VWXYZ-Dragon Catapult Cannon and the fusion materials", player), + "Obtain Hamon, Lord of Striking Thunder": + lambda state: state.has("Hamon, Lord of Striking Thunder", player), + "Obtain Raviel, Lord of Phantasms": + lambda state: state.has("Raviel, Lord of Phantasms", player), + "Obtain Uria, Lord of Searing Flames": + lambda state: state.has("Uria, Lord of Searing Flames", player), + "Obtain Gate Guardian and its pieces": + lambda state: state.has("Gate Guardian and its pieces", player), + "Obtain Dark Scorpion Combination and its required cards": + lambda state: state.has("Dark Scorpion Combination and its required cards", player), + # Collection Events + "Ojama Delta Hurricane and required cards": + lambda state: state.has_all(["Ojama Delta Hurricane", "Ojama Green", "Ojama Yellow", "Ojama Black"], + player), + "Huge Revolution and its required cards": + lambda state: state.has_all(["Huge Revolution", "Oppressed People", "United Resistance", + "People Running About"], player), + "Perfectly Ultimate Great Moth and its required cards": + lambda state: state.has_all(["Perfectly Ultimate Great Moth", "Petit Moth", "Cocoon of Evolution"], player), + "Valkyrion the Magna Warrior and its pieces": + lambda state: state.has_all(["Valkyrion the Magna Warrior", "Alpha the Magnet Warrior", + "Beta the Magnet Warrior", "Gamma the Magnet Warrior"], player), + "Dark Sage and its required cards": + lambda state: state.has_all(["Dark Sage", "Dark Magician", "Time Wizard"], player), + "Destiny Board and its letters": + lambda state: state.has_all(["Destiny Board", "Spirit Message 'I'", "Spirit Message 'N'", + "Spirit Message 'A'", "Spirit Message 'L'"], player), + "XYZ-Dragon Cannon fusions and their materials": + lambda state: state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", + "XY-Dragon Cannon", "XZ-Tank Cannon", "YZ-Tank Dragon", "XYZ-Dragon Cannon"], + player), + "VWXYZ-Dragon Catapult Cannon and the fusion materials": + lambda state: state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", "XYZ-Dragon Cannon", + "V-Tiger Jet", "W-Wing Catapult", "VW-Tiger Catapult", + "VWXYZ-Dragon Catapult Cannon"], + player), + "Gate Guardian and its pieces": + lambda state: state.has_all(["Gate Guardian", "Kazejin", "Suijin", "Sanga of the Thunder"], player), + "Dark Scorpion Combination and its required cards": + lambda state: state.has_all(["Dark Scorpion Combination", "Don Zaloog", "Dark Scorpion - Chick the Yellow", + "Dark Scorpion - Meanae the Thorn", "Dark Scorpion - Gorg the Strong", + "Cliff the Trap Remover"], player), + "Can Exodia Win": + lambda state: state.has_all(["Exodia", "Heart of the Underdog"], player), + "Can Last Turn Win": + lambda state: state.has_all(["Last Turn", "Wall of Revealing Light"], player) and + (state.has_any(["Jowgen the Spiritualist", "Jowls of Dark Demise", "Non Aggression Area"], + player) + or state.has_all(["Cyber-Stein", "The Last Warrior from Another Planet"], player)), + "Can Yata Lock": + lambda state: state.has_all(["Yata-Garasu", "Chaos Emperor Dragon - Envoy of the End", "Sangan"], player) + and state.has_any(["No Banlist", "Banlist September 2003"], player), + "Can Stall with Monsters": + lambda state: state.count_from_list_unique( + ["Spirit Reaper", "Giant Germ", "Marshmallon", "Nimble Momonga"], player) >= 2, + "Can Stall with ST": + lambda state: state.count_from_list_unique(["Level Limit - Area B", "Gravity Bind", "Messenger of Peace"], + player) >= 2, + "Has Back-row removal": + lambda state: back_row_removal(state, player) + + } + access_rules = { + # Limited + "LD01 All except Level 4 forbidden": + lambda state: yugioh06_difficulty(state, player, 2), + "LD02 Medium/high Level forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD03 ATK 1500 or more forbidden": + lambda state: yugioh06_difficulty(state, player, 4), + "LD04 Flip Effects forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD05 Tributes forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD06 Traps forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD07 Large Deck A": + lambda state: yugioh06_difficulty(state, player, 4), + "LD08 Large Deck B": + lambda state: yugioh06_difficulty(state, player, 4), + "LD09 Sets Forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD10 All except LV monsters forbidden": + lambda state: only_level(state, player) and yugioh06_difficulty(state, player, 2), + "LD11 All except Fairies forbidden": + lambda state: only_fairy(state, player) and yugioh06_difficulty(state, player, 2), + "LD12 All except Wind forbidden": + lambda state: only_wind(state, player) and yugioh06_difficulty(state, player, 2), + "LD13 All except monsters forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD14 Level 3 or below forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD15 DEF 1500 or less forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD16 Effect Monsters forbidden": + lambda state: only_normal(state, player) and yugioh06_difficulty(state, player, 4), + "LD17 Spells forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD18 Attacks forbidden": + lambda state: state.has_all(["Wave-Motion Cannon", "Stealth Bird"], player) + and state.count_from_list_unique(["Dark World Lightning", "Nobleman of Crossout", + "Shield Crash", "Tribute to the Doomed"], player) >= 2 + and yugioh06_difficulty(state, player, 3), + "LD19 All except E-Hero's forbidden": + lambda state: state.has_any(["Polymerization", "Fusion Gate"], player) and + count_has_materials(state, ["Elemental Hero Flame Wingman", + "Elemental Hero Madballman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Steam Healer", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge"], player) >= 3 and + yugioh06_difficulty(state, player, 3), + "LD20 All except Warriors forbidden": + lambda state: only_warrior(state, player) and yugioh06_difficulty(state, player, 2), + "LD21 All except Dark forbidden": + lambda state: only_dark(state, player) and yugioh06_difficulty(state, player, 2), + "LD22 All limited cards forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD23 Refer to Mar 05 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + "LD24 Refer to Sept 04 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + "LD25 Low Life Points": + lambda state: yugioh06_difficulty(state, player, 5), + "LD26 All except Toons forbidden": + lambda state: only_toons(state, player) and yugioh06_difficulty(state, player, 2), + "LD27 All except Spirits forbidden": + lambda state: only_spirit(state, player) and yugioh06_difficulty(state, player, 2), + "LD28 All except Dragons forbidden": + lambda state: only_dragon(state, player) and yugioh06_difficulty(state, player, 2), + "LD29 All except Spellcasters forbidden": + lambda state: only_spellcaster(state, player) and yugioh06_difficulty(state, player, 2), + "LD30 All except Light forbidden": + lambda state: only_light(state, player) and yugioh06_difficulty(state, player, 2), + "LD31 All except Fire forbidden": + lambda state: only_fire(state, player) and yugioh06_difficulty(state, player, 2), + "LD32 Decks with multiples forbidden": + lambda state: yugioh06_difficulty(state, player, 4), + "LD33 Special Summons forbidden": + lambda state: yugioh06_difficulty(state, player, 2), + "LD34 Normal Summons forbidden": + lambda state: state.has_all(["Polymerization", "King of the Swamp"], player) and + count_has_materials(state, ["Elemental Hero Flame Wingman", + "Elemental Hero Madballman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Steam Healer", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge"], player) >= 3 and + yugioh06_difficulty(state, player, 4), + "LD35 All except Zombies forbidden": + lambda state: only_zombie(state, player) and yugioh06_difficulty(state, player, 2), + "LD36 All except Earth forbidden": + lambda state: only_earth(state, player) and yugioh06_difficulty(state, player, 2), + "LD37 All except Water forbidden": + lambda state: only_water(state, player) and yugioh06_difficulty(state, player, 2), + "LD38 Refer to Mar 04 Banlist": + lambda state: yugioh06_difficulty(state, player, 4), + "LD39 Monsters forbidden": + lambda state: state.has_all(["Skull Zoma", "Embodiment of Apophis"], player) + and yugioh06_difficulty(state, player, 5), + "LD40 Refer to Sept 05 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + "LD41 Refer to Sept 03 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + # Theme Duels + "TD01 Battle Damage": + lambda state: yugioh06_difficulty(state, player, 1), + "TD02 Deflected Damage": + lambda state: state.has("Fairy Box", player) and yugioh06_difficulty(state, player, 1), + "TD03 Normal Summon": + lambda state: yugioh06_difficulty(state, player, 3), + "TD04 Ritual Summon": + lambda state: yugioh06_difficulty(state, player, 3) and + state.has_all(["Contract with the Abyss", + "Manju of the Ten Thousand Hands", + "Senju of the Thousand Hands", + "Sonic Bird", + "Pot of Avarice", + "Dark Master - Zorc", + "Demise, King of Armageddon", + "The Masked Beast", + "Magician of Black Chaos", + "Dark Magic Ritual"], player), + "TD05 Special Summon A": + lambda state: yugioh06_difficulty(state, player, 3), + "TD06 20x Spell": + lambda state: state.has("Magical Blast", player) and yugioh06_difficulty(state, player, 3), + "TD07 10x Trap": + lambda state: yugioh06_difficulty(state, player, 3), + "TD08 Draw": + lambda state: state.has_any(["Self-Destruct Button", "Dark Snake Syndrome"], player) and + yugioh06_difficulty(state, player, 3), + "TD09 Hand Destruction": + lambda state: state.has_all(["Cyber Jar", + "Morphing Jar", + "Book of Moon", + "Book of Taiyou", + "Card Destruction", + "Serial Spell", + "Spell Reproduction", + "The Shallow Grave"], player) and yugioh06_difficulty(state, player, 3), + "TD10 During Opponent's Turn": + lambda state: yugioh06_difficulty(state, player, 3), + "TD11 Recover": + lambda state: can_gain_lp_every_turn(state, player) and yugioh06_difficulty(state, player, 3), + "TD12 Remove Monsters by Effect": + lambda state: state.has("Soul Release", player) and yugioh06_difficulty(state, player, 2), + "TD13 Flip Summon": + lambda state: pacman_deck(state, player) and yugioh06_difficulty(state, player, 2), + "TD14 Special Summon B": + lambda state: state.has_any(["Manticore of Darkness", "Treeborn Frog"], player) and + state.has("Foolish Burial", player) and + yugioh06_difficulty(state, player, 2), + "TD15 Token": + lambda state: state.has_all(["Dandylion", "Ojama Trio", "Stray Lambs"], player) and + yugioh06_difficulty(state, player, 3), + "TD16 Union": + lambda state: equip_unions(state, player) and + yugioh06_difficulty(state, player, 2), + "TD17 10x Quick Spell": + lambda state: quick_plays(state, player) and + yugioh06_difficulty(state, player, 3), + "TD18 The Forbidden": + lambda state: state.has("Can Exodia Win", player), + "TD19 20 Turns": + lambda state: state.has("Final Countdown", player) and state.has("Can Stall with ST", player) and + yugioh06_difficulty(state, player, 3), + "TD20 Deck Destruction": + lambda state: state.has_any(["Cyber Jar", "Morphing Jar", "Morphing Jar #2", "Needle Worm"], player) + and state.has_any(["The Shallow Grave", "Spear Cretin"], + player) and yugioh06_difficulty(state, player, 2), + "TD21 Victory D.": + lambda state: state.has("Victory D.", player) and only_dragon(state, player) + and yugioh06_difficulty(state, player, 3), + "TD22 The Preventers Fight Back": + lambda state: state.has("Ojama Delta Hurricane and required cards", player) and + state.has_all(["Rescue Cat", "Enchanting Fitting Room", "Jerry Beans Man"], player) and + yugioh06_difficulty(state, player, 3), + "TD23 Huge Revolution": + lambda state: state.has("Huge Revolution and its required cards", player) and + state.has_all(["Enchanting Fitting Room", "Jerry Beans Man"], player) and + yugioh06_difficulty(state, player, 3), + "TD24 Victory in 5 Turns": + lambda state: yugioh06_difficulty(state, player, 3), + "TD25 Moth Grows Up": + lambda state: state.has("Perfectly Ultimate Great Moth and its required cards", player) and + state.has_all(["Gokipon", "Howling Insect"], player) and + yugioh06_difficulty(state, player, 3), + "TD26 Magnetic Power": + lambda state: state.has("Valkyrion the Magna Warrior and its pieces", player) and + yugioh06_difficulty(state, player, 2), + "TD27 Dark Sage": + lambda state: state.has("Dark Sage and its required cards", player) and + state.has_any(["Skilled Dark Magician", "Dark Magic Curtain"], player) and + yugioh06_difficulty(state, player, 2), + "TD28 Direct Damage": + lambda state: yugioh06_difficulty(state, player, 2), + "TD29 Destroy Monsters in Battle": + lambda state: yugioh06_difficulty(state, player, 2), + "TD30 Tribute Summon": + lambda state: state.has("Treeborn Frog", player) and yugioh06_difficulty(state, player, 2), + "TD31 Special Summon C": + lambda state: state.count_from_list_unique( + ["Aqua Spirit", "Rock Spirit", "Spirit of Flames", + "Garuda the Wind Spirit", "Gigantes", "Inferno", "Megarock Dragon", "Silpheed"], + player) > 4 and yugioh06_difficulty(state, player, 3), + "TD32 Toon": + lambda state: only_toons(state, player) and yugioh06_difficulty(state, player, 3), + "TD33 10x Counter": + lambda state: counter_traps(state, player) and yugioh06_difficulty(state, player, 2), + "TD34 Destiny Board": + lambda state: state.has("Destiny Board and its letters", player) + and state.has("Can Stall with Monsters", player) + and state.has("A Cat of Ill Omen", player) + and yugioh06_difficulty(state, player, 2), + "TD35 Huge Damage in a Turn": + lambda state: state.has_all(["Cyber-Stein", "Cyber Twin Dragon", "Megamorph"], player) + and yugioh06_difficulty(state, player, 3), + "TD36 V-Z In the House": + lambda state: state.has("VWXYZ-Dragon Catapult Cannon and the fusion materials", player) + and yugioh06_difficulty(state, player, 3), + "TD37 Uria, Lord of Searing Flames": + lambda state: state.has_all(["Uria, Lord of Searing Flames", + "Embodiment of Apophis", + "Skull Zoma", + "Metal Reflect Slime"], player) + and yugioh06_difficulty(state, player, 3), + "TD38 Hamon, Lord of Striking Thunder": + lambda state: state.has("Hamon, Lord of Striking Thunder", player) + and yugioh06_difficulty(state, player, 3), + "TD39 Raviel, Lord of Phantasms": + lambda state: state.has_all(["Raviel, Lord of Phantasms", "Giant Germ"], player) and + state.count_from_list_unique(["Archfiend Soldier", + "Skull Descovery Knight", + "Slate Warrior", + "D. D. Trainer", + "Earthbound Spirit"], player) >= 3 + and yugioh06_difficulty(state, player, 3), + "TD40 Make a Chain": + lambda state: state.has("Ultimate Offering", player) + and yugioh06_difficulty(state, player, 4), + "TD41 The Gatekeeper Stands Tall": + lambda state: state.has("Gate Guardian and its pieces", player) and + state.has_all(["Treeborn Frog", "Tribute Doll"], player) + and yugioh06_difficulty(state, player, 4), + "TD42 Serious Damage": + lambda state: yugioh06_difficulty(state, player, 3), + "TD43 Return Monsters with Effects": + lambda state: state.has_all(["Penguin Soldier", "Messenger of Peace"], player) + and yugioh06_difficulty(state, player, 4), + "TD44 Fusion Summon": + lambda state: state.has_all(["Fusion Gate", "Terraforming", "Dimension Fusion", + "Return from the Different Dimension"], player) and + count_has_materials(state, ["Elemental Hero Flame Wingman", + "Elemental Hero Madballman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Steam Healer", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge"], player) >= 4 and + yugioh06_difficulty(state, player, 7), + "TD45 Big Damage at once": + lambda state: state.has("Wave-Motion Cannon", player) + and yugioh06_difficulty(state, player, 3), + "TD46 XYZ In the House": + lambda state: state.has("XYZ-Dragon Cannon fusions and their materials", player) and + state.has("Dimension Fusion", player), + "TD47 Spell Counter": + lambda state: spell_counter(state, player) and yugioh06_difficulty(state, player, 3), + "TD48 Destroy Monsters with Effects": + lambda state: state.has_all(["Blade Rabbit", "Dream Clown"], player) and + state.has("Can Stall with ST", player) and + yugioh06_difficulty(state, player, 3), + "TD49 Plunder": + lambda state: take_control(state, player) and yugioh06_difficulty(state, player, 5), + "TD50 Dark Scorpion Combination": + lambda state: state.has("Dark Scorpion Combination and its required cards", player) and + state.has_all(["Reinforcement of the Army", "Mystic Tomato"], player) and + yugioh06_difficulty(state, player, 3) + } + multiworld.completion_condition[player] = lambda state: state.has("Goal", player) + + for loc in multiworld.get_locations(player): + if loc.name in location_rules: + add_rule(loc, location_rules[loc.name]) + if loc.name in access_rules: + add_rule(multiworld.get_entrance(loc.name, player), access_rules[loc.name]) + + +def only_light(state, player): + return state.has_from_list_unique([ + "Dunames Dark Witch", + "X-Head Cannon", + "Homunculus the Alchemic Being", + "Hysteric Fairy", + "Ninja Grandmaster Sasuke"], player, 2)\ + and state.has_from_list_unique([ + "Chaos Command Magician", + "Cybernetic Magician", + "Kaiser Glider", + "The Agent of Judgment - Saturn", + "Zaborg the Thunder Monarch", + "Cyber Dragon"], player, 1) \ + and state.has_from_list_unique([ + "D.D. Warrior Lady", + "Mystic Swordsman LV2", + "Y-Dragon Head", + "Z-Metal Tank", + ], player, 2) and state.has("Shining Angel", player) + + +def only_dark(state, player): + return state.has_from_list_unique([ + "Dark Elf", + "Archfiend Soldier", + "Mad Dog of Darkness", + "Vorse Raider", + "Skilled Dark Magician", + "Skull Descovery Knight", + "Mechanicalchaser", + "Dark Blade", + "Gil Garth", + "La Jinn the Mystical Genie of the Lamp", + "Opticlops", + "Zure, Knight of Dark World", + "Brron, Mad King of Dark World", + "D.D. Survivor", + "Exarion Universe", + "Kycoo the Ghost Destroyer", + "Regenerating Mummy" + ], player, 2) \ + and state.has_any([ + "Summoned Skull", + "Skull Archfiend of Lightning", + "The End of Anubis", + "Dark Ruler Ha Des", + "Beast of Talwar", + "Inferno Hammer", + "Jinzo", + "Ryu Kokki" + ], player) \ + and state.has_from_list_unique([ + "Legendary Fiend", + "Don Zaloog", + "Newdoria", + "Sangan", + "Spirit Reaper", + "Giant Germ" + ], player, 2) and state.has("Mystic Tomato", player) + + +def only_earth(state, player): + return state.has_from_list_unique([ + "Berserk Gorilla", + "Gemini Elf", + "Insect Knight", + "Toon Gemini Elf", + "Familiar-Possessed - Aussa", + "Neo Bug", + "Blindly Loyal Goblin", + "Chiron the Mage", + "Gearfried the Iron Knight" + ], player, 2) and state.has_any([ + "Dark Driceratops", + "Granmarg the Rock Monarch", + "Hieracosphinx", + "Saber Beetle" + ], player) and state.has_from_list_unique([ + "Hyper Hammerhead", + "Green Gadget", + "Red Gadget", + "Yellow Gadget", + "Dimensional Warrior", + "Enraged Muka Muka", + "Exiled Force" + ], player, 2) and state.has("Giant Rat", player) + + +def only_water(state, player): + return state.has_from_list_unique([ + "Gagagigo", + "Familiar-Possessed - Eria", + "7 Colored Fish", + "Sea Serpent Warrior of Darkness", + "Abyss Soldier" + ], player, 2) and state.has_any([ + "Giga Gagagigo", + "Amphibian Beast", + "Terrorking Salmon", + "Mobius the Frost Monarch" + ], player) and state.has_from_list_unique([ + "Revival Jam", + "Yomi Ship", + "Treeborn Frog" + ], player, 2) and state.has("Mother Grizzly", player) + + +def only_fire(state, player): + return state.has_from_list_unique([ + "Blazing Inpachi", + "Familiar-Possessed - Hiita", + "Great Angus", + "Fire Beaters" + ], player, 2) and state.has_any([ + "Thestalos the Firestorm Monarch", + "Horus the Black Flame Dragon LV6" + ], player) and state.has_from_list_unique([ + "Solar Flare Dragon", + "Tenkabito Shien", + "Ultimate Baseball Kid" + ], player, 2) and state.has("UFO Turtle", player) + + +def only_wind(state, player): + return state.has_from_list_unique([ + "Luster Dragon", + "Slate Warrior", + "Spear Dragon", + "Familiar-Possessed - Wynn", + "Harpie's Brother", + "Nin-Ken Dog", + "Cyber Harpie Lady", + "Oxygeddon" + ], player, 2) and state.has_any([ + "Cyber-Tech Alligator", + "Luster Dragon #2", + "Armed Dragon LV5", + "Roc from the Valley of Haze" + ], player) and state.has_from_list_unique([ + "Armed Dragon LV3", + "Twin-Headed Behemoth", + "Harpie Lady 1" + ], player, 2) and state.has("Flying Kamakiri 1", player) + + +def only_fairy(state, player): + return state.has_any([ + "Dunames Dark Witch", + "Hysteric Fairy" + ], player) and (state.count_from_list_unique([ + "Dunames Dark Witch", + "Hysteric Fairy", + "Dancing Fairy", + "Zolga", + "Shining Angel", + "Kelbek", + "Mudora", + "Asura Priest", + "Cestus of Dagla" + ], player) + (state.has_any([ + "The Agent of Judgment - Saturn", + "Airknight Parshath" + ], player))) >= 7 + + +def only_warrior(state, player): + return state.has_any([ + "Dark Blade", + "Blindly Loyal Goblin", + "D.D. Survivor", + "Gearfried the Iron knight", + "Ninja Grandmaster Sasuke", + "Warrior Beaters" + ], player) and (state.count_from_list_unique([ + "Warrior Lady of the Wasteland", + "Exiled Force", + "Mystic Swordsman LV2", + "Dimensional Warrior", + "Dandylion", + "D.D. Assailant", + "Blade Knight", + "D.D. Warrior Lady", + "Marauding Captain", + "Command Knight", + "Reinforcement of the Army" + ], player) + (state.has_any([ + "Freed the Matchless General", + "Holy Knight Ishzark", + "Silent Swordsman Lv5" + ], player))) >= 7 + + +def only_zombie(state, player): + return state.has("Pyramid Turtle", player) \ + and state.has_from_list_unique([ + "Regenerating Mummy", + "Ryu Kokki", + "Spirit Reaper", + "Master Kyonshee", + "Curse of Vampire", + "Vampire Lord", + "Goblin Zombie", + "Curse of Vampire", + "Vampire Lord", + "Goblin Zombie", + "Book of Life", + "Call of the Mummy" + ], player, 6) + + +def only_dragon(state, player): + return state.has_any([ + "Luster Dragon", + "Spear Dragon", + "Cave Dragon" + ], player) and (state.count_from_list_unique([ + "Luster Dragon", + "Spear Dragon", + "Cave Dragon" + "Armed Dragon LV3", + "Masked Dragon", + "Twin-Headed Behemoth", + "Element Dragon", + "Troop Dragon", + "Horus the Black Flame Dragon LV4", + "Stamping Destruction" + ], player) + (state.has_any([ + "Luster Dragon #2", + "Armed Dragon LV5", + "Kaiser Glider", + "Horus the Black Flame Dragon LV6" + ], player))) >= 7 + + +def only_spellcaster(state, player): + return state.has_any([ + "Dark Elf", + "Gemini Elf", + "Skilled Dark Magician", + "Toon Gemini Elf", + "Kycoo the Ghost Destroyer", + "Familiar-Possessed - Aussa" + ], player) and (state.count_from_list_unique([ + "Dark Elf", + "Gemini Elf", + "Skilled Dark Magician", + "Toon Gemini Elf", + "Kycoo the Ghost Destroyer", + "Familiar-Possessed - Aussa", + "Breaker the magical Warrior", + "The Tricky", + "Injection Fairy Lily", + "Magician of Faith", + "Tsukuyomi", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "Summon Priest", + "Old Vindictive Magician", + "Apprentice Magician", + "Magical Dimension" + ], player) + (state.has_any([ + "Chaos Command Magician", + "Cybernetic Magician" + ], player))) >= 7 + + +def equip_unions(state, player): + return (state.has_all(["Burning Beast", "Freezing Beast", + "Metallizing Parasite - Lunatite", "Mother Grizzly"], player) or + state.has_all(["Dark Blade", "Pitch-Dark Dragon", + "Giant Orc", "Second Goblin", "Mystic Tomato"], player) or + state.has_all(["Decayed Commander", "Zombie Tiger", + "Vampire Orchis", "Des Dendle", "Giant Rat"], player) or + state.has_all(["Indomitable Fighter Lei Lei", "Protective Soul Ailin", + "V-Tiger Jet", "W-Wing Catapult", "Shining Angel"], player) or + state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", "Shining Angel"], player)) and\ + state.has_any(["Frontline Base", "Formation Union", "Roll Out!"], player) + + +def can_gain_lp_every_turn(state, player): + return state.count_from_list_unique([ + "Solemn Wishes", + "Cure Mermaid", + "Dancing Fairy", + "Princess Pikeru", + "Kiseitai"], player) >= 3 + + +def only_normal(state, player): + return (state.has_from_list_unique([ + "Archfiend Soldier", + "Gemini Elf", + "Insect Knight", + "Luster Dragon", + "Mad Dog of Darkness", + "Vorse Raider", + "Blazing Inpachi", + "Gagagigo", + "Mechanicalchaser", + "7 Colored Fish", + "Dark Blade", + "Dunames Dark Witch", + "Giant Red Snake", + "Gil Garth", + "Great Angus", + "Harpie's Brother", + "La Jinn the Mystical Genie of the Lamp", + "Neo Bug", + "Nin-Ken Dog", + "Opticlops", + "Sea Serpent Warrior of Darkness", + "X-Head Cannon", + "Zure, Knight of Dark World"], player, 6) and + state.has_any([ + "Cyber-Tech Alligator", + "Summoned Skull", + "Giga Gagagigo", + "Amphibian Beast", + "Beast of Talwar", + "Luster Dragon #2", + "Terrorking Salmon"], player)) + + +def only_level(state, player): + return (state.has("Level Up!", player) and + (state.has_all(["Armed Dragon LV3", "Armed Dragon LV5"], player) + + state.has_all(["Horus the Black Flame Dragon LV4", "Horus the Black Flame Dragon LV6"], player) + + state.has_all(["Mystic Swordsman LV4", "Mystic Swordsman LV6"], player) + + state.has_all(["Silent Swordsman Lv3", "Silent Swordsman Lv5"], player) + + state.has_all(["Ultimate Insect Lv3", "Ultimate Insect Lv5"], player)) >= 3) + + +def spell_counter(state, player): + return (state.has("Pitch-Black Power Stone", player) and + state.has_from_list_unique(["Blast Magician", + "Magical Marionette", + "Mythical Beast Cerberus", + "Royal Magical Library", + "Spell-Counter Cards"], player, 2)) + + +def take_control(state, player): + return state.has_from_list_unique(["Aussa the Earth Charmer", + "Jowls of Dark Demise", + "Brain Control", + "Creature Swap", + "Enemy Controller", + "Mind Control", + "Magician of Faith"], player, 5) + + +def only_toons(state, player): + return state.has_all(["Toon Gemini Elf", + "Toon Goblin Attack Force", + "Toon Masked Sorcerer", + "Toon Mermaid", + "Toon Dark Magician Girl", + "Toon World"], player) + + +def only_spirit(state, player): + return state.has_all(["Asura Priest", + "Fushi No Tori", + "Maharaghi", + "Susa Soldier"], player) + + +def pacman_deck(state, player): + return state.has_from_list_unique(["Des Lacooda", + "Swarm of Locusts", + "Swarm of Scarabs", + "Wandering Mummy", + "Golem Sentry", + "Great Spirit", + "Royal Keeper", + "Stealth Bird"], player, 4) + + +def quick_plays(state, player): + return state.has_from_list_unique(["Collapse", + "Emergency Provisions", + "Enemy Controller", + "Graceful Dice", + "Mystik Wok", + "Offerings to the Doomed", + "Poison of the Old Man", + "Reload", + "Rush Recklessly", + "The Reliable Guardian"], player, 4) + + +def counter_traps(state, player): + return state.has_from_list_unique(["Cursed Seal of the Forbidden Spell", + "Divine Wrath", + "Horn of Heaven", + "Magic Drain", + "Magic Jammer", + "Negate Attack", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Spell Shield Type-8"], player, 5) + + +def back_row_removal(state, player): + return state.has_from_list_unique(["Anteatereatingant", + "B.E.S. Tetran", + "Breaker the Magical Warrior", + "Calamity of the Wicked", + "Chiron the Mage", + "Dust Tornado", + "Heavy Storm", + "Mystical Space Typhoon", + "Mobius the Frost Monarch", + "Raigeki Break", + "Stamping Destruction", + "Swarm of Locusts"], player, 2) diff --git a/worlds/yugioh06/structure_deck.py b/worlds/yugioh06/structure_deck.py new file mode 100644 index 000000000000..3559e7c5153e --- /dev/null +++ b/worlds/yugioh06/structure_deck.py @@ -0,0 +1,87 @@ +from typing import Dict, List + +structure_contents: Dict[str, List[str]] = { + "dragons_roar": [ + "Luster Dragon", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Masked Dragon", + "Twin-Headed Behemoth", + "Stamping Destruction", + "Nobleman of Crossout", + "Creature Swap", + "Reload", + "Stamping Destruction", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon" + ], + "zombie_madness": [ + "Pyramid Turtle", + "Regenerating Mummy", + "Ryu Kokki", + "Book of Life", + "Call of the Mummy", + "Creature Swap", + "Reload", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon" + ], + "blazing_destruction": [ + "Inferno", + "Solar Flare Dragon", + "UFO Turtle", + "Ultimate Baseball Kid", + "Fire Beaters", + "Tribute to The Doomed", + "Level Limit - Area B", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon" + ], + "fury_from_the_deep": [ + "Mother Grizzly", + "Water Beaters", + "Gravity Bind", + "Reload", + "Mobius the Frost Monarch", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon" + ], + "warriors_triumph": [ + "Gearfried the Iron Knight", + "D.D. Warrior Lady", + "Marauding Captain", + "Exiled Force", + "Reinforcement of the Army", + "Warrior Beaters", + "Reload", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon" + ], + "spellcasters_judgement": [ + "Dark Magician", + "Apprentice Magician", + "Breaker the Magical Warrior", + "Magician of Faith", + "Skilled Dark Magician", + "Tsukuyomi", + "Magical Dimension", + "Mage Power", + "Spell-Counter Cards", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon" + ], + "none": [], +} + + +def get_deck_content_locations(deck: str) -> Dict[str, str]: + return { + f"{deck} {i}": content + for i, content in enumerate(structure_contents[deck], 1) + } diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index b4e382e097d2..cf61d93ca4ce 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -14,7 +14,7 @@ from .gen_data import GenData from .logic import cs_to_zz_locs from .region import ZillionLocation, ZillionRegion -from .options import ZillionOptions, validate +from .options import ZillionOptions, validate, z_option_groups from .id_maps import ZillionSlotInfo, get_slot_info, item_name_to_id as _item_name_to_id, \ loc_name_to_id as _loc_name_to_id, make_id_to_others, \ zz_reg_name_to_reg_name, base_id @@ -62,6 +62,8 @@ class ZillionWebWorld(WebWorld): ["beauxq"] )] + option_groups = z_option_groups + class ZillionWorld(World): """ @@ -84,11 +86,6 @@ class ZillionWorld(World): item_name_to_id = _item_name_to_id location_name_to_id = _loc_name_to_id - # increment this every time something in your world's names/id mappings changes. - # While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be - # retrieved by clients on every connection. - data_version = 1 - logger: logging.Logger class LogStreamInterface: @@ -148,10 +145,10 @@ def generate_early(self) -> None: self._item_counts = item_counts with redirect_stdout(self.lsi): # type: ignore - self.zz_system.make_randomizer(zz_op) - - self.zz_system.seed(self.multiworld.seed) + self.zz_system.set_options(zz_op) + self.zz_system.seed(self.random.randrange(1999999999)) self.zz_system.make_map() + self.zz_system.make_randomizer() # just in case the options changed anything (I don't think they do) assert self.zz_system.randomizer, "init failed" @@ -332,7 +329,7 @@ def finalize_item_locations(self) -> GenData: assert isinstance(z_loc, ZillionLocation) # debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc) if z_loc.item is None: - self.logger.warn("generate_output location has no item - is that ok?") + self.logger.warning("generate_output location has no item - is that ok?") z_loc.zz_loc.item = empty elif z_loc.item.player == self.player: z_item = z_loc.item diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index 5c2e11453036..09d0565e1c5e 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -231,20 +231,20 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None: if cmd == "Connected": logger.info("logged in to Archipelago server") if "slot_data" not in args: - logger.warn("`Connected` packet missing `slot_data`") + logger.warning("`Connected` packet missing `slot_data`") return slot_data = args["slot_data"] if "start_char" not in slot_data: - logger.warn("invalid Zillion `Connected` packet, `slot_data` missing `start_char`") + logger.warning("invalid Zillion `Connected` packet, `slot_data` missing `start_char`") return self.start_char = slot_data['start_char'] if self.start_char not in {"Apple", "Champ", "JJ"}: - logger.warn("invalid Zillion `Connected` packet, " - f"`slot_data` `start_char` has invalid value: {self.start_char}") + logger.warning("invalid Zillion `Connected` packet, " + f"`slot_data` `start_char` has invalid value: {self.start_char}") if "rescues" not in slot_data: - logger.warn("invalid Zillion `Connected` packet, `slot_data` missing `rescues`") + logger.warning("invalid Zillion `Connected` packet, `slot_data` missing `rescues`") return rescues = slot_data["rescues"] self.rescues = {} @@ -272,8 +272,8 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None: self.loc_mem_to_id[mem] = id_ if len(self.loc_mem_to_id) != 394: - logger.warn("invalid Zillion `Connected` packet, " - f"`slot_data` missing locations in `loc_mem_to_id` - len {len(self.loc_mem_to_id)}") + logger.warning("invalid Zillion `Connected` packet, " + f"`slot_data` missing locations in `loc_mem_to_id` - len {len(self.loc_mem_to_id)}") self.got_slot_data.set() @@ -347,6 +347,11 @@ def process_from_game_queue(self) -> None: "operations": [{"operation": "replace", "value": doors_b64}] } async_start(self.send_msgs([payload])) + elif isinstance(event_from_game, events.MapEventFromGame): + row = event_from_game.map_index // 8 + col = event_from_game.map_index % 8 + room_name = f"({chr(row + 64)}-{col + 1})" + logger.info(f"You are at {room_name}") else: logger.warning(f"WARNING: unhandled event from game {event_from_game}") diff --git a/worlds/zillion/docs/en_Zillion.md b/worlds/zillion/docs/en_Zillion.md index 06a11b7d7993..697a9b7dadbe 100644 --- a/worlds/zillion/docs/en_Zillion.md +++ b/worlds/zillion/docs/en_Zillion.md @@ -4,9 +4,9 @@ Zillion is a metroidvania-style game released in 1987 for the 8-bit Sega Master It's based on the anime Zillion (赤ã„光弾ジリオン, Akai Koudan Zillion). -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What changes are made to this game? diff --git a/worlds/zillion/docs/setup_en.md b/worlds/zillion/docs/setup_en.md index 79f7912dd4fd..c8e29fc36cde 100644 --- a/worlds/zillion/docs/setup_en.md +++ b/worlds/zillion/docs/setup_en.md @@ -47,7 +47,7 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The [player settings page](/games/Zillion/player-settings) on the website allows you to configure your personal settings and export a config file from +The [player options page](/games/Zillion/player-options) on the website allows you to configure your personal options and export a config file from them. ### Verifying your config file @@ -56,7 +56,7 @@ If you would like to validate your config file to make sure it works, you may do ## Generating a Single-Player Game -1. Navigate to the [player settings page](/games/Zillion/player-settings), configure your options, and click the "Generate Game" button. +1. Navigate to the [player options page](/games/Zillion/player-options), configure your options, and click the "Generate Game" button. 2. A "Seed Info" page will appear. 3. Click the "Create New Room" link. 4. A server page will appear. Download your patch file from this page. diff --git a/worlds/zillion/gen_data.py b/worlds/zillion/gen_data.py index aa24ff8961b3..13cbee9ced20 100644 --- a/worlds/zillion/gen_data.py +++ b/worlds/zillion/gen_data.py @@ -28,6 +28,13 @@ def to_json(self) -> str: def from_json(gen_data_str: str) -> "GenData": """ the reverse of `to_json` """ from_json = json.loads(gen_data_str) + + # backwards compatibility for seeds generated before new map_gen options + room_gen = from_json["zz_game"]["options"].get("room_gen", None) + if room_gen is not None: + from_json["zz_game"]["options"]["map_gen"] = {False: "none", True: "rooms"}.get(room_gen, "none") + del from_json["zz_game"]["options"]["room_gen"] + return GenData( from_json["multi_items"], ZzGame.from_jsonable(from_json["zz_game"]), diff --git a/worlds/zillion/options.py b/worlds/zillion/options.py index 97f8b817f77c..5de0b65c82f0 100644 --- a/worlds/zillion/options.py +++ b/worlds/zillion/options.py @@ -1,9 +1,9 @@ from collections import Counter from dataclasses import dataclass -from typing import ClassVar, Dict, Tuple +from typing import ClassVar, Dict, Literal, Tuple from typing_extensions import TypeGuard # remove when Python >= 3.10 -from Options import DefaultOnToggle, NamedRange, PerGameCommonOptions, Range, Toggle, Choice +from Options import Choice, DefaultOnToggle, NamedRange, OptionGroup, PerGameCommonOptions, Range, Removed, Toggle from zilliandomizer.options import ( Options as ZzOptions, char_to_gun, char_to_jump, ID, @@ -222,7 +222,14 @@ class ZillionEarlyScope(Toggle): class ZillionSkill(Range): - """ the difficulty level of the game """ + """ + the difficulty level of the game + + higher skill: + - can require more precise platforming movement + - lowers your defense + - gives you less time to escape at the end + """ range_start = 0 range_end = 5 default = 2 @@ -244,9 +251,25 @@ class ZillionStartingCards(NamedRange): } -class ZillionRoomGen(Toggle): - """ whether to generate rooms with random terrain """ - display_name = "room generation" +class ZillionMapGen(Choice): + """ + - none: vanilla map + - rooms: random terrain inside rooms, but path through base is vanilla + - full: random path through base + """ + display_name = "map generation" + option_none = 0 + option_rooms = 1 + option_full = 2 + default = 0 + + def zz_value(self) -> Literal['none', 'rooms', 'full']: + if self.value == ZillionMapGen.option_none: + return "none" + if self.value == ZillionMapGen.option_rooms: + return "rooms" + assert self.value == ZillionMapGen.option_full + return "full" @dataclass @@ -269,7 +292,17 @@ class ZillionOptions(PerGameCommonOptions): early_scope: ZillionEarlyScope skill: ZillionSkill starting_cards: ZillionStartingCards - room_gen: ZillionRoomGen + map_gen: ZillionMapGen + + room_gen: Removed + + +z_option_groups = [ + OptionGroup("item counts", [ + ZillionIDCardCount, ZillionBreadCount, ZillionOpaOpaCount, ZillionZillionCount, + ZillionFloppyDiskCount, ZillionScopeCount, ZillionRedIDCardCount + ]) +] def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts: @@ -360,7 +393,7 @@ def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]": starting_cards = options.starting_cards - room_gen = options.room_gen + map_gen = options.map_gen.zz_value() zz_item_counts = convert_item_counts(item_counts) zz_op = ZzOptions( @@ -378,7 +411,7 @@ def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]": bool(options.early_scope.value), True, # balance defense starting_cards.value, - bool(room_gen.value) + map_gen ) zz_validate(zz_op) return zz_op, item_counts diff --git a/worlds/zillion/patch.py b/worlds/zillion/patch.py index dcbb85bcfc89..6bc6d04dd663 100644 --- a/worlds/zillion/patch.py +++ b/worlds/zillion/patch.py @@ -5,7 +5,7 @@ from typing_extensions import override import Utils -from worlds.Files import APPatch +from worlds.Files import APAutoPatchInterface from zilliandomizer.patch import Patcher @@ -14,7 +14,7 @@ USHASH = 'd4bf9e7bcf9a48da53785d2ae7bc4270' -class ZillionPatch(APPatch): +class ZillionPatch(APAutoPatchInterface): hash = USHASH game = "Zillion" patch_file_ending = ".apzl" diff --git a/worlds/zillion/requirements.txt b/worlds/zillion/requirements.txt index 3a784846a891..d6b01ac107ae 100644 --- a/worlds/zillion/requirements.txt +++ b/worlds/zillion/requirements.txt @@ -1,2 +1,2 @@ -zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@b36a23b5a138c78732ac8efb5b5ca8b0be07dcff#0.7.0 +zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@33045067f626266850f91c8045b9d3a9f52d02b0#0.9.0 typing-extensions>=4.7, <5 diff --git a/worlds/zillion/test/TestOptions.py b/worlds/zillion/test/TestOptions.py index c4f02d4bd3be..3820c32dd016 100644 --- a/worlds/zillion/test/TestOptions.py +++ b/worlds/zillion/test/TestOptions.py @@ -1,6 +1,6 @@ from . import ZillionTestBase -from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, ZillionOptions, validate +from ..options import ZillionJumpLevels, ZillionGunLevels, ZillionOptions, validate from zilliandomizer.options import VBLR_CHOICES diff --git a/worlds/zillion/test/TestReproducibleRandom.py b/worlds/zillion/test/TestReproducibleRandom.py index 392db657d90a..a92fae240709 100644 --- a/worlds/zillion/test/TestReproducibleRandom.py +++ b/worlds/zillion/test/TestReproducibleRandom.py @@ -1,7 +1,7 @@ from typing import cast from . import ZillionTestBase -from worlds.zillion import ZillionWorld +from .. import ZillionWorld class SeedTest(ZillionTestBase): diff --git a/worlds/zillion/test/__init__.py b/worlds/zillion/test/__init__.py index 93c0512fb045..fe62bae34c9e 100644 --- a/worlds/zillion/test/__init__.py +++ b/worlds/zillion/test/__init__.py @@ -1,6 +1,6 @@ from typing import cast from test.bases import WorldTestBase -from worlds.zillion import ZillionWorld +from .. import ZillionWorld class ZillionTestBase(WorldTestBase): diff --git a/worlds/zork_grand_inquisitor/LICENSE b/worlds/zork_grand_inquisitor/LICENSE new file mode 100644 index 000000000000..a94ca6bf9177 --- /dev/null +++ b/worlds/zork_grand_inquisitor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Serpent.AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/worlds/zork_grand_inquisitor/__init__.py b/worlds/zork_grand_inquisitor/__init__.py new file mode 100644 index 000000000000..4da257e47bd0 --- /dev/null +++ b/worlds/zork_grand_inquisitor/__init__.py @@ -0,0 +1,17 @@ +import worlds.LauncherComponents as LauncherComponents + +from .world import ZorkGrandInquisitorWorld + + +def launch_client() -> None: + from .client import main + LauncherComponents.launch_subprocess(main, name="ZorkGrandInquisitorClient") + + +LauncherComponents.components.append( + LauncherComponents.Component( + "Zork Grand Inquisitor Client", + func=launch_client, + component_type=LauncherComponents.Type.CLIENT + ) +) diff --git a/worlds/zork_grand_inquisitor/client.py b/worlds/zork_grand_inquisitor/client.py new file mode 100644 index 000000000000..11d6b7f8f183 --- /dev/null +++ b/worlds/zork_grand_inquisitor/client.py @@ -0,0 +1,188 @@ +import asyncio + +import CommonClient +import NetUtils +import Utils + +from typing import Any, Dict, List, Optional, Set, Tuple + +from .data_funcs import item_names_to_id, location_names_to_id, id_to_items, id_to_locations, id_to_goals +from .enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations +from .game_controller import GameController + + +class ZorkGrandInquisitorCommandProcessor(CommonClient.ClientCommandProcessor): + def _cmd_zork(self) -> None: + """Attach to an open Zork Grand Inquisitor process.""" + result: bool = self.ctx.game_controller.open_process_handle() + + if result: + self.ctx.process_attached_at_least_once = True + self.output("Successfully attached to Zork Grand Inquisitor process.") + else: + self.output("Failed to attach to Zork Grand Inquisitor process.") + + def _cmd_brog(self) -> None: + """List received Brog items.""" + self.ctx.game_controller.list_received_brog_items() + + def _cmd_griff(self) -> None: + """List received Griff items.""" + self.ctx.game_controller.list_received_griff_items() + + def _cmd_lucy(self) -> None: + """List received Lucy items.""" + self.ctx.game_controller.list_received_lucy_items() + + def _cmd_hotspots(self) -> None: + """List received Hotspots.""" + self.ctx.game_controller.list_received_hotspots() + + +class ZorkGrandInquisitorContext(CommonClient.CommonContext): + tags: Set[str] = {"AP"} + game: str = "Zork Grand Inquisitor" + command_processor: CommonClient.ClientCommandProcessor = ZorkGrandInquisitorCommandProcessor + items_handling: int = 0b111 + want_slot_data: bool = True + + item_name_to_id: Dict[str, int] = item_names_to_id() + location_name_to_id: Dict[str, int] = location_names_to_id() + + id_to_items: Dict[int, ZorkGrandInquisitorItems] = id_to_items() + id_to_locations: Dict[int, ZorkGrandInquisitorLocations] = id_to_locations() + + game_controller: GameController + + controller_task: Optional[asyncio.Task] + + process_attached_at_least_once: bool + can_display_process_message: bool + + def __init__(self, server_address: Optional[str], password: Optional[str]) -> None: + super().__init__(server_address, password) + + self.game_controller = GameController(logger=CommonClient.logger) + + self.controller_task = None + + self.process_attached_at_least_once = False + self.can_display_process_message = True + + def run_gui(self) -> None: + from kvui import GameManager + + class TextManager(GameManager): + logging_pairs: List[Tuple[str, str]] = [("Client", "Archipelago")] + base_title: str = "Archipelago Zork Grand Inquisitor Client" + + self.ui = TextManager(self) + self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super().server_auth(password_requested) + + await self.get_username() + await self.send_connect() + + def on_package(self, cmd: str, _args: Any) -> None: + if cmd == "Connected": + self.game = self.slot_info[self.slot].game + + # Options + self.game_controller.option_goal = id_to_goals()[_args["slot_data"]["goal"]] + self.game_controller.option_deathsanity = _args["slot_data"]["deathsanity"] == 1 + + self.game_controller.option_grant_missable_location_checks = ( + _args["slot_data"]["grant_missable_location_checks"] == 1 + ) + + async def controller(self): + while not self.exit_event.is_set(): + await asyncio.sleep(0.1) + + # Enqueue Received Item Delta + network_item: NetUtils.NetworkItem + for network_item in self.items_received: + item: ZorkGrandInquisitorItems = self.id_to_items[network_item.item] + + if item not in self.game_controller.received_items: + if item not in self.game_controller.received_items_queue: + self.game_controller.received_items_queue.append(item) + + # Game Controller Update + if self.game_controller.is_process_running(): + self.game_controller.update() + self.can_display_process_message = True + else: + process_message: str + + if self.process_attached_at_least_once: + process_message = ( + "Lost connection to Zork Grand Inquisitor process. Please restart the game and use the /zork " + "command to reattach." + ) + else: + process_message = ( + "Please use the /zork command to attach to a running Zork Grand Inquisitor process." + ) + + if self.can_display_process_message: + CommonClient.logger.info(process_message) + self.can_display_process_message = False + + # Send Checked Locations + checked_location_ids: List[int] = list() + + while len(self.game_controller.completed_locations_queue) > 0: + location: ZorkGrandInquisitorLocations = self.game_controller.completed_locations_queue.popleft() + location_id: int = self.location_name_to_id[location.value] + + checked_location_ids.append(location_id) + + await self.send_msgs([ + { + "cmd": "LocationChecks", + "locations": checked_location_ids + } + ]) + + # Check for Goal Completion + if self.game_controller.goal_completed: + await self.send_msgs([ + { + "cmd": "StatusUpdate", + "status": CommonClient.ClientStatus.CLIENT_GOAL + } + ]) + + +def main() -> None: + Utils.init_logging("ZorkGrandInquisitorClient", exception_logger="Client") + + async def _main(): + ctx: ZorkGrandInquisitorContext = ZorkGrandInquisitorContext(None, None) + + ctx.server_task = asyncio.create_task(CommonClient.server_loop(ctx), name="server loop") + ctx.controller_task = asyncio.create_task(ctx.controller(), name="ZorkGrandInquisitorController") + + if CommonClient.gui_enabled: + ctx.run_gui() + + ctx.run_cli() + + await ctx.exit_event.wait() + await ctx.shutdown() + + import colorama + + colorama.init() + + asyncio.run(_main()) + + colorama.deinit() + + +if __name__ == "__main__": + main() diff --git a/worlds/zork_grand_inquisitor/data/__init__.py b/worlds/zork_grand_inquisitor/data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/zork_grand_inquisitor/data/entrance_rule_data.py b/worlds/zork_grand_inquisitor/data/entrance_rule_data.py new file mode 100644 index 000000000000..f48be5eb6b6a --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/entrance_rule_data.py @@ -0,0 +1,419 @@ +from typing import Dict, Tuple, Union + +from ..enums import ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems, ZorkGrandInquisitorRegions + + +entrance_rule_data: Dict[ + Tuple[ + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorRegions, + ], + Union[ + Tuple[ + Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ], + ..., + ], + ..., + ], + None, + ], +] = { + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE, + ), + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH): ( + ( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): ( + ( + ZorkGrandInquisitorItems.SUBWAY_TOKEN, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS): None, + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): ( + ( + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.DM_LAIR): None, + (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WALKING_CASTLE): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_BLINDS, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ZorkGrandInquisitorEvents.KNOWS_YASTARD, + ), + ), + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON): ( + ( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW, + ), + ), + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): None, + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): None, + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME): ( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ZorkGrandInquisitorRegions.WHITE_HOUSE, + ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.CROSSROADS): None, + (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): ( + ( + ZorkGrandInquisitorItems.SPELL_IGRAM, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + (ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR,), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.GUE_TECH): None, + (ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.STUDENT_ID, + ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS): ( + (ZorkGrandInquisitorItems.MAP,), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.GUE_TECH): None, + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): ( + ( + ZorkGrandInquisitorEvents.KNOWS_SNAVIG, + ZorkGrandInquisitorItems.TOTEM_BROG, # Visually hiding this totem is tied to owning it; no choice + ), + ), + (ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_SHORE): ( + (ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), + ), + (ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): ( + ( + ZorkGrandInquisitorItems.SPELL_NARWILE, + ZorkGrandInquisitorEvents.KNOWS_YASTARD, + ), + ), + (ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.HADES): None, + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS): ( + (ZorkGrandInquisitorItems.MAP,), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.HADES): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,), + ), + (ZorkGrandInquisitorRegions.MENU, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, + (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): ( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): None, + (ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.MONASTERY): None, + (ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ZorkGrandInquisitorEvents.KNOWS_YASTARD, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.CROSSROADS): ( + ( + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE, + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_WELL, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP): ( + ( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): None, + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN): ( + ( + ZorkGrandInquisitorItems.TOTEM_LUCY, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.ENDGAME): ( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorRegions.WHITE_HOUSE, + ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): None, + (ZorkGrandInquisitorRegions.SPELL_LAB, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): None, + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.CROSSROADS): ( + (ZorkGrandInquisitorItems.MAP,), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): None, + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SPELL_LAB): ( + ( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, + ZorkGrandInquisitorEvents.DAM_DESTROYED, + ZorkGrandInquisitorItems.SPELL_GOLGATEM, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.CROSSROADS): None, + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.HADES_SHORE): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,), + ), + (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, + (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,), + ), + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,), + ), + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.MONASTERY): ( + ( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,), + ), + (ZorkGrandInquisitorRegions.WALKING_CASTLE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None, + (ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None, + (ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.ENDGAME): ( + ( + ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), +} diff --git a/worlds/zork_grand_inquisitor/data/item_data.py b/worlds/zork_grand_inquisitor/data/item_data.py new file mode 100644 index 000000000000..c312bbce3d09 --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/item_data.py @@ -0,0 +1,792 @@ +from typing import Dict, NamedTuple, Optional, Tuple, Union + +from BaseClasses import ItemClassification + +from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorTags + + +class ZorkGrandInquisitorItemData(NamedTuple): + statemap_keys: Optional[Tuple[int, ...]] + archipelago_id: Optional[int] + classification: ItemClassification + tags: Tuple[ZorkGrandInquisitorTags, ...] + maximum_quantity: Optional[int] = 1 + + +ITEM_OFFSET = 9758067000 + +item_data: Dict[ZorkGrandInquisitorItems, ZorkGrandInquisitorItemData] = { + # Inventory Items + ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: ZorkGrandInquisitorItemData( + statemap_keys=(67,), # Extinguished = 103 + archipelago_id=ITEM_OFFSET + 0, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: ZorkGrandInquisitorItemData( + statemap_keys=(68,), # Extinguished = 104 + archipelago_id=ITEM_OFFSET + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.BROGS_GRUE_EGG: ZorkGrandInquisitorItemData( + statemap_keys=(70,), # Boiled = 71 + archipelago_id=ITEM_OFFSET + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.BROGS_PLANK: ZorkGrandInquisitorItemData( + statemap_keys=(69,), + archipelago_id=ITEM_OFFSET + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: ZorkGrandInquisitorItemData( + statemap_keys=(54,), + archipelago_id=ITEM_OFFSET + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP: ZorkGrandInquisitorItemData( + statemap_keys=(86,), + archipelago_id=ITEM_OFFSET + 5, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH: ZorkGrandInquisitorItemData( + statemap_keys=(84,), + archipelago_id=ITEM_OFFSET + 6, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: ZorkGrandInquisitorItemData( + statemap_keys=(9,), + archipelago_id=ITEM_OFFSET + 7, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: ZorkGrandInquisitorItemData( + statemap_keys=(16,), + archipelago_id=ITEM_OFFSET + 8, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.HAMMER: ZorkGrandInquisitorItemData( + statemap_keys=(23,), + archipelago_id=ITEM_OFFSET + 9, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.HUNGUS_LARD: ZorkGrandInquisitorItemData( + statemap_keys=(55,), + archipelago_id=ITEM_OFFSET + 10, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: ZorkGrandInquisitorItemData( + statemap_keys=(56,), + archipelago_id=ITEM_OFFSET + 11, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LANTERN: ZorkGrandInquisitorItemData( + statemap_keys=(4,), + archipelago_id=ITEM_OFFSET + 12, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: ZorkGrandInquisitorItemData( + statemap_keys=(88,), + archipelago_id=ITEM_OFFSET + 13, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: ZorkGrandInquisitorItemData( + statemap_keys=(116,), # With fly = 120 + archipelago_id=ITEM_OFFSET + 14, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: ZorkGrandInquisitorItemData( + statemap_keys=(117,), # With fly = 121 + archipelago_id=ITEM_OFFSET + 15, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: ZorkGrandInquisitorItemData( + statemap_keys=(118,), # With fly = 122 + archipelago_id=ITEM_OFFSET + 16, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: ZorkGrandInquisitorItemData( + statemap_keys=(119,), # With fly = 123 + archipelago_id=ITEM_OFFSET + 17, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MAP: ZorkGrandInquisitorItemData( + statemap_keys=(6,), + archipelago_id=ITEM_OFFSET + 18, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MEAD_LIGHT: ZorkGrandInquisitorItemData( + statemap_keys=(2,), + archipelago_id=ITEM_OFFSET + 19, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MOSS_OF_MAREILON: ZorkGrandInquisitorItemData( + statemap_keys=(57,), + archipelago_id=ITEM_OFFSET + 20, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MUG: ZorkGrandInquisitorItemData( + statemap_keys=(35,), + archipelago_id=ITEM_OFFSET + 21, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: ZorkGrandInquisitorItemData( + statemap_keys=(17,), + archipelago_id=ITEM_OFFSET + 22, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: ZorkGrandInquisitorItemData( + statemap_keys=(36,), + archipelago_id=ITEM_OFFSET + 23, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: ZorkGrandInquisitorItemData( + statemap_keys=(3,), + archipelago_id=ITEM_OFFSET + 24, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: ZorkGrandInquisitorItemData( + statemap_keys=(5827,), + archipelago_id=ITEM_OFFSET + 25, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.PROZORK_TABLET: ZorkGrandInquisitorItemData( + statemap_keys=(65,), + archipelago_id=ITEM_OFFSET + 26, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: ZorkGrandInquisitorItemData( + statemap_keys=(53,), + archipelago_id=ITEM_OFFSET + 27, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.ROPE: ZorkGrandInquisitorItemData( + statemap_keys=(83,), + archipelago_id=ITEM_OFFSET + 28, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: ZorkGrandInquisitorItemData( + statemap_keys=(101,), # SNA = 41 + archipelago_id=ITEM_OFFSET + 29, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: ZorkGrandInquisitorItemData( + statemap_keys=(102,), # VIG = 48 + archipelago_id=ITEM_OFFSET + 30, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SHOVEL: ZorkGrandInquisitorItemData( + statemap_keys=(49,), + archipelago_id=ITEM_OFFSET + 31, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SNAPDRAGON: ZorkGrandInquisitorItemData( + statemap_keys=(50,), + archipelago_id=ITEM_OFFSET + 32, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.STUDENT_ID: ZorkGrandInquisitorItemData( + statemap_keys=(39,), + archipelago_id=ITEM_OFFSET + 33, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SUBWAY_TOKEN: ZorkGrandInquisitorItemData( + statemap_keys=(20,), + archipelago_id=ITEM_OFFSET + 34, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SWORD: ZorkGrandInquisitorItemData( + statemap_keys=(21,), + archipelago_id=ITEM_OFFSET + 35, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.ZIMDOR_SCROLL: ZorkGrandInquisitorItemData( + statemap_keys=(25,), + archipelago_id=ITEM_OFFSET + 36, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.ZORK_ROCKS: ZorkGrandInquisitorItemData( + statemap_keys=(37,), + archipelago_id=ITEM_OFFSET + 37, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + # Hotspots + ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: ZorkGrandInquisitorItemData( + statemap_keys=(9116,), + archipelago_id=ITEM_OFFSET + 100 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: ZorkGrandInquisitorItemData( + statemap_keys=(15434, 15436, 15438, 15440), + archipelago_id=ITEM_OFFSET + 100 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: ZorkGrandInquisitorItemData( + statemap_keys=(12096,), + archipelago_id=ITEM_OFFSET + 100 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_BLINDS: ZorkGrandInquisitorItemData( + statemap_keys=(4799,), + archipelago_id=ITEM_OFFSET + 100 + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(12691, 12692, 12693, 12694, 12695, 12696, 12697, 12698, 12699, 12700, 12701), + archipelago_id=ITEM_OFFSET + 100 + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12702,), + archipelago_id=ITEM_OFFSET + 100 + 5, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12909,), + archipelago_id=ITEM_OFFSET + 100 + 6, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12900,), + archipelago_id=ITEM_OFFSET + 100 + 7, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(5010,), + archipelago_id=ITEM_OFFSET + 100 + 8, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(9539,), + archipelago_id=ITEM_OFFSET + 100 + 9, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: ZorkGrandInquisitorItemData( + statemap_keys=(19712,), + archipelago_id=ITEM_OFFSET + 100 + 10, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: ZorkGrandInquisitorItemData( + statemap_keys=(2586,), + archipelago_id=ITEM_OFFSET + 100 + 11, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: ZorkGrandInquisitorItemData( + statemap_keys=(11878,), + archipelago_id=ITEM_OFFSET + 100 + 12, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: ZorkGrandInquisitorItemData( + statemap_keys=(11751,), + archipelago_id=ITEM_OFFSET + 100 + 13, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: ZorkGrandInquisitorItemData( + statemap_keys=(15147, 15153), + archipelago_id=ITEM_OFFSET + 100 + 14, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: ZorkGrandInquisitorItemData( + statemap_keys=(1705,), + archipelago_id=ITEM_OFFSET + 100 + 15, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: ZorkGrandInquisitorItemData( + statemap_keys=(1425, 1426), + archipelago_id=ITEM_OFFSET + 100 + 16, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: ZorkGrandInquisitorItemData( + statemap_keys=(13106,), + archipelago_id=ITEM_OFFSET + 100 + 17, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(13219, 13220, 13221, 13222), + archipelago_id=ITEM_OFFSET + 100 + 18, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: ZorkGrandInquisitorItemData( + statemap_keys=(14327, 14332, 14337, 14342), + archipelago_id=ITEM_OFFSET + 100 + 19, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12528,), + archipelago_id=ITEM_OFFSET + 100 + 20, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: ZorkGrandInquisitorItemData( + statemap_keys=(12523, 12524, 12525), + archipelago_id=ITEM_OFFSET + 100 + 21, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: ZorkGrandInquisitorItemData( + statemap_keys=(13002,), + archipelago_id=ITEM_OFFSET + 100 + 22, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: ZorkGrandInquisitorItemData( + statemap_keys=(10726,), + archipelago_id=ITEM_OFFSET + 100 + 23, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(12280,), + archipelago_id=ITEM_OFFSET + 100 + 24, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: ZorkGrandInquisitorItemData( + statemap_keys=( + 17694, + 17695, + 17696, + 17697, + 18200, + 17703, + 17704, + 17705, + 17710, + 17711, + 17712, + 17713, + 17714, + 17715, + 17716, + 17722, + 17723, + 17724, + 17725, + 17726, + 17727 + ), + archipelago_id=ITEM_OFFSET + 100 + 25, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(8448, 8449, 8450, 8451, 8452, 8453, 8454, 8455, 8456, 8457, 8458, 8459), + archipelago_id=ITEM_OFFSET + 100 + 26, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: ZorkGrandInquisitorItemData( + statemap_keys=(8446,), + archipelago_id=ITEM_OFFSET + 100 + 27, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HARRY: ZorkGrandInquisitorItemData( + statemap_keys=(4260,), + archipelago_id=ITEM_OFFSET + 100 + 28, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: ZorkGrandInquisitorItemData( + statemap_keys=(18026,), + archipelago_id=ITEM_OFFSET + 100 + 29, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: ZorkGrandInquisitorItemData( + statemap_keys=(17623,), + archipelago_id=ITEM_OFFSET + 100 + 30, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(13140,), + archipelago_id=ITEM_OFFSET + 100 + 31, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(10441,), + archipelago_id=ITEM_OFFSET + 100 + 32, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(19632, 19627), + archipelago_id=ITEM_OFFSET + 100 + 33, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(3025,), + archipelago_id=ITEM_OFFSET + 100 + 34, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: ZorkGrandInquisitorItemData( + statemap_keys=(3036,), + archipelago_id=ITEM_OFFSET + 100 + 35, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MIRROR: ZorkGrandInquisitorItemData( + statemap_keys=(5031,), + archipelago_id=ITEM_OFFSET + 100 + 36, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: ZorkGrandInquisitorItemData( + statemap_keys=(13597,), + archipelago_id=ITEM_OFFSET + 100 + 37, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: ZorkGrandInquisitorItemData( + statemap_keys=(13390,), + archipelago_id=ITEM_OFFSET + 100 + 38, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(2455, 2447), + archipelago_id=ITEM_OFFSET + 100 + 39, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: ZorkGrandInquisitorItemData( + statemap_keys=(12389, 12390), + archipelago_id=ITEM_OFFSET + 100 + 40, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: ZorkGrandInquisitorItemData( + statemap_keys=(4302,), + archipelago_id=ITEM_OFFSET + 100 + 41, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: ZorkGrandInquisitorItemData( + statemap_keys=(16383, 16384), + archipelago_id=ITEM_OFFSET + 100 + 42, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: ZorkGrandInquisitorItemData( + statemap_keys=(2769,), + archipelago_id=ITEM_OFFSET + 100 + 43, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: ZorkGrandInquisitorItemData( + statemap_keys=(4149,), + archipelago_id=ITEM_OFFSET + 100 + 44, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(12584, 12585, 12586, 12587), + archipelago_id=ITEM_OFFSET + 100 + 45, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12574,), + archipelago_id=ITEM_OFFSET + 100 + 46, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(13412,), + archipelago_id=ITEM_OFFSET + 100 + 47, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: ZorkGrandInquisitorItemData( + statemap_keys=(12170,), + archipelago_id=ITEM_OFFSET + 100 + 48, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: ZorkGrandInquisitorItemData( + statemap_keys=(16382,), + archipelago_id=ITEM_OFFSET + 100 + 49, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: ZorkGrandInquisitorItemData( + statemap_keys=(4209,), + archipelago_id=ITEM_OFFSET + 100 + 50, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: ZorkGrandInquisitorItemData( + statemap_keys=(11973,), + archipelago_id=ITEM_OFFSET + 100 + 51, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(13168,), + archipelago_id=ITEM_OFFSET + 100 + 52, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: ZorkGrandInquisitorItemData( + statemap_keys=(15396,), + archipelago_id=ITEM_OFFSET + 100 + 53, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: ZorkGrandInquisitorItemData( + statemap_keys=(9706,), + archipelago_id=ITEM_OFFSET + 100 + 54, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: ZorkGrandInquisitorItemData( + statemap_keys=(9728, 9729, 9730), + archipelago_id=ITEM_OFFSET + 100 + 55, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_WELL: ZorkGrandInquisitorItemData( + statemap_keys=(10314,), + archipelago_id=ITEM_OFFSET + 100 + 56, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + # Spells + ZorkGrandInquisitorItems.SPELL_GLORF: ZorkGrandInquisitorItemData( + statemap_keys=(202,), + archipelago_id=ITEM_OFFSET + 200 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_GOLGATEM: ZorkGrandInquisitorItemData( + statemap_keys=(192,), + archipelago_id=ITEM_OFFSET + 200 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_IGRAM: ZorkGrandInquisitorItemData( + statemap_keys=(199,), + archipelago_id=ITEM_OFFSET + 200 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_KENDALL: ZorkGrandInquisitorItemData( + statemap_keys=(196,), + archipelago_id=ITEM_OFFSET + 200 + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_NARWILE: ZorkGrandInquisitorItemData( + statemap_keys=(197,), + archipelago_id=ITEM_OFFSET + 200 + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_REZROV: ZorkGrandInquisitorItemData( + statemap_keys=(195,), + archipelago_id=ITEM_OFFSET + 200 + 5, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_THROCK: ZorkGrandInquisitorItemData( + statemap_keys=(200,), + archipelago_id=ITEM_OFFSET + 200 + 6, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_VOXAM: ZorkGrandInquisitorItemData( + statemap_keys=(191,), + archipelago_id=ITEM_OFFSET + 200 + 7, + classification=ItemClassification.useful, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + # Subway Destinations + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: ZorkGrandInquisitorItemData( + statemap_keys=(13757, 13297, 13486, 13625), + archipelago_id=ITEM_OFFSET + 300 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), + ), + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: ZorkGrandInquisitorItemData( + statemap_keys=(13758, 13309, 13498, 13637), + archipelago_id=ITEM_OFFSET + 300 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), + ), + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData( + statemap_keys=(13759, 13316, 13505, 13644), + archipelago_id=ITEM_OFFSET + 300 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), + ), + # Teleporter Destinations + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR: ZorkGrandInquisitorItemData( + statemap_keys=(2203,), + archipelago_id=ITEM_OFFSET + 400 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH: ZorkGrandInquisitorItemData( + statemap_keys=(7132,), + archipelago_id=ITEM_OFFSET + 400 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES: ZorkGrandInquisitorItemData( + statemap_keys=(7119,), + archipelago_id=ITEM_OFFSET + 400 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData( + statemap_keys=(7148,), + archipelago_id=ITEM_OFFSET + 400 + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB: ZorkGrandInquisitorItemData( + statemap_keys=(16545,), + archipelago_id=ITEM_OFFSET + 400 + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + # Totemizer Destinations + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: ZorkGrandInquisitorItemData( + statemap_keys=(9660,), + archipelago_id=ITEM_OFFSET + 500 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: ZorkGrandInquisitorItemData( + statemap_keys=(9666,), + archipelago_id=ITEM_OFFSET + 500 + 1, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: ZorkGrandInquisitorItemData( + statemap_keys=(9668,), + archipelago_id=ITEM_OFFSET + 500 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: ZorkGrandInquisitorItemData( + statemap_keys=(9662,), + archipelago_id=ITEM_OFFSET + 500 + 3, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + # Totems + ZorkGrandInquisitorItems.TOTEM_BROG: ZorkGrandInquisitorItemData( + statemap_keys=(4853,), + archipelago_id=ITEM_OFFSET + 600 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEM,), + ), + ZorkGrandInquisitorItems.TOTEM_GRIFF: ZorkGrandInquisitorItemData( + statemap_keys=(4315,), + archipelago_id=ITEM_OFFSET + 600 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEM,), + ), + ZorkGrandInquisitorItems.TOTEM_LUCY: ZorkGrandInquisitorItemData( + statemap_keys=(5223,), + archipelago_id=ITEM_OFFSET + 600 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEM,), + ), + # Filler + ZorkGrandInquisitorItems.FILLER_INQUISITION_PROPAGANDA_FLYER: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 0, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_UNREADABLE_SPELL_SCROLL: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 1, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_MAGIC_CONTRABAND: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 2, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_FROBOZZ_ELECTRIC_GADGET: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 3, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_NONSENSICAL_INQUISITION_PAPERWORK: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 4, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), +} diff --git a/worlds/zork_grand_inquisitor/data/location_data.py b/worlds/zork_grand_inquisitor/data/location_data.py new file mode 100644 index 000000000000..8b4e57392de8 --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/location_data.py @@ -0,0 +1,1535 @@ +from typing import Dict, NamedTuple, Optional, Tuple, Union + +from ..enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorTags, +) + + +class ZorkGrandInquisitorLocationData(NamedTuple): + game_state_trigger: Optional[ + Tuple[ + Union[ + Tuple[str, str], + Tuple[int, int], + Tuple[int, Tuple[int, ...]], + ], + ..., + ] + ] + archipelago_id: Optional[int] + region: ZorkGrandInquisitorRegions + tags: Optional[Tuple[ZorkGrandInquisitorTags, ...]] = None + requirements: Optional[ + Tuple[ + Union[ + Union[ + ZorkGrandInquisitorItems, + ZorkGrandInquisitorEvents, + ], + Tuple[ + Union[ + ZorkGrandInquisitorItems, + ZorkGrandInquisitorEvents, + ], + ..., + ], + ], + ..., + ] + ] = None + event_item_name: Optional[str] = None + + +LOCATION_OFFSET = 9758067000 + +location_data: Dict[ + Union[ZorkGrandInquisitorLocations, ZorkGrandInquisitorEvents], ZorkGrandInquisitorLocationData +] = { + ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2m"),), + archipelago_id=LOCATION_OFFSET + 0, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.ARREST_THE_VANDAL: ZorkGrandInquisitorLocationData( + game_state_trigger=((10789, 1),), + archipelago_id=LOCATION_OFFSET + 1, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED: ZorkGrandInquisitorLocationData( + game_state_trigger=((11787, 1), (11788, 1), (11789, 1)), + archipelago_id=LOCATION_OFFSET + 2, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER: ZorkGrandInquisitorLocationData( + game_state_trigger=((8929, 1),), + archipelago_id=LOCATION_OFFSET + 3, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_OBIDIL,), + ), + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9124, 1),), + archipelago_id=LOCATION_OFFSET + 4, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE, + ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX, + ), + ), + ZorkGrandInquisitorLocations.A_SMALLWAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((11777, 1),), + archipelago_id=LOCATION_OFFSET + 5, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ZorkGrandInquisitorItems.SPELL_IGRAM, + ), + ), + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY: ZorkGrandInquisitorLocationData( + game_state_trigger=((13278, 1),), + archipelago_id=LOCATION_OFFSET + 6, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE, + ZorkGrandInquisitorItems.SPELL_THROCK, + ), + ), + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED: ZorkGrandInquisitorLocationData( + game_state_trigger=((16315, 1),), + archipelago_id=LOCATION_OFFSET + 7, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE, + ZorkGrandInquisitorItems.SPELL_KENDALL, + ), + ), + ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3x"),), + archipelago_id=LOCATION_OFFSET + 8, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.BOING_BOING_BOING: ZorkGrandInquisitorLocationData( + game_state_trigger=((4220, 1),), + archipelago_id=LOCATION_OFFSET + 9, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.SNAPDRAGON, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.BONK: ZorkGrandInquisitorLocationData( + game_state_trigger=((19491, 1),), + archipelago_id=LOCATION_OFFSET + 10, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, + ), + ), + ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "us2g"),), + archipelago_id=LOCATION_OFFSET + 11, + region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.BROG_DO_GOOD: ZorkGrandInquisitorLocationData( + game_state_trigger=((2644, 1),), + archipelago_id=LOCATION_OFFSET + 12, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ) + ), + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS: ZorkGrandInquisitorLocationData( + game_state_trigger=((2629, 1),), + archipelago_id=LOCATION_OFFSET + 13, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ) + ), + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB: ZorkGrandInquisitorLocationData( + game_state_trigger=((2650, 1),), + archipelago_id=LOCATION_OFFSET + 14, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ) + ), + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME: ZorkGrandInquisitorLocationData( + game_state_trigger=((15715, 1),), + archipelago_id=LOCATION_OFFSET + 15, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ) + ), + ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dv1t"),), + archipelago_id=LOCATION_OFFSET + 16, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.CAVES_NOTES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3y"),), + archipelago_id=LOCATION_OFFSET + 17, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS: ZorkGrandInquisitorLocationData( + game_state_trigger=((9543, 1),), + archipelago_id=LOCATION_OFFSET + 18, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.CRISIS_AVERTED: ZorkGrandInquisitorLocationData( + game_state_trigger=((11769, 1),), + archipelago_id=LOCATION_OFFSET + 19, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED, + ZorkGrandInquisitorItems.SPELL_IGRAM, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER, + ), + ), + ZorkGrandInquisitorLocations.CUT_THAT_OUT_YOU_LITTLE_CREEP: ZorkGrandInquisitorLocationData( + game_state_trigger=((19350, 1),), + archipelago_id=LOCATION_OFFSET + 20, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER: ZorkGrandInquisitorLocationData( + game_state_trigger=((17632, 1),), + archipelago_id=LOCATION_OFFSET + 21, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_BLINDS, + ZorkGrandInquisitorItems.SPELL_GOLGATEM, + ), + ), + ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2q"),), + archipelago_id=LOCATION_OFFSET + 22, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "hp5e"), (8919, 2), (9, 100)), + archipelago_id=LOCATION_OFFSET + 23, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SWORD,), + ), + ZorkGrandInquisitorLocations.DOOOOOOWN: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 3600),), + archipelago_id=LOCATION_OFFSET + 24, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.DOWN: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 5300),), + archipelago_id=LOCATION_OFFSET + 25, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_LUCY, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL: ZorkGrandInquisitorLocationData( + game_state_trigger=((9216, 1),), + archipelago_id=LOCATION_OFFSET + 26, + region=ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_NARWILE,), + ), + ZorkGrandInquisitorLocations.DUNCE_LOCKER: ZorkGrandInquisitorLocationData( + game_state_trigger=((11851, 1),), + archipelago_id=LOCATION_OFFSET + 27, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + ), + ZorkGrandInquisitorLocations.EGGPLANTS: ZorkGrandInquisitorLocationData( + game_state_trigger=((3816, 11000),), + archipelago_id=LOCATION_OFFSET + 28, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.ELSEWHERE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pc1e"),), + archipelago_id=LOCATION_OFFSET + 29, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((11784, 1),), + archipelago_id=LOCATION_OFFSET + 30, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + ), + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP: ZorkGrandInquisitorLocationData( + game_state_trigger=((13743, 1),), + archipelago_id=LOCATION_OFFSET + 31, + region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_KENDALL,), + ), + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA: ZorkGrandInquisitorLocationData( + game_state_trigger=((16368, 1),), + archipelago_id=LOCATION_OFFSET + 32, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.SPELL_IGRAM,), + ), + ZorkGrandInquisitorLocations.FIRE_FIRE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10277, 1),), + archipelago_id=LOCATION_OFFSET + 33, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "ue1h"),), + archipelago_id=LOCATION_OFFSET + 34, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=((4222, 1),), + archipelago_id=LOCATION_OFFSET + 35, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.SNAPDRAGON, + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dw2g"),), + archipelago_id=LOCATION_OFFSET + 36, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((12892, 1),), + archipelago_id=LOCATION_OFFSET + 37, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT, + ), + ), + ZorkGrandInquisitorLocations.GO_AWAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((10654, 1),), + archipelago_id=LOCATION_OFFSET + 38, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2k"),), + archipelago_id=LOCATION_OFFSET + 39, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM: ZorkGrandInquisitorLocationData( + game_state_trigger=((11082, 1), (11307, 1), (11536, 1)), + archipelago_id=LOCATION_OFFSET + 40, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2j"),), + archipelago_id=LOCATION_OFFSET + 41, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2n"),), + archipelago_id=LOCATION_OFFSET + 42, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((8443, 1),), + archipelago_id=LOCATION_OFFSET + 43, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, + ) + ), + ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 1),), + archipelago_id=LOCATION_OFFSET + 44, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10421, 1),), + archipelago_id=LOCATION_OFFSET + 45, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH, + ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER, + ), + ), + ZorkGrandInquisitorLocations.HEY_FREE_DIRT: ZorkGrandInquisitorLocationData( + game_state_trigger=((11747, 1),), + archipelago_id=LOCATION_OFFSET + 46, + region=ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND, + ZorkGrandInquisitorItems.SHOVEL, + ), + ), + ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 2),), + archipelago_id=LOCATION_OFFSET + 47, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "mt2h"),), + archipelago_id=LOCATION_OFFSET + 48, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 5),), + archipelago_id=LOCATION_OFFSET + 49, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "uh1e"),), + archipelago_id=LOCATION_OFFSET + 50, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3s"),), + archipelago_id=LOCATION_OFFSET + 51, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.IMBUE_BEBURTT: ZorkGrandInquisitorLocationData( + game_state_trigger=((194, 1),), + archipelago_id=LOCATION_OFFSET + 52, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + ), + ZorkGrandInquisitorLocations.IM_COMPLETELY_NUDE: ZorkGrandInquisitorLocationData( + game_state_trigger=((19344, 1),), + archipelago_id=LOCATION_OFFSET + 53, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((13060, 1),), + archipelago_id=LOCATION_OFFSET + 54, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE, + ), + ), + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS: ZorkGrandInquisitorLocationData( + game_state_trigger=((12967, 1),), + archipelago_id=LOCATION_OFFSET + 55, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_IGRAM,), + ), + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE: ZorkGrandInquisitorLocationData( + game_state_trigger=((12931, 1),), + archipelago_id=LOCATION_OFFSET + 56, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE, + ), + ), + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST: ZorkGrandInquisitorLocationData( + game_state_trigger=((13062, 1),), + archipelago_id=LOCATION_OFFSET + 57, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR, + ), + ), + ZorkGrandInquisitorLocations.ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pe3j"),), + archipelago_id=LOCATION_OFFSET + 58, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((3816, 1008),), + archipelago_id=LOCATION_OFFSET + 59, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, + ), + ), + ZorkGrandInquisitorLocations.I_DONT_WANT_NO_TROUBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10694, 1),), + archipelago_id=LOCATION_OFFSET + 60, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9637, 1),), + archipelago_id=LOCATION_OFFSET + 61, + region=ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT, + ), + ), + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((16374, 1),), + archipelago_id=LOCATION_OFFSET + 62, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, + ZorkGrandInquisitorEvents.DAM_DESTROYED, + ZorkGrandInquisitorItems.SPELL_GOLGATEM, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM, + ), + ), + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tp1e"), (9, 87), (1011, 1)), + archipelago_id=LOCATION_OFFSET + 63, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), + ), + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS: ZorkGrandInquisitorLocationData( + game_state_trigger=((4129, 1),), + archipelago_id=LOCATION_OFFSET + 64, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_THROCK,), + ), + ZorkGrandInquisitorLocations.MAGIC_FOREVER: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pc1e"), (10304, 1), (5221, 1)), + archipelago_id=LOCATION_OFFSET + 65, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE, + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_WELL, + ), + ), + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL: ZorkGrandInquisitorLocationData( + game_state_trigger=((2498, (1, 2)),), + archipelago_id=LOCATION_OFFSET + 66, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + (ZorkGrandInquisitorItems.TOTEM_GRIFF, ZorkGrandInquisitorItems.TOTEM_LUCY), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR: ZorkGrandInquisitorLocationData( + game_state_trigger=((8623, 21),), + archipelago_id=LOCATION_OFFSET + 67, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CHARON_CALLED, + ZorkGrandInquisitorItems.SWORD, + ), + ), + ZorkGrandInquisitorLocations.MEAD_LIGHT: ZorkGrandInquisitorLocationData( + game_state_trigger=((10485, 1),), + archipelago_id=LOCATION_OFFSET + 68, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.MEAD_LIGHT, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + ), + ZorkGrandInquisitorLocations.MIKES_PANTS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2p"),), + archipelago_id=LOCATION_OFFSET + 69, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED: ZorkGrandInquisitorLocationData( + game_state_trigger=((4217, 1),), + archipelago_id=LOCATION_OFFSET + 70, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.NATIONAL_TREASURE: ZorkGrandInquisitorLocationData( + game_state_trigger=((14318, 1),), + archipelago_id=LOCATION_OFFSET + 71, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS, + ), + ), + ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dv1p"),), + archipelago_id=LOCATION_OFFSET + 72, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO: ZorkGrandInquisitorLocationData( + game_state_trigger=((12706, 1),), + archipelago_id=LOCATION_OFFSET + 73, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + ), + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4237, 1),), + archipelago_id=LOCATION_OFFSET + 74, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY, + ), + ), + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT: ZorkGrandInquisitorLocationData( + game_state_trigger=((8935, 1),), + archipelago_id=LOCATION_OFFSET + 75, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), + ), + ZorkGrandInquisitorLocations.NO_AUTOGRAPHS: ZorkGrandInquisitorLocationData( + game_state_trigger=((10476, 1),), + archipelago_id=LOCATION_OFFSET + 76, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR,), + ), + ZorkGrandInquisitorLocations.NO_BONDAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pe2e"), (10262, 2), (15150, 83)), + archipelago_id=LOCATION_OFFSET + 77, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH, + ), + ), + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP: ZorkGrandInquisitorLocationData( + game_state_trigger=((12164, 1),), + archipelago_id=LOCATION_OFFSET + 78, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + ), + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=((1300, 1),), + archipelago_id=LOCATION_OFFSET + 79, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ), + ), + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS: ZorkGrandInquisitorLocationData( + game_state_trigger=((2448, 1),), + archipelago_id=LOCATION_OFFSET + 80, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, + ), + ), + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU: ZorkGrandInquisitorLocationData( + game_state_trigger=((4869, 1),), + archipelago_id=LOCATION_OFFSET + 81, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE, + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS, + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB, + ZorkGrandInquisitorItems.MOSS_OF_MAREILON, + ZorkGrandInquisitorItems.MUG, + ), + ), + ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER: ZorkGrandInquisitorLocationData( + game_state_trigger=((4512, 32),), + archipelago_id=LOCATION_OFFSET + 82, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, # This can be done anywhere if the item requirement is met + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), + ), + ZorkGrandInquisitorLocations.ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pe5n"),), + archipelago_id=LOCATION_OFFSET + 83, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL: ZorkGrandInquisitorLocationData( + game_state_trigger=((8730, 1),), + archipelago_id=LOCATION_OFFSET + 84, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), + ), + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES: ZorkGrandInquisitorLocationData( + game_state_trigger=((4241, 1),), + archipelago_id=LOCATION_OFFSET + 85, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, + ), + ), + ZorkGrandInquisitorLocations.PERMASEAL: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "mt1g"),), + archipelago_id=LOCATION_OFFSET + 86, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.PLANETFALL: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pp1j"),), + archipelago_id=LOCATION_OFFSET + 87, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "te1g"),), + archipelago_id=LOCATION_OFFSET + 88, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL: ZorkGrandInquisitorLocationData( + game_state_trigger=((9404, 1),), + archipelago_id=LOCATION_OFFSET + 89, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ), + ), + ZorkGrandInquisitorLocations.PROZORKED: ZorkGrandInquisitorLocationData( + game_state_trigger=((4115, 1),), + archipelago_id=LOCATION_OFFSET + 90, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.PROZORK_TABLET, + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, + ), + ), + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG: ZorkGrandInquisitorLocationData( + game_state_trigger=((4512, 98),), + archipelago_id=LOCATION_OFFSET + 91, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS, + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV, + ZorkGrandInquisitorItems.HOTSPOT_MIRROR, + ), + ), + ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2h"),), + archipelago_id=LOCATION_OFFSET + 92, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 3),), + archipelago_id=LOCATION_OFFSET + 93, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 4),), + archipelago_id=LOCATION_OFFSET + 94, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED: ZorkGrandInquisitorLocationData( + game_state_trigger=((201, 1),), + archipelago_id=LOCATION_OFFSET + 95, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + ), + ZorkGrandInquisitorLocations.SOUVENIR: ZorkGrandInquisitorLocationData( + game_state_trigger=((13408, 1),), + archipelago_id=LOCATION_OFFSET + 96, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT, + ), + ), + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL: ZorkGrandInquisitorLocationData( + game_state_trigger=((9719, 1),), + archipelago_id=LOCATION_OFFSET + 97, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER: ZorkGrandInquisitorLocationData( + game_state_trigger=((14511, 1), (14524, 5)), + archipelago_id=LOCATION_OFFSET + 98, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), + ZorkGrandInquisitorLocations.SUCKING_ROCKS: ZorkGrandInquisitorLocationData( + game_state_trigger=((12859, 1),), + archipelago_id=LOCATION_OFFSET + 99, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE, + ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT, + ), + ), + ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR: ZorkGrandInquisitorLocationData( + game_state_trigger=((10299, 1),), + archipelago_id=LOCATION_OFFSET + 100, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL,), + ), + ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dv1h"),), + archipelago_id=LOCATION_OFFSET + 101, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS: ZorkGrandInquisitorLocationData( + game_state_trigger=((1311, 1), (1312, 1)), + archipelago_id=LOCATION_OFFSET + 102, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ), + ), + ZorkGrandInquisitorLocations.THATS_A_ROPE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10486, 1),), + archipelago_id=LOCATION_OFFSET + 103, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + ), + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS: ZorkGrandInquisitorLocationData( + game_state_trigger=((13805, 1),), + archipelago_id=LOCATION_OFFSET + 104, + region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + ), + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tp1e"), (9, 83), (1011, 1)), + archipelago_id=LOCATION_OFFSET + 105, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorEvents.ROPE_GLORFABLE,), + ), + ZorkGrandInquisitorLocations.THATS_THE_SPIRIT: ZorkGrandInquisitorLocationData( + game_state_trigger=((10341, 95),), + archipelago_id=LOCATION_OFFSET + 106, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS,), + ), + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9459, 1),), + archipelago_id=LOCATION_OFFSET + 107, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9473, 1),), + archipelago_id=LOCATION_OFFSET + 108, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO: ZorkGrandInquisitorLocationData( + game_state_trigger=((9520, 1),), + archipelago_id=LOCATION_OFFSET + 109, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "me1j"),), + archipelago_id=LOCATION_OFFSET + 110, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND: ZorkGrandInquisitorLocationData( + game_state_trigger=((13167, 1),), + archipelago_id=LOCATION_OFFSET + 111, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SUBWAY_TOKEN, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT, + ), + ), + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "cd60"), (1524, 1)), + archipelago_id=LOCATION_OFFSET + 112, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.TOTEM_LUCY,), + ), + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED: ZorkGrandInquisitorLocationData( + game_state_trigger=((4219, 1),), + archipelago_id=LOCATION_OFFSET + 113, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3z"),), + archipelago_id=LOCATION_OFFSET + 114, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.TOTEMIZED_DAILY_BILLBOARD: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "px1h"),), + archipelago_id=LOCATION_OFFSET + 115, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "cd60"), (1520, 1)), + archipelago_id=LOCATION_OFFSET + 116, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.TOTEM_BROG,), + ), + ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS: ZorkGrandInquisitorLocationData( + game_state_trigger=((12926, 1),), + archipelago_id=LOCATION_OFFSET + 117, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_BEBURTT,), + ), + ZorkGrandInquisitorLocations.UP: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 5200),), + archipelago_id=LOCATION_OFFSET + 118, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_LUCY, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.USELESS_BUT_FUN: ZorkGrandInquisitorLocationData( + game_state_trigger=((14321, 1),), + archipelago_id=LOCATION_OFFSET + 119, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,), + ), + ZorkGrandInquisitorLocations.UUUUUP: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 3500),), + archipelago_id=LOCATION_OFFSET + 120, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "uh1h"),), + archipelago_id=LOCATION_OFFSET + 121, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO: ZorkGrandInquisitorLocationData( + game_state_trigger=((4034, 1),), + archipelago_id=LOCATION_OFFSET + 122, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, + ZorkGrandInquisitorItems.MEAD_LIGHT, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH, + ), + ), + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE: ZorkGrandInquisitorLocationData( + game_state_trigger=((2461, 1),), + archipelago_id=LOCATION_OFFSET + 123, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, + ), + ), + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER: ZorkGrandInquisitorLocationData( + game_state_trigger=((15472, 1),), + archipelago_id=LOCATION_OFFSET + 124, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID: ZorkGrandInquisitorLocationData( + game_state_trigger=((10484, 1),), + archipelago_id=LOCATION_OFFSET + 125, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + ), + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL: ZorkGrandInquisitorLocationData( + game_state_trigger=((4983, 1),), + archipelago_id=LOCATION_OFFSET + 126, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ), + ), + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dc10"), (1596, 1)), + archipelago_id=LOCATION_OFFSET + 127, + region=ZorkGrandInquisitorRegions.WALKING_CASTLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dm2g"),), + archipelago_id=LOCATION_OFFSET + 128, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_MIRROR,), + ), + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dg4e"), (4266, 1), (9, 21), (4035, 1)), + archipelago_id=LOCATION_OFFSET + 129, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_HARRY, + ), + ), + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER: ZorkGrandInquisitorLocationData( + game_state_trigger=((16405, 1),), + archipelago_id=LOCATION_OFFSET + 130, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.SPELL_REZROV,), + ), + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS: ZorkGrandInquisitorLocationData( + game_state_trigger=((16342, 1),), + archipelago_id=LOCATION_OFFSET + 131, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, + ), + ), + ZorkGrandInquisitorLocations.YOU_ONE_OF_THEM_AGITATORS_AINT_YA: ZorkGrandInquisitorLocationData( + game_state_trigger=((10586, 1),), + archipelago_id=LOCATION_OFFSET + 132, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY: ZorkGrandInquisitorLocationData( + game_state_trigger=((15151, 1),), + archipelago_id=LOCATION_OFFSET + 133, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH,), + ), + # Deathsanity + ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 1)), + archipelago_id=LOCATION_OFFSET + 200 + 0, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 20)), + archipelago_id=LOCATION_OFFSET + 200 + 1, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, + ), + ), + ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 21)), + archipelago_id=LOCATION_OFFSET + 200 + 2, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + ), + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 18)), + archipelago_id=LOCATION_OFFSET + 200 + 3, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_WELL, + ), + ), + ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 3)), + archipelago_id=LOCATION_OFFSET + 200 + 4, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + ), + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 37)), + archipelago_id=LOCATION_OFFSET + 200 + 5, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), + ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 23)), + archipelago_id=LOCATION_OFFSET + 200 + 6, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), + ), + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 29)), + archipelago_id=LOCATION_OFFSET + 200 + 7, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, + ), + ), + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 30)), + archipelago_id=LOCATION_OFFSET + 200 + 8, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + ), + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 4)), + archipelago_id=LOCATION_OFFSET + 200 + 9, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SPELL_IGRAM, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ), + ), + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 11)), + archipelago_id=LOCATION_OFFSET + 200 + 10, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ), + ), + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 34)), + archipelago_id=LOCATION_OFFSET + 200 + 11, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + requirements=( + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS, + ), + ), + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, (9, 32, 33))), + archipelago_id=LOCATION_OFFSET + 200 + 12, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, (5, 6, 7, 8, 13))), + archipelago_id=LOCATION_OFFSET + 200 + 13, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH,), + ), + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 10)), + archipelago_id=LOCATION_OFFSET + 200 + 14, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), + ), + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 19)), + archipelago_id=LOCATION_OFFSET + 200 + 15, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED,), + ), + # Events + ZorkGrandInquisitorEvents.CHARON_CALLED: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ), + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + requirements=( + ZorkGrandInquisitorItems.LANTERN, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + event_item_name=ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, + ), + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ), + ZorkGrandInquisitorEvents.DAM_DESTROYED: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + requirements=( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ), + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR, + requirements=( + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, + ZorkGrandInquisitorItems.MEAD_LIGHT, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH, + ), + event_item_name=ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ), + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR, + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY, + ), + event_item_name=ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ), + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ), + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS, + ), + event_item_name=ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ), + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + requirements=( + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS, + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV, + ZorkGrandInquisitorItems.HOTSPOT_MIRROR, + ), + event_item_name=ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ), + ZorkGrandInquisitorEvents.KNOWS_BEBURTT: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ), + ZorkGrandInquisitorEvents.KNOWS_OBIDIL: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ), + ZorkGrandInquisitorEvents.KNOWS_SNAVIG: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ), + ZorkGrandInquisitorEvents.KNOWS_YASTARD: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + requirements=( + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE, + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS, + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB, + ZorkGrandInquisitorItems.MOSS_OF_MAREILON, + ZorkGrandInquisitorItems.MUG, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ), + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + event_item_name=ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE.value, + ), + ZorkGrandInquisitorEvents.ROPE_GLORFABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.CROSSROADS, + requirements=(ZorkGrandInquisitorItems.SPELL_GLORF,), + event_item_name=ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ), + ZorkGrandInquisitorEvents.VICTORY: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.ENDGAME, + event_item_name=ZorkGrandInquisitorEvents.VICTORY.value, + ), + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + requirements=( + (ZorkGrandInquisitorItems.TOTEM_GRIFF, ZorkGrandInquisitorItems.TOTEM_LUCY), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR, + ), + event_item_name=ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ), + ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), + event_item_name=ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE.value, + ), + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.ZORK_ROCKS, + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ), + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ), +} diff --git a/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py b/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py new file mode 100644 index 000000000000..ef6eacb78ceb --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py @@ -0,0 +1,200 @@ +from typing import Dict, NamedTuple, Optional, Tuple + +from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations + + +class ZorkGrandInquisitorMissableLocationGrantConditionsData(NamedTuple): + location_condition: ZorkGrandInquisitorLocations + item_conditions: Optional[Tuple[ZorkGrandInquisitorItems, ...]] + + +missable_location_grant_conditions_data: Dict[ + ZorkGrandInquisitorLocations, ZorkGrandInquisitorMissableLocationGrantConditionsData +] = { + ZorkGrandInquisitorLocations.BOING_BOING_BOING: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.BONK: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.PROZORKED, + item_conditions=(ZorkGrandInquisitorItems.HAMMER,), + ) + , + ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.MAGIC_FOREVER, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.A_SMALLWAY, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.THAR_SHE_BLOWS, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.CRISIS_AVERTED, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE, + item_conditions=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,), + ) + , + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.SPELL_IGRAM,), + ) + , + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.PROZORKED, + item_conditions=(ZorkGrandInquisitorItems.SPELL_THROCK,), + ) + , + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), + ) + , + ZorkGrandInquisitorLocations.MEAD_LIGHT: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=(ZorkGrandInquisitorItems.MEAD_LIGHT,), + ) + , + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.NO_AUTOGRAPHS: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.NO_BONDAGE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE, + item_conditions=(ZorkGrandInquisitorItems.ROPE,), + ) + , + ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.THATS_A_ROPE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=(ZorkGrandInquisitorItems.ROPE,), + ) + , + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.SPELL_GLORF,), + ) + , + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER,), + ) + , + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO, + item_conditions=(ZorkGrandInquisitorItems.SWORD, ZorkGrandInquisitorItems.HOTSPOT_HARRY), + ) + , + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.SPELL_REZROV,), + ) + , + ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE, + item_conditions=None, + ) + , +} diff --git a/worlds/zork_grand_inquisitor/data/region_data.py b/worlds/zork_grand_inquisitor/data/region_data.py new file mode 100644 index 000000000000..1aed160f3088 --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/region_data.py @@ -0,0 +1,183 @@ +from typing import Dict, NamedTuple, Optional, Tuple + +from ..enums import ZorkGrandInquisitorRegions + + +class ZorkGrandInquisitorRegionData(NamedTuple): + exits: Optional[Tuple[ZorkGrandInquisitorRegions, ...]] + + +region_data: Dict[ZorkGrandInquisitorRegions, ZorkGrandInquisitorRegionData] = { + ZorkGrandInquisitorRegions.CROSSROADS: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.PORT_FOOZLE, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.DM_LAIR: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.WALKING_CASTLE, + ZorkGrandInquisitorRegions.WHITE_HOUSE, + ) + ), + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, + ) + ), + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + ZorkGrandInquisitorRegions.ENDGAME, + ) + ), + ZorkGrandInquisitorRegions.ENDGAME: ZorkGrandInquisitorRegionData(exits=None), + ZorkGrandInquisitorRegions.GUE_TECH: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ) + ), + ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.GUE_TECH, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ) + ), + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.HADES: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, + ZorkGrandInquisitorRegions.HADES_SHORE, + ) + ), + ZorkGrandInquisitorRegions.HADES_BEYOND_GATES: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + ZorkGrandInquisitorRegions.HADES, + ) + ), + ZorkGrandInquisitorRegions.HADES_SHORE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.HADES, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.MENU: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,) + ), + ZorkGrandInquisitorRegions.MONASTERY: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.MONASTERY, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + ) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, + ) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + ) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.ENDGAME, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + ) + ), + ZorkGrandInquisitorRegions.SPELL_LAB: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,) + ), + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SPELL_LAB, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.MONASTERY, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + ) + ), + ZorkGrandInquisitorRegions.WALKING_CASTLE: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR,) + ), + ZorkGrandInquisitorRegions.WHITE_HOUSE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + ZorkGrandInquisitorRegions.ENDGAME, + ) + ), +} diff --git a/worlds/zork_grand_inquisitor/data_funcs.py b/worlds/zork_grand_inquisitor/data_funcs.py new file mode 100644 index 000000000000..2a7bff1fbb6b --- /dev/null +++ b/worlds/zork_grand_inquisitor/data_funcs.py @@ -0,0 +1,247 @@ +from typing import Dict, List, Set, Tuple, Union + +from .data.entrance_rule_data import entrance_rule_data +from .data.item_data import item_data, ZorkGrandInquisitorItemData +from .data.location_data import location_data, ZorkGrandInquisitorLocationData + +from .enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorGoals, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorTags, +) + + +def item_names_to_id() -> Dict[str, int]: + return {item.value: data.archipelago_id for item, data in item_data.items()} + + +def item_names_to_item() -> Dict[str, ZorkGrandInquisitorItems]: + return {item.value: item for item in item_data} + + +def location_names_to_id() -> Dict[str, int]: + return { + location.value: data.archipelago_id + for location, data in location_data.items() + if data.archipelago_id is not None + } + + +def location_names_to_location() -> Dict[str, ZorkGrandInquisitorLocations]: + return { + location.value: location + for location, data in location_data.items() + if data.archipelago_id is not None + } + + +def id_to_goals() -> Dict[int, ZorkGrandInquisitorGoals]: + return {goal.value: goal for goal in ZorkGrandInquisitorGoals} + + +def id_to_items() -> Dict[int, ZorkGrandInquisitorItems]: + return {data.archipelago_id: item for item, data in item_data.items()} + + +def id_to_locations() -> Dict[int, ZorkGrandInquisitorLocations]: + return { + data.archipelago_id: location + for location, data in location_data.items() + if data.archipelago_id is not None + } + + +def item_groups() -> Dict[str, List[str]]: + groups: Dict[str, List[str]] = dict() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + if data.tags is not None: + for tag in data.tags: + groups.setdefault(tag.value, list()).append(item.value) + + return {k: v for k, v in groups.items() if len(v)} + + +def items_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorItems]: + items: Set[ZorkGrandInquisitorItems] = set() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + if data.tags is not None and tag in data.tags: + items.add(item) + + return items + + +def game_id_to_items() -> Dict[int, ZorkGrandInquisitorItems]: + mapping: Dict[int, ZorkGrandInquisitorItems] = dict() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + if data.statemap_keys is not None: + for key in data.statemap_keys: + mapping[key] = item + + return mapping + + +def location_groups() -> Dict[str, List[str]]: + groups: Dict[str, List[str]] = dict() + + tag: ZorkGrandInquisitorTags + for tag in ZorkGrandInquisitorTags: + groups[tag.value] = list() + + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + for location, data in location_data.items(): + if data.tags is not None: + for tag in data.tags: + groups[tag.value].append(location.value) + + return {k: v for k, v in groups.items() if len(v)} + + +def locations_by_region(include_deathsanity: bool = False) -> Dict[ + ZorkGrandInquisitorRegions, List[ZorkGrandInquisitorLocations] +]: + mapping: Dict[ZorkGrandInquisitorRegions, List[ZorkGrandInquisitorLocations]] = dict() + + region: ZorkGrandInquisitorRegions + for region in ZorkGrandInquisitorRegions: + mapping[region] = list() + + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + for location, data in location_data.items(): + if not include_deathsanity and ZorkGrandInquisitorTags.DEATHSANITY in ( + data.tags or tuple() + ): + continue + + mapping[data.region].append(location) + + return mapping + + +def locations_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorLocations]: + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + + return {location for location, data in location_data.items() if data.tags is not None and tag in data.tags} + + +def location_access_rule_for(location: ZorkGrandInquisitorLocations, player: int) -> str: + data: ZorkGrandInquisitorLocationData = location_data[location] + + if data.requirements is None: + return "lambda state: True" + + lambda_string: str = "lambda state: " + + i: int + requirement: Union[ + Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ], + ..., + ], + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems + ] + + for i, requirement in enumerate(data.requirements): + if isinstance(requirement, tuple): + lambda_string += "(" + + ii: int + sub_requirement: Union[ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems] + for ii, sub_requirement in enumerate(requirement): + lambda_string += f"state.has(\"{sub_requirement.value}\", {player})" + + if ii < len(requirement) - 1: + lambda_string += " or " + + lambda_string += ")" + else: + lambda_string += f"state.has(\"{requirement.value}\", {player})" + + if i < len(data.requirements) - 1: + lambda_string += " and " + + return lambda_string + + +def entrance_access_rule_for( + region_origin: ZorkGrandInquisitorRegions, + region_destination: ZorkGrandInquisitorRegions, + player: int +) -> str: + data: Union[ + Tuple[ + Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ], + ..., + ], + ..., + ], + None, + ] = entrance_rule_data[(region_origin, region_destination)] + + if data is None: + return "lambda state: True" + + lambda_string: str = "lambda state: " + + i: int + requirement_group: Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ], + ..., + ] + for i, requirement_group in enumerate(data): + lambda_string += "(" + + ii: int + requirement: Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ] + for ii, requirement in enumerate(requirement_group): + requirement_type: Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ] = type(requirement) + + if requirement_type in (ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems): + lambda_string += f"state.has(\"{requirement.value}\", {player})" + elif requirement_type == ZorkGrandInquisitorRegions: + lambda_string += f"state.can_reach(\"{requirement.value}\", \"Region\", {player})" + + if ii < len(requirement_group) - 1: + lambda_string += " and " + + lambda_string += ")" + + if i < len(data) - 1: + lambda_string += " or " + + return lambda_string diff --git a/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md b/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md new file mode 100644 index 000000000000..d5821914beca --- /dev/null +++ b/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md @@ -0,0 +1,102 @@ +# Zork Grand Inquisitor + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a +configuration file. + +## Is a tracker available for this game? + +Yes! You can download the latest PopTracker pack for Zork Grand Inquisitor [here](https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker/releases/latest). + +## What does randomization do to this game? + +A majority of inventory items you can normally pick up are completely removed from the game (e.g. the lantern won't be +in the crate, the mead won't be at the fish market, etc.). Instead, these items will be distributed in the multiworld. +This means that you can expect to access areas and be in a position to solve certain puzzles in a completely different +order than you normally would. + +Subway, teleporter and totemizer destinations are initially locked and need to be unlocked by receiving the +corresponding item in the multiworld. This alone enables creative routing in a game that would otherwise be rather +linear. The Crossroads destination is always unlocked for both the subway and teleporter to prevent softlocks. Until you +receive your first totemizer destination, it will be locked to Newark, New Jersey. + +Important hotspots are also randomized. This means that you will be unable to interact with certain objects until you +receive the corresponding item in the multiworld. This can be a bit confusing at first, but it adds depth to the +randomization and makes the game more interesting to play. + +You can travel back to the surface without dying by looking inside the bucket. This will work as long as the rope is +still attached to the well. + +Attempting to cast VOXAM will teleport you back to the Crossroads. Fast Travel! + +## What item types are distributed in the multiworld? + +- Inventory items +- Pouch of Zorkmids +- Spells +- Totems +- Subway destinations +- Teleporter destinations +- Totemizer destinations +- Hotspots (with option to start with the items enabling them instead if you prefer not playing with the randomization + of hotspots) + +## When the player receives an item, what happens? + +- **Inventory items**: Directly added to the player's inventory. +- **Pouch of Zorkmids**: Appears on the inventory screen. The player can then pick up Zorkmid coins from it. +- **Spells**: Learned and directly added to the spell book. +- **Totems**: Appears on the inventory screen. +- **Subway destinations**: The destination button on the subway map becomes functional. +- **Teleporter destinations**: The destination can show up on the teleporter screen. +- **Totemizer destinations**: The destination button on the panel becomes functional. +- **Hotspots**: The hotspot becomes interactable. + +## What is considered a location check in Zork Grand Inquisitor? + +- Solving puzzles +- Accessing certain areas for the first time +- Triggering certain interactions, even if they aren't puzzles per se +- Dying in unique ways (Optional; Deathsanity option) + +## The location check names are fun but don't always convey well what's needed to unlock them. Is there a guide? + +Yes! You can find a complete guide for the location checks [here](https://gist.github.com/nbrochu/f7bed7a1fef4e2beb67ad6ddbf18b970). + +## What is the victory condition? + +Victory is achieved when the 3 artifacts of magic are retrieved and placed inside the walking castle. + +## Can I use the save system without a problem? + +Absolutely! The save system is fully supported (and its use is in fact strongly encouraged!). You can save and load your +game as you normally would and the client will automatically sync your items and hotspots with what you should have in +that game state. + +Depending on how your game progresses, there's a chance that certain location checks might become missable. This +presents an excellent opportunity to utilize the save system. Simply make it a habit to save before undertaking +irreversible actions, ensuring you can revert to a previous state if necessary. If you prefer not to depend on the save +system for accessing missable location checks, there's an option to automatically unlock them as they become +unavailable. + +## Unique Local Commands +The following commands are only available when using the Zork Grand Inquisitor Client to play the game with Archipelago. + +- `/zork` Attempts to attach to a running instance of Zork Grand Inquisitor. If successful, the client will then be able + to read and control the state of the game. +- `/brog` Lists received items for Brog. +- `/griff` Lists received items for Griff. +- `/lucy` Lists received items for Lucy. +- `/hotspots` Lists received hotspots. + +## Known issues + +- You will get a second rope right after using GLORF (one in your inventory and one on your cursor). This is a harmless + side effect that will go away after you store it in your inventory as duplicates are actively removed. +- After climbing up to the Monastery for the first time, a rope will forever remain in place in the vent. When you come + back to the Monastery, you will be able to climb up without needing to combine the sword and rope again. However, when + arriving at the top, you will receive a duplicate sword on a rope. This is a harmless side effect that will go away + after you store it in your inventory as duplicates are actively removed. +- Since the client is reading and manipulating the game's memory, rare game crashes can happen. If you encounter one, + simply restart the game, load your latest save and use the `/zork` command again in the client. Nothing will be lost. diff --git a/worlds/zork_grand_inquisitor/docs/setup_en.md b/worlds/zork_grand_inquisitor/docs/setup_en.md new file mode 100644 index 000000000000..f9078c6d39ba --- /dev/null +++ b/worlds/zork_grand_inquisitor/docs/setup_en.md @@ -0,0 +1,42 @@ +# Zork Grand Inquisitor Randomizer Setup Guide + +## Requirements + +- Windows OS (Hard required. Client is using memory reading / writing through Win32 API) +- A copy of Zork Grand Inquisitor. Only the GOG version is supported. The Steam version can work with some tinkering but + is not officially supported. +- ScummVM 2.7.1 64-bit (Important: Will not work with any other version. [Direct Download](https://downloads.scummvm.org/frs/scummvm/2.7.1/scummvm-2.7.1-win32-x86_64.zip)) +- Archipelago 0.4.4+ + +## Game Setup Instructions + +No game modding is required to play Zork Grand Inquisitor with Archipelago. The client does all the work by attaching to +the game process and reading and manipulating the game state in real-time. + +This being said, the game does need to be played through ScummVM 2.7.1, so some configuration is required around that. + +### GOG + +- Open the directory where you installed Zork Grand Inquisitor. You should see a `Launch Zork Grand Inquisitor` + shortcut. +- Open the `scummvm` directory. Delete the entire contents of that directory. +- Still inside the `scummvm` directory, unzip the contents of the ScummVM 2.7.1 zip file you downloaded earlier. +- Go back to the directory where you installed Zork Grand Inquisitor. +- Verify that the game still launches when using the `Launch Zork Grand Inquisitor` shortcut. +- Your game is now ready to be played with Archipelago. From now on, you can use the `Launch Zork Grand Inquisitor` + shortcut to launch the game. + +## Joining a Multiworld Game + +- Launch Zork Grand Inquisitor and start a new game. +- Open the Archipelago Launcher and click `Zork Grand Inquisitor Client`. +- Using the `Zork Grand Inquisitor Client`: + - Enter the room's hostname and port number (e.g. `archipelago.gg:54321`) in the top box and press `Connect`. + - Input your player name at the bottom when prompted and press `Enter`. + - You should now be connected to the Archipelago room. + - Next, input `/zork` at the bottom and press `Enter`. This will attach the client to the game process. + - If the command is successful, you are now ready to play Zork Grand Inquisitor with Archipelago. + +## Continuing a Multiworld Game + +- Perform the same steps as above, but instead of starting a new game, load your latest save file. diff --git a/worlds/zork_grand_inquisitor/enums.py b/worlds/zork_grand_inquisitor/enums.py new file mode 100644 index 000000000000..ecbb38a949b4 --- /dev/null +++ b/worlds/zork_grand_inquisitor/enums.py @@ -0,0 +1,350 @@ +import enum + + +class ZorkGrandInquisitorEvents(enum.Enum): + CHARON_CALLED = "Event: Charon Called" + CIGAR_ACCESSIBLE = "Event: Cigar Accessible" + DALBOZ_LOCKER_OPENABLE = "Event: Dalboz Locker Openable" + DAM_DESTROYED = "Event: Dam Destroyed" + DOOR_DRANK_MEAD = "Event: Door Drank Mead" + DOOR_SMOKED_CIGAR = "Event: Door Smoked Cigar" + DUNCE_LOCKER_OPENABLE = "Event: Dunce Locker Openable" + HAS_REPAIRABLE_OBIDIL = "Event: Has Repairable OBIDIL" + HAS_REPAIRABLE_SNAVIG = "Event: Has Repairable SNAVIG" + KNOWS_BEBURTT = "Event: Knows BEBURTT" + KNOWS_OBIDIL = "Event: Knows OBIDIL" + KNOWS_SNAVIG = "Event: Knows SNAVIG" + KNOWS_YASTARD = "Event: Knows YASTARD" + LANTERN_DALBOZ_ACCESSIBLE = "Event: Lantern (Dalboz) Accessible" + ROPE_GLORFABLE = "Event: Rope GLORFable" + VICTORY = "Victory" + WHITE_HOUSE_LETTER_MAILABLE = "Event: White House Letter Mailable" + ZORKMID_BILL_ACCESSIBLE = "Event: 500 Zorkmid Bill Accessible" + ZORK_ROCKS_ACTIVATED = "Event: Zork Rocks Activated" + ZORK_ROCKS_SUCKABLE = "Event: Zork Rocks Suckable" + + +class ZorkGrandInquisitorGoals(enum.Enum): + THREE_ARTIFACTS = 0 + + +class ZorkGrandInquisitorItems(enum.Enum): + BROGS_BICKERING_TORCH = "Brog's Bickering Torch" + BROGS_FLICKERING_TORCH = "Brog's Flickering Torch" + BROGS_GRUE_EGG = "Brog's Grue Egg" + BROGS_PLANK = "Brog's Plank" + FILLER_FROBOZZ_ELECTRIC_GADGET = "Frobozz Electric Gadget" + FILLER_INQUISITION_PROPAGANDA_FLYER = "Inquisition Propaganda Flyer" + FILLER_MAGIC_CONTRABAND = "Magic Contraband" + FILLER_NONSENSICAL_INQUISITION_PAPERWORK = "Nonsensical Inquisition Paperwork" + FILLER_UNREADABLE_SPELL_SCROLL = "Unreadable Spell Scroll" + FLATHEADIA_FUDGE = "Flatheadia Fudge" + GRIFFS_AIR_PUMP = "Griff's Air Pump" + GRIFFS_DRAGON_TOOTH = "Griff's Dragon Tooth" + GRIFFS_INFLATABLE_RAFT = "Griff's Inflatable Raft" + GRIFFS_INFLATABLE_SEA_CAPTAIN = "Griff's Inflatable Sea Captain" + HAMMER = "Hammer" + HOTSPOT_666_MAILBOX = "Hotspot: 666 Mailbox" + HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS = "Hotspot: Alpine's Quandry Card Slots" + HOTSPOT_BLANK_SCROLL_BOX = "Hotspot: Blank Scroll Box" + HOTSPOT_BLINDS = "Hotspot: Blinds" + HOTSPOT_CANDY_MACHINE_BUTTONS = "Hotspot: Candy Machine Buttons" + HOTSPOT_CANDY_MACHINE_COIN_SLOT = "Hotspot: Candy Machine Coin Slot" + HOTSPOT_CANDY_MACHINE_VACUUM_SLOT = "Hotspot: Candy Machine Vacuum Slot" + HOTSPOT_CHANGE_MACHINE_SLOT = "Hotspot: Change Machine Slot" + HOTSPOT_CLOSET_DOOR = "Hotspot: Closet Door" + HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT = "Hotspot: Closing the Time Tunnels Hammer Slot" + HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER = "Hotspot: Closing the Time Tunnels Lever" + HOTSPOT_COOKING_POT = "Hotspot: Cooking Pot" + HOTSPOT_DENTED_LOCKER = "Hotspot: Dented Locker" + HOTSPOT_DIRT_MOUND = "Hotspot: Dirt Mound" + HOTSPOT_DOCK_WINCH = "Hotspot: Dock Winch" + HOTSPOT_DRAGON_CLAW = "Hotspot: Dragon Claw" + HOTSPOT_DRAGON_NOSTRILS = "Hotspot: Dragon Nostrils" + HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE = "Hotspot: Dungeon Master's Lair Entrance" + HOTSPOT_FLOOD_CONTROL_BUTTONS = "Hotspot: Flood Control Buttons" + HOTSPOT_FLOOD_CONTROL_DOORS = "Hotspot: Flood Control Doors" + HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT = "Hotspot: Frozen Treat Machine Coin Slot" + HOTSPOT_FROZEN_TREAT_MACHINE_DOORS = "Hotspot: Frozen Treat Machine Doors" + HOTSPOT_GLASS_CASE = "Hotspot: Glass Case" + HOTSPOT_GRAND_INQUISITOR_DOLL = "Hotspot: Grand Inquisitor Doll" + HOTSPOT_GUE_TECH_DOOR = "Hotspot: GUE Tech Door" + HOTSPOT_GUE_TECH_GRASS = "Hotspot: GUE Tech Grass" + HOTSPOT_HADES_PHONE_BUTTONS = "Hotspot: Hades Phone Buttons" + HOTSPOT_HADES_PHONE_RECEIVER = "Hotspot: Hades Phone Receiver" + HOTSPOT_HARRY = "Hotspot: Harry" + HOTSPOT_HARRYS_ASHTRAY = "Hotspot: Harry's Ashtray" + HOTSPOT_HARRYS_BIRD_BATH = "Hotspot: Harry's Bird Bath" + HOTSPOT_IN_MAGIC_WE_TRUST_DOOR = "Hotspot: In Magic We Trust Door" + HOTSPOT_JACKS_DOOR = "Hotspot: Jack's Door" + HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS = "Hotspot: Loudspeaker Volume Buttons" + HOTSPOT_MAILBOX_DOOR = "Hotspot: Mailbox Door" + HOTSPOT_MAILBOX_FLAG = "Hotspot: Mailbox Flag" + HOTSPOT_MIRROR = "Hotspot: Mirror" + HOTSPOT_MONASTERY_VENT = "Hotspot: Monastery Vent" + HOTSPOT_MOSSY_GRATE = "Hotspot: Mossy Grate" + HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR = "Hotspot: Port Foozle Past Tavern Door" + HOTSPOT_PURPLE_WORDS = "Hotspot: Purple Words" + HOTSPOT_QUELBEE_HIVE = "Hotspot: Quelbee Hive" + HOTSPOT_ROPE_BRIDGE = "Hotspot: Rope Bridge" + HOTSPOT_SKULL_CAGE = "Hotspot: Skull Cage" + HOTSPOT_SNAPDRAGON = "Hotspot: Snapdragon" + HOTSPOT_SODA_MACHINE_BUTTONS = "Hotspot: Soda Machine Buttons" + HOTSPOT_SODA_MACHINE_COIN_SLOT = "Hotspot: Soda Machine Coin Slot" + HOTSPOT_SOUVENIR_COIN_SLOT = "Hotspot: Souvenir Coin Slot" + HOTSPOT_SPELL_CHECKER = "Hotspot: Spell Checker" + HOTSPOT_SPELL_LAB_CHASM = "Hotspot: Spell Lab Chasm" + HOTSPOT_SPRING_MUSHROOM = "Hotspot: Spring Mushroom" + HOTSPOT_STUDENT_ID_MACHINE = "Hotspot: Student ID Machine" + HOTSPOT_SUBWAY_TOKEN_SLOT = "Hotspot: Subway Token Slot" + HOTSPOT_TAVERN_FLY = "Hotspot: Tavern Fly" + HOTSPOT_TOTEMIZER_SWITCH = "Hotspot: Totemizer Switch" + HOTSPOT_TOTEMIZER_WHEELS = "Hotspot: Totemizer Wheels" + HOTSPOT_WELL = "Hotspot: Well" + HUNGUS_LARD = "Hungus Lard" + JAR_OF_HOTBUGS = "Jar of Hotbugs" + LANTERN = "Lantern" + LARGE_TELEGRAPH_HAMMER = "Large Telegraph Hammer" + LUCYS_PLAYING_CARD_1 = "Lucy's Playing Card: 1 Pip" + LUCYS_PLAYING_CARD_2 = "Lucy's Playing Card: 2 Pips" + LUCYS_PLAYING_CARD_3 = "Lucy's Playing Card: 3 Pips" + LUCYS_PLAYING_CARD_4 = "Lucy's Playing Card: 4 Pips" + MAP = "Map" + MEAD_LIGHT = "Mead Light" + MOSS_OF_MAREILON = "Moss of Mareilon" + MUG = "Mug" + OLD_SCRATCH_CARD = "Old Scratch Card" + PERMA_SUCK_MACHINE = "Perma-Suck Machine" + PLASTIC_SIX_PACK_HOLDER = "Plastic Six-Pack Holder" + POUCH_OF_ZORKMIDS = "Pouch of Zorkmids" + PROZORK_TABLET = "Prozork Tablet" + QUELBEE_HONEYCOMB = "Quelbee Honeycomb" + ROPE = "Rope" + SCROLL_FRAGMENT_ANS = "Scroll Fragment: ANS" + SCROLL_FRAGMENT_GIV = "Scroll Fragment: GIV" + SHOVEL = "Shovel" + SNAPDRAGON = "Snapdragon" + SPELL_GLORF = "Spell: GLORF" + SPELL_GOLGATEM = "Spell: GOLGATEM" + SPELL_IGRAM = "Spell: IGRAM" + SPELL_KENDALL = "Spell: KENDALL" + SPELL_NARWILE = "Spell: NARWILE" + SPELL_REZROV = "Spell: REZROV" + SPELL_THROCK = "Spell: THROCK" + SPELL_VOXAM = "Spell: VOXAM" + STUDENT_ID = "Student ID" + SUBWAY_DESTINATION_FLOOD_CONTROL_DAM = "Subway Destination: Flood Control Dam #3" + SUBWAY_DESTINATION_HADES = "Subway Destination: Hades" + SUBWAY_DESTINATION_MONASTERY = "Subway Destination: Monastery" + SUBWAY_TOKEN = "Subway Token" + SWORD = "Sword" + TELEPORTER_DESTINATION_DM_LAIR = "Teleporter Destination: Dungeon Master's Lair" + TELEPORTER_DESTINATION_GUE_TECH = "Teleporter Destination: GUE Tech" + TELEPORTER_DESTINATION_HADES = "Teleporter Destination: Hades" + TELEPORTER_DESTINATION_MONASTERY = "Teleporter Destination: Monastery Station" + TELEPORTER_DESTINATION_SPELL_LAB = "Teleporter Destination: Spell Lab" + TOTEM_BROG = "Totem: Brog" + TOTEM_GRIFF = "Totem: Griff" + TOTEM_LUCY = "Totem: Lucy" + TOTEMIZER_DESTINATION_HALL_OF_INQUISITION = "Totemizer Destination: Hall of Inquisition" + TOTEMIZER_DESTINATION_INFINITY = "Totemizer Destination: Infinity" + TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL = "Totemizer Destination: Straight to Hell" + TOTEMIZER_DESTINATION_SURFACE_OF_MERZ = "Totemizer Destination: Surface of Merz" + ZIMDOR_SCROLL = "ZIMDOR Scroll" + ZORK_ROCKS = "Zork Rocks" + + +class ZorkGrandInquisitorLocations(enum.Enum): + ALARM_SYSTEM_IS_DOWN = "Alarm System is Down" + ARREST_THE_VANDAL = "Arrest the Vandal!" + ARTIFACTS_EXPLAINED = "Artifacts, Explained" + A_BIG_FAT_SASSY_2_HEADED_MONSTER = "A Big, Fat, SASSY 2-Headed Monster" + A_LETTER_FROM_THE_WHITE_HOUSE = "A Letter from the White House" + A_SMALLWAY = "A Smallway" + BEAUTIFUL_THATS_PLENTY = "Beautiful, That's Plenty!" + BEBURTT_DEMYSTIFIED = "BEBURTT, Demystified" + BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES = "Better Spell Manufacturing in Under 10 Minutes" + BOING_BOING_BOING = "Boing, Boing, Boing" + BONK = "Bonk!" + BRAVE_SOULS_WANTED = "Brave Souls Wanted" + BROG_DO_GOOD = "Brog Do Good!" + BROG_EAT_ROCKS = "Brog Eat Rocks" + BROG_KNOW_DUMB_THAT_DUMB = "Brog Know Dumb. That Dumb" + BROG_MUCH_BETTER_AT_THIS_GAME = "Brog Much Better at This Game" + CASTLE_WATCHING_A_FIELD_GUIDE = "Castle Watching: A Field Guide" + CAVES_NOTES = "Cave's Notes" + CLOSING_THE_TIME_TUNNELS = "Closing the Time Tunnels" + CRISIS_AVERTED = "Crisis Averted" + CUT_THAT_OUT_YOU_LITTLE_CREEP = "Cut That Out You Little Creep!" + DEATH_ARRESTED_WITH_JACK = "Death: Arrested With Jack" + DEATH_ATTACKED_THE_QUELBEES = "Death: Attacked the Quelbees" + DEATH_CLIMBED_OUT_OF_THE_WELL = "Death: Climbed Out of the Well" + DEATH_EATEN_BY_A_GRUE = "Death: Eaten by a Grue" + DEATH_JUMPED_IN_BOTTOMLESS_PIT = "Death: Jumped in Bottomless Pit" + DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER = "Death: Lost Game of Strip Grue, Fire, Water" + DEATH_LOST_SOUL_TO_OLD_SCRATCH = "Death: Lost Soul to Old Scratch" + DEATH_OUTSMARTED_BY_THE_QUELBEES = "Death: Outsmarted by the Quelbees" + DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD = "Death: Sliced up by the Invisible Guard" + DEATH_STEPPED_INTO_THE_INFINITE = "Death: Step Into the Infinite" + DEATH_SWALLOWED_BY_A_DRAGON = "Death: Swallowed by a Dragon" + DEATH_THROCKED_THE_GRASS = "Death: THROCKed the Grass" + DEATH_TOTEMIZED = "Death: Totemized?" + DEATH_TOTEMIZED_PERMANENTLY = "Death: Totemized... Permanently" + DEATH_YOURE_NOT_CHARON = "Death: You're Not Charon!?" + DEATH_ZORK_ROCKS_EXPLODED = "Death: Zork Rocks Exploded" + DENIED_BY_THE_LAKE_MONSTER = "Denied by the Lake Monster" + DESPERATELY_SEEKING_TUTOR = "Desperately Seeking Tutor" + DONT_EVEN_START_WITH_US_SPARKY = "Don't Even Start With Us, Sparky" + DOOOOOOWN = "Doooooown" + DOWN = "Down" + DRAGON_ARCHIPELAGO_TIME_TUNNEL = "Dragon Archipelago Time Tunnel" + DUNCE_LOCKER = "Dunce Locker" + EGGPLANTS = "Eggplants" + ELSEWHERE = "Elsewhere" + EMERGENCY_MAGICATRONIC_MESSAGE = "Emergency Magicatronic Message" + ENJOY_YOUR_TRIP = "Enjoy Your Trip!" + FAT_LOT_OF_GOOD_THATLL_DO_YA = "Fat Lot of Good That'll Do Ya" + FIRE_FIRE = "Fire! Fire!" + FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE = "Flood Control Dam #3: The Not Remotely Boring Tale" + FLYING_SNAPDRAGON = "Flying Snapdragon" + FROBUARY_3_UNDERGROUNDHOG_DAY = "Frobruary 3 - Undergroundhog Day" + GETTING_SOME_CHANGE = "Getting Some Change" + GO_AWAY = "GO AWAY!" + GUE_TECH_DEANS_LIST = "GUE Tech Dean's List" + GUE_TECH_ENTRANCE_EXAM = "GUE Tech Entrance Exam" + GUE_TECH_HEALTH_MEMO = "GUE Tech Health Memo" + GUE_TECH_MAGEMEISTERS = "GUE Tech Magemeisters" + HAVE_A_HELL_OF_A_DAY = "Have a Hell of a Day!" + HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING = "Hello, This is Shona from Gurth Publishing" + HELP_ME_CANT_BREATHE = "Help... Me. Can't... Breathe" + HEY_FREE_DIRT = "Hey, Free Dirt!" + HI_MY_NAME_IS_DOUG = "Hi, My Name is Doug" + HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING = "Hmmm. Informative. Yet Deeply Disturbing" + HOLD_ON_FOR_AN_IMPORTANT_MESSAGE = "Hold on for an Important Message" + HOW_TO_HYPNOTIZE_YOURSELF = "How to Hypnotize Yourself" + HOW_TO_WIN_AT_DOUBLE_FANUCCI = "How to Win at Double Fanucci" + IMBUE_BEBURTT = "Imbue BEBURTT" + IM_COMPLETELY_NUDE = "I'm Completely Nude" + INTO_THE_FOLIAGE = "Into the Foliage" + INVISIBLE_FLOWERS = "Invisible Flowers" + IN_CASE_OF_ADVENTURE = "In Case of Adventure, Break Glass!" + IN_MAGIC_WE_TRUST = "In Magic We Trust" + ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN = "It's One of Those Adventurers Again..." + I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY = "I Don't Think You Would've Wanted That to Work Anyway" + I_DONT_WANT_NO_TROUBLE = "I Don't Want No Trouble!" + I_HOPE_YOU_CAN_CLIMB_UP_THERE = "I Hope You Can Climb Up There With All This Junk" + I_LIKE_YOUR_STYLE = "I Like Your Style!" + I_SPIT_ON_YOUR_FILTHY_COINAGE = "I Spit on Your Filthy Coinage" + LIT_SUNFLOWERS = "Lit Sunflowers" + MAGIC_FOREVER = "Magic Forever!" + MAILED_IT_TO_HELL = "Mailed it to Hell" + MAKE_LOVE_NOT_WAR = "Make Love, Not War" + MEAD_LIGHT = "Mead Light?" + MIKES_PANTS = "Mike's Pants" + MUSHROOM_HAMMERED = "Mushroom, Hammered" + NATIONAL_TREASURE = "300 Year Old National Treasure" + NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR = "Natural and Supernatural Creatures of Quendor" + NOOOOOOOOOOOOO = "NOOOOOOOOOOOOO!" + NOTHIN_LIKE_A_GOOD_STOGIE = "Nothin' Like a Good Stogie" + NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT = "Now You Look Like Us, Which is an Improvement" + NO_AUTOGRAPHS = "No Autographs" + NO_BONDAGE = "No Bondage" + OBIDIL_DRIED_UP = "OBIDIL, Dried Up" + OH_DEAR_GOD_ITS_A_DRAGON = "Oh Dear God, It's a Dragon!" + OH_VERY_FUNNY_GUYS = "Oh, Very Funny Guys" + OH_WOW_TALK_ABOUT_DEJA_VU = "Oh, Wow! Talk About Deja Vu" + OLD_SCRATCH_WINNER = "Old Scratch Winner!" + ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES = "Only You Can Prevent Foozle Fires" + OPEN_THE_GATES_OF_HELL = "Open the Gates of Hell" + OUTSMART_THE_QUELBEES = "Outsmart the Quelbees" + PERMASEAL = "PermaSeal" + PLANETFALL = "Planetfall" + PLEASE_DONT_THROCK_THE_GRASS = "Please Don't THROCK the Grass" + PORT_FOOZLE_TIME_TUNNEL = "Port Foozle Time Tunnel" + PROZORKED = "Prozorked" + REASSEMBLE_SNAVIG = "Reassemble SNAVIG" + RESTOCKED_ON_GRUESDAY = "Restocked on Gruesday" + RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE = "Right. Hello. Yes. Uh, This is Sneffle" + RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE = "Right. Uh, Sorry. It's Me Again. Sneffle" + SNAVIG_REPAIRED = "SNAVIG, Repaired" + SOUVENIR = "Souvenir" + STRAIGHT_TO_HELL = "Straight to Hell" + STRIP_GRUE_FIRE_WATER = "Strip Grue, Fire, Water" + SUCKING_ROCKS = "Sucking Rocks" + TALK_TO_ME_GRAND_INQUISITOR = "Talk to Me Grand Inquisitor" + TAMING_YOUR_SNAPDRAGON = "Taming Your Snapdragon" + THAR_SHE_BLOWS = "Thar She Blows!" + THATS_A_ROPE = "That's a Rope" + THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS = "That's it! Just Keep Hitting Those Buttons" + THATS_STILL_A_ROPE = "That's Still a Rope" + THATS_THE_SPIRIT = "That's the Spirit!" + THE_ALCHEMICAL_DEBACLE = "The Alchemical Debacle" + THE_ENDLESS_FIRE = "The Endless Fire" + THE_FLATHEADIAN_FUDGE_FIASCO = "The Flatheadian Fudge Fiasco" + THE_PERILS_OF_MAGIC = "The Perils of Magic" + THE_UNDERGROUND_UNDERGROUND = "The Underground Underground" + THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE = "This Doesn't Look Anything Like the Brochure" + THROCKED_MUSHROOM_HAMMERED = "THROCKed Mushroom, Hammered" + TIME_TRAVEL_FOR_DUMMIES = "Time Travel for Dummies" + TOTEMIZED_DAILY_BILLBOARD = "Totemized Daily Billboard Functioning Correctly" + UH_OH_BROG_CANT_SWIM = "Uh-Oh. Brog Can't Swim" + UMBRELLA_FLOWERS = "Umbrella Flowers" + UP = "Up" + USELESS_BUT_FUN = "Useless, But Fun" + UUUUUP = "Uuuuup" + VOYAGE_OF_CAPTAIN_ZAHAB = "Voyage of Captain Zahab" + WANT_SOME_RYE_COURSE_YA_DO = "Want Some Rye? Course Ya Do!" + WE_DONT_SERVE_YOUR_KIND_HERE = "We Don't Serve Your Kind Here" + WE_GOT_A_HIGH_ROLLER = "We Got a High Roller!" + WHAT_ARE_YOU_STUPID = "What Are You, Stupid?" + WHITE_HOUSE_TIME_TUNNEL = "White House Time Tunnel" + WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE = "Wow! I've Never Gone Inside Him Before!" + YAD_GOHDNUORGREDNU_3_YRAUBORF = "yaD gohdnuorgrednU - 3 yrauborF" + YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY = "Your Puny Weapons Don't Phase Me, Baby!" + YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER = "You Don't Go Messing With a Man's Zipper" + YOU_GAINED_86_EXPERIENCE_POINTS = "You Gained 86 Experience Points" + YOU_ONE_OF_THEM_AGITATORS_AINT_YA = "You One of Them Agitators, Ain't Ya?" + YOU_WANT_A_PIECE_OF_ME_DOCK_BOY = "You Want a Piece of Me, Dock Boy? or Girl" + + +class ZorkGrandInquisitorRegions(enum.Enum): + CROSSROADS = "Crossroads" + DM_LAIR = "Dungeon Master's Lair" + DM_LAIR_INTERIOR = "Dungeon Master's Lair - Interior" + DRAGON_ARCHIPELAGO = "Dragon Archipelago" + DRAGON_ARCHIPELAGO_DRAGON = "Dragon Archipelago - Dragon" + ENDGAME = "Endgame" + GUE_TECH = "GUE Tech" + GUE_TECH_HALLWAY = "GUE Tech - Hallway" + GUE_TECH_OUTSIDE = "GUE Tech - Outside" + HADES = "Hades" + HADES_BEYOND_GATES = "Hades - Beyond Gates" + HADES_SHORE = "Hades - Shore" + MENU = "Menu" + MONASTERY = "Monastery" + MONASTERY_EXHIBIT = "Monastery - Exhibit" + PORT_FOOZLE = "Port Foozle" + PORT_FOOZLE_JACKS_SHOP = "Port Foozle - Jack's Shop" + PORT_FOOZLE_PAST = "Port Foozle Past" + PORT_FOOZLE_PAST_TAVERN = "Port Foozle Past - Tavern" + SPELL_LAB = "Spell Lab" + SPELL_LAB_BRIDGE = "Spell Lab - Bridge" + SUBWAY_CROSSROADS = "Subway Platform - Crossroads" + SUBWAY_FLOOD_CONTROL_DAM = "Subway Platform - Flood Control Dam #3" + SUBWAY_MONASTERY = "Subway Platform - Monastery" + WALKING_CASTLE = "Walking Castle" + WHITE_HOUSE = "White House" + + +class ZorkGrandInquisitorTags(enum.Enum): + CORE = "Core" + DEATHSANITY = "Deathsanity" + FILLER = "Filler" + HOTSPOT = "Hotspot" + INVENTORY_ITEM = "Inventory Item" + MISSABLE = "Missable" + SPELL = "Spell" + SUBWAY_DESTINATION = "Subway Destination" + TELEPORTER_DESTINATION = "Teleporter Destination" + TOTEMIZER_DESTINATION = "Totemizer Destination" + TOTEM = "Totem" diff --git a/worlds/zork_grand_inquisitor/game_controller.py b/worlds/zork_grand_inquisitor/game_controller.py new file mode 100644 index 000000000000..7a60a1460829 --- /dev/null +++ b/worlds/zork_grand_inquisitor/game_controller.py @@ -0,0 +1,1388 @@ +import collections +import functools +import logging + +from typing import Dict, Optional, Set, Tuple, Union + +from .data.item_data import item_data, ZorkGrandInquisitorItemData +from .data.location_data import location_data, ZorkGrandInquisitorLocationData + +from .data.missable_location_grant_conditions_data import ( + missable_location_grant_conditions_data, + ZorkGrandInquisitorMissableLocationGrantConditionsData, +) + +from .data_funcs import game_id_to_items, items_with_tag, locations_with_tag + +from .enums import ( + ZorkGrandInquisitorGoals, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorTags, +) + +from .game_state_manager import GameStateManager + + +class GameController: + logger: Optional[logging.Logger] + + game_state_manager: GameStateManager + + received_items: Set[ZorkGrandInquisitorItems] + completed_locations: Set[ZorkGrandInquisitorLocations] + + completed_locations_queue: collections.deque + received_items_queue: collections.deque + + all_hotspot_items: Set[ZorkGrandInquisitorItems] + + game_id_to_items: Dict[int, ZorkGrandInquisitorItems] + + possible_inventory_items: Set[ZorkGrandInquisitorItems] + + available_inventory_slots: Set[int] + + goal_completed: bool + + option_goal: Optional[ZorkGrandInquisitorGoals] + option_deathsanity: Optional[bool] + option_grant_missable_location_checks: Optional[bool] + + def __init__(self, logger=None) -> None: + self.logger = logger + + self.game_state_manager = GameStateManager() + + self.received_items = set() + self.completed_locations = set() + + self.completed_locations_queue = collections.deque() + self.received_items_queue = collections.deque() + + self.all_hotspot_items = ( + items_with_tag(ZorkGrandInquisitorTags.HOTSPOT) + | items_with_tag(ZorkGrandInquisitorTags.SUBWAY_DESTINATION) + | items_with_tag(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION) + ) + + self.game_id_to_items = game_id_to_items() + + self.possible_inventory_items = ( + items_with_tag(ZorkGrandInquisitorTags.INVENTORY_ITEM) + | items_with_tag(ZorkGrandInquisitorTags.SPELL) + | items_with_tag(ZorkGrandInquisitorTags.TOTEM) + ) + + self.available_inventory_slots = set() + + self.goal_completed = False + + self.option_goal = None + self.option_deathsanity = None + self.option_grant_missable_location_checks = None + + @functools.cached_property + def brog_items(self) -> Set[ZorkGrandInquisitorItems]: + return { + ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.BROGS_PLANK, + } + + @functools.cached_property + def griff_items(self) -> Set[ZorkGrandInquisitorItems]: + return { + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + } + + @functools.cached_property + def lucy_items(self) -> Set[ZorkGrandInquisitorItems]: + return { + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + } + + @property + def totem_items(self) -> Set[ZorkGrandInquisitorItems]: + return self.brog_items | self.griff_items | self.lucy_items + + @functools.cached_property + def missable_locations(self) -> Set[ZorkGrandInquisitorLocations]: + return locations_with_tag(ZorkGrandInquisitorTags.MISSABLE) + + def log(self, message) -> None: + if self.logger: + self.logger.info(message) + + def log_debug(self, message) -> None: + if self.logger: + self.logger.debug(message) + + def open_process_handle(self) -> bool: + return self.game_state_manager.open_process_handle() + + def close_process_handle(self) -> bool: + return self.game_state_manager.close_process_handle() + + def is_process_running(self) -> bool: + return self.game_state_manager.is_process_running + + def list_received_brog_items(self) -> None: + self.log("Received Brog Items:") + + self._process_received_items() + received_brog_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.brog_items + + if not len(received_brog_items): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_brog_items): + self.log(f" {item}") + + def list_received_griff_items(self) -> None: + self.log("Received Griff Items:") + + self._process_received_items() + received_griff_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.griff_items + + if not len(received_griff_items): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_griff_items): + self.log(f" {item}") + + def list_received_lucy_items(self) -> None: + self.log("Received Lucy Items:") + + self._process_received_items() + received_lucy_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.lucy_items + + if not len(received_lucy_items): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_lucy_items): + self.log(f" {item}") + + def list_received_hotspots(self) -> None: + self.log("Received Hotspots:") + + self._process_received_items() + + hotspot_items: Set[ZorkGrandInquisitorItems] = items_with_tag(ZorkGrandInquisitorTags.HOTSPOT) + received_hotspots: Set[ZorkGrandInquisitorItems] = self.received_items & hotspot_items + + if not len(received_hotspots): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_hotspots): + self.log(f" {item}") + + def update(self) -> None: + if self.game_state_manager.is_process_still_running(): + try: + self.game_state_manager.refresh_game_location() + + self._apply_permanent_game_state() + self._apply_conditional_game_state() + + self._apply_permanent_game_flags() + + self._check_for_completed_locations() + + if self.option_grant_missable_location_checks: + self._check_for_missable_locations_to_grant() + + self._process_received_items() + + self._manage_hotspots() + self._manage_items() + + self._apply_conditional_teleports() + + self._check_for_victory() + except Exception as e: + self.log_debug(e) + + def _apply_permanent_game_state(self) -> None: + self._write_game_state_value_for(10934, 1) # Rope Taken + self._write_game_state_value_for(10418, 1) # Mead Light Taken + self._write_game_state_value_for(10275, 0) # Lantern in Crate + self._write_game_state_value_for(13929, 1) # Great Underground Door Open + self._write_game_state_value_for(13968, 1) # Subway Token Taken + self._write_game_state_value_for(12930, 1) # Hammer Taken + self._write_game_state_value_for(12935, 1) # Griff Totem Taken + self._write_game_state_value_for(12948, 1) # ZIMDOR Scroll Taken + self._write_game_state_value_for(4058, 1) # Shovel Taken + self._write_game_state_value_for(4059, 1) # THROCK Scroll Taken + self._write_game_state_value_for(11758, 1) # KENDALL Scroll Taken + self._write_game_state_value_for(16959, 1) # Old Scratch Card Taken + self._write_game_state_value_for(12840, 0) # Zork Rocks in Perma-Suck Machine + self._write_game_state_value_for(11886, 1) # Student ID Taken + self._write_game_state_value_for(16279, 1) # Prozork Tablet Taken + self._write_game_state_value_for(13260, 1) # GOLGATEM Scroll Taken + self._write_game_state_value_for(4834, 1) # Flatheadia Fudge Taken + self._write_game_state_value_for(4746, 1) # Jar of Hotbugs Taken + self._write_game_state_value_for(4755, 1) # Hungus Lard Taken + self._write_game_state_value_for(4758, 1) # Mug Taken + self._write_game_state_value_for(3716, 1) # NARWILE Scroll Taken + self._write_game_state_value_for(17147, 1) # Lucy Totem Taken + self._write_game_state_value_for(9818, 1) # Middle Telegraph Hammer Taken + self._write_game_state_value_for(3766, 0) # ANS Scroll in Window + self._write_game_state_value_for(4980, 0) # ANS Scroll in Window + self._write_game_state_value_for(3768, 0) # GIV Scroll in Window + self._write_game_state_value_for(4978, 0) # GIV Scroll in Window + self._write_game_state_value_for(3765, 0) # SNA Scroll in Window + self._write_game_state_value_for(4979, 0) # SNA Scroll in Window + self._write_game_state_value_for(3767, 0) # VIG Scroll in Window + self._write_game_state_value_for(4977, 0) # VIG Scroll in Window + self._write_game_state_value_for(15065, 1) # Brog's Bickering Torch Taken + self._write_game_state_value_for(15088, 1) # Brog's Flickering Torch Taken + self._write_game_state_value_for(2628, 4) # Brog's Grue Eggs Taken + self._write_game_state_value_for(2971, 1) # Brog's Plank Taken + self._write_game_state_value_for(1340, 1) # Griff's Inflatable Sea Captain Taken + self._write_game_state_value_for(1341, 1) # Griff's Inflatable Raft Taken + self._write_game_state_value_for(1477, 1) # Griff's Air Pump Taken + self._write_game_state_value_for(1814, 1) # Griff's Dragon Tooth Taken + self._write_game_state_value_for(15403, 0) # Lucy's Cards Taken + self._write_game_state_value_for(15404, 1) # Lucy's Cards Taken + self._write_game_state_value_for(15405, 4) # Lucy's Cards Taken + self._write_game_state_value_for(5222, 1) # User Has Spell Book + self._write_game_state_value_for(13930, 1) # Skip Well Cutscenes + self._write_game_state_value_for(19057, 1) # Skip Well Cutscenes + self._write_game_state_value_for(13934, 1) # Skip Well Cutscenes + self._write_game_state_value_for(13935, 1) # Skip Well Cutscenes + self._write_game_state_value_for(13384, 1) # Skip Meanwhile... Cutscene + self._write_game_state_value_for(8620, 1) # First Coin Paid to Charon + self._write_game_state_value_for(8731, 1) # First Coin Paid to Charon + + def _apply_conditional_game_state(self): + # Can teleport to Dungeon Master's Lair + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR): + self._write_game_state_value_for(2203, 1) + else: + self._write_game_state_value_for(2203, 0) + + # Can teleport to GUE Tech + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH): + self._write_game_state_value_for(7132, 1) + else: + self._write_game_state_value_for(7132, 0) + + # Can Teleport to Spell Lab + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB): + self._write_game_state_value_for(16545, 1) + else: + self._write_game_state_value_for(16545, 0) + + # Can Teleport to Hades + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES): + self._write_game_state_value_for(7119, 1) + else: + self._write_game_state_value_for(7119, 0) + + # Can Teleport to Monastery Station + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY): + self._write_game_state_value_for(7148, 1) + else: + self._write_game_state_value_for(7148, 0) + + # Initial Totemizer Destination + should_force_initial_totemizer_destination: bool = True + + if self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION): + should_force_initial_totemizer_destination = False + elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL): + should_force_initial_totemizer_destination = False + elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY): + should_force_initial_totemizer_destination = False + elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ): + should_force_initial_totemizer_destination = False + + if should_force_initial_totemizer_destination: + self._write_game_state_value_for(9617, 2) + + # Pouch of Zorkmids + if self._player_has(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS): + self._write_game_state_value_for(5827, 1) + else: + self._write_game_state_value_for(5827, 0) + + # Brog Torches + if self._player_is_brog() and self._player_has(ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH): + self._write_game_state_value_for(10999, 1) + else: + self._write_game_state_value_for(10999, 0) + + if self._player_is_brog() and self._player_has(ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH): + self._write_game_state_value_for(10998, 1) + else: + self._write_game_state_value_for(10998, 0) + + # Monastery Rope + if ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE in self.completed_locations: + self._write_game_state_value_for(9637, 1) + + def _apply_permanent_game_flags(self) -> None: + self._write_game_flags_value_for(9437, 2) # Monastery Exhibit Door to Outside + self._write_game_flags_value_for(3074, 2) # White House Door + self._write_game_flags_value_for(13005, 2) # Map + self._write_game_flags_value_for(13006, 2) # Sword + self._write_game_flags_value_for(13007, 2) # Sword + self._write_game_flags_value_for(13389, 2) # Moss of Mareilon + self._write_game_flags_value_for(4301, 2) # Quelbee Honeycomb + self._write_game_flags_value_for(12895, 2) # Change Machine Money + self._write_game_flags_value_for(4150, 2) # Prozorked Snapdragon + self._write_game_flags_value_for(13413, 2) # Letter Opener + self._write_game_flags_value_for(15403, 2) # Lucy's Cards + + def _check_for_completed_locations(self) -> None: + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + for location, data in location_data.items(): + if location in self.completed_locations or not isinstance( + location, ZorkGrandInquisitorLocations + ): + continue + + is_location_completed: bool = True + + trigger: [Union[str, int]] + value: Union[str, int, Tuple[int, ...]] + for trigger, value in data.game_state_trigger: + if trigger == "location": + if not self._player_is_at(value): + is_location_completed = False + break + elif isinstance(trigger, int): + if isinstance(value, int): + if self._read_game_state_value_for(trigger) != value: + is_location_completed = False + break + elif isinstance(value, tuple): + if self._read_game_state_value_for(trigger) not in value: + is_location_completed = False + break + else: + is_location_completed = False + break + else: + is_location_completed = False + break + + if is_location_completed: + self.completed_locations.add(location) + self.completed_locations_queue.append(location) + + def _check_for_missable_locations_to_grant(self) -> None: + missable_location: ZorkGrandInquisitorLocations + for missable_location in self.missable_locations: + if missable_location in self.completed_locations: + continue + + data: ZorkGrandInquisitorLocationData = location_data[missable_location] + + if ZorkGrandInquisitorTags.DEATHSANITY in data.tags and not self.option_deathsanity: + continue + + condition_data: ZorkGrandInquisitorMissableLocationGrantConditionsData = ( + missable_location_grant_conditions_data.get(missable_location) + ) + + if condition_data is None: + self.log_debug(f"Missable Location {missable_location.value} has no grant conditions") + continue + + if condition_data.location_condition in self.completed_locations: + grant_location: bool = True + + item: ZorkGrandInquisitorItems + for item in condition_data.item_conditions or tuple(): + if self._player_doesnt_have(item): + grant_location = False + break + + if grant_location: + self.completed_locations_queue.append(missable_location) + + def _process_received_items(self) -> None: + while len(self.received_items_queue) > 0: + item: ZorkGrandInquisitorItems = self.received_items_queue.popleft() + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.FILLER in data.tags: + continue + + self.received_items.add(item) + + def _manage_hotspots(self) -> None: + hotspot_item: ZorkGrandInquisitorItems + for hotspot_item in self.all_hotspot_items: + data: ZorkGrandInquisitorItemData = item_data[hotspot_item] + + if hotspot_item not in self.received_items: + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 2) + else: + if hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: + if self.game_state_manager.game_location == "hp5g": + if self._read_game_state_value_for(9113) == 0: + self._write_game_flags_value_for(9116, 0) + else: + self._write_game_flags_value_for(9116, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: + if self.game_state_manager.game_location == "qb2g": + if self._read_game_state_value_for(15433) == 0: + self._write_game_flags_value_for(15434, 0) + else: + self._write_game_flags_value_for(15434, 2) + + if self._read_game_state_value_for(15435) == 0: + self._write_game_flags_value_for(15436, 0) + else: + self._write_game_flags_value_for(15436, 2) + + if self._read_game_state_value_for(15437) == 0: + self._write_game_flags_value_for(15438, 0) + else: + self._write_game_flags_value_for(15438, 2) + + if self._read_game_state_value_for(15439) == 0: + self._write_game_flags_value_for(15440, 0) + else: + self._write_game_flags_value_for(15440, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: + if self.game_state_manager.game_location == "tp2g": + if self._read_game_state_value_for(12095) == 1: + self._write_game_flags_value_for(9115, 2) + else: + self._write_game_flags_value_for(9115, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_BLINDS: + if self.game_state_manager.game_location == "dv1e": + if self._read_game_state_value_for(4743) == 0: + self._write_game_flags_value_for(4799, 0) + else: + self._write_game_flags_value_for(4799, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: + if self.game_state_manager.game_location == "tr5g": + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: + if self.game_state_manager.game_location == "tr5g": + self._write_game_flags_value_for(12702, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: + if self.game_state_manager.game_location == "tr5m": + self._write_game_flags_value_for(12909, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: + if self.game_state_manager.game_location == "tr5j": + if self._read_game_state_value_for(12892) == 0: + self._write_game_flags_value_for(12900, 0) + else: + self._write_game_flags_value_for(12900, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: + if self.game_state_manager.game_location == "dw1e": + if self._read_game_state_value_for(4983) == 0: + self._write_game_flags_value_for(5010, 0) + else: + self._write_game_flags_value_for(5010, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: + if self.game_state_manager.game_location == "me2j": + if self._read_game_state_value_for(9491) == 2: + self._write_game_flags_value_for(9539, 0) + else: + self._write_game_flags_value_for(9539, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: + if self.game_state_manager.game_location == "me2j": + if self._read_game_state_value_for(9546) == 2 or self._read_game_state_value_for(9419) == 1: + self._write_game_flags_value_for(19712, 2) + else: + self._write_game_flags_value_for(19712, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: + if self.game_state_manager.game_location == "sg1f": + self._write_game_flags_value_for(2586, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: + if self.game_state_manager.game_location == "th3j": + five_is_open: bool = self._read_game_state_value_for(11847) == 1 + six_is_open: bool = self._read_game_state_value_for(11840) == 1 + seven_is_open: bool = self._read_game_state_value_for(11841) == 1 + eight_is_open: bool = self._read_game_state_value_for(11848) == 1 + + rocks_in_six: bool = self._read_game_state_value_for(11769) == 1 + six_blasted: bool = self._read_game_state_value_for(11770) == 1 + + if five_is_open or six_is_open or seven_is_open or eight_is_open or rocks_in_six or six_blasted: + self._write_game_flags_value_for(11878, 2) + else: + self._write_game_flags_value_for(11878, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: + if self.game_state_manager.game_location == "te5e": + if self._read_game_state_value_for(11747) == 0: + self._write_game_flags_value_for(11751, 0) + else: + self._write_game_flags_value_for(11751, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: + if self.game_state_manager.game_location == "pe2e": + self._write_game_flags_value_for(15147, 0) + self._write_game_flags_value_for(15153, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: + if self.game_state_manager.game_location == "cd70": + self._write_game_flags_value_for(1705, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: + if self.game_state_manager.game_location == "cd3h": + raft_in_left: bool = self._read_game_state_value_for(1301) == 1 + raft_in_right: bool = self._read_game_state_value_for(1304) == 1 + raft_inflated: bool = self._read_game_state_value_for(1379) == 1 + + captain_in_left: bool = self._read_game_state_value_for(1374) == 1 + captain_in_right: bool = self._read_game_state_value_for(1381) == 1 + captain_inflated: bool = self._read_game_state_value_for(1378) == 1 + + left_inflated: bool = (raft_in_left and raft_inflated) or (captain_in_left and captain_inflated) + + right_inflated: bool = (raft_in_right and raft_inflated) or ( + captain_in_right and captain_inflated + ) + + if left_inflated: + self._write_game_flags_value_for(1425, 2) + else: + self._write_game_flags_value_for(1425, 0) + + if right_inflated: + self._write_game_flags_value_for(1426, 2) + else: + self._write_game_flags_value_for(1426, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: + if self.game_state_manager.game_location == "uc3e": + if self._read_game_state_value_for(13060) == 0: + self._write_game_flags_value_for(13106, 0) + else: + self._write_game_flags_value_for(13106, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: + if self.game_state_manager.game_location == "ue1e": + if self._read_game_state_value_for(14318) == 0: + self._write_game_flags_value_for(13219, 0) + self._write_game_flags_value_for(13220, 0) + self._write_game_flags_value_for(13221, 0) + self._write_game_flags_value_for(13222, 0) + else: + self._write_game_flags_value_for(13219, 2) + self._write_game_flags_value_for(13220, 2) + self._write_game_flags_value_for(13221, 2) + self._write_game_flags_value_for(13222, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: + if self.game_state_manager.game_location == "ue1e": + if self._read_game_state_value_for(14318) == 0: + self._write_game_flags_value_for(14327, 0) + self._write_game_flags_value_for(14332, 0) + self._write_game_flags_value_for(14337, 0) + self._write_game_flags_value_for(14342, 0) + else: + self._write_game_flags_value_for(14327, 2) + self._write_game_flags_value_for(14332, 2) + self._write_game_flags_value_for(14337, 2) + self._write_game_flags_value_for(14342, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: + if self.game_state_manager.game_location == "tr5e": + self._write_game_flags_value_for(12528, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: + if self.game_state_manager.game_location == "tr5e": + if self._read_game_state_value_for(12220) == 0: + self._write_game_flags_value_for(12523, 2) + self._write_game_flags_value_for(12524, 2) + self._write_game_flags_value_for(12525, 2) + else: + self._write_game_flags_value_for(12523, 0) + self._write_game_flags_value_for(12524, 0) + self._write_game_flags_value_for(12525, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: + if self.game_state_manager.game_location == "uc1g": + if self._read_game_state_value_for(12931) == 1 or self._read_game_state_value_for(12929) == 1: + self._write_game_flags_value_for(13002, 2) + else: + self._write_game_flags_value_for(13002, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: + if self.game_state_manager.game_location == "pe5e": + if self._read_game_state_value_for(10277) == 0: + self._write_game_flags_value_for(10726, 0) + else: + self._write_game_flags_value_for(10726, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: + if self.game_state_manager.game_location == "tr1k": + if self._read_game_state_value_for(12212) == 0: + self._write_game_flags_value_for(12280, 0) + else: + self._write_game_flags_value_for(12280, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: + if self.game_state_manager.game_location in ("te10", "te1g", "te20", "te30", "te40"): + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: + if self.game_state_manager.game_location == "hp1e": + if self._read_game_state_value_for(8431) == 1: + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 0) + else: + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: + if self.game_state_manager.game_location == "hp1e": + if self._read_game_state_value_for(8431) == 1: + self._write_game_flags_value_for(8446, 2) + else: + self._write_game_flags_value_for(8446, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRY: + if self.game_state_manager.game_location == "dg4e": + if self._read_game_state_value_for(4237) == 1 and self._read_game_state_value_for(4034) == 1: + self._write_game_flags_value_for(4260, 2) + else: + self._write_game_flags_value_for(4260, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: + if self.game_state_manager.game_location == "dg4h": + if self._read_game_state_value_for(4279) == 1: + self._write_game_flags_value_for(18026, 2) + else: + self._write_game_flags_value_for(18026, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: + if self.game_state_manager.game_location == "dg4g": + if self._read_game_state_value_for(4034) == 1: + self._write_game_flags_value_for(17623, 2) + else: + self._write_game_flags_value_for(17623, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: + if self.game_state_manager.game_location == "uc4e": + if self._read_game_state_value_for(13062) == 1: + self._write_game_flags_value_for(13140, 2) + else: + self._write_game_flags_value_for(13140, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: + if self.game_state_manager.game_location == "pe1e": + if self._read_game_state_value_for(10451) == 1: + self._write_game_flags_value_for(10441, 2) + else: + self._write_game_flags_value_for(10441, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: + if self.game_state_manager.game_location == "pe2j": + self._write_game_flags_value_for(19632, 0) + self._write_game_flags_value_for(19627, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: + if self.game_state_manager.game_location == "sw4e": + if self._read_game_state_value_for(2989) == 1: + self._write_game_flags_value_for(3025, 2) + else: + self._write_game_flags_value_for(3025, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: + if self.game_state_manager.game_location == "sw4e": + self._write_game_flags_value_for(3036, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MIRROR: + if self.game_state_manager.game_location == "dw1f": + self._write_game_flags_value_for(5031, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: + if self.game_state_manager.game_location == "um1e": + if self._read_game_state_value_for(9637) == 0: + self._write_game_flags_value_for(13597, 0) + else: + self._write_game_flags_value_for(13597, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: + if self.game_state_manager.game_location == "ue2g": + if self._read_game_state_value_for(13278) == 0: + self._write_game_flags_value_for(13390, 0) + else: + self._write_game_flags_value_for(13390, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: + if self.game_state_manager.game_location == "qe1e": + if self._player_is_brog(): + self._write_game_flags_value_for(2447, 0) + elif self._player_is_griff(): + self._write_game_flags_value_for(2455, 0) + elif self._player_is_lucy(): + if self._read_game_state_value_for(2457) == 0: + self._write_game_flags_value_for(2455, 0) + else: + self._write_game_flags_value_for(2455, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: + if self.game_state_manager.game_location == "tr3h": + if self._read_game_state_value_for(11777) == 1: + self._write_game_flags_value_for(12389, 2) + else: + self._write_game_flags_value_for(12389, 0) + + self._write_game_state_value_for(12390, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: + if self.game_state_manager.game_location == "dg4f": + if self._read_game_state_value_for(4241) == 1: + self._write_game_flags_value_for(4302, 2) + else: + self._write_game_flags_value_for(4302, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: + if self.game_state_manager.game_location == "tp1e": + if self._read_game_state_value_for(16342) == 1: + self._write_game_flags_value_for(16383, 2) + self._write_game_flags_value_for(16384, 2) + else: + self._write_game_flags_value_for(16383, 0) + self._write_game_flags_value_for(16384, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: + if self.game_state_manager.game_location == "sg6e": + if self._read_game_state_value_for(15715) == 1: + self._write_game_flags_value_for(2769, 2) + else: + self._write_game_flags_value_for(2769, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: + if self.game_state_manager.game_location == "dg2f": + if self._read_game_state_value_for(4114) == 1 or self._read_game_state_value_for(4115) == 1: + self._write_game_flags_value_for(4149, 2) + else: + self._write_game_flags_value_for(4149, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: + if self.game_state_manager.game_location == "tr5f": + self._write_game_flags_value_for(12584, 0) + self._write_game_flags_value_for(12585, 0) + self._write_game_flags_value_for(12586, 0) + self._write_game_flags_value_for(12587, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: + if self.game_state_manager.game_location == "tr5f": + self._write_game_flags_value_for(12574, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: + if self.game_state_manager.game_location == "ue2j": + if self._read_game_state_value_for(13408) == 1: + self._write_game_flags_value_for(13412, 2) + else: + self._write_game_flags_value_for(13412, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: + if self.game_state_manager.game_location == "tp4g": + self._write_game_flags_value_for(12170, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: + if self.game_state_manager.game_location == "tp1e": + if self._read_game_state_value_for(16342) == 1 and self._read_game_state_value_for(16374) == 0: + self._write_game_flags_value_for(16382, 0) + else: + self._write_game_flags_value_for(16382, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: + if self.game_state_manager.game_location == "dg3e": + self._write_game_flags_value_for(4209, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: + if self.game_state_manager.game_location == "th3r": + self._write_game_flags_value_for(11973, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: + if self.game_state_manager.game_location == "uc6e": + self._write_game_flags_value_for(13168, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: + if self.game_state_manager.game_location == "qb2e": + if self._read_game_state_value_for(15395) == 1: + self._write_game_flags_value_for(15396, 2) + else: + self._write_game_flags_value_for(15396, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: + if self.game_state_manager.game_location == "mt2e": + self._write_game_flags_value_for(9706, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: + if self.game_state_manager.game_location == "mt2g": + self._write_game_flags_value_for(9728, 0) + self._write_game_flags_value_for(9729, 0) + self._write_game_flags_value_for(9730, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_WELL: + if self.game_state_manager.game_location == "pc1e": + self._write_game_flags_value_for(10314, 0) + elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: + if self.game_state_manager.game_location == "us2e": + self._write_game_flags_value_for(13757, 0) + elif self.game_state_manager.game_location == "ue2e": + self._write_game_flags_value_for(13297, 0) + elif self.game_state_manager.game_location == "uh2e": + self._write_game_flags_value_for(13486, 0) + elif self.game_state_manager.game_location == "um2e": + self._write_game_flags_value_for(13625, 0) + elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: + if self.game_state_manager.game_location == "us2e": + self._write_game_flags_value_for(13758, 0) + elif self.game_state_manager.game_location == "ue2e": + self._write_game_flags_value_for(13309, 0) + elif self.game_state_manager.game_location == "uh2e": + self._write_game_flags_value_for(13498, 0) + elif self.game_state_manager.game_location == "um2e": + self._write_game_flags_value_for(13637, 0) + elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: + if self.game_state_manager.game_location == "us2e": + self._write_game_flags_value_for(13759, 0) + elif self.game_state_manager.game_location == "ue2e": + self._write_game_flags_value_for(13316, 0) + elif self.game_state_manager.game_location == "uh2e": + self._write_game_flags_value_for(13505, 0) + elif self.game_state_manager.game_location == "um2e": + self._write_game_flags_value_for(13644, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9660, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9666, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9668, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9662, 0) + + def _manage_items(self) -> None: + if self._player_is_afgncaap(): + self.available_inventory_slots = self._determine_available_inventory_slots() + + received_inventory_items: Set[ZorkGrandInquisitorItems] + received_inventory_items = self.received_items & self.possible_inventory_items + + received_inventory_items = self._filter_received_inventory_items(received_inventory_items) + elif self._player_is_totem(): + self.available_inventory_slots = self._determine_available_inventory_slots(is_totem=True) + + received_inventory_items: Set[ZorkGrandInquisitorItems] + + if self._player_is_brog(): + received_inventory_items = self.received_items & self.brog_items + received_inventory_items = self._filter_received_brog_inventory_items(received_inventory_items) + elif self._player_is_griff(): + received_inventory_items = self.received_items & self.griff_items + received_inventory_items = self._filter_received_griff_inventory_items(received_inventory_items) + elif self._player_is_lucy(): + received_inventory_items = self.received_items & self.lucy_items + received_inventory_items = self._filter_received_lucy_inventory_items(received_inventory_items) + else: + return None + else: + return None + + game_state_inventory_items: Set[ZorkGrandInquisitorItems] = self._determine_game_state_inventory() + + inventory_items_to_remove: Set[ZorkGrandInquisitorItems] + inventory_items_to_remove = game_state_inventory_items - received_inventory_items + + inventory_items_to_add: Set[ZorkGrandInquisitorItems] + inventory_items_to_add = received_inventory_items - game_state_inventory_items + + item: ZorkGrandInquisitorItems + for item in inventory_items_to_remove: + self._remove_from_inventory(item) + + item: ZorkGrandInquisitorItems + for item in inventory_items_to_add: + self._add_to_inventory(item) + + # Item Deduplication (Just in Case) + seen_items: Set[int] = set() + + i: int + for i in range(151, 171): + item: int = self._read_game_state_value_for(i) + + if item in seen_items: + self._write_game_state_value_for(i, 0) + else: + seen_items.add(item) + + def _apply_conditional_teleports(self) -> None: + if self._player_is_at("uw1x"): + self.game_state_manager.set_game_location("uw10", 0) + + if self._player_is_at("uw1k") and self._read_game_state_value_for(13938) == 0: + self.game_state_manager.set_game_location("pc10", 250) + + if self._player_is_at("ue1q"): + self.game_state_manager.set_game_location("ue1e", 0) + + if self._player_is_at("ej10"): + self.game_state_manager.set_game_location("uc10", 1200) + + if self._read_game_state_value_for(9) == 224: + self._write_game_state_value_for(9, 0) + self.game_state_manager.set_game_location("uc10", 1200) + + def _check_for_victory(self) -> None: + if self.option_goal == ZorkGrandInquisitorGoals.THREE_ARTIFACTS: + coconut_is_placed = self._read_game_state_value_for(2200) == 1 + cube_is_placed = self._read_game_state_value_for(2322) == 1 + skull_is_placed = self._read_game_state_value_for(2321) == 1 + + self.goal_completed = coconut_is_placed and cube_is_placed and skull_is_placed + + def _determine_game_state_inventory(self) -> Set[ZorkGrandInquisitorItems]: + game_state_inventory: Set[ZorkGrandInquisitorItems] = set() + + # Item on Cursor + item_on_cursor: int = self._read_game_state_value_for(9) + + if item_on_cursor != 0: + if item_on_cursor in self.game_id_to_items: + game_state_inventory.add(self.game_id_to_items[item_on_cursor]) + + # Item in Inspector + item_in_inspector: int = self._read_game_state_value_for(4512) + + if item_in_inspector != 0: + if item_in_inspector in self.game_id_to_items: + game_state_inventory.add(self.game_id_to_items[item_in_inspector]) + + # Items in Inventory Slots + i: int + for i in range(151, 171): + if self._read_game_state_value_for(i) != 0: + if self._read_game_state_value_for(i) in self.game_id_to_items: + game_state_inventory.add( + self.game_id_to_items[self._read_game_state_value_for(i)] + ) + + # Pouch of Zorkmids + if self._read_game_state_value_for(5827) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS) + + # Spells + i: int + for i in range(191, 203): + if self._read_game_state_value_for(i) == 1: + if i in self.game_id_to_items: + game_state_inventory.add(self.game_id_to_items[i]) + + # Totems + if self._read_game_state_value_for(4853) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_BROG) + + if self._read_game_state_value_for(4315) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_GRIFF) + + if self._read_game_state_value_for(5223) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_LUCY) + + return game_state_inventory + + def _add_to_inventory(self, item: ZorkGrandInquisitorItems) -> None: + if item == ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: + return None + + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: + if len(self.available_inventory_slots): # Inventory slot overflow protection + inventory_slot: int = self.available_inventory_slots.pop() + self._write_game_state_value_for(inventory_slot, data.statemap_keys[0]) + elif ZorkGrandInquisitorTags.SPELL in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 1) + elif ZorkGrandInquisitorTags.TOTEM in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 1) + + def _remove_from_inventory(self, item: ZorkGrandInquisitorItems) -> None: + if item == ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: + return None + + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: + inventory_slot: Optional[int] = self._inventory_slot_for(item) + + if inventory_slot is None: + return None + + self._write_game_state_value_for(inventory_slot, 0) + + if inventory_slot != 9: + self.available_inventory_slots.add(inventory_slot) + elif ZorkGrandInquisitorTags.SPELL in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 0) + elif ZorkGrandInquisitorTags.TOTEM in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 0) + + def _determine_available_inventory_slots(self, is_totem: bool = False) -> Set[int]: + available_inventory_slots: Set[int] = set() + + inventory_slot_range_end: int = 171 + + if is_totem: + if self._player_is_brog(): + inventory_slot_range_end = 161 + elif self._player_is_griff(): + inventory_slot_range_end = 160 + elif self._player_is_lucy(): + inventory_slot_range_end = 157 + + i: int + for i in range(151, inventory_slot_range_end): + if self._read_game_state_value_for(i) == 0: + available_inventory_slots.add(i) + + return available_inventory_slots + + def _inventory_slot_for(self, item) -> Optional[int]: + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: + i: int + for i in range(151, 171): + if self._read_game_state_value_for(i) == data.statemap_keys[0]: + return i + + if self._read_game_state_value_for(9) == data.statemap_keys[0]: + return 9 + + if self._read_game_state_value_for(4512) == data.statemap_keys[0]: + return 4512 + + return None + + def _filter_received_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = self.totem_items + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 171): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(4512) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: + if self._read_game_state_value_for(4766) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.HUNGUS_LARD: + if self._read_game_state_value_for(4870) == 1: + to_filter_inventory_items.add(item) + elif ( + self._read_game_state_value_for(4244) == 1 + and self._read_game_state_value_for(4309) == 0 + ): + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: + if self._read_game_state_value_for(4750) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LANTERN: + if self._read_game_state_value_for(10477) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(5221) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: + if self._read_game_state_value_for(9491) == 3: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MAP: + if self._read_game_state_value_for(16618) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MEAD_LIGHT: + if 105 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(17620) > 0: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4034) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MOSS_OF_MAREILON: + if self._read_game_state_value_for(4763) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MUG: + if self._read_game_state_value_for(4772) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: + if 32 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(12892) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: + if self._read_game_state_value_for(12218) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: + if self._read_game_state_value_for(15150) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(10421) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.PROZORK_TABLET: + if self._read_game_state_value_for(4115) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: + if self._read_game_state_value_for(4769) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.ROPE: + if 22 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 111 in inventory_item_values: + to_filter_inventory_items.add(item) + elif ( + self._read_game_state_value_for(10304) == 1 + and not self._read_game_state_value_for(13938) == 1 + ): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15150) == 83: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: + if 41 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 98 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(201) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: + if 48 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 98 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(201) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SNAPDRAGON: + if self._read_game_state_value_for(4199) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.STUDENT_ID: + if self._read_game_state_value_for(11838) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SUBWAY_TOKEN: + if self._read_game_state_value_for(13167) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SWORD: + if 22 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 100 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 111 in inventory_item_values: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.ZIMDOR_SCROLL: + if 105 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(17620) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4034) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.ZORK_ROCKS: + if self._read_game_state_value_for(12486) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(12487) == 1: + to_filter_inventory_items.add(item) + elif 52 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(11769) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(11840) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _filter_received_brog_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 161): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(2194) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: + if 103 in inventory_item_values: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: + if 104 in inventory_item_values: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.BROGS_GRUE_EGG: + if self._read_game_state_value_for(2577) == 1: + to_filter_inventory_items.add(item) + elif 71 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(2641) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _filter_received_griff_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 160): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(4512) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: + if self._read_game_state_value_for(1301) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(1304) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(16562) == 1: + to_filter_inventory_items.add(item) + if item == ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: + if self._read_game_state_value_for(1374) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(1381) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(16562) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _filter_received_lucy_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 157): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(2198) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: + if 120 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: + if 121 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: + if 122 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: + if 123 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _read_game_state_value_for(self, key: int) -> Optional[int]: + try: + return self.game_state_manager.read_game_state_value_for(key) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to read game state key '{key}'") + raise e + + def _write_game_state_value_for(self, key: int, value: int) -> Optional[bool]: + try: + return self.game_state_manager.write_game_state_value_for(key, value) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to write '{key} = {value}' to game state") + raise e + + def _read_game_flags_value_for(self, key: int) -> Optional[int]: + try: + return self.game_state_manager.read_game_flags_value_for(key) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to read game flags key '{key}'") + raise e + + def _write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]: + try: + return self.game_state_manager.write_game_flags_value_for(key, value) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to write '{key} = {value}' to game flags") + raise e + + def _player_has(self, item: ZorkGrandInquisitorItems) -> bool: + return item in self.received_items + + def _player_doesnt_have(self, item: ZorkGrandInquisitorItems) -> bool: + return item not in self.received_items + + def _player_is_at(self, game_location: str) -> bool: + return self.game_state_manager.game_location == game_location + + def _player_is_afgncaap(self) -> bool: + return self._read_game_state_value_for(1596) == 1 + + def _player_is_totem(self) -> bool: + return self._player_is_brog() or self._player_is_griff() or self._player_is_lucy() + + def _player_is_brog(self) -> bool: + return self._read_game_state_value_for(1520) == 1 + + def _player_is_griff(self) -> bool: + return self._read_game_state_value_for(1296) == 1 + + def _player_is_lucy(self) -> bool: + return self._read_game_state_value_for(1524) == 1 diff --git a/worlds/zork_grand_inquisitor/game_state_manager.py b/worlds/zork_grand_inquisitor/game_state_manager.py new file mode 100644 index 000000000000..25b35969bf5e --- /dev/null +++ b/worlds/zork_grand_inquisitor/game_state_manager.py @@ -0,0 +1,370 @@ +from typing import Optional, Tuple + +from pymem import Pymem +from pymem.process import close_handle + + +class GameStateManager: + process_name = "scummvm.exe" + + process: Optional[Pymem] + is_process_running: bool + + script_manager_struct_address: int + render_manager_struct_address: int + + game_location: Optional[str] + game_location_offset: Optional[int] + + def __init__(self) -> None: + self.process = None + self.is_process_running = False + + self.script_manager_struct_address = 0x0 + self.render_manager_struct_address = 0x0 + + self.game_location = None + self.game_location_offset = None + + @property + def game_state_storage_pointer_address(self) -> int: + return self.script_manager_struct_address + 0x88 + + @property + def game_state_storage_address(self) -> int: + return self.process.read_longlong(self.game_state_storage_pointer_address) + + @property + def game_state_hashmap_size_address(self) -> int: + return self.script_manager_struct_address + 0x90 + + @property + def game_state_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x94 + + @property + def game_state_deleted_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x98 + + @property + def game_flags_storage_pointer_address(self) -> int: + return self.script_manager_struct_address + 0x120 + + @property + def game_flags_storage_address(self) -> int: + return self.process.read_longlong(self.game_flags_storage_pointer_address) + + @property + def game_flags_hashmap_size_address(self) -> int: + return self.script_manager_struct_address + 0x128 + + @property + def game_flags_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x12C + + @property + def game_flags_deleted_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x130 + + @property + def current_location_address(self) -> int: + return self.script_manager_struct_address + 0x400 + + @property + def current_location_offset_address(self) -> int: + return self.script_manager_struct_address + 0x404 + + @property + def next_location_address(self) -> int: + return self.script_manager_struct_address + 0x408 + + @property + def next_location_offset_address(self) -> int: + return self.script_manager_struct_address + 0x40C + + @property + def panorama_reversed_address(self) -> int: + return self.render_manager_struct_address + 0x1C + + def open_process_handle(self) -> bool: + try: + self.process = Pymem(self.process_name) + self.is_process_running = True + + self.script_manager_struct_address = self._resolve_address(0x5276600, (0xC8, 0x0)) + self.render_manager_struct_address = self._resolve_address(0x5276600, (0xD0, 0x120)) + except Exception: + return False + + return True + + def close_process_handle(self) -> bool: + if close_handle(self.process.process_handle): + self.is_process_running = False + self.process = None + + self.script_manager_struct_address = 0x0 + self.render_manager_struct_address = 0x0 + + return True + + return False + + def is_process_still_running(self) -> bool: + try: + self.process.read_int(self.process.base_address) + except Exception: + self.is_process_running = False + self.process = None + + self.script_manager_struct_address = 0x0 + self.render_manager_struct_address = 0x0 + + return False + + return True + + def read_game_state_value_for(self, key: int) -> Optional[int]: + return self.read_statemap_value_for(key, scope="game_state") + + def read_game_flags_value_for(self, key: int) -> Optional[int]: + return self.read_statemap_value_for(key, scope="game_flags") + + def read_statemap_value_for(self, key: int, scope: str = "game_state") -> Optional[int]: + if self.is_process_running: + offset: int + + address: int + address_value: int + + if scope == "game_state": + offset = self._get_game_state_address_read_offset_for(key) + + address = self.game_state_storage_address + offset + address_value = self.process.read_longlong(address) + elif scope == "game_flags": + offset = self._get_game_flags_address_read_offset_for(key) + + address = self.game_flags_storage_address + offset + address_value = self.process.read_longlong(address) + else: + raise ValueError(f"Invalid scope: {scope}") + + if address_value == 0: + return 0 + + statemap_value: int = self.process.read_int(address_value + 0x0) + statemap_key: int = self.process.read_int(address_value + 0x4) + + assert statemap_key == key + + return statemap_value + + return None + + def write_game_state_value_for(self, key: int, value: int) -> Optional[bool]: + return self.write_statemap_value_for(key, value, scope="game_state") + + def write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]: + return self.write_statemap_value_for(key, value, scope="game_flags") + + def write_statemap_value_for(self, key: int, value: int, scope: str = "game_state") -> Optional[bool]: + if self.is_process_running: + offset: int + is_existing_node: bool + is_reused_dummy_node: bool + + key_count_address: int + deleted_key_count_address: int + + storage_address: int + + if scope == "game_state": + offset, is_existing_node, is_reused_dummy_node = self._get_game_state_address_write_offset_for(key) + + key_count_address = self.game_state_key_count_address + deleted_key_count_address = self.game_state_deleted_key_count_address + + storage_address = self.game_state_storage_address + elif scope == "game_flags": + offset, is_existing_node, is_reused_dummy_node = self._get_game_flags_address_write_offset_for(key) + + key_count_address = self.game_flags_key_count_address + deleted_key_count_address = self.game_flags_deleted_key_count_address + + storage_address = self.game_flags_storage_address + else: + raise ValueError(f"Invalid scope: {scope}") + + statemap_key_count: int = self.process.read_int(key_count_address) + statemap_deleted_key_count: int = self.process.read_int(deleted_key_count_address) + + if value == 0: + if not is_existing_node: + return False + + self.process.write_longlong(storage_address + offset, 1) + + self.process.write_int(key_count_address, statemap_key_count - 1) + self.process.write_int(deleted_key_count_address, statemap_deleted_key_count + 1) + else: + if is_existing_node: + address_value: int = self.process.read_longlong(storage_address + offset) + self.process.write_int(address_value + 0x0, value) + else: + write_address: int = self.process.allocate(0x8) + + self.process.write_int(write_address + 0x0, value) + self.process.write_int(write_address + 0x4, key) + + self.process.write_longlong(storage_address + offset, write_address) + + self.process.write_int(key_count_address, statemap_key_count + 1) + + if is_reused_dummy_node: + self.process.write_int(deleted_key_count_address, statemap_deleted_key_count - 1) + + return True + + return None + + def refresh_game_location(self) -> Optional[bool]: + if self.is_process_running: + game_location_bytes: bytes = self.process.read_bytes(self.current_location_address, 4) + + self.game_location = game_location_bytes.decode("ascii") + self.game_location_offset = self.process.read_int(self.current_location_offset_address) + + return True + + return None + + def set_game_location(self, game_location: str, offset: int) -> Optional[bool]: + if self.is_process_running: + game_location_bytes: bytes = game_location.encode("ascii") + + self.process.write_bytes(self.next_location_address, game_location_bytes, 4) + self.process.write_int(self.next_location_offset_address, offset) + + return True + + return None + + def set_panorama_reversed(self, is_reversed: bool) -> Optional[bool]: + if self.is_process_running: + self.process.write_int(self.panorama_reversed_address, 1 if is_reversed else 0) + + return True + + return None + + def _resolve_address(self, base_offset: int, offsets: Tuple[int, ...]): + address: int = self.process.read_longlong(self.process.base_address + base_offset) + + for offset in offsets[:-1]: + address = self.process.read_longlong(address + offset) + + return address + offsets[-1] + + def _get_game_state_address_read_offset_for(self, key: int): + return self._get_statemap_address_read_offset_for(key, scope="game_state") + + def _get_game_flags_address_read_offset_for(self, key: int): + return self._get_statemap_address_read_offset_for(key, scope="game_flags") + + def _get_statemap_address_read_offset_for(self, key: int, scope: str = "game_state") -> int: + hashmap_size_address: int + storage_address: int + + if scope == "game_state": + hashmap_size_address = self.game_state_hashmap_size_address + storage_address = self.game_state_storage_address + elif scope == "game_flags": + hashmap_size_address = self.game_flags_hashmap_size_address + storage_address = self.game_flags_storage_address + else: + raise ValueError(f"Invalid scope: {scope}") + + statemap_hashmap_size: int = self.process.read_int(hashmap_size_address) + + perturb: int = key + perturb_shift: int = 0x5 + + index: int = key & statemap_hashmap_size + offset: int = index * 0x8 + + while True: + offset_value: int = self.process.read_longlong(storage_address + offset) + + if offset_value == 0: # Null Pointer + break + elif offset_value == 1: # Dummy Node + pass + elif offset_value > 1: # Existing Node + if self.process.read_int(offset_value + 0x4) == key: + break + + index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size + offset = index * 0x8 + + perturb >>= perturb_shift + + return offset + + def _get_game_state_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]: + return self._get_statemap_address_write_offset_for(key, scope="game_state") + + def _get_game_flags_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]: + return self._get_statemap_address_write_offset_for(key, scope="game_flags") + + def _get_statemap_address_write_offset_for(self, key: int, scope: str = "game_state") -> Tuple[int, bool, bool]: + hashmap_size_address: int + storage_address: int + + if scope == "game_state": + hashmap_size_address = self.game_state_hashmap_size_address + storage_address = self.game_state_storage_address + elif scope == "game_flags": + hashmap_size_address = self.game_flags_hashmap_size_address + storage_address = self.game_flags_storage_address + else: + raise ValueError(f"Invalid scope: {scope}") + + statemap_hashmap_size: int = self.process.read_int(hashmap_size_address) + + perturb: int = key + perturb_shift: int = 0x5 + + index: int = key & statemap_hashmap_size + offset: int = index * 0x8 + + node_found: bool = False + + dummy_node_found: bool = False + dummy_node_offset: Optional[int] = None + + while True: + offset_value: int = self.process.read_longlong(storage_address + offset) + + if offset_value == 0: # Null Pointer + break + elif offset_value == 1: # Dummy Node + if not dummy_node_found: + dummy_node_offset = offset + dummy_node_found = True + elif offset_value > 1: # Existing Node + if self.process.read_int(offset_value + 0x4) == key: + node_found = True + break + + index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size + offset = index * 0x8 + + perturb >>= perturb_shift + + if not node_found and dummy_node_found: # We should reuse the dummy node + return dummy_node_offset, False, True + elif not node_found and not dummy_node_found: # We should allocate a new node + return offset, False, False + + return offset, True, False # We should update the existing node diff --git a/worlds/zork_grand_inquisitor/options.py b/worlds/zork_grand_inquisitor/options.py new file mode 100644 index 000000000000..f06415199934 --- /dev/null +++ b/worlds/zork_grand_inquisitor/options.py @@ -0,0 +1,61 @@ +from dataclasses import dataclass + +from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Toggle + + +class Goal(Choice): + """ + Determines the victory condition + + Three Artifacts: Retrieve the three artifacts of magic and place them in the walking castle + """ + display_name: str = "Goal" + + default: int = 0 + option_three_artifacts: int = 0 + + +class QuickPortFoozle(DefaultOnToggle): + """If true, the items needed to go down the well will be found in early locations for a smoother early game""" + + display_name: str = "Quick Port Foozle" + + +class StartWithHotspotItems(DefaultOnToggle): + """ + If true, the player will be given all the hotspot items at the start of the game, effectively removing the need + to enable the important hotspots in the game before interacting with them. Recommended for beginners + + Note: The spots these hotspot items would have occupied in the item pool will instead be filled with junk items. + Expect a higher volume of filler items if you enable this option + """ + + display_name: str = "Start with Hotspot Items" + + +class Deathsanity(Toggle): + """If true, adds 16 player death locations to the world""" + + display_name: str = "Deathsanity" + + +class GrantMissableLocationChecks(Toggle): + """ + If true, performing an irreversible action will grant the locations checks that would have become unobtainable as a + result of that action when you meet the item requirements + + Otherwise, the player is expected to potentially have to use the save system to reach those location checks. If you + don't like the idea of rarely having to reload an earlier save to get a location check, make sure this option is + enabled + """ + + display_name: str = "Grant Missable Checks" + + +@dataclass +class ZorkGrandInquisitorOptions(PerGameCommonOptions): + goal: Goal + quick_port_foozle: QuickPortFoozle + start_with_hotspot_items: StartWithHotspotItems + deathsanity: Deathsanity + grant_missable_location_checks: GrantMissableLocationChecks diff --git a/worlds/zork_grand_inquisitor/requirements.txt b/worlds/zork_grand_inquisitor/requirements.txt new file mode 100644 index 000000000000..fe25267f6705 --- /dev/null +++ b/worlds/zork_grand_inquisitor/requirements.txt @@ -0,0 +1 @@ +Pymem>=1.13.0 \ No newline at end of file diff --git a/worlds/zork_grand_inquisitor/test/__init__.py b/worlds/zork_grand_inquisitor/test/__init__.py new file mode 100644 index 000000000000..c8ceda43a7bf --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/__init__.py @@ -0,0 +1,5 @@ +from test.bases import WorldTestBase + + +class ZorkGrandInquisitorTestBase(WorldTestBase): + game = "Zork Grand Inquisitor" diff --git a/worlds/zork_grand_inquisitor/test/test_access.py b/worlds/zork_grand_inquisitor/test/test_access.py new file mode 100644 index 000000000000..63a5f8c9ab1d --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/test_access.py @@ -0,0 +1,2927 @@ +from typing import List + +from . import ZorkGrandInquisitorTestBase + +from ..enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, +) + + +class AccessTestRegions(ZorkGrandInquisitorTestBase): + options = { + "start_with_hotspot_items": "false", + } + + def test_access_crossroads_to_dm_lair_sword(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_crossroads_to_dm_lair_teleporter(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_crossroads_to_gue_tech(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_crossroads_to_gue_tech_outside(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_crossroads_to_hades_shore(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_crossroads_to_port_foozle(self) -> None: + self._go_to_crossroads() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE.value)) + + def test_access_crossroads_to_spell_lab_bridge(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_crossroads_to_subway_crossroads(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SUBWAY_TOKEN.value, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_crossroads_to_subway_monastery(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_dm_lair_to_crossroads(self) -> None: + self._go_to_dm_lair() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_dm_lair_to_dm_lair_interior(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value, + ZorkGrandInquisitorItems.MEAD_LIGHT.value, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + def test_access_dm_lair_to_gue_tech_outside(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_dm_lair_to_hades_shore(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_dm_lair_to_spell_lab_bridge(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_dm_lair_to_subway_monastery(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_dm_lair_interior_to_dm_lair(self) -> None: + self._go_to_dm_lair_interior() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_dm_lair_interior_to_walking_castle(self) -> None: + self._go_to_dm_lair_interior() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.WALKING_CASTLE.value)) + + self._obtain_obidil() + + self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.WALKING_CASTLE.value)) + + def test_access_dm_lair_interior_to_white_house(self) -> None: + self._go_to_dm_lair_interior() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.WHITE_HOUSE.value)) + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.WHITE_HOUSE.value)) + + def test_access_dragon_archipelago_to_dragon_archipelago_dragon(self) -> None: + self._go_to_dragon_archipelago() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_GRIFF.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON.value)) + + def test_access_dragon_archipelago_to_hades_beyond_gates(self) -> None: + self._go_to_dragon_archipelago() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) + + def test_access_dragon_archipelago_dragon_to_dragon_archipelago(self) -> None: + self._go_to_dragon_archipelago_dragon() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) + + def test_access_dragon_archipelago_dragon_to_endgame(self) -> None: + self._go_to_dragon_archipelago_dragon() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, + ) + ) + + self._go_to_port_foozle_past_tavern() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, + ) + ) + + self._go_to_white_house() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_BROG.value, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, + ZorkGrandInquisitorItems.BROGS_PLANK.value, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + def test_access_gue_tech_to_crossroads(self) -> None: + self._go_to_gue_tech() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_gue_tech_to_gue_tech_hallway(self) -> None: + self._go_to_gue_tech() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_IGRAM.value, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) + + def test_access_gue_tech_to_gue_tech_outside(self) -> None: + self._go_to_gue_tech() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_gue_tech_hallway_to_gue_tech(self) -> None: + self._go_to_gue_tech_hallway() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_gue_tech_hallway_to_spell_lab_bridge(self) -> None: + self._go_to_gue_tech_hallway() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.STUDENT_ID.value, + ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_gue_tech_outside_to_crossroads(self) -> None: + self._go_to_gue_tech_outside() + + # Direct connection requires the map but indirect connection is free + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_gue_tech_outside_to_dm_lair(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_gue_tech_outside_to_gue_tech(self) -> None: + self._go_to_gue_tech_outside() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_gue_tech_outside_to_hades_shore(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_gue_tech_outside_to_spell_lab_bridge(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_gue_tech_outside_to_subway_monastery(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_hades_to_hades_beyond_gates(self) -> None: + self._go_to_hades() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) + + self._obtain_snavig() + + self.collect_by_name(ZorkGrandInquisitorItems.TOTEM_BROG.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) + + def test_access_hades_to_hades_shore(self) -> None: + self._go_to_hades() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_hades_beyond_gates_to_dragon_archipelago(self) -> None: + self._go_to_hades_beyond_gates() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) + + self._obtain_yastard() + + self.collect_by_name(ZorkGrandInquisitorItems.SPELL_NARWILE.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) + + def test_access_hades_beyond_gates_to_hades(self) -> None: + self._go_to_hades_beyond_gates() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) + + def test_access_hades_shore_to_crossroads(self) -> None: + self._go_to_hades_shore() + + # Direct connection requires the map but indirect connection is free + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_hades_shore_to_dm_lair(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_hades_shore_to_gue_tech_outside(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_hades_shore_to_hades(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value, + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) + + def test_access_hades_shore_to_spell_lab_bridge(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_hades_shore_to_subway_crossroads(self) -> None: + self._go_to_hades_shore() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_hades_shore_to_subway_flood_control_dam(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value)) + + def test_access_hades_shore_to_subway_monastery(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_monastery_to_hades_shore(self) -> None: + self._go_to_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_monastery_to_monastery_exhibit(self) -> None: + self._go_to_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) + + def test_access_monastery_to_subway_monastery(self) -> None: + self._go_to_monastery() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_monastery_exhibit_to_monastery(self) -> None: + self._go_to_monastery_exhibit() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) + + def test_access_monastery_exhibit_to_port_foozle_past(self) -> None: + self._go_to_monastery_exhibit() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) + + def test_access_port_foozle_to_crossroads(self) -> None: + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ZorkGrandInquisitorItems.ROPE.value, + ZorkGrandInquisitorItems.HOTSPOT_WELL.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_port_foozle_to_port_foozle_jacks_shop(self) -> None: + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP.value)) + + def test_access_port_foozle_jacks_shop_to_port_foozle(self) -> None: + self._go_to_port_foozle_jacks_shop() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE.value)) + + def test_access_port_foozle_past_to_monastery_exhibit(self) -> None: + self._go_to_port_foozle_past() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) + + def test_access_port_foozle_past_to_port_foozle_past_tavern(self) -> None: + self._go_to_port_foozle_past() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_LUCY.value, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN.value)) + + def test_access_port_foozle_past_tavern_to_endgame(self) -> None: + self._go_to_port_foozle_past_tavern() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, + ) + ) + + self._go_to_dragon_archipelago_dragon() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, + ) + ) + + self._go_to_white_house() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_BROG.value, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, + ZorkGrandInquisitorItems.BROGS_PLANK.value, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + def test_access_port_foozle_past_tavern_to_port_foozle_past(self) -> None: + self._go_to_port_foozle_past_tavern() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) + + def test_access_spell_lab_to_spell_lab_bridge(self) -> None: + self._go_to_spell_lab() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_spell_lab_bridge_to_crossroads(self) -> None: + self._go_to_spell_lab_bridge() + + # Direct connection requires the map but indirect connection is free + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_spell_lab_bridge_to_dm_lair(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_spell_lab_bridge_to_gue_tech_outside(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_spell_lab_bridge_to_gue_tech_hallway(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) + + def test_access_spell_lab_bridge_to_hades_shore(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_spell_lab_bridge_to_spell_lab(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB.value)) + + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB.value)) + + def test_access_spell_lab_bridge_to_subway_monastery(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_subway_crossroads_to_crossroads(self) -> None: + self._go_to_subway_crossroads() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_subway_crossroads_to_hades_shore(self) -> None: + self._go_to_subway_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_subway_crossroads_to_subway_flood_control_dam(self) -> None: + self._go_to_subway_crossroads() + + self.assertFalse( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value, + ) + ) + + self.assertTrue( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + def test_access_subway_crossroads_to_subway_monastery(self) -> None: + self._go_to_subway_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_subway_flood_control_dam_to_hades_shore(self) -> None: + self._go_to_subway_flood_control_dam() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_subway_flood_control_dam_to_subway_crossroads(self) -> None: + self._go_to_subway_flood_control_dam() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_subway_flood_control_dam_to_subway_monastery(self) -> None: + self._go_to_subway_flood_control_dam() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_subway_monastery_to_hades_shore(self) -> None: + self._go_to_subway_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_subway_monastery_to_monastery(self) -> None: + self._go_to_subway_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.SPELL_GLORF.value, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) + + def test_access_subway_monastery_to_subway_crossroads(self) -> None: + self._go_to_subway_monastery() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_subway_monastery_to_subway_flood_control_dam(self) -> None: + self._go_to_subway_monastery() + + self.assertFalse( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value) + + self.assertTrue( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + def test_access_walking_castle_to_dm_lair_interior(self) -> None: + self._go_to_walking_castle() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + def test_access_white_house_to_dm_lair_interior(self) -> None: + self._go_to_white_house() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + def test_access_white_house_to_endgame(self) -> None: + self._go_to_white_house() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_BROG.value, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, + ZorkGrandInquisitorItems.BROGS_PLANK.value, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, + ) + ) + + self._go_to_dragon_archipelago_dragon() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, + ) + ) + + self._go_to_port_foozle_past_tavern() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + def _go_to_crossroads(self) -> None: + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ZorkGrandInquisitorItems.ROPE.value, + ZorkGrandInquisitorItems.HOTSPOT_WELL.value, + ) + ) + + def _go_to_dm_lair(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value, + ) + ) + + def _go_to_dm_lair_interior(self) -> None: + self._go_to_dm_lair() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value, + ZorkGrandInquisitorItems.MEAD_LIGHT.value, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value, + ) + ) + + def _go_to_dragon_archipelago(self) -> None: + self._go_to_hades_beyond_gates() + self._obtain_yastard() + + self.collect_by_name(ZorkGrandInquisitorItems.SPELL_NARWILE.value) + + def _go_to_dragon_archipelago_dragon(self) -> None: + self._go_to_dragon_archipelago() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_GRIFF.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value, + ) + ) + + def _go_to_gue_tech(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value, + ) + ) + + def _go_to_gue_tech_hallway(self) -> None: + self._go_to_gue_tech() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_IGRAM.value, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value, + ) + ) + + def _go_to_gue_tech_outside(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + def _go_to_hades(self) -> None: + self._go_to_hades_shore() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value, + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, + ) + ) + + def _go_to_hades_beyond_gates(self) -> None: + self._go_to_hades() + self._obtain_snavig() + + self.collect_by_name(ZorkGrandInquisitorItems.TOTEM_BROG.value) + + def _go_to_hades_shore(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + def _go_to_monastery(self) -> None: + self._go_to_subway_monastery() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.SPELL_GLORF.value, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value, + ) + ) + + def _go_to_monastery_exhibit(self) -> None: + self._go_to_monastery() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, + ) + ) + + def _go_to_port_foozle_jacks_shop(self) -> None: + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ) + ) + + def _go_to_port_foozle_past(self) -> None: + self._go_to_monastery_exhibit() + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + def _go_to_port_foozle_past_tavern(self) -> None: + self._go_to_port_foozle_past() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_LUCY.value, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value, + ) + ) + + def _go_to_spell_lab(self) -> None: + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ) + ) + + self._go_to_spell_lab_bridge() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ) + ) + + def _go_to_spell_lab_bridge(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + def _go_to_subway_crossroads(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SUBWAY_TOKEN.value, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value, + ) + ) + + def _go_to_subway_flood_control_dam(self) -> None: + self._go_to_subway_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value, + ) + ) + + def _go_to_subway_monastery(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + def _go_to_white_house(self) -> None: + self._go_to_dm_lair_interior() + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + def _go_to_walking_castle(self) -> None: + self._go_to_dm_lair_interior() + + self._obtain_obidil() + self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value) + + def _obtain_obidil(self) -> None: + self._go_to_crossroads() + self._go_to_gue_tech() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT.value, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS.value, + ) + ) + + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ) + ) + + self._go_to_spell_lab_bridge() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value, + ) + ) + + def _obtain_snavig(self) -> None: + self._go_to_crossroads() + self._go_to_dm_lair_interior() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS.value, + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV.value, + ZorkGrandInquisitorItems.HOTSPOT_MIRROR.value, + ) + ) + + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ) + ) + + self._go_to_spell_lab_bridge() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value, + ) + ) + + def _obtain_yastard(self) -> None: + self._go_to_crossroads() + self._go_to_dm_lair_interior() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE.value, + ZorkGrandInquisitorItems.HUNGUS_LARD.value, + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS.value, + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB.value, + ZorkGrandInquisitorItems.MOSS_OF_MAREILON.value, + ZorkGrandInquisitorItems.MUG.value, + ) + ) + + +class AccessTestLocations(ZorkGrandInquisitorTestBase): + options = { + "deathsanity": "true", + "start_with_hotspot_items": "false", + } + + def test_access_locations_requiring_brogs_flickering_torch(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value,)] + ) + + def test_access_locations_requiring_brogs_grue_egg(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value,)] + ) + + def test_access_locations_requiring_brogs_plank(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.BROGS_PLANK.value,)] + ) + + def test_access_locations_requiring_flatheadia_fudge(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.FLATHEADIA_FUDGE.value,)] + ) + + def test_access_locations_requiring_griffs_air_pump(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value,)] + ) + + def test_access_locations_requiring_griffs_dragon_tooth(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value,)] + ) + + def test_access_locations_requiring_griffs_inflatable_raft(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value,)] + ) + + def test_access_locations_requiring_griffs_inflatable_sea_captain(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value,)] + ) + + def test_access_locations_requiring_hammer(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HAMMER.value,)] + ) + + def test_access_locations_requiring_hungus_lard(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HUNGUS_LARD.value,)] + ) + + def test_access_locations_requiring_jar_of_hotbugs(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.JAR_OF_HOTBUGS.value,)] + ) + + def test_access_locations_requiring_lantern(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LANTERN.value,)] + ) + + def test_access_locations_requiring_large_telegraph_hammer(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value,)] + ) + + def test_access_locations_requiring_lucys_playing_cards(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value,)] + ) + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value,)] + ) + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value,)] + ) + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value,)] + ) + + def test_access_locations_requiring_map(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MAP.value,)] + ) + + def test_access_locations_requiring_mead_light(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.MEAD_LIGHT.value, + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MEAD_LIGHT.value,)] + ) + + def test_access_locations_requiring_moss_of_mareilon(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MOSS_OF_MAREILON.value,)] + ) + + def test_access_locations_requiring_mug(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MUG.value,)] + ) + + def test_access_locations_requiring_old_scratch_card(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH.value, + ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER.value, + ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD.value,)] + ) + + def test_access_locations_requiring_perma_suck_machine(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE.value,)] + ) + + def test_access_locations_requiring_plastic_six_pack_holder(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE.value, + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER.value,)] + ) + + def test_access_locations_requiring_pouch_of_zorkmids(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value,)] + ) + + def test_access_locations_requiring_prozork_tablet(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.PROZORKED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.PROZORK_TABLET.value,)] + ) + + def test_access_locations_requiring_quelbee_honeycomb(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB.value,)] + ) + + def test_access_locations_requiring_rope(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN.value, + ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED.value, + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, + ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES.value, + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED.value, + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE.value, + ZorkGrandInquisitorLocations.CAVES_NOTES.value, + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL.value, + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE.value, + ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED.value, + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.EGGPLANTS.value, + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE.value, + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY.value, + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, + ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST.value, + ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM.value, + ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO.value, + ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING.value, + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE.value, + ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF.value, + ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, + ZorkGrandInquisitorLocations.MAGIC_FOREVER.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, + ZorkGrandInquisitorLocations.MIKES_PANTS.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR.value, + ZorkGrandInquisitorLocations.NO_BONDAGE.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.PROZORKED.value, + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY.value, + ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE.value, + ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THATS_A_ROPE.value, + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS.value, + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB.value, + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.ROPE.value,)] + ) + + def test_access_locations_requiring_scroll_fragment_ans(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS.value,)] + ) + + def test_access_locations_requiring_scroll_fragment_giv(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV.value,)] + ) + + def test_access_locations_requiring_shovel(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SHOVEL.value,)] + ) + + def test_access_locations_requiring_snapdragon(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SNAPDRAGON.value,)] + ) + + def test_access_locations_requiring_student_id(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.STUDENT_ID.value,)] + ) + + def test_access_locations_requiring_subway_token(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_TOKEN.value,)] + ) + + def test_access_locations_requiring_sword(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SWORD.value,)] + ) + + def test_access_locations_requiring_zimdor_scroll(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value,)] + ) + + def test_access_locations_requiring_zork_rocks(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.ZORK_ROCKS.value,)] + ) + + def test_access_locations_requiring_hotspot_666_mailbox(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX.value,)] + ) + + def test_access_locations_requiring_hotspot_alpines_quandry_card_slots(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value,)] + ) + + def test_access_locations_requiring_hotspot_blank_scroll_box(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX.value,)] + ) + + def test_access_locations_requiring_hotspot_blinds(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value,)] + ) + + def test_access_locations_requiring_hotspot_candy_machine_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_candy_machine_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_candy_machine_vacuum_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_change_machine_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_closet_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_closing_the_time_tunnels_hammer_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_closing_the_time_tunnels_lever(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value,)] + ) + + def test_access_locations_requiring_hotspot_cooking_pot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value,)] + ) + + def test_access_locations_requiring_hotspot_dented_locker(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER.value,)] + ) + + def test_access_locations_requiring_hotspot_dirt_mound(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND.value,)] + ) + + def test_access_locations_requiring_hotspot_dock_winch(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE.value, + ZorkGrandInquisitorLocations.NO_BONDAGE.value, + ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH.value,)] + ) + + def test_access_locations_requiring_hotspot_dragon_claw(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value,)] + ) + + def test_access_locations_requiring_hotspot_dragon_nostrils(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value,)] + ) + + def test_access_locations_requiring_hotspot_dungeon_masters_lair_entrance(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value,)] + ) + + def test_access_locations_requiring_hotspot_flood_control_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_flood_control_doors(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value,)] + ) + + def test_access_locations_requiring_hotspot_frozen_treat_machine_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_frozen_treat_machine_doors(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS.value,)] + ) + + def test_access_locations_requiring_hotspot_glass_case(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE.value,)] + ) + + def test_access_locations_requiring_hotspot_grand_inquisitor_doll(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.ARREST_THE_VANDAL.value, + ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK.value, + ZorkGrandInquisitorLocations.FIRE_FIRE.value, + ZorkGrandInquisitorLocations.PLANETFALL.value, + ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR.value, + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value,)] + ) + + def test_access_locations_requiring_hotspot_gue_tech_door(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_gue_tech_grass(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS.value,)] + ) + + def test_access_locations_requiring_hotspot_hades_phone_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_hades_phone_receiver(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value,)] + ) + + def test_access_locations_requiring_hotspot_harry(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRY.value,)] + ) + + def test_access_locations_requiring_hotspot_harrys_ashtray(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value,)] + ) + + def test_access_locations_requiring_hotspot_harrys_bird_bath(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value,)] + ) + + def test_access_locations_requiring_hotspot_in_magic_we_trust_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_jacks_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.MEAD_LIGHT.value, + ZorkGrandInquisitorLocations.NO_AUTOGRAPHS.value, + ZorkGrandInquisitorLocations.THATS_A_ROPE.value, + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID.value, + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_loudspeaker_volume_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THATS_THE_SPIRIT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_mailbox_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_mailbox_flag(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG.value,)] + ) + + def test_access_locations_requiring_hotspot_mirror(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MIRROR.value,)] + ) + + def test_access_locations_requiring_hotspot_monastery_vent(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value,)] + ) + + def test_access_locations_requiring_hotspot_mossy_grate(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE.value,)] + ) + + def test_access_locations_requiring_hotspot_port_foozle_past_tavern_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_purple_words(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value,)] + ) + + def test_access_locations_requiring_hotspot_quelbee_hive(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE.value,)] + ) + + def test_access_locations_requiring_hotspot_rope_bridge(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value,)] + ) + + def test_access_locations_requiring_hotspot_skull_cage(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value,)] + ) + + def test_access_locations_requiring_hotspot_snapdragon(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.PROZORKED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON.value,)] + ) + + def test_access_locations_requiring_hotspot_soda_machine_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_soda_machine_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_souvenir_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.SOUVENIR.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_spell_checker(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value,)] + ) + + def test_access_locations_requiring_hotspot_spell_lab_chasm(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value,)] + ) + + def test_access_locations_requiring_hotspot_spring_mushroom(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM.value,)] + ) + + def test_access_locations_requiring_hotspot_student_id_machine(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE.value,)] + ) + + def test_access_locations_requiring_hotspot_subway_token_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_tavern_fly(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value,)] + ) + + def test_access_locations_requiring_hotspot_totemizer_switch(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value,)] + ) + + def test_access_locations_requiring_hotspot_totemizer_wheels(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value,)] + ) + + def test_access_locations_requiring_hotspot_well(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN.value, + ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED.value, + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, + ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES.value, + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED.value, + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE.value, + ZorkGrandInquisitorLocations.CAVES_NOTES.value, + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL.value, + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE.value, + ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED.value, + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.EGGPLANTS.value, + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE.value, + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY.value, + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, + ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST.value, + ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM.value, + ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO.value, + ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING.value, + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE.value, + ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF.value, + ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, + ZorkGrandInquisitorLocations.MAGIC_FOREVER.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, + ZorkGrandInquisitorLocations.MIKES_PANTS.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.PROZORKED.value, + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY.value, + ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE.value, + ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS.value, + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB.value, + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_WELL.value,)] + ) + + def test_access_locations_requiring_spell_glorf(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_GLORF.value,)] + ) + + def test_access_locations_requiring_spell_golgatem(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_GOLGATEM.value,)] + ) + + def test_access_locations_requiring_spell_igram(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_IGRAM.value,)] + ) + + def test_access_locations_requiring_spell_kendall(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_KENDALL.value,)] + ) + + def test_access_locations_requiring_spell_narwile(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_NARWILE.value,)] + ) + + def test_access_locations_requiring_spell_rezrov(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_REZROV.value,)] + ) + + def test_access_locations_requiring_spell_throck(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_THROCK.value,)] + ) + + def test_access_locations_requiring_subway_destination_flood_control_dam(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value,)] + ) + + def test_access_locations_requiring_subway_destination_hades(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value,)] + ) + + def test_access_locations_requiring_subway_destination_monastery(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_dm_lair(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_gue_tech(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_hades(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_monastery(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_spell_lab(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value,)] + ) + + def test_access_locations_requiring_totemizer_destination_hall_of_inquisition(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value,)] + ) + + def test_access_locations_requiring_totemizer_destination_straight_to_hell(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL.value,)] + ) + + def test_access_locations_requiring_totem_brog(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEM_BROG.value,)] + ) + + def test_access_locations_requiring_totem_griff(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEM_GRIFF.value,)] + ) + + def test_access_locations_requiring_totem_lucy(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEM_LUCY.value,)] + ) diff --git a/worlds/zork_grand_inquisitor/test/test_data_funcs.py b/worlds/zork_grand_inquisitor/test/test_data_funcs.py new file mode 100644 index 000000000000..9d8d5a4ba356 --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/test_data_funcs.py @@ -0,0 +1,132 @@ +import unittest + +from ..data_funcs import location_access_rule_for, entrance_access_rule_for +from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorRegions + + +class DataFuncsTest(unittest.TestCase): + def test_location_access_rule_for(self) -> None: + # No Requirements + self.assertEqual( + "lambda state: True", + location_access_rule_for(ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN, 1), + ) + + # Single Item Requirement + self.assertEqual( + 'lambda state: state.has("Sword", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Spell: NARWILE", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL, 1), + ) + + # Single Event Requirement + self.assertEqual( + 'lambda state: state.has("Event: Knows OBIDIL", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Event: Dunce Locker Openable", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES, 1), + ) + + # Multiple Item Requirements + self.assertEqual( + 'lambda state: state.has("Hotspot: Purple Words", 1) and state.has("Spell: IGRAM", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.A_SMALLWAY, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Hotspot: Mossy Grate", 1) and state.has("Spell: THROCK", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY, 1), + ) + + # Multiple Item Requirements OR + self.assertEqual( + 'lambda state: (state.has("Totem: Griff", 1) or state.has("Totem: Lucy", 1)) and state.has("Hotspot: Mailbox Door", 1) and state.has("Hotspot: Mailbox Flag", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL, 1), + ) + + # Multiple Mixed Requirements + self.assertEqual( + 'lambda state: state.has("Event: Cigar Accessible", 1) and state.has("Hotspot: Grand Inquisitor Doll", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Sword", 1) and state.has("Event: Rope GLORFable", 1) and state.has("Hotspot: Monastery Vent", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE, 1), + ) + + def test_entrance_access_rule_for(self) -> None: + # No Requirements + self.assertEqual( + "lambda state: True", + entrance_access_rule_for( + ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE, 1 + ), + ) + + self.assertEqual( + "lambda state: True", + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS, 1 + ), + ) + + # Single Requirement + self.assertEqual( + 'lambda state: (state.has("Map", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS, 1 + ), + ) + + self.assertEqual( + 'lambda state: (state.has("Map", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS, 1 + ), + ) + + # Multiple Requirements AND + self.assertEqual( + 'lambda state: (state.has("Spell: REZROV", 1) and state.has("Hotspot: In Magic We Trust Door", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH, 1 + ), + ) + + self.assertEqual( + 'lambda state: (state.has("Event: Door Smoked Cigar", 1) and state.has("Event: Door Drank Mead", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, 1 + ), + ) + + self.assertEqual( + 'lambda state: (state.has("Hotspot: Closet Door", 1) and state.has("Spell: NARWILE", 1) and state.has("Event: Knows YASTARD", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE, 1 + ), + ) + + # Multiple Requirements AND + OR + self.assertEqual( + 'lambda state: (state.has("Sword", 1) and state.has("Hotspot: Dungeon Master\'s Lair Entrance", 1)) or (state.has("Map", 1) and state.has("Teleporter Destination: Dungeon Master\'s Lair", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR, 1 + ), + ) + + # Multiple Requirements Regions + self.assertEqual( + 'lambda state: (state.has("Griff\'s Air Pump", 1) and state.has("Griff\'s Inflatable Raft", 1) and state.has("Griff\'s Inflatable Sea Captain", 1) and state.has("Hotspot: Dragon Nostrils", 1) and state.has("Griff\'s Dragon Tooth", 1) and state.can_reach("Port Foozle Past - Tavern", "Region", 1) and state.has("Lucy\'s Playing Card: 1 Pip", 1) and state.has("Lucy\'s Playing Card: 2 Pips", 1) and state.has("Lucy\'s Playing Card: 3 Pips", 1) and state.has("Lucy\'s Playing Card: 4 Pips", 1) and state.has("Hotspot: Tavern Fly", 1) and state.has("Hotspot: Alpine\'s Quandry Card Slots", 1) and state.can_reach("White House", "Region", 1) and state.has("Totem: Brog", 1) and state.has("Brog\'s Flickering Torch", 1) and state.has("Brog\'s Grue Egg", 1) and state.has("Hotspot: Cooking Pot", 1) and state.has("Brog\'s Plank", 1) and state.has("Hotspot: Skull Cage", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME, 1 + ), + ) diff --git a/worlds/zork_grand_inquisitor/test/test_locations.py b/worlds/zork_grand_inquisitor/test/test_locations.py new file mode 100644 index 000000000000..fa576dd510dc --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/test_locations.py @@ -0,0 +1,49 @@ +from typing import Dict, Set + +from . import ZorkGrandInquisitorTestBase + +from ..data_funcs import location_names_to_location, locations_with_tag +from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorTags + + +class LocationsTestNoDeathsanity(ZorkGrandInquisitorTestBase): + options = { + "deathsanity": "false", + } + + def test_correct_locations_exist(self) -> None: + expected_locations: Set[ZorkGrandInquisitorLocations] = locations_with_tag( + ZorkGrandInquisitorTags.CORE + ) + + self._assert_expected_locations_exist(expected_locations) + + def _assert_expected_locations_exist(self, expected_locations: Set[ZorkGrandInquisitorLocations]) -> None: + location_name_to_location: Dict[str, ZorkGrandInquisitorLocations] = location_names_to_location() + + for location_object in self.multiworld.get_locations(1): + location: ZorkGrandInquisitorLocations = location_name_to_location.get( + location_object.name + ) + + if location is None: + continue + + self.assertIn(location, expected_locations) + + expected_locations.remove(location) + + self.assertEqual(0, len(expected_locations)) + + +class LocationsTestDeathsanity(LocationsTestNoDeathsanity): + options = { + "deathsanity": "true", + } + + def test_correct_locations_exist(self) -> None: + expected_locations: Set[ZorkGrandInquisitorLocations] = ( + locations_with_tag(ZorkGrandInquisitorTags.CORE) | locations_with_tag(ZorkGrandInquisitorTags.DEATHSANITY) + ) + + self._assert_expected_locations_exist(expected_locations) diff --git a/worlds/zork_grand_inquisitor/world.py b/worlds/zork_grand_inquisitor/world.py new file mode 100644 index 000000000000..a93f2c2134c1 --- /dev/null +++ b/worlds/zork_grand_inquisitor/world.py @@ -0,0 +1,205 @@ +from typing import Any, Dict, List, Tuple + +from BaseClasses import Item, ItemClassification, Location, Region, Tutorial + +from worlds.AutoWorld import WebWorld, World + +from .data.item_data import item_data, ZorkGrandInquisitorItemData +from .data.location_data import location_data, ZorkGrandInquisitorLocationData +from .data.region_data import region_data + +from .data_funcs import ( + item_names_to_id, + item_names_to_item, + location_names_to_id, + item_groups, + items_with_tag, + location_groups, + locations_by_region, + location_access_rule_for, + entrance_access_rule_for, +) + +from .enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorTags, +) + +from .options import ZorkGrandInquisitorOptions + + +class ZorkGrandInquisitorItem(Item): + game = "Zork Grand Inquisitor" + + +class ZorkGrandInquisitorLocation(Location): + game = "Zork Grand Inquisitor" + + +class ZorkGrandInquisitorWebWorld(WebWorld): + theme: str = "stone" + + tutorials: List[Tutorial] = [ + Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Zork Grand Inquisitor randomizer connected to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["Serpent.AI"], + ) + ] + + +class ZorkGrandInquisitorWorld(World): + """ + Zork: Grand Inquisitor is a 1997 point-and-click adventure game for PC. + Magic has been banned from the great Underground Empire of Zork. By edict of the Grand Inquisitor Mir Yannick, the + Empire has been sealed off and the practice of mystic arts declared punishable by "Totemization" (a very bad thing). + The only way to restore magic to the kingdom is to find three hidden artifacts: The Coconut of Quendor, The Cube of + Foundation, and The Skull of Yoruk. + """ + + options_dataclass = ZorkGrandInquisitorOptions + options: ZorkGrandInquisitorOptions + + game = "Zork Grand Inquisitor" + + item_name_to_id = item_names_to_id() + location_name_to_id = location_names_to_id() + + item_name_groups = item_groups() + location_name_groups = location_groups() + + required_client_version: Tuple[int, int, int] = (0, 4, 4) + + web = ZorkGrandInquisitorWebWorld() + + filler_item_names: List[str] = item_groups()["Filler"] + item_name_to_item: Dict[str, ZorkGrandInquisitorItems] = item_names_to_item() + + def create_regions(self) -> None: + deathsanity: bool = bool(self.options.deathsanity) + + region_mapping: Dict[ZorkGrandInquisitorRegions, Region] = dict() + + region_enum_item: ZorkGrandInquisitorRegions + for region_enum_item in region_data.keys(): + region_mapping[region_enum_item] = Region(region_enum_item.value, self.player, self.multiworld) + + region_locations_mapping: Dict[ZorkGrandInquisitorRegions, List[ZorkGrandInquisitorLocations]] + region_locations_mapping = locations_by_region(include_deathsanity=deathsanity) + + region_enum_item: ZorkGrandInquisitorRegions + region: Region + for region_enum_item, region in region_mapping.items(): + regions_locations: List[ZorkGrandInquisitorLocations] = region_locations_mapping[region_enum_item] + + # Locations + location_enum_item: ZorkGrandInquisitorLocations + for location_enum_item in regions_locations: + data: ZorkGrandInquisitorLocationData = location_data[location_enum_item] + + location: ZorkGrandInquisitorLocation = ZorkGrandInquisitorLocation( + self.player, + location_enum_item.value, + data.archipelago_id, + region_mapping[data.region], + ) + + if isinstance(location_enum_item, ZorkGrandInquisitorEvents): + location.place_locked_item( + ZorkGrandInquisitorItem( + data.event_item_name, + ItemClassification.progression, + None, + self.player, + ) + ) + + location_access_rule: str = location_access_rule_for(location_enum_item, self.player) + + if location_access_rule != "lambda state: True": + location.access_rule = eval(location_access_rule) + + region.locations.append(location) + + # Connections + region_exit: ZorkGrandInquisitorRegions + for region_exit in region_data[region_enum_item].exits or tuple(): + entrance_access_rule: str = entrance_access_rule_for(region_enum_item, region_exit, self.player) + + if entrance_access_rule == "lambda state: True": + region.connect(region_mapping[region_exit]) + else: + region.connect(region_mapping[region_exit], rule=eval(entrance_access_rule)) + + self.multiworld.regions.append(region) + + def create_items(self) -> None: + quick_port_foozle: bool = bool(self.options.quick_port_foozle) + start_with_hotspot_items: bool = bool(self.options.start_with_hotspot_items) + + item_pool: List[ZorkGrandInquisitorItem] = list() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + tags: Tuple[ZorkGrandInquisitorTags, ...] = data.tags or tuple() + + if ZorkGrandInquisitorTags.FILLER in tags: + continue + elif ZorkGrandInquisitorTags.HOTSPOT in tags and start_with_hotspot_items: + continue + + item_pool.append(self.create_item(item.value)) + + total_locations: int = len(self.multiworld.get_unfilled_locations(self.player)) + item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))] + + self.multiworld.itempool += item_pool + + if quick_port_foozle: + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.ROPE.value] = 1 + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.LANTERN.value] = 1 + + if not start_with_hotspot_items: + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_WELL.value] = 1 + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value] = 1 + + self.multiworld.early_items[self.player][ + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value + ] = 1 + + if start_with_hotspot_items: + item: ZorkGrandInquisitorItems + for item in items_with_tag(ZorkGrandInquisitorTags.HOTSPOT): + self.multiworld.push_precollected(self.create_item(item.value)) + + def create_item(self, name: str) -> ZorkGrandInquisitorItem: + data: ZorkGrandInquisitorItemData = item_data[self.item_name_to_item[name]] + + return ZorkGrandInquisitorItem( + name, + data.classification, + data.archipelago_id, + self.player, + ) + + def generate_basic(self) -> None: + self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) + + def fill_slot_data(self) -> Dict[str, Any]: + return self.options.as_dict( + "goal", + "quick_port_foozle", + "start_with_hotspot_items", + "deathsanity", + "grant_missable_location_checks", + ) + + def get_filler_item_name(self) -> str: + return self.random.choice(self.filler_item_names) From aab96bb9914f45278a30a3d3e153b0e9cc8d12db Mon Sep 17 00:00:00 2001 From: FlySniper Date: Sat, 3 Aug 2024 13:21:28 -0400 Subject: [PATCH 02/89] Wargroove 2: Updated README.md and CODEOWNERS to include Wargroove 2. --- README.md | 1 + docs/CODEOWNERS | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index cebd4f7e7529..453a33bffd94 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Currently, the following games are supported: * Aquaria * Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 * A Hat in Time +* Wargroove 2 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 diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index ab841e65ee4c..dc8d5f2b5d9a 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -190,6 +190,9 @@ # Wargroove /worlds/wargroove/ @FlySniper +# Wargroove 2 +/worlds/wargroove2/ @FlySniper + # The Witness /worlds/witness/ @NewSoupVi @blastron From 2f09b586322a065b3f06d4401d3d05fee8abd087 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Sat, 3 Aug 2024 16:01:35 -0400 Subject: [PATCH 03/89] Wargroove 2: Fixed erroneous adjustments to BaseClasses.py --- BaseClasses.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 388f13ff95db..a0c243c0fd9d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -37,7 +37,6 @@ class Group(TypedDict, total=False): class ThreadBarrierProxy: """Passes through getattr while passthrough is True""" - def __init__(self, obj: object) -> None: self.passthrough = True self.obj = obj @@ -98,9 +97,9 @@ class RegionManager: location_cache: Dict[int, Dict[str, Location]] def __init__(self, players: int): - self.region_cache = {player: {} for player in range(1, players + 1)} - self.entrance_cache = {player: {} for player in range(1, players + 1)} - self.location_cache = {player: {} for player in range(1, players + 1)} + self.region_cache = {player: {} for player in range(1, players+1)} + self.entrance_cache = {player: {} for player in range(1, players+1)} + self.location_cache = {player: {} for player in range(1, players+1)} def __iadd__(self, other: Iterable[Region]): self.extend(other) @@ -167,7 +166,7 @@ def set_player_attr(attr, val): set_player_attr('completion_condition', lambda state: True) self.worlds = {} self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the " - "world's random object instead (usually self.random)") + "world's random object instead (usually self.random)") self.plando_options = PlandoOptions.none def get_all_ids(self) -> Tuple[int, ...]: @@ -452,8 +451,7 @@ def find_item(self, item, player: int) -> Location: return next(location for location in self.get_locations() if location.item and location.item.name == item and location.item.player == player) - def find_items_in_locations(self, items: Set[str], player: int, resolve_group_locations: bool = False) -> List[ - Location]: + def find_items_in_locations(self, items: Set[str], player: int, resolve_group_locations: bool = False) -> List[Location]: if resolve_group_locations: player_groups = self.get_player_groups(player) return [location for location in self.get_locations() if @@ -500,15 +498,13 @@ def get_unfilled_locations(self, player: Optional[int] = None) -> List[Location] def get_filled_locations(self, player: Optional[int] = None) -> List[Location]: return [location for location in self.get_locations(player) if location.item is not None] - def get_reachable_locations(self, state: Optional[CollectionState] = None, player: Optional[int] = None) -> List[ - Location]: + def get_reachable_locations(self, state: Optional[CollectionState] = None, player: Optional[int] = None) -> List[Location]: state: CollectionState = state if state else self.state return [location for location in self.get_locations(player) if location.can_reach(state)] def get_placeable_locations(self, state=None, player=None) -> List[Location]: state: CollectionState = state if state else self.state - return [location for location in self.get_locations(player) if - location.item is None and location.can_reach(state)] + return [location for location in self.get_locations(player) if location.item is None and location.can_reach(state)] def get_unfilled_locations_for_players(self, location_names: List[str], players: Iterable[int]): for player in players: @@ -967,7 +963,7 @@ class LocationRegister(Register): def __delitem__(self, index: int) -> None: location: Location = self._list.__getitem__(index) self._list.__delitem__(index) - del (self.region_manager.location_cache[location.player][location.name]) + del(self.region_manager.location_cache[location.player][location.name]) def insert(self, index: int, value: Location) -> None: assert value.name not in self.region_manager.location_cache[value.player], \ @@ -979,7 +975,7 @@ class EntranceRegister(Register): def __delitem__(self, index: int) -> None: entrance: Entrance = self._list.__getitem__(index) self._list.__delitem__(index) - del (self.region_manager.entrance_cache[entrance.player][entrance.name]) + del(self.region_manager.entrance_cache[entrance.player][entrance.name]) def insert(self, index: int, value: Entrance) -> None: assert value.name not in self.region_manager.entrance_cache[value.player], \ @@ -1094,8 +1090,7 @@ def __repr__(self): return self.__str__() def __str__(self): - return self.multiworld.get_name_string_for_object( - self) if self.multiworld else f'{self.name} (Player {self.player})' + return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})' class LocationProgressType(IntEnum): From 8f60ee0022368ff2047a0d2d391924dfc769df76 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 17:42:09 -0400 Subject: [PATCH 04/89] Update worlds/wargroove2/Items.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Items.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/worlds/wargroove2/Items.py b/worlds/wargroove2/Items.py index 301aa40802ec..94ae61ffe77b 100644 --- a/worlds/wargroove2/Items.py +++ b/worlds/wargroove2/Items.py @@ -1,7 +1,5 @@ -import typing - from BaseClasses import Item, ItemClassification -from typing import Dict, List +from typing import Dict, List, NamedTuple, Optional PROGRESSION = ItemClassification.progression PROGRESSION_SKIP_BALANCING = ItemClassification.progression_skip_balancing From 79e2c92d6de121b44be93475cdce9e6ecc38b8f3 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 17:42:16 -0400 Subject: [PATCH 05/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 854b27d9ce58..85624ce982f7 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -58,7 +58,6 @@ class Wargroove2World(World): settings: typing.ClassVar[Wargroove2Settings] game = "Wargroove 2" topology_present = True - data_version = 1 web = Wargroove2Web() level_list: [Wargroove2Level] = None first_level: Wargroove2Level = None From b9af82a12ef2091245ca4ccda6a4e9f954a59726 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 17:42:38 -0400 Subject: [PATCH 06/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 85624ce982f7..27e5136b05ef 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -88,7 +88,7 @@ def generate_early(self): low_victory_checks_levels = list(level for level in self.level_list if level.low_victory_checks) high_victory_checks_levels = list(level for level in self.level_list if not level.low_victory_checks) if self.multiworld.level_shuffle_seed[self.player] == 0: - random = self.multiworld.random + random = self.random else: random = Random(str(self.multiworld.level_shuffle_seed[self.player])) From 8208d15627ac0027172de1a9fae54329bf6d6487 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 17:43:06 -0400 Subject: [PATCH 07/89] Update worlds/wargroove2/Rules.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index 4413d3ab4947..f764ec261765 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -9,7 +9,7 @@ class Wargroove2Logic(LogicMixin): pass -def set_rules(world, level_list: [Wargroove2Level], +def set_rules(multiworld, level_list: [Wargroove2Level], first_level: Wargroove2Level, final_levels: [Wargroove2Level], player: int): From ea9ddb0722d9b05f09a1c6743a1e4bb561cbe460 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 17:43:23 -0400 Subject: [PATCH 08/89] Update worlds/wargroove2/docs/en_Wargroove 2.md Co-authored-by: Scipio Wright --- worlds/wargroove2/docs/en_Wargroove 2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md index 95982541c2ca..48ddd0f1d10a 100644 --- a/worlds/wargroove2/docs/en_Wargroove 2.md +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -2,7 +2,7 @@ ## Where is the settings page? -The [player settings page for this game](../player-options) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? From 746cdf5af340f98eacf5c6171cb7c745bac1b958 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 17:43:33 -0400 Subject: [PATCH 09/89] Update worlds/wargroove2/docs/wargroove2_en.md Co-authored-by: Scipio Wright --- worlds/wargroove2/docs/wargroove2_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/wargroove2_en.md b/worlds/wargroove2/docs/wargroove2_en.md index fcfc3cdeb7d6..c9de86d17c89 100644 --- a/worlds/wargroove2/docs/wargroove2_en.md +++ b/worlds/wargroove2/docs/wargroove2_en.md @@ -38,7 +38,7 @@ This should install the mod and campaign for you. ## Starting a Multiworld game 1. Start the Wargroove 2 Client and connect to the server. Enter your username from your -[settings file.](/games/Wargroove/player-settings) +[options file.](/games/Wargroove/player-options) 2. Start Wargroove 2 and play the Archipelago 2 campaign by going to `Story->Custom->Archipelago 2`. ## Ending a Multiworld game From 9f2000baeaaad851d971ed3ef4d3cec3658d09c2 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 8 Aug 2024 19:20:20 -0400 Subject: [PATCH 10/89] Wargroove 2: Fixed tests. Removed debug line. --- worlds/wargroove2/Options.py | 1 + worlds/wargroove2/__init__.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index 43437a04ed88..69a1df0b859d 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -1,3 +1,4 @@ +import typing from dataclasses import dataclass from Options import Choice, Range, DeathLink, PerGameCommonOptions diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 2150ce4d9968..bffa6c185aa1 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -163,7 +163,6 @@ def create_regions(self): def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() - s = self.options.__dict__.keys() for option_name in self.options.__dict__.keys(): option = getattr(self.options, option_name) if isinstance(option, NumericOption): From 89be9fbe47ac9dd5717db6db86a4c326d8f06fc2 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:07:30 -0400 Subject: [PATCH 11/89] Update worlds/wargroove2/Levels.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Levels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 439e2322b2a5..35d34883a69f 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -4,7 +4,7 @@ from .Locations import location_table, Wargroove2Location from worlds.generic.Rules import set_rule -region_names: [str] = ["North 1", "East 1", "South 1", "West 1", +region_names: List[str] = ["North 1", "East 1", "South 1", "West 1", "North 2A", "North 2B", "North 2C", "East 2A", "East 2B", "East 2C", "South 2A", "South 2B", "South 2C", From 28ab661a35d88648a507b1d8ec708c7b82f00b10 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:07:48 -0400 Subject: [PATCH 12/89] Update worlds/wargroove2/Levels.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Levels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 35d34883a69f..a2f4b9dc1171 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -22,7 +22,7 @@ FINAL_LEVEL_COUNT = 4 -def set_region_exit_rules(region: Region, multiworld: MultiWorld, player: int, locations: List[str], operator: str = "or"): +def set_region_exit_rules(region: Region, multiworld: MultiWorld, player: int, locations: List[str], operator: str = "or") -> None: if operator == "or": exit_rule = lambda state, world=multiworld, player=player: any( world.get_location(location, player).access_rule(state) for location in locations) From 44c81d733c16b546f5bd12b03d96820a376930e3 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:08:02 -0400 Subject: [PATCH 13/89] Update worlds/wargroove2/Levels.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Levels.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index a2f4b9dc1171..bbb548ef69e7 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -57,11 +57,11 @@ def __init__(self, name: str, file_name: str, location_rules: dict, victory_loca else: self.victory_locations = [name + ': Victory'] - def define_access_rules(self, world: MultiWorld, additional_rule=lambda state: True): + def define_access_rules(self, multiworld: MultiWorld, additional_rule=lambda state: True) -> None: for location_name, rule in self.location_rules.items(): - set_rule(world.get_location(location_name, self.player), lambda state, rule=rule: + set_rule(multiworld.get_location(location_name, self.player), lambda state, rule=rule: state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) - set_region_exit_rules(self.region, world, self.player, self.victory_locations, operator='and') + set_region_exit_rules(self.region, multiworld, self.player, self.victory_locations, operator='and') def define_region(self, name: str, world: MultiWorld, exits=None) -> Region: self.region = Region(name, self.player, world) From ad718fbdadfca17a1268c1876d89fcbed3e3ab52 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:08:28 -0400 Subject: [PATCH 14/89] Update worlds/wargroove2/Levels.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Levels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index bbb548ef69e7..dd1dbd2863ae 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -63,8 +63,8 @@ def define_access_rules(self, multiworld: MultiWorld, additional_rule=lambda sta state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) set_region_exit_rules(self.region, multiworld, self.player, self.victory_locations, operator='and') - def define_region(self, name: str, world: MultiWorld, exits=None) -> Region: - self.region = Region(name, self.player, world) + def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region: + self.region = Region(name, self.player, multiworld) if self.location_rules.keys(): for location in self.location_rules.keys(): loc_id = location_table.get(location, 0) From a0e8b4c47cb93a6bcb14d3a324c84493333ad916 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:08:55 -0400 Subject: [PATCH 15/89] Update worlds/wargroove2/Locations.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Locations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/wargroove2/Locations.py b/worlds/wargroove2/Locations.py index a048b6d36378..666a26ac74f4 100644 --- a/worlds/wargroove2/Locations.py +++ b/worlds/wargroove2/Locations.py @@ -1,6 +1,7 @@ from BaseClasses import Location +from typing import Dict -location_table = { +location_table: Dict[str, int] = { "Humble Beginnings Rebirth: Talk to Nadia": 253001, "Humble Beginnings Rebirth: Victory": 253002, "Humble Beginnings Rebirth: Good Dog": 253003, From d24f573789fcaaa86580ab2c0bdfcadcd215c0d8 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:09:38 -0400 Subject: [PATCH 16/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index bffa6c185aa1..d78109ec6d6d 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -158,8 +158,8 @@ def set_rules(self): def create_item(self, name: str) -> Item: return Wargroove2Item(name, self.player) - def create_regions(self): - create_regions(self.multiworld, self.player, self.level_list, self.first_level, self.final_levels) + def create_regions(self) -> None: + create_regions(self) def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() From 6baf1b928d8cd6ffbd94d7a1c3618c5ab6dff24a Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:09:51 -0400 Subject: [PATCH 17/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index d78109ec6d6d..f70c8bd065ea 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -152,7 +152,7 @@ def create_items(self): self.multiworld.completion_condition[self.player] = lambda state: \ state.has("Wargroove 2 Victory", self.player, self.options.final_levels) - def set_rules(self): + def set_rules(self) -> None: set_rules(self.multiworld, self.level_list, self.first_level, self.final_levels, self.player) def create_item(self, name: str) -> Item: From 72890d042be9fb9d32b9ed9d1f5e09bd7453b3d6 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:10:02 -0400 Subject: [PATCH 18/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index f70c8bd065ea..593a9c13ea2d 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -161,7 +161,7 @@ def create_item(self, name: str) -> Item: def create_regions(self) -> None: create_regions(self) - def fill_slot_data(self) -> dict: + def fill_slot_data(self) -> typing.Dict[str, typing.Any]: slot_data = self._get_slot_data() for option_name in self.options.__dict__.keys(): option = getattr(self.options, option_name) From 168f619513a0040c7b0fc9262aa027b78b806416 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:10:28 -0400 Subject: [PATCH 19/89] Update worlds/wargroove2/Locations.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Locations.py b/worlds/wargroove2/Locations.py index 666a26ac74f4..308c60c8ef2c 100644 --- a/worlds/wargroove2/Locations.py +++ b/worlds/wargroove2/Locations.py @@ -110,7 +110,7 @@ 'Wargroove 2: Victory': None } -location_id_name: {int, str} = {} +location_id_name: Dict[int, str] = {} for name in location_table.keys(): id = location_table[name] if id is not None: From 3711650e3070f87840653abcacdf016a061e7f86 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:11:56 -0400 Subject: [PATCH 20/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 593a9c13ea2d..fb0499d1785b 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -67,7 +67,7 @@ class Wargroove2World(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = location_table - def _get_slot_data(self): + def _get_slot_data(self) -> typing.Dict[str, typing.Any]: return { 'seed': "".join( self.random.choice(string.ascii_letters) for i in range(16)), From f5d50d6d590e03496f0174c10f403cdefc2227c2 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:12:24 -0400 Subject: [PATCH 21/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index fb0499d1785b..3ff2c5c50e0d 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -70,7 +70,7 @@ class Wargroove2World(World): def _get_slot_data(self) -> typing.Dict[str, typing.Any]: return { 'seed': "".join( - self.random.choice(string.ascii_letters) for i in range(16)), + self.random.choice(string.ascii_letters) for _ in range(16)), 'income_boost': self.options.income_boost.value, 'commander_defense_boost': self.options.commander_defense_boost.value, 'starting_groove_multiplier': self.options.groove_boost.value, From e7a5ec8a1acb0a24ae203eba5087244a08765449 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:13:05 -0400 Subject: [PATCH 22/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 3ff2c5c50e0d..0ec46532fe72 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -110,7 +110,7 @@ def generate_early(self): self.final_levels = final_levels_no_ocean[0:1] + non_north_levels # Selecting a random starting faction - if self.options.commander_choice == 2: + if self.options.commander_choice == "random_starting_faction": factions = [faction for faction in faction_table.keys() if faction != "Starter"] starting_faction = Wargroove2Item(self.multiworld.random.choice(factions) + ' Commanders', self.player) self.multiworld.push_precollected(starting_faction) From e22621c6c2d1db440a3cc0c840d4ba8e32405864 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:13:29 -0400 Subject: [PATCH 23/89] Update worlds/wargroove2/Regions.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Regions.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index 4fe2ed098f0f..39f0fe168c30 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -1,13 +1,19 @@ from BaseClasses import Region, Entrance from worlds.wargroove2 import Wargroove2Level from worlds.wargroove2.Levels import region_names, FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4 +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import Wargroove2World -def create_regions(world, player: int, - level_list: [Wargroove2Level], - first_level: Wargroove2Level, - final_levels: [Wargroove2Level]): - menu_region = Region('Menu', player, world) +def create_regions(world: "Wargroove2World") -> None: + multiworld = world.multiworld + player = world.player + level_list = self.level_list + first_level = self.first_level + final_levels = self.final_levels + + menu_region = Region('Menu', player, multiworld) menu_region.exits.append(Entrance(player, 'Menu exits to Humble Beginnings Rebirth', menu_region)) first_level_region = first_level.define_region("Humble Beginnings Rebirth", world, exits=[region_names[0], region_names[1], From a4ee924fd476fc888f3e02c17b471ee1fc0ae0c8 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:14:04 -0400 Subject: [PATCH 24/89] Update worlds/wargroove2/Rules.py Co-authored-by: Scipio Wright --- worlds/wargroove2/Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index e354d1d0e84c..a77fc364b1d6 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -12,7 +12,7 @@ class Wargroove2Logic(LogicMixin): def set_rules(multiworld, level_list: [Wargroove2Level], first_level: Wargroove2Level, final_levels: [Wargroove2Level], - player: int): + player: int) -> None: # Level 0 first_level.define_access_rules(multiworld) From a8a0ca700af788756fc60b1b2a01a79b3a7717b2 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:14:20 -0400 Subject: [PATCH 25/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 0ec46532fe72..0bd990778a64 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -77,7 +77,7 @@ def _get_slot_data(self) -> typing.Dict[str, typing.Any]: 'level_shuffle_seed': self.options.level_shuffle_seed.value, 'can_choose_commander': self.options.commander_choice.value != 0, 'final_levels': self.options.final_levels.value, - 'death_link': self.options.death_link.value == 1 + 'death_link': self.options.death_link.value == 1, } def generate_early(self): From 6498e6b1797035bf5b830206955033ddc6544e9e Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:14:42 -0400 Subject: [PATCH 26/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 0bd990778a64..2e5db196f722 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -115,7 +115,7 @@ def generate_early(self): starting_faction = Wargroove2Item(self.multiworld.random.choice(factions) + ' Commanders', self.player) self.multiworld.push_precollected(starting_faction) - def create_items(self): + def create_items(self) -> None: # Fill out our pool with our items from the item table pool = [] precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]} From 1735e4eb89e634dad62ce191da015294437052b3 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 13:15:09 -0400 Subject: [PATCH 27/89] Update worlds/wargroove2/__init__.py Co-authored-by: Scipio Wright --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 2e5db196f722..120772aee88d 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -80,7 +80,7 @@ def _get_slot_data(self) -> typing.Dict[str, typing.Any]: 'death_link': self.options.death_link.value == 1, } - def generate_early(self): + def generate_early(self) -> None: # First level self.first_level = get_first_level(self.player) From e736be0889f6e1856b422e554a049039641efa64 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 14:15:33 -0400 Subject: [PATCH 28/89] Wargroove 2: Many more PR fixes. Tested by generating several WG2 games, booting the client and verifying the communication files. --- worlds/wargroove2/Levels.py | 6 ++-- worlds/wargroove2/Locations.py | 40 +++++++++++------------ worlds/wargroove2/Options.py | 3 +- worlds/wargroove2/Regions.py | 60 +++++++++++++++++----------------- worlds/wargroove2/__init__.py | 4 +-- 5 files changed, 56 insertions(+), 57 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index dd1dbd2863ae..9c1f940c0e9c 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -45,8 +45,6 @@ class Wargroove2Level: def __init__(self, name: str, file_name: str, location_rules: dict, victory_locations: List[str] = [], low_victory_checks: bool = True, has_ocean: bool = True): - if victory_locations is None: - victory_locations = [] self.name = name self.file_name = file_name self.location_rules = location_rules @@ -63,8 +61,8 @@ def define_access_rules(self, multiworld: MultiWorld, additional_rule=lambda sta state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) set_region_exit_rules(self.region, multiworld, self.player, self.victory_locations, operator='and') - def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region: - self.region = Region(name, self.player, multiworld) + def define_region(self, name: str, world: "Wargroove2World", exits=None) -> Region: + self.region = Region(name, self.player, world.multiworld) if self.location_rules.keys(): for location in self.location_rules.keys(): loc_id = location_table.get(location, 0) diff --git a/worlds/wargroove2/Locations.py b/worlds/wargroove2/Locations.py index 308c60c8ef2c..2ed1ec4a156d 100644 --- a/worlds/wargroove2/Locations.py +++ b/worlds/wargroove2/Locations.py @@ -9,13 +9,13 @@ "Nuru's Vengeance: Victory": 253005, "Nuru's Vengeance: Spearman Destroys the Gate": 253006, "Nuru's Vengeance: Defeat all Dogs": 253007, - 'Cherrystone Landing: Smacked a Trebuchet': 253008, - 'Cherrystone Landing: Smacked a Fortified Village': 253009, - 'Cherrystone Landing: Victory': 253010, - 'Den-Two-Away: Victory': 253011, - 'Den-Two-Away: Commander Captures the Lumbermill': 253012, - 'Skydiving: Victory': 253013, - 'Skydiving: Dragon Defeats Stronghold': 253014, + "Cherrystone Landing: Smacked a Trebuchet": 253008, + "Cherrystone Landing: Smacked a Fortified Village": 253009, + "Cherrystone Landing: Victory": 253010, + "Den-Two-Away: Victory": 253011, + "Den-Two-Away: Commander Captures the Lumbermill": 253012, + "Skydiving: Victory": 253013, + "Skydiving: Dragon Defeats Stronghold": 253014, "Terrible Tributaries: Victory": 253015, "Terrible Tributaries: Swimming Knights": 253016, "Terrible Tributaries: Steal Code Names": 253017, @@ -44,20 +44,20 @@ "Bridge Brigade: From the Depths": 253040, "Bridge Brigade: Back to the Depths": 253041, ######################################################### - 'Slippery Bridge: Victory': 253300, - 'Slippery Bridge: Control all Sea Villages': 253301, + "Slippery Bridge: Victory": 253300, + "Slippery Bridge: Control all Sea Villages": 253301, ######################################################### - 'Spire Fire: Victory': 253305, - 'Spire Fire: Kill Enemy Sky Rider': 253306, - 'Spire Fire: Win without losing your Dragon': 253307, + "Spire Fire: Victory": 253305, + "Spire Fire: Kill Enemy Sky Rider": 253306, + "Spire Fire: Win without losing your Dragon": 253307, ######################################################### - 'Sunken Forest: Victory': 253310, - 'Sunken Forest: High Ground': 253311, - 'Sunken Forest: Coastal Siege': 253312, + "Sunken Forest: Victory": 253310, + "Sunken Forest: High Ground": 253311, + "Sunken Forest: Coastal Siege": 253312, ######################################################### - 'Tenri\'s Mistake: Victory': 253315, - 'Tenri\'s Mistake: Mighty Barracks': 253316, - 'Tenri\'s Mistake: Commander Arrives': 253317, + "Tenri\'s Mistake: Victory": 253315, + "Tenri\'s Mistake: Mighty Barracks": 253316, + "Tenri\'s Mistake: Commander Arrives": 253317, ######################################################### "Enmity Cliffs: Victory": 253320, "Enmity Cliffs: Spear Flood": 253321, @@ -103,11 +103,11 @@ "Split Valley: Longshot": 253371, "Split Valley: Ranged Trinity": 253372, ######################################################### - 'Disastrous Crossing: Victory': None, + "Disastrous Crossing: Victory": None, "Dark Mirror: Victory": None, "Doomed Metropolis: Victory": None, "Dementia Castle: Victory": None, - 'Wargroove 2: Victory': None + "Wargroove 2: Victory": None } location_id_name: Dict[int, str] = {} diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index 69a1df0b859d..f17ab2fbbecf 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -1,7 +1,7 @@ import typing from dataclasses import dataclass -from Options import Choice, Range, DeathLink, PerGameCommonOptions +from Options import Choice, Range, DeathLink, PerGameCommonOptions, StartInventoryPool class IncomeBoost(Range): @@ -65,3 +65,4 @@ class Wargroove2Options(PerGameCommonOptions): commander_choice: CommanderChoice final_levels: FinalLevels death_link: DeathLink + start_inventory_from_pool: StartInventoryPool diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index 39f0fe168c30..e082d4105c36 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -9,21 +9,21 @@ def create_regions(world: "Wargroove2World") -> None: multiworld = world.multiworld player = world.player - level_list = self.level_list - first_level = self.first_level - final_levels = self.final_levels + level_list = world.level_list + first_level = world.first_level + final_levels = world.final_levels menu_region = Region('Menu', player, multiworld) menu_region.exits.append(Entrance(player, 'Menu exits to Humble Beginnings Rebirth', menu_region)) first_level_region = first_level.define_region("Humble Beginnings Rebirth", world, exits=[region_names[0], region_names[1], region_names[2], region_names[3]]) - world.regions += [menu_region, first_level_region] + multiworld.regions += [menu_region, first_level_region] # Define Level 1s for level_num in range(0, 4): next_level = level_num * 4 + 4 - level_num - world.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[ + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[ region_names[next_level], region_names[ next_level + 1], @@ -32,7 +32,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 2s for level_num in range(4, 16): next_level = level_num + 12 - world.regions += [level_list[level_num].define_region(region_names[level_num], world, + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[region_names[next_level]])] # Define Level 3s for level_num in range(16, 28): @@ -43,50 +43,50 @@ def create_regions(world: "Wargroove2World") -> None: final_level_name = FINAL_LEVEL_3 elif level_num >= 19: final_level_name = FINAL_LEVEL_2 - world.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[final_level_name])] + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[final_level_name])] # Define Final Levels - world.regions += [final_levels[0].define_region(FINAL_LEVEL_1, world), + multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, world), final_levels[1].define_region(FINAL_LEVEL_2, world), final_levels[2].define_region(FINAL_LEVEL_3, world), final_levels[3].define_region(FINAL_LEVEL_4, world)] # # link up our regions with the entrances - world.get_entrance("Menu exits to Humble Beginnings Rebirth", player).connect( - world.get_region('Humble Beginnings Rebirth', player)) - world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[0]}", player).connect( - world.get_region(region_names[0], player)) - world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[1]}", player).connect( - world.get_region(region_names[1], player)) - world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[2]}", player).connect( - world.get_region(region_names[2], player)) - world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[3]}", player).connect( - world.get_region(region_names[3], player)) + world.get_entrance("Menu exits to Humble Beginnings Rebirth").connect( + world.get_region('Humble Beginnings Rebirth')) + world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[0]}").connect( + world.get_region(region_names[0])) + world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[1]}").connect( + world.get_region(region_names[1])) + world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[2]}").connect( + world.get_region(region_names[2])) + world.get_entrance(f"Humble Beginnings Rebirth exits to {region_names[3]}").connect( + world.get_region(region_names[3])) # Define Levels 1-4 for level_num in range(0, 4): next_level = level_num * 4 + 4 - level_num - world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level]}", player).connect( - world.get_region(region_names[next_level], player)) - world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level + 1]}", player).connect( - world.get_region(region_names[next_level + 1], player)) - world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level + 2]}", player).connect( - world.get_region(region_names[next_level + 2], player)) + world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level]}").connect( + world.get_region(region_names[next_level])) + world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level + 1]}").connect( + world.get_region(region_names[next_level + 1])) + world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level + 2]}").connect( + world.get_region(region_names[next_level + 2])) for level_num in range(4, 16): next_level = level_num + 12 - world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level]}", player).connect( - world.get_region(region_names[next_level], player)) + world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level]}").connect( + world.get_region(region_names[next_level])) for level_num in range(16, 28): if level_num >= 25: final_level_name = f"{region_names[level_num]} exits to {FINAL_LEVEL_4}" - world.get_entrance(final_level_name, player).connect(world.get_region(FINAL_LEVEL_4, player)) + world.get_entrance(final_level_name).connect(world.get_region(FINAL_LEVEL_4)) elif level_num >= 22: final_level_name = f"{region_names[level_num]} exits to {FINAL_LEVEL_3}" - world.get_entrance(final_level_name, player).connect(world.get_region(FINAL_LEVEL_3, player)) + world.get_entrance(final_level_name).connect(world.get_region(FINAL_LEVEL_3)) elif level_num >= 19: final_level_name = f"{region_names[level_num]} exits to {FINAL_LEVEL_2}" - world.get_entrance(final_level_name, player).connect(world.get_region(FINAL_LEVEL_2, player)) + world.get_entrance(final_level_name).connect(world.get_region(FINAL_LEVEL_2)) else: final_level_name = f"{region_names[level_num]} exits to {FINAL_LEVEL_1}" - world.get_entrance(final_level_name, player).connect(world.get_region(FINAL_LEVEL_1, player)) + world.get_entrance(final_level_name).connect(world.get_region(FINAL_LEVEL_1)) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 120772aee88d..ec1704bb4288 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -119,7 +119,7 @@ def create_items(self) -> None: # Fill out our pool with our items from the item table pool = [] precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]} - ignore_faction_items = self.options.commander_choice == 0 + ignore_faction_items = self.options.commander_choice == "locked_random" for name, data in item_table.items(): if data.code is not None and name not in precollected_item_names and \ not data.classification == ItemClassification.filler: @@ -147,7 +147,7 @@ def create_items(self) -> None: victory = Wargroove2Item("Wargroove 2 Victory", self.player) for i in range(0, 4): final_level = self.final_levels[i] - self.multiworld.get_location(final_level.victory_locations[0], self.player).place_locked_item(victory) + self.get_location(final_level.victory_locations[0]).place_locked_item(victory) # Placing victory event at final location self.multiworld.completion_condition[self.player] = lambda state: \ state.has("Wargroove 2 Victory", self.player, self.options.final_levels) From 1e3bbbdeb475ac1956f4e993c3e6882df3243ecb Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 9 Aug 2024 14:33:53 -0400 Subject: [PATCH 29/89] Wargroove 2: Build fixes. --- worlds/wargroove2/Levels.py | 4 ++-- worlds/wargroove2/Regions.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 9c1f940c0e9c..0e1e25c5ae1e 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -61,8 +61,8 @@ def define_access_rules(self, multiworld: MultiWorld, additional_rule=lambda sta state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) set_region_exit_rules(self.region, multiworld, self.player, self.victory_locations, operator='and') - def define_region(self, name: str, world: "Wargroove2World", exits=None) -> Region: - self.region = Region(name, self.player, world.multiworld) + def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region: + self.region = Region(name, self.player, multiworld) if self.location_rules.keys(): for location in self.location_rules.keys(): loc_id = location_table.get(location, 0) diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index e082d4105c36..ecbf7a5d73d4 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -15,7 +15,7 @@ def create_regions(world: "Wargroove2World") -> None: menu_region = Region('Menu', player, multiworld) menu_region.exits.append(Entrance(player, 'Menu exits to Humble Beginnings Rebirth', menu_region)) - first_level_region = first_level.define_region("Humble Beginnings Rebirth", world, + first_level_region = first_level.define_region("Humble Beginnings Rebirth", multiworld, exits=[region_names[0], region_names[1], region_names[2], region_names[3]]) multiworld.regions += [menu_region, first_level_region] @@ -23,7 +23,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 1s for level_num in range(0, 4): next_level = level_num * 4 + 4 - level_num - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[ + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, exits=[ region_names[next_level], region_names[ next_level + 1], @@ -32,7 +32,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 2s for level_num in range(4, 16): next_level = level_num + 12 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, exits=[region_names[next_level]])] # Define Level 3s for level_num in range(16, 28): @@ -43,13 +43,13 @@ def create_regions(world: "Wargroove2World") -> None: final_level_name = FINAL_LEVEL_3 elif level_num >= 19: final_level_name = FINAL_LEVEL_2 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, exits=[final_level_name])] + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, exits=[final_level_name])] # Define Final Levels - multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, world), - final_levels[1].define_region(FINAL_LEVEL_2, world), - final_levels[2].define_region(FINAL_LEVEL_3, world), - final_levels[3].define_region(FINAL_LEVEL_4, world)] + multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, multiworld), + final_levels[1].define_region(FINAL_LEVEL_2, multiworld), + final_levels[2].define_region(FINAL_LEVEL_3, multiworld), + final_levels[3].define_region(FINAL_LEVEL_4, multiworld)] # # link up our regions with the entrances world.get_entrance("Menu exits to Humble Beginnings Rebirth").connect( From 79c5e076e222846a32a006e68d75db8748f4367c Mon Sep 17 00:00:00 2001 From: FlySniper Date: Tue, 20 Aug 2024 15:23:38 -0400 Subject: [PATCH 30/89] Wargroove 2: Fixed python 3.8 tests again?! --- worlds/wargroove2/Regions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index ecbf7a5d73d4..6e5d16333308 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -1,5 +1,4 @@ from BaseClasses import Region, Entrance -from worlds.wargroove2 import Wargroove2Level from worlds.wargroove2.Levels import region_names, FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4 from typing import TYPE_CHECKING if TYPE_CHECKING: From 01be6c284c5bd96573850eeb5c6d63a86402f736 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Sun, 1 Sep 2024 22:31:29 -0400 Subject: [PATCH 31/89] Wargroove 2: Options for Multiple Locations per Check --- worlds/wargroove2/Levels.py | 24 ++- worlds/wargroove2/Locations.py | 337 +++++++++++++++++++++++++++++++++ worlds/wargroove2/Options.py | 18 ++ worlds/wargroove2/Presets.py | 4 + worlds/wargroove2/__init__.py | 16 +- worlds/wargroove2/client.py | 22 ++- 6 files changed, 414 insertions(+), 7 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 0e1e25c5ae1e..74be78c89412 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -59,6 +59,15 @@ def define_access_rules(self, multiworld: MultiWorld, additional_rule=lambda sta for location_name, rule in self.location_rules.items(): set_rule(multiworld.get_location(location_name, self.player), lambda state, rule=rule: state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) + loc_id = location_table.get(location_name, 0) + extras = 1 + if loc_id is not None and location_name.endswith("Victory"): + extras = multiworld.worlds[self.player].options.victory_locations.value + elif loc_id is not None: + extras = multiworld.worlds[self.player].options.objective_locations.value + for i in range(1, extras): + set_rule(multiworld.get_location(location_name + f" Extra {i}", self.player), lambda state, rule=rule: + state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) set_region_exit_rules(self.region, multiworld, self.player, self.victory_locations, operator='and') def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region: @@ -66,8 +75,19 @@ def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region if self.location_rules.keys(): for location in self.location_rules.keys(): loc_id = location_table.get(location, 0) - location = Wargroove2Location(self.player, location, loc_id, self.region) - self.region.locations.append(location) + wg2_location = Wargroove2Location(self.player, location, loc_id, self.region) + self.region.locations.append(wg2_location) + extras = 1 + if loc_id is not None and location.endswith("Victory"): + extras = multiworld.worlds[self.player].options.victory_locations.value + elif loc_id is not None: + extras = multiworld.worlds[self.player].options.objective_locations.value + for i in range(1, extras): + extra_location = location + f" Extra {i}" + loc_id = location_table.get(extra_location, 0) + wg2_location = Wargroove2Location(self.player, extra_location, loc_id, self.region) + self.region.locations.append(wg2_location) + if exits: for exit in exits: self.region.exits.append(Entrance(self.player, f"{name} exits to {exit}", self.region)) diff --git a/worlds/wargroove2/Locations.py b/worlds/wargroove2/Locations.py index 2ed1ec4a156d..2b2e566361fa 100644 --- a/worlds/wargroove2/Locations.py +++ b/worlds/wargroove2/Locations.py @@ -103,6 +103,343 @@ "Split Valley: Longshot": 253371, "Split Valley: Ranged Trinity": 253372, ######################################################### + "Humble Beginnings Rebirth: Talk to Nadia Extra 1": 260000, + "Humble Beginnings Rebirth: Talk to Nadia Extra 2": 260001, + "Humble Beginnings Rebirth: Talk to Nadia Extra 3": 260002, + "Humble Beginnings Rebirth: Talk to Nadia Extra 4": 260003, + "Humble Beginnings Rebirth: Victory Extra 1": 260004, + "Humble Beginnings Rebirth: Victory Extra 2": 260005, + "Humble Beginnings Rebirth: Victory Extra 3": 260006, + "Humble Beginnings Rebirth: Victory Extra 4": 260007, + "Humble Beginnings Rebirth: Good Dog Extra 1": 260008, + "Humble Beginnings Rebirth: Good Dog Extra 2": 260009, + "Humble Beginnings Rebirth: Good Dog Extra 3": 260010, + "Humble Beginnings Rebirth: Good Dog Extra 4": 260011, + "Nuru's Vengeance: Victory Extra 1": 260012, + "Nuru's Vengeance: Victory Extra 2": 260013, + "Nuru's Vengeance: Victory Extra 3": 260014, + "Nuru's Vengeance: Victory Extra 4": 260015, + "Nuru's Vengeance: Spearman Destroys the Gate Extra 1": 260016, + "Nuru's Vengeance: Spearman Destroys the Gate Extra 2": 260017, + "Nuru's Vengeance: Spearman Destroys the Gate Extra 3": 260018, + "Nuru's Vengeance: Spearman Destroys the Gate Extra 4": 260019, + "Nuru's Vengeance: Defeat all Dogs Extra 1": 260020, + "Nuru's Vengeance: Defeat all Dogs Extra 2": 260021, + "Nuru's Vengeance: Defeat all Dogs Extra 3": 260022, + "Nuru's Vengeance: Defeat all Dogs Extra 4": 260023, + "Cherrystone Landing: Smacked a Trebuchet Extra 1": 260024, + "Cherrystone Landing: Smacked a Trebuchet Extra 2": 260025, + "Cherrystone Landing: Smacked a Trebuchet Extra 3": 260026, + "Cherrystone Landing: Smacked a Trebuchet Extra 4": 260027, + "Cherrystone Landing: Smacked a Fortified Village Extra 1": 260028, + "Cherrystone Landing: Smacked a Fortified Village Extra 2": 260029, + "Cherrystone Landing: Smacked a Fortified Village Extra 3": 260030, + "Cherrystone Landing: Smacked a Fortified Village Extra 4": 260031, + "Cherrystone Landing: Victory Extra 1": 260032, + "Cherrystone Landing: Victory Extra 2": 260033, + "Cherrystone Landing: Victory Extra 3": 260034, + "Cherrystone Landing: Victory Extra 4": 260035, + "Den-Two-Away: Victory Extra 1": 260036, + "Den-Two-Away: Victory Extra 2": 260037, + "Den-Two-Away: Victory Extra 3": 260038, + "Den-Two-Away: Victory Extra 4": 260039, + "Den-Two-Away: Commander Captures the Lumbermill Extra 1": 260040, + "Den-Two-Away: Commander Captures the Lumbermill Extra 2": 260041, + "Den-Two-Away: Commander Captures the Lumbermill Extra 3": 260042, + "Den-Two-Away: Commander Captures the Lumbermill Extra 4": 260043, + "Skydiving: Victory Extra 1": 260044, + "Skydiving: Victory Extra 2": 260045, + "Skydiving: Victory Extra 3": 260046, + "Skydiving: Victory Extra 4": 260047, + "Skydiving: Dragon Defeats Stronghold Extra 1": 260048, + "Skydiving: Dragon Defeats Stronghold Extra 2": 260049, + "Skydiving: Dragon Defeats Stronghold Extra 3": 260050, + "Skydiving: Dragon Defeats Stronghold Extra 4": 260051, + "Terrible Tributaries: Victory Extra 1": 260052, + "Terrible Tributaries: Victory Extra 2": 260053, + "Terrible Tributaries: Victory Extra 3": 260054, + "Terrible Tributaries: Victory Extra 4": 260055, + "Terrible Tributaries: Swimming Knights Extra 1": 260056, + "Terrible Tributaries: Swimming Knights Extra 2": 260057, + "Terrible Tributaries: Swimming Knights Extra 3": 260058, + "Terrible Tributaries: Swimming Knights Extra 4": 260059, + "Terrible Tributaries: Steal Code Names Extra 1": 260060, + "Terrible Tributaries: Steal Code Names Extra 2": 260061, + "Terrible Tributaries: Steal Code Names Extra 3": 260062, + "Terrible Tributaries: Steal Code Names Extra 4": 260063, + "Beached: Victory Extra 1": 260064, + "Beached: Victory Extra 2": 260065, + "Beached: Victory Extra 3": 260066, + "Beached: Victory Extra 4": 260067, + "Beached: Turtle Power Extra 1": 260068, + "Beached: Turtle Power Extra 2": 260069, + "Beached: Turtle Power Extra 3": 260070, + "Beached: Turtle Power Extra 4": 260071, + "Beached: Happy Turtle Extra 1": 260072, + "Beached: Happy Turtle Extra 2": 260073, + "Beached: Happy Turtle Extra 3": 260074, + "Beached: Happy Turtle Extra 4": 260075, + "Riflemen Blockade: Victory Extra 1": 260076, + "Riflemen Blockade: Victory Extra 2": 260077, + "Riflemen Blockade: Victory Extra 3": 260078, + "Riflemen Blockade: Victory Extra 4": 260079, + "Riflemen Blockade: From the Mountains Extra 1": 260080, + "Riflemen Blockade: From the Mountains Extra 2": 260081, + "Riflemen Blockade: From the Mountains Extra 3": 260082, + "Riflemen Blockade: From the Mountains Extra 4": 260083, + "Riflemen Blockade: To the Road Extra 1": 260084, + "Riflemen Blockade: To the Road Extra 2": 260085, + "Riflemen Blockade: To the Road Extra 3": 260086, + "Riflemen Blockade: To the Road Extra 4": 260087, + "Wagon Freeway: Victory Extra 1": 260088, + "Wagon Freeway: Victory Extra 2": 260089, + "Wagon Freeway: Victory Extra 3": 260090, + "Wagon Freeway: Victory Extra 4": 260091, + "Wagon Freeway: All Mine Now Extra 1": 260092, + "Wagon Freeway: All Mine Now Extra 2": 260093, + "Wagon Freeway: All Mine Now Extra 3": 260094, + "Wagon Freeway: All Mine Now Extra 4": 260095, + "Wagon Freeway: Pigeon Carrier Extra 1": 260096, + "Wagon Freeway: Pigeon Carrier Extra 2": 260097, + "Wagon Freeway: Pigeon Carrier Extra 3": 260098, + "Wagon Freeway: Pigeon Carrier Extra 4": 260099, + "Kraken Strait: Victory Extra 1": 260100, + "Kraken Strait: Victory Extra 2": 260101, + "Kraken Strait: Victory Extra 3": 260102, + "Kraken Strait: Victory Extra 4": 260103, + "Kraken Strait: Well Defended Extra 1": 260104, + "Kraken Strait: Well Defended Extra 2": 260105, + "Kraken Strait: Well Defended Extra 3": 260106, + "Kraken Strait: Well Defended Extra 4": 260107, + "Kraken Strait: Clipped Wings Extra 1": 260108, + "Kraken Strait: Clipped Wings Extra 2": 260109, + "Kraken Strait: Clipped Wings Extra 3": 260110, + "Kraken Strait: Clipped Wings Extra 4": 260111, + "A Ribbitting Time: Victory Extra 1": 260112, + "A Ribbitting Time: Victory Extra 2": 260113, + "A Ribbitting Time: Victory Extra 3": 260114, + "A Ribbitting Time: Victory Extra 4": 260115, + "A Ribbitting Time: Leap Frog Extra 1": 260116, + "A Ribbitting Time: Leap Frog Extra 2": 260117, + "A Ribbitting Time: Leap Frog Extra 3": 260118, + "A Ribbitting Time: Leap Frog Extra 4": 260119, + "A Ribbitting Time: Frogway Robbery Extra 1": 260120, + "A Ribbitting Time: Frogway Robbery Extra 2": 260121, + "A Ribbitting Time: Frogway Robbery Extra 3": 260122, + "A Ribbitting Time: Frogway Robbery Extra 4": 260123, + "Precarious Cliffs: Victory Extra 1": 260124, + "Precarious Cliffs: Victory Extra 2": 260125, + "Precarious Cliffs: Victory Extra 3": 260126, + "Precarious Cliffs: Victory Extra 4": 260127, + "Precarious Cliffs: No Crit for You Extra 1": 260128, + "Precarious Cliffs: No Crit for You Extra 2": 260129, + "Precarious Cliffs: No Crit for You Extra 3": 260130, + "Precarious Cliffs: No Crit for You Extra 4": 260131, + "Precarious Cliffs: Out Ranged Extra 1": 260132, + "Precarious Cliffs: Out Ranged Extra 2": 260133, + "Precarious Cliffs: Out Ranged Extra 3": 260134, + "Precarious Cliffs: Out Ranged Extra 4": 260135, + "Grand Theft Village: Victory Extra 1": 260136, + "Grand Theft Village: Victory Extra 2": 260137, + "Grand Theft Village: Victory Extra 3": 260138, + "Grand Theft Village: Victory Extra 4": 260139, + "Grand Theft Village: Stand Tall Extra 1": 260140, + "Grand Theft Village: Stand Tall Extra 2": 260141, + "Grand Theft Village: Stand Tall Extra 3": 260142, + "Grand Theft Village: Stand Tall Extra 4": 260143, + "Grand Theft Village: Pillager Extra 1": 260144, + "Grand Theft Village: Pillager Extra 2": 260145, + "Grand Theft Village: Pillager Extra 3": 260146, + "Grand Theft Village: Pillager Extra 4": 260147, + "Bridge Brigade: Victory Extra 1": 260148, + "Bridge Brigade: Victory Extra 2": 260149, + "Bridge Brigade: Victory Extra 3": 260150, + "Bridge Brigade: Victory Extra 4": 260151, + "Bridge Brigade: From the Depths Extra 1": 260152, + "Bridge Brigade: From the Depths Extra 2": 260153, + "Bridge Brigade: From the Depths Extra 3": 260154, + "Bridge Brigade: From the Depths Extra 4": 260155, + "Bridge Brigade: Back to the Depths Extra 1": 260156, + "Bridge Brigade: Back to the Depths Extra 2": 260157, + "Bridge Brigade: Back to the Depths Extra 3": 260158, + "Bridge Brigade: Back to the Depths Extra 4": 260159, + "Slippery Bridge: Victory Extra 1": 260160, + "Slippery Bridge: Victory Extra 2": 260161, + "Slippery Bridge: Victory Extra 3": 260162, + "Slippery Bridge: Victory Extra 4": 260163, + "Slippery Bridge: Control all Sea Villages Extra 1": 260164, + "Slippery Bridge: Control all Sea Villages Extra 2": 260165, + "Slippery Bridge: Control all Sea Villages Extra 3": 260166, + "Slippery Bridge: Control all Sea Villages Extra 4": 260167, + "Spire Fire: Victory Extra 1": 260168, + "Spire Fire: Victory Extra 2": 260169, + "Spire Fire: Victory Extra 3": 260170, + "Spire Fire: Victory Extra 4": 260171, + "Spire Fire: Kill Enemy Sky Rider Extra 1": 260172, + "Spire Fire: Kill Enemy Sky Rider Extra 2": 260173, + "Spire Fire: Kill Enemy Sky Rider Extra 3": 260174, + "Spire Fire: Kill Enemy Sky Rider Extra 4": 260175, + "Spire Fire: Win without losing your Dragon Extra 1": 260176, + "Spire Fire: Win without losing your Dragon Extra 2": 260177, + "Spire Fire: Win without losing your Dragon Extra 3": 260178, + "Spire Fire: Win without losing your Dragon Extra 4": 260179, + "Sunken Forest: Victory Extra 1": 260180, + "Sunken Forest: Victory Extra 2": 260181, + "Sunken Forest: Victory Extra 3": 260182, + "Sunken Forest: Victory Extra 4": 260183, + "Sunken Forest: High Ground Extra 1": 260184, + "Sunken Forest: High Ground Extra 2": 260185, + "Sunken Forest: High Ground Extra 3": 260186, + "Sunken Forest: High Ground Extra 4": 260187, + "Sunken Forest: Coastal Siege Extra 1": 260188, + "Sunken Forest: Coastal Siege Extra 2": 260189, + "Sunken Forest: Coastal Siege Extra 3": 260190, + "Sunken Forest: Coastal Siege Extra 4": 260191, + "Tenri's Mistake: Victory Extra 1": 260192, + "Tenri's Mistake: Victory Extra 2": 260193, + "Tenri's Mistake: Victory Extra 3": 260194, + "Tenri's Mistake: Victory Extra 4": 260195, + "Tenri's Mistake: Mighty Barracks Extra 1": 260196, + "Tenri's Mistake: Mighty Barracks Extra 2": 260197, + "Tenri's Mistake: Mighty Barracks Extra 3": 260198, + "Tenri's Mistake: Mighty Barracks Extra 4": 260199, + "Tenri's Mistake: Commander Arrives Extra 1": 260200, + "Tenri's Mistake: Commander Arrives Extra 2": 260201, + "Tenri's Mistake: Commander Arrives Extra 3": 260202, + "Tenri's Mistake: Commander Arrives Extra 4": 260203, + "Enmity Cliffs: Victory Extra 1": 260204, + "Enmity Cliffs: Victory Extra 2": 260205, + "Enmity Cliffs: Victory Extra 3": 260206, + "Enmity Cliffs: Victory Extra 4": 260207, + "Enmity Cliffs: Spear Flood Extra 1": 260208, + "Enmity Cliffs: Spear Flood Extra 2": 260209, + "Enmity Cliffs: Spear Flood Extra 3": 260210, + "Enmity Cliffs: Spear Flood Extra 4": 260211, + "Enmity Cliffs: Across the Gap Extra 1": 260212, + "Enmity Cliffs: Across the Gap Extra 2": 260213, + "Enmity Cliffs: Across the Gap Extra 3": 260214, + "Enmity Cliffs: Across the Gap Extra 4": 260215, + "Portal Peril: Victory Extra 1": 260216, + "Portal Peril: Victory Extra 2": 260217, + "Portal Peril: Victory Extra 3": 260218, + "Portal Peril: Victory Extra 4": 260219, + "Portal Peril: Unleash the Hounds Extra 1": 260220, + "Portal Peril: Unleash the Hounds Extra 2": 260221, + "Portal Peril: Unleash the Hounds Extra 3": 260222, + "Portal Peril: Unleash the Hounds Extra 4": 260223, + "Portal Peril: Overcharged Extra 1": 260224, + "Portal Peril: Overcharged Extra 2": 260225, + "Portal Peril: Overcharged Extra 3": 260226, + "Portal Peril: Overcharged Extra 4": 260227, + "Towers of the Abyss: Victory Extra 1": 260228, + "Towers of the Abyss: Victory Extra 2": 260229, + "Towers of the Abyss: Victory Extra 3": 260230, + "Towers of the Abyss: Victory Extra 4": 260231, + "Towers of the Abyss: Siege Master Extra 1": 260232, + "Towers of the Abyss: Siege Master Extra 2": 260233, + "Towers of the Abyss: Siege Master Extra 3": 260234, + "Towers of the Abyss: Siege Master Extra 4": 260235, + "Towers of the Abyss: Perfect Defense Extra 1": 260236, + "Towers of the Abyss: Perfect Defense Extra 2": 260237, + "Towers of the Abyss: Perfect Defense Extra 3": 260238, + "Towers of the Abyss: Perfect Defense Extra 4": 260239, + "Gnarled Mountaintop: Victory Extra 1": 260240, + "Gnarled Mountaintop: Victory Extra 2": 260241, + "Gnarled Mountaintop: Victory Extra 3": 260242, + "Gnarled Mountaintop: Victory Extra 4": 260243, + "Gnarled Mountaintop: Watch the Watchtower Extra 1": 260244, + "Gnarled Mountaintop: Watch the Watchtower Extra 2": 260245, + "Gnarled Mountaintop: Watch the Watchtower Extra 3": 260246, + "Gnarled Mountaintop: Watch the Watchtower Extra 4": 260247, + "Gnarled Mountaintop: Vine Skip Extra 1": 260248, + "Gnarled Mountaintop: Vine Skip Extra 2": 260249, + "Gnarled Mountaintop: Vine Skip Extra 3": 260250, + "Gnarled Mountaintop: Vine Skip Extra 4": 260251, + "Gold Rush: Victory Extra 1": 260252, + "Gold Rush: Victory Extra 2": 260253, + "Gold Rush: Victory Extra 3": 260254, + "Gold Rush: Victory Extra 4": 260255, + "Gold Rush: Lumber Island Extra 1": 260256, + "Gold Rush: Lumber Island Extra 2": 260257, + "Gold Rush: Lumber Island Extra 3": 260258, + "Gold Rush: Lumber Island Extra 4": 260259, + "Gold Rush: Starglass Rush Extra 1": 260260, + "Gold Rush: Starglass Rush Extra 2": 260261, + "Gold Rush: Starglass Rush Extra 3": 260262, + "Gold Rush: Starglass Rush Extra 4": 260263, + "Finishing Blow: Victory Extra 1": 260264, + "Finishing Blow: Victory Extra 2": 260265, + "Finishing Blow: Victory Extra 3": 260266, + "Finishing Blow: Victory Extra 4": 260267, + "Finishing Blow: Mass Destruction Extra 1": 260268, + "Finishing Blow: Mass Destruction Extra 2": 260269, + "Finishing Blow: Mass Destruction Extra 3": 260270, + "Finishing Blow: Mass Destruction Extra 4": 260271, + "Finishing Blow: Defortification Extra 1": 260272, + "Finishing Blow: Defortification Extra 2": 260273, + "Finishing Blow: Defortification Extra 3": 260274, + "Finishing Blow: Defortification Extra 4": 260275, + "Frantic Inlet: Victory Extra 1": 260276, + "Frantic Inlet: Victory Extra 2": 260277, + "Frantic Inlet: Victory Extra 3": 260278, + "Frantic Inlet: Victory Extra 4": 260279, + "Frantic Inlet: Plug the Gap Extra 1": 260280, + "Frantic Inlet: Plug the Gap Extra 2": 260281, + "Frantic Inlet: Plug the Gap Extra 3": 260282, + "Frantic Inlet: Plug the Gap Extra 4": 260283, + "Frantic Inlet: Portal Detour Extra 1": 260284, + "Frantic Inlet: Portal Detour Extra 2": 260285, + "Frantic Inlet: Portal Detour Extra 3": 260286, + "Frantic Inlet: Portal Detour Extra 4": 260287, + "Operation Seagull: Victory Extra 1": 260288, + "Operation Seagull: Victory Extra 2": 260289, + "Operation Seagull: Victory Extra 3": 260290, + "Operation Seagull: Victory Extra 4": 260291, + "Operation Seagull: Crack the Crystal Extra 1": 260292, + "Operation Seagull: Crack the Crystal Extra 2": 260293, + "Operation Seagull: Crack the Crystal Extra 3": 260294, + "Operation Seagull: Crack the Crystal Extra 4": 260295, + "Operation Seagull: Counter Break Extra 1": 260296, + "Operation Seagull: Counter Break Extra 2": 260297, + "Operation Seagull: Counter Break Extra 3": 260298, + "Operation Seagull: Counter Break Extra 4": 260299, + "Air Support: Victory Extra 1": 260300, + "Air Support: Victory Extra 2": 260301, + "Air Support: Victory Extra 3": 260302, + "Air Support: Victory Extra 4": 260303, + "Air Support: Roadkill Extra 1": 260304, + "Air Support: Roadkill Extra 2": 260305, + "Air Support: Roadkill Extra 3": 260306, + "Air Support: Roadkill Extra 4": 260307, + "Air Support: Flight Economy Extra 1": 260308, + "Air Support: Flight Economy Extra 2": 260309, + "Air Support: Flight Economy Extra 3": 260310, + "Air Support: Flight Economy Extra 4": 260311, + "Fortification: Victory Extra 1": 260312, + "Fortification: Victory Extra 2": 260313, + "Fortification: Victory Extra 3": 260314, + "Fortification: Victory Extra 4": 260315, + "Fortification: Hyper Repair Extra 1": 260316, + "Fortification: Hyper Repair Extra 2": 260317, + "Fortification: Hyper Repair Extra 3": 260318, + "Fortification: Hyper Repair Extra 4": 260319, + "Fortification: Defensive Artillery Extra 1": 260320, + "Fortification: Defensive Artillery Extra 2": 260321, + "Fortification: Defensive Artillery Extra 3": 260322, + "Fortification: Defensive Artillery Extra 4": 260323, + "Split Valley: Victory Extra 1": 260324, + "Split Valley: Victory Extra 2": 260325, + "Split Valley: Victory Extra 3": 260326, + "Split Valley: Victory Extra 4": 260327, + "Split Valley: Longshot Extra 1": 260328, + "Split Valley: Longshot Extra 2": 260329, + "Split Valley: Longshot Extra 3": 260330, + "Split Valley: Longshot Extra 4": 260331, + "Split Valley: Ranged Trinity Extra 1": 260332, + "Split Valley: Ranged Trinity Extra 2": 260333, + "Split Valley: Ranged Trinity Extra 3": 260334, + "Split Valley: Ranged Trinity Extra 4": 260335, + ######################################################### "Disastrous Crossing: Victory": None, "Dark Mirror: Victory": None, "Doomed Metropolis: Victory": None, diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index f17ab2fbbecf..bc1543255d57 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -4,6 +4,22 @@ from Options import Choice, Range, DeathLink, PerGameCommonOptions, StartInventoryPool +class VictoryLocations(Range): + """How many checks are sent per level completed.""" + display_name = "Victory Locations" + range_start = 1 + range_end = 5 + default = 2 + + +class ObjectiveLocations(Range): + """How many checks are sent per level completed.""" + display_name = "Objective Locations" + range_start = 1 + range_end = 5 + default = 1 + + class IncomeBoost(Range): """How much extra income the player gets per turn per boost received.""" display_name = "Income Boost" @@ -58,6 +74,8 @@ class FinalLevels(Range): @dataclass class Wargroove2Options(PerGameCommonOptions): + victory_locations: VictoryLocations + objective_locations: ObjectiveLocations income_boost: IncomeBoost commander_defense_boost: CommanderDefenseBoost groove_boost: GrooveBoost diff --git a/worlds/wargroove2/Presets.py b/worlds/wargroove2/Presets.py index bbff1bb60700..42fad1bd8944 100644 --- a/worlds/wargroove2/Presets.py +++ b/worlds/wargroove2/Presets.py @@ -4,6 +4,8 @@ wargroove2_option_presets: Dict[str, Dict[str, Any]] = { "Easy": { + "victory_locations": 3, + "objective_locations": 2, "income_boost": 50, "commander_defense_boost": 5, "groove_boost": 10, @@ -13,6 +15,8 @@ }, "Hard": { + "victory_locations": 2, + "objective_locations": 1, "income_boost": 0, "commander_defense_boost": 0, "groove_boost": 0, diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index ec1704bb4288..3d2bdc0af42f 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -71,6 +71,8 @@ def _get_slot_data(self) -> typing.Dict[str, typing.Any]: return { 'seed': "".join( self.random.choice(string.ascii_letters) for _ in range(16)), + 'victory_locations': self.options.victory_locations.value, + 'objective_locations': self.options.objective_locations.value, 'income_boost': self.options.income_boost.value, 'commander_defense_boost': self.options.commander_defense_boost.value, 'starting_groove_multiplier': self.options.groove_boost.value, @@ -133,9 +135,10 @@ def create_items(self) -> None: pool.append(Wargroove2Item("Income Boost", self.player)) # Matching number of unfilled locations with filler items - total_locations = len(self.first_level.location_rules.keys()) + total_locations = 0 + total_locations += self.get_total_locations_in_level(self.first_level) for level in self.level_list[0:LEVEL_COUNT]: - total_locations += len(level.location_rules.keys()) + total_locations += self.get_total_locations_in_level(level) locations_remaining = total_locations - len(pool) while locations_remaining > 0: # Filling the pool equally with the groove boost @@ -192,3 +195,12 @@ def fill_slot_data(self) -> typing.Dict[str, typing.Any]: def get_filler_item_name(self) -> str: return "Groove Boost" + + def get_total_locations_in_level(self, level: Wargroove2Level): + total_locations = 0 + for location_name in level.location_rules.keys(): + if location_name.endswith("Victory"): + total_locations += self.options.victory_locations + else: + total_locations += self.options.objective_locations + return total_locations diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index fb4cc4befe45..514957af192e 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -16,7 +16,7 @@ import ModuleUpdate from worlds.wargroove2.Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, get_level_table, FINAL_LEVEL_1, \ FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, get_final_levels -from worlds.wargroove2.Locations import location_table +from worlds.wargroove2.Locations import location_table, location_id_name from worlds.wargroove2.RegionFilter import Wargroove2LogicFilter ModuleUpdate.update() @@ -65,6 +65,9 @@ class Wargroove2Context(CommonContext): commander_defense_boost_multiplier: int = 0 income_boost_multiplier: int = 0 starting_groove_multiplier: int = 0 + victory_locations: int = 1 + objective_locations: int = 1 + has_death_link: bool = False has_death_link: bool = False final_levels: int = 1 level_shuffle_seed: int = 0 @@ -181,6 +184,9 @@ def remove_communication_files(self): def on_package(self, cmd: str, args: dict): if cmd in {"Connected"}: self.slot_data = args["slot_data"] + self.victory_locations = self.slot_data["victory_locations"] + self.objective_locations = self.slot_data["objective_locations"] + self.has_death_link = self.slot_data["death_link"] self.has_death_link = self.slot_data["death_link"] self.final_levels = self.slot_data["final_levels"] self.level_shuffle_seed = self.slot_data["level_shuffle_seed"] @@ -661,8 +667,18 @@ async def game_watcher(ctx: Wargroove2Context): for root, dirs, files in os.walk(ctx.game_communication_path): for file in files: if file.find("send") > -1: - st = file.split("send", -1)[1] - sending = sending + [(int(st))] + st = int(file.split("send", -1)[1]) + loc_name = location_id_name[st] + extras = 1 + if loc_name is not None and loc_name.endswith("Victory"): + extras = ctx.victory_locations + elif loc_name is not None and \ + st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: + extras = ctx.objective_locations + for i in range(1, extras): + sending = sending + [location_table[loc_name + f" Extra {i}"]] + sending = sending + [st] + os.remove(os.path.join(ctx.game_communication_path, file)) if file == "deathLinkSend" and ctx.has_death_link: with open(os.path.join(ctx.game_communication_path, file), 'r') as f: From f626cbcb6b986842fb380c46e3554dc55b5d122c Mon Sep 17 00:00:00 2001 From: Exempt-Medic Date: Mon, 2 Sep 2024 10:47:21 -0400 Subject: [PATCH 32/89] Various suggestions/changes --- worlds/wargroove2/Items.py | 6 +- worlds/wargroove2/Levels.py | 161 +++++++++++++++--------------- worlds/wargroove2/Locations.py | 4 +- worlds/wargroove2/Options.py | 11 +- worlds/wargroove2/RegionFilter.py | 11 +- worlds/wargroove2/Regions.py | 22 ++-- worlds/wargroove2/Rules.py | 31 +++--- worlds/wargroove2/__init__.py | 14 +-- worlds/wargroove2/client.py | 10 +- 9 files changed, 138 insertions(+), 132 deletions(-) diff --git a/worlds/wargroove2/Items.py b/worlds/wargroove2/Items.py index c9b2ba232be0..e9da463159a4 100644 --- a/worlds/wargroove2/Items.py +++ b/worlds/wargroove2/Items.py @@ -70,7 +70,7 @@ class ItemData(NamedTuple): } -item_id_name: {int, str} = {} +item_id_name: Dict[int, str] = {} for name in item_table.keys(): id = item_table[name].code if id is not None: @@ -80,7 +80,7 @@ class ItemData(NamedTuple): class CommanderData(NamedTuple): name: str internal_name: str - alt_name: str = None + alt_name: Optional[str] = None faction_table: Dict[str, List[CommanderData]] = { @@ -128,7 +128,7 @@ class CommanderData(NamedTuple): class Wargroove2Item(Item): game = "Wargroove 2" - def __init__(self, name, player: int = None): + def __init__(self, name, player): item_data = item_table[name] super(Wargroove2Item, self).__init__( name, diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 0e1e25c5ae1e..cac557a60ece 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -1,18 +1,20 @@ -from typing import List +from typing import List, TYPE_CHECKING from BaseClasses import Region, Entrance, MultiWorld from .Locations import location_table, Wargroove2Location from worlds.generic.Rules import set_rule +if TYPE_CHECKING: + from . import Wargroove2World region_names: List[str] = ["North 1", "East 1", "South 1", "West 1", - "North 2A", "North 2B", "North 2C", - "East 2A", "East 2B", "East 2C", - "South 2A", "South 2B", "South 2C", - "West 2A", "West 2B", "West 2C", - "North 3A", "North 3B", "North 3C", - "East 3A", "East 3B", "East 3C", - "South 3A", "South 3B", "South 3C", - "West 3A", "West 3B", "West 3C"] + "North 2A", "North 2B", "North 2C", + "East 2A", "East 2B", "East 2C", + "South 2A", "South 2B", "South 2C", + "West 2A", "West 2B", "West 2C", + "North 3A", "North 3B", "North 3C", + "East 3A", "East 3B", "East 3C", + "South 3A", "South 3B", "South 3C", + "West 3A", "West 3B", "West 3C"] FINAL_LEVEL_1 = "Northern Finale" FINAL_LEVEL_2 = "Eastern Finale" FINAL_LEVEL_3 = "Southern Finale" @@ -22,13 +24,13 @@ FINAL_LEVEL_COUNT = 4 -def set_region_exit_rules(region: Region, multiworld: MultiWorld, player: int, locations: List[str], operator: str = "or") -> None: +def set_region_exit_rules(region: Region, world: "Wargroove2World", locations: List[str], operator: str = "or") -> None: if operator == "or": - exit_rule = lambda state, world=multiworld, player=player: any( - world.get_location(location, player).access_rule(state) for location in locations) + exit_rule = lambda state: any( + world.get_location(location).access_rule(state) for location in locations) else: - exit_rule = lambda state, world=multiworld, player=player: all( - world.get_location(location, player).access_rule(state) for location in locations) + exit_rule = lambda state: all( + world.get_location(location).access_rule(state) for location in locations) for region_exit in region.exits: region_exit.access_rule = exit_rule @@ -40,8 +42,8 @@ class Wargroove2Level: location_rules: dict region: Region victory_locations: List[str] - low_victory_checks: bool - has_ocean: bool + low_victory_checks: bool = True + has_ocean: bool = True def __init__(self, name: str, file_name: str, location_rules: dict, victory_locations: List[str] = [], low_victory_checks: bool = True, has_ocean: bool = True): @@ -55,19 +57,18 @@ def __init__(self, name: str, file_name: str, location_rules: dict, victory_loca else: self.victory_locations = [name + ': Victory'] - def define_access_rules(self, multiworld: MultiWorld, additional_rule=lambda state: True) -> None: + def define_access_rules(self, world: "Wargroove2World", additional_rule=lambda state: True) -> None: for location_name, rule in self.location_rules.items(): - set_rule(multiworld.get_location(location_name, self.player), lambda state, rule=rule: - state.can_reach(self.region, 'Region', self.player) and rule(state) and additional_rule(state)) - set_region_exit_rules(self.region, multiworld, self.player, self.victory_locations, operator='and') + set_rule(world.get_location(location_name), lambda state, current_rule=rule: + state.can_reach_region(self.region.name, self.player) and current_rule(state) and additional_rule(state)) + set_region_exit_rules(self.region, world, self.victory_locations, operator='and') def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region: self.region = Region(name, self.player, multiworld) - if self.location_rules.keys(): - for location in self.location_rules.keys(): - loc_id = location_table.get(location, 0) - location = Wargroove2Location(self.player, location, loc_id, self.region) - self.region.locations.append(location) + for location in self.location_rules.keys(): + loc_id = location_table.get(location, 0) + location = Wargroove2Location(self.player, location, loc_id, self.region) + self.region.locations.append(location) if exits: for exit in exits: self.region.exits.append(Entrance(self.player, f"{name} exits to {exit}", self.region)) @@ -81,9 +82,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Spire Fire", file_name="Spire_Fire.json", location_rules={ - "Spire Fire: Victory": lambda state: state.has_any({"Mage", "Witch"}, player), + "Spire Fire: Victory": lambda state: state.has_any(["Mage", "Witch"], player), "Spire Fire: Kill Enemy Sky Rider": lambda state: state.has("Witch", player), - "Spire Fire: Win without losing your Dragon": lambda state: state.has_any({"Mage", "Witch"}, player) + "Spire Fire: Win without losing your Dragon": lambda state: state.has_any(["Mage", "Witch"], player) }, has_ocean=False ), @@ -94,7 +95,7 @@ def get_level_table(player: int) -> List[Wargroove2Level]: "Nuru's Vengeance: Victory": lambda state: state.has("Knight", player), "Nuru's Vengeance: Defeat all Dogs": lambda state: state.has("Knight", player), "Nuru's Vengeance: Spearman Destroys the Gate": lambda state: state.has_all( - {"Knight", "Spearman"}, player) + ["Knight", "Spearman"], player) }, has_ocean=False ), @@ -102,12 +103,12 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Cherrystone Landing", file_name="Cherrystone_Landing.json", location_rules={ - "Cherrystone Landing: Victory": lambda state: state.has_all({"Warship", "Barge", "Landing Event"}, + "Cherrystone Landing: Victory": lambda state: state.has_all(["Warship", "Barge", "Landing Event"], player), "Cherrystone Landing: Smacked a Trebuchet": lambda state: state.has_all( - {"Warship", "Barge", "Landing Event", "Golem"}, player), + ["Warship", "Barge", "Landing Event", "Golem"], player), "Cherrystone Landing: Smacked a Fortified Village": lambda state: state.has_all( - {"Barge", "Landing Event", "Golem"}, player) + ["Barge", "Landing Event", "Golem"], player) }, low_victory_checks=False ), @@ -124,7 +125,7 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Den-Two-Away.json", location_rules={ "Den-Two-Away: Victory": lambda state: state.has("Harpy", player), - "Den-Two-Away: Commander Captures the Lumbermill": lambda state: state.has_all({"Harpy", "Balloon"}, + "Den-Two-Away: Commander Captures the Lumbermill": lambda state: state.has_all(["Harpy", "Balloon"], player), } ), @@ -132,9 +133,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Skydiving", file_name="Skydiving.json", location_rules={ - "Skydiving: Victory": lambda state: state.has_all({"Balloon", "Airstrike Event"}, player), + "Skydiving: Victory": lambda state: state.has_all(["Balloon", "Airstrike Event"], player), "Skydiving: Dragon Defeats Stronghold": lambda state: state.has_all( - {"Balloon", "Airstrike Event", "Dragon"}, player), + ["Balloon", "Airstrike Event", "Dragon"], player), }, has_ocean=False ), @@ -142,18 +143,18 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Sunken Forest", file_name="Sunken_Forest.json", location_rules={ - "Sunken Forest: Victory": lambda state: state.has_any({"Mage", "Harpoon Ship"}, player), + "Sunken Forest: Victory": lambda state: state.has_any(["Mage", "Harpoon Ship"], player), "Sunken Forest: High Ground": lambda state: state.has("Archer", player), "Sunken Forest: Coastal Siege": lambda state: state.has("Warship", player) and state.has_any( - {"Mage", "Harpoon Ship"}, player), + ["Mage", "Harpoon Ship"], player), } ), Wargroove2Level( name="Tenri's Mistake", file_name="Tenris_Mistake.json", location_rules={ - "Tenri's Mistake: Victory": lambda state: state.has_any({"Balloon", "Air Trooper"}, player), - "Tenri's Mistake: Mighty Barracks": lambda state: state.has_any({"Balloon", "Air Trooper"}, player), + "Tenri's Mistake: Victory": lambda state: state.has_any(["Balloon", "Air Trooper"], player), + "Tenri's Mistake: Mighty Barracks": lambda state: state.has_any(["Balloon", "Air Trooper"], player), "Tenri's Mistake: Commander Arrives": lambda state: state.has("Balloon", player), } ), @@ -161,9 +162,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Enmity Cliffs", file_name="Enmity_Cliffs.json", location_rules={ - "Enmity Cliffs: Victory": lambda state: state.has_all({"Spearman", "Bridges Event"}, player), + "Enmity Cliffs: Victory": lambda state: state.has_all(["Spearman", "Bridges Event"], player), "Enmity Cliffs: Spear Flood": lambda state: state.has("Spearman", player), - "Enmity Cliffs: Across the Gap": lambda state: state.has_any({"Archer", "Rifleman"}, player), + "Enmity Cliffs: Across the Gap": lambda state: state.has_any(["Archer", "Rifleman"], player), }, has_ocean=False ), @@ -172,9 +173,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Terrible_Tributaries.json", location_rules={ "Terrible Tributaries: Victory": lambda state: state.has("River Boat", player), - "Terrible Tributaries: Swimming Knights": lambda state: state.has_all({"Merfolk", "River Boat"}, + "Terrible Tributaries: Swimming Knights": lambda state: state.has_all(["Merfolk", "River Boat"], player), - "Terrible Tributaries: Steal Code Names": lambda state: state.has_all({"Thief", "River Boat"}, player), + "Terrible Tributaries: Steal Code Names": lambda state: state.has_all(["Thief", "River Boat"], player), } ), Wargroove2Level( @@ -182,8 +183,8 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Beached.json", location_rules={ "Beached: Victory": lambda state: state.has("Knight", player), - "Beached: Turtle Power": lambda state: state.has_all({"Turtle", "Knight"}, player), - "Beached: Happy Turtle": lambda state: state.has_all({"Turtle", "Knight"}, player), + "Beached: Turtle Power": lambda state: state.has_all(["Turtle", "Knight"], player), + "Beached: Happy Turtle": lambda state: state.has_all(["Turtle", "Knight"], player), } ), Wargroove2Level( @@ -201,8 +202,8 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Riflemen_Blockade.json", location_rules={ "Riflemen Blockade: Victory": lambda state: state.has("Rifleman", player), - "Riflemen Blockade: From the Mountains": lambda state: state.has_all({"Rifleman", "Harpy"}, player), - "Riflemen Blockade: To the Road": lambda state: state.has_all({"Rifleman", "Dragon"}, player), + "Riflemen Blockade: From the Mountains": lambda state: state.has_all(["Rifleman", "Harpy"], player), + "Riflemen Blockade: To the Road": lambda state: state.has_all(["Rifleman", "Dragon"], player), }, has_ocean=False ), @@ -211,8 +212,8 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Towers_of_the_Abyss.json", location_rules={ "Towers of the Abyss: Victory": lambda state: state.has("Ballista", player), - "Towers of the Abyss: Siege Master": lambda state: state.has_all({"Ballista", "Trebuchet"}, player), - "Towers of the Abyss: Perfect Defense": lambda state: state.has_all({"Ballista", "Walls Event"}, + "Towers of the Abyss: Siege Master": lambda state: state.has_all(["Ballista", "Trebuchet"], player), + "Towers of the Abyss: Perfect Defense": lambda state: state.has_all(["Ballista", "Walls Event"], player), }, has_ocean=False @@ -221,7 +222,7 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Wagon Freeway", file_name="Wagon_Freeway.json", location_rules={ - "Wagon Freeway: Victory": lambda state: state.has_all({"Wagon", "Spearman"}, player), + "Wagon Freeway: Victory": lambda state: state.has_all(["Wagon", "Spearman"], player), "Wagon Freeway: All Mine Now": lambda state: True, "Wagon Freeway: Pigeon Carrier": lambda state: state.has("Air Trooper", player), }, @@ -231,8 +232,8 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Kraken Strait", file_name="Kraken_Strait.json", location_rules={ - "Kraken Strait: Victory": lambda state: state.has_all({"Frog", "Kraken"}, player), - "Kraken Strait: Well Defended": lambda state: state.has_all({"Frog", "Kraken"}, player), + "Kraken Strait: Victory": lambda state: state.has_all(["Frog", "Kraken"], player), + "Kraken Strait: Well Defended": lambda state: state.has_all(["Frog", "Kraken"], player), "Kraken Strait: Clipped Wings": lambda state: state.has("Harpoon Ship", player), } ), @@ -251,9 +252,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Gold_Rush.json", location_rules={ "Gold Rush: Victory": lambda state: state.has("Thief", player) and - state.has_any({"Rifleman", "Merfolk", "Warship"}, player), - "Gold Rush: Lumber Island": lambda state: state.has_any({"Merfolk", "River Boat", "Barge"}, player), - "Gold Rush: Starglass Rush": lambda state: state.has_any({"River Boat", "Barge"}, player), + state.has_any(["Rifleman", "Merfolk", "Warship"], player), + "Gold Rush: Lumber Island": lambda state: state.has_any(["Merfolk", "River Boat", "Barge"], player), + "Gold Rush: Starglass Rush": lambda state: state.has_any(["River Boat", "Barge"], player), } ), Wargroove2Level( @@ -270,9 +271,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Frantic_Inlet.json", location_rules={ "Frantic Inlet: Victory": lambda state: state.has("Turtle", player) and - state.has_any({"Barge", "Knight"}, player), + state.has_any(["Barge", "Knight"], player), "Frantic Inlet: Plug the Gap": lambda state: state.has("Spearman", player), - "Frantic Inlet: Portal Detour": lambda state: state.has_all({"Turtle", "Barge"}, player), + "Frantic Inlet: Portal Detour": lambda state: state.has_all(["Turtle", "Barge"], player), } ), Wargroove2Level( @@ -280,29 +281,29 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Operation_Seagull.json", location_rules={ "Operation Seagull: Victory": lambda state: state.has("Merfolk", player) and - state.has_any({"Harpoon Ship", "Witch"}, player) and - state.has_any({"Turtle", "Harpy"}, player), - "Operation Seagull: Crack the Crystal": lambda state: state.has_any({"Warship", "Kraken"}, player), + state.has_any(["Harpoon Ship", "Witch"], player) and + state.has_any(["Turtle", "Harpy"], player), + "Operation Seagull: Crack the Crystal": lambda state: state.has_any(["Warship", "Kraken"], player), "Operation Seagull: Counter Break": lambda state: state.has("Dragon", player) and - state.has_all({"Harpoon Ship", "Witch"}, player), + state.has_all(["Harpoon Ship", "Witch"], player), } ), Wargroove2Level( name="Air Support", file_name="Air_Support.json", location_rules={ - "Air Support: Victory": lambda state: state.has_all({"Dragon", "Bridges Event"}, player), - "Air Support: Roadkill": lambda state: state.has_all({"Dragon", "Bridges Event"}, player), - "Air Support: Flight Economy": lambda state: state.has_all({"Air Trooper", "Bridges Event"}, player), + "Air Support: Victory": lambda state: state.has_all(["Dragon", "Bridges Event"], player), + "Air Support: Roadkill": lambda state: state.has_all(["Dragon", "Bridges Event"], player), + "Air Support: Flight Economy": lambda state: state.has_all(["Air Trooper", "Bridges Event"], player), } ), Wargroove2Level( name="Fortification", file_name="Fortification.json", location_rules={ - "Fortification: Victory": lambda state: state.has_all({"Golem", "Walls Event"}, player) and - state.has_any({"Archer", "Trebuchet"}, player), - "Fortification: Hyper Repair": lambda state: state.has_all({"Golem", "Walls Event"}, player), + "Fortification: Victory": lambda state: state.has_all(["Golem", "Walls Event"], player) and + state.has_any(["Archer", "Trebuchet"], player), + "Fortification: Hyper Repair": lambda state: state.has_all(["Golem", "Walls Event"], player), "Fortification: Defensive Artillery": lambda state: state.has("Trebuchet", player), }, has_ocean=False @@ -313,16 +314,16 @@ def get_level_table(player: int) -> List[Wargroove2Level]: location_rules={ "A Ribbitting Time: Victory": lambda state: state.has("Frog", player), "A Ribbitting Time: Leap Frog": lambda state: state.has("Frog", player), - "A Ribbitting Time: Frogway Robbery": lambda state: state.has_all({"Frog", "Thief"}, player), + "A Ribbitting Time: Frogway Robbery": lambda state: state.has_all(["Frog", "Thief"], player), } ), Wargroove2Level( name="Precarious Cliffs", file_name="Precarious_Cliffs.json", location_rules={ - "Precarious Cliffs: Victory": lambda state: state.has_all({"Airstrike Event", "Archer"}, player), + "Precarious Cliffs: Victory": lambda state: state.has_all(["Airstrike Event", "Archer"], player), "Precarious Cliffs: No Crit for You": lambda state: state.has("Airstrike Event", player), - "Precarious Cliffs: Out Ranged": lambda state: state.has_all({"Airstrike Event", "Archer"}, player), + "Precarious Cliffs: Out Ranged": lambda state: state.has_all(["Airstrike Event", "Archer"], player), }, has_ocean=False ), @@ -331,9 +332,9 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Split_Valley.json", location_rules={ "Split Valley: Victory": lambda state: state.has("Trebuchet", player) and - state.has_any({"Bridges Event", "Air Trooper"}, player), + state.has_any(["Bridges Event", "Air Trooper"], player), "Split Valley: Longshot": lambda state: state.has("Trebuchet", player), - "Split Valley: Ranged Trinity": lambda state: state.has_all({"Trebuchet", "Archer", "Ballista"}, + "Split Valley: Ranged Trinity": lambda state: state.has_all(["Trebuchet", "Archer", "Ballista"], player), } ), @@ -342,7 +343,7 @@ def get_level_table(player: int) -> List[Wargroove2Level]: file_name="Grand_Theft_Village.json", location_rules={ "Grand Theft Village: Victory": lambda state: state.has("Thief", player) and - state.has_any({"Mage", "Ballista"}, player), + state.has_any(["Mage", "Ballista"], player), "Grand Theft Village: Stand Tall": lambda state: state.has("Golem", player), "Grand Theft Village: Pillager": lambda state: True, }, @@ -352,10 +353,10 @@ def get_level_table(player: int) -> List[Wargroove2Level]: name="Bridge Brigade", file_name="Bridge_Brigade.json", location_rules={ - "Bridge Brigade: Victory": lambda state: state.has_all({"Warship", "Spearman"}, player), + "Bridge Brigade: Victory": lambda state: state.has_all(["Warship", "Spearman"], player), "Bridge Brigade: From the Depths": lambda state: state.has("Kraken", player), "Bridge Brigade: Back to the Depths": lambda state: - state.has_all({"Warship", "Spearman", "Kraken"}, player), + state.has_all(["Warship", "Spearman", "Kraken"], player), }, has_ocean=False ), @@ -371,28 +372,28 @@ def get_final_levels(player: int) -> List[Wargroove2Level]: name="Disastrous Crossing", file_name="Disastrous_Crossing.json", location_rules={"Disastrous Crossing: Victory": - lambda state: state.has_any({"Merfolk", "River Boat"}, player) and - state.has_any({"Knight", "Kraken"}, player)} + lambda state: state.has_any(["Merfolk", "River Boat"], player) and + state.has_any(["Knight", "Kraken"], player)} ), Wargroove2Level( name="Dark Mirror", file_name="Dark_Mirror.json", location_rules={"Dark Mirror: Victory": lambda state: state.has("Archer", player) and - state.has_any({"Mage", "Ballista"}, player) and - state.has_any({"Harpy", "Dragon"}, player)}, + state.has_any(["Mage", "Ballista"], player) and + state.has_any(["Harpy", "Dragon"], player)}, has_ocean=False ), Wargroove2Level( name="Doomed Metropolis", file_name="Doomed_Metropolis.json", - location_rules={"Doomed Metropolis: Victory": lambda state: state.has_all({"Mage", "Knight"}, player)}, + location_rules={"Doomed Metropolis: Victory": lambda state: state.has_all(["Mage", "Knight"], player)}, has_ocean=False ), Wargroove2Level( name="Dementia Castle", file_name="Dementia_Castle.json", location_rules={"Dementia Castle: Victory": - lambda state: state.has_all({"Merfolk", "Mage", "Golem", "Harpy"}, player)} + lambda state: state.has_all(["Merfolk", "Mage", "Golem", "Harpy"], player)} ), ] for level in levels: diff --git a/worlds/wargroove2/Locations.py b/worlds/wargroove2/Locations.py index 2ed1ec4a156d..cc5e528b071d 100644 --- a/worlds/wargroove2/Locations.py +++ b/worlds/wargroove2/Locations.py @@ -1,7 +1,7 @@ from BaseClasses import Location -from typing import Dict +from typing import Dict, Optional -location_table: Dict[str, int] = { +location_table: Dict[str, Optional[int]] = { "Humble Beginnings Rebirth: Talk to Nadia": 253001, "Humble Beginnings Rebirth: Victory": 253002, "Humble Beginnings Rebirth: Good Dog": 253003, diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index f17ab2fbbecf..322144531074 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -1,4 +1,3 @@ -import typing from dataclasses import dataclass from Options import Choice, Range, DeathLink, PerGameCommonOptions, StartInventoryPool @@ -38,9 +37,13 @@ class LevelShuffleSeed(Range): class CommanderChoice(Choice): """How the player's commander is selected for missions. - Locked Random: The player's commander is randomly predetermined for each level. - Unlockable Factions: The player starts with Mercival and can unlock playable factions. - Random Starting Faction: The player starts with a random starting faction and can unlock the rest. + + - Locked Random: The player's commander is randomly predetermined for each level. + + - Unlockable Factions: The player starts with Mercival and can unlock playable factions. + + - Random Starting Faction: The player starts with a random starting faction and can unlock the rest. + When playing with unlockable factions, faction items are added to the pool.""" display_name = "Commander Choice" option_locked_random = 0 diff --git a/worlds/wargroove2/RegionFilter.py b/worlds/wargroove2/RegionFilter.py index e676b408fde4..cd4e7928141c 100644 --- a/worlds/wargroove2/RegionFilter.py +++ b/worlds/wargroove2/RegionFilter.py @@ -1,19 +1,22 @@ +from typing import List + + class Wargroove2LogicFilter: - items: [str] + items: List[str] - def __init__(self, items: [str]): + def __init__(self, items: List[str]): self.items = items def has(self, item: str, player: int) -> bool: return item in self.items - def has_all(self, items: [str], player: int) -> bool: + def has_all(self, items: List[str], player: int) -> bool: for item in items: if item not in self.items: return False return True - def has_any(self, items: [str], player: int) -> bool: + def has_any(self, items: List[str], player: int) -> bool: for item in items: if item in self.items: return True diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index 6e5d16333308..776384512de7 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -21,18 +21,17 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 1s for level_num in range(0, 4): - next_level = level_num * 4 + 4 - level_num + next_level = level_num * 3 + 4 multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, exits=[ region_names[next_level], - region_names[ - next_level + 1], - region_names[ - next_level + 2]])] + region_names[next_level + 1], + region_names[next_level + 2] + ])] # Define Level 2s for level_num in range(4, 16): next_level = level_num + 12 multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, - exits=[region_names[next_level]])] + exits=[region_names[next_level]])] # Define Level 3s for level_num in range(16, 28): final_level_name = FINAL_LEVEL_1 @@ -42,13 +41,14 @@ def create_regions(world: "Wargroove2World") -> None: final_level_name = FINAL_LEVEL_3 elif level_num >= 19: final_level_name = FINAL_LEVEL_2 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, exits=[final_level_name])] + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, + exits=[final_level_name])] # Define Final Levels multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, multiworld), - final_levels[1].define_region(FINAL_LEVEL_2, multiworld), - final_levels[2].define_region(FINAL_LEVEL_3, multiworld), - final_levels[3].define_region(FINAL_LEVEL_4, multiworld)] + final_levels[1].define_region(FINAL_LEVEL_2, multiworld), + final_levels[2].define_region(FINAL_LEVEL_3, multiworld), + final_levels[3].define_region(FINAL_LEVEL_4, multiworld)] # # link up our regions with the entrances world.get_entrance("Menu exits to Humble Beginnings Rebirth").connect( @@ -63,7 +63,7 @@ def create_regions(world: "Wargroove2World") -> None: world.get_region(region_names[3])) # Define Levels 1-4 for level_num in range(0, 4): - next_level = level_num * 4 + 4 - level_num + next_level = level_num * 3 + 4 world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level]}").connect( world.get_region(region_names[next_level])) world.get_entrance(f"{region_names[level_num]} exits to {region_names[next_level + 1]}").connect( diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index a77fc364b1d6..62873ef40629 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -1,28 +1,27 @@ -from typing import List - -from BaseClasses import Region, Location -from .Levels import Wargroove2Level +from typing import TYPE_CHECKING from worlds.AutoWorld import LogicMixin - +if TYPE_CHECKING: + from . import Wargroove2World class Wargroove2Logic(LogicMixin): pass -def set_rules(multiworld, level_list: [Wargroove2Level], - first_level: Wargroove2Level, - final_levels: [Wargroove2Level], - player: int) -> None: +def set_rules(world: "Wargroove2World") -> None: + level_list = world.level_list + first_level = world.first_level + final_levels = world.final_levels + player = world.player + # Level 0 - first_level.define_access_rules(multiworld) + first_level.define_access_rules(world) # Levels 1-28 (Top 28 of the list) for i in range(0, 28): - level_list[i].define_access_rules(multiworld) + level_list[i].define_access_rules(world) # Final Levels (Top 4 of the list) - final_levels[0].define_access_rules(multiworld, lambda state: state.has_all({"Final North", "Final Center"}, player)) - final_levels[1].define_access_rules(multiworld, lambda state: state.has_all({"Final East", "Final Center"}, player)) - final_levels[2].define_access_rules(multiworld, lambda state: state.has_all({"Final South", "Final Center"}, player)) - final_levels[3].define_access_rules(multiworld, lambda state: state.has_all({"Final West", "Final Center"}, player)) - + final_levels[0].define_access_rules(world, lambda state: state.has_all(["Final North", "Final Center"], player)) + final_levels[1].define_access_rules(world, lambda state: state.has_all(["Final East", "Final Center"], player)) + final_levels[2].define_access_rules(world, lambda state: state.has_all(["Final South", "Final Center"], player)) + final_levels[3].define_access_rules(world, lambda state: state.has_all(["Final West", "Final Center"], player)) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index ec1704bb4288..01e08f831fa4 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -60,12 +60,12 @@ class Wargroove2World(World): game = "Wargroove 2" topology_present = True web = Wargroove2Web() - level_list: [Wargroove2Level] = None - first_level: Wargroove2Level = None - final_levels: [Wargroove2Level] = None + level_list: typing.List[Wargroove2Level] = [] + first_level: Wargroove2Level + final_levels: typing.List[Wargroove2Level] = [] - item_name_to_id = {name: data.code for name, data in item_table.items()} - location_name_to_id = location_table + item_name_to_id = {name: data.code for name, data in item_table.items() if data.code is not None} + location_name_to_id = {name: code for name, code in location_table.items() if code is not None} def _get_slot_data(self) -> typing.Dict[str, typing.Any]: return { @@ -150,10 +150,10 @@ def create_items(self) -> None: self.get_location(final_level.victory_locations[0]).place_locked_item(victory) # Placing victory event at final location self.multiworld.completion_condition[self.player] = lambda state: \ - state.has("Wargroove 2 Victory", self.player, self.options.final_levels) + state.has("Wargroove 2 Victory", self.player, self.options.final_levels.value) def set_rules(self) -> None: - set_rules(self.multiworld, self.level_list, self.first_level, self.final_levels, self.player) + set_rules(self) def create_item(self, name: str) -> Item: return Wargroove2Item(name, self.player) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index fb4cc4befe45..dfa65f923ca4 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -10,14 +10,14 @@ from typing import Tuple, List, Iterable, Dict from settings import get_settings -from worlds.wargroove2 import Wargroove2World -from worlds.wargroove2.Items import item_table, faction_table, CommanderData, ItemData, item_id_name +from . import Wargroove2World +from .Items import item_table, faction_table, CommanderData, ItemData, item_id_name import ModuleUpdate -from worlds.wargroove2.Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, get_level_table, FINAL_LEVEL_1, \ +from .Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, get_level_table, FINAL_LEVEL_1, \ FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, get_final_levels -from worlds.wargroove2.Locations import location_table -from worlds.wargroove2.RegionFilter import Wargroove2LogicFilter +from .Locations import location_table +from .RegionFilter import Wargroove2LogicFilter ModuleUpdate.update() From 1540017fbafd5de4cabccfcd09394cae3e84650e Mon Sep 17 00:00:00 2001 From: Exempt-Medic Date: Mon, 2 Sep 2024 11:13:24 -0400 Subject: [PATCH 33/89] Forgot a newline --- worlds/wargroove2/Rules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index 62873ef40629..e28eeeef2415 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -3,6 +3,7 @@ if TYPE_CHECKING: from . import Wargroove2World + class Wargroove2Logic(LogicMixin): pass From 9f33abe1389ffd73a2d2535a27d0085a842ac324 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Mon, 2 Sep 2024 13:15:18 -0400 Subject: [PATCH 34/89] Update README.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d25247504028..9fd92de16040 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Currently, the following games are supported: * Old School Runescape * Kingdom Hearts 1 * Mega Man 2 +* Yacht Dice * Wargroove 2 For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). From 57d334c422bcb5dd4d4adfd6ecdab1f20f8e6d6f Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Mon, 2 Sep 2024 13:57:04 -0400 Subject: [PATCH 35/89] Wargroove 2: removed _get_slot_data method since most of it's values were being overwritten. --- worlds/wargroove2/__init__.py | 15 +-------------- worlds/wargroove2/client.py | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 01e08f831fa4..65b010142ca3 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -67,19 +67,6 @@ class Wargroove2World(World): item_name_to_id = {name: data.code for name, data in item_table.items() if data.code is not None} location_name_to_id = {name: code for name, code in location_table.items() if code is not None} - def _get_slot_data(self) -> typing.Dict[str, typing.Any]: - return { - 'seed': "".join( - self.random.choice(string.ascii_letters) for _ in range(16)), - 'income_boost': self.options.income_boost.value, - 'commander_defense_boost': self.options.commander_defense_boost.value, - 'starting_groove_multiplier': self.options.groove_boost.value, - 'level_shuffle_seed': self.options.level_shuffle_seed.value, - 'can_choose_commander': self.options.commander_choice.value != 0, - 'final_levels': self.options.final_levels.value, - 'death_link': self.options.death_link.value == 1, - } - def generate_early(self) -> None: # First level self.first_level = get_first_level(self.player) @@ -162,7 +149,7 @@ def create_regions(self) -> None: create_regions(self) def fill_slot_data(self) -> typing.Dict[str, typing.Any]: - slot_data = self._get_slot_data() + slot_data = {'seed': "".join(self.random.choice(string.ascii_letters) for _ in range(16))} for option_name in self.options.__dict__.keys(): option = getattr(self.options, option_name) if isinstance(option, NumericOption): diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index dfa65f923ca4..7068b7ab1eff 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -187,8 +187,8 @@ def on_package(self, cmd: str, args: dict): filename = f"AP_settings.json" with open(os.path.join(self.game_communication_path, filename), 'w') as f: json.dump(args["slot_data"], f) - self.can_choose_commander = self.slot_data["can_choose_commander"] - self.starting_groove_multiplier = self.slot_data["starting_groove_multiplier"] + self.can_choose_commander = self.slot_data["commander_choice"] != 0 + self.starting_groove_multiplier = self.slot_data["groove_boost"] self.income_boost_multiplier = self.slot_data["income_boost"] self.commander_defense_boost_multiplier = self.slot_data["commander_defense_boost"] f.close() From 3171c72331d7d3ad616a859f73735222ce91f9a1 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Mon, 2 Sep 2024 14:00:12 -0400 Subject: [PATCH 36/89] Update worlds/wargroove2/docs/en_Wargroove 2.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/docs/en_Wargroove 2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md index 48ddd0f1d10a..1efd2e44eb33 100644 --- a/worlds/wargroove2/docs/en_Wargroove 2.md +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -41,7 +41,7 @@ action is taken in game. ## What is the goal of this game when randomized? The goal is to beat 1-4 final levels ending with the name `Finale` by finding the `Final North`, `Final East`, -`Final South`, or `Final West` depending on which Finale the player is playing. +`Final South` or `Final West` depending on which Finale the player is playing. All final levels require the `Final Center`. The `Northern Finale` for example, requires `Final North` and `Final Center`, but the `Western Finale` requires `Final West` and `Final Center`. From 89792f8d576d727f5f056c49bd47d1df0a913c59 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Mon, 2 Sep 2024 14:00:32 -0400 Subject: [PATCH 37/89] Update worlds/wargroove2/docs/en_Wargroove 2.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/docs/en_Wargroove 2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md index 1efd2e44eb33..041d400a69e4 100644 --- a/worlds/wargroove2/docs/en_Wargroove 2.md +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -10,7 +10,7 @@ config file. This randomizer shuffles units, map events, factions and boosts. It features a custom, non-linear campaign with 4 final levels and 4 branching paths. The player cannot beat the final levels without specific items scattered throughout the branching paths. Certain levels on these paths may require specific units or items in order to progress. -Where levels appear in the campaign are randomized. +Where levels appear in the campaign is randomized. ## What items and locations get shuffled? From f7379efd56aa00dd6aa6cdca0bfd448c8b1096f7 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Mon, 2 Sep 2024 14:00:46 -0400 Subject: [PATCH 38/89] Update worlds/wargroove2/docs/en_Wargroove 2.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/docs/en_Wargroove 2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md index 041d400a69e4..9c48a9b94335 100644 --- a/worlds/wargroove2/docs/en_Wargroove 2.md +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -1,6 +1,6 @@ # Wargroove 2 (Steam, Windows) -## Where is the settings page? +## Where is the options page? The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. From c6eb49ac544726310f4a3c393ceff907bd3a33b4 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Mon, 2 Sep 2024 14:02:57 -0400 Subject: [PATCH 39/89] Update worlds/wargroove2/docs/wargroove2_en.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/docs/wargroove2_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/wargroove2_en.md b/worlds/wargroove2/docs/wargroove2_en.md index c9de86d17c89..80526b5dd65d 100644 --- a/worlds/wargroove2/docs/wargroove2_en.md +++ b/worlds/wargroove2/docs/wargroove2_en.md @@ -4,7 +4,7 @@ - Wargroove 2 installed through Steam on Windows - Only the Steam Windows version is supported. MAC and Switch are not supported. -- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) +- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases/latest) ## Backup playerProgress files `playerProgress` and `playerProgress.bak` contain save data for all of your Wargroove 2 campaigns. From d88a92927f708c7a551d4bd049f8754a954a3949 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Mon, 2 Sep 2024 14:44:44 -0400 Subject: [PATCH 40/89] Wargroove 2: Documentation updates. --- worlds/wargroove2/docs/en_Wargroove 2.md | 8 ++-- worlds/wargroove2/docs/wargroove2_en.md | 52 +++++++++--------------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md index 9c48a9b94335..a94589bdc92c 100644 --- a/worlds/wargroove2/docs/en_Wargroove 2.md +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -40,10 +40,10 @@ action is taken in game. ## What is the goal of this game when randomized? -The goal is to beat 1-4 final levels ending with the name `Finale` by finding the `Final North`, `Final East`, -`Final South` or `Final West` depending on which Finale the player is playing. -All final levels require the `Final Center`. The `Northern Finale` for example, requires `Final North` and -`Final Center`, but the `Western Finale` requires `Final West` and `Final Center`. +The goal is to beat 1-4 final levels ending with the name "Finale" by finding the "Final North", "Final East", +"Final South" or "Final West" items depending on which Finale the player is playing. +All final levels require the "Final Center". The "Northern Finale" for example, requires "Final North" and +"Final Center" but the "Western Finale" requires "Final West" and "Final Center". ## Contributing levels to the randomizer diff --git a/worlds/wargroove2/docs/wargroove2_en.md b/worlds/wargroove2/docs/wargroove2_en.md index 80526b5dd65d..3493c11cd26a 100644 --- a/worlds/wargroove2/docs/wargroove2_en.md +++ b/worlds/wargroove2/docs/wargroove2_en.md @@ -2,27 +2,10 @@ ## Required Files -- Wargroove 2 installed through Steam on Windows +- Wargroove 2 installed through Steam on Windows. - Only the Steam Windows version is supported. MAC and Switch are not supported. - [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases/latest) -## Backup playerProgress files -`playerProgress` and `playerProgress.bak` contain save data for all of your Wargroove 2 campaigns. -Backing up these files is strongly recommended in case they become corrupted. -1. Type `%appdata%\Chucklefish\Wargroove2\save` in the file browser and hit enter. -2. Copy the `playerProgress` and `playerProgress.bak` files and paste them into a backup directory. - -## Update host.yaml to include the Wargroove 2 root directory - -1. Look for your Archipelago install files. By default, the installer puts them in `C:\ProgramData\Archipelago`. -2. Open the `host.yaml` file in your favorite text editor (Notepad will work). -3. Put your Wargroove 2 root directory in the `root_directory:` under the `wargroove2_options:` section. - - The Wargroove 2 root directory can be found by going to - `Steam->Right Click Wargroove 2->Properties->Installed Files->Browse` and copying the path in the address bar. - - Paste the path in between the quotes next to `root_directory:` in the `host.yaml`. - - You may have to replace all single \\ with \\\\. -4. Start the Wargroove 2 client. - ## Installing the Archipelago Wargroove 2 Mod and Campaign files 1. Shut down Wargroove 2 if it is open. @@ -33,13 +16,13 @@ This should install the mod and campaign for you. ## Verify the campaign can be loaded 1. Start Wargroove 2 from Steam. -2. Go to `Story->Campaign->Custom->Archipelago 2` and click play. You should see the first level. +2. Go to `Story` → `Campaign` → `Custom` → `Archipelago 2` and click play. You should see the first level. ## Starting a Multiworld game 1. Start the Wargroove 2 Client and connect to the server. Enter your username from your -[options file.](/games/Wargroove/player-options) -2. Start Wargroove 2 and play the Archipelago 2 campaign by going to `Story->Custom->Archipelago 2`. +[options file](/games/Wargroove/player-options). +2. Start Wargroove 2 and play the Archipelago 2 campaign by going to `Story` → `Custom` → `Archipelago 2`. ## Ending a Multiworld game It is strongly recommended that you delete your campaign progress after finishing a multiworld game. @@ -49,43 +32,48 @@ This can be done by going to the level selection screen in the Archipelago 2 cam ## Updating to a new version of the Wargroove 2 mod or downloading new campaign files First, delete your campaign progress by going to the level selection screen in the Archipelago campaign, hitting `ESC` and clicking the `Delete Progress` button. -Next, go to `Custom Content->Create->Campaign`, click the `Archipelago 2` campaign and click the `Delete` button. +Next, go to `Custom Content` → `Create` → `Campaign`, click the `Archipelago 2` campaign and click the `Delete` button. -Follow the `Installing the Archipelago Wargroove 2 Mod and Campaign files` steps again, but look for the latest version +Follow the +[Installing the Archipelago Wargroove 2 Mod and Campaign files](#installing-the-archipelago-wargroove-2-mod-and-campaign-files) +steps again, but look for the latest version to download. In addition, follow the steps outlined in -`Wargroove 2 crashes when trying to run the Archipelago 2 campaign` when attempting to update the +[Wargroove 2 crashes when trying to run the Archipelago 2 campaign](#wargroove-2-crashes-when-trying-to-run-the-archipelago-2-campaign) +when attempting to update the campaign files and the mod. ## Troubleshooting ### The game is too hard -`Go to the campaign overview screen->Hit escape on the keyboard->Click adjust difficulty->Adjust the setttings` +Go to the campaign overview screen → Hit escape on the keyboard → Click adjust difficulty → Adjust the settings. ### The mod doesn't load Double-check the mod installation under `%appdata%\Chucklefish\Wargroove2\mods`. There should be 3 `.dat` files in `%appdata%\Chucklefish\Wargroove2\mods\ArchipelagoMod`. Otherwise, follow -`Installing the Archipelago Wargroove 2 Mod and Campaign files` steps once more. +[Installing the Archipelago Wargroove 2 Mod and Campaign files](#installing-the-archipelago-wargroove-2-mod-and-campaign-files) +steps once more. ### Wargroove 2 crashes or there is a lua error Wargroove 2 is finicky, but there could be several causes for this. If it happens often or can be reproduced, -please submit a bug report in the tech-support channel on the [discord](https://discord.gg/archipelago). +please submit a bug report in the bug-reports channel on the [discord](https://discord.gg/archipelago). Wargroove 2 may report an error when retrying a level. This is currently a bug in the game and not the mod. ### Wargroove 2 crashes when trying to run the Archipelago 2 campaign This is caused by not deleting campaign progress before updating the mod and campaign files. -1. Go to `Custom Content->Create->Campaign->Archipelago 2->Edit` and attempt to update the mod. +1. Go to `Custom Content` → `Create` → `Campaign` → `Archipelago 2` → `Edit` and attempt to update the mod. 2. Wargroove 2 will give an error message. -3. Go back to `Custom Content->Create->Campaign->Archipelago 2->Edit` and attempt to update the mod again. +3. Go back to `Custom Content` → `Create` → `Campaign` → `Archipelago 2` → `Edit` and attempt to update the mod again. 4. Wargroove 2 crashes. -5. Go back to `Custom Content->Create->Campaign->Archipelago 2->Edit` and attempt to update the mod again. +5. Go back to `Custom Content` → `Create` → `Campaign` → `Archipelago 2` → `Edit` and attempt to update the mod again. 6. In the edit menu, hit `ESC` and click `Delete Progress`. 7. In the edit menu, hit `ESC` and click `Mods`. 8. Uncheck the `Archipelago Mod` box, check it again and then click `Save and Reload Map` -9. If the above steps do not allow you to start the campaign from `Story->Campaign->Custom->Archipelago 2` replace +9. If the above steps do not allow you to start the campaign from `Story` → `Campaign` → `Custom` → `Archipelago 2` replace `playerProgress` and `playerProgress.bak` with your previously backed up files. ### Mod is out of date when trying to run the Archipelago campaign -Please follow the above steps in `Wargroove 2 crashes when trying to run the Archipelago 2 campaign`. +Please follow the above steps in +[Wargroove 2 crashes when trying to run the Archipelago 2 campaign](#wargroove-2-crashes-when-trying-to-run-the-archipelago-2-campaign). ### Using undo turn ignores the income boost or causes bugs Undoing a turn is bugged in Wargroove 2 and not supported in the randomizer. From 98cd4d7d3ecc6d047337ce13e91ef8e49428fd1b Mon Sep 17 00:00:00 2001 From: FlySniper Date: Tue, 3 Sep 2024 08:22:55 -0400 Subject: [PATCH 41/89] Update worlds/wargroove2/docs/wargroove2_en.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/docs/wargroove2_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/wargroove2_en.md b/worlds/wargroove2/docs/wargroove2_en.md index 3493c11cd26a..c6b3a1a4ba50 100644 --- a/worlds/wargroove2/docs/wargroove2_en.md +++ b/worlds/wargroove2/docs/wargroove2_en.md @@ -67,7 +67,7 @@ This is caused by not deleting campaign progress before updating the mod and cam 5. Go back to `Custom Content` → `Create` → `Campaign` → `Archipelago 2` → `Edit` and attempt to update the mod again. 6. In the edit menu, hit `ESC` and click `Delete Progress`. 7. In the edit menu, hit `ESC` and click `Mods`. -8. Uncheck the `Archipelago Mod` box, check it again and then click `Save and Reload Map` +8. Uncheck the `Archipelago Mod` box, check it again and then click `Save and Reload Map`. 9. If the above steps do not allow you to start the campaign from `Story` → `Campaign` → `Custom` → `Archipelago 2` replace `playerProgress` and `playerProgress.bak` with your previously backed up files. From ab9451c9ce53dc9596d984c97705c7360a953118 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Tue, 3 Sep 2024 08:23:09 -0400 Subject: [PATCH 42/89] Update worlds/wargroove2/docs/wargroove2_en.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/docs/wargroove2_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/wargroove2_en.md b/worlds/wargroove2/docs/wargroove2_en.md index c6b3a1a4ba50..7aecb9d9e91c 100644 --- a/worlds/wargroove2/docs/wargroove2_en.md +++ b/worlds/wargroove2/docs/wargroove2_en.md @@ -4,7 +4,7 @@ - Wargroove 2 installed through Steam on Windows. - Only the Steam Windows version is supported. MAC and Switch are not supported. -- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases/latest) +- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases/latest). ## Installing the Archipelago Wargroove 2 Mod and Campaign files From 705694c28aa6822f56d2792e401a8f78677bd5f0 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Tue, 3 Sep 2024 08:35:49 -0400 Subject: [PATCH 43/89] Wargroove 2: Eliminated a bunch of warnings from client.py --- worlds/wargroove2/client.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 7068b7ab1eff..51ce83f4ac7d 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -7,31 +7,30 @@ import pkgutil import random import typing +import Utils +import json +import logging +import ModuleUpdate from typing import Tuple, List, Iterable, Dict from settings import get_settings from . import Wargroove2World from .Items import item_table, faction_table, CommanderData, ItemData, item_id_name -import ModuleUpdate from .Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, get_level_table, FINAL_LEVEL_1, \ FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, get_final_levels from .Locations import location_table from .RegionFilter import Wargroove2LogicFilter +from NetUtils import ClientStatus +from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \ + CommonContext, server_loop -ModuleUpdate.update() -import Utils -import json -import logging +ModuleUpdate.update() if __name__ == "__main__": Utils.init_logging("Wargroove2Client", exception_logger="Client") -from NetUtils import ClientStatus -from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \ - CommonContext, server_loop - wg2_logger = logging.getLogger("WG2") @@ -44,7 +43,7 @@ def _cmd_resync(self): def _cmd_commander(self, *commander_name: Iterable[str]): """Set the current commander to the given commander.""" if commander_name: - self.ctx.set_commander(' '.join(commander_name)) + self.ctx.set_commander(' '.join(commander_name[0])) else: if self.ctx.can_choose_commander: commanders = self.ctx.get_commanders() @@ -340,7 +339,7 @@ class Wargroove2Manager(GameManager): level_4_Layout: GridLayout(cols=1) trigger_tracker: BoxLayout boost_tracker: BoxLayout - commander_buttons: Dict[int, List[CommanderButton]] + commander_buttons: Dict[str, List[CommanderButton]] tracker_items = { "Swordsman": ItemData(None, "Unit", False), "Dog": ItemData(None, "Unit", False), @@ -467,7 +466,8 @@ def update_levels(self): if stored_data is not None and final_level_1_name in stored_data: level_name_text = f"\n{final_level_1_name}" status_color = (1.0, 1.0, 1.0, 1) - elif final_level_1_name is not None and region_filter.has_all({"Final North", "Final Center"}, self.ctx.slot): + elif final_level_1_name is not None and region_filter.has_all(["Final North", "Final Center"], + self.ctx.slot): level_name_text = f"\n{final_level_1_name}" is_beatable: bool = final_level_rules[final_level_1_name] \ [f"{final_level_1_name}: Victory"](region_filter) @@ -483,7 +483,8 @@ def update_levels(self): if stored_data is not None and final_level_2_name in stored_data: level_name_text = f"\n{final_level_2_name}" status_color = (1.0, 1.0, 1.0, 1) - elif final_level_2_name is not None and region_filter.has_all({"Final East", "Final Center"}, self.ctx.slot): + elif final_level_2_name is not None and region_filter.has_all(["Final East", "Final Center"], + self.ctx.slot): level_name_text = f"\n{final_level_2_name}" is_beatable: bool = final_level_rules[final_level_2_name] \ [f"{final_level_2_name}: Victory"](region_filter) @@ -499,7 +500,8 @@ def update_levels(self): if stored_data is not None and final_level_3_name in stored_data: level_name_text = f"\n{final_level_3_name}" status_color = (1.0, 1.0, 1.0, 1) - elif final_level_3_name is not None and region_filter.has_all({"Final South", "Final Center"}, self.ctx.slot): + elif final_level_3_name is not None and region_filter.has_all(["Final South", "Final Center"], + self.ctx.slot): level_name_text = f"\n{final_level_3_name}" is_beatable: bool = final_level_rules[final_level_3_name] \ [f"{final_level_3_name}: Victory"](region_filter) @@ -515,7 +517,8 @@ def update_levels(self): if stored_data is not None and final_level_4_name in stored_data: level_name_text = f"\n{final_level_4_name}" status_color = (1.0, 1.0, 1.0, 1) - elif final_level_4_name is not None and region_filter.has_all({"Final West", "Final Center"}, self.ctx.slot): + elif final_level_4_name is not None and region_filter.has_all(["Final West", "Final Center"], + self.ctx.slot): level_name_text = f"\n{final_level_4_name}" is_beatable: bool = final_level_rules[final_level_4_name] \ [f"{final_level_4_name}: Victory"](region_filter) @@ -621,7 +624,7 @@ def set_commander(self, commander_name: str) -> bool: """Sets the current commander to the given one, if possible""" if not self.can_choose_commander: wg2_logger.error("Cannot set commanders in this game mode.") - return + return False match_name = commander_name.lower() for commander, unlocked in self.get_commanders(): if commander.name.lower() == match_name or commander.alt_name and commander.alt_name.lower() == match_name: @@ -649,7 +652,7 @@ def get_commanders(self) -> List[Tuple[CommanderData, bool]]: async def game_watcher(ctx: Wargroove2Context): while not ctx.exit_event.is_set(): - if ctx.syncing == True: + if ctx.syncing: sync_msg = [{'cmd': 'Sync'}] if ctx.locations_checked: sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) From 78c94c9bec141c6a7b42629ed64f8189bf030863 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Tue, 3 Sep 2024 10:12:40 -0400 Subject: [PATCH 44/89] Wargroove 2: Refactored level lists to be static instead of per Wargroove 2 World. --- worlds/wargroove2/Levels.py | 756 ++++++++++++++++++---------------- worlds/wargroove2/Regions.py | 20 +- worlds/wargroove2/Rules.py | 18 +- worlds/wargroove2/__init__.py | 19 +- worlds/wargroove2/client.py | 18 +- 5 files changed, 442 insertions(+), 389 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index cac557a60ece..2a9f93418663 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -3,6 +3,7 @@ from BaseClasses import Region, Entrance, MultiWorld from .Locations import location_table, Wargroove2Location from worlds.generic.Rules import set_rule + if TYPE_CHECKING: from . import Wargroove2World @@ -36,380 +37,437 @@ def set_region_exit_rules(region: Region, world: "Wargroove2World", locations: L class Wargroove2Level: - player: int name: str file_name: str location_rules: dict - region: Region + region_name: str victory_locations: List[str] - low_victory_checks: bool = True has_ocean: bool = True def __init__(self, name: str, file_name: str, location_rules: dict, victory_locations: List[str] = [], - low_victory_checks: bool = True, has_ocean: bool = True): + has_ocean: bool = True): self.name = name self.file_name = file_name self.location_rules = location_rules - self.low_victory_checks = low_victory_checks self.has_ocean = has_ocean if victory_locations: self.victory_locations = victory_locations else: self.victory_locations = [name + ': Victory'] - def define_access_rules(self, world: "Wargroove2World", additional_rule=lambda state: True) -> None: + def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=lambda state: True) -> None: for location_name, rule in self.location_rules.items(): set_rule(world.get_location(location_name), lambda state, current_rule=rule: - state.can_reach_region(self.region.name, self.player) and current_rule(state) and additional_rule(state)) - set_region_exit_rules(self.region, world, self.victory_locations, operator='and') + state.can_reach_region(self.region_name, player) and current_rule(state, player)() and + additional_rule(state)) + region = world.get_region(self.region_name) + set_region_exit_rules(region, world, self.victory_locations, operator='and') - def define_region(self, name: str, multiworld: MultiWorld, exits=None) -> Region: - self.region = Region(name, self.player, multiworld) + def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=None) -> Region: + self.region_name = name + region = Region(name, player, multiworld) for location in self.location_rules.keys(): loc_id = location_table.get(location, 0) - location = Wargroove2Location(self.player, location, loc_id, self.region) - self.region.locations.append(location) + location = Wargroove2Location(player, location, loc_id, region) + region.locations.append(location) if exits: for exit in exits: - self.region.exits.append(Entrance(self.player, f"{name} exits to {exit}", self.region)) - - return self.region - - -def get_level_table(player: int) -> List[Wargroove2Level]: - levels = [ - Wargroove2Level( - name="Spire Fire", - file_name="Spire_Fire.json", - location_rules={ - "Spire Fire: Victory": lambda state: state.has_any(["Mage", "Witch"], player), - "Spire Fire: Kill Enemy Sky Rider": lambda state: state.has("Witch", player), - "Spire Fire: Win without losing your Dragon": lambda state: state.has_any(["Mage", "Witch"], player) - }, - has_ocean=False - ), - Wargroove2Level( - name="Nuru's Vengeance", - file_name="Nuru_Vengeance.json", - location_rules={ - "Nuru's Vengeance: Victory": lambda state: state.has("Knight", player), - "Nuru's Vengeance: Defeat all Dogs": lambda state: state.has("Knight", player), - "Nuru's Vengeance: Spearman Destroys the Gate": lambda state: state.has_all( - ["Knight", "Spearman"], player) - }, - has_ocean=False - ), - Wargroove2Level( - name="Cherrystone Landing", - file_name="Cherrystone_Landing.json", - location_rules={ - "Cherrystone Landing: Victory": lambda state: state.has_all(["Warship", "Barge", "Landing Event"], - player), - "Cherrystone Landing: Smacked a Trebuchet": lambda state: state.has_all( - ["Warship", "Barge", "Landing Event", "Golem"], player), - "Cherrystone Landing: Smacked a Fortified Village": lambda state: state.has_all( - ["Barge", "Landing Event", "Golem"], player) - }, - low_victory_checks=False - ), - Wargroove2Level( - name="Slippery Bridge", - file_name="Slippery_Bridge.json", - location_rules={ - "Slippery Bridge: Victory": lambda state: state.has("Frog", player), - "Slippery Bridge: Control all Sea Villages": lambda state: state.has("Merfolk", player), - } - ), - Wargroove2Level( - name="Den-Two-Away", - file_name="Den-Two-Away.json", - location_rules={ - "Den-Two-Away: Victory": lambda state: state.has("Harpy", player), - "Den-Two-Away: Commander Captures the Lumbermill": lambda state: state.has_all(["Harpy", "Balloon"], - player), - } - ), - Wargroove2Level( - name="Skydiving", - file_name="Skydiving.json", - location_rules={ - "Skydiving: Victory": lambda state: state.has_all(["Balloon", "Airstrike Event"], player), - "Skydiving: Dragon Defeats Stronghold": lambda state: state.has_all( - ["Balloon", "Airstrike Event", "Dragon"], player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Sunken Forest", - file_name="Sunken_Forest.json", - location_rules={ - "Sunken Forest: Victory": lambda state: state.has_any(["Mage", "Harpoon Ship"], player), - "Sunken Forest: High Ground": lambda state: state.has("Archer", player), - "Sunken Forest: Coastal Siege": lambda state: state.has("Warship", player) and state.has_any( - ["Mage", "Harpoon Ship"], player), - } - ), - Wargroove2Level( - name="Tenri's Mistake", - file_name="Tenris_Mistake.json", - location_rules={ - "Tenri's Mistake: Victory": lambda state: state.has_any(["Balloon", "Air Trooper"], player), - "Tenri's Mistake: Mighty Barracks": lambda state: state.has_any(["Balloon", "Air Trooper"], player), - "Tenri's Mistake: Commander Arrives": lambda state: state.has("Balloon", player), - } - ), - Wargroove2Level( - name="Enmity Cliffs", - file_name="Enmity_Cliffs.json", - location_rules={ - "Enmity Cliffs: Victory": lambda state: state.has_all(["Spearman", "Bridges Event"], player), - "Enmity Cliffs: Spear Flood": lambda state: state.has("Spearman", player), - "Enmity Cliffs: Across the Gap": lambda state: state.has_any(["Archer", "Rifleman"], player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Terrible Tributaries", - file_name="Terrible_Tributaries.json", - location_rules={ - "Terrible Tributaries: Victory": lambda state: state.has("River Boat", player), - "Terrible Tributaries: Swimming Knights": lambda state: state.has_all(["Merfolk", "River Boat"], - player), - "Terrible Tributaries: Steal Code Names": lambda state: state.has_all(["Thief", "River Boat"], player), - } - ), - Wargroove2Level( - name="Beached", - file_name="Beached.json", - location_rules={ - "Beached: Victory": lambda state: state.has("Knight", player), - "Beached: Turtle Power": lambda state: state.has_all(["Turtle", "Knight"], player), - "Beached: Happy Turtle": lambda state: state.has_all(["Turtle", "Knight"], player), - } - ), - Wargroove2Level( - name="Portal Peril", - file_name="Portal_Peril.json", - location_rules={ - "Portal Peril: Victory": lambda state: state.has("Wagon", player), - "Portal Peril: Unleash the Hounds": lambda state: state.has("Wagon", player), - "Portal Peril: Overcharged": lambda state: state.has("Wagon", player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Riflemen Blockade", - file_name="Riflemen_Blockade.json", - location_rules={ - "Riflemen Blockade: Victory": lambda state: state.has("Rifleman", player), - "Riflemen Blockade: From the Mountains": lambda state: state.has_all(["Rifleman", "Harpy"], player), - "Riflemen Blockade: To the Road": lambda state: state.has_all(["Rifleman", "Dragon"], player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Towers of the Abyss", - file_name="Towers_of_the_Abyss.json", - location_rules={ - "Towers of the Abyss: Victory": lambda state: state.has("Ballista", player), - "Towers of the Abyss: Siege Master": lambda state: state.has_all(["Ballista", "Trebuchet"], player), - "Towers of the Abyss: Perfect Defense": lambda state: state.has_all(["Ballista", "Walls Event"], - player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Wagon Freeway", - file_name="Wagon_Freeway.json", - location_rules={ - "Wagon Freeway: Victory": lambda state: state.has_all(["Wagon", "Spearman"], player), - "Wagon Freeway: All Mine Now": lambda state: True, - "Wagon Freeway: Pigeon Carrier": lambda state: state.has("Air Trooper", player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Kraken Strait", - file_name="Kraken_Strait.json", - location_rules={ - "Kraken Strait: Victory": lambda state: state.has_all(["Frog", "Kraken"], player), - "Kraken Strait: Well Defended": lambda state: state.has_all(["Frog", "Kraken"], player), - "Kraken Strait: Clipped Wings": lambda state: state.has("Harpoon Ship", player), - } - ), - Wargroove2Level( - name="Gnarled Mountaintop", - file_name="Gnarled_Mountaintop.json", - location_rules={ - "Gnarled Mountaintop: Victory": lambda state: state.has("Harpy", player), - "Gnarled Mountaintop: Watch the Watchtower": lambda state: state.has("Harpy", player), - "Gnarled Mountaintop: Vine Skip": lambda state: state.has("Air Trooper", player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Gold Rush", - file_name="Gold_Rush.json", - location_rules={ - "Gold Rush: Victory": lambda state: state.has("Thief", player) and - state.has_any(["Rifleman", "Merfolk", "Warship"], player), - "Gold Rush: Lumber Island": lambda state: state.has_any(["Merfolk", "River Boat", "Barge"], player), - "Gold Rush: Starglass Rush": lambda state: state.has_any(["River Boat", "Barge"], player), - } - ), - Wargroove2Level( - name="Finishing Blow", - file_name="Finishing_Blow.json", - location_rules={ - "Finishing Blow: Victory": lambda state: state.has("Witch", player), - "Finishing Blow: Mass Destruction": lambda state: state.has("Witch", player), - "Finishing Blow: Defortification": lambda state: state.has("Thief", player), - } - ), - Wargroove2Level( - name="Frantic Inlet", - file_name="Frantic_Inlet.json", - location_rules={ - "Frantic Inlet: Victory": lambda state: state.has("Turtle", player) and - state.has_any(["Barge", "Knight"], player), - "Frantic Inlet: Plug the Gap": lambda state: state.has("Spearman", player), - "Frantic Inlet: Portal Detour": lambda state: state.has_all(["Turtle", "Barge"], player), - } - ), - Wargroove2Level( - name="Operation Seagull", - file_name="Operation_Seagull.json", - location_rules={ - "Operation Seagull: Victory": lambda state: state.has("Merfolk", player) and - state.has_any(["Harpoon Ship", "Witch"], player) and - state.has_any(["Turtle", "Harpy"], player), - "Operation Seagull: Crack the Crystal": lambda state: state.has_any(["Warship", "Kraken"], player), - "Operation Seagull: Counter Break": lambda state: state.has("Dragon", player) and - state.has_all(["Harpoon Ship", "Witch"], player), - } - ), - Wargroove2Level( - name="Air Support", - file_name="Air_Support.json", - location_rules={ - "Air Support: Victory": lambda state: state.has_all(["Dragon", "Bridges Event"], player), - "Air Support: Roadkill": lambda state: state.has_all(["Dragon", "Bridges Event"], player), - "Air Support: Flight Economy": lambda state: state.has_all(["Air Trooper", "Bridges Event"], player), - } - ), - Wargroove2Level( - name="Fortification", - file_name="Fortification.json", - location_rules={ - "Fortification: Victory": lambda state: state.has_all(["Golem", "Walls Event"], player) and - state.has_any(["Archer", "Trebuchet"], player), - "Fortification: Hyper Repair": lambda state: state.has_all(["Golem", "Walls Event"], player), - "Fortification: Defensive Artillery": lambda state: state.has("Trebuchet", player), - }, - has_ocean=False - ), - Wargroove2Level( - name="A Ribbitting Time", - file_name="A_Ribbitting_Time.json", - location_rules={ - "A Ribbitting Time: Victory": lambda state: state.has("Frog", player), - "A Ribbitting Time: Leap Frog": lambda state: state.has("Frog", player), - "A Ribbitting Time: Frogway Robbery": lambda state: state.has_all(["Frog", "Thief"], player), - } - ), - Wargroove2Level( - name="Precarious Cliffs", - file_name="Precarious_Cliffs.json", - location_rules={ - "Precarious Cliffs: Victory": lambda state: state.has_all(["Airstrike Event", "Archer"], player), - "Precarious Cliffs: No Crit for You": lambda state: state.has("Airstrike Event", player), - "Precarious Cliffs: Out Ranged": lambda state: state.has_all(["Airstrike Event", "Archer"], player), - }, - has_ocean=False - ), - Wargroove2Level( - name="Split Valley", - file_name="Split_Valley.json", - location_rules={ - "Split Valley: Victory": lambda state: state.has("Trebuchet", player) and - state.has_any(["Bridges Event", "Air Trooper"], player), - "Split Valley: Longshot": lambda state: state.has("Trebuchet", player), - "Split Valley: Ranged Trinity": lambda state: state.has_all(["Trebuchet", "Archer", "Ballista"], - player), - } - ), - Wargroove2Level( - name="Grand Theft Village", - file_name="Grand_Theft_Village.json", - location_rules={ - "Grand Theft Village: Victory": lambda state: state.has("Thief", player) and - state.has_any(["Mage", "Ballista"], player), - "Grand Theft Village: Stand Tall": lambda state: state.has("Golem", player), - "Grand Theft Village: Pillager": lambda state: True, - }, - has_ocean=False - ), - Wargroove2Level( - name="Bridge Brigade", - file_name="Bridge_Brigade.json", - location_rules={ - "Bridge Brigade: Victory": lambda state: state.has_all(["Warship", "Spearman"], player), - "Bridge Brigade: From the Depths": lambda state: state.has("Kraken", player), - "Bridge Brigade: Back to the Depths": lambda state: - state.has_all(["Warship", "Spearman", "Kraken"], player), - }, - has_ocean=False - ), - ] - for level in levels: - level.player = player - return levels + region.exits.append(Entrance(player, f"{name} exits to {exit}", region)) + return region -def get_final_levels(player: int) -> List[Wargroove2Level]: - levels = [ - Wargroove2Level( - name="Disastrous Crossing", - file_name="Disastrous_Crossing.json", - location_rules={"Disastrous Crossing: Victory": - lambda state: state.has_any(["Merfolk", "River Boat"], player) and - state.has_any(["Knight", "Kraken"], player)} - ), - Wargroove2Level( - name="Dark Mirror", - file_name="Dark_Mirror.json", - location_rules={"Dark Mirror: Victory": lambda state: state.has("Archer", player) and - state.has_any(["Mage", "Ballista"], player) and - state.has_any(["Harpy", "Dragon"], player)}, - has_ocean=False - ), - Wargroove2Level( - name="Doomed Metropolis", - file_name="Doomed_Metropolis.json", - location_rules={"Doomed Metropolis: Victory": lambda state: state.has_all(["Mage", "Knight"], player)}, - has_ocean=False - ), - Wargroove2Level( - name="Dementia Castle", - file_name="Dementia_Castle.json", - location_rules={"Dementia Castle: Victory": - lambda state: state.has_all(["Merfolk", "Mage", "Golem", "Harpy"], player)} - ), - ] - for level in levels: - level.player = player - return levels - -def get_first_level(player: int) -> Wargroove2Level: - first_level = Wargroove2Level( - name="Humble Beginnings Rebirth", - file_name="", +high_victory_checks_levels = [ + Wargroove2Level( + name="Cherrystone Landing", + file_name="Cherrystone_Landing.json", + location_rules={ + "Cherrystone Landing: Victory": lambda state, player: lambda + state=state: state.has_all(["Warship", "Barge", "Landing Event"], + player), + "Cherrystone Landing: Smacked a Trebuchet": lambda state, player: lambda + state=state: state.has_all( + ["Warship", "Barge", "Landing Event", "Golem"], player), + "Cherrystone Landing: Smacked a Fortified Village": lambda state, player: lambda + state=state: state.has_all( + ["Barge", "Landing Event", "Golem"], player) + } + ), +] +low_victory_checks_levels = [ + Wargroove2Level( + name="Spire Fire", + file_name="Spire_Fire.json", + location_rules={ + "Spire Fire: Victory": lambda state, player: lambda state=state: state.has_any( + ["Mage", "Witch"], player), + "Spire Fire: Kill Enemy Sky Rider": lambda state, player: lambda + state=state: state.has("Witch", player), + "Spire Fire: Win without losing your Dragon": lambda state, player: lambda + state=state: state.has_any(["Mage", "Witch"], player) + }, + has_ocean=False + ), + Wargroove2Level( + name="Nuru's Vengeance", + file_name="Nuru_Vengeance.json", + location_rules={ + "Nuru's Vengeance: Victory": lambda state, player: lambda state=state: state.has( + "Knight", player), + "Nuru's Vengeance: Defeat all Dogs": lambda state, player: lambda + state=state: state.has("Knight", player), + "Nuru's Vengeance: Spearman Destroys the Gate": lambda state, player: lambda + state=state: state.has_all( + ["Knight", "Spearman"], player) + }, + has_ocean=False + ), + Wargroove2Level( + name="Slippery Bridge", + file_name="Slippery_Bridge.json", + location_rules={ + "Slippery Bridge: Victory": lambda state, player: lambda state=state: state.has("Frog", player), + "Slippery Bridge: Control all Sea Villages": lambda state, player: lambda state=state: + state.has("Merfolk", player), + } + ), + Wargroove2Level( + name="Den-Two-Away", + file_name="Den-Two-Away.json", + location_rules={ + "Den-Two-Away: Victory": lambda state, player: lambda state=state: state.has("Harpy", player), + "Den-Two-Away: Commander Captures the Lumbermill": lambda state, player: lambda + state=state: state.has_all(["Harpy", "Balloon"], player), + } + ), + Wargroove2Level( + name="Skydiving", + file_name="Skydiving.json", + location_rules={ + "Skydiving: Victory": lambda state, player: lambda state=state: state.has_all( + ["Balloon", "Airstrike Event"], player), + "Skydiving: Dragon Defeats Stronghold": lambda state, player: lambda + state=state: state.has_all( + ["Balloon", "Airstrike Event", "Dragon"], player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Sunken Forest", + file_name="Sunken_Forest.json", + location_rules={ + "Sunken Forest: Victory": lambda state, player: lambda state=state: state.has_any( + ["Mage", "Harpoon Ship"], player), + "Sunken Forest: High Ground": lambda state, player: lambda state=state: state.has( + "Archer", player), + "Sunken Forest: Coastal Siege": lambda state, player: lambda state=state: state.has( + "Warship", player) and state.has_any( + ["Mage", "Harpoon Ship"], player), + } + ), + Wargroove2Level( + name="Tenri's Mistake", + file_name="Tenris_Mistake.json", + location_rules={ + "Tenri's Mistake: Victory": lambda state, player: lambda state=state: state.has_any( + ["Balloon", "Air Trooper"], player), + "Tenri's Mistake: Mighty Barracks": lambda state, player: lambda + state=state: state.has_any(["Balloon", "Air Trooper"], player), + "Tenri's Mistake: Commander Arrives": lambda state, player: lambda + state=state: state.has("Balloon", player), + } + ), + Wargroove2Level( + name="Enmity Cliffs", + file_name="Enmity_Cliffs.json", + location_rules={ + "Enmity Cliffs: Victory": lambda state, player: lambda state=state: state.has_all( + ["Spearman", "Bridges Event"], player), + "Enmity Cliffs: Spear Flood": lambda state, player: lambda state=state: state.has( + "Spearman", player), + "Enmity Cliffs: Across the Gap": lambda state, player: lambda + state=state: state.has_any(["Archer", "Rifleman"], player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Terrible Tributaries", + file_name="Terrible_Tributaries.json", + location_rules={ + "Terrible Tributaries: Victory": lambda state, player: lambda state=state: state.has( + "River Boat", player), + "Terrible Tributaries: Swimming Knights": lambda state, player: lambda + state=state: state.has_all(["Merfolk", "River Boat"], player), + "Terrible Tributaries: Steal Code Names": lambda state, player: lambda + state=state: state.has_all(["Thief", "River Boat"], player), + } + ), + Wargroove2Level( + name="Beached", + file_name="Beached.json", + location_rules={ + "Beached: Victory": lambda state, player: lambda state=state: state.has("Knight", player), + "Beached: Turtle Power": lambda state, player: lambda state=state: state.has_all( + ["Turtle", "Knight"], player), + "Beached: Happy Turtle": lambda state, player: lambda state=state: state.has_all( + ["Turtle", "Knight"], player), + } + ), + Wargroove2Level( + name="Portal Peril", + file_name="Portal_Peril.json", + location_rules={ + "Portal Peril: Victory": lambda state, player: lambda state=state: state.has("Wagon", player), + "Portal Peril: Unleash the Hounds": lambda state, player: lambda state=state: state.has("Wagon", player), + "Portal Peril: Overcharged": lambda state, player: lambda state=state: state.has("Wagon", player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Riflemen Blockade", + file_name="Riflemen_Blockade.json", + location_rules={ + "Riflemen Blockade: Victory": lambda state, player: lambda state=state: state.has( + "Rifleman", player), + "Riflemen Blockade: From the Mountains": lambda state, player: lambda + state=state: state.has_all(["Rifleman", "Harpy"], player), + "Riflemen Blockade: To the Road": lambda state, player: lambda + state=state: state.has_all(["Rifleman", "Dragon"], player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Towers of the Abyss", + file_name="Towers_of_the_Abyss.json", location_rules={ - "Humble Beginnings Rebirth: Victory": lambda state: True, - "Humble Beginnings Rebirth: Talk to Nadia": lambda state: True, - "Humble Beginnings Rebirth: Good Dog": lambda state: True + "Towers of the Abyss: Victory": lambda state, player: lambda state=state: state.has("Ballista", player), + "Towers of the Abyss: Siege Master": lambda state, player: lambda state=state: + state.has_all(["Ballista", "Trebuchet"], player), + "Towers of the Abyss: Perfect Defense": lambda state, player: lambda state=state: + state.has_all(["Ballista", "Walls Event"], player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Wagon Freeway", + file_name="Wagon_Freeway.json", + location_rules={ + "Wagon Freeway: Victory": lambda state, player: lambda state=state: state.has_all( + ["Wagon", "Spearman"], player), + "Wagon Freeway: All Mine Now": lambda state, player: lambda state=state: True, + "Wagon Freeway: Pigeon Carrier": lambda state, player: lambda state=state: + state.has("Air Trooper", player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Kraken Strait", + file_name="Kraken_Strait.json", + location_rules={ + "Kraken Strait: Victory": lambda state, player: lambda state=state: + state.has_all(["Frog", "Kraken"], player), + "Kraken Strait: Well Defended": lambda state, player: lambda state=state: + state.has_all(["Frog", "Kraken"], player), + "Kraken Strait: Clipped Wings": lambda state, player: lambda state=state: state.has("Harpoon Ship", player), + } + ), + Wargroove2Level( + name="Gnarled Mountaintop", + file_name="Gnarled_Mountaintop.json", + location_rules={ + "Gnarled Mountaintop: Victory": lambda state, player: lambda state=state: state.has( + "Harpy", player), + "Gnarled Mountaintop: Watch the Watchtower": lambda state, player: lambda state=state: state.has( + "Harpy", player), + "Gnarled Mountaintop: Vine Skip": lambda state, player: lambda state=state: state.has( + "Air Trooper", player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Gold Rush", + file_name="Gold_Rush.json", + location_rules={ + "Gold Rush: Victory": lambda state, player: lambda state=state: state.has("Thief", player) and + state.has_any( + ["Rifleman", "Merfolk", "Warship"], + player), + "Gold Rush: Lumber Island": lambda state, player: lambda state=state: state.has_any( + ["Merfolk", "River Boat", "Barge"], player), + "Gold Rush: Starglass Rush": lambda state, player: lambda state=state: state.has_any( + ["River Boat", "Barge"], player), + } + ), + Wargroove2Level( + name="Finishing Blow", + file_name="Finishing_Blow.json", + location_rules={ + "Finishing Blow: Victory": lambda state, player: lambda state=state: state.has( + "Witch", player), + "Finishing Blow: Mass Destruction": lambda state, player: lambda + state=state: state.has("Witch", player), + "Finishing Blow: Defortification": lambda state, player: lambda + state=state: state.has("Thief", player), + } + ), + Wargroove2Level( + name="Frantic Inlet", + file_name="Frantic_Inlet.json", + location_rules={ + "Frantic Inlet: Victory": lambda state, player: lambda state=state: state.has( + "Turtle", player) and state.has_any(["Barge", "Knight"], player), + "Frantic Inlet: Plug the Gap": lambda state, player: lambda state=state: state.has("Spearman", player), + "Frantic Inlet: Portal Detour": lambda state, player: lambda + state=state: state.has_all(["Turtle", "Barge"], player), + } + ), + Wargroove2Level( + name="Operation Seagull", + file_name="Operation_Seagull.json", + location_rules={ + "Operation Seagull: Victory": lambda state, player: lambda state=state: state.has( + "Merfolk", player) and state.has_any(["Harpoon Ship", "Witch"], player) and state.has_any( + ["Turtle", "Harpy"], player), + "Operation Seagull: Crack the Crystal": lambda state, player: lambda + state=state: state.has_any(["Warship", "Kraken"], player), + "Operation Seagull: Counter Break": lambda state, player: lambda + state=state: state.has("Dragon", player) and + state.has_all(["Harpoon Ship", "Witch"], player), + } + ), + Wargroove2Level( + name="Air Support", + file_name="Air_Support.json", + location_rules={ + "Air Support: Victory": lambda state, player: lambda state=state: state.has_all( + ["Dragon", "Bridges Event"], player), + "Air Support: Roadkill": lambda state, player: lambda state=state: state.has_all( + ["Dragon", "Bridges Event"], player), + "Air Support: Flight Economy": lambda state, player: lambda + state=state: state.has_all(["Air Trooper", "Bridges Event"], player), } - ) - first_level.player = player - return first_level + ), + Wargroove2Level( + name="Fortification", + file_name="Fortification.json", + location_rules={ + "Fortification: Victory": lambda state, player: lambda state=state: state.has_all( + ["Golem", "Walls Event"], player) and state.has_any(["Archer", "Trebuchet"], player), + "Fortification: Hyper Repair": lambda state, player: lambda + state=state: state.has_all(["Golem", "Walls Event"], player), + "Fortification: Defensive Artillery": lambda state, player: lambda + state=state: state.has("Trebuchet", player), + }, + has_ocean=False + ), + Wargroove2Level( + name="A Ribbitting Time", + file_name="A_Ribbitting_Time.json", + location_rules={ + "A Ribbitting Time: Victory": lambda state, player: lambda state=state: state.has( + "Frog", player), + "A Ribbitting Time: Leap Frog": lambda state, player: lambda state=state: state.has( + "Frog", player), + "A Ribbitting Time: Frogway Robbery": lambda state, player: lambda + state=state: state.has_all(["Frog", "Thief"], player), + } + ), + Wargroove2Level( + name="Precarious Cliffs", + file_name="Precarious_Cliffs.json", + location_rules={ + "Precarious Cliffs: Victory": lambda state, player: lambda state=state: state.has_all( + ["Airstrike Event", "Archer"], player), + "Precarious Cliffs: No Crit for You": lambda state, player: lambda + state=state: state.has("Airstrike Event", player), + "Precarious Cliffs: Out Ranged": lambda state, player: lambda + state=state: state.has_all(["Airstrike Event", "Archer"], player), + }, + has_ocean=False + ), + Wargroove2Level( + name="Split Valley", + file_name="Split_Valley.json", + location_rules={ + "Split Valley: Victory": lambda state, player: lambda state=state: state.has( + "Trebuchet", player) and state.has_any(["Bridges Event", "Air Trooper"], player), + "Split Valley: Longshot": lambda state, player: lambda state=state: state.has( + "Trebuchet", player), + "Split Valley: Ranged Trinity": lambda state, player: lambda + state=state: state.has_all(["Trebuchet", "Archer", "Ballista"], + player), + } + ), + Wargroove2Level( + name="Grand Theft Village", + file_name="Grand_Theft_Village.json", + location_rules={ + "Grand Theft Village: Victory": lambda state, player: lambda state=state: state.has( + "Thief", player) and state.has_any(["Mage", "Ballista"], player), + "Grand Theft Village: Stand Tall": lambda state, player: lambda + state=state: state.has("Golem", player), + "Grand Theft Village: Pillager": lambda state, player: lambda state=state: True, + }, + has_ocean=False + ), + Wargroove2Level( + name="Bridge Brigade", + file_name="Bridge_Brigade.json", + location_rules={ + "Bridge Brigade: Victory": lambda state, player: lambda state=state: state.has_all( + ["Warship", "Spearman"], player), + "Bridge Brigade: From the Depths": lambda state, player: lambda + state=state: state.has("Kraken", player), + "Bridge Brigade: Back to the Depths": lambda state, player: lambda state=state: + state.has_all(["Warship", "Spearman", "Kraken"], player), + }, + has_ocean=False + ), +] + +final_levels = [ + Wargroove2Level( + name="Disastrous Crossing", + file_name="Disastrous_Crossing.json", + location_rules={"Disastrous Crossing: Victory": + lambda state, player: lambda state=state: state.has_any( + ["Merfolk", "River Boat"], player) and + state.has_any( + ["Knight", "Kraken"], + player)} + ), + Wargroove2Level( + name="Dark Mirror", + file_name="Dark_Mirror.json", + location_rules={ + "Dark Mirror: Victory": lambda state, player: lambda state=state: state.has( + "Archer", player) and state.has_any(["Mage", "Ballista"], player) and state.has_any( + ["Harpy", "Dragon"], player)}, + has_ocean=False + ), + Wargroove2Level( + name="Doomed Metropolis", + file_name="Doomed_Metropolis.json", + location_rules={ + "Doomed Metropolis: Victory": lambda state, player: lambda state=state: state.has_all( + ["Mage", "Knight"], player)}, + has_ocean=False + ), + Wargroove2Level( + name="Dementia Castle", + file_name="Dementia_Castle.json", + location_rules={"Dementia Castle: Victory": + lambda state, player: lambda state=state: state.has_all( + ["Merfolk", "Mage", "Golem", "Harpy"], player)} + ), +] + +first_level = Wargroove2Level( + name="Humble Beginnings Rebirth", + file_name="", + location_rules={ + "Humble Beginnings Rebirth: Victory": lambda state, player: lambda state=state: True, + "Humble Beginnings Rebirth: Talk to Nadia": lambda state, player: lambda state=state: True, + "Humble Beginnings Rebirth: Good Dog": lambda state, player: lambda state=state: True + } +) diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index 776384512de7..0a15e28a4f84 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -1,5 +1,6 @@ from BaseClasses import Region, Entrance -from worlds.wargroove2.Levels import region_names, FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4 +from worlds.wargroove2.Levels import first_level, region_names, \ + FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4 from typing import TYPE_CHECKING if TYPE_CHECKING: from . import Wargroove2World @@ -9,12 +10,11 @@ def create_regions(world: "Wargroove2World") -> None: multiworld = world.multiworld player = world.player level_list = world.level_list - first_level = world.first_level final_levels = world.final_levels menu_region = Region('Menu', player, multiworld) menu_region.exits.append(Entrance(player, 'Menu exits to Humble Beginnings Rebirth', menu_region)) - first_level_region = first_level.define_region("Humble Beginnings Rebirth", multiworld, + first_level_region = first_level.define_region("Humble Beginnings Rebirth", multiworld, player, exits=[region_names[0], region_names[1], region_names[2], region_names[3]]) multiworld.regions += [menu_region, first_level_region] @@ -22,7 +22,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 1s for level_num in range(0, 4): next_level = level_num * 3 + 4 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, exits=[ + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, player, exits=[ region_names[next_level], region_names[next_level + 1], region_names[next_level + 2] @@ -30,7 +30,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 2s for level_num in range(4, 16): next_level = level_num + 12 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, player, exits=[region_names[next_level]])] # Define Level 3s for level_num in range(16, 28): @@ -41,14 +41,14 @@ def create_regions(world: "Wargroove2World") -> None: final_level_name = FINAL_LEVEL_3 elif level_num >= 19: final_level_name = FINAL_LEVEL_2 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, player, exits=[final_level_name])] # Define Final Levels - multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, multiworld), - final_levels[1].define_region(FINAL_LEVEL_2, multiworld), - final_levels[2].define_region(FINAL_LEVEL_3, multiworld), - final_levels[3].define_region(FINAL_LEVEL_4, multiworld)] + multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, multiworld, player), + final_levels[1].define_region(FINAL_LEVEL_2, multiworld, player), + final_levels[2].define_region(FINAL_LEVEL_3, multiworld, player), + final_levels[3].define_region(FINAL_LEVEL_4, multiworld, player)] # # link up our regions with the entrances world.get_entrance("Menu exits to Humble Beginnings Rebirth").connect( diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index e28eeeef2415..c0b96a7ff7ba 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING from worlds.AutoWorld import LogicMixin +from worlds.wargroove2.Levels import first_level if TYPE_CHECKING: from . import Wargroove2World @@ -10,19 +11,22 @@ class Wargroove2Logic(LogicMixin): def set_rules(world: "Wargroove2World") -> None: level_list = world.level_list - first_level = world.first_level final_levels = world.final_levels player = world.player # Level 0 - first_level.define_access_rules(world) + first_level.define_access_rules(world, player) # Levels 1-28 (Top 28 of the list) for i in range(0, 28): - level_list[i].define_access_rules(world) + level_list[i].define_access_rules(world, player) # Final Levels (Top 4 of the list) - final_levels[0].define_access_rules(world, lambda state: state.has_all(["Final North", "Final Center"], player)) - final_levels[1].define_access_rules(world, lambda state: state.has_all(["Final East", "Final Center"], player)) - final_levels[2].define_access_rules(world, lambda state: state.has_all(["Final South", "Final Center"], player)) - final_levels[3].define_access_rules(world, lambda state: state.has_all(["Final West", "Final Center"], player)) + final_levels[0].define_access_rules(world, player, + lambda state: state.has_all(["Final North", "Final Center"], player)) + final_levels[1].define_access_rules(world, player, + lambda state: state.has_all(["Final East", "Final Center"], player)) + final_levels[2].define_access_rules(world, player, + lambda state: state.has_all(["Final South", "Final Center"], player)) + final_levels[3].define_access_rules(world, player, + lambda state: state.has_all(["Final West", "Final Center"], player)) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 65b010142ca3..d8ae7c556a45 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -7,7 +7,8 @@ from BaseClasses import Item, Tutorial, ItemClassification from Options import NumericOption from .Items import item_table, faction_table, Wargroove2Item -from .Levels import Wargroove2Level, get_level_table, get_first_level, get_final_levels, region_names, FINAL_LEVEL_1, \ +from .Levels import Wargroove2Level, low_victory_checks_levels, high_victory_checks_levels, first_level, \ + final_levels, region_names, FINAL_LEVEL_1, \ FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, LEVEL_COUNT, FINAL_LEVEL_COUNT from .Locations import location_table from .Presets import wargroove2_option_presets @@ -61,20 +62,12 @@ class Wargroove2World(World): topology_present = True web = Wargroove2Web() level_list: typing.List[Wargroove2Level] = [] - first_level: Wargroove2Level final_levels: typing.List[Wargroove2Level] = [] item_name_to_id = {name: data.code for name, data in item_table.items() if data.code is not None} location_name_to_id = {name: code for name, code in location_table.items() if code is not None} def generate_early(self) -> None: - # First level - self.first_level = get_first_level(self.player) - - # Standard levels - self.level_list = get_level_table(self.player) - low_victory_checks_levels = list(level for level in self.level_list if level.low_victory_checks) - high_victory_checks_levels = list(level for level in self.level_list if not level.low_victory_checks) if self.options.level_shuffle_seed == 0: random = self.random else: @@ -86,10 +79,8 @@ def generate_early(self) -> None: random.shuffle(non_starting_levels) self.level_list = low_victory_checks_levels[0:4] + non_starting_levels - # Final Levels - self.final_levels = get_final_levels(self.player) - final_levels_no_ocean = list(level for level in self.final_levels if not level.has_ocean) - final_levels_ocean = list(level for level in self.final_levels if level.has_ocean) + final_levels_no_ocean = list(level for level in final_levels if not level.has_ocean) + final_levels_ocean = list(level for level in final_levels if level.has_ocean) random.shuffle(final_levels_no_ocean) random.shuffle(final_levels_ocean) non_north_levels = final_levels_ocean + final_levels_no_ocean[1:] @@ -120,7 +111,7 @@ def create_items(self) -> None: pool.append(Wargroove2Item("Income Boost", self.player)) # Matching number of unfilled locations with filler items - total_locations = len(self.first_level.location_rules.keys()) + total_locations = len(first_level.location_rules.keys()) for level in self.level_list[0:LEVEL_COUNT]: total_locations += len(level.location_rules.keys()) locations_remaining = total_locations - len(pool) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 51ce83f4ac7d..41d3826aa2b0 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -17,8 +17,9 @@ from . import Wargroove2World from .Items import item_table, faction_table, CommanderData, ItemData, item_id_name -from .Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, get_level_table, FINAL_LEVEL_1, \ - FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, get_final_levels +from .Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, \ + low_victory_checks_levels, high_victory_checks_levels, \ + FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, final_levels from .Locations import location_table from .RegionFilter import Wargroove2LogicFilter from NetUtils import ClientStatus @@ -376,7 +377,7 @@ def build_levels(self) -> LevelsLayout: def update_levels(self): received_names = [item_id_name[item.item] for item in self.ctx.items_received] - levels = get_level_table(self.ctx.slot) + levels = low_victory_checks_levels + high_victory_checks_levels level_rules = {level.name: level.location_rules for level in levels} region_filter = Wargroove2LogicFilter(received_names) self.level_1_Layout.clear_widgets() @@ -395,7 +396,7 @@ def update_levels(self): level_name: str = self.ctx.slot_data[region_name] level_name_text = f"\n{level_name}" for location_name in level_rules[level_name].keys(): - is_beatable: bool = level_rules[level_name][location_name](region_filter) + is_beatable: bool = level_rules[level_name][location_name](region_filter, self.ctx.slot)() is_fully_beaten = is_fully_beaten and \ location_table[location_name] in self.ctx.checked_locations if location_name.endswith(": Victory"): @@ -448,7 +449,6 @@ def update_levels(self): self.level_4_Layout.add_widget(label) level_counter += 1 - final_levels = get_final_levels(self.ctx.slot) final_level_rules = {final_level.name: final_level.location_rules for final_level in final_levels} final_level_1_name = None final_level_2_name = None @@ -470,7 +470,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_1_name}" is_beatable: bool = final_level_rules[final_level_1_name] \ - [f"{final_level_1_name}: Victory"](region_filter) + [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -487,7 +487,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_2_name}" is_beatable: bool = final_level_rules[final_level_2_name] \ - [f"{final_level_2_name}: Victory"](region_filter) + [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -504,7 +504,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_3_name}" is_beatable: bool = final_level_rules[final_level_3_name] \ - [f"{final_level_3_name}: Victory"](region_filter) + [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -521,7 +521,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_4_name}" is_beatable: bool = final_level_rules[final_level_4_name] \ - [f"{final_level_4_name}: Victory"](region_filter) + [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: From 5fbcbb8eec306bdcbf05e082c8509c5f4582b279 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Tue, 3 Sep 2024 11:08:06 -0400 Subject: [PATCH 45/89] Update worlds/wargroove2/Regions.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/Regions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index 0a15e28a4f84..30ac4caac781 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -1,5 +1,5 @@ from BaseClasses import Region, Entrance -from worlds.wargroove2.Levels import first_level, region_names, \ +from .Levels import first_level, region_names, \ FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4 from typing import TYPE_CHECKING if TYPE_CHECKING: From 14eae241b9d7779ef9066219f4ecc4696a048d82 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Tue, 3 Sep 2024 11:08:18 -0400 Subject: [PATCH 46/89] Update worlds/wargroove2/Rules.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/wargroove2/Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index c0b96a7ff7ba..3c49cc75913b 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING from worlds.AutoWorld import LogicMixin -from worlds.wargroove2.Levels import first_level +from .Levels import first_level if TYPE_CHECKING: from . import Wargroove2World From 4146b741e85d3a894fdd969cd823a2a52cebebb0 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Tue, 3 Sep 2024 12:39:38 -0400 Subject: [PATCH 47/89] Wargroove 2: Fixed several warnings with the WG2 client. --- worlds/wargroove2/client.py | 63 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 41d3826aa2b0..fb90453772ad 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -26,7 +26,6 @@ from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \ CommonContext, server_loop - ModuleUpdate.update() if __name__ == "__main__": @@ -57,7 +56,7 @@ def _cmd_commander(self, *commander_name: Iterable[str]): class Wargroove2Context(CommonContext): - command_processor: int = Wargroove2ClientCommandProcessor + command_processor = Wargroove2ClientCommandProcessor game = "Wargroove 2" items_handling = 0b111 # full remote current_commander: CommanderData = faction_table["Starter"][0] @@ -68,9 +67,9 @@ class Wargroove2Context(CommonContext): has_death_link: bool = False final_levels: int = 1 level_shuffle_seed: int = 0 - slot_data: {} + slot_data: dict stored_finale_key: str = "" - completed_final_regions: [str] = [] + completed_final_regions: list = [] faction_item_ids = { 'Starter': 0, 'Cherrystone': 252034, @@ -89,7 +88,7 @@ class Wargroove2Context(CommonContext): def __init__(self, server_address, password): super(Wargroove2Context, self).__init__(server_address, password) - self.send_index: int = 0 + self.send_index = 0 self.syncing = False self.awaiting_bridge = False # self.game_communication_path: files go in this path to pass data between us and the actual game @@ -202,7 +201,7 @@ def on_package(self, cmd: str, args: dict): self.update_commander_data() self.ui.update_ui() - random.seed(self.seed_name + str(self.slot)) + random.seed(str(self.seed_name) + str(self.slot)) # Our indexes start at 0 and we have ?? levels for i in range(0, 100): filename = f"seed{i}" @@ -215,18 +214,20 @@ def on_package(self, cmd: str, args: dict): file_data = pkgutil.get_data("worlds.wargroove2", os.path.join(self.level_directory, level_file_name)) if file_data is None: print_error_and_close("Wargroove2Client couldn't find Wargoove 2 level files in install!") - with open(os.path.join(self.game_communication_path, filename), 'wb') as f: - f.write(file_data) - f.close() + else: + with open(os.path.join(self.game_communication_path, filename), 'wb') as f: + f.write(file_data) + f.close() for i in range(0, FINAL_LEVEL_COUNT): filename = f"AP_{i + LEVEL_COUNT + 1}.map" level_file_name = self.slot_data[f"Final Level File #{i}"] file_data = pkgutil.get_data("worlds.wargroove2", os.path.join(self.level_directory, level_file_name)) if file_data is None: print_error_and_close("Wargroove2Client couldn't find Wargoove 2 level files in install!") - with open(os.path.join(self.game_communication_path, filename), 'wb') as f: - f.write(file_data) - f.close() + else: + with open(os.path.join(self.game_communication_path, filename), 'wb') as f: + f.write(file_data) + f.close() if cmd in {"RoomInfo"}: self.seed_name = args["seed_name"] @@ -284,18 +285,13 @@ def on_package(self, cmd: str, args: dict): def run_gui(self): """Import kivy UI system and start running it as self.ui_task.""" - from kvui import GameManager, HoverBehavior, ServerToolTip + from kvui import GameManager from kivy.uix.tabbedpanel import TabbedPanelItem from kivy.lang import Builder - from kivy.uix.button import Button from kivy.uix.togglebutton import ToggleButton from kivy.uix.boxlayout import BoxLayout from kivy.uix.gridlayout import GridLayout - from kivy.uix.image import AsyncImage, Image - from kivy.uix.stacklayout import StackLayout from kivy.uix.label import Label - from kivy.properties import ColorProperty - from kivy.uix.image import Image import pkgutil class TrackerLayout(BoxLayout): @@ -342,8 +338,8 @@ class Wargroove2Manager(GameManager): boost_tracker: BoxLayout commander_buttons: Dict[str, List[CommanderButton]] tracker_items = { - "Swordsman": ItemData(None, "Unit", False), - "Dog": ItemData(None, "Unit", False), + "Swordsman": ItemData(None, "Unit"), + "Dog": ItemData(None, "Unit"), **item_table } @@ -358,8 +354,8 @@ def build(self): return container def build_levels(self) -> LevelsLayout: + levels_layout = LevelsLayout(orientation="horizontal") try: - levels_layout = LevelsLayout(orientation="horizontal") level_tracker = LevelTracker(padding=[0, 20]) self.level_1_Layout = GridLayout(cols=1) self.level_2_Layout = GridLayout(cols=1) @@ -371,9 +367,9 @@ def build_levels(self) -> LevelsLayout: level_tracker.add_widget(self.level_4_Layout) levels_layout.add_widget(level_tracker) self.update_levels() - return levels_layout except Exception as e: print(e) + return levels_layout def update_levels(self): received_names = [item_id_name[item.item] for item in self.ctx.items_received] @@ -393,10 +389,10 @@ def update_levels(self): is_fully_beaten = True is_victory_reached = False if level_counter <= LEVEL_COUNT and hasattr(self.ctx, 'slot_data'): - level_name: str = self.ctx.slot_data[region_name] + level_name = self.ctx.slot_data[region_name] level_name_text = f"\n{level_name}" for location_name in level_rules[level_name].keys(): - is_beatable: bool = level_rules[level_name][location_name](region_filter, self.ctx.slot)() + is_beatable = level_rules[level_name][location_name](region_filter, self.ctx.slot)() is_fully_beaten = is_fully_beaten and \ location_table[location_name] in self.ctx.checked_locations if location_name.endswith(": Victory"): @@ -469,7 +465,7 @@ def update_levels(self): elif final_level_1_name is not None and region_filter.has_all(["Final North", "Final Center"], self.ctx.slot): level_name_text = f"\n{final_level_1_name}" - is_beatable: bool = final_level_rules[final_level_1_name] \ + is_beatable = final_level_rules[final_level_1_name] \ [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) @@ -486,7 +482,7 @@ def update_levels(self): elif final_level_2_name is not None and region_filter.has_all(["Final East", "Final Center"], self.ctx.slot): level_name_text = f"\n{final_level_2_name}" - is_beatable: bool = final_level_rules[final_level_2_name] \ + is_beatable = final_level_rules[final_level_2_name] \ [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) @@ -503,7 +499,7 @@ def update_levels(self): elif final_level_3_name is not None and region_filter.has_all(["Final South", "Final Center"], self.ctx.slot): level_name_text = f"\n{final_level_3_name}" - is_beatable: bool = final_level_rules[final_level_3_name] \ + is_beatable = final_level_rules[final_level_3_name] \ [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) @@ -520,7 +516,7 @@ def update_levels(self): elif final_level_4_name is not None and region_filter.has_all(["Final West", "Final Center"], self.ctx.slot): level_name_text = f"\n{final_level_4_name}" - is_beatable: bool = final_level_rules[final_level_4_name] \ + is_beatable = final_level_rules[final_level_4_name] \ [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) @@ -532,8 +528,8 @@ def update_levels(self): self.level_4_Layout.add_widget(label) def build_tracker(self) -> TrackerLayout: + tracker = TrackerLayout(orientation="horizontal") try: - tracker = TrackerLayout(orientation="horizontal") commander_select = CommanderSelect(orientation="vertical") self.commander_buttons = {} @@ -568,6 +564,7 @@ def build_tracker(self) -> TrackerLayout: return tracker except Exception as e: print(e) + return tracker def update_tracker(self): received_ids = [item.item for item in self.ctx.items_received] @@ -639,6 +636,7 @@ def set_commander(self, commander_name: str) -> bool: return False else: wg2_logger.error(f"{commander_name} is not a recognized Wargroove 2 commander.") + return False def get_commanders(self) -> List[Tuple[CommanderData, bool]]: """Gets a list of commanders with their unlocked status""" @@ -658,7 +656,7 @@ async def game_watcher(ctx: Wargroove2Context): sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) await ctx.send_msgs(sync_msg) ctx.syncing = False - sending = [] + sending: list = [] victory = False await ctx.update_death_link(ctx.has_death_link) for root, dirs, files in os.walk(ctx.game_communication_path): @@ -670,8 +668,9 @@ async def game_watcher(ctx: Wargroove2Context): if file == "deathLinkSend" and ctx.has_death_link: with open(os.path.join(ctx.game_communication_path, file), 'r') as f: failed_mission = f.read() - await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") - f.close() + if ctx.slot is not None: + await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") + f.close() os.remove(os.path.join(ctx.game_communication_path, file)) if file == "victory": with open(os.path.join(ctx.game_communication_path, file), 'r') as f: From 7d211721256c2ecb309196fb33555815c0d87921 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Wed, 4 Sep 2024 11:42:54 -0400 Subject: [PATCH 48/89] Wargroove 2: Fixed an issue where the level seed option would produce different level configurations using the same seed for two WG2 worlds in the same multiworld. --- worlds/wargroove2/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index d8ae7c556a45..e15cf6dfa6f1 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -68,16 +68,18 @@ class Wargroove2World(World): location_name_to_id = {name: code for name, code in location_table.items() if code is not None} def generate_early(self) -> None: - if self.options.level_shuffle_seed == 0: + if self.options.level_shuffle_seed.value == 0: random = self.random else: random = Random(str(self.options.level_shuffle_seed)) - random.shuffle(low_victory_checks_levels) - random.shuffle(high_victory_checks_levels) - non_starting_levels = high_victory_checks_levels + low_victory_checks_levels[4:] + low_victory_checks_levels_copy = low_victory_checks_levels.copy() + high_victory_checks_levels_copy = high_victory_checks_levels.copy() + random.shuffle(low_victory_checks_levels_copy) + random.shuffle(high_victory_checks_levels_copy) + non_starting_levels = high_victory_checks_levels_copy + low_victory_checks_levels_copy[4:] random.shuffle(non_starting_levels) - self.level_list = low_victory_checks_levels[0:4] + non_starting_levels + self.level_list = low_victory_checks_levels_copy[0:4] + non_starting_levels final_levels_no_ocean = list(level for level in final_levels if not level.has_ocean) final_levels_ocean = list(level for level in final_levels if level.has_ocean) From 618e39e6245ddc5fe470a436ffe29a4df8278d29 Mon Sep 17 00:00:00 2001 From: Fly Sniper Date: Thu, 5 Sep 2024 11:52:29 -0400 Subject: [PATCH 49/89] Wargroove 2: 1.1 working with the new PR changes. --- worlds/wargroove2/Levels.py | 10 +++++----- worlds/wargroove2/__init__.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 596d5ac484b6..f0ad6acbb375 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -63,14 +63,14 @@ def define_access_rules(self, world: "Wargroove2World", player: int, additional_ loc_id = location_table.get(location_name, 0) extras = 1 if loc_id is not None and location_name.endswith("Victory"): - extras = world.worlds[player].options.victory_locations.value + extras = world.options.victory_locations.value elif loc_id is not None: - extras = world.worlds[player].options.objective_locations.value + extras = world.options.objective_locations.value for i in range(1, extras): - set_rule(world.get_location(location_name + f" Extra {i}", player), lambda state, rule=rule: - state.can_reach_region(self.region_name, player) and rule(state)() and additional_rule(state)) + set_rule(world.get_location(location_name + f" Extra {i}"), lambda state, rule=rule: + state.can_reach_region(self.region_name, player) and rule(state, player)() and additional_rule(state)) region = world.get_region(self.region_name) - set_region_exit_rules(region, world, player, self.victory_locations, operator='and') + set_region_exit_rules(region, world, self.victory_locations, operator='and') def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=None) -> Region: self.region_name = name diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 18d615885305..5bd8911713d6 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -114,7 +114,7 @@ def create_items(self) -> None: # Matching number of unfilled locations with filler items total_locations = 0 - total_locations += self.get_total_locations_in_level(self.first_level) + total_locations += self.get_total_locations_in_level(first_level) for level in self.level_list[0:LEVEL_COUNT]: total_locations += self.get_total_locations_in_level(level) From 861c3328a7ebac43a1567c8f200312586eb9963e Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 10 Oct 2024 21:32:07 -0400 Subject: [PATCH 50/89] Wargroove 2: 4 new maps. Random terrain generation in mod. --- worlds/wargroove2/Levels.py | 90 +++++++++++++----- worlds/wargroove2/Locations.py | 60 ++++++++++++ .../data/mods/ArchipelagoMod/maps.dat | Bin 350032 -> 350099 bytes .../data/mods/ArchipelagoMod/mod.dat | Bin 692 -> 692 bytes .../data/mods/ArchipelagoMod/modAssets.dat | Bin 305148 -> 367117 bytes ...paign-45747c660b6a2f09601327a18d662a7d.cmp | Bin 220976 -> 221008 bytes ...n-45747c660b6a2f09601327a18d662a7d.cmp.bak | Bin 221024 -> 220960 bytes .../levels/Ancient_Discoveries.json | 1 + .../wargroove2/levels/Majestic_Mountain.json | 1 + .../wargroove2/levels/Observation_Isle.json | 1 + .../levels/Swimming_at_the_Docks.json | 1 + 11 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 worlds/wargroove2/levels/Ancient_Discoveries.json create mode 100644 worlds/wargroove2/levels/Majestic_Mountain.json create mode 100644 worlds/wargroove2/levels/Observation_Isle.json create mode 100644 worlds/wargroove2/levels/Swimming_at_the_Docks.json diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index f0ad6acbb375..947de286890a 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -114,8 +114,6 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No ["Barge", "Landing Event", "Golem"], player) } ), -] -low_victory_checks_levels = [ Wargroove2Level( name="Spire Fire", file_name="Spire_Fire.json", @@ -269,18 +267,6 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No }, has_ocean=False ), - Wargroove2Level( - name="Wagon Freeway", - file_name="Wagon_Freeway.json", - location_rules={ - "Wagon Freeway: Victory": lambda state, player: lambda state=state: state.has_all( - ["Wagon", "Spearman"], player), - "Wagon Freeway: All Mine Now": lambda state, player: lambda state=state: True, - "Wagon Freeway: Pigeon Carrier": lambda state, player: lambda state=state: - state.has("Air Trooper", player), - }, - has_ocean=False - ), Wargroove2Level( name="Kraken Strait", file_name="Kraken_Strait.json", @@ -419,6 +405,19 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No player), } ), + Wargroove2Level( + name="Bridge Brigade", + file_name="Bridge_Brigade.json", + location_rules={ + "Bridge Brigade: Victory": lambda state, player: lambda state=state: state.has_all( + ["Warship", "Spearman"], player), + "Bridge Brigade: From the Depths": lambda state, player: lambda + state=state: state.has("Kraken", player), + "Bridge Brigade: Back to the Depths": lambda state, player: lambda state=state: + state.has_all(["Warship", "Spearman", "Kraken"], player), + }, + has_ocean=False + ), Wargroove2Level( name="Grand Theft Village", file_name="Grand_Theft_Village.json", @@ -432,15 +431,14 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No has_ocean=False ), Wargroove2Level( - name="Bridge Brigade", - file_name="Bridge_Brigade.json", + name="Wagon Freeway", + file_name="Wagon_Freeway.json", location_rules={ - "Bridge Brigade: Victory": lambda state, player: lambda state=state: state.has_all( - ["Warship", "Spearman"], player), - "Bridge Brigade: From the Depths": lambda state, player: lambda - state=state: state.has("Kraken", player), - "Bridge Brigade: Back to the Depths": lambda state, player: lambda state=state: - state.has_all(["Warship", "Spearman", "Kraken"], player), + "Wagon Freeway: Victory": lambda state, player: lambda state=state: state.has_all( + ["Wagon", "Spearman"], player), + "Wagon Freeway: All Mine Now": lambda state, player: lambda state=state: True, + "Wagon Freeway: Pigeon Carrier": lambda state, player: lambda state=state: + state.has("Air Trooper", player), }, has_ocean=False ), @@ -483,6 +481,54 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No ), ] +low_victory_checks_levels = [ + + Wargroove2Level( + name="Swimming at the Docks", + file_name="Swimming_at_the_Docks.json", + location_rules={ + "Swimming at the Docks: Victory": lambda state, player: lambda state=state: True, + "Swimming at the Docks: Dogs Counter Knights": lambda state, player: lambda + state=state: True, + "Swimming at the Docks: Kayaking": lambda state, player: lambda + state=state: state.has("River Boat", player), + } + ), + Wargroove2Level( + name="Ancient Discoveries", + file_name="Ancient_Discoveries.json", + location_rules={ + "Ancient Discoveries: Victory": lambda state, player: lambda state=state: True, + "Ancient Discoveries: So many Choices": lambda state, player: lambda + state=state: True, + "Ancient Discoveries: Height Advantage": lambda state, player: lambda + state=state: state.has("Golem", player), + } + ), + Wargroove2Level( + name="Observation Isle", + file_name="Observation_Isle.json", + location_rules={ + "Observation Isle: Victory": lambda state, player: lambda state=state: True, + "Observation Isle: Become the Watcher": lambda state, player: lambda + state=state: True, + "Observation Isle: Execute the Watcher": lambda state, player: lambda + state=state: state.has("Walls Event", player), + } + ), + Wargroove2Level( + name="Majestic Mountain", + file_name="Majestic_Mountain.json", + location_rules={ + "Majestic Mountain: Victory": lambda state, player: lambda state=state: True, + "Majestic Mountain: Mountain Climbing": lambda state, player: lambda + state=state: True, + "Majestic Mountain: Legend of the Mountains": lambda state, player: lambda + state=state: state.has("Air Trooper", player), + } + ), +] + first_level = Wargroove2Level( name="Humble Beginnings Rebirth", file_name="", diff --git a/worlds/wargroove2/Locations.py b/worlds/wargroove2/Locations.py index 63120258bc05..83ec9d73196a 100644 --- a/worlds/wargroove2/Locations.py +++ b/worlds/wargroove2/Locations.py @@ -43,6 +43,18 @@ "Bridge Brigade: Victory": 253039, "Bridge Brigade: From the Depths": 253040, "Bridge Brigade: Back to the Depths": 253041, + "Swimming at the Docks: Victory": 253042, + "Swimming at the Docks: Dogs Counter Knights": 253043, + "Swimming at the Docks: Kayaking": 253044, + "Ancient Discoveries: Victory": 253045, + "Ancient Discoveries: So many Choices": 253046, + "Ancient Discoveries: Height Advantage": 253047, + "Observation Isle: Victory": 253048, + "Observation Isle: Become the Watcher": 253049, + "Observation Isle: Execute the Watcher": 253050, + "Majestic Mountain: Victory": 253051, + "Majestic Mountain: Mountain Climbing": 253052, + "Majestic Mountain: Legend of the Mountains": 253053, ######################################################### "Slippery Bridge: Victory": 253300, "Slippery Bridge: Control all Sea Villages": 253301, @@ -439,6 +451,54 @@ "Split Valley: Ranged Trinity Extra 2": 260333, "Split Valley: Ranged Trinity Extra 3": 260334, "Split Valley: Ranged Trinity Extra 4": 260335, + "Swimming at the Docks: Victory Extra 1": 260336, + "Swimming at the Docks: Victory Extra 2": 260337, + "Swimming at the Docks: Victory Extra 3": 260338, + "Swimming at the Docks: Victory Extra 4": 260339, + "Swimming at the Docks: Dogs Counter Knights Extra 1": 260340, + "Swimming at the Docks: Dogs Counter Knights Extra 2": 260341, + "Swimming at the Docks: Dogs Counter Knights Extra 3": 260342, + "Swimming at the Docks: Dogs Counter Knights Extra 4": 260343, + "Swimming at the Docks: Kayaking Extra 1": 260344, + "Swimming at the Docks: Kayaking Extra 2": 260345, + "Swimming at the Docks: Kayaking Extra 3": 260346, + "Swimming at the Docks: Kayaking Extra 4": 260347, + "Ancient Discoveries: Victory Extra 1": 260348, + "Ancient Discoveries: Victory Extra 2": 260349, + "Ancient Discoveries: Victory Extra 3": 260350, + "Ancient Discoveries: Victory Extra 4": 260351, + "Ancient Discoveries: So many Choices Extra 1": 260352, + "Ancient Discoveries: So many Choices Extra 2": 260353, + "Ancient Discoveries: So many Choices Extra 3": 260354, + "Ancient Discoveries: So many Choices Extra 4": 260355, + "Ancient Discoveries: Height Advantage Extra 1": 260356, + "Ancient Discoveries: Height Advantage Extra 2": 260357, + "Ancient Discoveries: Height Advantage Extra 3": 260358, + "Ancient Discoveries: Height Advantage Extra 4": 260359, + "Observation Isle: Victory Extra 1": 260360, + "Observation Isle: Victory Extra 2": 260361, + "Observation Isle: Victory Extra 3": 260362, + "Observation Isle: Victory Extra 4": 260363, + "Observation Isle: Become the Watcher Extra 1": 260364, + "Observation Isle: Become the Watcher Extra 2": 260365, + "Observation Isle: Become the Watcher Extra 3": 260366, + "Observation Isle: Become the Watcher Extra 4": 260367, + "Observation Isle: Execute the Watcher Extra 1": 260368, + "Observation Isle: Execute the Watcher Extra 2": 260369, + "Observation Isle: Execute the Watcher Extra 3": 260370, + "Observation Isle: Execute the Watcher Extra 4": 260371, + "Majestic Mountain: Victory Extra 1": 260372, + "Majestic Mountain: Victory Extra 2": 260373, + "Majestic Mountain: Victory Extra 3": 260374, + "Majestic Mountain: Victory Extra 4": 260375, + "Majestic Mountain: Mountain Climbing Extra 1": 260376, + "Majestic Mountain: Mountain Climbing Extra 2": 260377, + "Majestic Mountain: Mountain Climbing Extra 3": 260378, + "Majestic Mountain: Mountain Climbing Extra 4": 260379, + "Majestic Mountain: Legend of the Mountains Extra 1": 260380, + "Majestic Mountain: Legend of the Mountains Extra 2": 260381, + "Majestic Mountain: Legend of the Mountains Extra 3": 260382, + "Majestic Mountain: Legend of the Mountains Extra 4": 260383, ######################################################### "Disastrous Crossing: Victory": None, "Dark Mirror: Victory": None, diff --git a/worlds/wargroove2/data/mods/ArchipelagoMod/maps.dat b/worlds/wargroove2/data/mods/ArchipelagoMod/maps.dat index 77045915f58d6aec809ed61c000d337cbcd43526..41f791718eb278421b36ad63030edacdb2d67b50 100644 GIT binary patch delta 259502 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

    1H>L7l@TaV7Br*UQb5GM-^j5wtAMor=j5JTj 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

    BgFJ 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

    qOIjPK5 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

    tC06!)~@L{j%Rupq_w9SR`TJ)n-`aGM>&5E z3HZ(#Qnd3NU+FRE?R1bg0tz#m0i7vrTkxXp)_Sj{SlQ*3m@XCv> zK|ak{xAiOBecnh^Wv@&(a`@UfJNvw6_<&r%WS12GVq34eI&N&@td{*viu0q^OM!gs zWLGZL+i3+-Kz(q=!Rq|C^Al%u_a@@}Q9I72b4*9c%DR}J-P3qd;KaX=HYNe^k;KYHunfLt`&2Y>7(^2jKW3#4Tjq@R^ z2VZw*@6YCc7Kd&ce#&YInI{33QeXhk_s6(O+409zIVwEizm>TGA7xA0L|Ojx-aek1 zOc-xD$zXYyS%QA$-)}W1#mZ3qpre*`ezO$qJ^6G3afHg8=B~3*ys`Z?euetTZg=JA zuSfQ^6HEN&j`wqy?Dm%1?FJ9^5~_mS560Fvi)S;ZWHv`zzuv41;xvp}opG?Z9I%V| zC7b?fJ1{wst!Db~+sBe-a30B#iEG=+I0m~>XA2hGYDR+m)hpp0dX*vLvlEBcU2`nt z9FL#g2=u%jDufwp(XVX$xE35Mq8IF557U}EYj20;` zWA^hI@Y6w)RrX79Gf$j>cggZ*rzOuLRIN&?^Dx7y*$Eku{x>%%9(UE;KEMGW(zSsf zwg$?peoPD0^9VX~lpj$Mxvf|0YI(Ml2dwAHKPwNM9Qdf;8bLog{`e1dFKS7b&P7M2 z@!x5&MzuTe@Y?T&hS}cRfC+Ei43Weva5oMEy7K8H|CC&4oLSTs<7c^?uH0>9V9fO7 zffGfumb-HnzBXyDSW-2_rdFf+4yS8Mz>`Dy8k(Gw{JO4_Ch$+)-JRcNLZF4pBNtmH zCV6gAKTZ(1h6?SM3;6ypyr<{uD%>t%_X3oTM~fanj9b?l+$U04Ut;NVs4+avQRfI$ z&~74Q>jQtt`HygW?&hd)uJTI$A~6V@u<#KO0$Nr4N_NrNcWD;UIs5R?{-YJ3CnB;= z2Z5_AC5qvTWJhkojUL$e_$qFn>HhB2+*N~4wm~`bT{Pw@tQz1;4Zf6AECcMOeOuT3 zVMW}o_N23!(doJmS9rTINRD7K29FlhNS<$uo)n_HTuh(i+M_HZW1;2Wmhn2;Pk$l! zBDF!@Il!ap7=Kcx=D%?Im7kS~oHmWARqCM6ZBC^uX`TJ_-^r@9DK0eHI!v!IS16qM zUACD+uWDglR^t&@r;W8;it2w`5_Z>lT$j@tm_{W9XRf9+V#+{6jvyGg;`;|JCRKnj z^6ZcEz7NmA0WMLpNhYHSnzX`IOlZTz;01X1&MqJ%+&>7o&Ru*i1i)idUkNP$6t=^U zzuj|YD@fT(#WJ+`Dac()R#zPI&Ppf+a8wStPOEMy3TogHB~=!gqi5l(mpJeJ4T0g4 zIWjN$K06I(R^Yz_ zOiKwyW;^S*s(hSH);OK_E94c-S(E)LY(io!G9y3231^RnJi}~>Wv4nJKap-JCZyK% z?g^Y9?*lI1Zc{?U8HELY;=G+1J*YA{qX7M&tMJOh%Iu&B;G}z9{GJnqmW~!+H38P> zuR#iIx1(v%oC_4wxe-mKickYK;Q7|StX?0`I1R?O3n_vnHs1sO+XBi^Ce->fs-o(~ zCA9@F+j)xzb$%cknHl5=DGGv&FOCd=ts^~ zBuGwE;=&%wgTnpW?|N zEd;1`6I;19ykc0QSMi_Vy%S2v@svRaGZ*lJ1=q(69p#|Xhjhk|iB;wYR=sD_xVwsGW88

    5jIr7piv0Y5u_z&D37*E00 z$Xp!I5R4BTF)t`p|AU9cop<51G3UB=lXj$&ariM{TAw9@%G6$Bhc8u-T8ui%rTt{c z!B(m_9Ek+%;?4(Jq0OaU3E^ZCnueyPT2_s3VnrfvYA_%S>PeMvc9Msj=wJhSCZP+u!Kt}sh@X?L z4{5zmB7Vs+3$4U&dh5(K#!11oF)tu0mFP}IgJmnJ-c&(gj6e%}mIxvQ%?mN|!qguE z6l4W5aINR8F~%fDK70aaf7d4m!GR&3TdNdZQCUrTMulJfsP8OylNOj{-Ab4P`Q#5E zqEe5Um;UdQ&k{dU3RiM#H8nGf4XvJocCfwz^N>Ei%9!mlT}Ni2O(iyPhf2MZGcm`(?=0YfVz%_nR1Wu`WL}o!oda17z)VDqV2v-10TF+1a zYp*S^Ij5loKNDpm4ES|%oLTTx?i+Od2AS_&v5T_^;wqvypO$SjGRXSIoo6= zIb&yY0s!Ye^8%urY}_s6I|+n}nSxdEQt5OZ9!?cYv73oc7G)$AJvemmz*$UFk4z5~ zX2~JDH2U8vJOlZlES3)XAZ!<$K~Rru1#pfxq#(HG1hON6_>UZpYmxLY{EzCIbI}OK-&vNbp{C7RSMf~+ zj#P(pE@omEsYOSN;4Xct$>$XMRQ0c!(A!Kq&Rh~V>bQ)uy)_k*#JD7`s0D36|t6#UiTB|nebX8-~yh12S#ko;f-w^h5OvSu39--VBEB{0U_WE3_3z3|bUoVm$V)TR

    (Q8oww>teywML0-d8|WNUUcj3}3g-h} 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

    5}QxaioP6Sr3u`%LUy{G+(t-s!K{ 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 pwx+?05q(45>%?OF+O4pJ4i5Sg4 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 j}PnDNwEIQ0EvKuLAX8Ni*ZV7}=*`QRe zD$%IgGf`ouTkT;4h}jY(>PRtg?(w+EMgcycIeJnL4A9EG5ct#JGhB%NnjB|&MX@m+ z;z~8dmQYw+r8jinV{!Cot7W2bWzI4uU#s^TE%bVh)V+>;m%m+^rCL{Xn=`4g>~Xun zI}ri$fiHct#{J}p#o<~{Xl)Z2o?HB|wOFDg6~~zRk!}s3>;sa7G5a>rB;(n{p^tRRk0^2c|Go24zTUTV3YG9_vM2SU3u*JXp^;rq3FUtV!Of ziZ=#+R+8{UT{ke8ZZ*7@9i#`Si34{i=!7aQeHQ_aoJttiLin2@n-h-BEJ0-fZ9k5y zYK|K9muxD$`7n(-KU6t-Fwr1K>T@#J8NAs;bP%9TD?sY{WXDz3jKTMWwV#lqc*@F^ zw3R29R)zl)YSIR0ZdPMvSEd65-?3Kizm$xfA6lD>&Q-7;RqH)h4{ex1sE&>OF1#84 zN&){|lb>P$6>opbs`kx7QkvABt0jRE+?J9y6}XA$nhoR_MwOOC7mz@xn8Z#E7|UBK zYX7eG>Y$a1UC_}Unxd@aOvzdOZGL3NLTyPw(M;FI__~ZBr7ynFa43r&D_hjKLBXTU zIUXFgD!`{MRLIb&BDw2A#Be5(sU|HO0(hgu57=ry4khGl5*f(hK^x#`JS(f+Er#n0 zQ_e=h_#Fj$6H04APzHll)gJy{gZZb%S0AWsrHBI?G4QKWWJV%Fu5dk}^Dl|K zXWUaLO~$-$|8C0O{$gKe)LoT~D?amIWM~LUYm z^C=pB^;T`A)KFsUgV^;>dUETJ$dg&YkGm5YvX znj1mX(E%u($&$itX@&s^ez;{L4JV^kGobyeF`JW8*JIt(u=Mht>yCE*UW`>`XXuzk z*e$Mhn09j0`cDon#39Uz0G1XFX{)XxGkh(|;EAMislv zM4d3F@m~vfcMK)NBE7l%1f=Y zr}>h`C_=CxY3^@}{@w>vaEITP9xTPF=Dg7g!MQUJm{TRN_G185zG1`Ue?^h)c^Wyq zqfIlXu!vsfqc1X(Bpz1IvEXpd_*&^?acxh|oypmS?va{e{3>P*QEepEa&CR~WL?cC zttN)Co!c!eBN%Yb0G(#$5mh*6oknKp+2m~T10G%ZMt%&n50ZIh!rXKP4+>$dKkb(7R>jp)kK1KiUV+ijlzvbm6PH-|q-{4TCG+RKHdAL)tv!WWvDnJ%|o{!By_tSlw}FM1{SBNbX* z61Z2~W#~F>9P1+P4H{WBW{JQ=)D{CX9xO@bbDC~^jaO)ZHA6!mbvN7&&sV;Dp@}@q zp_4+o9@(T2^Au5>EGTY{tonjy@l^91(*Xp2?d~z>-eN()OZ}3k$~pkmEuUbk32UfjOVB5gCa!e}V0T6f^?nUN8?d2Xrl95lIBT4gGE_xN)ftu24$x_fe#> z6O#-a<;|2jIrxCQBeyQ^NR|yEsvkXUN!cQ(eLZx@5Au*aTBWR0_^?;%s_T$=8)zvmh-k(Rb%B=n}TOd_1`hLa^WbasJq7?JbO zy@bXoIym7s5(jTzC^5|*<3&0;h<#Yt385-TWRVCIb~5a76C(Qh#05)jaZHYW79aU9 z;`}5=OT(%N2uzB%Gfc*pr|(W}M-!8TLTA z3U)-EG|?>jl!&$`L+lL3tb%znx4?X!e*K!s(g|vtH*zmHWa5^g8Xo|FGz{h_2>hB8 z>UL+@OO=WUz1?`|_*t{&gfz(=(6v8!^e<0{@alypmCSDQLD_@fYV_UH;1?rRc!KXQ zsKUvUKKd~1u@)i!#ep>gGEKtafe)eKzu`yr&n)-D_fvykin-d>8AMUt4dma`g5pPoM`NGtxO=jP+Gxq2KWDJ*Od|5vZ-HJwZ z(QgUHAB#Fc{L}x`4hf2ZU$Vk$k2R76AdHjT!8A(1THlkB`F4`5FO)0xI)F*#GTJ`V z4g?y+8e)4Gqij@?uS}fTlS*> zcjogxnmWR;B<#P0Z)b875ufeNKRnww`anOvAk679FP=9oPmt!+p7Ci<41+Zw-g$-R zSUZxreD%J6BwLCd5I|?L5RaA?V!ymUa&^Va7M1FyDAj-fMo^TATE}8SL*g9#aqhfC ziE?^UUuWIlTt{AD30tY8{?fXj4A=?H45xobmu-wNO4!~8ks2*Ew9!_VzlA!%sCTpc z@OGdSq13i%UZCiDj@zejUC;|s`UpIZr4is5rzL9Ej6*Iv3&R#!dmBsNPKTRH1Cj*L z(>l=IA`SHcPzoKHPk$Q=4*tY_rNa;(I_Z_pijis}gbK=M08`i({cV8#TZdN%`wZpYmlvT=XvShvM-0jvPPggawcT<}L^R}+ zhGS-FH-Y~k5Zq9TK=rY<;2^S4jCJ&loXq@BhZkiPK#O(-#N$d)MxC|x;Px;KqS8#w zxbu&6dV8}K{-9>Z{jgyTdKa}>fb6nMUmm}59>zAdHFPh2YT;l?Yv&RA1PVU-j6W8; za!{MN2lW;VTBvjFCj6IJd6B8g-IImZe1C2=7973*sQzh=ON{)z*^>34{=6CT+%7SS z#Ag2Q1tqCbS=If|QrA!=-(2%@VvbxC^p4@Y7NFA@^9CWOP_-16AbzikYj`vcGY&;s z&`}#;R=108Z~(8kgg)T1BclG-ce~jBw+g#&+u%m+1kt*sYE>v6b&tqpLT6Y-TBm~; zJnfYuB8Iii)9ph4W6)D~&CaP_yQwQb{gu@`gQ`csCO{87JD?7BDNZZWF_LTxmm=JnKC`N`d)XV(y$@?|_QgZgH2_aihk1N& zy6uqQ%s!`c9>&!7km*K154QHCx8eg<9%Gl(60`QYz5cnHS}HM7kvT>#5?vQpnvAil zjz*FiId3JNRH0D+Rmb`k(fR6DB`~Yiy;l_#2d&KmsWp{h`?bp%Lq?QBlRLm=6+6QH zGK{rid>70#D?Zi-;BK;g>ulaa4X8jj(}AqSX)d9-$kf(sSnkQLjqnS;b|Z#zFXv?B$Ft)-czQ^f_LmC68k9SYgc^bzV(1ep z|Nn|d#8N|~gV8ArwoXxwQEUMM4A`Bx9}eQ#M3ubF716V^A^_VMx$c9dGYy(wp$l{2w;${k^KGx> z55oRLO5>9r&5g zwuy@AUcsH}{fukL9G`On6;DL@oSjt<3&)+c=+wcz>_jQR?8c`N64SL^gf5tsJ?_lL zmjorXy0wcz4vDM@*9!hE30hNFlS31^`7;R;i5zE6AIHTmy%`&MDPft@SGLi{JFS}@g7W9jvXi+{=gPCEuVE4MgnztVW~qfJ1t(^N z-k5N0>t(ebpEW9m09*8pzq4Z2g`w#^j-A(iE71OBahg3OX(S8~xBz%x4k4A0NlL;P zQo*;pI5YyJIpu$g77V5Am))nMVYb0OzmBo@M4jQpY}st+ZSr06#*U$W^UA~&jBcSb zco0LT;sOcO_}0OIvbw@?*(OVi4dLK#=f~%bUxfMLW7A0l?)fL|0`z!~IPyx%q5(tf zZb1aGs2BOkun_O?G_9Qhf>wHBAB_9_?(Z-`oL9kUF41#HS=dyh;D4>wM%|& zq(2clQHmbdk}=@2x_WptcbK422WvYhy2!@h7CH)QQB{LGi7y61&x4lyqP9bb8!k@> zGg&YJ^$w;+@6??sbc}bw4maw+%)m$CunGkBzCipS+~Ux2IXC_26jf0|-`;cafb6pA zCu035UcAXJ{)|6ka!E4|AU=eq9i-)-Sn0t48&GCV%f0SM__KHL)J~bzT z?kw~k#AD&l@b}jVLEVjbgp$zdyzi^p_lN#p2D6@V{Z8~Qdxk8uk^v-M>g5KFv39$2 zFp6-5lB@->RG9gb?*!FDz|S{8#9-I{YfU0gx~g)2ddiVoItQ>48*JSU_LI6ULfn@y zPhWg_p=&}*(awbak%L*;t(y%Aqmn3WNcgv2h)x~(vASBn-+|Kn~H@*%>tN1E*oe8**c`&^C{d{sWA|rje_g+J@x95h(&@R(WW{4$8@|~} zvILlSA&D(X){y-tGq!v!Mt-@C2O$1@^E|u5!l9A@E0t1lo9Y7|DwLTX=y2K=1-jq} zj`f!MU8qbNIew$z74f6Kt}e9uWc@q47kDJ+mLqG2ka%YKH9X4bZRYuSA1-8KKaZYz zI3Aan%~_)_?kaTu*8aSgwVGZ)`Oi#2rvLb?o`$A%*+fAnMq7Zaq}OG`;dZlXB5`^V zpU4967|-mYfUr82wX(5ETG>&hy{t9A=P=eKW0vpmBn19DEAn;Kv03P`NK=&@hZ|`` zN&r#N&B0+$6prPLI9v6>?O7j z<2-*CXLhYvn|@E_j|YpA$lnL`{d+C6Jm+6f3ew%|Vh~<&;^U-`;9P<%C+>5otLO~` zmop_1;C)%*FerqPpI=-)ptEaZ=@*xd(kZLC*UGH=K-O5El=+D)H!Hz^Dp%#QJ?!S) zByx?(i7h2fFv1H>DZB%C*ZN#GduT`y)NcH1P+ZXj0R5ul^kO1VS*HOX95}BO@wLI7 z_CPB`MxgE&JeCbOlEso<&>bqotS@q_`H$py+T`wUF`7)VFNiy?rxfSU&YO)9?OPox zccJn*H+_h0%XsnyMDRC|ZZ2;FL~8$PFQV`YPauaki;{sb0JYg!_+WFg@J?HTpSa?I z$UB@G(`6Ezrta~xcXj|p8cC=Y<9>V2r52A;>M0)>B$Z7(nF9=|&nbv>Z;a1a5=%BJ39|PX zR0HCQh-2Ri;DVb-)bw^gPSE%M)es*kC;ltm=l)bmMU)t;W6XjRm7nhvD-_Vq3W&bD zVG@ftbI(Gsl=dfzwic8-peLEI1E{8&;^1_W!sgBy!LE-5iuXwYQlq8vFSINY5xWU0 zQlY^!>XN6p`)Aa3Qz-{2leqEX?|GWKjRa?Z7^h+YvWjo9Z4CI)mgqLk`Kf}WeIWPf zrYwXH9w{b98R`fmXF&IS@SX3QlB?!1&oRk!(i3SUWOuX;e$0>bL%@u@qvj?bi_@p+ z(~sj!92yz0?)C@L7W*%T4{MTO1+I%+l<&sFbtt##n7Xjnuv44M^OIUl!S2ly=gnpz zDi_cIW=hG`E`of9(i8J_VCpdWyD2SmhcJYjmc!=J+4PFUj{FKmoD?ygR391`84?|f z6pRZ)cS_gtY=opj{*5>E(Hm*)_55sHK020UhEkp`3C2o3G`J*U6gpSt zB;k&*RGKTAX`N*sQN6xuD5G9I#7IxJd#H8+sM)+D;8+on^{GhhU!JAcXYpgS4@KMp zx1*(rFtAJpU$5p5gOwwBOf{glOQ~kR9Dm*K z;Gyf4Kc;b;A@hmSk@5B%!+2h1m<@5eEG_9ZYFl4Ffyg{Lsq^K@NBp!46Yb!pi~D9P zo;~DSA63BQOf~LqJJTuh6M+vB)RK}us}tR7)>0Za_eSdT0#37z2zv@*TND)wBRlWf zBy|De#BG={$uX-so^C1ngjRHcOC+EaP}XbRpo1+#wecN54%tPyMU7NA6rmvnH50g#J6c;uyi7L)NY^wgt?Y|9PR--siDEAQ*X7+VAPKNmYv_@}0!yrYpCNQvQhKtGAH#Y$+Y3 ztrMN3(h66l#JJw?Gwe*i>nSkNpp6qg^l+jBPdk>FhysFB%5`V!r&-Qa*%F>IF^14W zmul_#qgVrtiopC%h%qQbl+vaQupf8LB*Qcmoti5YhlWkvr^6WLnONs1h+#WeP~(;W z15RNr_x5WL)M`qgG^nC7^oU!33IigZqUbzY=1Fo1OH`ck7+oFiAGzM!_)+_aQoS_W zU%SnoDLNs6-u{V-Cds2Z2VPu;K#h+~?ib9@aGH<_7Fv&H4{}VO0K+q8K$`VC?Ruzd zV3E-elYZshgZUNMG**)I9pVKFFGvZD5W!<|Z8+Zx7>}r8fDi*@XdCshz9SFDFnQHF zF?%;hb=$0yZM}#+uY;wc?HpH?B~i+LRG=m)Y17~QWo9nZD%vFym?L%oXu#6CRv2P2 zl>k69eeI|pYv9|6pbb3-0CPBP>Zhaz8LDw+AJsz~aODIhN`sHR4uwW}N3ge(QZOcs z`Q!*S&*c0opto46#OAMAEnGA_MnL|)#6-GYSlPnk#rWel{!->whMU9&@En!0^nl1! z@;YQ{;3f9l#;IuOu|~*Hh&CBUQF9wSZp1d{zvWk#-Vv7 z!G-f+!Pi4v9m8{(a3VelmtLEsu^y2E?c05mVz~fwf&$%$Mus>dlQx!4uC8I~+xoky z%P|=J1`^+IEJnl}Z?wa-FS)AungB{c?++Ie@nI&j|547NX)aD7zPtXCP94&5AcN=y z>-Fe;3~PqP*5rD?lN|YP?t~0;)`KY{_8XYx-nIn0(_#I`UDmN)zn(zDx1&i`x{>g( zt9~9cnVI=j7Qfdt0v-tLA1_y;fB)CnwKPHCGfriWQh#E~;F|j3L_v5G9psW=g?z3v zrzcdJ0}lkAbc`SLsZlAe5K`2_UaeFXrg0hZj&-Vv3X}Z=tw>U)#$?1qFs3y$RcriZ zSk8IWE!PF?X04?_)WVH1A{O;|jpWsef2CFv-tA}F5)XvBf^P~@nO(!c@gl8fT5Snb z`FOc-O6r2)zwR5^+MxGrSXJmF#1lJL1&a9Le7UgWYx~kYiR{;hi?OUIyZf@xak9cU z1UHNKgp%=ZP+9J8o&SZHX*-D!+%0ds5SCy7Bw%^w9|HfcNa0l6;V+EZ>C0=A#jvXU#!<4veh+u_=N?1C#l%;Fx-jLy|nrpXZTX&n6Xi6#ta&BU$h*4O?cyO*k zNXJmPkxe0yegDB+5Ru4uk)JJ<9X9H1I&ObA-2O>Tn~g8kZhJpfzNr4}@CLx-Zxg=F z92|NE^NX%%zDne9{4tmTfDE0{d&=5u>_s7sf#(K)3rY}faK{KZQF<+q%?NmcU3!Kd zWeNKf$QpQoWC_HE%Sql!N~;D#|C6RdlD^#@@qhq)9bxM0X@uFgrlTW?1%c6cB<}|( zY}$xd)u%B?Fva1c5^7@pz6VSiw&^`vX>M*r4(WGKq^W~OUH&?0q8>qMLU)3}U{%N@ zX#otTaZw$fx&)x}D{ZBu8r3@kNa{5{hIP9;e_S(>Mc+#QwD5m#x`bzdq+#m zvy0PH?Ilb&p%P{=tcX5^w{_VNwi(p~hnY=~sBAlVN5G0R1AhSLj7oUKpx&geT8gZd z%_mdfo0&QxiEm}q#v81lo4OF+Op}a^qh;=BZw-6*(DQIq%`7B&yV*3mp3*2@@8OWi zWFG?CP*i7)i%Azv4Up8`kJ0Gy$LG(%Fk|UB`*z7%PJQcn@f%HRi5k+?)8GISu#0zh zV!Vq-gpI}wHLT!6PD8|*hiFbtH|ePOgobHl4ckS&?36 zA6YFOTdP_Bk;AH@+y`Eswq49FN1q|RwjI_{7eQwr`2r?60JII23JQbsz`+RHq+wu@ zrZmLXDoq{j=`eRb>Gbs==^L7=mz^H!$_AVm2KHwV-|p2`wS=h`TAHz=bSHR1hrYi8 zne4F&=|-_Sun7kC-MPWkb)KXORPE@^ZbxIp#d$pV>8jqzbBNQ967v|0cwplYG-@ts zmIHXCFe%fI~O!j2R;<1me5iwjaWU?(xUh(%x# z+`zR^tHn3n(X`g1n0U*G9pRX%EQd@TnXK}nW5#WX#ZQQp@(V=QM33@|3(eOjmWv5z z*CmM!%Bhc8J#2_asI_|*gdroecc;&lP}uvO#2eWN0M}J0Q0p*FZnY-!3E5YS$1dzn z?lVsRy{DrjL)IGQr6Iyhrb2-reWPnS3~_P}*g%^HjdqSSutkyNm}=gJL^G`#8v&}l zx%q_(GP*ewk_>YmfdC!*{G$Vo~^d>-O zA_4R-j$&YhVASm8(2)W^+}1v5UeOtpdfi*qvzC{dbL!$Wez zK-KCB%BMa>w%U^yuunL-mM>?lkjw_~6fru=FpnsmoK!?tCq|bRr!J_Tr2gGpv*B^l zWb|}b{Dw)84RAjgmQYu>;`-ECTW!zG1}O0A)@6iuxCyEk`qW%hqhCSzJjFBBicD6^ zH1{ig@`1Xv8*rewU$L}!e$_I1>OFW(@Sd;z526EU>d`}bUMz&Rmvv^hWnr1h%*f_e zGTE3Sf1q^-b-hK!_Q9MqYlPMr_SbZG&>gkBd-f-^4MCO^y*L^-XEc5f>J|e`XN|tq zIiK=^exR5(OBDSfvpf4jziJZ?t8OWzVV{=LA6%ELLUbis^uP;Sr$A0fVJ%%507^B>H0 zQj>1WEY%9g(57d3-GfJW>p=z8pPMktdbN?YiG-edb=W%Oc>%l!|3RcH5mQzFhi^wi zs_?4ePsQr4AU@~Zu|i~l?bM})zA6$tiZo{(klrsWXYb=+EzRSJlG;YM?_Yu zhewpGRAW;?u{Woyes(Avf#41JAkg3b5#=JqIzO+3M)I@xSgy4VT~A|O4P1v*f^i~S zT(x--nKq6G@Sh#YFqa<8o4%~$@sdJ_jC*f^O`ciUpWZr84P@1%uG4K=_2YbC z1ohvr22XHPs}oCvWmwc_XHw26mIkwr=(SxT0#j3}Z@x2t{>@Hq#4zR3=6ayw)J~kA zuKmOs2ebAg2nZ`hwKS7ro6%~_C|y1#a?B{e_di_y?a0ppM0&F(aVNuie|*v$431*0 z&>{2Oy@|?r%+#kSm%3;UWLly^3Vz<6vLt8<%qXHRAoxI|cyUWjAH*Zl$_+K?=d=cO zA!pjiq#MVLlDmMAmL{VI`kWmQYOkq$5+)miUt9c>XQSU5>zKzCPtLrggV#oKBLGX& zatj*u??d-Kt|lx&1=1&A%gR1nCQhC+P10Q!=p$tEijLKfvzK6eIo}BBUj9 zrr_sPIC0a6HoQBe1xospRHP$(XqqW>&}i-r6!^u3z7!IRyPGm*6sn7hpCWKpq{n3X zOk%Ph>cUQKc4Z>%Yc!)LU3>x*DB?#wOZa%wO=7t&G4gt`FAZCy^3oR|eNm>i6!5jD zYCgWD4c@zbHH3>Z0Rot6SM{IgqwXY`InwN0fZ3ajH^4b*<)5m?2F9qd_AEou0%T=G zt946OtagGG-y$z9%Kv%F(t{@Hs@w^uM16%y=wtMcE@|3oGK6Wslqh%;x>9t_Ph=UE zuf-Ffefr1WuA;|U%~qVxZ((BsymEl!9yPVpuBPSH2$cW@I&{4Bs>Px`9Y^mbn}!0i zvD8zgfq4-Lr~+_j7*!yc6M}$-H93?Oi=S9*=3`rSDP?U2-Zr6TwD#kxTVp%2Aq6<7 z>=2FZ8VY^DV1b|?a0VIppddiMEt8mNv!MGme>0vv$X+|49`-0NVXmo+CL`E%nipt5 z(A0F)%=<-|cK)5pP7vkF8I&BzEc-LeX!u+dAO|(42=u4*b*2!&L!udMV~-V)LwSHr)+f4|GRD!nri! zU=C@(K7yZHG){^5;9NGja!$j(xZ(1w-tJ>XmEo$YA)Ec~p3(i&m#uMNg8wcVoBN?M zvyuTLmVbi8*h8ebTs046=FPU7dc^?h2!x3VQjhJca=K3f|4NQvtJ@OuFK7?$;h8QE zVl7-Q zv%YemH`anfjlAy4$V!A;sMLLAx}9OSA%w@iS1GzdAFQri| zcX3?wf|D7a>4r?TRAK@9sT_@{N&RxsPn-f+n#VMNV}h!72YRr+Ohc^&s+m^y^n$^^t-6fRP!3PM;-wTy=df$H#uS0o7wLA54L6EZEPCllGB4lC0g;cY`S7iI&h* zTgcKysdP?GwL7}UE)(|>>#$QMeDl9|SSvTM^Di9Q7 zm^j4%wzIuE9pk~+zRCKF1hny3fhJsn_@uG!01Wdw#b6#j#E}7)Fj252|JIX3R7%(? zZVXX*7>y*hupEv^2{ngGf!HA*<~-|t4_SnzfxCFzP(mlk&cj_~yo>mjd;OZRb%ARZcJiY{Mgk1(O7iXN|nB@Qw+z=!O+50M}6}n5PO>> zvR&jjdHpcOsNhhX5)LZU_6EIOMGPsEr@F>;gsuneE=VNW`VzcyT4(58r$M z5RLyIQ*Rv<*ZaGH;_mK6i@OwecXxMpr*N?1?(R?=iWYZwcPQ@eMSA!1{oT29|2s1~ zJDc++dGaQk~lOrEx|Yv*8SqP#-Ct#7}$|3v6zG zab}WYd*2tNV_uteiC3Xlb(ViBPgFOY@QQt41jp?F_R>%5#HgFEIh0G&ym`kk{uZan z5#z$2$q|sRgI_%ZaY^CAVqXKd?~$bcc2&L~jK+KZoe!5;8&t=+Q|@H)Vp}(xFfa!* z+|gl#RLQsx<0YT73@AturHr^>JH$mCv{0JbP2*};=6=o@1M6mDCsmA{9*^yB1vj{Adn42_mGjuYB?O7=hnu zLA&$}l57vFo+FsJ8#|4(!XXiN7f276;)|xQ!?8J)r?G_C?e~(Y zr8f$)T#gS$ZVvorJWUk7$`I;5i~t^R{{rMT++X^iQ9~CTk;=7)l{cD7kd z{9e!B%;$`yuSXC9w8~)bBY#N}Kc+evG+KP~R^jyBnYr?L>uc%;T}&P)@_84(z7;md zWg(>WJJm4)r#OP%KiCid5$i)tU;baI1@vz|ck8_pR3rq@#5d5;u@1|@S3DNiP(^GR zwgnri>(~Jzh;Q9mz4B`#ltA1|g)Nenrin^9pz@KuBcKiR-lIZSBE1d!L*?Wn3p${8(B z?9?SO!2SYgzYPL@e&N0k3_v*@X;w^DW1^IrrjjZayF%rP`vZmo5$%ne;C(U6J<2c& z>boQU!7)vhOvj&}$!XR_~@s9Wf`42cwHw}t%}# z0W_zt6NVP&PU?1&@AXuRE4R1UV?WQF4&Kuqe3zsb*kil@&bS7^64n&Y5hC*RK*lZ4 zoiqYa^bYnaHOAKhX}Q7Ud)enp$(&_}LZkB-Y4w*Mv{KJ|$7 z&k<0ZzD6rPw|@AdH1tjoL&JG~6>=x15hU#CBX;Nkj$9m6tT)9HY8%wu+td)2*VqJ( zXe*|6V=9Q88SH6&Bi}mzdST-@x2ENY7p(F$P*m$aEWoVBhvLORlIPf}@g zsS`Mi8N;dS+Z{3tTg{%Y)T_ZZp166 zw$|e0&vP6@`-c8Q^K_>xrpUPQCIH>a0c)xWm#CfNr)=>p%qEyyM@V>^(Pwhb&Jl_5 zNuwli!^hh5*DW>ca#Zs%*PVmLE>JrAO;L=P3a>N#$B1bF^7EjELaimEWf^Yu0QKvf zguUqm729aJ-ALJ$K>BK$<`9|cN=_M8bMVo+G7k2O6YUhXbxMB;izE0DFMAM|sq z{F|!e@hnYlh{l1K}0G)<*WR7~n|S#|34q`Im_27xVoUZ#zQ zd%?u*!WDx6cVd1t@vk%_^5fT3&+K!Z=d*cKkg3q2`IXH9Z{0aYV*hy~FF+4Em6D3J z!C95#DoY*mtS|ucIBcVg@q(dc=&1cU*_YQI<#6G|Ml$w}Cr!nPviN9(#HChra%z`5 z-q*h)jxJ)d<`oMg|(v1tIkcnTNCFNB*m_bz>O3vmwQy}dk^8tPx#OGmbr)4`gS zkT!;dSS1u1)qUY0tXfcIbXzNlTI4J!ht1;4S1NXYV+O({PoMJJTyYA!s79#@N0Dr? zq1Zu609TRX4Ngc?HGK{%(Y ztZbu6O2_+TIY1pqGZxnxxcMQ>uw{kiG&mIkkNZ5(5|zJjE`lKLwB(Mc=7fMV1M{{ z?iw&efZfC^UVI((R>OGgC7lBTSHj>C6?)$1S!-#V2X=t&XvMihSt?mrs-A7ddNxt{ zc@pQ}n-K_@whg3;^Zhj@>-vi7!n8P(qHJY*!%L)Cyp!LisX_RRLi5hD=&|fVEh*j@ z+<12KdhXm5b}l)UB_>ULga)X#;uxV4YMD&TQG^X9aT`EabSm8aD+p(De^u8fKc*1-)2Eo@mzct z{ZXrE@ML@v0XA3=N{XEc$z+<`f2t>LM`&m(vyQMPuqRzfX%F^E)2}Rm4RyWLknBpl zN~pvx`k!!%knb$kBC*!BdgY~6=^Hfx^>Jrg!pQ4b$oNycIQF>*7g9!v_Z#}I%T}@5 zc@wY12obyh!N)c*_R5J>{GX?K>rK|}!VRK|d0{^1JYt!Tf#L#t8lv>a$`faECDM8h z%GBw&Z~Kzaa-xmbjNY2-*pm4o;c4|rfyuI-MkAhj?!IJE3`~yMKE}uD=Xf;67OSx7 z3Z0J?CC-SYkhUVgo~nGRtTl#qchRur- z_8J@BynZqOuLyvz9SudEAhVfT{ni8z%I*x@;{x4?lzjcsAkX7!U2kLiA^+BJRNBYqIz1EeJ@zv=gto zH4I`|y?^_N9fIH>Rue9BzU3Vf(0)hy_`bU=XXL%zkduC69_7^il5{a&K=W^qVmoG@ z!`VRj#g#Asztc!Y``2hlZ$8F1O#=JXL086^ZQhe+@9F#cd$f(dC~>l?VpNPW5FbYU z1FffO-kJ=<1+#N~J%0dBEg|$Wt0#3g_N^4th}4a_#f(b|6;@5~=+HaEsD~-^yM|9< z1&qOQjSbh{k*Nw%;yd)=Y9Dwo>Ug`I()y=eo)u$*D)@h^&#fZmzDHlc}KB5pml*ZoJNVlulvhNPzE3X76gWEsi@IMOym$01;nX1`+R6Qovr~8ovaUr=DF{BfZxY5+(fT;!yCbb7akhuQ7QR zCjTLI^BSG9(Ab%bac_1rWrNpxaA^$zjlBLuKtIa$ss{t(6GT!`7pGl403Tga{>^Hk z;i%6-eZGvI))L3Ka}B1@RCJVcy%LYBmm@u>XHZ1k8Sc!N%LEX8_khxuv-?5DJTZt` zUQQOxr?p=}bBBxCw;~?I&1Cd`Na#(+KM5lo;o#$S)cs|TPn~z7OhZn#<#*OEa{l{} zg;?hKiyefBnPuTK$;XdJ02`&a&or0@o-^{l(A3<84~ zbLB8F)adEOJ#vi=R;ebHur!T|YC+U96B{I)eO3ghTH?NG4i=GWOqz(L(DXTG<(hnl?v z?UV7PDKJ;J22-YWi{MZ;(p(iKd)=Cd z-qLv?)wbWkakjs?oP8_$a-6DlQNiBzxL1=7;yJr}Nb_(r-D$|3>50e(@cHUGd#?xR z2IDcx{*m8&?5|#0a(npC4za?Ih5n{o_ug8}@8a~Q&Rxn;20}93Xnx;yDu@SI`n?+l z(hlVLk5>=6DpfBy=jhJJWi$zz&V2u}E-90hJg?J|Jq>YI-JQ+W}xJ^dwM`pEm3-gNgJ~jm6BmGuKjt*ek(A>zzmYc}JyO;I!`^ zg&rNgfDglf1eAwEfz~IswBYx`G<{DOtFQ&1d8A70awFMLl+%ahw>q(!Hb2+RE2v* z6F*9^PFYCRGLa0>1kGr)=byMA3Fsf+$ypm+Fr;_?VMz^hJb~?0EQZpn_Mxe**xjIV z^?E`g>B-2sSrQ6zM#6G?qcsjA;*TXmaZQ&=fe-wWY5&A&^uo7H0N}?u-IPYNz`7BXPU-Y9U^q$*wcHtLB`kHY-r*2%Iat$1$iF>(kT-6}z&PZ@_EmQz0vWB-orT(#NB+k(jpoZ2kyWmqchnF2 zXcLcC^!#}*J9y#dzuc?7NiMfOrcLCQ6_`7mwa87cgGi){S&%;F;7J%+;v|&}u0QVeZ;6jrj5v?`>Md^34<2;%5aC^Ht=Nz5QClI0 z3{b|0+kRxS6%%Z)p4am6{qDfGuVc^Am)eZ2bUJRk-Yrpp{7-3_^%sQ^CT{<$>G(2 zbbMurlmneT*~R_ooboJA@H^X$rP)i=BCx9GkXOfqeCLCzW8F_Fj7vBw?NByC1x|DSqndCN*F>!PnVxRmulQR0jk2KG=jVP=lKbIWSar4K1t3B! z)*9QJf7g_2+z5_@QAVHfAKRrfTLfQSUyFMCYh2cANG$Oez8LB>@3++&)@s-RaOUV6 zQ~R0QCstjJx0>m&;E#qvQ@Yu-HdLd`JtYDL=;le)yhtr>V`>*@c05Ev2ez~qm8U~) zX@A1T-@VmOe=^F?X|z2JMO8SmssI5}yB9Xj)j7MWg#*3H0|Wl{#?O9wr3#0sS4p}g z+f(HF`Ws?40pbmtAE_-BNyFq2$bD>5_44kzJhyvRztfi^amBLqnTy;klXMo;x!X#iS+aBal= z5ygLIrqq|{M3hvY_S=_(2@$P={VQ(DMseOj{{1gc+AVk?rdQy7;ns?3m878C_yg=w z`U0|Op&jwjVly2XyF7ejg0Q@&!($Hor~~}I2iHc?ISV}l*WX7d{2BZlaT!%R|Gm-L zo$!3(g@uq#8}(1K!g-(4Ya=iNwaz}3&Yo6C1sC#-%-g@UlZv~2Y=IXOX6g39#2P`9 zg4Z0Cc!ES+kCeBjV=bhjjPxBuyhY0m7O(;aSR(@!avVer5R&*b6b_dm*QG-U-+fG2~kz=O}7TO4$gVqYm#i&}7afd^JHvt2;?ahDbM?2xi#flbp5KU0?+^AlplzCd6AcwC zIx7$;X#u0#u#?wx%xixwVnu|RF+r2P)oAZ5bWnQbVi8>csI_6~N}3$%BH=~rbgB_B zoD6uf*y4ZVtBY_bg<4yQNKgAigT6zK%KF2r0eSa=b0U2uM{@rMkmLKhso~ihCGiJU zU{1Lq~l=x*WUk%sX=o z#P9A$%-e#kEjz#bAN$5Ob?C)qqmbjT8RVza@IHnBBA;UP*gZK)A*RN7Ie($98?C1x zzUS5BK>zPnd0TgYyyu~+CnwM774B_qFEoKf94^1uqFCgwpCjJmVVp4#hU$29lTk_g zMjRcdvu=2PYv!1>&rzr4p=5UI9ZB?s+Tq=zE>n$;&;!DM=_U!5*P(MeLIX{-T&W0Z z#f%aKVOH!Cx)B<~nxo7Nk_KjZM3vo9SQt+?7woNB4jAPD7CM<}un^SWBx<~YJ;1+; zJVUwm-xe4j``3#P%MmotnxzCa2abMF)Opdx{ynQ4Zo}ikEbn`k5?C?T#s$oH^D8D- z@A;Ac-lp;&A9Nf3$Eet?xlq-5)wMLGUX1-y35`W|5@GEF-DboC$0d7(n`TuZJg(3U z0ozG#v>mvlM}VWqJ{PSC!_r}V68VLMFhI4QniDjgNst2Wh9|oG%Xn_bi%d1`CYXUI z1|~|x-idLf9<&)E<&h8_E~}(&um0qk-gm~=)q3cO;Yin0q1yZ#u`hibbg^3)8k*hW zrUF%~1?RIHY}LxjpDWe}xMIv0;~{L1U*h-Cv#o#zF7T!#(8YJom3VS%}pX$4#c173{?pAf4Wu%E-a*l|R`j0h7 zl1+f4>rA%4xW6gqsD>7;Y3C~wnS6L06DiVTlb(q20Ulm6ZoYk#33F)LMVXisDilnl z7;FQ34BGmSgMr%SH1m62_Xm3tNXUv2^V?^tsct6sKGd5>^Mb+CDvSTTRl~IF4vAPaf18vJM(|4 z-DYEBDd*IJD#9&4_U+vN8O+XbTPW9A*> z<`!vo^Co;_2;uNjDXjtv|09UOqe|wMr?e{US`^70NZ9Od>J@i|0Xc)`{W{UG#{8zP z-2ZG>?1v4H1Lnw31X@^tW3B}+Sqq;qV+Xq(F{z*qBS61QuY!bJ+KCnn29H*Oc$P+*xd>*X#ft&b4v$)h}YenT5(kOK@+5=0GA=IVS?J(L|MRzLBaDyfx&tV=$t z)$ad$uU|i5jPjPu_&X3(2#XuCt0D{goA5|wbNr~rOt2d~Ynl(;9p$K`MipH#$`1GJ z6sW|95VXAL9hbvYLJ4e@>0PZ$mj_Q0O!(=3X|b9-;J}|s4w(y);($!aX^5w$u(7Hi zXv%mo)22^5xX`sz%hfABSVx7ztCWC?qqjZ7knu;twv0uii}TFIIFpQ@P5EYM^SN z>c!t>p2;h7D?K`%YRRjiD_!Llb^Z z9T%QBU^FmX*?~711i;Sc3sqt7RWE>yj?@nhI~!5rv}35#+kST@T3T3CZ=o z{?6{1UFeAZ{T0LiQpnfW3xhN9Wajf{*@`LUkx`a3WU-b-Png}h%WiU7+m$eKc;W}= z;d%jm%v`P}s8OXYjp}EAMYg#{TDQSiq?rM8;3ck`0Z-xfU{rCEEqVja!1Awp6SAhS zD|jZ_Kh|n(4_TOw_DQ+{{l;t*e~DwJ?R^p?xs7xjX}lE$ieDJ2Iy3}s=SVwlm6?c)$cXGNg7sgBDURNx-nKbK`YV2dx!nMqAk zRq+^yr_KYts8Z3So_93+rHx)eL5lTZIS>UTjvdtyo@Tz*8i=?=|9n<&E`93GO`A6y z)EuhYX^;yHjLI@i?&B?lKGThPK`8wF4Wgg%v*lz}&%m*(kSp6e*E7JosZmi_7n_X=FOK8KRGDvYvsbMVn)j@^*_TN;q){$wq zYeOnEL|xGTws(5Lj}bQs2v+o!Kj2kSMsdV|wd-xjU1Nu81muJ#ibWLrV-DPGE3`2c z0>vR~ykVEA4`B2jF7P&=B#q!% zCx7napvPlFSW5Awpwy8YF-cw=*aM^9pH5mW1vhAWA$mh9CzMp&`_VUUAw3e-&92)9 z_7LC6{I&27hq(}wia6ke>D&5@pfl_s+Q?hM7wWk4g>3Nzq|yG!a>&-sBnN-#J5LBV zsi-!yq0Q*5Hmj2V9uiHjlv%yx*u8YJIw~^xK+CBUj~r=_P&nP#whIB%9g3mNU#3?Q zQ(bo?QV6^v`t8i+ecSCH~-?akF;I6qH zB^k+MU8R}H*IlKn`^o^TS4jrn^5obX3GGoFb`uduFpvZ^H-@BDSNTR0=kqj09DmZ& zsj0Ua@`xsdG~d6wwYg%Tn1ZTWIXV0NNJM)ZiF{9GDQy=wAI`nk(9FQNDCB+THSaAR z&VbBzoB#7PDv_0Q`cwGBj0)Po9 zt?`0LXKL{m%teIYeTitFo0P*5-IyRvig>cR%J^DYgHQeWC!~-l7?KDG*mJ#djKVb62ZTgY=6xhKGBgS+LU)RbIk+I{SQ^%e$N*ecN9L<&~wrQ z?@5NghO+v=m7#XFvXyEI1Sb~#H@kvu6J247Zo}SNfg|#vSNgT@crbe$fk@*CtZD*20{U6e$rT^m6m-+z9*N%`Dx49%3+Aa(Cmn%vfT$bBDMesM_7tEF?^ZYA z21X`!wH9^&_?4=&x|4s0;8rZL{j{+d@xuj6b>^aHe!FZ;apcr-5{7?G2EAk3l>C@- zWs{um62B2pSAyk!J>7i};8iy~f((v4hk711!CUZ);Ty4N^NDNau=2v-MJRq1J=IRS z5;q(LRyxI1eE`8?7uWo}?VrC0ebhz^ENqAn=)Z)@yQIFpdzdVVJrhB5^FDqzsz-KN z+kZ{Dvw-Avw+u+qOVhyl0``t|3%FuPQfI>FQ0~IZ8EoFlu$58UZ;ZVI2V1B;10q3S%*MR(J67htYCFYqK9zsZ zzeE!PIKJS?#|gYLahBSAnL~LKHQ(U;u2$jGlF;`#DIuQwX|fbBdAWV?UgRkL!7voN zqwOF<;(#TO?EXYLWOb``scn&=u=~e;_H~;!H6Ck_P-Xb_5BNeJOHwX3jsL-r(u3AM z5TEmBY+na!&~*l?KJ!K=;I8*R+09J}o5f^##uJN*Al^#sh~nDMMb~tgCy|&L*IZ>+ zz-dn=+W2|(EjW0nQ~d7wV4%k;XvF=}BugQuenxLvuWqModW)~F$H(s`%x#0X-Klfv z^Qz|*1y-oNu>fU|Kl*e8e@-a2|AwO(XhEO4L~Y=HG_AXSHDo*94lKQxCf66_S9U2X)Q&M;!q_?@aXiN+mfkEog` z44qxVNN%=Plp9WMn7jvMeP$gA;EfDt`IP?f?p$F~1?46jX{`NnMsAZ}Q#DffJL~nh zU4VtSisgC;mCQ!RMRqe)4C>Xf47H14yl-dnk01S}%prWayyE~)&kcAuj!FUr{rDfc zm!|}(^LXnA8Y+bYwz+U;LCFpfV265jPL@P6^=B!ajTp^O;|gj%0BF)-FZ2z@y-rgPGl49VLaoTa~^; zWkCQ{m|$RfZ2!N(!kJA;{QQHdXDOQrDI1ki2?%vp;S!tdC-vq?Q3?W1N)P1M1P+>SqO0-~vNXTGB3;+rP0~gD(GjB`hI!0(Xexxzm zi!ynbt zFeMU74mUMc?4M1q-HbxKc_iyO8!KJ_m&co+Iax#-wk5A~$3fhltY~AL2P!5hu5sjm zm?;&Z(2!;f5q?NR?U^TQ5PIv5M1u9}-D5|cR+2S2tsG^|bCo|F!@d5=z~+Av;WGS6|HBK z=#4JlFGa(_<#5U$8eTb~a+(IA-Gr3YlBwG%rB^zspC8?ZhH}^opeB@KKx@K|HAs|F zsQle{rbFenGJ5uT3FqI>439bbXnDid~V9b{1u5qpCqZAEmsb4b|sRIIqIvx%tabE_f}o<#Xa+>p`dWtj!SU$cMjse ziAUo^vfA>R%$urx6RCoVP`nq zg28Btr$Uwa60iG>xH5S-VHW#kuE>yI#CE2@U%E28JW&1OM=0kIOZlvNM%t+z?0c`^ z2#bd-Tw`Fwch7zQv4yBvy#nymb_^;!Mr9zfOa(DGJKa7dB$p+k+#)DjYd&WSaOkTq z3i%#DSAc7qDDp0-9qB?k9iVNysw9jaz3H7HRnq_K59Ju!TV2pn|`2 z>auQcrp=@;rkI2$qG1H{kC-rcno}Wiy_rr zDz7x7eG|^Dli7&Lw+W5QSr{AHA*X03_tYONP`dFks5$W*4y)rjW~nd$P}W~zOR7EM z$mxr8DY2{EzV7?&J8I?BO4IACZ?4(;cN%yEx}0TM@(wEZph4aTUM5W$H)WG9V@5}g zFIY!Dg?|?v@hQa_(xfB=E=WS`F+xbUWreY9I+pMPBRb=)9>VI|w(R!F7I|sw-fN(s zXj`q^s0@q5!f7J1eRW5GZG=QoRAslR!aa?1UnEb5X^tP{i#gJ1i+U)ULQmtsgL98YVM_EWZBC!w)Nz|%_m!!HRG>+)2i)Qv`{;tMYXQ2wQ zJA{@pYiKJ0SH^p3yoAPA-rpoGt9)BYzw}6j=ZI#armAX+lS=pn=os=M!+;Y11_^GA z2L+2=c4(Omj$S+GZa@C+9$$xdO{OcUKo9NG03Y@-ETuM;YJ71UFHUM-TX7`ILT58g zT3yM)7{(4amHN((4FbdG{Pw^~#q2Vnh<1{~^;=MZvKB;}t<%czWZ`qd0p~m*Vx-=s|x<&?YbC#HR=ygD0gI@&i0h+j=83Z27nOgx| zbOBi?Tc$K;za6?Lnk_Amd;;B27ou!tXog4rX-wDwZOYcROrqS#Va1ooS_nT-SU|NM zb{Z%rmZps-8%C(>aA(;NJVzl^qNRcjF-5eUk91wb=o~0A3Pn@d^}#WM_yvLOJ^HWF z)i~ukeuGn3VxC>cLO;`5VZNP@K0XyhA|?uxt?!`8tU4Nv{=mRmGV;*7ye25Av5}f8 z_yV~L*!-s(`&JU``Gp|m1_Njj%3u_N8iyswG!Rt})pkF(@YDt&6&}i0h={-FMRCp9 za0LWnXa|r*nD)%mWQ+ViL>~osNvG)Kqq$(Qx5%;*{p19B*_1$Q(rvz3znBp?dD}^EH*L&t_NFAjqh}S4c4z(h-HF!s)zP5fiN1wNax`q?e>#pt9i*_L7 zO*q1r)?d+~w{d(9*r8QiPWXLQNpCaFBoa1DB(6isyFm~$o>fH~j?gHXLl24q0CA@h zOd(GP$PLIyIbgAmFS=ucXspPxobOoXazqxDO?2&zicDj|D3^m^xS4E&s_ z5`t;5`#usMKQC&syu=;%MBP;KDh1hEyLTS3*xno*wIK4s#4)ALYd*kE;AR8##|g=K zxuPcz#YB8W!r>36dqk%N2K2`52!r_##u&SPCyzy}suEdHN=6h^FiqK@Z z=;t$NyeVO;pRJ=;$a$n{>1(vJng?X{Gl!@?Bi+L z>D-l^38DSqBmK|q=q$HazkBnTd_LtIwM=utpmMg>sPyrbW#H?8mlxV3HV(xOy&h7& z%XJHcH>aCy_^$-j-xnC^`98tKPU72x`RGR;4>-h8er-oz8uW*Ajv0xRUJj_&ZpTxr z>b_&|?`p8&CE^-&SNd`6xclP$-OVu3^=A4uyOvb|*D9DE8T_$CT6N%Uuy zqH07iF%MCUtrHOIw7#OIjd7-6T7;5fA18-T*4d=|?kQVDi*nfbo+%>^np?fYpz2!RjiYli^*0tC%OuLJjZ=kkuiWtw($JshC#hDHgHb)L=Fru1Q-%3$+0^rA2rcC zlEx#>S7&cy{HQxA{8Dc$p}ufs!4R%Qn1r4OJ2u=D^WK9A6CHb4dVfoPn|`Jw#)rtZ zch|tRsy&VD**W!y&EN|;Iaw|>u+7Jg@pOej)Cl||VeoygyaB+&l?ShBbObkOj2%jt)WBl48g!mnUv8}(=C>`1-{x8|Wb;KWYGvx1A?j&z{ zH|+^eUGOL?SxM|TQb~i@YI(bA+~0a6#c)28{T>9xevfnt9ii$um}hChT%$AJekt>2 zt~Uc{)tNIBNYw7!=lsqI1qdE%;lEu{HF=$c=cl()k~924abk%sRuLp{k&1kO3Hr4C z4jYboHC4=Vq-9ao_iHWeSA@#|YLZ2SbS#%iQ9bpzZanq<91ZEp2X_t8K5}z_tpFLK zlhd1;(6>k-fhQr-%?Xg>vA(Ds7tUPVTV?@neiwMLo%F@vZGZQw>I}Q(4et#DE|=18 zqPI1!4<`oF!|L=CTg^z4J2GdDqG|jyd`~j$`SZ{B3ctKT3f(0Ch#)l-UdG~@a-audRo%s~f?Ek%iB+{}})P`vn`QZ7Mi z$w()>mQIKaU~Ulik?ER?2BmY7S5^}bJkfKRpUY=)_WwqoB#?Q1j7rCH>tBx(wvSdO zc-cH-FBzh>3@56!npITBMC56PRC{23Tf_2ms%&{6I=kj?(% z&9!0sjpm3c+HdQ;ee=L-&E-_Ev{P%~a$yJS&p zFZ(5FD#=za_l}x9iJQnvLiX)&ys}lhJ1}7~84gyW(N6s8ta4Z3IK!E@<@>;9{>#2h zFJHAU)#1H**AmXFlE)B`QOAu~Sp^Mhw2>$s7kP{Z7%{B{T0$y89j8G)=Qj9sZ)5xj z7TVTl`;J=D*jSSe3DGH@Kaj{gieao*>9Y*AvpmYzcmyKB*4C0Zfy!nDRYz3N2eZ5n^Zp34 z)BDi%jUEpn+m`FK&rPmxc{i=Kt^c?BaM7<;aa&M7&JZ1eLQc%ab@q2Xk}u5cX9FDc z_}z=;=H=8Z*RSS4TK7fstGl7{BUh^zYgPtX{=MVJG^y<|v+3`FfY)>mn=$NP5wbr& zLZQqhi-V1IXNl-)0@kbN3FH@IpJ+ypTE_^Y^<#0S)R2tHX<=HUn)rrS9&MU7;cp=Y zi4<^O8Fw=$26nR#Vk!H!Lvn2Mr;NJL=T1HH0imPB*vwPlS#H%VdTfP8KltJsz9`z# z_=6MljQM)sCviX0`i{E*`g%Br{9pKgG%+8nxBL7_)nKn0g&NS#cjW&{$aKm_ba1#6*FKVKHL%}(bV$Baro>l$s**Y5X4pajE!dayjysoF^ykq zVEQSVcoxn8@U=!Dt{G`W|H&jR3_pvHzN0^xTx+Apf)<}qN^$B@>}UvdlXjANgQEhq#HZ$%1)pzu
      9D>-NDj+0ULHolpA;4qPeNv$)0q5n`PVDa9$T>VuVhYJQ*dPLx z`N87Ce*8;K5tRp6gona-|5L38*uUHVPLxUxq*J&TcdeO-Y@0fuQoK=l!jFma_@r#f zLQ!?=-Xd+&u;*KN;(;zf;Qtv2c2scqMgjXO zCBkF>XEecrsn+9=IYbNs@lT*L&TIij5`7g;_)sSO8Mdu*P0W`3YIKH;f&PZF5}cLd z7@4_Tv;1^L_()v8@OC#4K?$9*(X-_;jw1)j@Pvy&1xI;9> zaDc4IeEhJUfz!dr$V9zaPMKr;6m%1ig8b-;#R;w93gatonj02g!#YNQf=gw)AYM^D zqp5Pk=YE>fb>kAWhYChqe);`um0>tixFl&&5qH$;;;^Vq#qXJ1vsyS!mO|UMxn#4H zK_3L|EX<8~;PbewZE78`EE=|!byxq@D6$I)8G8eDW;~STnsy-w*PQhO~d?vwJGwB;CA&T{INP!aN<1GLagp@iBa&^hvMwn zwW7=u9aS)I0xn~pIwVJ3aMKHyCr=&P%r0StQrAPEVI#-RaXz!`j4qlsDt}Lw>Xhd; z8iV|m5OPrXkm%=1T-xT(Wi6r?H`A?#)Vk-7N?IFxq=7_D4Ug}`WN%Knl6nmcr@*XB>M0bb3%F`4scZDk*Ah%Lu@!Xn8`>K|+ zc4B^x03SSGaDdThVx>0F^orFl!3*xt#f?)NLtfedwf5Rfgd!WFzwtyEp=aZ2o5Vy4D@-(&e#ssq?E(EAE;CxzX4co< zbpw7VQIoCJZ4RDEaG~< zESpPhwQ{OqZLb^7VScP$D}7~$>;LXh$w>z)VeFyui}Kg?#hvU*a}-8fVXZd(LYM08 z2b*TwyAZQ3cevON-WQteSUZ0|O*CjUJvuXogG60KAdyqJ=d;WT@B@DStR9mlK&{xQ z=(&R$+Rif10)Ezh1am<`8pItalW+6~4i143b3y%fQO_%`@f)X68;LgcjQ@aIo@6Zc z$G;PX=(I+a5e^ovD9X5CQv%|spzyqLA!49RL91R&U zTx0Xpn%}dt5JJ-A5jeXCJUY#ns<8N``_zbkET2PcFm9r1EWwM#n5XNfM*C0+kPYRe@(7e{8zWhDaA zt-OyHLT*~0nvqwou9@1FS8kdFTo|8qrttM&EM)2H3bt(y@A0hvR60M41f0`Pj2-m5 zH>Ee?Jw!}&pHZPYNACQjOBLQzKY^iR^7@I>c0}UhpXA@rfMjG>x6_nIG4J1YdGLLO zHIfn1e1?I}0=Y825k(CfFMXTfM_5CC52+VXC$G-}E9+gW6VH<{%Gmk7?md{&vq}B$ zJKnNC>pvrB3rI0QC9mAQ1#P%*uZRpNV>=xGRwm;pK^CUfL?)ai{B;NH6Jf>PnHZdX z1xPrr!zg(P`HGI!Q@XO|Qoy4a>1xVI3kI}sBpjemj!EfdW#c3@K8ilty{TZ0iLu*h z*wsSQzmwo3elodIYm_*VY!Uf^^w}R%aTZ8Xh#z00Mt4M0D2$-|PDTXL^k?EHhlZie z^ut;9oTwDB)_zV$wy^XiRTG>MjC*E=h|Qy$(*rvnp2O+{5C%7TXkBZ7bXMWjmX&WmgRnSmw}|IX|?&Rv%vM_Nal&t00d z{9)PoE3uN&%3%(qVTzzI@vk8s7A-IOTg)gBdy4qMIPr)P4Z@wO`Qmi0%6;yj&GS(x+PQCeCjKOF>mR0unq0Grj}^-)lN9+7{5; z!IJEeZ;?Z5V+=O}8RB8h$1#q-Q=_zLB7Aa}B`Mc@$upN$3>qHT8Y1I`?}9@+XTrB< z+K9#o?HUtS>|LFtVdptAS8Mtzq=`Z!hii&n^|9x zDZ4;)4!84res>w9gUFxpmNt`{u-nQ4!Iu1lXwWwcafyC@$%!{{%gCuAGa4U*%ed5O zBOi}qt+`toM}xIZGsol4{1tZ{B2H!aLT_&YT)%-bmqMMl&!Db9`k%X-@MZ46P?E1( zS~$*omgbnxUeb4X(Q{3M{E}2HJ@G>sq|d%W*ZV21XrPnBYuh!#TxUjtx~ObR+)!sV z+=*w4Xoo;id#0=sZtjuW@-*!_r{A4o(hN_O%}hHUh~ouLCCv;H|9*tDVE_8cWq|Xt zg|xPBUNsd?Oq)qC(YR3bE~v2yOnd8U(x%fv0ENo}y5wBpw`ona&^@!%1}W@*)b&4&O~{58#=944 zA&?QaLKhcf6BEYvL(M}8T@}9{sl$CzTk0WYd^0J-t0iIxm_|~5UL}m!bvxNVu2eZz zBztGX%V}|+Mx?cQ@7hpk94bf&hI+6^%ipU@rUc=Aeq zI&1ZEa-Ryv7G*brS5#})_#K}KB-OYQb`hLkkRYu3BdH40>~!r3)i%$=#v%i_M?&x| z7*rk0hSE7IPdaDHm@OY!9=0M{cU5?Q=4~Av{sa3Kt74rmceEGUz944~0dQKO^As*kTQrMLQUG?d9US!5q@(*-o4v>&=)bl(g;s zt}WL1;YAqxAmasF+=hw&zte#8|DAd;huvk)+RJtaj+~{rUK9#$HvdV_*=$EtJN}yU z)D@A-MRQi?@P)79#cnP*Kme~Nv_Np7c^}xcrcAfQ_71^_2JpP^TrSJNy9i}1NL`-r zYap|^h=nti>bIni(K1o_8;K~(UV+=uG(_M2WkJ4pW8iXF3+dgLA@&eTJAA*qlpRcS!s~T4JEaFOK&=Tz^XWA@No-KFMcXJhU1|<;90>sBSgtu^; zXjKSEL~})vQ+10k6bO1>4#rde=(rQm7j(7llit9#uN{>%gG`g#2^M!f4FzT!M=?JH z#>FIFZB(j^IbvMt{V0s777>6`3(NU~pa#8YGP-`XE*nv`bh5B@pGVlmj-}BuQ!yCF zR5hqpnj@?GF5M$b>A0A}`?!CKUZYPCQNnp?lF~6tMXe(hDAb+N*R>IH%34vI6{GRfbCGp!4NmANhgkIm}AY$+@#xbl=PBOdh0T>l&Gc|Pk9ldGhLX8Mrx@fJ)I1q z5zSp!lFG;oP6?VbjSC@EV}YZyG6;xy(ehSP2 zglHIO#TrMcdH^(8zxmZn8o*Yt%yTA_C_03%_q7$RNyojE<$N5fargPxFbXy@4(tNw zvDgrPN|Elm6i9HSKA~nke&ag2u~$a!#CNpX=75)1E)0j_`>;c)bB=7SJZMF|1%o$S zcEM;#sRnv6**51XK(svQ{5%n;4#O#889`W)+`8&yVP{>@N`6<4KcLmWp08Fm09HfU zzGv^#RK21pK`;K&W?i*h=)l1cx!@R;C~j6`T`UIQ`ES$4geR`va?2iY50qlX+D=RS zDSVt7lL%d1@csi(Y@Nja9}4 zXgvQ7iwV^zYjK@#{i|#ztOB`u?K2=Faf^)p{L@y`HVbP^?dH*Q^RLlHWgtr@7H=J! zJe5Wvm|c3zIYMW}o-tEHd(jHm;OHb!m*DI-$ZGvtzo9iSUXC7-jpl5qhe$}?e0yGv zHbisBYo$~2gNzmxAD99qj3|L#vnX++EFyV$FrY_PU*OD1P+Mt!y9m` zkpq`4S-F`p6kl*mxV@3NIuX9pT07M!M|IjWYOc*2Zw!N~Xp0K{PuUZA<8^h`t!El5 zv!ub&8J%^}AuuLn+ssleTMJVZ#!E|^MqC3!0BMoNT?0b{DUxPT0|NrmS{t4Kh)=Uo zYj3kST9%-qt9~d2&JKk3#QZQk6UA^v5#;+*Oq_sKD}S;ykcT?f-lk3%@Pjz)<;l_m zoD-@+{c~cQL`Gb?AG|DaGs`I=1!p}|Fi=h2-YTA&T`lZr!0X04Nq||1E{h zDB~L|FpiWz@tsM7E~}%~%>ob72er%H^-vC~wRpJ@Pw1aEdQ|MsQawp*)q?N#}QP6Ku{cM7~Arzf9n#+)+N zgu3f8)W7BmfCJ}C+ii~GoEtCoP!yGlju!7s?(!$0sC|Mi&l{!{PVE5zY%f~s=9qLA zhN9*I?STmv8+KA~N@;+gFGhI{LkapQbhqVYZ%LNmD8<%ZQ>zl(>kTzGO;M$MKe%tS z?dOepNBN5Xzg{rN*<^d;)8U}1jHQn#1opg7$<-Bgk5DI_CXRV zWQwcl=T6Dtjosb)9?OrF*p+n^IYCRZ&t1x}@dp*iW=gQLx`mn#l<3g`Tu`#pATts} z-rf;H4j>;%{`~P%(?OZkd<%Q|cq40`8WW}=ZBoON8!sApgV}*SZ0IWL>|asR2oN3c zpg3wOL*nNYhys0Lj7N@Y0R?`}beNpT$uv-G?@CGx1kq2GvXlA4AM^G>q6su^K6IHf zp+tOUdiaR%OL-Mpe^YjzOp`;uVANrG{6u8N1xfSJhPel_76+`6*S%?BNi=m4E-6eJl=u>%Q?yd93;fr z&TYD=oH7(kJ#Ypy%&e%MSW%X~J|y0I5YNXv?ZSxeiXB|Y=a1IksyKFiQ5B;cmb7q| znHq{i<_@3DhMCBlkTfDBKoX~BhGlX2s?^W*nSGNdq_W#%#QxE z0q`8^!6Kjt0i>u!RV(sPkPvZ;x<82$iZr?{{i$d^HSN)%C%hqD&Geb@;T z@axaO{$-Sz<9H!%&zN)7Xovq(EApNF!V`;Fa?k7FeGT}u^Ut0{Q1IXT7;VqJ@8`T^ zUM_$79_Zj;b|3x}v6|vWP3G>3$K*Jo=C71FaOE~z!bJocNKjY8d<_-Irtm3}J?eA( zSg3|yt0r9hsK|r45W1!P9UhJ8nwP+0sp`Dso2GFTX};bdHjB>Wc*>jv_dquL6eMdL(3CAGSU{xoIYbv9Zdy?R;o<1ibgxvCEc^RJ-B6NiGUkCE56{$3dPygS z>?w1rDHp!OJ-3cHz&DTpb&w>8aW^jn)Khyq-iy=+DUCqDY8^qA$E+z^{*FhNZ0s{L%cTv$+_du7Rwf`_Nc7+~{(3 z6>;V#(w(m9NfEZe_Y2h{M`V;rIK6Dvc{KRJCnuVS((#=_t;vyqcUN&&a+u-(Vl%^3 zOnOfklkL^@DT*lnPj;_zmKbvTB~qOO ztN2^y(ILEJYKNJ5xp$JDb4bJ8<5fj5B)!uHzyurg#`8&KuAb2b_oJDkSmQ~P`eoQy z_r(&glPpfPaz;vLIaP5+TV-pHwRF0E7KZ>>o%SWmlNP zJmUyw$;UR{ov&WtHGmS$2WS4H;b?)@rbNEjdjMK@1dfE$evJojT}%pkmXM|xbeay} z(!4pS62LAyfCim@5@=8hJre}zoiOgI8*oFAXE8wfJy>@B1Rm;C>UCX%dP zINOMS!`MLq6W3dDhP{^E_PDJQ&0mr-rfm3ZnOVD}&IhGyc_Z|=awBN%_P}omA_J+0 zzZAqTT-wy$VEKk`Jz#n*{t=XVh3S%Zq!Whv<_{Af8m^7V*6c2Pvd=?0#az58KpmW& zeFNOuS+05`Xf#a_P%Ih?E?q_aNy7DvGsn$j_m%@RxE{y$+uYM2(yh^VYVB|~apmf1 z@p_L4b|ti%VMi@Ih%OJq5>2fL{6|R|gU^KlAaGGODhKEQG$WxV#8s~XQ#SHC@2XdDbXStC0tk?X zj}C|upHF`yM9~V{8e8R%VEwml+#bsIGtQ{^{#(VSa8YYFWKmd`Mw_n+dgeUAp$qWD z*P+}x{#%0rKFhCY0zEJMDZtd)U@>y2!I4zg60eH}Gl(h!!-=dc`Q%(@Vfp(cu4658 zS8~W-SD;Ts0>YrF?BR@Pp}22N|D9$|gfw1F1DZ6inS#+MZ@j{!2+8|&GJfe|eaUyC zw0_bGA6R_F%_k1NlaFZ&FHeEP0Bhjz3Hnv#;ZU5Drdo}-@3LWY!;;cSn*iV&jjlXf{+U43@-I+RCmq>RP@4IpJht@QS7 z65CH^a5yEgjS#!we3y%JL@Uaqb;IA}%JL;2oojvwB4b(o*{*Q<#q;hn2McAs|MW!1 zxEvsmYvOXI#CQ1(v5$Oov7mm^`7;u*422ZM({qeZoK~lgj(0fQ_4i_Rckcr;*i4(T zWq5@Sr}S9Nzu+G2^b3x8R2pO3VC8G9Jv(jOql$hT%qc*2BYH$H1(-KU;>kQJ*F2A+Mx=3trq9sZJ{bWKB-O(0~HUF$pn*xUoukc`nw0KQd) zb?cb_ii~EbFBqPrs{|KnOr||t2UkN5j0O@!DiaIy@#_p)`rBbYdTjd}9eCq{Ra9|l zK0Po@z-`2OPuiWHPo;@77H3T&(q>y*_hp&;Lz{*IcFxmwMk#e8FB_4n%2ldxI24nI z$2z)-gq3oPVJQr7C6fXswVBcn14n?pYtYnK9ZAup{J(DLDmdpn< zOAo{no1&wwGx$ua^Hj&`le{xiGfb1MjT$YKay7pQFc=UvbN-qq1OQfgO2t<`s0Tw= z@U_gtYswyikrPBR^q|+*D@L){q%E`6P5EKz98v!qO zD}t>Z8he$%>&N8^c`6{T>z)KaOQY93{GvDCIMc@OLY{o!u2jJW?B(+#Vx z!C_J^2ea7b5hbJ33>9`MUJRGOsWtkrgay@fMmgmD3)*FM$fC{q{~93$gFCtUaFzOX z{o3)vvzHFqrhTI)G63UIW_b^vR1~bf8L`X8c(m&*DB?1nf&%^*M#kKexjz~H6N>qn zqA_+8>2oO{D7Xl9+7VGsaY&tCz$?6IP;(!i$={Z={Y`sdubvo)536Bnf)kZgB8(VO$=*6#`) z=6I1t&S7Ij5~eiwSfsD+HG^TRJto;I#`jlz_d>fk&8J$B z@4d}~)ex5rUDSq*T>?5Fh1#$h#S`m(C&7#5IV4z+gEJuVPp*lS1dZ&Yl&LnGaP$Pg zkv`KixKW{q?mU)5*~09pWT!!f4Sp$?Jc}xNDJzIuca5j~WE6@cyYEL+U%*qWJq^c$ zIjK=TV!86D@1I@93nRgfK$ zl(7z#F@($zyPz5*=6rX!vz#8iHAmIG7_0wU0ji>EW2+-kpysA6zk`59huRF&cvd7T zp15=!D09(SKh;$CV*>sRcr*;FUCQCg&snPWZ=FxZ1jb-_xv&5H8*$@blTl`)FwbB3 z)n=(~bj~3ZQ)d|E$vhEGi{^~g^xHBAcJ7M|OUkj$gBg29ZIedIX7*p9GLu7FmMs{j zz=Pf7hG@ZqHCS>*ZR$;6q$%!!OaC0bk#u}lAiJ^g-)X77M|b4R!?=?n%FGZe_OOMY zX8ZD1hbF@{vKc3rGF7?3vR`ISC?Y+76jqu|*Bz-c5Y9T*hu_XMs*S%>MAXTg;+zFl z@oUc+1ltcUarR7(QoH&M)YS#j2~Mv*i+?%FPP?6;%gZ%zY~ua|np<7;;6hy2)9pUU z1bDte2z*GhJ{9A+Ptz3E$9rHB&Q^+hBMo+n$aC$k{Rf3%AKC%|v9 zf9oSNErhOLt?@ym{*!l{q%m0X-y6?sD73&aIYp2~?PH}B`hdMvx+EU#`PFkva!v06 z|5WXi(5r0BTCb-Ju>CBK1pbFIhhYG1vGRimkn5FIN}u1LSQ^fKJMj{Va~o48X+$}j z+2n^&3J*FMxM96tbzvbHxl>NYfz?vqd#L#vs0Q=KpDCUqetpOAk{d|aB{L2meZ3%# zHGMYMbyDNa}Of_f+H!yy_thCiNw=AEqcWs0sdX z9$yjP4aC;=5_E9HG@AuP7QM*?hXYCv#aza?EIJOLE-j*{+7lx*H-XtdnQXCefBZ1T zFo4ro&2QFMw^xJ0V|syG9rVhbdal;|rb}zqCzTtNVv_3gnQU=YZ2`Z*e8_h__|@hM zeA;oJs0b$!&`I{Mr$enE1O_)eS1I;rvJy{=Jn4>6w9~P!DK9$X&fTU#q zv_JV(N;`*Wjx2jTN&L>3C9b8qgA-rhjGE#qa!goDnfhWc&qhh5 z_&ewn7kxc@KzVGP%a!y=Af_FeihnC=U3_XU!bn0K$Vd5O`TVd#DdBiAnMOxE-422i zeWz&jr{T5uCGgpBDkeh5HD+e-Q8WXYvF@m$z4qW&yUqQ1-t}3Ih>swcdwSp8vWRre z8B-oBoF3Omt3>u6S6yc6`F=em16BPi&d0H%X|FTJs9vRV@e2t`!(As@4<@t?S4n+J zXB8%}wePnye-Wrjgk5V)*MKqHF`Wb!3XkS%ll zE#zH2a5gD=R2Kw=MvZ+2(#drs8^m-dZ;-1`JecZY_0H%HS zF%;l+L11b1*->X(l5(Eet7s<@2aDeAn<9ErY>Iaq>IRmlH{sp2Gsr1eQTeRdAghFze^mGQo@Y<8P52tgRB#7Y6!*X z@AAd^m$|=O#x|?u;giK|p7!bpdg6|;NjKr+=0VxRE>m-vhLsf>MjhPw8WHVP3iw^7 zDPd7ssX`YEBT#cS5m?aGg&vR4cThS(IT(3eD08c7uCip#A;LG`-l?X8u7TnqYX<69 zf(onW$}slkVa?5r9&8*4?W}6+5Qs+%L9p%`X)F>msa+#`6M2(qLk4~@ z^zzRptcCt3F<1Z>8kpg{CfWNeJ~h+>&aEmqioJ4n|GELX?U(wXQN}K%{FebUS0sN! zJmpq|y}BTFq`Pkg8aggz6u<+0yMzj;amJsn-@9a~ZyXTK`P4*?rRziHEyY$vw3_Z| z$V7#hfc%?F8g8wKi?bPZqXSi;*Ck9AP-{m%Wp7_^c+EPtncC+npQ0x{z|64T)^tlU z^eckVP+Dt<3|hq?WCC=peAOZ@p3)Z$(%H9C4FX=X*)2TYwur4S(;Sgz6$#823gHrzOhrcgr*#PUl0mgKP`_IThz32$VaI|J~tg1b;x<}wj=c6;i21DSx?t%5-;lmIyv7f<9aY@JDxgqFo#Kz} zs~7D~(k+E!%QQuesk|=rDCPGQDZZn>!}?yTY`XxDwS67ORb4{8RuoRck6ecsMr~*#^r; zcV6yw~f!;2> zV1Ow7fMLItM*hU8$M-mjE0B1K#2*QXwK3lv9_9 z+I5uyK#7r3NoJay0N_`a&-1C4jJy_jstc=w?4r<`K9{uFaL#_K1g2aWbN|~~)@+sM z7I{$#Mw_-aGF8XdpRg}JmvU+azosZuX0R0$RjEsY!qMi7l(B$;l`0*v(7nqj5OXFbd0F(9UrdEA?hGNo zXhdkZn-}DkUaX(_Sf>B6(d;nksU)jf?fGc#)20y2=y~A z3BE~lNZ9&T#Ou}=&j*5$uo0qt0x%Z@SJ%M5lt3b=h!TbPsJD7i#DLeX@`8sJo7XG2 zH$M9_5`vN*^uvJQV7OG$8fmmeZp1+x->gF%s%YR&(*`ThLy$@l{wJbh^}VpeX+9NaKc1L3~|QNh&*EPSo}%7fsTwgDSOMEa(vSSLx} zEz{}@T%Y<~#e}uD9;s(tC-9}GQak300gbwvH8v<~d^Of}io=-(rtE%2m{Kte#B!v~ zIWu#%A%HJXdpt;E3h?C?d6brN5K2LOtmR>2MuX6 zSY%PLyE1nLXA2sO%#4SggO`c~FXMIEL!g7^B}n^_lJ3 z9492vxT+HzNxvl=-UK8r@?-Un41omqceohuG!>fECA=LnKN*Yb2JW(D?n>}UQ~3sF z+GCNtzyZJ+I;3R+${wzH5dDGr<||JE__Ea8MT`AMTNf^R4bstLYK(&{@mDRgLP&V7 z3=wAbVwD(i~yNn{5CpBnId3suN+3mL zkxn?jl-&+F@khn!BvSnPfRQuq!0NJP6Bufkva8+7C~tt*N=Y+=P>%xNHeLCO~Y zT~hnCBr3!|ba#nGPEMi2@ab<$y-i&Z(w}{QDFlVoN;KV|emYpWb#%Iz5jx^SQ)m=o z^FPFm>PSbxEprr{$&49q(A9g{zteg~f50#6=-OZd_tMw^j<nAuI+%qp zU0HD5o!P{~)%k0c%X4&%h%jBj!x-^H!XN-)xw_46-VcVM|CDxmbfJ(%=Ww=wU(!=h{}BL$#2(Uwz`>*#%<{so8Bs69 z6_zq}Um)|izP&Qgg4l&~ELc4?Lvm~ zfz~9f9W|SxCYe@}Y*L&I-EBZ`T17=~T88#CJmrv50!|c6{XN^uoQeNO7NyzfOz7w7 zN&w%KL-^y9;l-%0IZo`Wo+T(;t9%;)U%y`ZJ1kWQtOk2P2G@{lUf8XrG_3GBGN-a9 zHzpV>pAK|Ei`vFo-qd-#^uveYP4af(FCpD-m;4VbFOyE5geeX4-AzwB{>4PucLw`S z_zuTnp04yiupDiV3=y>2YJcP`Ekdhkl~%n3Lnd7CFC8|+>sZ?_ELLMT+uSVQW~eE> zvZ;63s&`ROEj0vYe=Kdj(HvTR)VOsI%d>rN!udegdsPHOpg?zy5An%44Rsj?Uo2H- z1=UepI$2!!4OaGqp!~J0FpKQ3YIu_1r1XXt?vgpXrs{K!b@5RFXJUN)lBszr27`3b z*SOZ%=@RE}WuqeOJ!)xI%P`-8%eNH_CQ)UxHh#E9iwUplt#8>1Ep3M$@TQnl=*Q6| zcLh642qChYdyMWa!V(tIb%$1g_NX3%2bp>Cx?9_ImYD*Dv~iXMHl0t$i(mhsEqskW>UA zsx5tdS&IBA;5&-7JKhAeIM;!^m&5;s^d~n*orH3DdlnF-;AEKZD!NIRHJ=cFU@Y)* z9tQ^R(R+wumMO`dGv$6DSQ`;LoUxA|@kaDII(+9pK4_TX9G>`qi4K+oIp1#HS;g}M zTlb`q+!qaWpluvAlWF3LxYvJ&=^C-^G7iR`RutbZx)97uDrW*Mut2FR+k6H}VmZ@A z;;P!oT!Mw9C&~@CgyRA$t|?+7TzLmZu2i*(ms{=LuS*w-H1lymarTNQIot9=nH$~S zS6emcLEfaB?KQb`-%AYe#DL>p`NnFTCE6a~wX(&m*kkwTbY!nEhuL?Wq)CbgEB)3VKC*3N23+zfyp)XN$ z#Fbm%(d04HO|RvBeOWzk0BGH#o&WX^!rD3I$5$L3HYw=OxL!YG6iFbok#r_IQl&HV zJk*4VUtPN0u~#7|S0Vd>J9m*xVG%cH;jka3LQUOC{uQ{L2jkUTVQZUYb_$BsdFl_~)T=}3zGB72nlrq>#I7D+q%A5Yj#U#~K(+scM|jkW)IQ21gnV!%3n-$7yE*lFz2Q6uJI3R9+&_Bv6* zx7;4yGg*-V^sIvz2CXV^`vD#7g^dQVS0#zlGE3bjsXE1e_z10UTHu}LOLSq5Q8Ur_ zn~8|bt3bHPgB#*m*zZPL&K14~TRB!q0A!MYdwGS3Jv?k+t?H#SK#JN)&lXQz3;4(N zIoD7@TH8+M;)*Q4O(le>qh=cDAzd9s(4T3aS@x?qhxA}&R+KE`o9`H}HXVF$Q|AQS zMlU@_{Dbm8!(O~Ir&^RPa+E~)JHJ%e-P=S}pnmsjdeac4hZW_?x^@QFMUYax5^xKW zPP0+fPyHa%N%nA37b$H_BT29BfHdKPnMGqP>^VB;lu+}7M@dM&$ibl`5c+qIr`0Mt z?)aB44F1mTc7{f`o={UmoZn`D+(ja~-qOhFf{?M(kR@E?dV2=w_->nitVk!<20mr^ zOZL$iUZ~cdFe;S#XpaET_Qxq`y_A$hKi)g~SOd&$YickX~xgpi$Pg!RO zDZhOhh+9g`5u%|-tm+hX1Sqf(Vu#&hv$=Y>L(woVvD2O;BdQoflk)!LCFUc2sWvWG z8F3iuRl;80&}#KbNw0yLnNl=;6)jbpbRnRVcPt&3I2bihw-ww+joVsac!m`^ALx|% zk8B&%hOq?`4+XumGS*hK10w?u+0k}+3^NJ|bh-B>uDJGE8FVkzDek3FxythV?Fwu6 z4~ti$@hI$)^MYTZU~9XRu!-$yUESn02pE1C39K*+O0? zBP?{E<7&6LJ5IUx30>F=mIXTt)(K2W1(7=q#{rDz$j2=!zZZheZ+}We?hAzXM;w=l zKyQ-pbYuS}RPITff`ud&DzUOk1(LLpkC;$Wt7r zzZA_#57MX2(YrLeSgQp41Yk>fxBLv8MlBg+K}f8yo5O0)<|?i>P-*B* z6ev2S`0S)=sywzur~1a1jThZQvg(QL21-Oon(Um)H#Qb0;MI0=y#Zpfxp6(ehG6cq z9|_(%@upDiz7=rgP~)|6X_u(%0{5JGmGzJIUE@QrH!?$vm!764ttpdJPFMJTWc_LF9}&pd zSb@9{xJvB@?ulW;)pUjIlDvadGoGFIUZJsuv|4usEPLt?)ueRU<1~$LK;zh)?fPk3 z-U0S#7JvsDY=T*`uK=zqL0{|ZDbbhv zxhPA1kLG*@_OiY|K00Kx$my|QJ7-M$U2k^n5?0`wCe=mN407UPDNn88h0j-SCUjqjF@`lx*!qh2V78}2ub0guUSVU+b6bgr25qPp^| z`a3*xU>r>mvq=Z;wEx$)9g-quePpC5&O!!)*ek#yDkaZP{~>YxH&q~q-wBK&DRU87 zXw^1mEfgo8=TXg-hhO6jd&L87G+Jwshvq*Uo{XovR=lzlscgO&;FTS&DbtO!{iSr& z9B%L33m7EQker{J(`)tK zdwhkVEhK9^bEha>2eLb;kYN<>3pp$78QFD>>Z`w&)IubB<_6Wn#QwcW*$^w5`5NV1 zpJ;_~3%z$Pd$vm61pJ3|K5EVBNMbXNDoI(ogQ%hlHcvkEenvqJ_Z;re>U8?zxo>sp z=di`%I`|F4@MO*>Grds~B&MIPo_cc-&U-1BE!Smw!j?xe4z$jhmCnXKz!Bt`RGERjE=%<@=cd^JZ1#%v>FitPcz^+C2hm{oE8C1XM~S*Uj{;!AdjU4YC$TX4^VzwszU*>%`j*skWV9xskdzdGKtU%kJYt zuSF$k80vGueKEmR&;Iv^B30=nVEE`^+}8gUn$$rI6IRWvLXhh`o_dPHAVSc3cYkv4 zp?A-4*ZV9{oVvAJhvWcIc(~aCGES%mv#T}*^ z304ufLqKLc)i}1SZPBbSQ%;N9$2?e^^X|4Wn<`;W`bAu20CE4Le^{T>l!hA9;+E3C z@~`VOw6QeDXrF#!Tn4G-PE)P#d_HDW3fXjDLz8wlq&cVSt8=*dZ_R1ZwyTLsr!$`u zFZP)TE^;XO8~-40zkabl&Fa&aqs4W(_2slKAOPkr3_zA{C*sHU_s^I#UEB`f7@dW4 zno@J4?)@Z)l9)%ZWpP_%>ubTIqp7lFXJMV$R@~K8h)Y>?PKp;VuFxBF;mJnFKAjiG z$0=v|Bvqn9g>v>~7B|M0O(SkgGz{5-b;%HmHE8BV?yWTHKF>C<_|=RD2Vs3{dnx$K zq6ug?1z(O@E;)|ZIS)tM*AsCic8mRd(ChtM;ToLQmnM3wpjsmx)wmMdedF{&Oeslg zPXTF!5v8$EUO{{@y#j)5&g(WJvq6MGT@(hY771_vudBBXilYs-hXVw6CpZLmcM0z9 z?(Xg^fyG@HC%6W84G;p0ySrO(5Axgh-umiReg93>Y)#EP-Tmy$Iektq;CmKFIbmbN z0>^)KhLgt1LF*aTtJP_)5E35HU^D_@A^6{Es`YSHX3xdAMZA;!rYHJ=6&dVEiw zRB}f=?vz_}h(}pP|B^-CRIC3RE_^lDcS6&mx2fYxGij0#lvpwH_K(foE5_+2FC>b!Q4m^z~OWksHX3mTm6o`yZ(I<)Qsr*1IRiKPK z83Sf%q2@{9iU?E}ZWnYm!`F(UZU>DpFnJzU@1Pk4sU?Ohs}%ZimT*SGQJq$W8F9d= z|AK!VO;k!#>vm(=1t+v~CKyxr4~e64=auqy)4GhCU4A*^5wC*R@GIH!)qBfF*L}%1 z2Y*0$+3nr_U&~?7&1W-cfH~HuPn~%G=heKHu-^LR@M87MLz&rMR?SpnpORbkS3OZz z2Ll$7avV+6Sv$Qy_qHt`XAJi&aQ58WW1&~fr1)~$#&*W|j`{_Iz7o@h4y_XAK6Umy zI|Pe$==teG^rMNZO9r7Z?CV28+lp7D(H@m?DXSe{4kjR*wCJXNv^GRiEqL$pe78Wz zbcwx-GSE11U*7GgzCDKoUvy-TuuqI`$Se@{WEky_7`xrCr~G(*l#*%lFgMkSSTqRC zuj&rJfRi(w=juf97G|xWMCL?C{wzy~R6${;ucz%;&Jc+wab@=F6W%n%X{{Ld!)JoISpb+Rlu@U|U*F>K1c-W_+bgsI@i;u zr{*?QtLZ}|D+U$XI>o>gL!H6aU(;sU)a7cH0PLvLS5rvWc2ruzX|A*$L)9OGh>+aR zO=1uFKURG)zJ!GvY@tJm@zGZ754eQ$0WEO`94fs|uX^yrH*TqbrlTr-Qf=XrD{zv? zhj6B87%g6~(tE3&)%#3ro|(*=)ehlwG+nMe_;!Ig8;Zx0fcB)XEEaGPD_oQ5@>mP%6%M8sm@N2St$@?J!X2b&;H`hVz zC9@2b#E%bA(zBEB%Or|@p)8m6Ppf@3Kdblca|8B1?bIQVsP9>j zJ5O4K9Mg|ob_j%z`fPY%QK(yBmzCE5{)S3TAAF484M>vkJ%EqGh@6+!zu*YjIXHVtQo9eBp&@H`GrSAp+$r&B=yJ_{D(a{nyqG z3aav$Kho~p8H=*+?+$LUEYctW|2|49B_Kzkpxy1aeR}4TX-HGr=nY^bqug{I?w2jG zxPdt&qO!FP-rkx@ZePI*rd%oi!!iVPiMOps3*N4^wB9{#Q_sEQ<{n9(`vmXQ@H4~56iuN?y^yH1P=4N~n z_wLX5S1f(}`aJ{LP3E0;b)h94+ z1}8~Zd`J6((8NicOq_6fk=taN%UF?sZGNz!pCCd_fIbAWg(_v{iyyC;e$&ub(AX7q#4nE4m7r>U~M9k@LcAWdt5P;;D{4 zRQN`US-}gw@qad2k>b8vQ%jt*J~wGMtk)ah`kSE7F*E34mkC0pv)Pl1RkfOj&B#I^ zYV(mq@sEf2eB(qY=tASwRbkNtz;`->$TS3#USq6Nct{EHH;)$Ag_``eHA*<*QhgiC z@Psg|`iznS@MhO|kmHRqk8M=S3QNLYQ{3aX5xk%7tF4NCJ^>bS8phuT!Jz3>c zEFNzg3>G!g!Ap~vsV{7VT1eak@u?{p|HYdGlYJ%JA&RsDmPA1DiEu2mzV%tGn0T;A zdCttg28VpUNO~x;P=7l;R7!!6a`c{wpUwxakkyF&p$@=}R>+#MxXQM~9yGt#6h_KX zouA``@%__4G%_Sj;>B%I|3Damc=qEA!PwT|o(v(ODs9l|Xu)4GO=%2g67d!$bwUyv z*_E;6h)P&M&Z+`a=evbxIjd^7`%^E6Jniu7oK)svNXB=(Dy zn_A;WYKIA2XSGE)4Y3^(R?5tbK$Ll}CDa0~d)@W5D!g)U;-6gE)R&A7cqZ339WnY= zD7-Tla-@WZr}T%S%sQT5>})ecndbtz0p-v8OhWOJsZ zEQlTqV0^G-UPOK)%hl{NOLx4;>o_z2 zWzgA_J5lm)u?;5K{v8PLK1c^5TB>GGa3xNfXm#25a8smVmHnn;mNAH+bRanaU^k)} zSL4VYiu~Qrj9|2_n}6`n8mAz|Hg{XG*W+uS#Gh(YWe!|AgBizaD9X=yW(=1MP2*Gq zO1-&NGj=*{lX5uJHa91q!J(MK6htPu$(a$SkeD((|=p`4#t#O-ef3Z`^1|O zTT>+T%9^`RBId|Gc93h($ldUj18VP982M3s)zV4UzoAYwHMsKG>>yr7<_5Y@_I+5p!|pF zu3G?nxDRw{;1iY8No!mOxZmW5>=;C_&*|U5eq}P0rYJlcc?WHc8U&>R*;BXEwreD* zO}cbT&B%o`bK^q!6WSV+_Rw^i>X5Kn@g6ODLzCoQ_@c95ri^I-mY-O&;>!Z-Cgcv1 z&0bLv8?VxqImWG&5k2Bk%N0wps$Ni(DN*Ni4!cf}iTnIJirhv4mz&8K?vCD+z~3r^ zbO!%I3>i5Ov6d4Wr{|vS1>#3i*(OronyD7ZobhqQ4sKmB8DgI{4h{Zf;?&K`8 z#Cbcno|^isO*&~h9nrn!(f77IQN>iKR|Q*->emW{)}m8cK*6CZtWern(qv49L^*cN`45mTu`Oz8+X!VHTY5 zB8be0hpi)kb5nUlvHe+{x*fL#>SnDUtHy_7wXBXY( z&{QwH0Y%=pHU_uG>1A=|feKQ5c6uZgAU>pPN7ffT$(;>?7h-3>mPal=R;IR56#KY@!BHo8yg) zk#EPkosRutZBCe1ZJF+?NY_Ph=t(v9dqEd1S$FQ^YOUw_p!O1 z(Bm_ZOrd(1Yh(i5{Cs)YO{9Tp4OB0no27+ZY!LM-RgSTeVEX>rqx#D$he1&rmw$?F zKB?}xGe`rq<5>&^)Z4pJ=>9kq6eNvT{4F8MsHZ}xlbC#pGXHvM?f@P2?-StLhHTf< z;1=bmhvcZZc_V=p73COETT()zYWXJUQzc2L&(=nR3X34qT+nzE28nRQTvowbV_8ez z_IkV-$fS~2_uiV1%Z)vxOWWL43A_fATUSu{Q@*y3yPi)c5!ki=X}BOMu?Ypf4rk_F zv*%wLO)M=1;2#@#Y5Mb;3m?FAyaE@m+%&7?=Io541%>gexQjaje(e5_r|0jZ1x(1n zj$FCYlK!}coAaV;GXAYDMy{<|NF{R_ygp;CPtYl*2BgL-c%THtreRW)XB~zS6F|pz z^;V@AE(`|jp2|%p&G(?~05DMCAqDZit#M3o-{eki^3G+d(pfQ>e+0k?f7*tz;7SZ! zq4|0M%!@cdk}jM+*&T(7v_~lo)7?_sUS!rF*=JZx>0UZy6;ENq!3vP65 z4H$Fi8FkPrc+nPEzv;Nq0Rq`%N`jGz&ZFQ43$G>?iL-?^P5CtYNG`_9AO94-kUaEo zat2E(y-CulEIS0lrvpkx2N|)Kl2?c}Q!)v5N)rfK+_rS{U*y-$cbEh2Tog6_bGm>Nn03}S5j zg1_XzUfIW6(U1u@=iU9&@uKZx(Lh%VFsFtsN-O=oka5o%h`gh4X%Dfl!I*3B{9pT; zQFz#VXee8d9TMzU7+ESjP$Cj+DimY+S7cZys2dO!3hW@%G3Xct_6iEVZ3PwfD-_H> zlkBz+G}vk=n8#iV&`%7=(KJX86LyG%uXQe9)8UMAW=m%}ptjhE>Lv2=?_It^bF8GO z4@eLTwjSyLbdCj^3DEgWyOSa}aYS|#|0<@ZTpBJ18(cs`TmNL`x6$Kqd7(m=w`r03 zCE%?5(a|7G`NqbLV2b*`J{BOz08lml*IyY4l?+(~xmeMD`x^~ckz2i1o56}9ans2# z6Gk^iWAR2J`jkaWnfOF8aVY)^Wn8GUuJ)UX@HCyTsIo!_@@Y7Su)HRarM;x>lW zn|^vnJVNYZ?I`{}4u{ljr3bCFk{iqEwVQ;eis{}0I3%JoCL%KbJX6pT~-`GiG+6Q3&)*moSNRZgqB| zM_4;l01JDPH~-hGO2hOh;Ku)#HBGk8nV7(fFLXZ`i zy!(I^0EeUDePd|rU2$vMgY*ymx7p@zL^Vb=DQv2ExVL-Tz&{3v$LuI5_usi->EqV= z;>6hJt6T;c^#0I1`Y}m}LktFK#PVf}RZROPzF0L_-|!>PUZu5I;94?ede+ZHs)x7J zb&*a7V$!Jm^+`>%d%Xk8{|1-lejzc)5AlRS2Mp2~tp$~ZS9-#@%NVVL!S+0{%VBI_ zj5>+)e0V^b)YjTfc8^>sa1*Vp$!9ZI zhG8>K`Epv&r_A%_VZ#Ty6S3YEp&A&Jg8hcv|7>UN?xDR*X6Z&qvqKd(;A!E;De_?p zc=&>AuZ1^R(wkc!U#zI@7Fae$36W^B$+;3+>XIuE3K!de-OPF>&5V^TwV#nz;Iy*v zM)X?L^PD;9P!FC@!uP&U;4_kKwMzi6>5ODgY%5165|q;U2` zFm4qiif%@u5z21|ZntHywWD;%zme7h%x{t#hL>x)Rb_864#0@T{m~*>{7j^BkE3G_ zC;LqF2sdTfLM-al(%wvzxl^~zidtMtH$f#@^rBS+LZdEnD$G`V%mgny5I_Y_jxnk< zR;R6rjoP9=v~pJUyK@N$X{ZRLG;~OieUs;t!OOYbae@2$yzQFN!-_(avU6FAe-T3or^=kA$S#i!(WM|Qo%}$vlzrplSBKxV zw+5|~=^I6YXQ4QwSxR`q)>0j zC4_W*W8wO_j7EP8=93K?Tw7Es+iUe49?`z3XEnz(RNuU~&cHl9LI&dk_8r|1OA5&5 zm-#(xzf;lU(l4KdmaUzti{5z+GVo+z>eAL4{{m|y@=`hk?$&+G{jT+E7vQ(mshMJL zJ58!%FeJ|R@m7=8y|oJuC)RjeG-ag<0^oahMBz_3cVtXcRx? zFd;5yGFM<>m|vbH4?n4qv!nE=u>V!>NbA%pl8a9Y(`$`(Xt7Lc8OttI+x)i|) zy#CbNpY*I}X7#|60GE<}gZ8aIg*GOtdX+hkGazwH^!WwfjPO~N&ZsR3-FEHD_?5#( z*LoCZf1Iv`m%Zs7uq=(xp(Kuip)))Hd}$x~{rOc8VNR(M2J%V(j*%`<$DD0(;h@H% zLA%$k)Im#U{X1SbAqT>BV?(P;-lfgeB2Gy8Zb0&dAUNSHA0mFRcyH?J>4wz>RN#?ze*U2HQSa=KUxHgRq#e2T10{NPaGg7N(Annjz?@(W# zn2aH_>^^&iQ%(u_y_Don$F6)wP|!3J<)*GTjaM9jj|&l&tGc5?2E!{5Ab5ccwTNCp z>*?m|O*v>J;z!Ekpw*=HF#83za*4M7V~KAWYbSu#OO|j4cNn3K@PSXVt0+I)DS{UY$*OSy|H|$CnP<0aJEM|X(9TVB3s+Nr68aIL zp#KCAELeO?GnP(qQ0qNmL$$ar#Rb(o>3nF;t+|wKwBm>%lr8PH;(VvGK4_Q{iSDw5 zUAQchxRi3wc-a-pZYxK_Zf|LYXutK34RydRSFAl;Wb3DfVSt%JAElFkSd5F8epH;Z z>3|u0KfVD~GXWNgPJKr4jeER`Twyj*cwB(hx*Qs1tGE?4cM2y%uc~lFq!pti-dXab zUfNIQm>LTf12SQqIR{MsPI(-0{2@zxls0z!!V3s#K}h-19^cU`JDKV@H2opA?3S9sxuQ?%>hFoe26PST0qdxr4hr~1+usjnAqXZDjD5+Dlu1nk>VBS7lc zjfXeH3T6+41^Ys`zh1ic!7y+@eFC}3pZ46YKmU8we9fN;XK%Idy|7Cdmm!*&@QX{1 z{Vm}J3r?4GhKPgSWr_BULfr&M@|6Wb({n6{O>mx^%!%ylNu-EL^ zlzk{_*yE^{pdls$s_qCO4i%XOgUIzC4%?k*Z@O<=CV;xKrNR#YcB)Yc)WS;2u-RQw z(DBdqzZ4R8+N#2ElH=U9i%cr2;eMt9OWz_J?l$CV&3H-tS$oGW53@%dwn~@8F&#IUM@S;b(j0+3;b&D1RI#x!dTmRinJww=t zxxwjr^osgMG2jhxB#Es;XegdopN|%q-xD*d3r;nx@oc)QwdTic<2x=a1p$=TJ>AlD z;TsD^)s|A^W((1@!5?&T{#Z1 zSr+cc4Z6KQi9WkzBedBNb5RQ9KC>e+;`RgOZgGA{1KdNdtTFiIVr$gHPuS4Tqa6^) zehf6%3rB5M3q3rSEj#g+hwpc4ZgN*yX_^=M$C1iIjOsWM?j5&>B#I>~y7qslBPu{r zd9N`Ty>_H$pUE91@=bgVA5FO3%kO;YX8^$?#_7c(5!*|eA?u0&5bk~!@0`K1E!`qGJ3b~A@7*vGu^L@w@$mJZ=-r($ zPr{n)-IS>I+?mb1#QmBd!97N3w7*e@^N+zq>CnB3CF;kNNKiC&`v$q^r!O{bfnqJ) z4&T=qySCrHR%kz%wZM|gV@Ek-YCcXnwJk+P0OS?o-ia7cS-wSHyikXzrO$=z6z=~n zfgNh~Bv^{%CLle`HJ=UL-SpS&(O%q65p0CM7X4V53s|YHK+$s*{iw}0j=ZP~Upx8f zdDV-(rBGbRC>u(|`yqD)@BXGRjrlhpUP3mZG-9A1 zfV>$WzI>qBRhf;Z%;7eqx_nKVLL4$mm9&ZQn2lrfl>Pp)Cx}fxDsbYbTG0!njCdNF z>T2I%YA8d|)W}ee8t(%M`SWNpdzq-xIN;&Ir}d zzr4Z_tm@>x zY*WXvJQgORGJ&Lvhrxvkp1ra^q@p_>x)dP;&$EJ!q%)u*K`p+TCk0>_ixz@3H?Z+9 zX%RB)bKi|X;7DR?&u@ znv_%~s_$?q2&7K@N6IS#3Xa$`qVp#P2k%-+4Ri>?W&mNY^ZEP;C?v>N)XhSx2o;@r zL{KgN?Zmbil0Kb)h~2O0#FBl;qYirRJx6Is3m!iu;8petk;vP?6+#bso=ek^gupnh z^*|-S)3YBg?UX^~Mi_W_(41_?gj}KQh+&ED3oZ&|SqFeqE#zDXMHSK`rRbqm9^~0< z+@8M6z2g6EWKL{xAGC@_oDu)|-$XvYE$s56Mku=jkd9ofdp!Y@CpD0zDyvO}@x+#n zBRWSq;h03i1n<%GwT;hW4Er7(BA*lPCDDjU+je{8jG1G>&OvCfcX~gO@=+Pth$DFc zNB5R*MJFu-yD|q4OH3o>d*;cJ1hO(P@5>VSZ6|sHd4#%~0^;K;EkM zwBk4ELWhAZZ%I_Z8qBs}T+xAaiox?3*VxSw<0Khl2ruK2XD(7I=6tQ&^SHvfKQE$< z<@cU;&G6fh)S++q3bC=A(!6mb#fWz&T3HV~+~S5+Tr-*EApDiqp5#QRrC8Pq zd-jmTK;5Jp2fDdm#$^t5KWu_D*T}vg@2#M3Gl~sgHMG?j)f!=n>*Q3ZxpE~HO(FBC}aN@#-^|M|BgJ$u$=y`s8MuwGg8N1d3Q zA_tZv*$hD)pP6WC8VH%gD&_a1$Ep0(P0JUwLoYE|zx%i@!D`S zN#HY!-eyOQEXF1A#9XGLO{pGQauPre$Y@h1w3A|}gckqtTU=gfgRMk}+uN1`QT}*M zqbo(B3h3f(2}p%*KS{2vVrG9rWX9_JB`57Vihgre5ycCv6<*Po3*$=n`!WN(&PMvU z78gmk@_Yx9MKDN=V1Md-!ca+T8}dybql~(oqEosK`eTIA*lx1RJOv+$q5`mM#2TK! z4k4$|ta;`bJ$PHeDqn$te8iNSiQ@?g=#*YpHQ!KJU5IBfu4iZmVbrSe$>39&J7ZO6 z@rO076R5_@PUr0Y&3ITqk_CLpMBj*M?{F)RRRBYVqx8?RtSwl+bqj=c9OUkU| zdXG|5!o%zb>t6>$1~nw8&_`$a((;YSGhMaZTFHx>d9jm3aIRSHtAZDH5J}=&MLrl` zS^n(ZiNx!TfG1B9mj{o=t4m7H=@YGrRla;3@42Hy3|2?94i0`zq6bhaRk0+ksff>( zzjU3Xk_Y?uD(|OcjmIo40xxhe9dE~jcbOdn&8}Nrn+Et z!p9-Km*AjHmkNAL6B@P|S^@Ju*N$bfwY-t5K*OE;TW}D@Z~8M5!#{4KTt*acE4`!l z20Z40eK!9Tw;eR;mjRykLXXL)(v0^b1c(0P!B}MFrKu=aXF?HU|7(6lMj)O9-c z=&mmP}u!^w?>=+vjd^Q#*2XMc`^cc%7?o1$+hM(RGIiDItYB39SWshxaP@+fH6&Xa=e`tuR6)%2aJ)vl~;tQyL0`a5E=~N zIRdzK8=04@v0r@MEm4q}WBdPDus4hDzeSn3{HI6o?B0{PkD{|ovK-EJx8(mzg9R8j zG{zkV?i&7Gj=o*;9dwOoV_oo0>~g;INyq`k-AMG3?CXg%{95y&n?$Q;)4T3W&vNRX z@v}&MJ2cUBtdd!x8~UdNIJ;$B=O`S@CV<5^?y{vYxs49O5PN$i|C2!Ko>Do_e)1#S zJ$O2>qWi3WLw1vfEWy^PcH>QU0x=*+X}RpzSko4tQ%QsKxqe7qz3*02eTZY$Z;tbK z2pE%Mt|qMKRUDh_#`;1knxSBlm}c|a#A(09alkWcGV)8pRVWQNz`n*V>E@(cu9C7z zgS$TUIFxlR{#`xkW{Ht7Z#|9_R}*DhQXQoh*AHH|8*i7ELWCYE<+;kAYTX7X_DUMe zI~ICnC)I3Hy&04_{S5O?4HK&hA#{*S$t}k<8Kso_L(6uFXr5cgvwD^GP@j1ja>n-S z`fg;gAjAv~h87I~;<1ES`;XN&lT8$+DweJ!+g<@6fzDVpy1sulq#RS(XCPuN z(?^SWSfixv;dPBPLu#y`WE7$-a$fK`DEr=J-xOdGnrJ{s6X&Q6st>SeGMB)O%)R~c z#iDaOqJO}F=c&{~7(SK8xYBHZW@kA_7LAUafN30W53}o zJ~IJqD_kPbWxS$y9h2+)FEFDL_?f3IixpM}8Y&NT%??YA{{4)+!2{Cq>m0utf7U($ zqUL}VLw+1Na=OVnTxkr{0=aR(@<8!|DmY*Tpk1;-`y8;BP%5B#PFOG$xGkRxHVO)g z2t>;R+W-X%TH%5H162n4!3&E6;cQxXVJo4ba@tG;VE@2i_`6*>Nj}I)g6wvOCxl!I zLEIv+_5if!K)v6?U}$W>WWf<5QzsNHl`}@$tCJ?_T)z+a=vIX+Fqk#Q8vW`HW9#Gm zyS=?V`m8^^lCRnBj~A~2QG7T@r?v^WkKZ^V;n*O(R=1u znvA7?xiGZ8mybOW=rmSFQ=OcjNw9ybyX5F0EC5J-sVn9ym;$5irhiETt0j*w*5B4J z+@uY)?;X5C7v-DkrTKb;OlC={j+rV`=z{WXp5i9Q4G^{5{S#r|#%kFyQ935QO0 z(`rANTDW~mHZ$5o>Dko_w6+ zc`bv9&|u{3SWS`hh2h2wpB)24gc@CmW1Vu*D2j`}7-?A*>y!8)-(2$BK z97eN6O&ZgPnx5K;il3PnmN#KBV-N-DAI-qr_+3>;KJA>xPF)DU?(X!c^koYB^7Q}! z0D{@+2#Hqq+#%FWsCc=hq=C8QQcvTglKH3w0Rs`LoTAj) zo`IO`V^$e?*DoZTduo(C-ll6b|0sQz9(4v%#KNOTr%9) z{&}29j`R~V%)IRBInBdZI;flea^zWzz_+EcK|NWnVcvxsy|s(urpa_gh+#BN54PGJ zp$8wzI6Tg^8|=mdLBo8b07*%6kI3C$p}tBK-pXz`WVd-0=)8CMQ8oSV6{TE@&2K3xdsz_pMB7XL0{B~b8)_D9zb!?~* zLwxoCkA4(YT{e=ZYd}oT7}x23aIfOW*AkvL)RQ zF_308pu{t*_i7LqI8-^LfW5zkaUk-R1?74i*}yaIH%UE~sjrIFxDHi0j3%f1a!eS= zW!9Wby<9xk908QCA}0LwEkIGEk_&#+v2>%IPV@r z-&;pWAaC4*%gB{8rf6}MuPIsei(60PR;!$GjPQ!Y|Za-LlXmkXBpL`7Md4tfioHK}ahl$1KjIUN&Qr8Y*9iq5(BJ?-U z7B7U5W*&#HXGd;oia^XTdZT_y){_vyf&FgFQ--DB;ygOPXgLIhMO6Rte(JFr8n@;o z;d2CWUjSa2)xg}DhoLL$>(!_J^Md2lZiM@$!Phj5E?dlI4Bq5bgeenE1|$xpXex{% zj6l?dYK+Umd;A6?;C7Ab+AVn_~gY+3+r8c~0XMJjkq4Y^XQE2a@ zH2esf76;<<;l|ms&UZgVyGfDKOHP$v`@~Fa>i}H5l{IBF`|4MPL;2M zLvjPAp@%>55YKppaz^U1BH&YD`bTE~^ULXD6v6xwissZogX{$6OO!x{yKq`yd#E(R zwZqq4iE0iNo9NerWYgWHF4-X2Pw^&Ua=!SLD?mC5KmeP8!8C#*a z5Mcb2(2NZ_%A~FdH^gNVCNCzYN=Ymc{ zwIxkXJHPF5WE`7YeC5CTG);~4OZ1;TwrPDkqj94bZXfjYmuy!0d;n1t+Eb*Vc*8Z; z?2jPKmBn5``qr10q1(Dgbzma3#5=k|FSHcP|Gb#AtxLnEKwvam^*|Npa}K-ZPf*lj9uu##y152hcurT_o{ diff --git a/worlds/wargroove2/data/mods/ArchipelagoMod/mod.dat b/worlds/wargroove2/data/mods/ArchipelagoMod/mod.dat index ee816dee3bb3fd488844bcc24526f92690959e73..7265709ce689f2c0d7f90b3538b034fe3e2d9ccf 100644 GIT binary patch delta 36 mcmdnOx`lPa3?`Yx)FdNgV-vG9Gjo$Pa}#5G2G#}!C;$My)(6r6 delta 36 mcmdnOx`lPa3?>;fV{Gm@U9XN_j$ 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
      DGKT$L?G- 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 ZDw0yuuB7Dah-nj2(-Mz#6yPQ4Ujd-%th)dJ delta 1608 zcmX9+3s6&M7C!&I|9vDHZ%DGi_#l^1t5DQnkf2mbc=+gwyj7?OA-Tb5$i>_M+8Bec z($?}^F=EmBnr>Z+OB+E!``GU4I(FS1wP-sP7uh<6`j}N&X=~fv?CzO4^UXJB=6v5b z=QN(y{`5Ch3m*^(_+LfS|HmzzjfG#kzc4EI82;3oZ;4Cif82>R=-vnaNPaZ^{J3oQ z_d70Szn|FBxTdcz;^$W{ewF;nz|oga{t}_Z1~2*#h-0?>*}e&k!Q_f`|w-$b!S0$b7+7suu4IF-K z%>6Ji*dnDmk~{eJce@S-wiG_6dEs+e=IwjV+e=?5OTNCkv9I+n?D38hEqBdDC1dTI z8=F@fFIz5oZw~F1HQgTl>qJ(nTtiQOZdU*Dn6V*zvhSkHd#9#r*U1b0t3O{Gms8Km zVg+-1U}KH*=GXZ?RpeWjCSSYSdA9GRLt}rh5mghkKa5?9xps1DPRQzx<+gck0X|o9=&5y?*OQ zRAKzbfxXH%WQh`tD|hu66(J%9?_qEMw~%O3PT%MfK#nolmoz`(Nlflu`111UwKP zTl4+qiJR(M882S`^|0Oj=ERx;TsbtduDI)R*_6_=hxyZ;BUJaMf7n(_iW7G~`l0(lV7&OXCxktvLK@FG_1%P& zEcn{4W?tKUrO3a>++eys=stR`xw7ok-lvrjNBzCT=(?d#)VsE4uPjM~_hi4>N6b>n z!%t-ZP=d*6M_M=+xI&#%F#}P)>7=K4V?KaO!fKnNlN{Bq2-ku1&t z#U89t{X4t~V)&JM;9dr$;#I5;V}60)17i*m>&gET`7jwK=o>PNFLFEc zkv=Lc%j_W4lhT0nk$4H{6P5txR*_SnA8-!BgP`G`5^#%PkAr@W14=Mg3Y1#c4Uh?n zQsC+_8}JBLyS2i_kf4ms2i|VaA(0Ny-{}$FXY}({x1E=~qDSavQhf=2f#fQHq@O3X zjV4xEY+^OpCYDZMg9-borMcatR%K)11VpdM>;Y~QQgZ89qkhhMn56iE)J(MkTf~wf z;0i&bxJ|0G0}NZhf`ZT+6@-=^BQ=??%Xz_L0b&8>7&iFl{Z!2S*;wOaSeQnjVFJBQ z7;U2K00AQ`F%MaN=CDQYHc&Zs5ox@`YHMVJx*!!D*hf>_k#b2bL7xV_;WSKQ4q#1$ zj%BB^cp2yyE(4l7hL!0_;d7?oO4n9CTdeglFclH^$#h<d9TyqUBa;J*$5{ z9SD{p*rgD$cqtI=KpdfFiH^gR!70s0XazP_3$|hfJqwr~$}%II=s-DQe}#G~tR9cm zwn-|pR&RvdjPRhyC4WSFMcyOWJT*=)AO3s3(j(Tm?eEKuNb~p%t2-P|ih**c+aY>v zQFP|U8joNAd>-qkOMs*ePP-u1c!T{N)V$dpm~+ESz0M7&z$?}X9uf3uBH;N}XBFrf K1i4{Nfd2sRZw+Yx diff --git a/worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp b/worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp index fc3f4aa5813d6cdd25040a6c8bbfc495854e8bbb..f10c2e0ecb83be423a02cbc8fabea69a8caf4eb1 100644 GIT binary patch literal 221008 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
      W-jgbir8Q1% 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

      gp$uzmg#uw3eVMqt`rYmWJFoFV!T1`Z24q!``XL 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¯yc4%x>rW!Wt_N#0OMq(aR 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 diff --git a/worlds/wargroove2/levels/Ancient_Discoveries.json b/worlds/wargroove2/levels/Ancient_Discoveries.json new file mode 100644 index 000000000000..64cdf46e7ccf --- /dev/null +++ b/worlds/wargroove2/levels/Ancient_Discoveries.json @@ -0,0 +1 @@ +{"Map_Tile_8_17":{"terrain":"abyss"}, "Map_Tile_15_12":{"terrain":"abyss"}, "Map_Tile_26_7":{"terrain":"abyss"}, "Map_Tile_2_10":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_22_6":{"terrain":"abyss"}, "Map_Tile_10_13":{"terrain":"abyss"}, "Map_Tile_28_10":{"terrain":"abyss"}, "Map_Tile_29_2":{"terrain":"abyss"}, "Map_Tile_11_5":{"terrain":"abyss"}, "Map_Tile_24_14":{"terrain":"abyss"}, "Map_Tile_26_5":{"terrain":"abyss"}, "Map_Tile_14_2":{"terrain":"abyss"}, "Map_Tile_17_7":{"terrain":"abyss"}, "Map_Tile_24_17":{"terrain":"abyss"}, "Map_Tile_27_19":{"terrain":"abyss"}, "Map_Tile_18_16":{"terrain":"abyss"}, "Map_Tile_9_5":{"terrain":"abyss"}, "Map_Tile_18_4":{"terrain":"abyss"}, "Map_Tile_8_6":{"terrain":"abyss"}, "Map_Tile_1_18":{"terrain":"abyss"}, "Map_Tile_2_14":{"terrain":"abyss"}, "Map_Tile_17_9":{"terrain":"abyss"}, "Map_Tile_18_18":{"terrain":"abyss"}, "Map_Tile_25_16":{"terrain":"abyss"}, "Map_Tile_18_9":{"terrain":"abyss"}, "Map_Tile_15_2":{"terrain":"abyss"}, "Map_Tile_6_16":{"terrain":"abyss"}, "Map_Tile_8_16":{"terrain":"abyss"}, "Map_Tile_6_2":{"terrain":"abyss"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_17_6":{"terrain":"abyss"}, "Map_Tile_10_10":{"terrain":"abyss"}, "Map_Tile_12_16":{"terrain":"abyss"}, "Map_Tile_3_2":{"terrain":"abyss"}, "Map_Tile_11_18":{"terrain":"abyss"}, "Player_2":{"recruit_knight":false, "recruit_soldier":true, "recruit_frog":true, "recruit_travelboat":false, "recruit_dog":true, "recruit_griffin_walking":true, "recruit_caravel":true, "team":1, "recruit_giant":false, "recruit_harpoonship":true, "recruit_mage":true, "recruit_balloon":false, "gold":100, "recruit_kraken":false, "recruit_wagon":false, "recruit_ballista":false, "recruit_turtle":true, "recruit_warship":false, "recruit_archer":true, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_harpy":false, "recruit_dragon":false, "recruit_thief":true, "recruit_witch":true, "recruit_trebuchet":false}, "Map_Tile_10_14":{"terrain":"abyss"}, "Map_Tile_23_11":{"terrain":"abyss"}, "Map_Tile_19_3":{"terrain":"abyss"}, "Map_Tile_25_19":{"terrain":"abyss"}, "Map_Tile_21_4":{"terrain":"abyss"}, "Map_Tile_1_8":{"terrain":"abyss"}, "Map_Tile_5_7":{"terrain":"abyss"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_17_19":{"terrain":"abyss"}, "Map_Tile_29_3":{"terrain":"abyss"}, "Map_Tile_28_16":{"terrain":"abyss"}, "Map_Tile_12_19":{"terrain":"abyss"}, "Map_Tile_16_6":{"terrain":"abyss"}, "Map_Tile_14_12":{"terrain":"abyss"}, "Map_Tile_4_14":{"terrain":"abyss"}, "Map_Tile_22_13":{"terrain":"abyss"}, "Map_Tile_15_15":{"terrain":"abyss"}, "Map_Tile_3_3":{"terrain":"abyss"}, "Map_Tile_13_14":{"terrain":"abyss"}, "Map_Tile_17_17":{"terrain":"abyss"}, "Map_Tile_18_6":{"terrain":"abyss"}, "Map_Tile_5_16":{"terrain":"abyss"}, "Map_Tile_16_7":{"terrain":"abyss"}, "Map_Tile_28_0":{"terrain":"abyss"}, "Map_Tile_3_7":{"terrain":"abyss"}, "Map_Tile_14_18":{"terrain":"abyss"}, "Map_Tile_20_16":{"terrain":"abyss"}, "Map_Tile_7_16":{"terrain":"abyss"}, "Map_Tile_13_2":{"terrain":"abyss"}, "Map_Tile_0_4":{"terrain":"abyss"}, "Map_Tile_19_6":{"terrain":"abyss"}, "Map_Tile_14_5":{"terrain":"abyss"}, "Map_Tile_13_18":{"terrain":"abyss"}, "Map_Tile_15_18":{"terrain":"abyss"}, "Map_Tile_5_14":{"terrain":"abyss"}, "Map_Tile_29_0":{"terrain":"abyss"}, "Map_Tile_9_13":{"terrain":"abyss"}, "Map_Tile_11_19":{"terrain":"abyss"}, "Map_Tile_19_10":{"terrain":"abyss"}, "Map_Tile_27_18":{"terrain":"abyss"}, "Map_Tile_24_16":{"terrain":"abyss"}, "Map_Tile_26_4":{"terrain":"abyss"}, "Map_Tile_29_16":{"terrain":"abyss"}, "Map_Tile_17_3":{"terrain":"abyss"}, "Map_Tile_20_17":{"terrain":"abyss"}, "Map_Tile_20_7":{"terrain":"abyss"}, "Map_Tile_28_13":{"terrain":"abyss"}, "Map_Tile_6_12":{"terrain":"abyss"}, "Map_Tile_10_19":{"terrain":"abyss"}, "Map_Tile_17_0":{"terrain":"abyss"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"abyss"}, "Map_Tile_16_17":{"terrain":"abyss"}, "Map_Tile_12_3":{"terrain":"abyss"}, "Map_Tile_27_17":{"terrain":"abyss"}, "Map_Tile_3_15":{"terrain":"abyss"}, "Map_Tile_9_11":{"terrain":"abyss"}, "Map_Tile_21_2":{"terrain":"abyss"}, "Map_Tile_11_3":{"terrain":"abyss"}, "Map_Tile_6_14":{"terrain":"abyss"}, "Map_Tile_1_4":{"terrain":"abyss"}, "Map_Tile_5_13":{"terrain":"abyss"}, "Map_Tile_9_17":{"terrain":"abyss"}, "Map_Tile_29_7":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"abyss"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_24_6":{"terrain":"abyss"}, "Map_Tile_1_15":{"terrain":"abyss"}, "Map_Tile_17_1":{"terrain":"abyss"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_18_0":{"terrain":"abyss"}, "Map_Tile_25_14":{"terrain":"abyss"}, "Map_Tile_24_5":{"terrain":"abyss"}, "Map_Name":"Ancient Discoveries", "Map_Tile_14_19":{"terrain":"abyss"}, "Map_Tile_5_18":{"terrain":"abyss"}, "Map_Tile_29_1":{"terrain":"abyss"}, "Map_Tile_0_17":{"terrain":"abyss"}, "Map_Tile_5_5":{"terrain":"abyss"}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_10_12":{"terrain":"abyss"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"abyss"}, "Map_Tile_2_9":{"terrain":"abyss"}, "Counters":{}, "Map_Tile_3_0":{"terrain":"abyss"}, "Map_Tile_8_2":{"terrain":"abyss"}, "Map_Tile_5_6":{"terrain":"abyss"}, "Map_Tile_7_11":{"terrain":"abyss"}, "Map_Tile_22_0":{"terrain":"abyss"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"abyss"}, "Map_Tile_27_1":{"terrain":"abyss"}, "Map_Tile_9_4":{"terrain":"abyss"}, "Map_Tile_21_11":{"terrain":"abyss"}, "Map_Tile_1_16":{"terrain":"abyss"}, "Map_Tile_4_13":{"terrain":"abyss"}, "Map_Tile_4_0":{"terrain":"abyss"}, "Map_Tile_4_12":{"terrain":"abyss"}, "Map_Tile_9_16":{"terrain":"abyss"}, "Map_Tile_13_4":{"terrain":"abyss"}, "Map_Tile_8_1":{"terrain":"abyss"}, "Map_Tile_25_17":{"terrain":"abyss"}, "Map_Tile_20_0":{"terrain":"abyss"}, "Map_Tile_6_19":{"terrain":"abyss"}, "Map_Tile_26_3":{"terrain":"abyss"}, "Map_Tile_19_18":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"abyss"}, "Map_Tile_1_3":{"terrain":"abyss"}, "Map_Tile_27_12":{"terrain":"abyss"}, "Map_Tile_4_15":{"terrain":"abyss"}, "Map_Tile_10_18":{"terrain":"abyss"}, "Map_Tile_29_4":{"terrain":"abyss"}, "Map_Tile_26_2":{"terrain":"abyss"}, "Map_Tile_12_13":{"terrain":"abyss"}, "Map_Tile_24_18":{"terrain":"abyss"}, "Map_Tile_28_11":{"terrain":"abyss"}, "Map_Tile_1_7":{"terrain":"abyss"}, "Map_Tile_22_17":{"terrain":"abyss"}, "Map_Tile_5_15":{"terrain":"abyss"}, "Map_Tile_23_0":{"terrain":"abyss"}, "Map_Tile_20_4":{"terrain":"abyss"}, "Map_Tile_21_6":{"terrain":"abyss"}, "Map_Tile_10_3":{"terrain":"abyss"}, "Map_Tile_1_2":{"terrain":"abyss"}, "Map_Tile_1_11":{"terrain":"abyss"}, "Map_Tile_8_9":{"terrain":"abyss"}, "Map_Tile_14_17":{"terrain":"abyss"}, "Map_Tile_29_17":{"terrain":"abyss"}, "Map_Tile_5_2":{"terrain":"abyss"}, "Map_Tile_4_1":{"terrain":"abyss"}, "Map_Tile_18_14":{"terrain":"abyss"}, "Map_Tile_11_17":{"terrain":"abyss"}, "Map_Tile_22_5":{"terrain":"abyss"}, "Map_Tile_13_5":{"terrain":"abyss"}, "Map_Tile_5_0":{"terrain":"abyss"}, "Map_Tile_0_14":{"terrain":"abyss"}, "Map_Tile_23_3":{"terrain":"abyss"}, "Map_Tile_23_1":{"terrain":"abyss"}, "Map_Tile_19_17":{"terrain":"abyss"}, "Map_Tile_7_17":{"terrain":"abyss"}, "Map_Tile_5_17":{"terrain":"abyss"}, "Map_Tile_18_10":{"terrain":"abyss"}, "Map_Tile_23_7":{"terrain":"abyss"}, "Map_Tile_12_4":{"terrain":"abyss"}, "Map_Tile_21_18":{"terrain":"abyss"}, "Map_Tile_14_16":{"terrain":"abyss"}, "Objectives":["Spawn 3 enemy strongholds.", "Kill an enemy stronghold with a golem.", "Win by eliminating an enemy stronghold."], "Map_Tile_28_9":{"terrain":"abyss"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_23_4":{"terrain":"abyss"}, "Map_Tile_11_9":{"terrain":"abyss"}, "Map_Tile_2_1":{"terrain":"abyss"}, "Map_Tile_27_11":{"terrain":"abyss"}, "Map_Tile_4_8":{"terrain":"abyss"}, "Map_Tile_0_9":{"terrain":"abyss"}, "Map_Tile_7_0":{"terrain":"abyss"}, "Map_Tile_19_16":{"terrain":"abyss"}, "Map_Tile_25_7":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"abyss"}, "Map_Tile_6_3":{"terrain":"abyss"}, "Map_Tile_2_17":{"terrain":"abyss"}, "Map_Tile_10_16":{"terrain":"abyss"}, "Map_Tile_26_10":{"terrain":"abyss"}, "Map_Tile_0_13":{"terrain":"abyss"}, "Map_Tile_20_11":{"terrain":"abyss"}, "Map_Tile_2_13":{"terrain":"abyss"}, "Map_Tile_28_17":{"terrain":"abyss"}, "Map_Tile_13_0":{"terrain":"abyss"}, "Map_Tile_17_4":{"terrain":"abyss"}, "Map_Tile_6_13":{"terrain":"abyss"}, "Map_Tile_29_5":{"terrain":"abyss"}, "Map_Tile_2_12":{"terrain":"abyss"}, "Map_Tile_22_1":{"terrain":"abyss"}, "Map_Tile_4_18":{"terrain":"abyss"}, "Map_Tile_7_8":{"terrain":"abyss"}, "Map_Tile_14_15":{"terrain":"abyss"}, "Map_Tile_12_10":{"terrain":"abyss"}, "Map_Tile_27_8":{"terrain":"abyss"}, "Map_Tile_2_11":{"terrain":"abyss"}, "Map_Tile_2_16":{"terrain":"abyss"}, "Map_Tile_12_7":{"terrain":"abyss"}, "Map_Tile_11_2":{"terrain":"abyss"}, "Map_Tile_7_15":{"terrain":"abyss"}, "Map_Tile_10_0":{"terrain":"abyss"}, "Map_Tile_17_5":{"terrain":"abyss"}, "Map_Tile_21_0":{"terrain":"abyss"}, "Map_Tile_5_4":{"terrain":"abyss"}, "Map_Tile_21_14":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"abyss"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"abyss"}, "Map_Tile_11_1":{"terrain":"abyss"}, "Map_Tile_6_18":{"terrain":"abyss"}, "Map_Tile_17_13":{"terrain":"abyss"}, "Map_Tile_24_11":{"terrain":"abyss"}, "Map_Tile_27_10":{"terrain":"abyss"}, "Map_Tile_20_10":{"terrain":"abyss"}, "Map_Tile_24_1":{"terrain":"abyss"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_24_3":{"terrain":"abyss"}, "Map_Tile_4_6":{"terrain":"abyss"}, "Map_Tile_19_4":{"terrain":"abyss"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"abyss"}, "Map_Tile_0_6":{"terrain":"abyss"}, "Map_Tile_13_13":{"terrain":"abyss"}, "Map_Tile_16_15":{"terrain":"abyss"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_17_12":{"terrain":"abyss"}, "Map_Tile_15_1":{"terrain":"abyss"}, "Map_Tile_16_18":{"terrain":"abyss"}, "Map_Tile_0_12":{"terrain":"abyss"}, "Flags":{}, "Author":"Fly Sniper", "Map_Tile_26_9":{"terrain":"abyss"}, "Map_Tile_0_1":{"terrain":"abyss"}, "Map_Tile_29_14":{"terrain":"abyss"}, "Map_Tile_16_19":{"terrain":"abyss"}, "Map_Tile_7_19":{"terrain":"abyss"}, "Map_Tile_17_8":{"terrain":"abyss"}, "Map_Tile_29_9":{"terrain":"abyss"}, "Map_Tile_27_16":{"terrain":"abyss"}, "Map_Tile_22_18":{"terrain":"abyss"}, "Map_Tile_23_16":{"terrain":"abyss"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_8_14":{"terrain":"abyss"}, "Map_Tile_23_14":{"terrain":"abyss"}, "Map_Tile_0_8":{"terrain":"abyss"}, "Map_Tile_6_8":{"terrain":"abyss"}, "Map_Tile_15_19":{"terrain":"abyss"}, "Map_Tile_11_14":{"terrain":"abyss"}, "Map_Tile_29_15":{"terrain":"abyss"}, "Map_Tile_2_18":{"terrain":"abyss"}, "Map_Tile_1_1":{"terrain":"abyss"}, "Map_Tile_19_2":{"terrain":"abyss"}, "Map_Tile_21_3":{"terrain":"abyss"}, "Map_Tile_7_6":{"terrain":"abyss"}, "Map_Tile_4_2":{"terrain":"abyss"}, "Map_Tile_24_8":{"terrain":"abyss"}, "Map_Tile_21_15":{"terrain":"abyss"}, "Map_Tile_17_14":{"terrain":"abyss"}, "Map_Tile_26_16":{"terrain":"abyss"}, "Map_Tile_10_17":{"terrain":"abyss"}, "Map_Tile_28_2":{"terrain":"abyss"}, "Map_Tile_21_12":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"abyss"}, "Map_Tile_3_4":{"terrain":"abyss"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_10_15":{"terrain":"abyss"}, "Map_Tile_3_1":{"terrain":"abyss"}, "Map_Tile_12_14":{"terrain":"abyss"}, "Map_Tile_24_19":{"terrain":"abyss"}, "Map_Tile_6_9":{"terrain":"abyss"}, "Map_Tile_25_13":{"terrain":"abyss"}, "Map_Tile_4_11":{"terrain":"abyss"}, "Map_Tile_5_11":{"terrain":"abyss"}, "Map_Tile_27_0":{"terrain":"abyss"}, "Map_Tile_25_4":{"terrain":"abyss"}, "Map_Tile_19_7":{"terrain":"abyss"}, "Map_Tile_15_14":{"terrain":"abyss"}, "Map_Tile_29_19":{"terrain":"abyss"}, "Map_Tile_29_12":{"terrain":"abyss"}, "Map_Tile_11_7":{"terrain":"abyss"}, "Map_Tile_8_18":{"terrain":"abyss"}, "Player_1":{"recruit_knight":true, "recruit_soldier":true, "recruit_frog":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_griffin_walking":true, "recruit_caravel":true, "team":0, "recruit_giant":true, "recruit_harpoonship":true, "recruit_mage":true, "recruit_balloon":true, "gold":100, "recruit_kraken":true, "recruit_wagon":true, "recruit_ballista":true, "recruit_turtle":true, "recruit_warship":true, "recruit_archer":true, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_harpy":true, "recruit_dragon":true, "recruit_thief":true, "recruit_witch":true, "recruit_trebuchet":true}, "Map_Tile_16_0":{"terrain":"abyss"}, "Map_Tile_16_4":{"terrain":"abyss"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"abyss"}, "Map_Tile_18_12":{"terrain":"abyss"}, "Map_Tile_20_8":{"terrain":"abyss"}, "Map_Tile_18_11":{"terrain":"abyss"}, "Map_Tile_27_5":{"terrain":"abyss"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"abyss"}, "Map_Tile_2_7":{"terrain":"abyss"}, "Map_Tile_19_14":{"terrain":"abyss"}, "Map_Tile_19_19":{"terrain":"abyss"}, "Map_Tile_19_13":{"terrain":"abyss"}, "Map_Tile_25_12":{"terrain":"abyss"}, "Map_Tile_27_4":{"terrain":"abyss"}, "Map_Tile_26_6":{"terrain":"abyss"}, "Map_Tile_2_3":{"terrain":"abyss"}, "Map_Tile_7_3":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"abyss"}, "Map_Tile_9_0":{"terrain":"abyss"}, "Map_Tile_19_11":{"terrain":"abyss"}, "Map_Tile_5_1":{"terrain":"abyss"}, "Map_Tile_13_7":{"terrain":"abyss"}, "Map_Tile_8_11":{"terrain":"abyss"}, "Map_Tile_13_19":{"terrain":"abyss"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_28_15":{"terrain":"abyss"}, "Map_Tile_29_18":{"terrain":"abyss"}, "Map_Tile_20_3":{"terrain":"abyss"}, "Map_Tile_15_3":{"terrain":"abyss"}, "Map_Tile_26_17":{"terrain":"abyss"}, "Map_Tile_2_4":{"terrain":"abyss"}, "Map_Tile_18_5":{"terrain":"abyss"}, "Map_Tile_10_6":{"terrain":"abyss"}, "Map_Tile_6_15":{"terrain":"abyss"}, "Map_Tile_1_10":{"terrain":"abyss"}, "Map_Tile_4_9":{"terrain":"abyss"}, "Map_Tile_2_19":{"terrain":"abyss"}, "Map_Tile_9_8":{"terrain":"abyss"}, "Map_Tile_11_15":{"terrain":"abyss"}, "Map_Tile_22_14":{"terrain":"abyss"}, "Map_Tile_18_15":{"terrain":"abyss"}, "Map_Tile_15_7":{"terrain":"abyss"}, "Map_Tile_22_12":{"terrain":"abyss"}, "Locations":{"1":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":8, "x":16}, {"y":9, "x":16}, {"y":10, "x":16}, {"y":11, "x":16}, {"y":11, "x":17}, {"y":11, "x":18}, {"y":11, "x":19}, {"y":11, "x":20}, {"y":11, "x":21}, {"y":10, "x":21}, {"y":9, "x":21}, {"y":8, "x":21}, {"y":8, "x":20}, {"y":8, "x":19}, {"y":8, "x":18}, {"y":8, "x":17}, {"y":9, "x":17}, {"y":10, "x":17}, {"y":10, "x":18}, {"y":9, "x":18}, {"y":9, "x":19}, {"y":9, "x":20}, {"y":10, "x":20}, {"y":10, "x":19}, {"y":7, "x":18}, {"y":7, "x":19}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":12, "x":21}, {"y":12, "x":20}, {"y":12, "x":19}, {"y":12, "x":18}, {"y":12, "x":17}, {"y":7, "x":17}], "name":"Challenge 1", "id":1, "centre":{"y":10, "x":19}}, "2":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":10, "x":15}, {"y":10, "x":14}, {"y":9, "x":14}, {"y":9, "x":15}], "name":"Spawn", "id":2, "centre":{"y":10, "x":15}}, "3":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":12, "x":12}, {"y":12, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":12, "x":17}, {"y":13, "x":17}, {"y":14, "x":17}, {"y":15, "x":17}, {"y":15, "x":16}, {"y":15, "x":15}, {"y":15, "x":14}, {"y":15, "x":13}, {"y":15, "x":12}, {"y":14, "x":12}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":13, "x":16}, {"y":14, "x":16}, {"y":14, "x":15}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}], "name":"Challenge 2", "id":3, "centre":{"y":13, "x":15}}, "4":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":11, "x":12}, {"y":10, "x":12}, {"y":9, "x":12}, {"y":8, "x":12}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":10, "x":11}, {"y":11, "x":11}, {"y":12, "x":11}, {"y":7, "x":11}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":8, "x":8}, {"y":9, "x":8}, {"y":10, "x":8}, {"y":11, "x":8}, {"y":12, "x":8}, {"y":12, "x":9}, {"y":12, "x":10}, {"y":11, "x":10}, {"y":10, "x":10}, {"y":9, "x":10}, {"y":8, "x":10}, {"y":8, "x":9}, {"y":9, "x":9}, {"y":10, "x":9}, {"y":11, "x":9}, {"y":8, "x":13}, {"y":9, "x":13}, {"y":10, "x":13}, {"y":11, "x":13}, {"y":7, "x":12}, {"y":12, "x":12}], "name":"Challenge 3", "id":4, "centre":{"y":9, "x":10}}, "5":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":8, "x":16}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":7, "x":13}, {"y":7, "x":12}, {"y":7, "x":14}, {"y":7, "x":15}, {"y":7, "x":16}, {"y":7, "x":17}, {"y":6, "x":17}, {"y":6, "x":16}, {"y":6, "x":15}, {"y":6, "x":14}, {"y":6, "x":13}, {"y":6, "x":12}, {"y":5, "x":17}, {"y":5, "x":12}, {"y":5, "x":13}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":5, "x":16}, {"y":4, "x":17}, {"y":4, "x":16}, {"y":4, "x":15}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":12}], "name":"Challenge 4", "id":5, "centre":{"y":6, "x":15}}, "6":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":13, "x":17}, {"y":12, "x":18}, {"y":14, "x":17}, {"y":15, "x":17}, {"y":15, "x":18}, {"y":14, "x":18}, {"y":13, "x":18}, {"y":13, "x":19}, {"y":12, "x":19}, {"y":12, "x":20}, {"y":12, "x":21}, {"y":12, "x":22}, {"y":12, "x":23}, {"y":13, "x":23}, {"y":14, "x":23}, {"y":14, "x":22}, {"y":15, "x":22}, {"y":15, "x":21}, {"y":15, "x":20}, {"y":15, "x":19}, {"y":14, "x":19}, {"y":13, "x":20}, {"y":13, "x":21}, {"y":13, "x":22}, {"y":14, "x":21}, {"y":14, "x":20}, {"y":15, "x":23}], "name":"Challenge 5", "id":6, "centre":{"y":14, "x":20}}, "7":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":13, "x":12}, {"y":13, "x":11}, {"y":12, "x":11}, {"y":14, "x":11}, {"y":14, "x":12}, {"y":14, "x":10}, {"y":13, "x":10}, {"y":12, "x":10}, {"y":15, "x":11}, {"y":15, "x":12}, {"y":15, "x":10}, {"y":15, "x":9}, {"y":14, "x":9}, {"y":13, "x":9}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":13, "x":8}, {"y":14, "x":8}, {"y":15, "x":8}, {"y":12, "x":7}, {"y":13, "x":7}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":12, "x":6}, {"y":13, "x":6}, {"y":14, "x":6}, {"y":15, "x":6}], "name":"Challenge 6", "id":7, "centre":{"y":14, "x":9}}, "8":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":7, "x":11}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":10}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":4, "x":6}, {"y":5, "x":11}, {"y":4, "x":11}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":4, "x":7}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":5, "x":9}, {"y":5, "x":10}, {"y":5, "x":12}, {"y":4, "x":12}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":7, "x":7}], "name":"Challenge 7", "id":8, "centre":{"y":5, "x":9}}, "9":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":6, "x":17}, {"y":7, "x":18}, {"y":6, "x":18}, {"y":5, "x":18}, {"y":4, "x":18}, {"y":5, "x":19}, {"y":6, "x":19}, {"y":6, "x":20}, {"y":6, "x":21}, {"y":6, "x":22}, {"y":6, "x":23}, {"y":5, "x":23}, {"y":5, "x":22}, {"y":5, "x":21}, {"y":5, "x":20}, {"y":4, "x":19}, {"y":4, "x":20}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":23}, {"y":4, "x":17}, {"y":5, "x":17}, {"y":7, "x":19}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":7, "x":22}], "name":"Challenge 8", "id":9, "centre":{"y":5, "x":20}}, "10":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":7, "x":8}, {"y":8, "x":8}, {"y":9, "x":8}, {"y":10, "x":8}, {"y":11, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":12, "x":0}, {"y":11, "x":0}, {"y":10, "x":0}, {"y":9, "x":0}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":6, "x":1}, {"y":6, "x":2}, {"y":6, "x":3}, {"y":6, "x":4}, {"y":6, "x":5}, {"y":6, "x":6}, {"y":6, "x":7}, {"y":7, "x":7}, {"y":8, "x":7}, {"y":9, "x":7}, {"y":10, "x":7}, {"y":11, "x":7}, {"y":11, "x":6}, {"y":11, "x":5}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":10, "x":1}, {"y":9, "x":1}, {"y":8, "x":1}, {"y":7, "x":1}, {"y":7, "x":2}, {"y":7, "x":3}, {"y":7, "x":4}, {"y":7, "x":5}, {"y":7, "x":6}, {"y":8, "x":6}, {"y":9, "x":6}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":5, "x":2}, {"y":5, "x":1}, {"y":5, "x":0}, {"y":13, "x":5}, {"y":13, "x":4}, {"y":13, "x":3}, {"y":13, "x":2}, {"y":13, "x":1}, {"y":13, "x":0}, {"y":13, "x":6}, {"y":5, "x":6}], "name":"Challenge 9", "id":10, "centre":{"y":9, "x":4}}, "11":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":4, "x":6}, {"y":4, "x":7}, {"y":4, "x":8}, {"y":4, "x":9}, {"y":4, "x":10}, {"y":4, "x":11}, {"y":4, "x":12}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":1, "x":14}, {"y":0, "x":14}, {"y":0, "x":13}, {"y":0, "x":12}, {"y":0, "x":11}, {"y":0, "x":10}, {"y":0, "x":9}, {"y":0, "x":8}, {"y":0, "x":7}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":2, "x":6}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":3, "x":8}, {"y":3, "x":9}, {"y":3, "x":10}, {"y":3, "x":11}, {"y":3, "x":12}, {"y":3, "x":13}, {"y":2, "x":13}, {"y":1, "x":13}, {"y":1, "x":12}, {"y":1, "x":11}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":5}, {"y":4, "x":4}, {"y":4, "x":3}, {"y":4, "x":2}, {"y":4, "x":1}, {"y":4, "x":0}, {"y":3, "x":0}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":1, "x":5}, {"y":2, "x":5}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}, {"y":2, "x":1}, {"y":1, "x":1}, {"y":1, "x":2}, {"y":1, "x":3}, {"y":1, "x":4}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":5, "x":2}, {"y":5, "x":1}, {"y":5, "x":0}], "name":"Challenge 10", "id":11, "centre":{"y":2, "x":7}}, "12":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":4, "x":15}, {"y":3, "x":15}, {"y":2, "x":15}, {"y":1, "x":15}, {"y":0, "x":15}, {"y":0, "x":14}, {"y":1, "x":14}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":16}, {"y":4, "x":17}, {"y":4, "x":18}, {"y":4, "x":19}, {"y":4, "x":20}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":23}, {"y":4, "x":24}, {"y":4, "x":25}, {"y":4, "x":26}, {"y":4, "x":27}, {"y":4, "x":28}, {"y":4, "x":29}, {"y":5, "x":24}, {"y":5, "x":25}, {"y":5, "x":26}, {"y":5, "x":27}, {"y":5, "x":28}, {"y":5, "x":29}, {"y":3, "x":29}, {"y":3, "x":28}, {"y":3, "x":27}, {"y":3, "x":26}, {"y":3, "x":25}, {"y":3, "x":24}, {"y":3, "x":23}, {"y":3, "x":22}, {"y":3, "x":21}, {"y":3, "x":20}, {"y":3, "x":19}, {"y":3, "x":18}, {"y":3, "x":17}, {"y":3, "x":16}, {"y":2, "x":16}, {"y":1, "x":16}, {"y":0, "x":16}, {"y":0, "x":17}, {"y":0, "x":18}, {"y":0, "x":19}, {"y":0, "x":20}, {"y":0, "x":21}, {"y":0, "x":22}, {"y":0, "x":23}, {"y":0, "x":24}, {"y":0, "x":25}, {"y":0, "x":26}, {"y":0, "x":27}, {"y":0, "x":28}, {"y":0, "x":29}, {"y":2, "x":29}, {"y":2, "x":28}, {"y":2, "x":27}, {"y":2, "x":26}, {"y":2, "x":25}, {"y":2, "x":24}, {"y":2, "x":23}, {"y":2, "x":22}, {"y":2, "x":21}, {"y":2, "x":20}, {"y":2, "x":19}, {"y":2, "x":18}, {"y":2, "x":17}, {"y":1, "x":17}, {"y":1, "x":18}, {"y":1, "x":19}, {"y":1, "x":20}, {"y":1, "x":21}, {"y":1, "x":22}, {"y":1, "x":23}, {"y":1, "x":24}, {"y":1, "x":25}, {"y":1, "x":26}, {"y":1, "x":27}, {"y":1, "x":28}, {"y":1, "x":29}], "name":"Challenge 11", "id":12, "centre":{"y":2, "x":22}}, "13":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":7, "x":21}, {"y":8, "x":21}, {"y":9, "x":21}, {"y":10, "x":21}, {"y":11, "x":21}, {"y":12, "x":22}, {"y":6, "x":22}, {"y":5, "x":23}, {"y":5, "x":24}, {"y":5, "x":25}, {"y":5, "x":26}, {"y":5, "x":27}, {"y":5, "x":28}, {"y":5, "x":29}, {"y":13, "x":23}, {"y":13, "x":24}, {"y":13, "x":25}, {"y":13, "x":26}, {"y":13, "x":27}, {"y":13, "x":28}, {"y":13, "x":29}, {"y":12, "x":29}, {"y":12, "x":28}, {"y":12, "x":27}, {"y":12, "x":26}, {"y":12, "x":25}, {"y":12, "x":24}, {"y":12, "x":23}, {"y":11, "x":23}, {"y":11, "x":22}, {"y":10, "x":22}, {"y":9, "x":22}, {"y":8, "x":22}, {"y":7, "x":22}, {"y":6, "x":23}, {"y":7, "x":23}, {"y":8, "x":23}, {"y":9, "x":23}, {"y":10, "x":23}, {"y":10, "x":24}, {"y":9, "x":24}, {"y":8, "x":24}, {"y":7, "x":24}, {"y":7, "x":25}, {"y":6, "x":24}, {"y":11, "x":24}, {"y":10, "x":25}, {"y":9, "x":25}, {"y":8, "x":25}, {"y":6, "x":25}, {"y":11, "x":25}, {"y":8, "x":26}, {"y":9, "x":26}, {"y":10, "x":26}, {"y":11, "x":26}, {"y":7, "x":26}, {"y":6, "x":26}, {"y":8, "x":27}, {"y":9, "x":27}, {"y":10, "x":27}, {"y":10, "x":28}, {"y":9, "x":28}, {"y":8, "x":28}, {"y":7, "x":28}, {"y":6, "x":28}, {"y":11, "x":27}, {"y":7, "x":27}, {"y":6, "x":27}, {"y":11, "x":28}, {"y":11, "x":29}, {"y":10, "x":29}, {"y":9, "x":29}, {"y":8, "x":29}, {"y":7, "x":29}, {"y":6, "x":29}], "name":"Challenge 12", "id":13, "centre":{"y":9, "x":25}}, "14":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":15, "x":14}, {"y":16, "x":14}, {"y":17, "x":14}, {"y":18, "x":14}, {"y":19, "x":14}, {"y":15, "x":13}, {"y":15, "x":12}, {"y":15, "x":11}, {"y":15, "x":10}, {"y":15, "x":9}, {"y":15, "x":8}, {"y":15, "x":7}, {"y":15, "x":6}, {"y":15, "x":5}, {"y":15, "x":4}, {"y":15, "x":3}, {"y":15, "x":2}, {"y":15, "x":1}, {"y":15, "x":0}, {"y":14, "x":5}, {"y":13, "x":5}, {"y":13, "x":4}, {"y":13, "x":3}, {"y":13, "x":2}, {"y":13, "x":1}, {"y":13, "x":0}, {"y":14, "x":0}, {"y":14, "x":1}, {"y":14, "x":2}, {"y":14, "x":3}, {"y":14, "x":4}, {"y":14, "x":6}, {"y":16, "x":13}, {"y":16, "x":12}, {"y":16, "x":11}, {"y":16, "x":10}, {"y":16, "x":9}, {"y":16, "x":8}, {"y":16, "x":7}, {"y":16, "x":6}, {"y":16, "x":5}, {"y":16, "x":4}, {"y":16, "x":3}, {"y":16, "x":2}, {"y":16, "x":1}, {"y":16, "x":0}, {"y":17, "x":0}, {"y":18, "x":0}, {"y":19, "x":0}, {"y":19, "x":1}, {"y":19, "x":2}, {"y":19, "x":3}, {"y":19, "x":4}, {"y":19, "x":5}, {"y":19, "x":6}, {"y":19, "x":7}, {"y":19, "x":8}, {"y":19, "x":9}, {"y":19, "x":10}, {"y":19, "x":11}, {"y":19, "x":12}, {"y":19, "x":13}, {"y":18, "x":13}, {"y":17, "x":13}, {"y":17, "x":12}, {"y":17, "x":11}, {"y":17, "x":10}, {"y":17, "x":9}, {"y":17, "x":8}, {"y":17, "x":7}, {"y":17, "x":6}, {"y":17, "x":5}, {"y":17, "x":4}, {"y":17, "x":3}, {"y":17, "x":2}, {"y":17, "x":1}, {"y":18, "x":1}, {"y":18, "x":2}, {"y":18, "x":3}, {"y":18, "x":4}, {"y":18, "x":5}, {"y":18, "x":6}, {"y":18, "x":7}, {"y":18, "x":8}, {"y":18, "x":9}, {"y":18, "x":10}, {"y":18, "x":11}, {"y":18, "x":12}], "name":"Challenge 13", "id":14, "centre":{"y":16, "x":6}}, "15":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":15, "x":15}, {"y":16, "x":15}, {"y":16, "x":14}, {"y":17, "x":14}, {"y":18, "x":14}, {"y":19, "x":14}, {"y":19, "x":15}, {"y":18, "x":15}, {"y":17, "x":15}, {"y":16, "x":16}, {"y":16, "x":17}, {"y":16, "x":18}, {"y":16, "x":19}, {"y":16, "x":20}, {"y":16, "x":21}, {"y":16, "x":22}, {"y":16, "x":23}, {"y":16, "x":24}, {"y":16, "x":25}, {"y":16, "x":26}, {"y":16, "x":27}, {"y":16, "x":28}, {"y":16, "x":29}, {"y":17, "x":29}, {"y":18, "x":29}, {"y":19, "x":29}, {"y":19, "x":28}, {"y":19, "x":27}, {"y":19, "x":26}, {"y":19, "x":25}, {"y":19, "x":24}, {"y":19, "x":23}, {"y":19, "x":22}, {"y":19, "x":21}, {"y":19, "x":20}, {"y":19, "x":19}, {"y":19, "x":18}, {"y":19, "x":17}, {"y":19, "x":16}, {"y":18, "x":16}, {"y":17, "x":16}, {"y":17, "x":17}, {"y":17, "x":18}, {"y":17, "x":19}, {"y":17, "x":20}, {"y":17, "x":21}, {"y":17, "x":22}, {"y":17, "x":23}, {"y":17, "x":24}, {"y":17, "x":25}, {"y":17, "x":26}, {"y":17, "x":27}, {"y":17, "x":28}, {"y":18, "x":28}, {"y":18, "x":18}, {"y":18, "x":17}, {"y":18, "x":19}, {"y":18, "x":20}, {"y":18, "x":21}, {"y":18, "x":22}, {"y":18, "x":23}, {"y":18, "x":24}, {"y":18, "x":25}, {"y":18, "x":26}, {"y":18, "x":27}, {"y":15, "x":16}, {"y":15, "x":17}, {"y":15, "x":18}, {"y":15, "x":19}, {"y":15, "x":20}, {"y":15, "x":21}, {"y":15, "x":22}, {"y":15, "x":23}, {"y":15, "x":24}, {"y":15, "x":25}, {"y":15, "x":26}, {"y":15, "x":27}, {"y":15, "x":28}, {"y":15, "x":29}, {"y":14, "x":29}, {"y":14, "x":28}, {"y":14, "x":27}, {"y":14, "x":26}, {"y":14, "x":25}, {"y":14, "x":24}, {"y":14, "x":23}, {"y":13, "x":24}, {"y":13, "x":29}, {"y":13, "x":28}, {"y":13, "x":27}, {"y":13, "x":26}, {"y":13, "x":25}], "name":"Challenge 14", "id":15, "centre":{"y":17, "x":22}}, "0":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":8, "x":13}, {"y":8, "x":14}, {"y":8, "x":15}, {"y":8, "x":16}, {"y":9, "x":16}, {"y":10, "x":16}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":10, "x":13}, {"y":9, "x":13}], "name":"Start", "id":0, "centre":{"y":9, "x":15}}}, "Map_Tile_25_11":{"terrain":"abyss"}, "Map_Tile_28_19":{"terrain":"abyss"}, "Map_Tile_3_17":{"terrain":"abyss"}, "Map_Tile_29_13":{"terrain":"abyss"}, "Map_Tile_27_2":{"terrain":"abyss"}, "Map_Tile_29_11":{"terrain":"abyss"}, "Map_Tile_29_10":{"terrain":"abyss"}, "Map_Tile_22_11":{"terrain":"abyss"}, "Map_Tile_26_0":{"terrain":"abyss"}, "Map_Tile_25_8":{"terrain":"abyss"}, "Map_Tile_14_13":{"terrain":"abyss"}, "Map_Tile_9_18":{"terrain":"abyss"}, "Map_Tile_29_6":{"terrain":"abyss"}, "Map_Tile_26_12":{"terrain":"abyss"}, "Map_Tile_28_18":{"terrain":"abyss"}, "Map_Tile_28_14":{"terrain":"abyss"}, "Map_Tile_28_12":{"terrain":"abyss"}, "Map_Tile_28_8":{"terrain":"abyss"}, "Map_Tile_28_7":{"terrain":"abyss"}, "Map_Tile_28_6":{"terrain":"abyss"}, "Map_Tile_22_9":{"terrain":"abyss"}, "Map_Tile_1_13":{"terrain":"abyss"}, "Map_Tile_28_5":{"terrain":"abyss"}, "Map_Tile_28_4":{"terrain":"abyss"}, "Map_Tile_28_3":{"terrain":"abyss"}, "Map_Tile_9_14":{"terrain":"abyss"}, "Map_Tile_14_7":{"terrain":"abyss"}, "Map_Tile_18_7":{"terrain":"abyss"}, "Map_Tile_12_1":{"terrain":"abyss"}, "Map_Tile_7_7":{"terrain":"abyss"}, "Map_Tile_27_7":{"terrain":"abyss"}, "Map_Tile_27_15":{"terrain":"abyss"}, "Map_Tile_27_14":{"terrain":"abyss"}, "Map_Tile_20_15":{"terrain":"abyss"}, "Map_Tile_0_5":{"terrain":"abyss"}, "Map_Tile_24_2":{"terrain":"abyss"}, "Map_Tile_7_18":{"terrain":"abyss"}, "Map_Tile_21_1":{"terrain":"abyss"}, "Map_Tile_23_8":{"terrain":"abyss"}, "Map_Tile_27_3":{"terrain":"abyss"}, "Map_Tile_19_1":{"terrain":"abyss"}, "Map_Tile_16_3":{"terrain":"abyss"}, "Map_Tile_26_19":{"terrain":"abyss"}, "Map_Tile_16_16":{"terrain":"abyss"}, "Map_Tile_26_15":{"terrain":"abyss"}, "Map_Tile_26_14":{"terrain":"abyss"}, "Map_Tile_3_16":{"terrain":"abyss"}, "Map_Tile_6_1":{"terrain":"abyss"}, "Map_Tile_26_11":{"terrain":"abyss"}, "Map_Tile_10_11":{"terrain":"abyss"}, "Map_Tile_17_11":{"terrain":"abyss"}, "Map_Tile_25_18":{"terrain":"abyss"}, "Map_Tile_24_15":{"terrain":"abyss"}, "Map_Tile_25_15":{"terrain":"abyss"}, "Map_Tile_19_0":{"terrain":"abyss"}, "Map_Tile_25_10":{"terrain":"abyss"}, "Map_Tile_5_19":{"terrain":"abyss"}, "Map_Tile_2_8":{"terrain":"abyss"}, "Map_Tile_8_4":{"terrain":"abyss"}, "Map_Tile_25_9":{"terrain":"abyss"}, "Map_Tile_29_8":{"terrain":"abyss"}, "Map_Tile_3_5":{"terrain":"abyss"}, "Map_Tile_0_15":{"terrain":"abyss"}, "Map_Tile_25_5":{"terrain":"abyss"}, "Map_Tile_5_12":{"terrain":"abyss"}, "Map_Tile_19_9":{"terrain":"abyss"}, "Map_Tile_8_19":{"terrain":"abyss"}, "Map_Tile_2_5":{"terrain":"abyss"}, "Map_Tile_12_18":{"terrain":"abyss"}, "Map_Tile_6_0":{"terrain":"abyss"}, "Map_Tile_15_13":{"terrain":"abyss"}, "Map_Tile_13_6":{"terrain":"abyss"}, "Map_Tile_18_2":{"terrain":"abyss"}, "Map_Tile_25_1":{"terrain":"abyss"}, "Map_Tile_25_0":{"terrain":"abyss"}, "Map_Tile_26_8":{"terrain":"abyss"}, "Map_Tile_16_14":{"terrain":"abyss"}, "Map_Tile_4_16":{"terrain":"abyss"}, "Map_Tile_16_13":{"terrain":"abyss"}, "Map_Tile_16_1":{"terrain":"abyss"}, "Map_Tile_1_0":{"terrain":"abyss"}, "Map_Tile_22_10":{"terrain":"abyss"}, "Map_Tile_1_5":{"terrain":"abyss"}, "Map_Tile_3_8":{"terrain":"abyss"}, "Map_Tile_15_16":{"terrain":"abyss"}, "Map_Tile_6_7":{"terrain":"abyss"}, "Map_Tile_7_1":{"terrain":"abyss"}, "Map_Tile_10_1":{"terrain":"abyss"}, "Map_Tile_24_12":{"terrain":"abyss"}, "Map_Tile_7_5":{"terrain":"abyss"}, "Map_Tile_24_9":{"terrain":"abyss"}, "Map_Tile_8_8":{"terrain":"abyss"}, "Map_Tile_11_6":{"terrain":"abyss"}, "Map_Tile_24_7":{"terrain":"abyss"}, "Map_Tile_27_13":{"terrain":"abyss"}, "Map_Tile_15_6":{"terrain":"abyss"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_13_15":{"terrain":"abyss"}, "Map_Tile_12_5":{"terrain":"abyss"}, "Map_Tile_11_11":{"terrain":"abyss"}, "Map_Tile_24_4":{"terrain":"abyss"}, "Map_Tile_21_5":{"terrain":"abyss"}, "Map_Tile_24_13":{"terrain":"abyss"}, "Map_Tile_25_2":{"terrain":"abyss"}, "Map_Tile_24_0":{"terrain":"abyss"}, "Map_Tile_3_14":{"terrain":"abyss"}, "Map_Tile_0_10":{"terrain":"abyss"}, "Map_Tile_23_18":{"terrain":"abyss"}, "Map_Tile_17_2":{"terrain":"abyss"}, "Map_Tile_14_1":{"terrain":"abyss"}, "Map_Tile_12_9":{"terrain":"abyss"}, "Map_Tile_2_0":{"terrain":"abyss"}, "Map_Tile_13_1":{"terrain":"abyss"}, "Map_Tile_23_15":{"terrain":"abyss"}, "Map_Tile_9_7":{"terrain":"abyss"}, "Map_Tile_12_15":{"terrain":"abyss"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_27_6":{"terrain":"abyss"}, "Map_Tile_9_3":{"terrain":"abyss"}, "Map_Tile_16_12":{"terrain":"abyss"}, "Map_Tile_10_2":{"terrain":"abyss"}, "Map_Tile_23_13":{"terrain":"abyss"}, "Map_Tile_23_12":{"terrain":"abyss"}, "Map_Tile_23_10":{"terrain":"abyss"}, "Map_Tile_20_13":{"terrain":"abyss"}, "Map_Tile_23_9":{"terrain":"abyss"}, "Map_Tile_28_1":{"terrain":"abyss"}, "Map_Tile_10_8":{"terrain":"abyss"}, "Map_Tile_8_13":{"terrain":"abyss"}, "Map_Tile_23_6":{"terrain":"abyss"}, "Map_Tile_1_9":{"terrain":"abyss"}, "Map_Tile_9_19":{"terrain":"abyss"}, "Map_Tile_6_6":{"terrain":"abyss"}, "Map_Tile_1_19":{"terrain":"abyss"}, "Map_Tile_9_15":{"terrain":"abyss"}, "Map_Tile_1_6":{"terrain":"abyss"}, "Map_Tile_4_17":{"terrain":"abyss"}, "Map_Tile_11_4":{"terrain":"abyss"}, "Map_Tile_7_14":{"terrain":"abyss"}, "Map_Tile_13_16":{"terrain":"abyss"}, "Map_Tile_22_19":{"terrain":"abyss"}, "Map_Tile_8_3":{"terrain":"abyss"}, "Map_Tile_12_11":{"terrain":"abyss"}, "Map_Tile_22_16":{"terrain":"abyss"}, "Map_Tile_11_0":{"terrain":"abyss"}, "Map_Size":{"y":20, "x":30}, "Map_Tile_6_4":{"terrain":"abyss"}, "Map_Tile_14_6":{"terrain":"abyss"}, "Map_Tile_2_2":{"terrain":"abyss"}, "Map_Tile_21_7":{"terrain":"abyss"}, "Map_Tile_4_4":{"terrain":"abyss"}, "Map_Tile_22_15":{"terrain":"abyss"}, "Triggers":[{"isIntro":false, "enabled":true, "conditions":{}, "recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"AP: Export", "actions":[{"enabled":true, "id":"ap_export", "parameters":["926328", "Ancient Discoveries", "Fly Sniper", "Spawn 3 enemy strongholds.", "Kill an enemy stronghold with a golem.", "", "Win by eliminating an enemy stronghold."]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_no_units", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}], "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Defeat (Lost Commander)", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}], "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_hq", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}], "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_victory", "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":{}, "recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Generate Map", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["0", "-5", "0", "0", "0", "50", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "2", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "2", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "2", "P1", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "1"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 1", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["1", "-7", "0", "0", "0", "95", "0", "0", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "1", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "1", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "1", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "3"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 2", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["3", "-7", "0", "0", "0", "95", "0", "0", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "3", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "3", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "4"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 3", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["4", "-7", "0", "0", "0", "0", "95", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "4", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "4", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "5"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 4", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["5", "-7", "0", "0", "0", "50", "50", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "5", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["griffin_walking", "5", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "5", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "6"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 5", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["6", "-7", "0", "50", "5", "0", "0", "0", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "6", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["caravel", "6", "P2", "1", "1", "3", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "6", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "6", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "7"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 6", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["7", "-7", "0", "0", "0", "20", "40", "40", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "7", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["archer", "7", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "7", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "8"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 7", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["8", "-7", "0", "0", "0", "60", "40", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "8", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "8", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "8", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "9"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 8", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["9", "-10", "0", "50", "0", "0", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["knight", "9", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["fortified_city", "9", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["griffin_walking", "9", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "10"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 9", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["10", "-13", "0", "0", "50", "10", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "10", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["merman", "10", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["port", "10", "P1", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "11"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 10", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["11", "-13", "0", "0", "0", "50", "0", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "11", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "11", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "11", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "12"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 11", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["12", "-10", "0", "0", "0", "5", "10", "5", "80"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "12", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "12", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "13"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 12", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["13", "-13", "0", "0", "0", "60", "10", "5", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "13", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["knight", "13", "P2", "1", "1", "3", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "14"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 13", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["14", "-13", "0", "50", "5", "20", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "14", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "14", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["giant", "14", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "15"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 14", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["15", "-13", "0", "0", "0", "50", "20", "0", "10"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "15", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "15", "P2", "1", "1", "5", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "15", "P2", "1", "1", "3", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["current"]}], "recurring":"end_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Victorious (253045)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253045"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "2", "2", "hq", "-1"]}], "recurring":"once", "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"Spawn 3 Enemy Strongholds (253046)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253046"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_killed", "parameters":["giant", "P1", "hq", "P2", "-1"]}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Giant kills stronghold (253047)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253047"]}]}], "Map_Tile_27_9":{"terrain":"abyss"}, "Map_Tile_25_6":{"terrain":"abyss"}, "Map_Tile_0_7":{"terrain":"abyss"}, "Map_Tile_22_7":{"terrain":"abyss"}, "Map_Tile_10_7":{"terrain":"abyss"}, "Map_Tile_9_1":{"terrain":"abyss"}, "Map_Tile_13_3":{"terrain":"abyss"}, "Map_Tile_22_4":{"terrain":"abyss"}, "Map_Tile_6_17":{"terrain":"abyss"}, "Map_Tile_22_2":{"terrain":"abyss"}, "Map_Tile_1_17":{"terrain":"abyss"}, "Map_Tile_17_10":{"terrain":"abyss"}, "Map_Tile_23_17":{"terrain":"abyss"}, "Map_Tile_15_0":{"terrain":"abyss"}, "Map_Tile_23_2":{"terrain":"abyss"}, "Map_Tile_26_13":{"terrain":"abyss"}, "Map_Tile_8_5":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"abyss"}, "Map_Tile_21_19":{"terrain":"abyss"}, "Map_Tile_5_9":{"terrain":"abyss"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_12_12":{"terrain":"abyss"}, "Map_Tile_21_17":{"terrain":"abyss"}, "Map_Tile_21_16":{"terrain":"abyss"}, "Map_Tile_23_5":{"terrain":"abyss"}, "Map_Tile_21_13":{"terrain":"abyss"}, "Map_Tile_18_19":{"terrain":"abyss"}, "Map_Tile_6_5":{"terrain":"abyss"}, "Map_Tile_21_10":{"terrain":"abyss"}, "Map_Tile_21_9":{"terrain":"abyss"}, "Map_Tile_21_8":{"terrain":"abyss"}, "Map_Tile_20_19":{"terrain":"abyss"}, "Map_Tile_2_6":{"terrain":"abyss"}, "Map_Tile_18_1":{"terrain":"abyss"}, "Map_Tile_18_8":{"terrain":"abyss"}, "Map_Tile_20_12":{"terrain":"abyss"}, "Map_Tile_8_0":{"terrain":"abyss"}, "Map_Tile_20_18":{"terrain":"abyss"}, "Map_Tile_3_6":{"terrain":"abyss"}, "Map_Tile_20_14":{"terrain":"abyss"}, "Map_Tile_12_2":{"terrain":"abyss"}, "Map_Tile_26_18":{"terrain":"abyss"}, "Map_Tile_8_15":{"terrain":"abyss"}, "Map_Tile_3_11":{"terrain":"abyss"}, "Map_Tile_20_9":{"terrain":"abyss"}, "Map_Tile_3_13":{"terrain":"abyss"}, "Map_Tile_20_6":{"terrain":"abyss"}, "Map_Tile_20_5":{"terrain":"abyss"}, "Map_Tile_11_12":{"terrain":"abyss"}, "Map_Tile_15_17":{"terrain":"abyss"}, "Map_Tile_3_19":{"terrain":"abyss"}, "Map_Tile_0_2":{"terrain":"abyss"}, "Map_Tile_20_2":{"terrain":"abyss"}, "Map_Tile_8_7":{"terrain":"abyss"}, "Map_Tile_11_13":{"terrain":"abyss"}, "Map_Tile_10_5":{"terrain":"abyss"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_12_8":{"terrain":"abyss"}, "Map_Tile_3_18":{"terrain":"abyss"}, "Map_Tile_20_1":{"terrain":"abyss"}, "Map_Tile_0_18":{"terrain":"abyss"}, "Map_Tile_18_17":{"terrain":"abyss"}, "Map_Tile_10_9":{"terrain":"abyss"}, "Map_Tile_13_17":{"terrain":"abyss"}, "Map_Tile_19_15":{"terrain":"abyss"}, "Map_Tile_7_2":{"terrain":"abyss"}, "Map_Tile_9_6":{"terrain":"abyss"}, "Map_Tile_19_8":{"terrain":"abyss"}, "Map_Tile_14_0":{"terrain":"abyss"}, "Map_Tile_19_5":{"terrain":"abyss"}, "Map_Tile_18_3":{"terrain":"abyss"}, "Map_Tile_4_19":{"terrain":"abyss"}, "Map_Tile_26_1":{"terrain":"abyss"}, "Map_Tile_5_3":{"terrain":"abyss"}, "Map_Tile_18_13":{"terrain":"abyss"}, "Map_Tile_8_12":{"terrain":"abyss"}, "Map_Tile_17_18":{"terrain":"abyss"}, "Player_Count":2, "Map_Tile_15_5":{"terrain":"abyss"}, "Map_Tile_17_16":{"terrain":"abyss"}, "Map_Tile_4_5":{"terrain":"abyss"}, "Map_Tile_17_15":{"terrain":"abyss"}, "Map_Tile_19_12":{"terrain":"abyss"}, "Map_Tile_0_11":{"terrain":"abyss"}, "Map_Tile_7_13":{"terrain":"abyss"}, "Map_Tile_14_4":{"terrain":"abyss"}, "Map_Tile_0_19":{"terrain":"abyss"}, "Map_Tile_1_12":{"terrain":"abyss"}, "Map_Tile_0_3":{"terrain":"abyss"}, "Map_Tile_7_4":{"terrain":"abyss"}, "Map_Tile_15_4":{"terrain":"abyss"}, "Map_Tile_5_8":{"terrain":"abyss"}, "Map_Tile_14_14":{"terrain":"abyss"}, "Map_Tile_23_19":{"terrain":"abyss"}, "Map_Tile_13_12":{"terrain":"abyss"}, "Map_Tile_24_10":{"terrain":"abyss"}, "Map_Tile_9_2":{"terrain":"abyss"}, "Map_Tile_12_6":{"terrain":"abyss"}, "Map_Tile_4_7":{"terrain":"abyss"}, "Map_Tile_0_0":{"terrain":"abyss"}, "Map_Tile_0_16":{"terrain":"abyss"}, "Map_Tile_25_3":{"terrain":"abyss"}, "Map_Tile_22_3":{"terrain":"abyss"}, "Map_Tile_12_0":{"terrain":"abyss"}, "Map_Tile_11_16":{"terrain":"abyss"}, "Map_Tile_2_15":{"terrain":"abyss"}, "Map_Tile_3_12":{"terrain":"abyss"}, "Map_Tile_12_17":{"terrain":"abyss"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Majestic_Mountain.json b/worlds/wargroove2/levels/Majestic_Mountain.json new file mode 100644 index 000000000000..f36c7e0d3f44 --- /dev/null +++ b/worlds/wargroove2/levels/Majestic_Mountain.json @@ -0,0 +1 @@ +{"Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"mountain"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"mountain"}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"mountain"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"mountain"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"mountain"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"mountain"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Objectives":["Grab the hiking boots.", "Get a legendary Air Trooper crit.", "Win with standard conditions."], "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"mountain", "item":{"itemId":1, "isConsumable":false, "pos":{"y":2, "x":12}, "type":"hiking_boots", "unitTypeRestriction":{}}}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"mountain"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"mountain"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"plains"}, "Flags":{}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Player_1":{"recruit_frog":true, "recruit_spearman":true, "recruit_archer":true, "recruit_turtle":true, "recruit_wagon":true, "gold":100, "recruit_knight":true, "recruit_witch":true, "recruit_warship":true, "recruit_griffin_walking":true, "recruit_trebuchet":true, "recruit_caravel":true, "team":0, "recruit_kraken":true, "recruit_merman":true, "recruit_travelboat":true, "recruit_soldier":true, "recruit_dragon":true, "recruit_ballista":true, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_dog":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_mage":true}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Size":{"y":15, "x":17}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"mountain"}, "Map_Tile_16_0":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Name":"Majestic Mountain", "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"mountain"}, "Map_Tile_10_6":{"terrain":"mountain"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"mountain"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"mountain"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"mountain"}, "Map_Tile_9_6":{"terrain":"mountain"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"mountain"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"mountain"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Counters":{}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Triggers":[{"actions":[{"enabled":true, "parameters":["98476", "Majestic Mountain", "Fly Sniper", "Grab the hiking boots.", "Get a legendary Air Trooper crit.", "", "Win with standard conditions."], "id":"ap_export"}], "conditions":{}, "recurring":"start_of_match", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"AP: Export"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_defeat_no_units"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_defeat_commander"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_defeat_hq"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_victory"}, {"actions":[{"enabled":true, "parameters":["0", "-10", "0", "0", "0", "50", "4", "2", "0"], "id":"map_randomize"}, {"enabled":true, "parameters":["3", "0", "0", "0", "0", "0", "2", "5", "0"], "id":"position_asymmetric_randomize"}, {"enabled":true, "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["spearman", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["mage", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["spearman", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["mage", "4", "P2", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["frog", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["harpy", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["tower", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["tower", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}], "conditions":{}, "recurring":"start_of_match", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"Generate Map"}, {"actions":[{"enabled":true, "parameters":["253051"], "id":"ap_location_send"}], "conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "recurring":"end_of_match", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"P1 Victorious (253051)"}, {"actions":[{"enabled":true, "parameters":["253052"], "id":"ap_location_send"}], "conditions":[{"enabled":true, "parameters":["current", "*unit", "*item", "-1"], "id":"unit_item_presence"}], "recurring":"once", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"Dog Kills Knight (253052)"}, {"actions":[{"enabled":true, "parameters":["253053"], "id":"ap_location_send"}], "conditions":[{"enabled":true, "parameters":["current", "1", "0", "griffin_walking", "-10"], "id":"unit_presence"}], "recurring":"once", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"P1 Air Trooper crit (253053)"}], "Locations":{"1":{"getArea":null, "name":"P1 Stronghold Location", "positions":[{"y":10, "x":6}, {"y":11, "x":6}, {"y":10, "x":5}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":11, "x":5}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":10, "x":4}, {"y":9, "x":4}, {"y":8, "x":4}], "setArea":null, "interactable":false, "centre":{"y":9, "x":5}, "id":1}, "2":{"getArea":null, "name":"P2 Stronghold Location", "positions":[{"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":9, "x":13}, {"y":8, "x":12}, {"y":9, "x":14}, {"y":8, "x":15}], "setArea":null, "interactable":false, "centre":{"y":8, "x":13}, "id":2}, "3":{"getArea":null, "name":"P1 Starting Zone", "positions":[{"y":0, "x":3}, {"y":1, "x":3}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":1, "x":5}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":1, "x":4}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":7}, {"y":2, "x":6}, {"y":0, "x":7}, {"y":2, "x":7}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":5}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":3, "x":2}, {"y":0, "x":2}, {"y":1, "x":2}, {"y":2, "x":2}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":13, "x":2}, {"y":14, "x":1}, {"y":14, "x":0}, {"y":13, "x":0}, {"y":12, "x":0}, {"y":14, "x":2}], "setArea":null, "interactable":false, "centre":{"y":5, "x":4}, "id":3}, "4":{"getArea":null, "name":"P2 Starting Zone", "positions":[{"y":0, "x":15}, {"y":0, "x":16}, {"y":1, "x":16}, {"y":2, "x":16}, {"y":3, "x":16}, {"y":3, "x":15}, {"y":4, "x":15}, {"y":2, "x":15}, {"y":1, "x":15}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":3, "x":13}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":16}, {"y":14, "x":16}, {"y":14, "x":15}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":13, "x":13}, {"y":12, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":13, "x":16}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}], "setArea":null, "interactable":false, "centre":{"y":8, "x":15}, "id":4}, "0":{"getArea":null, "name":"Land", "positions":[{"y":0, "x":11}, {"y":0, "x":10}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":2, "x":9}, {"y":2, "x":8}, {"y":2, "x":7}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":2, "x":1}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":0, "x":7}, {"y":0, "x":8}, {"y":0, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":1, "x":4}, {"y":1, "x":3}, {"y":1, "x":2}, {"y":1, "x":1}, {"y":3, "x":0}, {"y":4, "x":0}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":9, "x":0}, {"y":10, "x":0}, {"y":11, "x":0}, {"y":12, "x":0}, {"y":13, "x":0}, {"y":14, "x":0}, {"y":14, "x":1}, {"y":14, "x":2}, {"y":14, "x":3}, {"y":14, "x":4}, {"y":14, "x":5}, {"y":14, "x":6}, {"y":14, "x":7}, {"y":14, "x":8}, {"y":14, "x":9}, {"y":14, "x":10}, {"y":14, "x":11}, {"y":14, "x":12}, {"y":14, "x":13}, {"y":14, "x":14}, {"y":14, "x":15}, {"y":14, "x":16}, {"y":13, "x":16}, {"y":12, "x":16}, {"y":11, "x":16}, {"y":10, "x":16}, {"y":9, "x":16}, {"y":8, "x":16}, {"y":7, "x":16}, {"y":6, "x":16}, {"y":5, "x":16}, {"y":4, "x":16}, {"y":3, "x":16}, {"y":2, "x":16}, {"y":1, "x":16}, {"y":0, "x":16}, {"y":0, "x":15}, {"y":1, "x":15}, {"y":2, "x":15}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":3, "x":15}, {"y":3, "x":13}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":4, "x":15}, {"y":5, "x":13}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":5, "x":11}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":6, "x":15}, {"y":7, "x":15}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":7, "x":12}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":13}, {"y":6, "x":14}, {"y":7, "x":11}, {"y":7, "x":10}, {"y":8, "x":10}, {"y":8, "x":9}, {"y":8, "x":8}, {"y":8, "x":7}, {"y":7, "x":7}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":4, "x":8}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":3, "x":6}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}, {"y":4, "x":1}, {"y":5, "x":1}, {"y":6, "x":1}, {"y":7, "x":1}, {"y":8, "x":1}, {"y":9, "x":1}, {"y":10, "x":1}, {"y":11, "x":1}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":13, "x":2}, {"y":13, "x":3}, {"y":13, "x":4}, {"y":13, "x":5}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":13, "x":8}, {"y":13, "x":9}, {"y":13, "x":10}, {"y":13, "x":11}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":12, "x":15}, {"y":11, "x":15}, {"y":10, "x":15}, {"y":9, "x":15}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":8, "x":12}, {"y":8, "x":11}, {"y":9, "x":7}, {"y":9, "x":8}, {"y":9, "x":9}, {"y":9, "x":10}, {"y":9, "x":11}, {"y":9, "x":12}, {"y":9, "x":13}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":12, "x":14}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":10}, {"y":11, "x":10}, {"y":11, "x":11}, {"y":10, "x":12}, {"y":10, "x":13}, {"y":11, "x":13}, {"y":11, "x":12}, {"y":10, "x":11}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":11, "x":9}, {"y":10, "x":10}, {"y":11, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":11, "x":7}, {"y":10, "x":9}, {"y":11, "x":6}, {"y":10, "x":7}, {"y":10, "x":8}, {"y":11, "x":5}, {"y":10, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":10, "x":2}, {"y":10, "x":3}, {"y":10, "x":4}, {"y":10, "x":5}, {"y":12, "x":2}, {"y":9, "x":6}, {"y":9, "x":2}, {"y":9, "x":3}, {"y":9, "x":4}, {"y":9, "x":5}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":6, "x":2}, {"y":5, "x":2}, {"y":4, "x":2}, {"y":4, "x":3}, {"y":4, "x":4}, {"y":4, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":6, "x":3}, {"y":7, "x":3}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":7, "x":4}, {"y":6, "x":4}, {"y":5, "x":5}, {"y":6, "x":5}, {"y":7, "x":5}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":7, "x":6}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":4, "x":7}, {"y":4, "x":6}], "setArea":null, "interactable":false, "centre":{"y":7, "x":8}, "id":0}}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_13_2":{"terrain":"mountain"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Player_2":{"recruit_frog":true, "recruit_spearman":true, "recruit_archer":true, "recruit_turtle":true, "recruit_wagon":false, "gold":100, "recruit_knight":true, "recruit_witch":true, "recruit_warship":false, "recruit_griffin_walking":true, "recruit_trebuchet":false, "recruit_caravel":true, "team":1, "recruit_kraken":false, "recruit_merman":true, "recruit_travelboat":false, "recruit_soldier":true, "recruit_dragon":true, "recruit_ballista":false, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_dog":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_mage":true}, "Author":"Fly Sniper", "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"mountain"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"mountain"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"mountain"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Observation_Isle.json b/worlds/wargroove2/levels/Observation_Isle.json new file mode 100644 index 000000000000..391276690115 --- /dev/null +++ b/worlds/wargroove2/levels/Observation_Isle.json @@ -0,0 +1 @@ +{"Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_7_11":{"terrain":"beach"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"sea"}, "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"sea"}, "Map_Tile_9_1":{"terrain":"sea"}, "Map_Tile_6_16":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_18_8":{"terrain":"sea"}, "Map_Tile_12_9":{"terrain":"sea"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_11_16":{"terrain":"plains"}, "Triggers":[{"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"AP: Export", "enabled":true, "actions":[{"id":"ap_export", "enabled":true, "parameters":["161792", "Observation Isle", "Fly Sniper", "Step on Observation Isle (No Requirements).", "Kill player 3's commander (Requires Walls event).", "", "Win with standard conditions."]}], "conditions":{}}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"$trigger_default_defeat_no_units", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"]}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"$trigger_default_defeat_commander", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["*commander", "current", "-1"]}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"$trigger_default_defeat_hq", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["hq", "current", "-1"]}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Victory (One Opponent Left)", "enabled":true, "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"number_of_opponents", "enabled":true, "parameters":["current", "3", "2"]}]}, {"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Generate Map", "enabled":true, "actions":[{"id":"map_randomize", "enabled":true, "parameters":["0", "-10", "0", "0", "50", "0", "0", "0", "0"]}, {"id":"map_randomize", "enabled":true, "parameters":["5", "-12", "0", "0", "0", "50", "10", "5", "0"]}, {"id":"position_asymmetric_randomize", "enabled":true, "parameters":["3", "0", "0", "0", "0", "0", "0", "8", "0"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["barracks", "3", "P1", "1", "1", "2", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["barracks", "4", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"id":"modify_health", "enabled":true, "parameters":["*structure", "-1", "any", "0", "100"]}], "conditions":{}}, {"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Walls Crumble", "enabled":true, "actions":[{"id":"play_sound_effect", "enabled":true, "parameters":["bellToll", "6"]}, {"id":"screenshake", "enabled":true, "parameters":["3000", "2", "2", "5"]}, {"id":"wait", "enabled":true, "parameters":["3000"]}, {"id":"map_randomize", "enabled":true, "parameters":["6", "-10", "0", "0", "0", "50", "0", "0", "0"]}], "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252024", "0", "1"]}]}, {"recurring":"end_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"P1 Victorious (253048)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253048"]}], "conditions":[{"id":"player_victorious", "enabled":true, "parameters":["current"]}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Become the Watcher (253049)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253049"]}], "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["P1", "1", "0", "*unit_structure", "6"]}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Execute the Watcher (253050)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253050"]}], "conditions":[{"id":"unit_killed", "enabled":true, "parameters":["*unit", "P1", "*commander", "P3", "-1"]}]}], "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_0_15":{"terrain":"bridge"}, "Map_Tile_13_6":{"terrain":"bridge"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"wall"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"sea"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"sea"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_3_16":{"terrain":"bridge"}, "Map_Tile_15_9":{"terrain":"sea"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"sea"}, "Map_Tile_12_2":{"terrain":"beach"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"sea"}, "Map_Tile_18_4":{"terrain":"wall"}, "Map_Tile_4_12":{"terrain":"beach"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"sea"}, "Map_Tile_9_5":{"terrain":"bridge"}, "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_4_15":{"terrain":"beach"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_17_1":{"terrain":"wall"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"beach"}, "Map_Tile_16_0":{"terrain":"wall"}, "Map_Tile_5_11":{"terrain":"beach"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"sea"}, "Author":"Fly Sniper", "Map_Tile_5_13":{"terrain":"beach"}, "Map_Tile_10_10":{"terrain":"sea"}, "Map_Tile_17_3":{"terrain":"wall"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"wall"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"beach"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"sea"}, "Map_Tile_15_15":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"sea"}, "Map_Tile_15_4":{"terrain":"wall"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"bridge"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"wall"}, "Map_Tile_4_13":{"terrain":"beach"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"sea"}, "Player_2":{"team":1, "recruit_thief":true, "recruit_rifleman":false, "recruit_witch":false, "recruit_caravel":true, "recruit_frog":true, "recruit_wagon":false, "recruit_dog":true, "recruit_soldier":true, "recruit_trebuchet":false, "recruit_knight":false, "recruit_spearman":true, "recruit_harpy":false, "recruit_warship":false, "recruit_kraken":false, "recruit_turtle":true, "recruit_merman":false, "recruit_ballista":false, "recruit_harpoonship":false, "recruit_mage":true, "recruit_travelboat":false, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":false, "recruit_giant":false, "recruit_dragon":false, "gold":100}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"wall"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"sea"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_2_16":{"terrain":"bridge"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"sea"}, "Map_Tile_0_14":{"terrain":"bridge"}, "Map_Tile_11_4":{"terrain":"sea"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"sea"}, "Map_Tile_6_13":{"terrain":"plains"}, "Player_3":{"team":2, "recruit_thief":true, "recruit_rifleman":false, "recruit_witch":false, "recruit_caravel":false, "recruit_frog":true, "recruit_wagon":false, "recruit_dog":true, "recruit_soldier":true, "recruit_trebuchet":false, "recruit_knight":false, "recruit_spearman":true, "recruit_harpy":false, "recruit_warship":false, "recruit_kraken":false, "recruit_turtle":true, "recruit_merman":false, "recruit_ballista":false, "recruit_harpoonship":false, "recruit_mage":true, "recruit_travelboat":false, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":false, "recruit_giant":false, "recruit_dragon":false, "gold":0}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"wall"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_9_16":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"sea"}, "Map_Tile_12_10":{"terrain":"sea"}, "Map_Tile_16_9":{"terrain":"sea"}, "Map_Tile_8_1":{"terrain":"sea"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_13_10":{"terrain":"bridge"}, "Map_Tile_14_5":{"terrain":"beach"}, "Map_Tile_7_1":{"terrain":"sea"}, "Map_Tile_16_10":{"terrain":"sea"}, "Map_Tile_10_1":{"terrain":"sea"}, "Map_Tile_5_16":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"bridge"}, "Map_Tile_12_0":{"terrain":"beach"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"bridge"}, "Map_Tile_14_2":{"terrain":"wall"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"sea"}, "Map_Tile_3_12":{"terrain":"plains"}, "Player_1":{"team":0, "recruit_thief":true, "recruit_rifleman":true, "recruit_witch":true, "recruit_caravel":true, "recruit_frog":true, "recruit_wagon":true, "recruit_dog":true, "recruit_soldier":true, "recruit_trebuchet":true, "recruit_knight":true, "recruit_spearman":true, "recruit_harpy":true, "recruit_warship":true, "recruit_kraken":true, "recruit_turtle":true, "recruit_merman":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_mage":true, "recruit_travelboat":true, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":true, "recruit_giant":true, "recruit_dragon":true, "gold":100}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"sea"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"sea"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"sea"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"sea"}, "Map_Tile_11_9":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"sea"}, "Map_Tile_18_7":{"terrain":"sea"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"sea"}, "Map_Tile_13_8":{"terrain":"bridge"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"sea"}, "Map_Tile_18_11":{"terrain":"plains"}, "Locations":{"1":{"interactable":false, "getArea":null, "name":"P1 Stronghold Location", "id":1, "setArea":null, "centre":{"y":9, "x":4}, "positions":[{"y":9, "x":4}, {"y":9, "x":3}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":8, "x":4}, {"y":8, "x":3}, {"y":8, "x":5}, {"y":9, "x":5}]}, "2":{"interactable":false, "getArea":null, "name":"P2 Stronghold Location", "id":2, "setArea":null, "centre":{"y":13, "x":9}, "positions":[{"y":13, "x":10}, {"y":14, "x":9}, {"y":14, "x":8}, {"y":12, "x":10}, {"y":13, "x":9}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":11, "x":10}]}, "3":{"interactable":false, "getArea":null, "name":"P1 Starting Zone", "id":3, "setArea":null, "centre":{"y":1, "x":3}, "positions":[{"y":0, "x":0}, {"y":1, "x":0}, {"y":1, "x":1}, {"y":1, "x":2}, {"y":1, "x":3}, {"y":0, "x":3}, {"y":1, "x":4}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":0, "x":2}, {"y":0, "x":1}, {"y":2, "x":0}, {"y":2, "x":1}, {"y":2, "x":2}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":2, "x":5}, {"y":2, "x":6}]}, "4":{"interactable":false, "getArea":null, "name":"P2 Starting Zone", "id":4, "setArea":null, "centre":{"y":14, "x":12}, "positions":[{"y":12, "x":12}, {"y":11, "x":12}, {"y":11, "x":13}, {"y":12, "x":13}, {"y":13, "x":13}, {"y":14, "x":13}, {"y":15, "x":13}, {"y":16, "x":13}, {"y":16, "x":12}, {"y":15, "x":12}, {"y":14, "x":12}, {"y":13, "x":12}, {"y":11, "x":11}, {"y":12, "x":11}, {"y":13, "x":11}, {"y":14, "x":11}, {"y":15, "x":11}, {"y":16, "x":11}]}, "5":{"interactable":false, "getArea":null, "name":"Land", "id":5, "setArea":null, "centre":{"y":9, "x":7}, "positions":[{"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":1, "x":4}, {"y":1, "x":3}, {"y":1, "x":2}, {"y":1, "x":1}, {"y":1, "x":0}, {"y":2, "x":0}, {"y":3, "x":0}, {"y":4, "x":0}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":9, "x":0}, {"y":10, "x":0}, {"y":11, "x":0}, {"y":12, "x":0}, {"y":12, "x":1}, {"y":12, "x":2}, {"y":12, "x":3}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":10, "x":1}, {"y":9, "x":1}, {"y":8, "x":1}, {"y":7, "x":1}, {"y":6, "x":1}, {"y":5, "x":1}, {"y":4, "x":1}, {"y":3, "x":1}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":3, "x":2}, {"y":4, "x":2}, {"y":5, "x":2}, {"y":6, "x":2}, {"y":7, "x":2}, {"y":9, "x":2}, {"y":10, "x":2}, {"y":10, "x":3}, {"y":10, "x":4}, {"y":11, "x":4}, {"y":10, "x":5}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":7, "x":6}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":4, "x":6}, {"y":3, "x":6}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":4, "x":3}, {"y":4, "x":4}, {"y":5, "x":4}, {"y":6, "x":4}, {"y":7, "x":4}, {"y":7, "x":3}, {"y":5, "x":3}, {"y":4, "x":5}, {"y":5, "x":5}, {"y":6, "x":5}, {"y":6, "x":3}, {"y":7, "x":5}, {"y":14, "x":5}, {"y":14, "x":6}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":12, "x":7}, {"y":12, "x":8}, {"y":11, "x":8}, {"y":11, "x":9}, {"y":12, "x":9}, {"y":13, "x":8}, {"y":15, "x":6}, {"y":15, "x":5}, {"y":16, "x":5}, {"y":16, "x":6}, {"y":16, "x":7}, {"y":16, "x":8}, {"y":16, "x":9}, {"y":16, "x":10}, {"y":16, "x":11}, {"y":16, "x":12}, {"y":16, "x":13}, {"y":16, "x":14}, {"y":16, "x":15}, {"y":16, "x":16}, {"y":16, "x":17}, {"y":16, "x":18}, {"y":15, "x":18}, {"y":14, "x":18}, {"y":13, "x":18}, {"y":12, "x":18}, {"y":11, "x":18}, {"y":11, "x":17}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":11, "x":12}, {"y":11, "x":11}, {"y":11, "x":10}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":15, "x":8}, {"y":14, "x":8}, {"y":14, "x":9}, {"y":13, "x":9}, {"y":12, "x":10}, {"y":12, "x":11}, {"y":14, "x":10}, {"y":15, "x":9}, {"y":12, "x":12}, {"y":13, "x":12}, {"y":14, "x":12}, {"y":15, "x":11}, {"y":15, "x":10}, {"y":13, "x":11}, {"y":13, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":13, "x":15}, {"y":13, "x":14}, {"y":14, "x":14}, {"y":15, "x":13}, {"y":14, "x":13}, {"y":12, "x":13}, {"y":15, "x":12}, {"y":15, "x":14}, {"y":14, "x":15}, {"y":13, "x":16}, {"y":13, "x":17}, {"y":12, "x":17}, {"y":14, "x":16}, {"y":15, "x":16}, {"y":15, "x":17}, {"y":14, "x":17}, {"y":15, "x":15}, {"y":16, "x":0}, {"y":14, "x":11}, {"y":2, "x":1}]}, "6":{"interactable":false, "getArea":null, "name":"Watcher Area", "id":6, "setArea":null, "centre":{"y":3, "x":15}, "positions":[{"y":4, "x":13}, {"y":4, "x":14}, {"y":4, "x":15}, {"y":4, "x":16}, {"y":4, "x":17}, {"y":4, "x":18}, {"y":3, "x":18}, {"y":3, "x":17}, {"y":3, "x":16}, {"y":3, "x":15}, {"y":3, "x":14}, {"y":3, "x":13}, {"y":2, "x":13}, {"y":2, "x":14}, {"y":2, "x":15}, {"y":2, "x":16}, {"y":2, "x":17}, {"y":2, "x":18}, {"y":1, "x":18}, {"y":1, "x":17}, {"y":1, "x":16}, {"y":1, "x":15}, {"y":1, "x":14}, {"y":1, "x":13}, {"y":0, "x":13}, {"y":0, "x":14}, {"y":0, "x":15}, {"y":0, "x":16}, {"y":0, "x":18}, {"y":0, "x":12}, {"y":1, "x":12}, {"y":2, "x":12}, {"y":3, "x":12}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":5, "x":13}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":5, "x":16}, {"y":5, "x":17}, {"y":5, "x":18}]}, "0":{"interactable":false, "getArea":null, "name":"Sea", "id":0, "setArea":null, "centre":{"y":7, "x":11}, "positions":[{"y":10, "x":12}, {"y":10, "x":11}, {"y":10, "x":10}, {"y":10, "x":9}, {"y":10, "x":8}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":9, "x":12}, {"y":9, "x":10}, {"y":9, "x":9}, {"y":9, "x":8}, {"y":9, "x":7}, {"y":8, "x":10}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":7, "x":11}, {"y":6, "x":11}, {"y":4, "x":11}, {"y":3, "x":11}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":4, "x":7}, {"y":3, "x":10}, {"y":3, "x":9}, {"y":8, "x":7}, {"y":8, "x":8}, {"y":7, "x":8}, {"y":7, "x":9}, {"y":7, "x":10}, {"y":8, "x":9}, {"y":6, "x":10}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":7, "x":7}, {"y":6, "x":14}, {"y":6, "x":15}, {"y":6, "x":16}, {"y":6, "x":17}, {"y":6, "x":18}, {"y":7, "x":18}, {"y":8, "x":18}, {"y":9, "x":18}, {"y":10, "x":18}, {"y":10, "x":17}, {"y":10, "x":16}, {"y":10, "x":15}, {"y":10, "x":14}, {"y":7, "x":14}, {"y":7, "x":15}, {"y":7, "x":16}, {"y":7, "x":17}, {"y":9, "x":17}, {"y":9, "x":16}, {"y":9, "x":15}, {"y":9, "x":14}, {"y":8, "x":14}, {"y":8, "x":15}, {"y":8, "x":16}, {"y":1, "x":11}, {"y":0, "x":11}, {"y":0, "x":10}, {"y":0, "x":9}, {"y":0, "x":8}, {"y":0, "x":7}, {"y":1, "x":7}, {"y":1, "x":9}, {"y":1, "x":10}, {"y":1, "x":8}, {"y":14, "x":3}, {"y":14, "x":2}, {"y":14, "x":1}, {"y":15, "x":1}, {"y":15, "x":2}, {"y":15, "x":3}, {"y":8, "x":17}]}}, "Map_Tile_17_15":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"sea"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_18_5":{"terrain":"beach"}, "Map_Tile_18_6":{"terrain":"sea"}, "Map_Tile_18_2":{"terrain":"wall"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_18_1":{"terrain":"wall"}, "Map_Tile_10_6":{"terrain":"sea"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_17_16":{"terrain":"plains"}, "Map_Tile_17_14":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"sea"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Tile_6_12":{"terrain":"beach"}, "Map_Tile_17_12":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"beach"}, "Map_Tile_5_3":{"terrain":"plains"}, "Objectives":["Step on Observation Isle (No Requirements).", "Kill player 3's commander (Requires Walls event).", "Win with standard conditions."], "Map_Tile_17_11":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_17_8":{"unit":{"state":{}, "rangedDamageTakenPercent":100, "tentacled":false, "items":{}, "grooveId":"", "attackerPlayerId":-1, "attackerUnitClass":"", "factionOverride":"", "stunned":false, "health":100, "canChargeGroove":true, "recruitDiscounts":{}, "attackerId":-1, "killedByLosing":false, "canBeAttacked":true, "loadedUnits":{}, "startPos":{"facing":3, "y":8, "x":17}, "merchantDiscounts":{}, "itemDropNumber":0, "recruits":{}, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "unitClass":{"weaponIds":["caravelWeapon"], "verbCostMultiplier":1.0, "loadCapacity":0, "weapons":[{"canMoveAndAttack":true, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"caravelWeapon", "maxRange":1, "canAttackSubmerged":false, "canCounterAttack":true}], "isDamagingParentUnit":false, "inAir":false, "isAttackable":true, "critConditionId":"", "moveRange":5, "movementType":"river_sailing", "transportTags":{}, "isRecruitable":true, "aliasId":"", "isCommander":false, "inWater":true, "canBeActivated":false, "cost":250, "canReinforce":false, "passiveMultiplier":1.5, "maxHealth":100, "isStructure":false, "reinforceMultiplier":1.0, "tags":["caravel", "type.sea.light"], "maxGroove":0, "canAttack":true, "id":"caravel", "resourceCost":1, "canBeCaptured":false, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "itemId":"", "underwater":false, "attachedFlagId":-1, "playerId":1, "unitClassId":"caravel", "hadTurn":false, "blessings":{}, "merchantDiscountMultiplier":0.0, "inTransport":false, "damageTakenPercent":100, "transportedBy":-1, "recruitDiscountMultiplier":0.0, "pos":{"facing":3, "y":8, "x":17}, "id":1, "garrisonClassId":"", "miniGrooveId":"", "grooveCharge":0}, "terrain":"sea"}, "Map_Tile_12_5":{"terrain":"beach"}, "Map_Tile_17_6":{"terrain":"sea"}, "Map_Tile_17_5":{"terrain":"beach"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_18_13":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_17_2":{"terrain":"wall"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"beach"}, "Map_Tile_4_14":{"terrain":"beach"}, "Map_Tile_7_10":{"terrain":"beach"}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"sea"}, "Map_Tile_6_10":{"terrain":"beach"}, "Counters":{}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_1_16":{"terrain":"bridge"}, "Map_Tile_16_2":{"terrain":"wall"}, "Map_Tile_16_4":{"terrain":"wall"}, "Map_Tile_16_3":{"terrain":"wall"}, "Map_Tile_16_5":{"terrain":"beach"}, "Map_Tile_13_2":{"terrain":"wall"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"sea"}, "Map_Tile_15_7":{"terrain":"sea"}, "Map_Tile_9_6":{"terrain":"sea"}, "Map_Tile_15_5":{"terrain":"beach"}, "Map_Tile_14_14":{"terrain":"plains"}, "Flags":{}, "Map_Tile_14_4":{"terrain":"wall"}, "Map_Tile_15_3":{"terrain":"wall"}, "Map_Tile_10_5":{"terrain":"bridge"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"beach"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"sea"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"wall"}, "Map_Tile_2_15":{"terrain":"sea"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"sea"}, "Map_Tile_14_6":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"wall"}, "Map_Tile_14_16":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"beach"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_13_16":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"sea"}, "Map_Tile_17_9":{"terrain":"sea"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_16_16":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"wall"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"wall"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_3_5":{"terrain":"plains"}, "Player_Count":3, "Map_Tile_17_0":{"unit":{"state":{}, "rangedDamageTakenPercent":100, "tentacled":false, "items":{}, "grooveId":"smoke_screen", "attackerPlayerId":-1, "attackerUnitClass":"", "factionOverride":"", "stunned":false, "health":100, "canChargeGroove":true, "recruitDiscounts":{}, "attackerId":-1, "killedByLosing":false, "canBeAttacked":true, "loadedUnits":{}, "startPos":{"facing":3, "y":0, "x":17}, "merchantDiscounts":{}, "itemDropNumber":0, "recruits":{}, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "unitClass":{"weaponIds":["vesperWhip"], "verbCostMultiplier":1.0, "loadCapacity":0, "weapons":[{"canMoveAndAttack":true, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"vesperWhip", "maxRange":1, "canAttackSubmerged":false, "canCounterAttack":true}], "isDamagingParentUnit":false, "inAir":false, "isAttackable":true, "critConditionId":"", "moveRange":4, "movementType":"walking", "transportTags":{}, "isRecruitable":false, "aliasId":"", "isCommander":true, "inWater":false, "canBeActivated":false, "cost":500, "canReinforce":false, "passiveMultiplier":1.0, "maxHealth":100, "isStructure":false, "reinforceMultiplier":1.0, "tags":["commander", "type.ground.light"], "maxGroove":400, "canAttack":true, "id":"commander_vesper", "resourceCost":3, "canBeCaptured":false, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "itemId":"", "underwater":false, "attachedFlagId":-1, "playerId":2, "unitClassId":"commander_vesper", "hadTurn":false, "blessings":{}, "merchantDiscountMultiplier":0.0, "inTransport":false, "damageTakenPercent":100, "transportedBy":-1, "recruitDiscountMultiplier":0.0, "pos":{"facing":3, "y":0, "x":17}, "id":2, "garrisonClassId":"", "miniGrooveId":"", "grooveCharge":0}, "terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_17_4":{"terrain":"wall"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"sea"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"beach"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"sea"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Size":{"y":17, "x":19}, "Map_Tile_11_5":{"terrain":"bridge"}, "Map_Tile_14_3":{"terrain":"wall"}, "Map_Tile_7_13":{"terrain":"sea"}, "Map_Name":"Observation Isle", "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"sea"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_7_15":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_10_7":{"terrain":"sea"}, "Map_Tile_18_0":{"terrain":"wall"}, "Map_Tile_7_5":{"terrain":"bridge"}, "Map_Tile_9_15":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_3_15":{"terrain":"sea"}, "Map_Tile_18_3":{"terrain":"wall"}, "Map_Tile_15_1":{"terrain":"wall"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_1_15":{"terrain":"sea"}, "Map_Tile_4_16":{"terrain":"beach"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Swimming_at_the_Docks.json b/worlds/wargroove2/levels/Swimming_at_the_Docks.json new file mode 100644 index 000000000000..a5d315894524 --- /dev/null +++ b/worlds/wargroove2/levels/Swimming_at_the_Docks.json @@ -0,0 +1 @@ +{"Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"beach"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Name":"Swimming at the Docks", "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"beach"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"beach"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"beach"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"beach"}, "Map_Tile_13_11":{"terrain":"beach"}, "Map_Tile_1_6":{"terrain":"beach"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Objectives":["Kill a knight with a dog.", "Build 2 riverboats.", "Win with standard conditions."], "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"beach"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Flags":{}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Triggers":[{"actions":[{"enabled":true, "parameters":["67190", "Swimming at the Docks", "Fly Sniper", "Kill a knight with a dog.", "Build 2 riverboats.", "", "Win with standard conditions."], "id":"ap_export"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"AP: Export", "isIntro":false, "conditions":{}, "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "isIntro":false, "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "isIntro":false, "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["0", "-3", "0", "75", "25", "0", "0", "0", "0"], "id":"map_randomize"}, {"enabled":true, "parameters":["3", "0", "0", "0", "0", "0", "2", "5", "0"], "id":"position_asymmetric_randomize"}, {"enabled":true, "parameters":["port", "0", "neutral", "1", "1", "4", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["soldier", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["dog", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["spearman", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["mage", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["soldier", "4", "P2", "1", "1", "4", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["frog", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["merman", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["knight", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"Generate Map", "isIntro":false, "conditions":{}, "enabled":true}, {"actions":[{"enabled":true, "parameters":["253042"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"end_of_match", "id":"P1 Victorious (253042)", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["happy", "caesar", "Woof!", "1", "Dog"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["253043"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Dog Kills Knight (253043)", "isIntro":false, "conditions":[{"enabled":true, "parameters":["dog", "current", "knight", "P2", "-1"], "id":"unit_killed"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["253044"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"P1 has 2 Riverboats (253044)", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "4", "2", "caravel", "-1"], "id":"unit_presence"}], "enabled":true}], "Map_Tile_1_1":{"terrain":"beach"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"beach"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"beach"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"beach"}, "Map_Tile_13_7":{"terrain":"beach"}, "Map_Size":{"y":13, "x":15}, "Map_Tile_12_11":{"terrain":"beach"}, "Map_Tile_6_1":{"terrain":"beach"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"beach"}, "Map_Tile_13_6":{"terrain":"beach"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"beach"}, "Map_Tile_8_1":{"terrain":"beach"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"beach"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"beach"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"beach"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"beach"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Locations":{"1":{"setArea":null, "name":"P1 Stronghold Location", "getArea":null, "id":1, "centre":{"y":6, "x":1}, "interactable":false, "positions":[{"y":4, "x":0}, {"y":4, "x":1}, {"y":4, "x":2}, {"y":5, "x":2}, {"y":6, "x":2}, {"y":7, "x":2}, {"y":8, "x":2}, {"y":8, "x":1}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":5, "x":0}, {"y":5, "x":1}, {"y":6, "x":1}, {"y":7, "x":1}]}, "2":{"setArea":null, "name":"P2 Stronghold Location", "getArea":null, "id":2, "centre":{"y":6, "x":13}, "interactable":false, "positions":[{"y":4, "x":12}, {"y":5, "x":12}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":8, "x":13}, {"y":8, "x":14}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":6, "x":13}, {"y":5, "x":13}, {"y":6, "x":14}, {"y":5, "x":14}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":11}, {"y":5, "x":11}, {"y":6, "x":11}, {"y":7, "x":11}, {"y":8, "x":11}]}, "3":{"setArea":null, "name":"P1 Starting Zone", "getArea":null, "id":3, "centre":{"y":2, "x":5}, "interactable":false, "positions":[{"y":0, "x":3}, {"y":1, "x":3}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":1, "x":5}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":1, "x":4}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":7}, {"y":2, "x":6}, {"y":0, "x":7}, {"y":2, "x":7}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":5}, {"y":3, "x":6}, {"y":3, "x":7}]}, "4":{"setArea":null, "name":"P2 Starting Zone", "getArea":null, "id":4, "centre":{"y":10, "x":12}, "interactable":false, "positions":[{"y":8, "x":11}, {"y":8, "x":12}, {"y":8, "x":13}, {"y":8, "x":14}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":14}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":9, "x":10}, {"y":9, "x":11}, {"y":9, "x":12}, {"y":10, "x":12}, {"y":10, "x":11}, {"y":9, "x":13}, {"y":10, "x":13}, {"y":11, "x":12}, {"y":11, "x":11}, {"y":11, "x":10}, {"y":10, "x":10}, {"y":11, "x":9}, {"y":12, "x":7}, {"y":7, "x":11}, {"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}]}, "0":{"setArea":null, "name":"Sea", "getArea":null, "id":0, "centre":{"y":6, "x":7}, "interactable":false, "positions":[{"y":10, "x":12}, {"y":10, "x":11}, {"y":10, "x":10}, {"y":10, "x":9}, {"y":10, "x":8}, {"y":10, "x":7}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":6, "x":2}, {"y":5, "x":2}, {"y":4, "x":2}, {"y":3, "x":2}, {"y":2, "x":2}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":2, "x":6}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":3, "x":12}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":9, "x":12}, {"y":9, "x":10}, {"y":9, "x":9}, {"y":9, "x":8}, {"y":9, "x":7}, {"y":9, "x":6}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":8, "x":10}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":7, "x":11}, {"y":6, "x":11}, {"y":5, "x":11}, {"y":4, "x":11}, {"y":3, "x":11}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":4, "x":7}, {"y":4, "x":6}, {"y":4, "x":5}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":10}, {"y":3, "x":6}, {"y":3, "x":9}, {"y":4, "x":4}, {"y":3, "x":3}, {"y":4, "x":3}, {"y":5, "x":3}, {"y":6, "x":3}, {"y":7, "x":3}, {"y":7, "x":4}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":8, "x":7}, {"y":8, "x":8}, {"y":8, "x":3}, {"y":7, "x":8}, {"y":7, "x":9}, {"y":7, "x":10}, {"y":8, "x":9}, {"y":6, "x":10}, {"y":5, "x":10}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":5, "x":8}, {"y":5, "x":9}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":7, "x":7}, {"y":7, "x":6}, {"y":6, "x":6}, {"y":6, "x":5}, {"y":6, "x":4}, {"y":5, "x":4}, {"y":5, "x":5}, {"y":7, "x":5}, {"y":5, "x":6}]}}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"beach"}, "Map_Tile_1_11":{"terrain":"beach"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"beach"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"beach"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"beach"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"beach"}, "Map_Tile_4_1":{"terrain":"beach"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"beach"}, "Map_Tile_11_11":{"terrain":"beach"}, "Player_1":{"recruit_travelboat":true, "recruit_balloon":true, "recruit_frog":true, "recruit_kraken":true, "recruit_thief":true, "recruit_knight":true, "recruit_archer":true, "recruit_soldier":true, "recruit_ballista":true, "recruit_wagon":true, "recruit_turtle":true, "recruit_dog":true, "recruit_dragon":true, "team":0, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_trebuchet":true, "recruit_mage":true, "recruit_caravel":true, "recruit_witch":true, "gold":100, "recruit_giant":true, "recruit_warship":true, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_griffin_walking":true}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"beach"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"beach"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"beach"}, "Map_Tile_2_11":{"terrain":"beach"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Player_2":{"recruit_travelboat":false, "recruit_balloon":true, "recruit_frog":true, "recruit_kraken":false, "recruit_thief":true, "recruit_knight":true, "recruit_archer":true, "recruit_soldier":true, "recruit_ballista":false, "recruit_wagon":false, "recruit_turtle":true, "recruit_dog":true, "recruit_dragon":true, "team":1, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_trebuchet":false, "recruit_mage":true, "recruit_caravel":true, "recruit_witch":true, "gold":100, "recruit_giant":true, "recruit_warship":false, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_griffin_walking":true}, "Author":"Fly Sniper", "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"beach"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"beach"}, "Map_Tile_1_10":{"terrain":"beach"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"beach"}, "Counters":{}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}} \ No newline at end of file From 118877dd751954712ae86319a2f04674941dc54c Mon Sep 17 00:00:00 2001 From: FlySniper Date: Mon, 14 Oct 2024 18:34:59 -0400 Subject: [PATCH 51/89] Wargroove 2: Adjusted several levels --- worlds/wargroove2/levels/Ancient_Discoveries.json | 2 +- worlds/wargroove2/levels/Majestic_Mountain.json | 2 +- worlds/wargroove2/levels/Observation_Isle.json | 2 +- worlds/wargroove2/levels/Swimming_at_the_Docks.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/wargroove2/levels/Ancient_Discoveries.json b/worlds/wargroove2/levels/Ancient_Discoveries.json index 64cdf46e7ccf..4ae1efc60c7b 100644 --- a/worlds/wargroove2/levels/Ancient_Discoveries.json +++ b/worlds/wargroove2/levels/Ancient_Discoveries.json @@ -1 +1 @@ -{"Map_Tile_8_17":{"terrain":"abyss"}, "Map_Tile_15_12":{"terrain":"abyss"}, "Map_Tile_26_7":{"terrain":"abyss"}, "Map_Tile_2_10":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_22_6":{"terrain":"abyss"}, "Map_Tile_10_13":{"terrain":"abyss"}, "Map_Tile_28_10":{"terrain":"abyss"}, "Map_Tile_29_2":{"terrain":"abyss"}, "Map_Tile_11_5":{"terrain":"abyss"}, "Map_Tile_24_14":{"terrain":"abyss"}, "Map_Tile_26_5":{"terrain":"abyss"}, "Map_Tile_14_2":{"terrain":"abyss"}, "Map_Tile_17_7":{"terrain":"abyss"}, "Map_Tile_24_17":{"terrain":"abyss"}, "Map_Tile_27_19":{"terrain":"abyss"}, "Map_Tile_18_16":{"terrain":"abyss"}, "Map_Tile_9_5":{"terrain":"abyss"}, "Map_Tile_18_4":{"terrain":"abyss"}, "Map_Tile_8_6":{"terrain":"abyss"}, "Map_Tile_1_18":{"terrain":"abyss"}, "Map_Tile_2_14":{"terrain":"abyss"}, "Map_Tile_17_9":{"terrain":"abyss"}, "Map_Tile_18_18":{"terrain":"abyss"}, "Map_Tile_25_16":{"terrain":"abyss"}, "Map_Tile_18_9":{"terrain":"abyss"}, "Map_Tile_15_2":{"terrain":"abyss"}, "Map_Tile_6_16":{"terrain":"abyss"}, "Map_Tile_8_16":{"terrain":"abyss"}, "Map_Tile_6_2":{"terrain":"abyss"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_17_6":{"terrain":"abyss"}, "Map_Tile_10_10":{"terrain":"abyss"}, "Map_Tile_12_16":{"terrain":"abyss"}, "Map_Tile_3_2":{"terrain":"abyss"}, "Map_Tile_11_18":{"terrain":"abyss"}, "Player_2":{"recruit_knight":false, "recruit_soldier":true, "recruit_frog":true, "recruit_travelboat":false, "recruit_dog":true, "recruit_griffin_walking":true, "recruit_caravel":true, "team":1, "recruit_giant":false, "recruit_harpoonship":true, "recruit_mage":true, "recruit_balloon":false, "gold":100, "recruit_kraken":false, "recruit_wagon":false, "recruit_ballista":false, "recruit_turtle":true, "recruit_warship":false, "recruit_archer":true, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_harpy":false, "recruit_dragon":false, "recruit_thief":true, "recruit_witch":true, "recruit_trebuchet":false}, "Map_Tile_10_14":{"terrain":"abyss"}, "Map_Tile_23_11":{"terrain":"abyss"}, "Map_Tile_19_3":{"terrain":"abyss"}, "Map_Tile_25_19":{"terrain":"abyss"}, "Map_Tile_21_4":{"terrain":"abyss"}, "Map_Tile_1_8":{"terrain":"abyss"}, "Map_Tile_5_7":{"terrain":"abyss"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_17_19":{"terrain":"abyss"}, "Map_Tile_29_3":{"terrain":"abyss"}, "Map_Tile_28_16":{"terrain":"abyss"}, "Map_Tile_12_19":{"terrain":"abyss"}, "Map_Tile_16_6":{"terrain":"abyss"}, "Map_Tile_14_12":{"terrain":"abyss"}, "Map_Tile_4_14":{"terrain":"abyss"}, "Map_Tile_22_13":{"terrain":"abyss"}, "Map_Tile_15_15":{"terrain":"abyss"}, "Map_Tile_3_3":{"terrain":"abyss"}, "Map_Tile_13_14":{"terrain":"abyss"}, "Map_Tile_17_17":{"terrain":"abyss"}, "Map_Tile_18_6":{"terrain":"abyss"}, "Map_Tile_5_16":{"terrain":"abyss"}, "Map_Tile_16_7":{"terrain":"abyss"}, "Map_Tile_28_0":{"terrain":"abyss"}, "Map_Tile_3_7":{"terrain":"abyss"}, "Map_Tile_14_18":{"terrain":"abyss"}, "Map_Tile_20_16":{"terrain":"abyss"}, "Map_Tile_7_16":{"terrain":"abyss"}, "Map_Tile_13_2":{"terrain":"abyss"}, "Map_Tile_0_4":{"terrain":"abyss"}, "Map_Tile_19_6":{"terrain":"abyss"}, "Map_Tile_14_5":{"terrain":"abyss"}, "Map_Tile_13_18":{"terrain":"abyss"}, "Map_Tile_15_18":{"terrain":"abyss"}, "Map_Tile_5_14":{"terrain":"abyss"}, "Map_Tile_29_0":{"terrain":"abyss"}, "Map_Tile_9_13":{"terrain":"abyss"}, "Map_Tile_11_19":{"terrain":"abyss"}, "Map_Tile_19_10":{"terrain":"abyss"}, "Map_Tile_27_18":{"terrain":"abyss"}, "Map_Tile_24_16":{"terrain":"abyss"}, "Map_Tile_26_4":{"terrain":"abyss"}, "Map_Tile_29_16":{"terrain":"abyss"}, "Map_Tile_17_3":{"terrain":"abyss"}, "Map_Tile_20_17":{"terrain":"abyss"}, "Map_Tile_20_7":{"terrain":"abyss"}, "Map_Tile_28_13":{"terrain":"abyss"}, "Map_Tile_6_12":{"terrain":"abyss"}, "Map_Tile_10_19":{"terrain":"abyss"}, "Map_Tile_17_0":{"terrain":"abyss"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"abyss"}, "Map_Tile_16_17":{"terrain":"abyss"}, "Map_Tile_12_3":{"terrain":"abyss"}, "Map_Tile_27_17":{"terrain":"abyss"}, "Map_Tile_3_15":{"terrain":"abyss"}, "Map_Tile_9_11":{"terrain":"abyss"}, "Map_Tile_21_2":{"terrain":"abyss"}, "Map_Tile_11_3":{"terrain":"abyss"}, "Map_Tile_6_14":{"terrain":"abyss"}, "Map_Tile_1_4":{"terrain":"abyss"}, "Map_Tile_5_13":{"terrain":"abyss"}, "Map_Tile_9_17":{"terrain":"abyss"}, "Map_Tile_29_7":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"abyss"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_24_6":{"terrain":"abyss"}, "Map_Tile_1_15":{"terrain":"abyss"}, "Map_Tile_17_1":{"terrain":"abyss"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_18_0":{"terrain":"abyss"}, "Map_Tile_25_14":{"terrain":"abyss"}, "Map_Tile_24_5":{"terrain":"abyss"}, "Map_Name":"Ancient Discoveries", "Map_Tile_14_19":{"terrain":"abyss"}, "Map_Tile_5_18":{"terrain":"abyss"}, "Map_Tile_29_1":{"terrain":"abyss"}, "Map_Tile_0_17":{"terrain":"abyss"}, "Map_Tile_5_5":{"terrain":"abyss"}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_10_12":{"terrain":"abyss"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"abyss"}, "Map_Tile_2_9":{"terrain":"abyss"}, "Counters":{}, "Map_Tile_3_0":{"terrain":"abyss"}, "Map_Tile_8_2":{"terrain":"abyss"}, "Map_Tile_5_6":{"terrain":"abyss"}, "Map_Tile_7_11":{"terrain":"abyss"}, "Map_Tile_22_0":{"terrain":"abyss"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"abyss"}, "Map_Tile_27_1":{"terrain":"abyss"}, "Map_Tile_9_4":{"terrain":"abyss"}, "Map_Tile_21_11":{"terrain":"abyss"}, "Map_Tile_1_16":{"terrain":"abyss"}, "Map_Tile_4_13":{"terrain":"abyss"}, "Map_Tile_4_0":{"terrain":"abyss"}, "Map_Tile_4_12":{"terrain":"abyss"}, "Map_Tile_9_16":{"terrain":"abyss"}, "Map_Tile_13_4":{"terrain":"abyss"}, "Map_Tile_8_1":{"terrain":"abyss"}, "Map_Tile_25_17":{"terrain":"abyss"}, "Map_Tile_20_0":{"terrain":"abyss"}, "Map_Tile_6_19":{"terrain":"abyss"}, "Map_Tile_26_3":{"terrain":"abyss"}, "Map_Tile_19_18":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"abyss"}, "Map_Tile_1_3":{"terrain":"abyss"}, "Map_Tile_27_12":{"terrain":"abyss"}, "Map_Tile_4_15":{"terrain":"abyss"}, "Map_Tile_10_18":{"terrain":"abyss"}, "Map_Tile_29_4":{"terrain":"abyss"}, "Map_Tile_26_2":{"terrain":"abyss"}, "Map_Tile_12_13":{"terrain":"abyss"}, "Map_Tile_24_18":{"terrain":"abyss"}, "Map_Tile_28_11":{"terrain":"abyss"}, "Map_Tile_1_7":{"terrain":"abyss"}, "Map_Tile_22_17":{"terrain":"abyss"}, "Map_Tile_5_15":{"terrain":"abyss"}, "Map_Tile_23_0":{"terrain":"abyss"}, "Map_Tile_20_4":{"terrain":"abyss"}, "Map_Tile_21_6":{"terrain":"abyss"}, "Map_Tile_10_3":{"terrain":"abyss"}, "Map_Tile_1_2":{"terrain":"abyss"}, "Map_Tile_1_11":{"terrain":"abyss"}, "Map_Tile_8_9":{"terrain":"abyss"}, "Map_Tile_14_17":{"terrain":"abyss"}, "Map_Tile_29_17":{"terrain":"abyss"}, "Map_Tile_5_2":{"terrain":"abyss"}, "Map_Tile_4_1":{"terrain":"abyss"}, "Map_Tile_18_14":{"terrain":"abyss"}, "Map_Tile_11_17":{"terrain":"abyss"}, "Map_Tile_22_5":{"terrain":"abyss"}, "Map_Tile_13_5":{"terrain":"abyss"}, "Map_Tile_5_0":{"terrain":"abyss"}, "Map_Tile_0_14":{"terrain":"abyss"}, "Map_Tile_23_3":{"terrain":"abyss"}, "Map_Tile_23_1":{"terrain":"abyss"}, "Map_Tile_19_17":{"terrain":"abyss"}, "Map_Tile_7_17":{"terrain":"abyss"}, "Map_Tile_5_17":{"terrain":"abyss"}, "Map_Tile_18_10":{"terrain":"abyss"}, "Map_Tile_23_7":{"terrain":"abyss"}, "Map_Tile_12_4":{"terrain":"abyss"}, "Map_Tile_21_18":{"terrain":"abyss"}, "Map_Tile_14_16":{"terrain":"abyss"}, "Objectives":["Spawn 3 enemy strongholds.", "Kill an enemy stronghold with a golem.", "Win by eliminating an enemy stronghold."], "Map_Tile_28_9":{"terrain":"abyss"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_23_4":{"terrain":"abyss"}, "Map_Tile_11_9":{"terrain":"abyss"}, "Map_Tile_2_1":{"terrain":"abyss"}, "Map_Tile_27_11":{"terrain":"abyss"}, "Map_Tile_4_8":{"terrain":"abyss"}, "Map_Tile_0_9":{"terrain":"abyss"}, "Map_Tile_7_0":{"terrain":"abyss"}, "Map_Tile_19_16":{"terrain":"abyss"}, "Map_Tile_25_7":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"abyss"}, "Map_Tile_6_3":{"terrain":"abyss"}, "Map_Tile_2_17":{"terrain":"abyss"}, "Map_Tile_10_16":{"terrain":"abyss"}, "Map_Tile_26_10":{"terrain":"abyss"}, "Map_Tile_0_13":{"terrain":"abyss"}, "Map_Tile_20_11":{"terrain":"abyss"}, "Map_Tile_2_13":{"terrain":"abyss"}, "Map_Tile_28_17":{"terrain":"abyss"}, "Map_Tile_13_0":{"terrain":"abyss"}, "Map_Tile_17_4":{"terrain":"abyss"}, "Map_Tile_6_13":{"terrain":"abyss"}, "Map_Tile_29_5":{"terrain":"abyss"}, "Map_Tile_2_12":{"terrain":"abyss"}, "Map_Tile_22_1":{"terrain":"abyss"}, "Map_Tile_4_18":{"terrain":"abyss"}, "Map_Tile_7_8":{"terrain":"abyss"}, "Map_Tile_14_15":{"terrain":"abyss"}, "Map_Tile_12_10":{"terrain":"abyss"}, "Map_Tile_27_8":{"terrain":"abyss"}, "Map_Tile_2_11":{"terrain":"abyss"}, "Map_Tile_2_16":{"terrain":"abyss"}, "Map_Tile_12_7":{"terrain":"abyss"}, "Map_Tile_11_2":{"terrain":"abyss"}, "Map_Tile_7_15":{"terrain":"abyss"}, "Map_Tile_10_0":{"terrain":"abyss"}, "Map_Tile_17_5":{"terrain":"abyss"}, "Map_Tile_21_0":{"terrain":"abyss"}, "Map_Tile_5_4":{"terrain":"abyss"}, "Map_Tile_21_14":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"abyss"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"abyss"}, "Map_Tile_11_1":{"terrain":"abyss"}, "Map_Tile_6_18":{"terrain":"abyss"}, "Map_Tile_17_13":{"terrain":"abyss"}, "Map_Tile_24_11":{"terrain":"abyss"}, "Map_Tile_27_10":{"terrain":"abyss"}, "Map_Tile_20_10":{"terrain":"abyss"}, "Map_Tile_24_1":{"terrain":"abyss"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_24_3":{"terrain":"abyss"}, "Map_Tile_4_6":{"terrain":"abyss"}, "Map_Tile_19_4":{"terrain":"abyss"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"abyss"}, "Map_Tile_0_6":{"terrain":"abyss"}, "Map_Tile_13_13":{"terrain":"abyss"}, "Map_Tile_16_15":{"terrain":"abyss"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_17_12":{"terrain":"abyss"}, "Map_Tile_15_1":{"terrain":"abyss"}, "Map_Tile_16_18":{"terrain":"abyss"}, "Map_Tile_0_12":{"terrain":"abyss"}, "Flags":{}, "Author":"Fly Sniper", "Map_Tile_26_9":{"terrain":"abyss"}, "Map_Tile_0_1":{"terrain":"abyss"}, "Map_Tile_29_14":{"terrain":"abyss"}, "Map_Tile_16_19":{"terrain":"abyss"}, "Map_Tile_7_19":{"terrain":"abyss"}, "Map_Tile_17_8":{"terrain":"abyss"}, "Map_Tile_29_9":{"terrain":"abyss"}, "Map_Tile_27_16":{"terrain":"abyss"}, "Map_Tile_22_18":{"terrain":"abyss"}, "Map_Tile_23_16":{"terrain":"abyss"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_8_14":{"terrain":"abyss"}, "Map_Tile_23_14":{"terrain":"abyss"}, "Map_Tile_0_8":{"terrain":"abyss"}, "Map_Tile_6_8":{"terrain":"abyss"}, "Map_Tile_15_19":{"terrain":"abyss"}, "Map_Tile_11_14":{"terrain":"abyss"}, "Map_Tile_29_15":{"terrain":"abyss"}, "Map_Tile_2_18":{"terrain":"abyss"}, "Map_Tile_1_1":{"terrain":"abyss"}, "Map_Tile_19_2":{"terrain":"abyss"}, "Map_Tile_21_3":{"terrain":"abyss"}, "Map_Tile_7_6":{"terrain":"abyss"}, "Map_Tile_4_2":{"terrain":"abyss"}, "Map_Tile_24_8":{"terrain":"abyss"}, "Map_Tile_21_15":{"terrain":"abyss"}, "Map_Tile_17_14":{"terrain":"abyss"}, "Map_Tile_26_16":{"terrain":"abyss"}, "Map_Tile_10_17":{"terrain":"abyss"}, "Map_Tile_28_2":{"terrain":"abyss"}, "Map_Tile_21_12":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"abyss"}, "Map_Tile_3_4":{"terrain":"abyss"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_10_15":{"terrain":"abyss"}, "Map_Tile_3_1":{"terrain":"abyss"}, "Map_Tile_12_14":{"terrain":"abyss"}, "Map_Tile_24_19":{"terrain":"abyss"}, "Map_Tile_6_9":{"terrain":"abyss"}, "Map_Tile_25_13":{"terrain":"abyss"}, "Map_Tile_4_11":{"terrain":"abyss"}, "Map_Tile_5_11":{"terrain":"abyss"}, "Map_Tile_27_0":{"terrain":"abyss"}, "Map_Tile_25_4":{"terrain":"abyss"}, "Map_Tile_19_7":{"terrain":"abyss"}, "Map_Tile_15_14":{"terrain":"abyss"}, "Map_Tile_29_19":{"terrain":"abyss"}, "Map_Tile_29_12":{"terrain":"abyss"}, "Map_Tile_11_7":{"terrain":"abyss"}, "Map_Tile_8_18":{"terrain":"abyss"}, "Player_1":{"recruit_knight":true, "recruit_soldier":true, "recruit_frog":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_griffin_walking":true, "recruit_caravel":true, "team":0, "recruit_giant":true, "recruit_harpoonship":true, "recruit_mage":true, "recruit_balloon":true, "gold":100, "recruit_kraken":true, "recruit_wagon":true, "recruit_ballista":true, "recruit_turtle":true, "recruit_warship":true, "recruit_archer":true, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_harpy":true, "recruit_dragon":true, "recruit_thief":true, "recruit_witch":true, "recruit_trebuchet":true}, "Map_Tile_16_0":{"terrain":"abyss"}, "Map_Tile_16_4":{"terrain":"abyss"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"abyss"}, "Map_Tile_18_12":{"terrain":"abyss"}, "Map_Tile_20_8":{"terrain":"abyss"}, "Map_Tile_18_11":{"terrain":"abyss"}, "Map_Tile_27_5":{"terrain":"abyss"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"abyss"}, "Map_Tile_2_7":{"terrain":"abyss"}, "Map_Tile_19_14":{"terrain":"abyss"}, "Map_Tile_19_19":{"terrain":"abyss"}, "Map_Tile_19_13":{"terrain":"abyss"}, "Map_Tile_25_12":{"terrain":"abyss"}, "Map_Tile_27_4":{"terrain":"abyss"}, "Map_Tile_26_6":{"terrain":"abyss"}, "Map_Tile_2_3":{"terrain":"abyss"}, "Map_Tile_7_3":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"abyss"}, "Map_Tile_9_0":{"terrain":"abyss"}, "Map_Tile_19_11":{"terrain":"abyss"}, "Map_Tile_5_1":{"terrain":"abyss"}, "Map_Tile_13_7":{"terrain":"abyss"}, "Map_Tile_8_11":{"terrain":"abyss"}, "Map_Tile_13_19":{"terrain":"abyss"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_28_15":{"terrain":"abyss"}, "Map_Tile_29_18":{"terrain":"abyss"}, "Map_Tile_20_3":{"terrain":"abyss"}, "Map_Tile_15_3":{"terrain":"abyss"}, "Map_Tile_26_17":{"terrain":"abyss"}, "Map_Tile_2_4":{"terrain":"abyss"}, "Map_Tile_18_5":{"terrain":"abyss"}, "Map_Tile_10_6":{"terrain":"abyss"}, "Map_Tile_6_15":{"terrain":"abyss"}, "Map_Tile_1_10":{"terrain":"abyss"}, "Map_Tile_4_9":{"terrain":"abyss"}, "Map_Tile_2_19":{"terrain":"abyss"}, "Map_Tile_9_8":{"terrain":"abyss"}, "Map_Tile_11_15":{"terrain":"abyss"}, "Map_Tile_22_14":{"terrain":"abyss"}, "Map_Tile_18_15":{"terrain":"abyss"}, "Map_Tile_15_7":{"terrain":"abyss"}, "Map_Tile_22_12":{"terrain":"abyss"}, "Locations":{"1":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":8, "x":16}, {"y":9, "x":16}, {"y":10, "x":16}, {"y":11, "x":16}, {"y":11, "x":17}, {"y":11, "x":18}, {"y":11, "x":19}, {"y":11, "x":20}, {"y":11, "x":21}, {"y":10, "x":21}, {"y":9, "x":21}, {"y":8, "x":21}, {"y":8, "x":20}, {"y":8, "x":19}, {"y":8, "x":18}, {"y":8, "x":17}, {"y":9, "x":17}, {"y":10, "x":17}, {"y":10, "x":18}, {"y":9, "x":18}, {"y":9, "x":19}, {"y":9, "x":20}, {"y":10, "x":20}, {"y":10, "x":19}, {"y":7, "x":18}, {"y":7, "x":19}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":12, "x":21}, {"y":12, "x":20}, {"y":12, "x":19}, {"y":12, "x":18}, {"y":12, "x":17}, {"y":7, "x":17}], "name":"Challenge 1", "id":1, "centre":{"y":10, "x":19}}, "2":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":10, "x":15}, {"y":10, "x":14}, {"y":9, "x":14}, {"y":9, "x":15}], "name":"Spawn", "id":2, "centre":{"y":10, "x":15}}, "3":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":12, "x":12}, {"y":12, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":12, "x":17}, {"y":13, "x":17}, {"y":14, "x":17}, {"y":15, "x":17}, {"y":15, "x":16}, {"y":15, "x":15}, {"y":15, "x":14}, {"y":15, "x":13}, {"y":15, "x":12}, {"y":14, "x":12}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":13, "x":16}, {"y":14, "x":16}, {"y":14, "x":15}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}], "name":"Challenge 2", "id":3, "centre":{"y":13, "x":15}}, "4":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":11, "x":12}, {"y":10, "x":12}, {"y":9, "x":12}, {"y":8, "x":12}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":10, "x":11}, {"y":11, "x":11}, {"y":12, "x":11}, {"y":7, "x":11}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":8, "x":8}, {"y":9, "x":8}, {"y":10, "x":8}, {"y":11, "x":8}, {"y":12, "x":8}, {"y":12, "x":9}, {"y":12, "x":10}, {"y":11, "x":10}, {"y":10, "x":10}, {"y":9, "x":10}, {"y":8, "x":10}, {"y":8, "x":9}, {"y":9, "x":9}, {"y":10, "x":9}, {"y":11, "x":9}, {"y":8, "x":13}, {"y":9, "x":13}, {"y":10, "x":13}, {"y":11, "x":13}, {"y":7, "x":12}, {"y":12, "x":12}], "name":"Challenge 3", "id":4, "centre":{"y":9, "x":10}}, "5":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":8, "x":16}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":7, "x":13}, {"y":7, "x":12}, {"y":7, "x":14}, {"y":7, "x":15}, {"y":7, "x":16}, {"y":7, "x":17}, {"y":6, "x":17}, {"y":6, "x":16}, {"y":6, "x":15}, {"y":6, "x":14}, {"y":6, "x":13}, {"y":6, "x":12}, {"y":5, "x":17}, {"y":5, "x":12}, {"y":5, "x":13}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":5, "x":16}, {"y":4, "x":17}, {"y":4, "x":16}, {"y":4, "x":15}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":12}], "name":"Challenge 4", "id":5, "centre":{"y":6, "x":15}}, "6":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":13, "x":17}, {"y":12, "x":18}, {"y":14, "x":17}, {"y":15, "x":17}, {"y":15, "x":18}, {"y":14, "x":18}, {"y":13, "x":18}, {"y":13, "x":19}, {"y":12, "x":19}, {"y":12, "x":20}, {"y":12, "x":21}, {"y":12, "x":22}, {"y":12, "x":23}, {"y":13, "x":23}, {"y":14, "x":23}, {"y":14, "x":22}, {"y":15, "x":22}, {"y":15, "x":21}, {"y":15, "x":20}, {"y":15, "x":19}, {"y":14, "x":19}, {"y":13, "x":20}, {"y":13, "x":21}, {"y":13, "x":22}, {"y":14, "x":21}, {"y":14, "x":20}, {"y":15, "x":23}], "name":"Challenge 5", "id":6, "centre":{"y":14, "x":20}}, "7":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":13, "x":12}, {"y":13, "x":11}, {"y":12, "x":11}, {"y":14, "x":11}, {"y":14, "x":12}, {"y":14, "x":10}, {"y":13, "x":10}, {"y":12, "x":10}, {"y":15, "x":11}, {"y":15, "x":12}, {"y":15, "x":10}, {"y":15, "x":9}, {"y":14, "x":9}, {"y":13, "x":9}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":13, "x":8}, {"y":14, "x":8}, {"y":15, "x":8}, {"y":12, "x":7}, {"y":13, "x":7}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":12, "x":6}, {"y":13, "x":6}, {"y":14, "x":6}, {"y":15, "x":6}], "name":"Challenge 6", "id":7, "centre":{"y":14, "x":9}}, "8":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":7, "x":11}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":10}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":4, "x":6}, {"y":5, "x":11}, {"y":4, "x":11}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":4, "x":7}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":5, "x":9}, {"y":5, "x":10}, {"y":5, "x":12}, {"y":4, "x":12}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":7, "x":7}], "name":"Challenge 7", "id":8, "centre":{"y":5, "x":9}}, "9":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":6, "x":17}, {"y":7, "x":18}, {"y":6, "x":18}, {"y":5, "x":18}, {"y":4, "x":18}, {"y":5, "x":19}, {"y":6, "x":19}, {"y":6, "x":20}, {"y":6, "x":21}, {"y":6, "x":22}, {"y":6, "x":23}, {"y":5, "x":23}, {"y":5, "x":22}, {"y":5, "x":21}, {"y":5, "x":20}, {"y":4, "x":19}, {"y":4, "x":20}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":23}, {"y":4, "x":17}, {"y":5, "x":17}, {"y":7, "x":19}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":7, "x":22}], "name":"Challenge 8", "id":9, "centre":{"y":5, "x":20}}, "10":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":7, "x":8}, {"y":8, "x":8}, {"y":9, "x":8}, {"y":10, "x":8}, {"y":11, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":12, "x":0}, {"y":11, "x":0}, {"y":10, "x":0}, {"y":9, "x":0}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":6, "x":1}, {"y":6, "x":2}, {"y":6, "x":3}, {"y":6, "x":4}, {"y":6, "x":5}, {"y":6, "x":6}, {"y":6, "x":7}, {"y":7, "x":7}, {"y":8, "x":7}, {"y":9, "x":7}, {"y":10, "x":7}, {"y":11, "x":7}, {"y":11, "x":6}, {"y":11, "x":5}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":10, "x":1}, {"y":9, "x":1}, {"y":8, "x":1}, {"y":7, "x":1}, {"y":7, "x":2}, {"y":7, "x":3}, {"y":7, "x":4}, {"y":7, "x":5}, {"y":7, "x":6}, {"y":8, "x":6}, {"y":9, "x":6}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":5, "x":2}, {"y":5, "x":1}, {"y":5, "x":0}, {"y":13, "x":5}, {"y":13, "x":4}, {"y":13, "x":3}, {"y":13, "x":2}, {"y":13, "x":1}, {"y":13, "x":0}, {"y":13, "x":6}, {"y":5, "x":6}], "name":"Challenge 9", "id":10, "centre":{"y":9, "x":4}}, "11":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":4, "x":6}, {"y":4, "x":7}, {"y":4, "x":8}, {"y":4, "x":9}, {"y":4, "x":10}, {"y":4, "x":11}, {"y":4, "x":12}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":1, "x":14}, {"y":0, "x":14}, {"y":0, "x":13}, {"y":0, "x":12}, {"y":0, "x":11}, {"y":0, "x":10}, {"y":0, "x":9}, {"y":0, "x":8}, {"y":0, "x":7}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":2, "x":6}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":3, "x":8}, {"y":3, "x":9}, {"y":3, "x":10}, {"y":3, "x":11}, {"y":3, "x":12}, {"y":3, "x":13}, {"y":2, "x":13}, {"y":1, "x":13}, {"y":1, "x":12}, {"y":1, "x":11}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":5}, {"y":4, "x":4}, {"y":4, "x":3}, {"y":4, "x":2}, {"y":4, "x":1}, {"y":4, "x":0}, {"y":3, "x":0}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":1, "x":5}, {"y":2, "x":5}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}, {"y":2, "x":1}, {"y":1, "x":1}, {"y":1, "x":2}, {"y":1, "x":3}, {"y":1, "x":4}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":5, "x":2}, {"y":5, "x":1}, {"y":5, "x":0}], "name":"Challenge 10", "id":11, "centre":{"y":2, "x":7}}, "12":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":4, "x":15}, {"y":3, "x":15}, {"y":2, "x":15}, {"y":1, "x":15}, {"y":0, "x":15}, {"y":0, "x":14}, {"y":1, "x":14}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":16}, {"y":4, "x":17}, {"y":4, "x":18}, {"y":4, "x":19}, {"y":4, "x":20}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":23}, {"y":4, "x":24}, {"y":4, "x":25}, {"y":4, "x":26}, {"y":4, "x":27}, {"y":4, "x":28}, {"y":4, "x":29}, {"y":5, "x":24}, {"y":5, "x":25}, {"y":5, "x":26}, {"y":5, "x":27}, {"y":5, "x":28}, {"y":5, "x":29}, {"y":3, "x":29}, {"y":3, "x":28}, {"y":3, "x":27}, {"y":3, "x":26}, {"y":3, "x":25}, {"y":3, "x":24}, {"y":3, "x":23}, {"y":3, "x":22}, {"y":3, "x":21}, {"y":3, "x":20}, {"y":3, "x":19}, {"y":3, "x":18}, {"y":3, "x":17}, {"y":3, "x":16}, {"y":2, "x":16}, {"y":1, "x":16}, {"y":0, "x":16}, {"y":0, "x":17}, {"y":0, "x":18}, {"y":0, "x":19}, {"y":0, "x":20}, {"y":0, "x":21}, {"y":0, "x":22}, {"y":0, "x":23}, {"y":0, "x":24}, {"y":0, "x":25}, {"y":0, "x":26}, {"y":0, "x":27}, {"y":0, "x":28}, {"y":0, "x":29}, {"y":2, "x":29}, {"y":2, "x":28}, {"y":2, "x":27}, {"y":2, "x":26}, {"y":2, "x":25}, {"y":2, "x":24}, {"y":2, "x":23}, {"y":2, "x":22}, {"y":2, "x":21}, {"y":2, "x":20}, {"y":2, "x":19}, {"y":2, "x":18}, {"y":2, "x":17}, {"y":1, "x":17}, {"y":1, "x":18}, {"y":1, "x":19}, {"y":1, "x":20}, {"y":1, "x":21}, {"y":1, "x":22}, {"y":1, "x":23}, {"y":1, "x":24}, {"y":1, "x":25}, {"y":1, "x":26}, {"y":1, "x":27}, {"y":1, "x":28}, {"y":1, "x":29}], "name":"Challenge 11", "id":12, "centre":{"y":2, "x":22}}, "13":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":7, "x":21}, {"y":8, "x":21}, {"y":9, "x":21}, {"y":10, "x":21}, {"y":11, "x":21}, {"y":12, "x":22}, {"y":6, "x":22}, {"y":5, "x":23}, {"y":5, "x":24}, {"y":5, "x":25}, {"y":5, "x":26}, {"y":5, "x":27}, {"y":5, "x":28}, {"y":5, "x":29}, {"y":13, "x":23}, {"y":13, "x":24}, {"y":13, "x":25}, {"y":13, "x":26}, {"y":13, "x":27}, {"y":13, "x":28}, {"y":13, "x":29}, {"y":12, "x":29}, {"y":12, "x":28}, {"y":12, "x":27}, {"y":12, "x":26}, {"y":12, "x":25}, {"y":12, "x":24}, {"y":12, "x":23}, {"y":11, "x":23}, {"y":11, "x":22}, {"y":10, "x":22}, {"y":9, "x":22}, {"y":8, "x":22}, {"y":7, "x":22}, {"y":6, "x":23}, {"y":7, "x":23}, {"y":8, "x":23}, {"y":9, "x":23}, {"y":10, "x":23}, {"y":10, "x":24}, {"y":9, "x":24}, {"y":8, "x":24}, {"y":7, "x":24}, {"y":7, "x":25}, {"y":6, "x":24}, {"y":11, "x":24}, {"y":10, "x":25}, {"y":9, "x":25}, {"y":8, "x":25}, {"y":6, "x":25}, {"y":11, "x":25}, {"y":8, "x":26}, {"y":9, "x":26}, {"y":10, "x":26}, {"y":11, "x":26}, {"y":7, "x":26}, {"y":6, "x":26}, {"y":8, "x":27}, {"y":9, "x":27}, {"y":10, "x":27}, {"y":10, "x":28}, {"y":9, "x":28}, {"y":8, "x":28}, {"y":7, "x":28}, {"y":6, "x":28}, {"y":11, "x":27}, {"y":7, "x":27}, {"y":6, "x":27}, {"y":11, "x":28}, {"y":11, "x":29}, {"y":10, "x":29}, {"y":9, "x":29}, {"y":8, "x":29}, {"y":7, "x":29}, {"y":6, "x":29}], "name":"Challenge 12", "id":13, "centre":{"y":9, "x":25}}, "14":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":15, "x":14}, {"y":16, "x":14}, {"y":17, "x":14}, {"y":18, "x":14}, {"y":19, "x":14}, {"y":15, "x":13}, {"y":15, "x":12}, {"y":15, "x":11}, {"y":15, "x":10}, {"y":15, "x":9}, {"y":15, "x":8}, {"y":15, "x":7}, {"y":15, "x":6}, {"y":15, "x":5}, {"y":15, "x":4}, {"y":15, "x":3}, {"y":15, "x":2}, {"y":15, "x":1}, {"y":15, "x":0}, {"y":14, "x":5}, {"y":13, "x":5}, {"y":13, "x":4}, {"y":13, "x":3}, {"y":13, "x":2}, {"y":13, "x":1}, {"y":13, "x":0}, {"y":14, "x":0}, {"y":14, "x":1}, {"y":14, "x":2}, {"y":14, "x":3}, {"y":14, "x":4}, {"y":14, "x":6}, {"y":16, "x":13}, {"y":16, "x":12}, {"y":16, "x":11}, {"y":16, "x":10}, {"y":16, "x":9}, {"y":16, "x":8}, {"y":16, "x":7}, {"y":16, "x":6}, {"y":16, "x":5}, {"y":16, "x":4}, {"y":16, "x":3}, {"y":16, "x":2}, {"y":16, "x":1}, {"y":16, "x":0}, {"y":17, "x":0}, {"y":18, "x":0}, {"y":19, "x":0}, {"y":19, "x":1}, {"y":19, "x":2}, {"y":19, "x":3}, {"y":19, "x":4}, {"y":19, "x":5}, {"y":19, "x":6}, {"y":19, "x":7}, {"y":19, "x":8}, {"y":19, "x":9}, {"y":19, "x":10}, {"y":19, "x":11}, {"y":19, "x":12}, {"y":19, "x":13}, {"y":18, "x":13}, {"y":17, "x":13}, {"y":17, "x":12}, {"y":17, "x":11}, {"y":17, "x":10}, {"y":17, "x":9}, {"y":17, "x":8}, {"y":17, "x":7}, {"y":17, "x":6}, {"y":17, "x":5}, {"y":17, "x":4}, {"y":17, "x":3}, {"y":17, "x":2}, {"y":17, "x":1}, {"y":18, "x":1}, {"y":18, "x":2}, {"y":18, "x":3}, {"y":18, "x":4}, {"y":18, "x":5}, {"y":18, "x":6}, {"y":18, "x":7}, {"y":18, "x":8}, {"y":18, "x":9}, {"y":18, "x":10}, {"y":18, "x":11}, {"y":18, "x":12}], "name":"Challenge 13", "id":14, "centre":{"y":16, "x":6}}, "15":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":15, "x":15}, {"y":16, "x":15}, {"y":16, "x":14}, {"y":17, "x":14}, {"y":18, "x":14}, {"y":19, "x":14}, {"y":19, "x":15}, {"y":18, "x":15}, {"y":17, "x":15}, {"y":16, "x":16}, {"y":16, "x":17}, {"y":16, "x":18}, {"y":16, "x":19}, {"y":16, "x":20}, {"y":16, "x":21}, {"y":16, "x":22}, {"y":16, "x":23}, {"y":16, "x":24}, {"y":16, "x":25}, {"y":16, "x":26}, {"y":16, "x":27}, {"y":16, "x":28}, {"y":16, "x":29}, {"y":17, "x":29}, {"y":18, "x":29}, {"y":19, "x":29}, {"y":19, "x":28}, {"y":19, "x":27}, {"y":19, "x":26}, {"y":19, "x":25}, {"y":19, "x":24}, {"y":19, "x":23}, {"y":19, "x":22}, {"y":19, "x":21}, {"y":19, "x":20}, {"y":19, "x":19}, {"y":19, "x":18}, {"y":19, "x":17}, {"y":19, "x":16}, {"y":18, "x":16}, {"y":17, "x":16}, {"y":17, "x":17}, {"y":17, "x":18}, {"y":17, "x":19}, {"y":17, "x":20}, {"y":17, "x":21}, {"y":17, "x":22}, {"y":17, "x":23}, {"y":17, "x":24}, {"y":17, "x":25}, {"y":17, "x":26}, {"y":17, "x":27}, {"y":17, "x":28}, {"y":18, "x":28}, {"y":18, "x":18}, {"y":18, "x":17}, {"y":18, "x":19}, {"y":18, "x":20}, {"y":18, "x":21}, {"y":18, "x":22}, {"y":18, "x":23}, {"y":18, "x":24}, {"y":18, "x":25}, {"y":18, "x":26}, {"y":18, "x":27}, {"y":15, "x":16}, {"y":15, "x":17}, {"y":15, "x":18}, {"y":15, "x":19}, {"y":15, "x":20}, {"y":15, "x":21}, {"y":15, "x":22}, {"y":15, "x":23}, {"y":15, "x":24}, {"y":15, "x":25}, {"y":15, "x":26}, {"y":15, "x":27}, {"y":15, "x":28}, {"y":15, "x":29}, {"y":14, "x":29}, {"y":14, "x":28}, {"y":14, "x":27}, {"y":14, "x":26}, {"y":14, "x":25}, {"y":14, "x":24}, {"y":14, "x":23}, {"y":13, "x":24}, {"y":13, "x":29}, {"y":13, "x":28}, {"y":13, "x":27}, {"y":13, "x":26}, {"y":13, "x":25}], "name":"Challenge 14", "id":15, "centre":{"y":17, "x":22}}, "0":{"getArea":null, "interactable":false, "setArea":null, "positions":[{"y":8, "x":13}, {"y":8, "x":14}, {"y":8, "x":15}, {"y":8, "x":16}, {"y":9, "x":16}, {"y":10, "x":16}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":10, "x":13}, {"y":9, "x":13}], "name":"Start", "id":0, "centre":{"y":9, "x":15}}}, "Map_Tile_25_11":{"terrain":"abyss"}, "Map_Tile_28_19":{"terrain":"abyss"}, "Map_Tile_3_17":{"terrain":"abyss"}, "Map_Tile_29_13":{"terrain":"abyss"}, "Map_Tile_27_2":{"terrain":"abyss"}, "Map_Tile_29_11":{"terrain":"abyss"}, "Map_Tile_29_10":{"terrain":"abyss"}, "Map_Tile_22_11":{"terrain":"abyss"}, "Map_Tile_26_0":{"terrain":"abyss"}, "Map_Tile_25_8":{"terrain":"abyss"}, "Map_Tile_14_13":{"terrain":"abyss"}, "Map_Tile_9_18":{"terrain":"abyss"}, "Map_Tile_29_6":{"terrain":"abyss"}, "Map_Tile_26_12":{"terrain":"abyss"}, "Map_Tile_28_18":{"terrain":"abyss"}, "Map_Tile_28_14":{"terrain":"abyss"}, "Map_Tile_28_12":{"terrain":"abyss"}, "Map_Tile_28_8":{"terrain":"abyss"}, "Map_Tile_28_7":{"terrain":"abyss"}, "Map_Tile_28_6":{"terrain":"abyss"}, "Map_Tile_22_9":{"terrain":"abyss"}, "Map_Tile_1_13":{"terrain":"abyss"}, "Map_Tile_28_5":{"terrain":"abyss"}, "Map_Tile_28_4":{"terrain":"abyss"}, "Map_Tile_28_3":{"terrain":"abyss"}, "Map_Tile_9_14":{"terrain":"abyss"}, "Map_Tile_14_7":{"terrain":"abyss"}, "Map_Tile_18_7":{"terrain":"abyss"}, "Map_Tile_12_1":{"terrain":"abyss"}, "Map_Tile_7_7":{"terrain":"abyss"}, "Map_Tile_27_7":{"terrain":"abyss"}, "Map_Tile_27_15":{"terrain":"abyss"}, "Map_Tile_27_14":{"terrain":"abyss"}, "Map_Tile_20_15":{"terrain":"abyss"}, "Map_Tile_0_5":{"terrain":"abyss"}, "Map_Tile_24_2":{"terrain":"abyss"}, "Map_Tile_7_18":{"terrain":"abyss"}, "Map_Tile_21_1":{"terrain":"abyss"}, "Map_Tile_23_8":{"terrain":"abyss"}, "Map_Tile_27_3":{"terrain":"abyss"}, "Map_Tile_19_1":{"terrain":"abyss"}, "Map_Tile_16_3":{"terrain":"abyss"}, "Map_Tile_26_19":{"terrain":"abyss"}, "Map_Tile_16_16":{"terrain":"abyss"}, "Map_Tile_26_15":{"terrain":"abyss"}, "Map_Tile_26_14":{"terrain":"abyss"}, "Map_Tile_3_16":{"terrain":"abyss"}, "Map_Tile_6_1":{"terrain":"abyss"}, "Map_Tile_26_11":{"terrain":"abyss"}, "Map_Tile_10_11":{"terrain":"abyss"}, "Map_Tile_17_11":{"terrain":"abyss"}, "Map_Tile_25_18":{"terrain":"abyss"}, "Map_Tile_24_15":{"terrain":"abyss"}, "Map_Tile_25_15":{"terrain":"abyss"}, "Map_Tile_19_0":{"terrain":"abyss"}, "Map_Tile_25_10":{"terrain":"abyss"}, "Map_Tile_5_19":{"terrain":"abyss"}, "Map_Tile_2_8":{"terrain":"abyss"}, "Map_Tile_8_4":{"terrain":"abyss"}, "Map_Tile_25_9":{"terrain":"abyss"}, "Map_Tile_29_8":{"terrain":"abyss"}, "Map_Tile_3_5":{"terrain":"abyss"}, "Map_Tile_0_15":{"terrain":"abyss"}, "Map_Tile_25_5":{"terrain":"abyss"}, "Map_Tile_5_12":{"terrain":"abyss"}, "Map_Tile_19_9":{"terrain":"abyss"}, "Map_Tile_8_19":{"terrain":"abyss"}, "Map_Tile_2_5":{"terrain":"abyss"}, "Map_Tile_12_18":{"terrain":"abyss"}, "Map_Tile_6_0":{"terrain":"abyss"}, "Map_Tile_15_13":{"terrain":"abyss"}, "Map_Tile_13_6":{"terrain":"abyss"}, "Map_Tile_18_2":{"terrain":"abyss"}, "Map_Tile_25_1":{"terrain":"abyss"}, "Map_Tile_25_0":{"terrain":"abyss"}, "Map_Tile_26_8":{"terrain":"abyss"}, "Map_Tile_16_14":{"terrain":"abyss"}, "Map_Tile_4_16":{"terrain":"abyss"}, "Map_Tile_16_13":{"terrain":"abyss"}, "Map_Tile_16_1":{"terrain":"abyss"}, "Map_Tile_1_0":{"terrain":"abyss"}, "Map_Tile_22_10":{"terrain":"abyss"}, "Map_Tile_1_5":{"terrain":"abyss"}, "Map_Tile_3_8":{"terrain":"abyss"}, "Map_Tile_15_16":{"terrain":"abyss"}, "Map_Tile_6_7":{"terrain":"abyss"}, "Map_Tile_7_1":{"terrain":"abyss"}, "Map_Tile_10_1":{"terrain":"abyss"}, "Map_Tile_24_12":{"terrain":"abyss"}, "Map_Tile_7_5":{"terrain":"abyss"}, "Map_Tile_24_9":{"terrain":"abyss"}, "Map_Tile_8_8":{"terrain":"abyss"}, "Map_Tile_11_6":{"terrain":"abyss"}, "Map_Tile_24_7":{"terrain":"abyss"}, "Map_Tile_27_13":{"terrain":"abyss"}, "Map_Tile_15_6":{"terrain":"abyss"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_13_15":{"terrain":"abyss"}, "Map_Tile_12_5":{"terrain":"abyss"}, "Map_Tile_11_11":{"terrain":"abyss"}, "Map_Tile_24_4":{"terrain":"abyss"}, "Map_Tile_21_5":{"terrain":"abyss"}, "Map_Tile_24_13":{"terrain":"abyss"}, "Map_Tile_25_2":{"terrain":"abyss"}, "Map_Tile_24_0":{"terrain":"abyss"}, "Map_Tile_3_14":{"terrain":"abyss"}, "Map_Tile_0_10":{"terrain":"abyss"}, "Map_Tile_23_18":{"terrain":"abyss"}, "Map_Tile_17_2":{"terrain":"abyss"}, "Map_Tile_14_1":{"terrain":"abyss"}, "Map_Tile_12_9":{"terrain":"abyss"}, "Map_Tile_2_0":{"terrain":"abyss"}, "Map_Tile_13_1":{"terrain":"abyss"}, "Map_Tile_23_15":{"terrain":"abyss"}, "Map_Tile_9_7":{"terrain":"abyss"}, "Map_Tile_12_15":{"terrain":"abyss"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_27_6":{"terrain":"abyss"}, "Map_Tile_9_3":{"terrain":"abyss"}, "Map_Tile_16_12":{"terrain":"abyss"}, "Map_Tile_10_2":{"terrain":"abyss"}, "Map_Tile_23_13":{"terrain":"abyss"}, "Map_Tile_23_12":{"terrain":"abyss"}, "Map_Tile_23_10":{"terrain":"abyss"}, "Map_Tile_20_13":{"terrain":"abyss"}, "Map_Tile_23_9":{"terrain":"abyss"}, "Map_Tile_28_1":{"terrain":"abyss"}, "Map_Tile_10_8":{"terrain":"abyss"}, "Map_Tile_8_13":{"terrain":"abyss"}, "Map_Tile_23_6":{"terrain":"abyss"}, "Map_Tile_1_9":{"terrain":"abyss"}, "Map_Tile_9_19":{"terrain":"abyss"}, "Map_Tile_6_6":{"terrain":"abyss"}, "Map_Tile_1_19":{"terrain":"abyss"}, "Map_Tile_9_15":{"terrain":"abyss"}, "Map_Tile_1_6":{"terrain":"abyss"}, "Map_Tile_4_17":{"terrain":"abyss"}, "Map_Tile_11_4":{"terrain":"abyss"}, "Map_Tile_7_14":{"terrain":"abyss"}, "Map_Tile_13_16":{"terrain":"abyss"}, "Map_Tile_22_19":{"terrain":"abyss"}, "Map_Tile_8_3":{"terrain":"abyss"}, "Map_Tile_12_11":{"terrain":"abyss"}, "Map_Tile_22_16":{"terrain":"abyss"}, "Map_Tile_11_0":{"terrain":"abyss"}, "Map_Size":{"y":20, "x":30}, "Map_Tile_6_4":{"terrain":"abyss"}, "Map_Tile_14_6":{"terrain":"abyss"}, "Map_Tile_2_2":{"terrain":"abyss"}, "Map_Tile_21_7":{"terrain":"abyss"}, "Map_Tile_4_4":{"terrain":"abyss"}, "Map_Tile_22_15":{"terrain":"abyss"}, "Triggers":[{"isIntro":false, "enabled":true, "conditions":{}, "recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"AP: Export", "actions":[{"enabled":true, "id":"ap_export", "parameters":["926328", "Ancient Discoveries", "Fly Sniper", "Spawn 3 enemy strongholds.", "Kill an enemy stronghold with a golem.", "", "Win by eliminating an enemy stronghold."]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_no_units", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}], "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Defeat (Lost Commander)", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}], "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_hq", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}], "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_victory", "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}]}, {"isIntro":false, "enabled":true, "conditions":{}, "recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Generate Map", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["0", "-5", "0", "0", "0", "50", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "2", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "2", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "2", "P1", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "1"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 1", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["1", "-7", "0", "0", "0", "95", "0", "0", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "1", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "1", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "1", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "3"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 2", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["3", "-7", "0", "0", "0", "95", "0", "0", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "3", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "3", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "4"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 3", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["4", "-7", "0", "0", "0", "0", "95", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "4", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "4", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "5"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 4", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["5", "-7", "0", "0", "0", "50", "50", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "5", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["griffin_walking", "5", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "5", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "6"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 5", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["6", "-7", "0", "50", "5", "0", "0", "0", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "6", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["caravel", "6", "P2", "1", "1", "3", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "6", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "6", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "7"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 6", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["7", "-7", "0", "0", "0", "20", "40", "40", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "7", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["archer", "7", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "7", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "8"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 7", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["8", "-7", "0", "0", "0", "60", "40", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "8", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["city", "8", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "8", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "9"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 8", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["9", "-10", "0", "50", "0", "0", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["knight", "9", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["fortified_city", "9", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["griffin_walking", "9", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "10"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 9", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["10", "-13", "0", "0", "50", "10", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "10", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["merman", "10", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["port", "10", "P1", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "11"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 10", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["11", "-13", "0", "0", "0", "50", "0", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "11", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "11", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "11", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "12"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 11", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["12", "-10", "0", "0", "0", "5", "10", "5", "80"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "12", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "12", "P2", "1", "1", "2", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "13"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 12", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["13", "-13", "0", "0", "0", "60", "10", "5", "5"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "13", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["knight", "13", "P2", "1", "1", "3", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "14"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 13", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["14", "-13", "0", "50", "5", "20", "0", "0", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "14", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "14", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["giant", "14", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "1", "0", "*unit_structure", "15"]}, {"enabled":true, "id":"end_of_unit_turn", "parameters":{}}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Challenge 14", "actions":[{"enabled":true, "id":"map_randomize", "parameters":["15", "-13", "0", "0", "0", "50", "20", "0", "10"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "15", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "15", "P2", "1", "1", "5", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "15", "P2", "1", "1", "3", "1", "undefined", "centre"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["current"]}], "recurring":"end_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Victorious (253045)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253045"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "2", "2", "hq", "-1"]}], "recurring":"once", "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"Spawn 3 Enemy Strongholds (253046)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253046"]}]}, {"isIntro":false, "enabled":true, "conditions":[{"enabled":true, "id":"unit_killed", "parameters":["giant", "P1", "hq", "P2", "-1"]}], "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Giant kills stronghold (253047)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253047"]}]}], "Map_Tile_27_9":{"terrain":"abyss"}, "Map_Tile_25_6":{"terrain":"abyss"}, "Map_Tile_0_7":{"terrain":"abyss"}, "Map_Tile_22_7":{"terrain":"abyss"}, "Map_Tile_10_7":{"terrain":"abyss"}, "Map_Tile_9_1":{"terrain":"abyss"}, "Map_Tile_13_3":{"terrain":"abyss"}, "Map_Tile_22_4":{"terrain":"abyss"}, "Map_Tile_6_17":{"terrain":"abyss"}, "Map_Tile_22_2":{"terrain":"abyss"}, "Map_Tile_1_17":{"terrain":"abyss"}, "Map_Tile_17_10":{"terrain":"abyss"}, "Map_Tile_23_17":{"terrain":"abyss"}, "Map_Tile_15_0":{"terrain":"abyss"}, "Map_Tile_23_2":{"terrain":"abyss"}, "Map_Tile_26_13":{"terrain":"abyss"}, "Map_Tile_8_5":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"abyss"}, "Map_Tile_21_19":{"terrain":"abyss"}, "Map_Tile_5_9":{"terrain":"abyss"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_12_12":{"terrain":"abyss"}, "Map_Tile_21_17":{"terrain":"abyss"}, "Map_Tile_21_16":{"terrain":"abyss"}, "Map_Tile_23_5":{"terrain":"abyss"}, "Map_Tile_21_13":{"terrain":"abyss"}, "Map_Tile_18_19":{"terrain":"abyss"}, "Map_Tile_6_5":{"terrain":"abyss"}, "Map_Tile_21_10":{"terrain":"abyss"}, "Map_Tile_21_9":{"terrain":"abyss"}, "Map_Tile_21_8":{"terrain":"abyss"}, "Map_Tile_20_19":{"terrain":"abyss"}, "Map_Tile_2_6":{"terrain":"abyss"}, "Map_Tile_18_1":{"terrain":"abyss"}, "Map_Tile_18_8":{"terrain":"abyss"}, "Map_Tile_20_12":{"terrain":"abyss"}, "Map_Tile_8_0":{"terrain":"abyss"}, "Map_Tile_20_18":{"terrain":"abyss"}, "Map_Tile_3_6":{"terrain":"abyss"}, "Map_Tile_20_14":{"terrain":"abyss"}, "Map_Tile_12_2":{"terrain":"abyss"}, "Map_Tile_26_18":{"terrain":"abyss"}, "Map_Tile_8_15":{"terrain":"abyss"}, "Map_Tile_3_11":{"terrain":"abyss"}, "Map_Tile_20_9":{"terrain":"abyss"}, "Map_Tile_3_13":{"terrain":"abyss"}, "Map_Tile_20_6":{"terrain":"abyss"}, "Map_Tile_20_5":{"terrain":"abyss"}, "Map_Tile_11_12":{"terrain":"abyss"}, "Map_Tile_15_17":{"terrain":"abyss"}, "Map_Tile_3_19":{"terrain":"abyss"}, "Map_Tile_0_2":{"terrain":"abyss"}, "Map_Tile_20_2":{"terrain":"abyss"}, "Map_Tile_8_7":{"terrain":"abyss"}, "Map_Tile_11_13":{"terrain":"abyss"}, "Map_Tile_10_5":{"terrain":"abyss"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_12_8":{"terrain":"abyss"}, "Map_Tile_3_18":{"terrain":"abyss"}, "Map_Tile_20_1":{"terrain":"abyss"}, "Map_Tile_0_18":{"terrain":"abyss"}, "Map_Tile_18_17":{"terrain":"abyss"}, "Map_Tile_10_9":{"terrain":"abyss"}, "Map_Tile_13_17":{"terrain":"abyss"}, "Map_Tile_19_15":{"terrain":"abyss"}, "Map_Tile_7_2":{"terrain":"abyss"}, "Map_Tile_9_6":{"terrain":"abyss"}, "Map_Tile_19_8":{"terrain":"abyss"}, "Map_Tile_14_0":{"terrain":"abyss"}, "Map_Tile_19_5":{"terrain":"abyss"}, "Map_Tile_18_3":{"terrain":"abyss"}, "Map_Tile_4_19":{"terrain":"abyss"}, "Map_Tile_26_1":{"terrain":"abyss"}, "Map_Tile_5_3":{"terrain":"abyss"}, "Map_Tile_18_13":{"terrain":"abyss"}, "Map_Tile_8_12":{"terrain":"abyss"}, "Map_Tile_17_18":{"terrain":"abyss"}, "Player_Count":2, "Map_Tile_15_5":{"terrain":"abyss"}, "Map_Tile_17_16":{"terrain":"abyss"}, "Map_Tile_4_5":{"terrain":"abyss"}, "Map_Tile_17_15":{"terrain":"abyss"}, "Map_Tile_19_12":{"terrain":"abyss"}, "Map_Tile_0_11":{"terrain":"abyss"}, "Map_Tile_7_13":{"terrain":"abyss"}, "Map_Tile_14_4":{"terrain":"abyss"}, "Map_Tile_0_19":{"terrain":"abyss"}, "Map_Tile_1_12":{"terrain":"abyss"}, "Map_Tile_0_3":{"terrain":"abyss"}, "Map_Tile_7_4":{"terrain":"abyss"}, "Map_Tile_15_4":{"terrain":"abyss"}, "Map_Tile_5_8":{"terrain":"abyss"}, "Map_Tile_14_14":{"terrain":"abyss"}, "Map_Tile_23_19":{"terrain":"abyss"}, "Map_Tile_13_12":{"terrain":"abyss"}, "Map_Tile_24_10":{"terrain":"abyss"}, "Map_Tile_9_2":{"terrain":"abyss"}, "Map_Tile_12_6":{"terrain":"abyss"}, "Map_Tile_4_7":{"terrain":"abyss"}, "Map_Tile_0_0":{"terrain":"abyss"}, "Map_Tile_0_16":{"terrain":"abyss"}, "Map_Tile_25_3":{"terrain":"abyss"}, "Map_Tile_22_3":{"terrain":"abyss"}, "Map_Tile_12_0":{"terrain":"abyss"}, "Map_Tile_11_16":{"terrain":"abyss"}, "Map_Tile_2_15":{"terrain":"abyss"}, "Map_Tile_3_12":{"terrain":"abyss"}, "Map_Tile_12_17":{"terrain":"abyss"}} \ No newline at end of file +{"Map_Tile_14_18":{"terrain":"abyss"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_23_1":{"terrain":"abyss"}, "Map_Tile_11_2":{"terrain":"abyss"}, "Map_Tile_1_6":{"terrain":"abyss"}, "Map_Tile_12_10":{"terrain":"abyss"}, "Map_Tile_0_14":{"terrain":"abyss"}, "Map_Tile_10_10":{"terrain":"abyss"}, "Map_Tile_26_6":{"terrain":"abyss"}, "Map_Tile_23_10":{"terrain":"abyss"}, "Map_Tile_29_4":{"terrain":"abyss"}, "Map_Tile_14_4":{"terrain":"abyss"}, "Map_Tile_15_19":{"terrain":"abyss"}, "Map_Tile_24_9":{"terrain":"abyss"}, "Map_Tile_17_10":{"terrain":"abyss"}, "Map_Tile_20_2":{"terrain":"abyss"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"abyss"}, "Map_Tile_17_6":{"terrain":"abyss"}, "Map_Tile_12_0":{"terrain":"abyss"}, "Map_Tile_16_14":{"terrain":"abyss"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_21_19":{"terrain":"abyss"}, "Map_Tile_5_14":{"terrain":"abyss"}, "Map_Tile_4_6":{"terrain":"abyss"}, "Map_Tile_17_5":{"terrain":"abyss"}, "Map_Tile_17_8":{"terrain":"abyss"}, "Map_Tile_27_17":{"terrain":"abyss"}, "Map_Tile_17_18":{"terrain":"abyss"}, "Map_Tile_8_16":{"terrain":"abyss"}, "Map_Tile_15_17":{"terrain":"abyss"}, "Map_Tile_10_17":{"terrain":"abyss"}, "Map_Tile_17_4":{"terrain":"abyss"}, "Map_Tile_8_17":{"terrain":"abyss"}, "Map_Tile_20_1":{"terrain":"abyss"}, "Map_Tile_0_13":{"terrain":"abyss"}, "Map_Tile_12_15":{"terrain":"abyss"}, "Map_Tile_17_13":{"terrain":"abyss"}, "Map_Tile_19_5":{"terrain":"abyss"}, "Map_Tile_3_13":{"terrain":"abyss"}, "Map_Tile_15_4":{"terrain":"abyss"}, "Map_Tile_11_4":{"terrain":"abyss"}, "Map_Tile_12_12":{"terrain":"abyss"}, "Map_Tile_28_13":{"terrain":"abyss"}, "Map_Tile_21_0":{"terrain":"abyss"}, "Map_Tile_6_0":{"terrain":"abyss"}, "Map_Tile_11_3":{"terrain":"abyss"}, "Map_Tile_7_13":{"terrain":"abyss"}, "Map_Tile_22_5":{"terrain":"abyss"}, "Map_Tile_10_8":{"terrain":"abyss"}, "Map_Tile_0_15":{"terrain":"abyss"}, "Map_Tile_27_18":{"terrain":"abyss"}, "Map_Tile_9_8":{"terrain":"abyss"}, "Map_Tile_19_14":{"terrain":"abyss"}, "Map_Tile_16_6":{"terrain":"abyss"}, "Map_Tile_21_8":{"terrain":"abyss"}, "Map_Tile_11_18":{"terrain":"abyss"}, "Map_Tile_27_14":{"terrain":"abyss"}, "Map_Tile_2_0":{"terrain":"abyss"}, "Map_Tile_3_11":{"terrain":"abyss"}, "Map_Tile_3_19":{"terrain":"abyss"}, "Map_Tile_25_7":{"terrain":"abyss"}, "Map_Tile_8_5":{"terrain":"abyss"}, "Map_Tile_19_18":{"terrain":"abyss"}, "Map_Tile_5_15":{"terrain":"abyss"}, "Map_Tile_16_7":{"terrain":"abyss"}, "Triggers":[{"recurring":"start_of_match", "id":"AP: Export", "actions":[{"parameters":["926328", "Ancient Discoveries", "Fly Sniper", "Spawn 3 enemy strongholds.", "Kill an enemy stronghold with a golem.", "", "Win by eliminating an enemy stronghold."], "id":"ap_export", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence", "enabled":true}], "enabled":true}, {"recurring":"oncePerPlayer", "id":"Defeat (Lost Commander)", "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["*commander", "current", "-1"], "id":"unit_lost", "enabled":true}], "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["hq", "current", "-1"], "id":"unit_lost", "enabled":true}], "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_victory", "actions":[{"parameters":["current"], "id":"victory", "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0"], "id":"number_of_opponents", "enabled":true}], "enabled":true}, {"recurring":"start_of_match", "id":"Generate Map", "actions":[{"parameters":["0", "-5", "0", "0", "0", "50", "0", "0", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["barracks", "2", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["hq", "2", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["*commander", "2", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "enabled":true}, {"recurring":"once", "id":"Challenge 1", "actions":[{"parameters":["1", "-7", "0", "0", "0", "95", "0", "0", "5"], "id":"map_randomize", "enabled":true}, {"parameters":["barracks", "1", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["city", "1", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["dog", "1", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "1"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 2", "actions":[{"parameters":["3", "-7", "0", "0", "0", "95", "0", "0", "5"], "id":"map_randomize", "enabled":true}, {"parameters":["*commander", "3", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["city", "3", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "3"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 3", "actions":[{"parameters":["4", "-7", "0", "0", "0", "0", "95", "5", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["city", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["mage", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "4"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 4", "actions":[{"parameters":["5", "-7", "0", "0", "0", "50", "50", "5", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["city", "5", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["griffin_walking", "5", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["soldier", "5", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "5"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 5", "actions":[{"parameters":["6", "-7", "0", "50", "5", "0", "0", "0", "5"], "id":"map_randomize", "enabled":true}, {"parameters":["city", "6", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["caravel", "6", "P2", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["dog", "6", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["mage", "6", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "6"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 6", "actions":[{"parameters":["7", "-7", "0", "0", "0", "20", "40", "40", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["*commander", "7", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["archer", "7", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["city", "7", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "7"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 7", "actions":[{"parameters":["8", "-7", "0", "0", "0", "60", "40", "0", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["barracks", "8", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["city", "8", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["soldier", "8", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "8"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 8", "actions":[{"parameters":["9", "-10", "0", "50", "0", "0", "0", "0", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["knight", "9", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["fortified_city", "9", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["griffin_walking", "9", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "9"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 9", "actions":[{"parameters":["10", "-13", "0", "0", "50", "10", "0", "0", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["hq", "10", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["merman", "10", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["port", "10", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "10"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 10", "actions":[{"parameters":["11", "-13", "0", "0", "0", "50", "0", "5", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["hq", "11", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["*commander", "11", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["barracks", "11", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "11"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 11", "actions":[{"parameters":["12", "-10", "0", "0", "0", "5", "10", "5", "80"], "id":"map_randomize", "enabled":true}, {"parameters":["hq", "12", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["*commander", "12", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["port", "12", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["river_city", "12", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "12"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 12", "actions":[{"parameters":["13", "-13", "0", "0", "0", "60", "10", "5", "5"], "id":"map_randomize", "enabled":true}, {"parameters":["hq", "13", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["knight", "13", "P2", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "13"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 13", "actions":[{"parameters":["14", "-13", "0", "50", "5", "20", "0", "0", "0"], "id":"map_randomize", "enabled":true}, {"parameters":["hq", "14", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["mage", "14", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["giant", "14", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "14"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Challenge 14", "actions":[{"parameters":["15", "-13", "0", "0", "0", "50", "20", "0", "10"], "id":"map_randomize", "enabled":true}, {"parameters":["hq", "15", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["soldier", "15", "P2", "1", "1", "5", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["dog", "15", "P2", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_turn", "enabled":true}, {"parameters":["current", "1", "0", "*unit_structure", "15"], "id":"unit_presence", "enabled":true}, {"parameters":{}, "id":"end_of_unit_turn", "enabled":true}], "enabled":true}, {"recurring":"end_of_match", "id":"P1 Victorious (253045)", "actions":[{"parameters":["253045"], "id":"ap_location_send", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "id":"player_victorious", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Spawn 3 Enemy Strongholds (253046)", "actions":[{"parameters":["253046"], "id":"ap_location_send", "enabled":true}], "isIntro":false, "players":[0, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "2", "2", "hq", "-1"], "id":"unit_presence", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"P1 Giant kills stronghold (253047)", "actions":[{"parameters":["253047"], "id":"ap_location_send", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["giant", "P1", "hq", "P2", "-1"], "id":"unit_killed", "enabled":true}], "enabled":true}], "Map_Tile_19_12":{"terrain":"abyss"}, "Map_Tile_6_16":{"terrain":"abyss"}, "Map_Tile_8_2":{"terrain":"abyss"}, "Map_Tile_20_17":{"terrain":"abyss"}, "Map_Tile_28_4":{"terrain":"abyss"}, "Map_Tile_13_2":{"terrain":"abyss"}, "Map_Tile_1_12":{"terrain":"abyss"}, "Map_Tile_4_14":{"terrain":"abyss"}, "Map_Tile_1_1":{"terrain":"abyss"}, "Map_Tile_1_9":{"terrain":"abyss"}, "Map_Tile_24_12":{"terrain":"abyss"}, "Map_Tile_16_3":{"terrain":"abyss"}, "Map_Tile_13_13":{"terrain":"abyss"}, "Map_Tile_18_9":{"terrain":"abyss"}, "Map_Tile_25_10":{"terrain":"abyss"}, "Map_Tile_7_11":{"terrain":"abyss"}, "Map_Tile_11_13":{"terrain":"abyss"}, "Map_Tile_11_15":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"abyss"}, "Map_Tile_6_1":{"terrain":"abyss"}, "Map_Tile_13_15":{"terrain":"abyss"}, "Map_Tile_17_0":{"terrain":"abyss"}, "Map_Tile_14_13":{"terrain":"abyss"}, "Map_Tile_6_7":{"terrain":"abyss"}, "Map_Tile_27_7":{"terrain":"abyss"}, "Map_Tile_0_18":{"terrain":"abyss"}, "Map_Tile_11_14":{"terrain":"abyss"}, "Map_Tile_7_2":{"terrain":"abyss"}, "Map_Tile_18_1":{"terrain":"abyss"}, "Map_Tile_6_9":{"terrain":"abyss"}, "Map_Tile_2_5":{"terrain":"abyss"}, "Map_Tile_4_7":{"terrain":"abyss"}, "Map_Tile_20_8":{"terrain":"abyss"}, "Map_Tile_26_17":{"terrain":"abyss"}, "Map_Tile_9_4":{"terrain":"abyss"}, "Map_Tile_5_0":{"terrain":"abyss"}, "Map_Tile_14_0":{"terrain":"abyss"}, "Map_Tile_25_4":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"abyss"}, "Locations":{"1":{"centre":{"y":10, "x":19}, "id":1, "positions":[{"y":8, "x":16}, {"y":9, "x":16}, {"y":10, "x":16}, {"y":11, "x":16}, {"y":11, "x":17}, {"y":11, "x":18}, {"y":11, "x":19}, {"y":11, "x":20}, {"y":11, "x":21}, {"y":10, "x":21}, {"y":9, "x":21}, {"y":8, "x":21}, {"y":8, "x":20}, {"y":8, "x":19}, {"y":8, "x":18}, {"y":8, "x":17}, {"y":9, "x":17}, {"y":10, "x":17}, {"y":10, "x":18}, {"y":9, "x":18}, {"y":9, "x":19}, {"y":9, "x":20}, {"y":10, "x":20}, {"y":10, "x":19}, {"y":7, "x":18}, {"y":7, "x":19}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":12, "x":21}, {"y":12, "x":20}, {"y":12, "x":19}, {"y":12, "x":18}, {"y":12, "x":17}, {"y":7, "x":17}], "interactable":false, "getArea":null, "name":"Challenge 1", "setArea":null}, "2":{"centre":{"y":10, "x":15}, "id":2, "positions":[{"y":10, "x":15}, {"y":10, "x":14}, {"y":9, "x":14}, {"y":9, "x":15}], "interactable":false, "getArea":null, "name":"Spawn", "setArea":null}, "3":{"centre":{"y":13, "x":15}, "id":3, "positions":[{"y":12, "x":12}, {"y":12, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":12, "x":17}, {"y":13, "x":17}, {"y":14, "x":17}, {"y":15, "x":17}, {"y":15, "x":16}, {"y":15, "x":15}, {"y":15, "x":14}, {"y":15, "x":13}, {"y":15, "x":12}, {"y":14, "x":12}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":13, "x":16}, {"y":14, "x":16}, {"y":14, "x":15}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}], "interactable":false, "getArea":null, "name":"Challenge 2", "setArea":null}, "4":{"centre":{"y":9, "x":10}, "id":4, "positions":[{"y":11, "x":12}, {"y":10, "x":12}, {"y":9, "x":12}, {"y":8, "x":12}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":10, "x":11}, {"y":11, "x":11}, {"y":12, "x":11}, {"y":7, "x":11}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":8, "x":8}, {"y":9, "x":8}, {"y":10, "x":8}, {"y":11, "x":8}, {"y":12, "x":8}, {"y":12, "x":9}, {"y":12, "x":10}, {"y":11, "x":10}, {"y":10, "x":10}, {"y":9, "x":10}, {"y":8, "x":10}, {"y":8, "x":9}, {"y":9, "x":9}, {"y":10, "x":9}, {"y":11, "x":9}, {"y":8, "x":13}, {"y":9, "x":13}, {"y":10, "x":13}, {"y":11, "x":13}, {"y":7, "x":12}, {"y":12, "x":12}], "interactable":false, "getArea":null, "name":"Challenge 3", "setArea":null}, "5":{"centre":{"y":6, "x":15}, "id":5, "positions":[{"y":8, "x":16}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":7, "x":13}, {"y":7, "x":12}, {"y":7, "x":14}, {"y":7, "x":15}, {"y":7, "x":16}, {"y":7, "x":17}, {"y":6, "x":17}, {"y":6, "x":16}, {"y":6, "x":15}, {"y":6, "x":14}, {"y":6, "x":13}, {"y":6, "x":12}, {"y":5, "x":17}, {"y":5, "x":12}, {"y":5, "x":13}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":5, "x":16}, {"y":4, "x":17}, {"y":4, "x":16}, {"y":4, "x":15}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":12}], "interactable":false, "getArea":null, "name":"Challenge 4", "setArea":null}, "6":{"centre":{"y":14, "x":20}, "id":6, "positions":[{"y":13, "x":17}, {"y":12, "x":18}, {"y":14, "x":17}, {"y":15, "x":17}, {"y":15, "x":18}, {"y":14, "x":18}, {"y":13, "x":18}, {"y":13, "x":19}, {"y":12, "x":19}, {"y":12, "x":20}, {"y":12, "x":21}, {"y":12, "x":22}, {"y":12, "x":23}, {"y":13, "x":23}, {"y":14, "x":23}, {"y":14, "x":22}, {"y":15, "x":22}, {"y":15, "x":21}, {"y":15, "x":20}, {"y":15, "x":19}, {"y":14, "x":19}, {"y":13, "x":20}, {"y":13, "x":21}, {"y":13, "x":22}, {"y":14, "x":21}, {"y":14, "x":20}, {"y":15, "x":23}], "interactable":false, "getArea":null, "name":"Challenge 5", "setArea":null}, "7":{"centre":{"y":14, "x":9}, "id":7, "positions":[{"y":13, "x":12}, {"y":13, "x":11}, {"y":12, "x":11}, {"y":14, "x":11}, {"y":14, "x":12}, {"y":14, "x":10}, {"y":13, "x":10}, {"y":12, "x":10}, {"y":15, "x":11}, {"y":15, "x":12}, {"y":15, "x":10}, {"y":15, "x":9}, {"y":14, "x":9}, {"y":13, "x":9}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":13, "x":8}, {"y":14, "x":8}, {"y":15, "x":8}, {"y":12, "x":7}, {"y":13, "x":7}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":12, "x":6}, {"y":13, "x":6}, {"y":14, "x":6}, {"y":15, "x":6}], "interactable":false, "getArea":null, "name":"Challenge 6", "setArea":null}, "8":{"centre":{"y":5, "x":9}, "id":8, "positions":[{"y":7, "x":11}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":10}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":4, "x":6}, {"y":5, "x":11}, {"y":4, "x":11}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":4, "x":7}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":5, "x":9}, {"y":5, "x":10}, {"y":5, "x":12}, {"y":4, "x":12}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":7, "x":7}], "interactable":false, "getArea":null, "name":"Challenge 7", "setArea":null}, "9":{"centre":{"y":5, "x":20}, "id":9, "positions":[{"y":6, "x":17}, {"y":7, "x":18}, {"y":6, "x":18}, {"y":5, "x":18}, {"y":4, "x":18}, {"y":5, "x":19}, {"y":6, "x":19}, {"y":6, "x":20}, {"y":6, "x":21}, {"y":6, "x":22}, {"y":6, "x":23}, {"y":5, "x":23}, {"y":5, "x":22}, {"y":5, "x":21}, {"y":5, "x":20}, {"y":4, "x":19}, {"y":4, "x":20}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":23}, {"y":4, "x":17}, {"y":5, "x":17}, {"y":7, "x":19}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":7, "x":22}], "interactable":false, "getArea":null, "name":"Challenge 8", "setArea":null}, "10":{"centre":{"y":9, "x":4}, "id":10, "positions":[{"y":7, "x":8}, {"y":8, "x":8}, {"y":9, "x":8}, {"y":10, "x":8}, {"y":11, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":12, "x":0}, {"y":11, "x":0}, {"y":10, "x":0}, {"y":9, "x":0}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":6, "x":1}, {"y":6, "x":2}, {"y":6, "x":3}, {"y":6, "x":4}, {"y":6, "x":5}, {"y":6, "x":6}, {"y":6, "x":7}, {"y":7, "x":7}, {"y":8, "x":7}, {"y":9, "x":7}, {"y":10, "x":7}, {"y":11, "x":7}, {"y":11, "x":6}, {"y":11, "x":5}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":10, "x":1}, {"y":9, "x":1}, {"y":8, "x":1}, {"y":7, "x":1}, {"y":7, "x":2}, {"y":7, "x":3}, {"y":7, "x":4}, {"y":7, "x":5}, {"y":7, "x":6}, {"y":8, "x":6}, {"y":9, "x":6}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":5, "x":2}, {"y":5, "x":1}, {"y":5, "x":0}, {"y":13, "x":5}, {"y":13, "x":4}, {"y":13, "x":3}, {"y":13, "x":2}, {"y":13, "x":1}, {"y":13, "x":0}, {"y":13, "x":6}, {"y":5, "x":6}], "interactable":false, "getArea":null, "name":"Challenge 9", "setArea":null}, "11":{"centre":{"y":2, "x":7}, "id":11, "positions":[{"y":4, "x":6}, {"y":4, "x":7}, {"y":4, "x":8}, {"y":4, "x":9}, {"y":4, "x":10}, {"y":4, "x":11}, {"y":4, "x":12}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":1, "x":14}, {"y":0, "x":14}, {"y":0, "x":13}, {"y":0, "x":12}, {"y":0, "x":11}, {"y":0, "x":10}, {"y":0, "x":9}, {"y":0, "x":8}, {"y":0, "x":7}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":2, "x":6}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":3, "x":8}, {"y":3, "x":9}, {"y":3, "x":10}, {"y":3, "x":11}, {"y":3, "x":12}, {"y":3, "x":13}, {"y":2, "x":13}, {"y":1, "x":13}, {"y":1, "x":12}, {"y":1, "x":11}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":5}, {"y":4, "x":4}, {"y":4, "x":3}, {"y":4, "x":2}, {"y":4, "x":1}, {"y":4, "x":0}, {"y":3, "x":0}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":1, "x":5}, {"y":2, "x":5}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}, {"y":2, "x":1}, {"y":1, "x":1}, {"y":1, "x":2}, {"y":1, "x":3}, {"y":1, "x":4}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":5, "x":2}, {"y":5, "x":1}, {"y":5, "x":0}], "interactable":false, "getArea":null, "name":"Challenge 10", "setArea":null}, "12":{"centre":{"y":2, "x":22}, "id":12, "positions":[{"y":4, "x":15}, {"y":3, "x":15}, {"y":2, "x":15}, {"y":1, "x":15}, {"y":0, "x":15}, {"y":0, "x":14}, {"y":1, "x":14}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":16}, {"y":4, "x":17}, {"y":4, "x":18}, {"y":4, "x":19}, {"y":4, "x":20}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":23}, {"y":4, "x":24}, {"y":4, "x":25}, {"y":4, "x":26}, {"y":4, "x":27}, {"y":4, "x":28}, {"y":4, "x":29}, {"y":5, "x":24}, {"y":5, "x":25}, {"y":5, "x":26}, {"y":5, "x":27}, {"y":5, "x":28}, {"y":5, "x":29}, {"y":3, "x":29}, {"y":3, "x":28}, {"y":3, "x":27}, {"y":3, "x":26}, {"y":3, "x":25}, {"y":3, "x":24}, {"y":3, "x":23}, {"y":3, "x":22}, {"y":3, "x":21}, {"y":3, "x":20}, {"y":3, "x":19}, {"y":3, "x":18}, {"y":3, "x":17}, {"y":3, "x":16}, {"y":2, "x":16}, {"y":1, "x":16}, {"y":0, "x":16}, {"y":0, "x":17}, {"y":0, "x":18}, {"y":0, "x":19}, {"y":0, "x":20}, {"y":0, "x":21}, {"y":0, "x":22}, {"y":0, "x":23}, {"y":0, "x":24}, {"y":0, "x":25}, {"y":0, "x":26}, {"y":0, "x":27}, {"y":0, "x":28}, {"y":0, "x":29}, {"y":2, "x":29}, {"y":2, "x":28}, {"y":2, "x":27}, {"y":2, "x":26}, {"y":2, "x":25}, {"y":2, "x":24}, {"y":2, "x":23}, {"y":2, "x":22}, {"y":2, "x":21}, {"y":2, "x":20}, {"y":2, "x":19}, {"y":2, "x":18}, {"y":2, "x":17}, {"y":1, "x":17}, {"y":1, "x":18}, {"y":1, "x":19}, {"y":1, "x":20}, {"y":1, "x":21}, {"y":1, "x":22}, {"y":1, "x":23}, {"y":1, "x":24}, {"y":1, "x":25}, {"y":1, "x":26}, {"y":1, "x":27}, {"y":1, "x":28}, {"y":1, "x":29}], "interactable":false, "getArea":null, "name":"Challenge 11", "setArea":null}, "13":{"centre":{"y":9, "x":25}, "id":13, "positions":[{"y":7, "x":21}, {"y":8, "x":21}, {"y":9, "x":21}, {"y":10, "x":21}, {"y":11, "x":21}, {"y":12, "x":22}, {"y":6, "x":22}, {"y":5, "x":23}, {"y":5, "x":24}, {"y":5, "x":25}, {"y":5, "x":26}, {"y":5, "x":27}, {"y":5, "x":28}, {"y":5, "x":29}, {"y":13, "x":23}, {"y":13, "x":24}, {"y":13, "x":25}, {"y":13, "x":26}, {"y":13, "x":27}, {"y":13, "x":28}, {"y":13, "x":29}, {"y":12, "x":29}, {"y":12, "x":28}, {"y":12, "x":27}, {"y":12, "x":26}, {"y":12, "x":25}, {"y":12, "x":24}, {"y":12, "x":23}, {"y":11, "x":23}, {"y":11, "x":22}, {"y":10, "x":22}, {"y":9, "x":22}, {"y":8, "x":22}, {"y":7, "x":22}, {"y":6, "x":23}, {"y":7, "x":23}, {"y":8, "x":23}, {"y":9, "x":23}, {"y":10, "x":23}, {"y":10, "x":24}, {"y":9, "x":24}, {"y":8, "x":24}, {"y":7, "x":24}, {"y":7, "x":25}, {"y":6, "x":24}, {"y":11, "x":24}, {"y":10, "x":25}, {"y":9, "x":25}, {"y":8, "x":25}, {"y":6, "x":25}, {"y":11, "x":25}, {"y":8, "x":26}, {"y":9, "x":26}, {"y":10, "x":26}, {"y":11, "x":26}, {"y":7, "x":26}, {"y":6, "x":26}, {"y":8, "x":27}, {"y":9, "x":27}, {"y":10, "x":27}, {"y":10, "x":28}, {"y":9, "x":28}, {"y":8, "x":28}, {"y":7, "x":28}, {"y":6, "x":28}, {"y":11, "x":27}, {"y":7, "x":27}, {"y":6, "x":27}, {"y":11, "x":28}, {"y":11, "x":29}, {"y":10, "x":29}, {"y":9, "x":29}, {"y":8, "x":29}, {"y":7, "x":29}, {"y":6, "x":29}], "interactable":false, "getArea":null, "name":"Challenge 12", "setArea":null}, "14":{"centre":{"y":16, "x":6}, "id":14, "positions":[{"y":15, "x":14}, {"y":16, "x":14}, {"y":17, "x":14}, {"y":18, "x":14}, {"y":19, "x":14}, {"y":15, "x":13}, {"y":15, "x":12}, {"y":15, "x":11}, {"y":15, "x":10}, {"y":15, "x":9}, {"y":15, "x":8}, {"y":15, "x":7}, {"y":15, "x":6}, {"y":15, "x":5}, {"y":15, "x":4}, {"y":15, "x":3}, {"y":15, "x":2}, {"y":15, "x":1}, {"y":15, "x":0}, {"y":14, "x":5}, {"y":13, "x":5}, {"y":13, "x":4}, {"y":13, "x":3}, {"y":13, "x":2}, {"y":13, "x":1}, {"y":13, "x":0}, {"y":14, "x":0}, {"y":14, "x":1}, {"y":14, "x":2}, {"y":14, "x":3}, {"y":14, "x":4}, {"y":14, "x":6}, {"y":16, "x":13}, {"y":16, "x":12}, {"y":16, "x":11}, {"y":16, "x":10}, {"y":16, "x":9}, {"y":16, "x":8}, {"y":16, "x":7}, {"y":16, "x":6}, {"y":16, "x":5}, {"y":16, "x":4}, {"y":16, "x":3}, {"y":16, "x":2}, {"y":16, "x":1}, {"y":16, "x":0}, {"y":17, "x":0}, {"y":18, "x":0}, {"y":19, "x":0}, {"y":19, "x":1}, {"y":19, "x":2}, {"y":19, "x":3}, {"y":19, "x":4}, {"y":19, "x":5}, {"y":19, "x":6}, {"y":19, "x":7}, {"y":19, "x":8}, {"y":19, "x":9}, {"y":19, "x":10}, {"y":19, "x":11}, {"y":19, "x":12}, {"y":19, "x":13}, {"y":18, "x":13}, {"y":17, "x":13}, {"y":17, "x":12}, {"y":17, "x":11}, {"y":17, "x":10}, {"y":17, "x":9}, {"y":17, "x":8}, {"y":17, "x":7}, {"y":17, "x":6}, {"y":17, "x":5}, {"y":17, "x":4}, {"y":17, "x":3}, {"y":17, "x":2}, {"y":17, "x":1}, {"y":18, "x":1}, {"y":18, "x":2}, {"y":18, "x":3}, {"y":18, "x":4}, {"y":18, "x":5}, {"y":18, "x":6}, {"y":18, "x":7}, {"y":18, "x":8}, {"y":18, "x":9}, {"y":18, "x":10}, {"y":18, "x":11}, {"y":18, "x":12}], "interactable":false, "getArea":null, "name":"Challenge 13", "setArea":null}, "15":{"centre":{"y":17, "x":22}, "id":15, "positions":[{"y":15, "x":15}, {"y":16, "x":15}, {"y":16, "x":14}, {"y":17, "x":14}, {"y":18, "x":14}, {"y":19, "x":14}, {"y":19, "x":15}, {"y":18, "x":15}, {"y":17, "x":15}, {"y":16, "x":16}, {"y":16, "x":17}, {"y":16, "x":18}, {"y":16, "x":19}, {"y":16, "x":20}, {"y":16, "x":21}, {"y":16, "x":22}, {"y":16, "x":23}, {"y":16, "x":24}, {"y":16, "x":25}, {"y":16, "x":26}, {"y":16, "x":27}, {"y":16, "x":28}, {"y":16, "x":29}, {"y":17, "x":29}, {"y":18, "x":29}, {"y":19, "x":29}, {"y":19, "x":28}, {"y":19, "x":27}, {"y":19, "x":26}, {"y":19, "x":25}, {"y":19, "x":24}, {"y":19, "x":23}, {"y":19, "x":22}, {"y":19, "x":21}, {"y":19, "x":20}, {"y":19, "x":19}, {"y":19, "x":18}, {"y":19, "x":17}, {"y":19, "x":16}, {"y":18, "x":16}, {"y":17, "x":16}, {"y":17, "x":17}, {"y":17, "x":18}, {"y":17, "x":19}, {"y":17, "x":20}, {"y":17, "x":21}, {"y":17, "x":22}, {"y":17, "x":23}, {"y":17, "x":24}, {"y":17, "x":25}, {"y":17, "x":26}, {"y":17, "x":27}, {"y":17, "x":28}, {"y":18, "x":28}, {"y":18, "x":18}, {"y":18, "x":17}, {"y":18, "x":19}, {"y":18, "x":20}, {"y":18, "x":21}, {"y":18, "x":22}, {"y":18, "x":23}, {"y":18, "x":24}, {"y":18, "x":25}, {"y":18, "x":26}, {"y":18, "x":27}, {"y":15, "x":16}, {"y":15, "x":17}, {"y":15, "x":18}, {"y":15, "x":19}, {"y":15, "x":20}, {"y":15, "x":21}, {"y":15, "x":22}, {"y":15, "x":23}, {"y":15, "x":24}, {"y":15, "x":25}, {"y":15, "x":26}, {"y":15, "x":27}, {"y":15, "x":28}, {"y":15, "x":29}, {"y":14, "x":29}, {"y":14, "x":28}, {"y":14, "x":27}, {"y":14, "x":26}, {"y":14, "x":25}, {"y":14, "x":24}, {"y":14, "x":23}, {"y":13, "x":24}, {"y":13, "x":29}, {"y":13, "x":28}, {"y":13, "x":27}, {"y":13, "x":26}, {"y":13, "x":25}], "interactable":false, "getArea":null, "name":"Challenge 14", "setArea":null}, "0":{"centre":{"y":9, "x":15}, "id":0, "positions":[{"y":8, "x":13}, {"y":8, "x":14}, {"y":8, "x":15}, {"y":8, "x":16}, {"y":9, "x":16}, {"y":10, "x":16}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":10, "x":13}, {"y":9, "x":13}], "interactable":false, "getArea":null, "name":"Start", "setArea":null}}, "Map_Tile_10_5":{"terrain":"abyss"}, "Map_Tile_19_3":{"terrain":"abyss"}, "Map_Tile_15_5":{"terrain":"abyss"}, "Map_Tile_1_8":{"terrain":"abyss"}, "Map_Tile_22_10":{"terrain":"abyss"}, "Map_Tile_17_19":{"terrain":"abyss"}, "Map_Tile_19_2":{"terrain":"abyss"}, "Map_Tile_0_7":{"terrain":"abyss"}, "Map_Tile_24_3":{"terrain":"abyss"}, "Map_Tile_16_5":{"terrain":"abyss"}, "Map_Tile_10_19":{"terrain":"abyss"}, "Map_Tile_2_17":{"terrain":"abyss"}, "Map_Tile_11_17":{"terrain":"abyss"}, "Map_Tile_15_13":{"terrain":"abyss"}, "Map_Tile_28_12":{"terrain":"abyss"}, "Map_Tile_18_6":{"terrain":"abyss"}, "Map_Tile_12_17":{"terrain":"abyss"}, "Map_Tile_16_15":{"terrain":"abyss"}, "Map_Tile_1_17":{"terrain":"abyss"}, "Map_Tile_22_16":{"terrain":"abyss"}, "Map_Tile_3_0":{"terrain":"abyss"}, "Map_Tile_11_6":{"terrain":"abyss"}, "Map_Tile_14_3":{"terrain":"abyss"}, "Map_Tile_2_6":{"terrain":"abyss"}, "Map_Tile_5_9":{"terrain":"abyss"}, "Map_Tile_25_17":{"terrain":"abyss"}, "Map_Tile_8_13":{"terrain":"abyss"}, "Map_Tile_19_7":{"terrain":"abyss"}, "Map_Size":{"y":20, "x":30}, "Map_Tile_13_6":{"terrain":"abyss"}, "Map_Tile_6_5":{"terrain":"abyss"}, "Map_Tile_21_9":{"terrain":"abyss"}, "Map_Tile_2_16":{"terrain":"abyss"}, "Map_Tile_22_15":{"terrain":"abyss"}, "Map_Tile_3_18":{"terrain":"abyss"}, "Map_Tile_21_4":{"terrain":"abyss"}, "Map_Tile_10_9":{"terrain":"abyss"}, "Map_Tile_5_6":{"terrain":"abyss"}, "Map_Tile_5_19":{"terrain":"abyss"}, "Map_Tile_27_5":{"terrain":"abyss"}, "Map_Tile_26_8":{"terrain":"abyss"}, "Map_Tile_2_19":{"terrain":"abyss"}, "Map_Tile_0_4":{"terrain":"abyss"}, "Map_Tile_10_13":{"terrain":"abyss"}, "Map_Tile_6_3":{"terrain":"abyss"}, "Map_Tile_26_14":{"terrain":"abyss"}, "Map_Tile_14_12":{"terrain":"abyss"}, "Map_Tile_7_1":{"terrain":"abyss"}, "Objectives":["Spawn 3 enemy strongholds.", "Kill an enemy stronghold with a golem.", "Win by eliminating an enemy stronghold."], "Map_Tile_12_9":{"terrain":"abyss"}, "Counters":{}, "Map_Tile_8_7":{"terrain":"abyss"}, "Map_Tile_3_2":{"terrain":"abyss"}, "Map_Tile_9_14":{"terrain":"abyss"}, "Map_Tile_0_9":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"abyss"}, "Map_Tile_26_11":{"terrain":"abyss"}, "Map_Tile_19_19":{"terrain":"abyss"}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_26_3":{"terrain":"abyss"}, "Map_Tile_15_1":{"terrain":"abyss"}, "Map_Tile_9_2":{"terrain":"abyss"}, "Map_Tile_15_6":{"terrain":"abyss"}, "Map_Tile_24_16":{"terrain":"abyss"}, "Map_Tile_13_14":{"terrain":"abyss"}, "Map_Tile_21_5":{"terrain":"abyss"}, "Map_Tile_10_12":{"terrain":"abyss"}, "Map_Tile_2_3":{"terrain":"abyss"}, "Map_Tile_4_8":{"terrain":"abyss"}, "Map_Tile_27_2":{"terrain":"abyss"}, "Map_Tile_7_19":{"terrain":"abyss"}, "Map_Tile_22_9":{"terrain":"abyss"}, "Map_Tile_3_1":{"terrain":"abyss"}, "Map_Tile_10_0":{"terrain":"abyss"}, "Map_Tile_5_2":{"terrain":"abyss"}, "Map_Tile_9_18":{"terrain":"abyss"}, "Map_Tile_6_15":{"terrain":"abyss"}, "Map_Tile_8_1":{"terrain":"abyss"}, "Map_Tile_23_19":{"terrain":"abyss"}, "Map_Tile_3_12":{"terrain":"abyss"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_6_13":{"terrain":"abyss"}, "Map_Tile_0_3":{"terrain":"abyss"}, "Map_Tile_27_1":{"terrain":"abyss"}, "Map_Tile_10_11":{"terrain":"abyss"}, "Map_Tile_5_1":{"terrain":"abyss"}, "Map_Tile_1_7":{"terrain":"abyss"}, "Map_Tile_29_12":{"terrain":"abyss"}, "Map_Tile_2_18":{"terrain":"abyss"}, "Map_Tile_8_4":{"terrain":"abyss"}, "Player_Count":2, "Map_Tile_29_15":{"terrain":"abyss"}, "Map_Tile_9_12":{"terrain":"abyss"}, "Player_1":{"recruit_caravel":true, "recruit_spearman":true, "recruit_archer":true, "recruit_dog":true, "recruit_giant":true, "recruit_soldier":true, "recruit_knight":true, "recruit_wagon":true, "recruit_trebuchet":true, "recruit_dragon":true, "recruit_merman":true, "recruit_travelboat":true, "recruit_harpoonship":true, "recruit_ballista":true, "recruit_harpy":true, "recruit_warship":true, "gold":100, "recruit_balloon":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_kraken":true, "recruit_witch":true, "recruit_turtle":true, "recruit_mage":true, "team":0, "recruit_frog":true, "recruit_griffin_walking":true}, "Map_Tile_21_12":{"terrain":"abyss"}, "Map_Tile_3_3":{"terrain":"abyss"}, "Map_Tile_12_19":{"terrain":"abyss"}, "Map_Tile_9_0":{"terrain":"abyss"}, "Map_Tile_28_8":{"terrain":"abyss"}, "Map_Tile_3_15":{"terrain":"abyss"}, "Map_Tile_8_0":{"terrain":"abyss"}, "Map_Tile_18_2":{"terrain":"abyss"}, "Map_Tile_12_2":{"terrain":"abyss"}, "Map_Tile_5_18":{"terrain":"abyss"}, "Map_Tile_10_2":{"terrain":"abyss"}, "Map_Tile_19_11":{"terrain":"abyss"}, "Map_Tile_8_14":{"terrain":"abyss"}, "Map_Tile_8_12":{"terrain":"abyss"}, "Map_Tile_22_18":{"terrain":"abyss"}, "Map_Tile_2_9":{"terrain":"abyss"}, "Map_Tile_20_18":{"terrain":"abyss"}, "Map_Tile_13_0":{"terrain":"abyss"}, "Map_Tile_1_4":{"terrain":"abyss"}, "Map_Tile_18_0":{"terrain":"abyss"}, "Map_Tile_28_15":{"terrain":"abyss"}, "Map_Tile_12_6":{"terrain":"abyss"}, "Map_Tile_0_8":{"terrain":"abyss"}, "Map_Tile_15_12":{"terrain":"abyss"}, "Map_Tile_19_9":{"terrain":"abyss"}, "Map_Tile_8_8":{"terrain":"abyss"}, "Map_Tile_19_15":{"terrain":"abyss"}, "Map_Tile_24_5":{"terrain":"abyss"}, "Map_Tile_8_9":{"terrain":"abyss"}, "Map_Tile_11_12":{"terrain":"abyss"}, "Map_Tile_14_17":{"terrain":"abyss"}, "Map_Tile_28_10":{"terrain":"abyss"}, "Map_Tile_5_11":{"terrain":"abyss"}, "Map_Tile_12_18":{"terrain":"abyss"}, "Map_Tile_23_5":{"terrain":"abyss"}, "Map_Tile_16_12":{"terrain":"abyss"}, "Map_Tile_5_12":{"terrain":"abyss"}, "Map_Tile_21_6":{"terrain":"abyss"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_8_15":{"terrain":"abyss"}, "Map_Name":"Ancient Discoveries", "Map_Tile_3_5":{"terrain":"abyss"}, "Map_Tile_17_17":{"terrain":"abyss"}, "Map_Tile_13_12":{"terrain":"abyss"}, "Map_Tile_15_14":{"terrain":"abyss"}, "Map_Tile_18_5":{"terrain":"abyss"}, "Map_Tile_27_0":{"terrain":"abyss"}, "Map_Tile_21_2":{"terrain":"abyss"}, "Map_Tile_0_12":{"terrain":"abyss"}, "Map_Tile_16_18":{"terrain":"abyss"}, "Map_Tile_4_13":{"terrain":"abyss"}, "Map_Tile_21_7":{"terrain":"abyss"}, "Map_Tile_20_12":{"terrain":"abyss"}, "Map_Tile_20_3":{"terrain":"abyss"}, "Map_Tile_12_13":{"terrain":"abyss"}, "Map_Tile_7_14":{"terrain":"abyss"}, "Map_Tile_23_16":{"terrain":"abyss"}, "Map_Tile_22_11":{"terrain":"abyss"}, "Map_Tile_13_4":{"terrain":"abyss"}, "Map_Tile_11_19":{"terrain":"abyss"}, "Map_Tile_28_19":{"terrain":"abyss"}, "Map_Tile_24_0":{"terrain":"abyss"}, "Map_Tile_27_10":{"terrain":"abyss"}, "Map_Tile_18_11":{"terrain":"abyss"}, "Map_Tile_23_9":{"terrain":"abyss"}, "Map_Tile_24_14":{"terrain":"abyss"}, "Map_Tile_27_3":{"terrain":"abyss"}, "Map_Tile_20_6":{"terrain":"abyss"}, "Map_Tile_9_7":{"terrain":"abyss"}, "Map_Tile_18_14":{"terrain":"abyss"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"abyss"}, "Map_Tile_29_14":{"terrain":"abyss"}, "Map_Tile_12_3":{"terrain":"abyss"}, "Map_Tile_23_8":{"terrain":"abyss"}, "Flags":{}, "Map_Tile_26_13":{"terrain":"abyss"}, "Map_Tile_16_16":{"terrain":"abyss"}, "Map_Tile_26_10":{"terrain":"abyss"}, "Map_Tile_11_11":{"terrain":"abyss"}, "Map_Tile_20_11":{"terrain":"abyss"}, "Map_Tile_15_15":{"terrain":"abyss"}, "Map_Tile_22_12":{"terrain":"abyss"}, "Map_Tile_20_14":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"abyss"}, "Map_Tile_17_9":{"terrain":"abyss"}, "Map_Tile_29_6":{"terrain":"abyss"}, "Map_Tile_13_18":{"terrain":"abyss"}, "Map_Tile_11_16":{"terrain":"abyss"}, "Map_Tile_17_15":{"terrain":"abyss"}, "Map_Tile_16_17":{"terrain":"abyss"}, "Map_Tile_4_18":{"terrain":"abyss"}, "Map_Tile_15_0":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"abyss"}, "Map_Tile_4_9":{"terrain":"abyss"}, "Map_Tile_8_3":{"terrain":"abyss"}, "Map_Tile_16_0":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"abyss"}, "Map_Tile_25_12":{"terrain":"abyss"}, "Map_Tile_11_1":{"terrain":"abyss"}, "Map_Tile_23_15":{"terrain":"abyss"}, "Map_Tile_22_2":{"terrain":"abyss"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_17_2":{"terrain":"abyss"}, "Map_Tile_23_11":{"terrain":"abyss"}, "Map_Tile_9_11":{"terrain":"abyss"}, "Map_Tile_26_18":{"terrain":"abyss"}, "Map_Tile_21_3":{"terrain":"abyss"}, "Map_Tile_13_16":{"terrain":"abyss"}, "Map_Tile_17_14":{"terrain":"abyss"}, "Map_Tile_4_5":{"terrain":"abyss"}, "Map_Tile_26_15":{"terrain":"abyss"}, "Map_Tile_22_14":{"terrain":"abyss"}, "Map_Tile_10_16":{"terrain":"abyss"}, "Map_Tile_4_12":{"terrain":"abyss"}, "Map_Tile_2_10":{"terrain":"abyss"}, "Map_Tile_11_7":{"terrain":"abyss"}, "Map_Tile_28_14":{"terrain":"abyss"}, "Map_Tile_6_2":{"terrain":"abyss"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_13_17":{"terrain":"abyss"}, "Map_Tile_2_13":{"terrain":"abyss"}, "Map_Tile_16_13":{"terrain":"abyss"}, "Map_Tile_16_19":{"terrain":"abyss"}, "Map_Tile_2_15":{"terrain":"abyss"}, "Map_Tile_13_10":{"terrain":"plains"}, "Author":"Fly Sniper", "Map_Tile_21_16":{"terrain":"abyss"}, "Map_Tile_24_8":{"terrain":"abyss"}, "Map_Tile_19_16":{"terrain":"abyss"}, "Map_Tile_27_15":{"terrain":"abyss"}, "Map_Tile_7_18":{"terrain":"abyss"}, "Map_Tile_20_5":{"terrain":"abyss"}, "Map_Tile_29_16":{"terrain":"abyss"}, "Map_Tile_10_15":{"terrain":"abyss"}, "Map_Tile_29_13":{"terrain":"abyss"}, "Map_Tile_29_11":{"terrain":"abyss"}, "Map_Tile_29_17":{"terrain":"abyss"}, "Map_Tile_27_8":{"terrain":"abyss"}, "Map_Tile_29_9":{"terrain":"abyss"}, "Map_Tile_21_10":{"terrain":"abyss"}, "Map_Tile_29_8":{"terrain":"abyss"}, "Map_Tile_2_8":{"terrain":"abyss"}, "Map_Tile_18_10":{"terrain":"abyss"}, "Map_Tile_29_7":{"terrain":"abyss"}, "Map_Tile_21_14":{"terrain":"abyss"}, "Map_Tile_5_5":{"terrain":"abyss"}, "Map_Tile_22_0":{"terrain":"abyss"}, "Map_Tile_29_2":{"terrain":"abyss"}, "Map_Tile_18_3":{"terrain":"abyss"}, "Map_Tile_17_12":{"terrain":"abyss"}, "Map_Tile_24_1":{"terrain":"abyss"}, "Map_Tile_29_0":{"terrain":"abyss"}, "Map_Tile_28_18":{"terrain":"abyss"}, "Map_Tile_28_17":{"terrain":"abyss"}, "Map_Tile_4_11":{"terrain":"abyss"}, "Map_Tile_1_5":{"terrain":"abyss"}, "Map_Tile_3_4":{"terrain":"abyss"}, "Map_Tile_21_15":{"terrain":"abyss"}, "Map_Tile_21_17":{"terrain":"abyss"}, "Map_Tile_8_6":{"terrain":"abyss"}, "Map_Tile_0_0":{"terrain":"abyss"}, "Map_Tile_24_18":{"terrain":"abyss"}, "Map_Tile_25_19":{"terrain":"abyss"}, "Map_Tile_23_18":{"terrain":"abyss"}, "Map_Tile_28_9":{"terrain":"abyss"}, "Map_Tile_15_18":{"terrain":"abyss"}, "Map_Tile_28_7":{"terrain":"abyss"}, "Map_Tile_18_8":{"terrain":"abyss"}, "Map_Tile_28_6":{"terrain":"abyss"}, "Map_Tile_1_11":{"terrain":"abyss"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_4_19":{"terrain":"abyss"}, "Map_Tile_28_5":{"terrain":"abyss"}, "Map_Tile_23_2":{"terrain":"abyss"}, "Map_Tile_28_3":{"terrain":"abyss"}, "Map_Tile_12_7":{"terrain":"abyss"}, "Map_Tile_28_2":{"terrain":"abyss"}, "Map_Tile_28_1":{"terrain":"abyss"}, "Map_Tile_7_3":{"terrain":"abyss"}, "Map_Tile_28_0":{"terrain":"abyss"}, "Map_Tile_9_16":{"terrain":"abyss"}, "Map_Tile_27_16":{"terrain":"abyss"}, "Map_Tile_29_18":{"terrain":"abyss"}, "Map_Tile_0_5":{"terrain":"abyss"}, "Map_Tile_27_13":{"terrain":"abyss"}, "Map_Tile_6_8":{"terrain":"abyss"}, "Map_Tile_27_12":{"terrain":"abyss"}, "Map_Tile_27_11":{"terrain":"abyss"}, "Map_Tile_27_9":{"terrain":"abyss"}, "Map_Tile_7_0":{"terrain":"abyss"}, "Map_Tile_0_17":{"terrain":"abyss"}, "Map_Tile_27_6":{"terrain":"abyss"}, "Map_Tile_18_19":{"terrain":"abyss"}, "Map_Tile_5_4":{"terrain":"abyss"}, "Map_Tile_26_16":{"terrain":"abyss"}, "Map_Tile_2_12":{"terrain":"abyss"}, "Map_Tile_6_17":{"terrain":"abyss"}, "Map_Tile_23_3":{"terrain":"abyss"}, "Map_Tile_27_19":{"terrain":"abyss"}, "Map_Tile_24_19":{"terrain":"abyss"}, "Map_Tile_5_17":{"terrain":"abyss"}, "Map_Tile_26_5":{"terrain":"abyss"}, "Map_Tile_26_4":{"terrain":"abyss"}, "Map_Tile_26_2":{"terrain":"abyss"}, "Map_Tile_9_6":{"terrain":"abyss"}, "Map_Tile_26_1":{"terrain":"abyss"}, "Map_Tile_15_16":{"terrain":"abyss"}, "Map_Tile_25_18":{"terrain":"abyss"}, "Map_Tile_25_16":{"terrain":"abyss"}, "Map_Tile_25_15":{"terrain":"abyss"}, "Map_Tile_25_14":{"terrain":"abyss"}, "Map_Tile_16_4":{"terrain":"abyss"}, "Map_Tile_25_13":{"terrain":"abyss"}, "Map_Tile_1_19":{"terrain":"abyss"}, "Map_Tile_22_19":{"terrain":"abyss"}, "Map_Tile_25_11":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"abyss"}, "Map_Tile_25_9":{"terrain":"abyss"}, "Map_Tile_18_17":{"terrain":"abyss"}, "Map_Tile_25_8":{"terrain":"abyss"}, "Map_Tile_14_2":{"terrain":"abyss"}, "Map_Tile_13_5":{"terrain":"abyss"}, "Map_Tile_25_6":{"terrain":"abyss"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_25_5":{"terrain":"abyss"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_2_7":{"terrain":"abyss"}, "Map_Tile_10_6":{"terrain":"abyss"}, "Map_Tile_14_19":{"terrain":"abyss"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"abyss"}, "Map_Tile_10_1":{"terrain":"abyss"}, "Map_Tile_13_7":{"terrain":"abyss"}, "Map_Tile_25_1":{"terrain":"abyss"}, "Map_Tile_3_10":{"terrain":"abyss"}, "Map_Tile_20_19":{"terrain":"abyss"}, "Map_Tile_2_11":{"terrain":"abyss"}, "Map_Tile_25_0":{"terrain":"abyss"}, "Map_Tile_26_7":{"terrain":"abyss"}, "Map_Tile_28_11":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"abyss"}, "Map_Tile_9_1":{"terrain":"abyss"}, "Map_Tile_6_19":{"terrain":"abyss"}, "Map_Tile_0_11":{"terrain":"abyss"}, "Map_Tile_1_10":{"terrain":"abyss"}, "Map_Tile_6_18":{"terrain":"abyss"}, "Map_Tile_14_7":{"terrain":"abyss"}, "Map_Tile_4_16":{"terrain":"abyss"}, "Map_Tile_24_17":{"terrain":"abyss"}, "Map_Tile_9_3":{"terrain":"abyss"}, "Map_Tile_26_19":{"terrain":"abyss"}, "Map_Tile_3_8":{"terrain":"abyss"}, "Map_Tile_28_16":{"terrain":"abyss"}, "Map_Tile_4_2":{"terrain":"abyss"}, "Map_Tile_14_14":{"terrain":"abyss"}, "Map_Tile_12_16":{"terrain":"abyss"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_9_17":{"terrain":"abyss"}, "Map_Tile_24_15":{"terrain":"abyss"}, "Map_Tile_3_16":{"terrain":"abyss"}, "Map_Tile_4_1":{"terrain":"abyss"}, "Map_Tile_29_19":{"terrain":"abyss"}, "Map_Tile_29_10":{"terrain":"abyss"}, "Map_Tile_24_11":{"terrain":"abyss"}, "Map_Tile_9_19":{"terrain":"abyss"}, "Map_Tile_24_10":{"terrain":"abyss"}, "Map_Tile_2_14":{"terrain":"abyss"}, "Map_Tile_26_9":{"terrain":"abyss"}, "Map_Tile_26_0":{"terrain":"abyss"}, "Map_Tile_24_7":{"terrain":"abyss"}, "Map_Tile_19_17":{"terrain":"abyss"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"abyss"}, "Map_Tile_23_0":{"terrain":"abyss"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_18_7":{"terrain":"abyss"}, "Map_Tile_5_13":{"terrain":"abyss"}, "Map_Tile_24_2":{"terrain":"abyss"}, "Map_Tile_3_6":{"terrain":"abyss"}, "Map_Tile_1_0":{"terrain":"abyss"}, "Map_Tile_24_4":{"terrain":"abyss"}, "Map_Tile_2_4":{"terrain":"abyss"}, "Map_Tile_19_8":{"terrain":"abyss"}, "Map_Tile_24_6":{"terrain":"abyss"}, "Map_Tile_0_2":{"terrain":"abyss"}, "Map_Tile_12_11":{"terrain":"abyss"}, "Map_Tile_29_1":{"terrain":"abyss"}, "Map_Tile_7_7":{"terrain":"abyss"}, "Map_Tile_7_6":{"terrain":"abyss"}, "Map_Tile_19_1":{"terrain":"abyss"}, "Map_Tile_23_17":{"terrain":"abyss"}, "Map_Tile_12_5":{"terrain":"abyss"}, "Map_Tile_29_5":{"terrain":"abyss"}, "Map_Tile_9_5":{"terrain":"abyss"}, "Map_Tile_10_7":{"terrain":"abyss"}, "Map_Tile_11_9":{"terrain":"abyss"}, "Map_Tile_23_12":{"terrain":"abyss"}, "Map_Tile_7_17":{"terrain":"abyss"}, "Map_Tile_14_16":{"terrain":"abyss"}, "Map_Tile_25_2":{"terrain":"abyss"}, "Map_Tile_23_7":{"terrain":"abyss"}, "Map_Tile_23_6":{"terrain":"abyss"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_0_1":{"terrain":"abyss"}, "Map_Tile_6_4":{"terrain":"abyss"}, "Map_Tile_19_4":{"terrain":"abyss"}, "Map_Tile_17_1":{"terrain":"abyss"}, "Map_Tile_3_17":{"terrain":"abyss"}, "Map_Tile_23_4":{"terrain":"abyss"}, "Map_Tile_1_2":{"terrain":"abyss"}, "Map_Tile_20_13":{"terrain":"abyss"}, "Map_Tile_26_12":{"terrain":"abyss"}, "Map_Tile_5_16":{"terrain":"abyss"}, "Map_Tile_5_3":{"terrain":"abyss"}, "Map_Tile_12_4":{"terrain":"abyss"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"abyss"}, "Map_Tile_22_17":{"terrain":"abyss"}, "Map_Tile_21_11":{"terrain":"abyss"}, "Map_Tile_2_1":{"terrain":"abyss"}, "Map_Tile_7_16":{"terrain":"abyss"}, "Map_Tile_24_13":{"terrain":"abyss"}, "Map_Tile_22_13":{"terrain":"abyss"}, "Map_Tile_10_4":{"terrain":"abyss"}, "Map_Tile_8_18":{"terrain":"abyss"}, "Map_Tile_14_5":{"terrain":"abyss"}, "Map_Tile_19_13":{"terrain":"abyss"}, "Map_Tile_10_14":{"terrain":"abyss"}, "Map_Tile_11_5":{"terrain":"abyss"}, "Map_Tile_22_7":{"terrain":"abyss"}, "Map_Tile_9_13":{"terrain":"abyss"}, "Map_Tile_22_3":{"terrain":"abyss"}, "Map_Tile_22_1":{"terrain":"abyss"}, "Map_Tile_29_3":{"terrain":"abyss"}, "Map_Tile_8_11":{"terrain":"abyss"}, "Map_Tile_12_14":{"terrain":"abyss"}, "Map_Tile_15_3":{"terrain":"abyss"}, "Map_Tile_21_13":{"terrain":"abyss"}, "Map_Tile_10_3":{"terrain":"abyss"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_21_1":{"terrain":"abyss"}, "Map_Tile_0_19":{"terrain":"abyss"}, "Map_Tile_12_1":{"terrain":"abyss"}, "Map_Tile_6_14":{"terrain":"abyss"}, "Map_Tile_10_18":{"terrain":"abyss"}, "Map_Tile_4_15":{"terrain":"abyss"}, "Map_Tile_6_6":{"terrain":"abyss"}, "Map_Tile_13_3":{"terrain":"abyss"}, "Map_Tile_17_3":{"terrain":"abyss"}, "Map_Tile_11_0":{"terrain":"abyss"}, "Map_Tile_20_15":{"terrain":"abyss"}, "Map_Tile_20_10":{"terrain":"abyss"}, "Map_Tile_20_9":{"terrain":"abyss"}, "Map_Tile_20_7":{"terrain":"abyss"}, "Map_Tile_20_4":{"terrain":"abyss"}, "Map_Tile_20_0":{"terrain":"abyss"}, "Map_Tile_0_10":{"terrain":"abyss"}, "Map_Tile_15_2":{"terrain":"abyss"}, "Map_Tile_3_14":{"terrain":"abyss"}, "Map_Tile_1_3":{"terrain":"abyss"}, "Map_Tile_5_7":{"terrain":"abyss"}, "Map_Tile_14_6":{"terrain":"abyss"}, "Map_Tile_20_16":{"terrain":"abyss"}, "Map_Tile_6_12":{"terrain":"abyss"}, "Map_Tile_19_10":{"terrain":"abyss"}, "Map_Tile_23_14":{"terrain":"abyss"}, "Map_Tile_16_2":{"terrain":"abyss"}, "Map_Tile_1_18":{"terrain":"abyss"}, "Map_Tile_19_6":{"terrain":"abyss"}, "Map_Tile_0_16":{"terrain":"abyss"}, "Map_Tile_4_17":{"terrain":"abyss"}, "Map_Tile_4_0":{"terrain":"abyss"}, "Map_Tile_22_6":{"terrain":"abyss"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Player_2":{"recruit_caravel":true, "recruit_spearman":true, "recruit_archer":true, "recruit_dog":true, "recruit_giant":false, "recruit_soldier":true, "recruit_knight":false, "recruit_wagon":false, "recruit_trebuchet":false, "recruit_dragon":false, "recruit_merman":true, "recruit_travelboat":false, "recruit_harpoonship":true, "recruit_ballista":false, "recruit_harpy":false, "recruit_warship":false, "gold":100, "recruit_balloon":false, "recruit_thief":true, "recruit_rifleman":true, "recruit_kraken":false, "recruit_witch":true, "recruit_turtle":true, "recruit_mage":true, "team":1, "recruit_frog":true, "recruit_griffin_walking":true}, "Map_Tile_13_19":{"terrain":"abyss"}, "Map_Tile_19_0":{"terrain":"abyss"}, "Map_Tile_12_8":{"terrain":"abyss"}, "Map_Tile_22_4":{"terrain":"abyss"}, "Map_Tile_7_15":{"terrain":"abyss"}, "Map_Tile_27_4":{"terrain":"abyss"}, "Map_Tile_1_13":{"terrain":"abyss"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_18_18":{"terrain":"abyss"}, "Map_Tile_18_16":{"terrain":"abyss"}, "Map_Tile_18_15":{"terrain":"abyss"}, "Map_Tile_8_19":{"terrain":"abyss"}, "Map_Tile_18_13":{"terrain":"abyss"}, "Map_Tile_18_12":{"terrain":"abyss"}, "Map_Tile_25_3":{"terrain":"abyss"}, "Map_Tile_1_15":{"terrain":"abyss"}, "Map_Tile_18_4":{"terrain":"abyss"}, "Map_Tile_17_16":{"terrain":"abyss"}, "Map_Tile_17_11":{"terrain":"abyss"}, "Map_Tile_17_7":{"terrain":"abyss"}, "Map_Tile_1_16":{"terrain":"abyss"}, "Map_Tile_16_1":{"terrain":"abyss"}, "Map_Tile_15_7":{"terrain":"abyss"}, "Map_Tile_5_8":{"terrain":"abyss"}, "Map_Tile_14_15":{"terrain":"abyss"}, "Map_Tile_7_4":{"terrain":"abyss"}, "Map_Tile_13_1":{"terrain":"abyss"}, "Map_Tile_21_18":{"terrain":"abyss"}, "Map_Tile_1_14":{"terrain":"abyss"}, "Map_Tile_9_15":{"terrain":"abyss"}, "Map_Tile_23_13":{"terrain":"abyss"}, "Map_Tile_2_2":{"terrain":"abyss"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Majestic_Mountain.json b/worlds/wargroove2/levels/Majestic_Mountain.json index f36c7e0d3f44..1981841b5cec 100644 --- a/worlds/wargroove2/levels/Majestic_Mountain.json +++ b/worlds/wargroove2/levels/Majestic_Mountain.json @@ -1 +1 @@ -{"Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"mountain"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"mountain"}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"mountain"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"mountain"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"mountain"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"mountain"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Objectives":["Grab the hiking boots.", "Get a legendary Air Trooper crit.", "Win with standard conditions."], "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"mountain", "item":{"itemId":1, "isConsumable":false, "pos":{"y":2, "x":12}, "type":"hiking_boots", "unitTypeRestriction":{}}}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"mountain"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"mountain"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"plains"}, "Flags":{}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Player_1":{"recruit_frog":true, "recruit_spearman":true, "recruit_archer":true, "recruit_turtle":true, "recruit_wagon":true, "gold":100, "recruit_knight":true, "recruit_witch":true, "recruit_warship":true, "recruit_griffin_walking":true, "recruit_trebuchet":true, "recruit_caravel":true, "team":0, "recruit_kraken":true, "recruit_merman":true, "recruit_travelboat":true, "recruit_soldier":true, "recruit_dragon":true, "recruit_ballista":true, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_dog":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_mage":true}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Size":{"y":15, "x":17}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"mountain"}, "Map_Tile_16_0":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Name":"Majestic Mountain", "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"mountain"}, "Map_Tile_10_6":{"terrain":"mountain"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"mountain"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"mountain"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"mountain"}, "Map_Tile_9_6":{"terrain":"mountain"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"mountain"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"mountain"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Counters":{}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Triggers":[{"actions":[{"enabled":true, "parameters":["98476", "Majestic Mountain", "Fly Sniper", "Grab the hiking boots.", "Get a legendary Air Trooper crit.", "", "Win with standard conditions."], "id":"ap_export"}], "conditions":{}, "recurring":"start_of_match", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"AP: Export"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_defeat_no_units"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_defeat_commander"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_defeat_hq"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "recurring":"oncePerPlayer", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"$trigger_default_victory"}, {"actions":[{"enabled":true, "parameters":["0", "-10", "0", "0", "0", "50", "4", "2", "0"], "id":"map_randomize"}, {"enabled":true, "parameters":["3", "0", "0", "0", "0", "0", "2", "5", "0"], "id":"position_asymmetric_randomize"}, {"enabled":true, "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["spearman", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["mage", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["spearman", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["mage", "4", "P2", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["frog", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["harpy", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["tower", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["tower", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}], "conditions":{}, "recurring":"start_of_match", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"Generate Map"}, {"actions":[{"enabled":true, "parameters":["253051"], "id":"ap_location_send"}], "conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "recurring":"end_of_match", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"P1 Victorious (253051)"}, {"actions":[{"enabled":true, "parameters":["253052"], "id":"ap_location_send"}], "conditions":[{"enabled":true, "parameters":["current", "*unit", "*item", "-1"], "id":"unit_item_presence"}], "recurring":"once", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"Dog Kills Knight (253052)"}, {"actions":[{"enabled":true, "parameters":["253053"], "id":"ap_location_send"}], "conditions":[{"enabled":true, "parameters":["current", "1", "0", "griffin_walking", "-10"], "id":"unit_presence"}], "recurring":"once", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "id":"P1 Air Trooper crit (253053)"}], "Locations":{"1":{"getArea":null, "name":"P1 Stronghold Location", "positions":[{"y":10, "x":6}, {"y":11, "x":6}, {"y":10, "x":5}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":11, "x":5}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":10, "x":4}, {"y":9, "x":4}, {"y":8, "x":4}], "setArea":null, "interactable":false, "centre":{"y":9, "x":5}, "id":1}, "2":{"getArea":null, "name":"P2 Stronghold Location", "positions":[{"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":9, "x":13}, {"y":8, "x":12}, {"y":9, "x":14}, {"y":8, "x":15}], "setArea":null, "interactable":false, "centre":{"y":8, "x":13}, "id":2}, "3":{"getArea":null, "name":"P1 Starting Zone", "positions":[{"y":0, "x":3}, {"y":1, "x":3}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":1, "x":5}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":1, "x":4}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":7}, {"y":2, "x":6}, {"y":0, "x":7}, {"y":2, "x":7}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":5}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":3, "x":2}, {"y":0, "x":2}, {"y":1, "x":2}, {"y":2, "x":2}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":13, "x":2}, {"y":14, "x":1}, {"y":14, "x":0}, {"y":13, "x":0}, {"y":12, "x":0}, {"y":14, "x":2}], "setArea":null, "interactable":false, "centre":{"y":5, "x":4}, "id":3}, "4":{"getArea":null, "name":"P2 Starting Zone", "positions":[{"y":0, "x":15}, {"y":0, "x":16}, {"y":1, "x":16}, {"y":2, "x":16}, {"y":3, "x":16}, {"y":3, "x":15}, {"y":4, "x":15}, {"y":2, "x":15}, {"y":1, "x":15}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":3, "x":13}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":16}, {"y":14, "x":16}, {"y":14, "x":15}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":13, "x":13}, {"y":12, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":13, "x":16}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}], "setArea":null, "interactable":false, "centre":{"y":8, "x":15}, "id":4}, "0":{"getArea":null, "name":"Land", "positions":[{"y":0, "x":11}, {"y":0, "x":10}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":2, "x":9}, {"y":2, "x":8}, {"y":2, "x":7}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":2, "x":1}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":0, "x":7}, {"y":0, "x":8}, {"y":0, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":1, "x":4}, {"y":1, "x":3}, {"y":1, "x":2}, {"y":1, "x":1}, {"y":3, "x":0}, {"y":4, "x":0}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":9, "x":0}, {"y":10, "x":0}, {"y":11, "x":0}, {"y":12, "x":0}, {"y":13, "x":0}, {"y":14, "x":0}, {"y":14, "x":1}, {"y":14, "x":2}, {"y":14, "x":3}, {"y":14, "x":4}, {"y":14, "x":5}, {"y":14, "x":6}, {"y":14, "x":7}, {"y":14, "x":8}, {"y":14, "x":9}, {"y":14, "x":10}, {"y":14, "x":11}, {"y":14, "x":12}, {"y":14, "x":13}, {"y":14, "x":14}, {"y":14, "x":15}, {"y":14, "x":16}, {"y":13, "x":16}, {"y":12, "x":16}, {"y":11, "x":16}, {"y":10, "x":16}, {"y":9, "x":16}, {"y":8, "x":16}, {"y":7, "x":16}, {"y":6, "x":16}, {"y":5, "x":16}, {"y":4, "x":16}, {"y":3, "x":16}, {"y":2, "x":16}, {"y":1, "x":16}, {"y":0, "x":16}, {"y":0, "x":15}, {"y":1, "x":15}, {"y":2, "x":15}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":3, "x":15}, {"y":3, "x":13}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":4, "x":15}, {"y":5, "x":13}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":5, "x":11}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":6, "x":15}, {"y":7, "x":15}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":7, "x":12}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":13}, {"y":6, "x":14}, {"y":7, "x":11}, {"y":7, "x":10}, {"y":8, "x":10}, {"y":8, "x":9}, {"y":8, "x":8}, {"y":8, "x":7}, {"y":7, "x":7}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":4, "x":8}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":3, "x":6}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}, {"y":4, "x":1}, {"y":5, "x":1}, {"y":6, "x":1}, {"y":7, "x":1}, {"y":8, "x":1}, {"y":9, "x":1}, {"y":10, "x":1}, {"y":11, "x":1}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":13, "x":2}, {"y":13, "x":3}, {"y":13, "x":4}, {"y":13, "x":5}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":13, "x":8}, {"y":13, "x":9}, {"y":13, "x":10}, {"y":13, "x":11}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":12, "x":15}, {"y":11, "x":15}, {"y":10, "x":15}, {"y":9, "x":15}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":8, "x":12}, {"y":8, "x":11}, {"y":9, "x":7}, {"y":9, "x":8}, {"y":9, "x":9}, {"y":9, "x":10}, {"y":9, "x":11}, {"y":9, "x":12}, {"y":9, "x":13}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":12, "x":14}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":10}, {"y":11, "x":10}, {"y":11, "x":11}, {"y":10, "x":12}, {"y":10, "x":13}, {"y":11, "x":13}, {"y":11, "x":12}, {"y":10, "x":11}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":11, "x":9}, {"y":10, "x":10}, {"y":11, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":11, "x":7}, {"y":10, "x":9}, {"y":11, "x":6}, {"y":10, "x":7}, {"y":10, "x":8}, {"y":11, "x":5}, {"y":10, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":10, "x":2}, {"y":10, "x":3}, {"y":10, "x":4}, {"y":10, "x":5}, {"y":12, "x":2}, {"y":9, "x":6}, {"y":9, "x":2}, {"y":9, "x":3}, {"y":9, "x":4}, {"y":9, "x":5}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":6, "x":2}, {"y":5, "x":2}, {"y":4, "x":2}, {"y":4, "x":3}, {"y":4, "x":4}, {"y":4, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":6, "x":3}, {"y":7, "x":3}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":7, "x":4}, {"y":6, "x":4}, {"y":5, "x":5}, {"y":6, "x":5}, {"y":7, "x":5}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":7, "x":6}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":4, "x":7}, {"y":4, "x":6}], "setArea":null, "interactable":false, "centre":{"y":7, "x":8}, "id":0}}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_13_2":{"terrain":"mountain"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Player_2":{"recruit_frog":true, "recruit_spearman":true, "recruit_archer":true, "recruit_turtle":true, "recruit_wagon":false, "gold":100, "recruit_knight":true, "recruit_witch":true, "recruit_warship":false, "recruit_griffin_walking":true, "recruit_trebuchet":false, "recruit_caravel":true, "team":1, "recruit_kraken":false, "recruit_merman":true, "recruit_travelboat":false, "recruit_soldier":true, "recruit_dragon":true, "recruit_ballista":false, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_dog":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_mage":true}, "Author":"Fly Sniper", "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"mountain"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"mountain"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"mountain"}} \ No newline at end of file +{"Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"mountain"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"mountain"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"plains"}, "Counters":{}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"mountain"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"mountain"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Size":{"y":15, "x":17}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Name":"Majestic Mountain", "Map_Tile_10_3":{"terrain":"mountain"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"mountain"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"mountain"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"mountain"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"mountain"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains"}, "Triggers":[{"enabled":true, "actions":[{"id":"ap_export", "parameters":["79216", "Majestic Mountain", "Fly Sniper", "Grab the hiking boots.", "Get a legendary Air Trooper crit.", "", "Win with standard conditions."], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"AP: Export", "conditions":{}, "isIntro":false}, {"enabled":true, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true}], "isIntro":false}, {"enabled":true, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}], "isIntro":false}, {"enabled":true, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}], "isIntro":false}, {"enabled":true, "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}], "isIntro":false}, {"enabled":true, "actions":[{"id":"map_randomize", "parameters":["0", "-10", "0", "0", "0", "50", "4", "2", "0"], "enabled":true}, {"id":"position_asymmetric_randomize", "parameters":["3", "0", "0", "0", "0", "0", "2", "5", "0"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["spearman", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["mage", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["spearman", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["mage", "4", "P2", "1", "1", "3", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["frog", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["harpy", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["barracks", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["barracks", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["tower", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["tower", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["portal", "0", "neutral", "1", "1", "3", "1", "undefined", "centre"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"Generate Map", "conditions":{}, "isIntro":false}, {"enabled":true, "actions":[{"id":"ap_location_send", "parameters":["253051"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"end_of_match", "id":"P1 Victorious (253051)", "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}], "isIntro":false}, {"enabled":true, "actions":[{"id":"ap_location_send", "parameters":["253052"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Dog Kills Knight (253052)", "conditions":[{"id":"unit_item_presence", "parameters":["current", "*unit", "*item", "-1"], "enabled":true}], "isIntro":false}, {"enabled":true, "actions":[{"id":"ap_location_send", "parameters":["253053"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"P1 Air Trooper crit (253053)", "conditions":[{"id":"unit_presence", "parameters":["current", "1", "0", "griffin_walking", "-10"], "enabled":true}], "isIntro":false}], "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"plains"}, "Locations":{"1":{"centre":{"y":9, "x":5}, "name":"P1 Stronghold Location", "setArea":null, "getArea":null, "id":1, "positions":[{"y":10, "x":6}, {"y":11, "x":6}, {"y":10, "x":5}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":11, "x":5}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":10, "x":4}, {"y":9, "x":4}, {"y":8, "x":4}], "interactable":false}, "2":{"centre":{"y":8, "x":13}, "name":"P2 Stronghold Location", "setArea":null, "getArea":null, "id":2, "positions":[{"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":9, "x":13}, {"y":8, "x":12}, {"y":9, "x":14}, {"y":8, "x":15}], "interactable":false}, "3":{"centre":{"y":5, "x":4}, "name":"P1 Starting Zone", "setArea":null, "getArea":null, "id":3, "positions":[{"y":0, "x":3}, {"y":1, "x":3}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":1, "x":5}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":1, "x":4}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":7}, {"y":2, "x":6}, {"y":0, "x":7}, {"y":2, "x":7}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":5}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":3, "x":2}, {"y":0, "x":2}, {"y":1, "x":2}, {"y":2, "x":2}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":13, "x":2}, {"y":14, "x":1}, {"y":14, "x":0}, {"y":13, "x":0}, {"y":12, "x":0}, {"y":14, "x":2}], "interactable":false}, "4":{"centre":{"y":8, "x":15}, "name":"P2 Starting Zone", "setArea":null, "getArea":null, "id":4, "positions":[{"y":0, "x":15}, {"y":0, "x":16}, {"y":1, "x":16}, {"y":2, "x":16}, {"y":3, "x":16}, {"y":3, "x":15}, {"y":4, "x":15}, {"y":2, "x":15}, {"y":1, "x":15}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":3, "x":13}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":4, "x":16}, {"y":14, "x":16}, {"y":14, "x":15}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":13, "x":13}, {"y":12, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":13, "x":16}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}], "interactable":false}, "0":{"centre":{"y":7, "x":8}, "name":"Land", "setArea":null, "getArea":null, "id":0, "positions":[{"y":0, "x":11}, {"y":0, "x":10}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":2, "x":9}, {"y":2, "x":8}, {"y":2, "x":7}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":2, "x":1}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":0, "x":7}, {"y":0, "x":8}, {"y":0, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":1, "x":4}, {"y":1, "x":3}, {"y":1, "x":2}, {"y":1, "x":1}, {"y":3, "x":0}, {"y":4, "x":0}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":9, "x":0}, {"y":10, "x":0}, {"y":11, "x":0}, {"y":12, "x":0}, {"y":13, "x":0}, {"y":14, "x":0}, {"y":14, "x":1}, {"y":14, "x":2}, {"y":14, "x":3}, {"y":14, "x":4}, {"y":14, "x":5}, {"y":14, "x":6}, {"y":14, "x":7}, {"y":14, "x":8}, {"y":14, "x":9}, {"y":14, "x":10}, {"y":14, "x":11}, {"y":14, "x":12}, {"y":14, "x":13}, {"y":14, "x":14}, {"y":14, "x":15}, {"y":14, "x":16}, {"y":13, "x":16}, {"y":12, "x":16}, {"y":11, "x":16}, {"y":10, "x":16}, {"y":9, "x":16}, {"y":8, "x":16}, {"y":7, "x":16}, {"y":6, "x":16}, {"y":5, "x":16}, {"y":4, "x":16}, {"y":3, "x":16}, {"y":2, "x":16}, {"y":1, "x":16}, {"y":0, "x":16}, {"y":0, "x":15}, {"y":1, "x":15}, {"y":2, "x":15}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":3, "x":15}, {"y":3, "x":13}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":4, "x":15}, {"y":5, "x":13}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":5, "x":11}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":6, "x":15}, {"y":7, "x":15}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":7, "x":12}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":13}, {"y":6, "x":14}, {"y":7, "x":11}, {"y":7, "x":10}, {"y":8, "x":10}, {"y":8, "x":9}, {"y":8, "x":8}, {"y":8, "x":7}, {"y":7, "x":7}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":4, "x":8}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":3, "x":6}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}, {"y":4, "x":1}, {"y":5, "x":1}, {"y":6, "x":1}, {"y":7, "x":1}, {"y":8, "x":1}, {"y":9, "x":1}, {"y":10, "x":1}, {"y":11, "x":1}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":13, "x":2}, {"y":13, "x":3}, {"y":13, "x":4}, {"y":13, "x":5}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":13, "x":8}, {"y":13, "x":9}, {"y":13, "x":10}, {"y":13, "x":11}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":13, "x":14}, {"y":13, "x":15}, {"y":12, "x":15}, {"y":11, "x":15}, {"y":10, "x":15}, {"y":9, "x":15}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":8, "x":12}, {"y":8, "x":11}, {"y":9, "x":7}, {"y":9, "x":8}, {"y":9, "x":9}, {"y":9, "x":10}, {"y":9, "x":11}, {"y":9, "x":12}, {"y":9, "x":13}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":12, "x":14}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":10}, {"y":11, "x":10}, {"y":11, "x":11}, {"y":10, "x":12}, {"y":10, "x":13}, {"y":11, "x":13}, {"y":11, "x":12}, {"y":10, "x":11}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":11, "x":9}, {"y":10, "x":10}, {"y":11, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":11, "x":7}, {"y":10, "x":9}, {"y":11, "x":6}, {"y":10, "x":7}, {"y":10, "x":8}, {"y":11, "x":5}, {"y":10, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":10, "x":2}, {"y":10, "x":3}, {"y":10, "x":4}, {"y":10, "x":5}, {"y":12, "x":2}, {"y":9, "x":6}, {"y":9, "x":2}, {"y":9, "x":3}, {"y":9, "x":4}, {"y":9, "x":5}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":6, "x":2}, {"y":5, "x":2}, {"y":4, "x":2}, {"y":4, "x":3}, {"y":4, "x":4}, {"y":4, "x":5}, {"y":5, "x":4}, {"y":5, "x":3}, {"y":6, "x":3}, {"y":7, "x":3}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":7, "x":4}, {"y":6, "x":4}, {"y":5, "x":5}, {"y":6, "x":5}, {"y":7, "x":5}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":7, "x":6}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":4, "x":7}, {"y":4, "x":6}, {"y":6, "x":9}, {"y":4, "x":10}, {"y":3, "x":11}, {"y":0, "x":12}, {"y":4, "x":9}], "interactable":false}}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Player_1":{"recruit_archer":true, "recruit_turtle":true, "recruit_balloon":true, "recruit_ballista":true, "recruit_trebuchet":true, "recruit_dog":true, "recruit_travelboat":true, "gold":100, "recruit_giant":true, "recruit_wagon":true, "recruit_warship":true, "recruit_witch":true, "recruit_dragon":true, "recruit_harpy":true, "recruit_frog":true, "recruit_griffin_walking":true, "recruit_caravel":true, "recruit_spearman":true, "recruit_soldier":true, "recruit_rifleman":true, "recruit_kraken":true, "recruit_knight":true, "recruit_mage":true, "recruit_thief":true, "recruit_harpoonship":true, "team":0, "recruit_merman":true}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Flags":{}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"mountain", "item":{"isConsumable":false, "itemId":1, "type":"hiking_boots", "unitTypeRestriction":{}, "pos":{"y":2, "x":12}}}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Author":"Fly Sniper", "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"mountain"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"mountain"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"mountain"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"mountain"}, "Player_2":{"recruit_archer":true, "recruit_turtle":true, "recruit_balloon":true, "recruit_ballista":false, "recruit_trebuchet":false, "recruit_dog":true, "recruit_travelboat":false, "gold":100, "recruit_giant":true, "recruit_wagon":false, "recruit_warship":false, "recruit_witch":true, "recruit_dragon":true, "recruit_harpy":true, "recruit_frog":true, "recruit_griffin_walking":true, "recruit_caravel":true, "recruit_spearman":true, "recruit_soldier":true, "recruit_rifleman":true, "recruit_kraken":false, "recruit_knight":true, "recruit_mage":true, "recruit_thief":true, "recruit_harpoonship":true, "team":1, "recruit_merman":true}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"mountain"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"mountain"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Objectives":["Grab the hiking boots.", "Get a legendary Air Trooper crit.", "Win with standard conditions."], "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"mountain"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"mountain"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"mountain"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Observation_Isle.json b/worlds/wargroove2/levels/Observation_Isle.json index 391276690115..68a2388c8426 100644 --- a/worlds/wargroove2/levels/Observation_Isle.json +++ b/worlds/wargroove2/levels/Observation_Isle.json @@ -1 +1 @@ -{"Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_7_11":{"terrain":"beach"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"sea"}, "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"sea"}, "Map_Tile_9_1":{"terrain":"sea"}, "Map_Tile_6_16":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_18_8":{"terrain":"sea"}, "Map_Tile_12_9":{"terrain":"sea"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_11_16":{"terrain":"plains"}, "Triggers":[{"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"AP: Export", "enabled":true, "actions":[{"id":"ap_export", "enabled":true, "parameters":["161792", "Observation Isle", "Fly Sniper", "Step on Observation Isle (No Requirements).", "Kill player 3's commander (Requires Walls event).", "", "Win with standard conditions."]}], "conditions":{}}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"$trigger_default_defeat_no_units", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"]}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"$trigger_default_defeat_commander", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["*commander", "current", "-1"]}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"$trigger_default_defeat_hq", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["hq", "current", "-1"]}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Victory (One Opponent Left)", "enabled":true, "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "conditions":[{"id":"number_of_opponents", "enabled":true, "parameters":["current", "3", "2"]}]}, {"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Generate Map", "enabled":true, "actions":[{"id":"map_randomize", "enabled":true, "parameters":["0", "-10", "0", "0", "50", "0", "0", "0", "0"]}, {"id":"map_randomize", "enabled":true, "parameters":["5", "-12", "0", "0", "0", "50", "10", "5", "0"]}, {"id":"position_asymmetric_randomize", "enabled":true, "parameters":["3", "0", "0", "0", "0", "0", "0", "8", "0"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["barracks", "3", "P1", "1", "1", "2", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["barracks", "4", "P2", "1", "1", "2", "1", "undefined", "centre"]}, {"id":"modify_health", "enabled":true, "parameters":["*structure", "-1", "any", "0", "100"]}], "conditions":{}}, {"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Walls Crumble", "enabled":true, "actions":[{"id":"play_sound_effect", "enabled":true, "parameters":["bellToll", "6"]}, {"id":"screenshake", "enabled":true, "parameters":["3000", "2", "2", "5"]}, {"id":"wait", "enabled":true, "parameters":["3000"]}, {"id":"map_randomize", "enabled":true, "parameters":["6", "-10", "0", "0", "0", "50", "0", "0", "0"]}], "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252024", "0", "1"]}]}, {"recurring":"end_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"P1 Victorious (253048)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253048"]}], "conditions":[{"id":"player_victorious", "enabled":true, "parameters":["current"]}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Become the Watcher (253049)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253049"]}], "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["P1", "1", "0", "*unit_structure", "6"]}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "id":"Execute the Watcher (253050)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253050"]}], "conditions":[{"id":"unit_killed", "enabled":true, "parameters":["*unit", "P1", "*commander", "P3", "-1"]}]}], "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_0_15":{"terrain":"bridge"}, "Map_Tile_13_6":{"terrain":"bridge"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"wall"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"sea"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"sea"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_3_16":{"terrain":"bridge"}, "Map_Tile_15_9":{"terrain":"sea"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"sea"}, "Map_Tile_12_2":{"terrain":"beach"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"sea"}, "Map_Tile_18_4":{"terrain":"wall"}, "Map_Tile_4_12":{"terrain":"beach"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"sea"}, "Map_Tile_9_5":{"terrain":"bridge"}, "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_4_15":{"terrain":"beach"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_17_1":{"terrain":"wall"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"beach"}, "Map_Tile_16_0":{"terrain":"wall"}, "Map_Tile_5_11":{"terrain":"beach"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"sea"}, "Author":"Fly Sniper", "Map_Tile_5_13":{"terrain":"beach"}, "Map_Tile_10_10":{"terrain":"sea"}, "Map_Tile_17_3":{"terrain":"wall"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"wall"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"beach"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"sea"}, "Map_Tile_15_15":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"sea"}, "Map_Tile_15_4":{"terrain":"wall"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"bridge"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"wall"}, "Map_Tile_4_13":{"terrain":"beach"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"sea"}, "Player_2":{"team":1, "recruit_thief":true, "recruit_rifleman":false, "recruit_witch":false, "recruit_caravel":true, "recruit_frog":true, "recruit_wagon":false, "recruit_dog":true, "recruit_soldier":true, "recruit_trebuchet":false, "recruit_knight":false, "recruit_spearman":true, "recruit_harpy":false, "recruit_warship":false, "recruit_kraken":false, "recruit_turtle":true, "recruit_merman":false, "recruit_ballista":false, "recruit_harpoonship":false, "recruit_mage":true, "recruit_travelboat":false, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":false, "recruit_giant":false, "recruit_dragon":false, "gold":100}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"wall"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"sea"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_2_16":{"terrain":"bridge"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"sea"}, "Map_Tile_0_14":{"terrain":"bridge"}, "Map_Tile_11_4":{"terrain":"sea"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"sea"}, "Map_Tile_6_13":{"terrain":"plains"}, "Player_3":{"team":2, "recruit_thief":true, "recruit_rifleman":false, "recruit_witch":false, "recruit_caravel":false, "recruit_frog":true, "recruit_wagon":false, "recruit_dog":true, "recruit_soldier":true, "recruit_trebuchet":false, "recruit_knight":false, "recruit_spearman":true, "recruit_harpy":false, "recruit_warship":false, "recruit_kraken":false, "recruit_turtle":true, "recruit_merman":false, "recruit_ballista":false, "recruit_harpoonship":false, "recruit_mage":true, "recruit_travelboat":false, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":false, "recruit_giant":false, "recruit_dragon":false, "gold":0}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"wall"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_9_16":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"sea"}, "Map_Tile_12_10":{"terrain":"sea"}, "Map_Tile_16_9":{"terrain":"sea"}, "Map_Tile_8_1":{"terrain":"sea"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_13_10":{"terrain":"bridge"}, "Map_Tile_14_5":{"terrain":"beach"}, "Map_Tile_7_1":{"terrain":"sea"}, "Map_Tile_16_10":{"terrain":"sea"}, "Map_Tile_10_1":{"terrain":"sea"}, "Map_Tile_5_16":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"bridge"}, "Map_Tile_12_0":{"terrain":"beach"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"bridge"}, "Map_Tile_14_2":{"terrain":"wall"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"sea"}, "Map_Tile_3_12":{"terrain":"plains"}, "Player_1":{"team":0, "recruit_thief":true, "recruit_rifleman":true, "recruit_witch":true, "recruit_caravel":true, "recruit_frog":true, "recruit_wagon":true, "recruit_dog":true, "recruit_soldier":true, "recruit_trebuchet":true, "recruit_knight":true, "recruit_spearman":true, "recruit_harpy":true, "recruit_warship":true, "recruit_kraken":true, "recruit_turtle":true, "recruit_merman":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_mage":true, "recruit_travelboat":true, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":true, "recruit_giant":true, "recruit_dragon":true, "gold":100}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"sea"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"sea"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"sea"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"sea"}, "Map_Tile_11_9":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"sea"}, "Map_Tile_18_7":{"terrain":"sea"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"sea"}, "Map_Tile_13_8":{"terrain":"bridge"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"sea"}, "Map_Tile_18_11":{"terrain":"plains"}, "Locations":{"1":{"interactable":false, "getArea":null, "name":"P1 Stronghold Location", "id":1, "setArea":null, "centre":{"y":9, "x":4}, "positions":[{"y":9, "x":4}, {"y":9, "x":3}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":8, "x":4}, {"y":8, "x":3}, {"y":8, "x":5}, {"y":9, "x":5}]}, "2":{"interactable":false, "getArea":null, "name":"P2 Stronghold Location", "id":2, "setArea":null, "centre":{"y":13, "x":9}, "positions":[{"y":13, "x":10}, {"y":14, "x":9}, {"y":14, "x":8}, {"y":12, "x":10}, {"y":13, "x":9}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":11, "x":10}]}, "3":{"interactable":false, "getArea":null, "name":"P1 Starting Zone", "id":3, "setArea":null, "centre":{"y":1, "x":3}, "positions":[{"y":0, "x":0}, {"y":1, "x":0}, {"y":1, "x":1}, {"y":1, "x":2}, {"y":1, "x":3}, {"y":0, "x":3}, {"y":1, "x":4}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":0, "x":2}, {"y":0, "x":1}, {"y":2, "x":0}, {"y":2, "x":1}, {"y":2, "x":2}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":2, "x":5}, {"y":2, "x":6}]}, "4":{"interactable":false, "getArea":null, "name":"P2 Starting Zone", "id":4, "setArea":null, "centre":{"y":14, "x":12}, "positions":[{"y":12, "x":12}, {"y":11, "x":12}, {"y":11, "x":13}, {"y":12, "x":13}, {"y":13, "x":13}, {"y":14, "x":13}, {"y":15, "x":13}, {"y":16, "x":13}, {"y":16, "x":12}, {"y":15, "x":12}, {"y":14, "x":12}, {"y":13, "x":12}, {"y":11, "x":11}, {"y":12, "x":11}, {"y":13, "x":11}, {"y":14, "x":11}, {"y":15, "x":11}, {"y":16, "x":11}]}, "5":{"interactable":false, "getArea":null, "name":"Land", "id":5, "setArea":null, "centre":{"y":9, "x":7}, "positions":[{"y":0, "x":0}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":5}, {"y":1, "x":4}, {"y":1, "x":3}, {"y":1, "x":2}, {"y":1, "x":1}, {"y":1, "x":0}, {"y":2, "x":0}, {"y":3, "x":0}, {"y":4, "x":0}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":9, "x":0}, {"y":10, "x":0}, {"y":11, "x":0}, {"y":12, "x":0}, {"y":12, "x":1}, {"y":12, "x":2}, {"y":12, "x":3}, {"y":11, "x":3}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":10, "x":1}, {"y":9, "x":1}, {"y":8, "x":1}, {"y":7, "x":1}, {"y":6, "x":1}, {"y":5, "x":1}, {"y":4, "x":1}, {"y":3, "x":1}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":3, "x":2}, {"y":4, "x":2}, {"y":5, "x":2}, {"y":6, "x":2}, {"y":7, "x":2}, {"y":9, "x":2}, {"y":10, "x":2}, {"y":10, "x":3}, {"y":10, "x":4}, {"y":11, "x":4}, {"y":10, "x":5}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":8, "x":3}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":7, "x":6}, {"y":6, "x":6}, {"y":5, "x":6}, {"y":4, "x":6}, {"y":3, "x":6}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":4, "x":3}, {"y":4, "x":4}, {"y":5, "x":4}, {"y":6, "x":4}, {"y":7, "x":4}, {"y":7, "x":3}, {"y":5, "x":3}, {"y":4, "x":5}, {"y":5, "x":5}, {"y":6, "x":5}, {"y":6, "x":3}, {"y":7, "x":5}, {"y":14, "x":5}, {"y":14, "x":6}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":12, "x":7}, {"y":12, "x":8}, {"y":11, "x":8}, {"y":11, "x":9}, {"y":12, "x":9}, {"y":13, "x":8}, {"y":15, "x":6}, {"y":15, "x":5}, {"y":16, "x":5}, {"y":16, "x":6}, {"y":16, "x":7}, {"y":16, "x":8}, {"y":16, "x":9}, {"y":16, "x":10}, {"y":16, "x":11}, {"y":16, "x":12}, {"y":16, "x":13}, {"y":16, "x":14}, {"y":16, "x":15}, {"y":16, "x":16}, {"y":16, "x":17}, {"y":16, "x":18}, {"y":15, "x":18}, {"y":14, "x":18}, {"y":13, "x":18}, {"y":12, "x":18}, {"y":11, "x":18}, {"y":11, "x":17}, {"y":11, "x":16}, {"y":11, "x":15}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":11, "x":12}, {"y":11, "x":11}, {"y":11, "x":10}, {"y":14, "x":7}, {"y":15, "x":7}, {"y":15, "x":8}, {"y":14, "x":8}, {"y":14, "x":9}, {"y":13, "x":9}, {"y":12, "x":10}, {"y":12, "x":11}, {"y":14, "x":10}, {"y":15, "x":9}, {"y":12, "x":12}, {"y":13, "x":12}, {"y":14, "x":12}, {"y":15, "x":11}, {"y":15, "x":10}, {"y":13, "x":11}, {"y":13, "x":13}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":16}, {"y":13, "x":15}, {"y":13, "x":14}, {"y":14, "x":14}, {"y":15, "x":13}, {"y":14, "x":13}, {"y":12, "x":13}, {"y":15, "x":12}, {"y":15, "x":14}, {"y":14, "x":15}, {"y":13, "x":16}, {"y":13, "x":17}, {"y":12, "x":17}, {"y":14, "x":16}, {"y":15, "x":16}, {"y":15, "x":17}, {"y":14, "x":17}, {"y":15, "x":15}, {"y":16, "x":0}, {"y":14, "x":11}, {"y":2, "x":1}]}, "6":{"interactable":false, "getArea":null, "name":"Watcher Area", "id":6, "setArea":null, "centre":{"y":3, "x":15}, "positions":[{"y":4, "x":13}, {"y":4, "x":14}, {"y":4, "x":15}, {"y":4, "x":16}, {"y":4, "x":17}, {"y":4, "x":18}, {"y":3, "x":18}, {"y":3, "x":17}, {"y":3, "x":16}, {"y":3, "x":15}, {"y":3, "x":14}, {"y":3, "x":13}, {"y":2, "x":13}, {"y":2, "x":14}, {"y":2, "x":15}, {"y":2, "x":16}, {"y":2, "x":17}, {"y":2, "x":18}, {"y":1, "x":18}, {"y":1, "x":17}, {"y":1, "x":16}, {"y":1, "x":15}, {"y":1, "x":14}, {"y":1, "x":13}, {"y":0, "x":13}, {"y":0, "x":14}, {"y":0, "x":15}, {"y":0, "x":16}, {"y":0, "x":18}, {"y":0, "x":12}, {"y":1, "x":12}, {"y":2, "x":12}, {"y":3, "x":12}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":5, "x":13}, {"y":5, "x":14}, {"y":5, "x":15}, {"y":5, "x":16}, {"y":5, "x":17}, {"y":5, "x":18}]}, "0":{"interactable":false, "getArea":null, "name":"Sea", "id":0, "setArea":null, "centre":{"y":7, "x":11}, "positions":[{"y":10, "x":12}, {"y":10, "x":11}, {"y":10, "x":10}, {"y":10, "x":9}, {"y":10, "x":8}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":9, "x":12}, {"y":9, "x":10}, {"y":9, "x":9}, {"y":9, "x":8}, {"y":9, "x":7}, {"y":8, "x":10}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":7, "x":11}, {"y":6, "x":11}, {"y":4, "x":11}, {"y":3, "x":11}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":4, "x":7}, {"y":3, "x":10}, {"y":3, "x":9}, {"y":8, "x":7}, {"y":8, "x":8}, {"y":7, "x":8}, {"y":7, "x":9}, {"y":7, "x":10}, {"y":8, "x":9}, {"y":6, "x":10}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":7, "x":7}, {"y":6, "x":14}, {"y":6, "x":15}, {"y":6, "x":16}, {"y":6, "x":17}, {"y":6, "x":18}, {"y":7, "x":18}, {"y":8, "x":18}, {"y":9, "x":18}, {"y":10, "x":18}, {"y":10, "x":17}, {"y":10, "x":16}, {"y":10, "x":15}, {"y":10, "x":14}, {"y":7, "x":14}, {"y":7, "x":15}, {"y":7, "x":16}, {"y":7, "x":17}, {"y":9, "x":17}, {"y":9, "x":16}, {"y":9, "x":15}, {"y":9, "x":14}, {"y":8, "x":14}, {"y":8, "x":15}, {"y":8, "x":16}, {"y":1, "x":11}, {"y":0, "x":11}, {"y":0, "x":10}, {"y":0, "x":9}, {"y":0, "x":8}, {"y":0, "x":7}, {"y":1, "x":7}, {"y":1, "x":9}, {"y":1, "x":10}, {"y":1, "x":8}, {"y":14, "x":3}, {"y":14, "x":2}, {"y":14, "x":1}, {"y":15, "x":1}, {"y":15, "x":2}, {"y":15, "x":3}, {"y":8, "x":17}]}}, "Map_Tile_17_15":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"sea"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_18_5":{"terrain":"beach"}, "Map_Tile_18_6":{"terrain":"sea"}, "Map_Tile_18_2":{"terrain":"wall"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_18_1":{"terrain":"wall"}, "Map_Tile_10_6":{"terrain":"sea"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_17_16":{"terrain":"plains"}, "Map_Tile_17_14":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"sea"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Tile_6_12":{"terrain":"beach"}, "Map_Tile_17_12":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"beach"}, "Map_Tile_5_3":{"terrain":"plains"}, "Objectives":["Step on Observation Isle (No Requirements).", "Kill player 3's commander (Requires Walls event).", "Win with standard conditions."], "Map_Tile_17_11":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_17_8":{"unit":{"state":{}, "rangedDamageTakenPercent":100, "tentacled":false, "items":{}, "grooveId":"", "attackerPlayerId":-1, "attackerUnitClass":"", "factionOverride":"", "stunned":false, "health":100, "canChargeGroove":true, "recruitDiscounts":{}, "attackerId":-1, "killedByLosing":false, "canBeAttacked":true, "loadedUnits":{}, "startPos":{"facing":3, "y":8, "x":17}, "merchantDiscounts":{}, "itemDropNumber":0, "recruits":{}, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "unitClass":{"weaponIds":["caravelWeapon"], "verbCostMultiplier":1.0, "loadCapacity":0, "weapons":[{"canMoveAndAttack":true, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"caravelWeapon", "maxRange":1, "canAttackSubmerged":false, "canCounterAttack":true}], "isDamagingParentUnit":false, "inAir":false, "isAttackable":true, "critConditionId":"", "moveRange":5, "movementType":"river_sailing", "transportTags":{}, "isRecruitable":true, "aliasId":"", "isCommander":false, "inWater":true, "canBeActivated":false, "cost":250, "canReinforce":false, "passiveMultiplier":1.5, "maxHealth":100, "isStructure":false, "reinforceMultiplier":1.0, "tags":["caravel", "type.sea.light"], "maxGroove":0, "canAttack":true, "id":"caravel", "resourceCost":1, "canBeCaptured":false, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "itemId":"", "underwater":false, "attachedFlagId":-1, "playerId":1, "unitClassId":"caravel", "hadTurn":false, "blessings":{}, "merchantDiscountMultiplier":0.0, "inTransport":false, "damageTakenPercent":100, "transportedBy":-1, "recruitDiscountMultiplier":0.0, "pos":{"facing":3, "y":8, "x":17}, "id":1, "garrisonClassId":"", "miniGrooveId":"", "grooveCharge":0}, "terrain":"sea"}, "Map_Tile_12_5":{"terrain":"beach"}, "Map_Tile_17_6":{"terrain":"sea"}, "Map_Tile_17_5":{"terrain":"beach"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_18_13":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_17_2":{"terrain":"wall"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"beach"}, "Map_Tile_4_14":{"terrain":"beach"}, "Map_Tile_7_10":{"terrain":"beach"}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"sea"}, "Map_Tile_6_10":{"terrain":"beach"}, "Counters":{}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_1_16":{"terrain":"bridge"}, "Map_Tile_16_2":{"terrain":"wall"}, "Map_Tile_16_4":{"terrain":"wall"}, "Map_Tile_16_3":{"terrain":"wall"}, "Map_Tile_16_5":{"terrain":"beach"}, "Map_Tile_13_2":{"terrain":"wall"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"sea"}, "Map_Tile_15_7":{"terrain":"sea"}, "Map_Tile_9_6":{"terrain":"sea"}, "Map_Tile_15_5":{"terrain":"beach"}, "Map_Tile_14_14":{"terrain":"plains"}, "Flags":{}, "Map_Tile_14_4":{"terrain":"wall"}, "Map_Tile_15_3":{"terrain":"wall"}, "Map_Tile_10_5":{"terrain":"bridge"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"beach"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"sea"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"wall"}, "Map_Tile_2_15":{"terrain":"sea"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"sea"}, "Map_Tile_14_6":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"wall"}, "Map_Tile_14_16":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"beach"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_13_16":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"sea"}, "Map_Tile_17_9":{"terrain":"sea"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_16_16":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"wall"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"wall"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_3_5":{"terrain":"plains"}, "Player_Count":3, "Map_Tile_17_0":{"unit":{"state":{}, "rangedDamageTakenPercent":100, "tentacled":false, "items":{}, "grooveId":"smoke_screen", "attackerPlayerId":-1, "attackerUnitClass":"", "factionOverride":"", "stunned":false, "health":100, "canChargeGroove":true, "recruitDiscounts":{}, "attackerId":-1, "killedByLosing":false, "canBeAttacked":true, "loadedUnits":{}, "startPos":{"facing":3, "y":0, "x":17}, "merchantDiscounts":{}, "itemDropNumber":0, "recruits":{}, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "unitClass":{"weaponIds":["vesperWhip"], "verbCostMultiplier":1.0, "loadCapacity":0, "weapons":[{"canMoveAndAttack":true, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"vesperWhip", "maxRange":1, "canAttackSubmerged":false, "canCounterAttack":true}], "isDamagingParentUnit":false, "inAir":false, "isAttackable":true, "critConditionId":"", "moveRange":4, "movementType":"walking", "transportTags":{}, "isRecruitable":false, "aliasId":"", "isCommander":true, "inWater":false, "canBeActivated":false, "cost":500, "canReinforce":false, "passiveMultiplier":1.0, "maxHealth":100, "isStructure":false, "reinforceMultiplier":1.0, "tags":["commander", "type.ground.light"], "maxGroove":400, "canAttack":true, "id":"commander_vesper", "resourceCost":3, "canBeCaptured":false, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "itemId":"", "underwater":false, "attachedFlagId":-1, "playerId":2, "unitClassId":"commander_vesper", "hadTurn":false, "blessings":{}, "merchantDiscountMultiplier":0.0, "inTransport":false, "damageTakenPercent":100, "transportedBy":-1, "recruitDiscountMultiplier":0.0, "pos":{"facing":3, "y":0, "x":17}, "id":2, "garrisonClassId":"", "miniGrooveId":"", "grooveCharge":0}, "terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_17_4":{"terrain":"wall"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"sea"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"beach"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"sea"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Size":{"y":17, "x":19}, "Map_Tile_11_5":{"terrain":"bridge"}, "Map_Tile_14_3":{"terrain":"wall"}, "Map_Tile_7_13":{"terrain":"sea"}, "Map_Name":"Observation Isle", "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"sea"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_7_15":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_10_7":{"terrain":"sea"}, "Map_Tile_18_0":{"terrain":"wall"}, "Map_Tile_7_5":{"terrain":"bridge"}, "Map_Tile_9_15":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_3_15":{"terrain":"sea"}, "Map_Tile_18_3":{"terrain":"wall"}, "Map_Tile_15_1":{"terrain":"wall"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_1_15":{"terrain":"sea"}, "Map_Tile_4_16":{"terrain":"beach"}} \ No newline at end of file +{"Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"sea"}, "Map_Tile_12_0":{"terrain":"beach"}, "Map_Tile_13_7":{"terrain":"bridge"}, "Map_Tile_14_6":{"terrain":"sea"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_18_7":{"terrain":"sea"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_14_16":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Name":"Observation Isle", "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_6_15":{"terrain":"plains"}, "Objectives":["Step on Observation Isle (No Requirements).", "Kill player 3's commander (Requires Walls event).", "Win with standard conditions."], "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"sea"}, "Map_Tile_13_2":{"terrain":"wall"}, "Map_Tile_14_10":{"terrain":"sea"}, "Flags":{}, "Player_1":{"recruit_warship":true, "recruit_kraken":true, "recruit_rifleman":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_trebuchet":true, "recruit_giant":true, "recruit_soldier":true, "team":0, "recruit_caravel":true, "recruit_harpoonship":true, "recruit_ballista":true, "recruit_turtle":true, "recruit_griffin_walking":true, "recruit_spearman":true, "recruit_wagon":true, "recruit_archer":true, "recruit_dragon":true, "recruit_mage":true, "recruit_harpy":true, "recruit_balloon":true, "recruit_knight":true, "recruit_merman":true, "recruit_frog":true, "recruit_witch":true, "gold":100, "recruit_thief":true}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_2_16":{"terrain":"bridge"}, "Player_Count":3, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"beach"}, "Map_Tile_3_14":{"terrain":"sea"}, "Map_Tile_9_6":{"terrain":"sea"}, "Author":"Fly Sniper", "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"sea"}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"bridge"}, "Map_Tile_10_0":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"wall"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"beach"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"wall"}, "Map_Tile_17_11":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"beach"}, "Map_Tile_4_12":{"terrain":"beach"}, "Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"beach"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"sea"}, "Map_Tile_17_8":{"terrain":"sea", "unit":{"attachedFlagId":-1, "pos":{"y":8, "x":17, "facing":3}, "garrisonClassId":"", "playerId":1, "itemId":"", "merchantDiscounts":{}, "attackerPlayerId":-1, "hasBeenKilled":false, "factionOverride":"", "attackerUnitClass":"", "state":{}, "transportedBy":-1, "killedByLosing":false, "recruits":{}, "attackerId":-1, "blessings":{}, "miniGrooveId":"", "stunned":false, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "setGroove":null, "tentacled":false, "hadTurn":false, "id":1, "canBeAttackedFromDistance":true, "underwater":false, "grooveCharge":0, "setHealth":null, "canChargeGroove":true, "items":{}, "loadedUnits":{}, "inTransport":false, "grooveId":"", "itemDropNumber":0, "damageTakenPercent":100, "unitClassId":"caravel", "recruitDiscounts":{}, "startPos":{"y":8, "x":17, "facing":3}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "unitClass":{"isCommander":false, "isStructure":false, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "weapons":[{"canAttackSubmerged":false, "directionality":"omni", "minRange":1, "id":"caravelWeapon", "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canAttackAir":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "maxRange":1, "canCounterAttack":true}], "isRecruitable":true, "isAttackable":true, "inAir":false, "loadCapacity":0, "passiveMultiplier":1.5, "canBeCaptured":false, "canReinforce":false, "moveRange":5, "verbCostMultiplier":1.0, "maxHealth":100, "cost":250, "reinforceMultiplier":1.0, "tags":["caravel", "type.sea.light"], "id":"caravel", "canBeActivated":false, "movementType":"river_sailing", "maxGroove":0, "inWater":true, "weaponIds":["caravelWeapon"], "transportTags":{}, "recruitingCostMultiplier":1.0, "canAttack":true, "critConditionId":""}}}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_1_15":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"sea"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"sea"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"sea"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_1_16":{"terrain":"bridge"}, "Map_Tile_15_0":{"terrain":"wall"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_2_15":{"terrain":"sea"}, "Map_Tile_10_4":{"terrain":"sea"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_18_11":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"sea"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"wall"}, "Map_Tile_1_14":{"terrain":"sea"}, "Map_Tile_7_5":{"terrain":"bridge"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_13_16":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_0_14":{"terrain":"bridge"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_17_6":{"terrain":"sea"}, "Map_Tile_9_5":{"terrain":"bridge"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"beach"}, "Map_Tile_15_15":{"terrain":"plains"}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"bridge"}, "Map_Tile_17_14":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"beach"}, "Map_Tile_14_4":{"terrain":"wall"}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"plains"}, "Triggers":[{"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["99992", "Observation Isle", "Fly Sniper", "Step on Observation Isle (No Requirements).", "Kill player 3's commander (Requires Walls event).", "", "Win with standard conditions."], "enabled":true, "id":"ap_export"}], "recurring":"start_of_match", "conditions":{}, "isIntro":false, "id":"AP: Export", "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "recurring":"oncePerPlayer", "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "isIntro":false, "id":"$trigger_default_defeat_no_units", "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "recurring":"oncePerPlayer", "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "isIntro":false, "id":"$trigger_default_defeat_commander", "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "recurring":"oncePerPlayer", "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "isIntro":false, "id":"$trigger_default_defeat_hq", "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "recurring":"oncePerPlayer", "conditions":[{"parameters":["current", "3", "2"], "enabled":true, "id":"number_of_opponents"}], "isIntro":false, "id":"Victory (One Opponent Left)", "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["0", "-10", "0", "0", "50", "0", "0", "0", "0"], "enabled":true, "id":"map_randomize"}, {"parameters":["5", "-12", "0", "0", "0", "50", "10", "5", "0"], "enabled":true, "id":"map_randomize"}, {"parameters":["3", "0", "0", "0", "0", "0", "0", "8", "0"], "enabled":true, "id":"position_asymmetric_randomize"}, {"parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["barracks", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["barracks", "4", "P2", "1", "1", "2", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["*structure", "P2", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*structure", "-1", "any", "0", "100"], "enabled":true, "id":"modify_health"}], "recurring":"start_of_match", "conditions":{}, "isIntro":false, "id":"Generate Map", "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["bellToll", "6"], "enabled":true, "id":"play_sound_effect"}, {"parameters":["3000", "2", "2", "5"], "enabled":true, "id":"screenshake"}, {"parameters":["3000"], "enabled":true, "id":"wait"}, {"parameters":["6", "-10", "0", "0", "0", "50", "0", "0", "0"], "enabled":true, "id":"map_randomize"}], "recurring":"start_of_match", "conditions":[{"parameters":["252024", "0", "1"], "enabled":true, "id":"ap_has_item"}], "isIntro":false, "id":"Walls Crumble", "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["253048"], "enabled":true, "id":"ap_location_send"}], "recurring":"end_of_match", "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "isIntro":false, "id":"P1 Victorious (253048)", "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["253049"], "enabled":true, "id":"ap_location_send"}], "recurring":"once", "conditions":[{"parameters":["P1", "1", "0", "*unit_structure", "6"], "enabled":true, "id":"unit_presence"}], "isIntro":false, "id":"Become the Watcher (253049)", "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["253050"], "enabled":true, "id":"ap_location_send"}], "recurring":"once", "conditions":[{"parameters":["*unit", "P1", "*commander", "P3", "-1"], "enabled":true, "id":"unit_killed"}], "isIntro":false, "id":"Execute the Watcher (253050)", "enabled":true}], "Map_Tile_7_10":{"terrain":"beach"}, "Map_Tile_17_15":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"wall"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_18_6":{"terrain":"sea"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"sea"}, "Map_Tile_17_3":{"terrain":"wall"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_12_8":{"terrain":"sea"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Size":{"x":19, "y":17}, "Map_Tile_10_7":{"terrain":"sea"}, "Map_Tile_0_15":{"terrain":"bridge"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"wall"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Counters":{}, "Locations":{"1":{"interactable":false, "getArea":null, "name":"P1 Stronghold Location", "setArea":null, "positions":[{"x":4, "y":9}, {"x":3, "y":9}, {"x":2, "y":9}, {"x":2, "y":8}, {"x":4, "y":8}, {"x":3, "y":8}, {"x":5, "y":8}, {"x":5, "y":9}], "id":1, "centre":{"x":4, "y":9}}, "2":{"interactable":false, "getArea":null, "name":"P2 Stronghold Location", "setArea":null, "positions":[{"x":10, "y":13}, {"x":9, "y":14}, {"x":8, "y":14}, {"x":10, "y":12}, {"x":9, "y":13}, {"x":7, "y":14}, {"x":7, "y":15}, {"x":10, "y":11}], "id":2, "centre":{"x":9, "y":13}}, "3":{"interactable":false, "getArea":null, "name":"P1 Starting Zone", "setArea":null, "positions":[{"x":0, "y":0}, {"x":0, "y":1}, {"x":1, "y":1}, {"x":2, "y":1}, {"x":3, "y":1}, {"x":3, "y":0}, {"x":4, "y":1}, {"x":4, "y":2}, {"x":3, "y":2}, {"x":2, "y":0}, {"x":1, "y":0}, {"x":0, "y":2}, {"x":1, "y":2}, {"x":2, "y":2}, {"x":4, "y":0}, {"x":5, "y":0}, {"x":6, "y":0}, {"x":6, "y":1}, {"x":5, "y":1}, {"x":5, "y":2}, {"x":6, "y":2}], "id":3, "centre":{"x":3, "y":1}}, "4":{"interactable":false, "getArea":null, "name":"P2 Starting Zone", "setArea":null, "positions":[{"x":12, "y":12}, {"x":12, "y":11}, {"x":13, "y":11}, {"x":13, "y":12}, {"x":13, "y":13}, {"x":13, "y":14}, {"x":13, "y":15}, {"x":13, "y":16}, {"x":12, "y":16}, {"x":12, "y":15}, {"x":12, "y":14}, {"x":12, "y":13}, {"x":11, "y":11}, {"x":11, "y":12}, {"x":11, "y":13}, {"x":11, "y":14}, {"x":11, "y":15}, {"x":11, "y":16}], "id":4, "centre":{"x":12, "y":14}}, "5":{"interactable":false, "getArea":null, "name":"Land", "setArea":null, "positions":[{"x":0, "y":0}, {"x":1, "y":0}, {"x":2, "y":0}, {"x":3, "y":0}, {"x":4, "y":0}, {"x":5, "y":0}, {"x":6, "y":0}, {"x":6, "y":1}, {"x":5, "y":1}, {"x":4, "y":1}, {"x":3, "y":1}, {"x":2, "y":1}, {"x":1, "y":1}, {"x":0, "y":1}, {"x":0, "y":2}, {"x":0, "y":3}, {"x":0, "y":4}, {"x":0, "y":5}, {"x":0, "y":6}, {"x":0, "y":7}, {"x":0, "y":8}, {"x":0, "y":9}, {"x":0, "y":10}, {"x":0, "y":11}, {"x":0, "y":12}, {"x":1, "y":12}, {"x":2, "y":12}, {"x":3, "y":12}, {"x":3, "y":11}, {"x":2, "y":11}, {"x":1, "y":11}, {"x":1, "y":10}, {"x":1, "y":9}, {"x":1, "y":8}, {"x":1, "y":7}, {"x":1, "y":6}, {"x":1, "y":5}, {"x":1, "y":4}, {"x":1, "y":3}, {"x":6, "y":2}, {"x":5, "y":2}, {"x":4, "y":2}, {"x":3, "y":2}, {"x":2, "y":2}, {"x":2, "y":3}, {"x":2, "y":4}, {"x":2, "y":5}, {"x":2, "y":6}, {"x":2, "y":7}, {"x":2, "y":9}, {"x":2, "y":10}, {"x":3, "y":10}, {"x":4, "y":10}, {"x":4, "y":11}, {"x":5, "y":10}, {"x":5, "y":9}, {"x":6, "y":9}, {"x":4, "y":9}, {"x":3, "y":9}, {"x":3, "y":8}, {"x":4, "y":8}, {"x":5, "y":8}, {"x":6, "y":8}, {"x":6, "y":7}, {"x":6, "y":6}, {"x":6, "y":5}, {"x":6, "y":4}, {"x":6, "y":3}, {"x":5, "y":3}, {"x":4, "y":3}, {"x":3, "y":3}, {"x":3, "y":4}, {"x":4, "y":4}, {"x":4, "y":5}, {"x":4, "y":6}, {"x":4, "y":7}, {"x":3, "y":7}, {"x":3, "y":5}, {"x":5, "y":4}, {"x":5, "y":5}, {"x":5, "y":6}, {"x":3, "y":6}, {"x":5, "y":7}, {"x":5, "y":14}, {"x":6, "y":14}, {"x":6, "y":13}, {"x":7, "y":13}, {"x":7, "y":12}, {"x":8, "y":12}, {"x":8, "y":11}, {"x":9, "y":11}, {"x":9, "y":12}, {"x":8, "y":13}, {"x":6, "y":15}, {"x":5, "y":15}, {"x":5, "y":16}, {"x":6, "y":16}, {"x":7, "y":16}, {"x":8, "y":16}, {"x":9, "y":16}, {"x":10, "y":16}, {"x":11, "y":16}, {"x":12, "y":16}, {"x":13, "y":16}, {"x":14, "y":16}, {"x":15, "y":16}, {"x":16, "y":16}, {"x":17, "y":16}, {"x":18, "y":16}, {"x":18, "y":15}, {"x":18, "y":14}, {"x":18, "y":13}, {"x":18, "y":12}, {"x":18, "y":11}, {"x":17, "y":11}, {"x":16, "y":11}, {"x":15, "y":11}, {"x":14, "y":11}, {"x":12, "y":11}, {"x":11, "y":11}, {"x":10, "y":11}, {"x":7, "y":14}, {"x":7, "y":15}, {"x":8, "y":15}, {"x":8, "y":14}, {"x":9, "y":14}, {"x":9, "y":13}, {"x":10, "y":12}, {"x":11, "y":12}, {"x":10, "y":14}, {"x":9, "y":15}, {"x":12, "y":12}, {"x":12, "y":13}, {"x":12, "y":14}, {"x":10, "y":15}, {"x":11, "y":13}, {"x":13, "y":13}, {"x":14, "y":12}, {"x":15, "y":12}, {"x":16, "y":12}, {"x":15, "y":13}, {"x":14, "y":13}, {"x":14, "y":14}, {"x":13, "y":15}, {"x":13, "y":14}, {"x":13, "y":12}, {"x":12, "y":15}, {"x":14, "y":15}, {"x":15, "y":14}, {"x":16, "y":13}, {"x":17, "y":13}, {"x":17, "y":12}, {"x":16, "y":14}, {"x":16, "y":15}, {"x":17, "y":15}, {"x":17, "y":14}, {"x":15, "y":15}, {"x":0, "y":16}, {"x":11, "y":14}, {"x":1, "y":2}], "id":5, "centre":{"x":7, "y":9}}, "6":{"interactable":false, "getArea":null, "name":"Watcher Area", "setArea":null, "positions":[{"x":13, "y":4}, {"x":14, "y":4}, {"x":15, "y":4}, {"x":16, "y":4}, {"x":17, "y":4}, {"x":18, "y":4}, {"x":18, "y":3}, {"x":17, "y":3}, {"x":16, "y":3}, {"x":15, "y":3}, {"x":14, "y":3}, {"x":13, "y":3}, {"x":13, "y":2}, {"x":14, "y":2}, {"x":15, "y":2}, {"x":16, "y":2}, {"x":17, "y":2}, {"x":18, "y":2}, {"x":18, "y":1}, {"x":17, "y":1}, {"x":16, "y":1}, {"x":15, "y":1}, {"x":14, "y":1}, {"x":13, "y":1}, {"x":13, "y":0}, {"x":14, "y":0}, {"x":15, "y":0}, {"x":16, "y":0}, {"x":18, "y":0}, {"x":12, "y":0}, {"x":12, "y":1}, {"x":12, "y":2}, {"x":12, "y":3}, {"x":12, "y":4}, {"x":12, "y":5}, {"x":13, "y":5}, {"x":14, "y":5}, {"x":15, "y":5}, {"x":16, "y":5}, {"x":17, "y":5}, {"x":18, "y":5}], "id":6, "centre":{"x":15, "y":3}}, "0":{"interactable":false, "getArea":null, "name":"Sea", "setArea":null, "positions":[{"x":12, "y":10}, {"x":11, "y":10}, {"x":10, "y":10}, {"x":9, "y":10}, {"x":8, "y":10}, {"x":7, "y":2}, {"x":8, "y":2}, {"x":9, "y":2}, {"x":10, "y":2}, {"x":11, "y":2}, {"x":12, "y":6}, {"x":12, "y":7}, {"x":12, "y":8}, {"x":12, "y":9}, {"x":10, "y":9}, {"x":9, "y":9}, {"x":8, "y":9}, {"x":7, "y":9}, {"x":10, "y":8}, {"x":11, "y":8}, {"x":11, "y":9}, {"x":11, "y":7}, {"x":11, "y":6}, {"x":11, "y":4}, {"x":11, "y":3}, {"x":8, "y":3}, {"x":7, "y":3}, {"x":7, "y":4}, {"x":10, "y":3}, {"x":9, "y":3}, {"x":7, "y":8}, {"x":8, "y":8}, {"x":8, "y":7}, {"x":9, "y":7}, {"x":10, "y":7}, {"x":9, "y":8}, {"x":10, "y":6}, {"x":10, "y":4}, {"x":9, "y":4}, {"x":8, "y":4}, {"x":9, "y":6}, {"x":8, "y":6}, {"x":7, "y":6}, {"x":7, "y":7}, {"x":14, "y":6}, {"x":15, "y":6}, {"x":16, "y":6}, {"x":17, "y":6}, {"x":18, "y":6}, {"x":18, "y":7}, {"x":18, "y":8}, {"x":18, "y":9}, {"x":18, "y":10}, {"x":17, "y":10}, {"x":16, "y":10}, {"x":15, "y":10}, {"x":14, "y":10}, {"x":14, "y":7}, {"x":15, "y":7}, {"x":16, "y":7}, {"x":17, "y":7}, {"x":17, "y":9}, {"x":16, "y":9}, {"x":15, "y":9}, {"x":14, "y":9}, {"x":14, "y":8}, {"x":15, "y":8}, {"x":16, "y":8}, {"x":11, "y":1}, {"x":11, "y":0}, {"x":10, "y":0}, {"x":9, "y":0}, {"x":8, "y":0}, {"x":7, "y":0}, {"x":7, "y":1}, {"x":9, "y":1}, {"x":10, "y":1}, {"x":8, "y":1}, {"x":3, "y":14}, {"x":2, "y":14}, {"x":1, "y":14}, {"x":1, "y":15}, {"x":2, "y":15}, {"x":3, "y":15}, {"x":17, "y":8}], "id":0, "centre":{"x":11, "y":7}}}, "Map_Tile_4_15":{"terrain":"beach"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_17_9":{"terrain":"sea"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"sea"}, "Map_Tile_18_13":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"wall"}, "Map_Tile_11_9":{"terrain":"sea"}, "Map_Tile_18_10":{"terrain":"sea"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"sea"}, "Map_Tile_11_5":{"terrain":"bridge"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_3_15":{"terrain":"sea"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"sea"}, "Map_Tile_18_1":{"terrain":"wall"}, "Map_Tile_2_13":{"terrain":"beach"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"wall"}, "Map_Tile_17_12":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"sea"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"bridge"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_6_10":{"terrain":"beach"}, "Map_Tile_7_1":{"terrain":"sea"}, "Map_Tile_17_5":{"terrain":"beach"}, "Map_Tile_17_4":{"terrain":"wall"}, "Map_Tile_16_3":{"terrain":"wall"}, "Map_Tile_10_8":{"terrain":"sea"}, "Map_Tile_4_16":{"terrain":"beach"}, "Map_Tile_17_2":{"terrain":"wall"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_17_1":{"terrain":"wall"}, "Map_Tile_6_16":{"terrain":"plains"}, "Player_2":{"recruit_warship":false, "recruit_kraken":false, "recruit_rifleman":false, "recruit_travelboat":false, "recruit_dog":true, "recruit_trebuchet":false, "recruit_giant":false, "recruit_soldier":true, "team":1, "recruit_caravel":true, "recruit_harpoonship":false, "recruit_ballista":false, "recruit_turtle":true, "recruit_griffin_walking":true, "recruit_spearman":true, "recruit_wagon":false, "recruit_archer":true, "recruit_dragon":false, "recruit_mage":true, "recruit_harpy":false, "recruit_balloon":false, "recruit_knight":false, "recruit_merman":false, "recruit_frog":true, "recruit_witch":false, "gold":100, "recruit_thief":true}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"wall"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"sea"}, "Map_Tile_16_9":{"terrain":"sea"}, "Map_Tile_16_8":{"terrain":"sea"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Tile_16_4":{"terrain":"wall"}, "Map_Tile_16_1":{"terrain":"wall"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"sea"}, "Map_Tile_15_10":{"terrain":"sea"}, "Map_Tile_15_8":{"terrain":"sea"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"sea"}, "Map_Tile_18_4":{"terrain":"wall"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"wall"}, "Map_Tile_5_13":{"terrain":"beach"}, "Map_Tile_17_16":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"sea"}, "Map_Tile_17_0":{"terrain":"plains", "unit":{"attachedFlagId":-1, "pos":{"y":0, "x":17, "facing":3}, "garrisonClassId":"", "playerId":2, "itemId":"", "merchantDiscounts":{}, "attackerPlayerId":-1, "hasBeenKilled":false, "factionOverride":"", "attackerUnitClass":"", "state":{}, "transportedBy":-1, "killedByLosing":false, "recruits":{}, "attackerId":-1, "blessings":{}, "miniGrooveId":"", "stunned":false, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "setGroove":null, "tentacled":false, "hadTurn":false, "id":2, "canBeAttackedFromDistance":true, "underwater":false, "grooveCharge":0, "setHealth":null, "canChargeGroove":true, "items":{}, "loadedUnits":{}, "inTransport":false, "grooveId":"smoke_screen", "itemDropNumber":0, "damageTakenPercent":100, "unitClassId":"commander_vesper", "recruitDiscounts":{}, "startPos":{"y":0, "x":17, "facing":3}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "unitClass":{"isCommander":true, "isStructure":false, "isDamagingParentUnit":false, "resourceCost":3, "aliasId":"", "weapons":[{"canAttackSubmerged":false, "directionality":"omni", "minRange":1, "id":"vesperWhip", "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canAttackAir":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "maxRange":1, "canCounterAttack":true}], "isRecruitable":false, "isAttackable":true, "inAir":false, "loadCapacity":0, "passiveMultiplier":1.0, "canBeCaptured":false, "canReinforce":false, "moveRange":4, "verbCostMultiplier":1.0, "maxHealth":100, "cost":500, "reinforceMultiplier":1.0, "tags":["commander", "type.ground.light"], "id":"commander_vesper", "canBeActivated":false, "movementType":"walking", "maxGroove":400, "inWater":false, "weaponIds":["vesperWhip"], "transportTags":{}, "recruitingCostMultiplier":1.0, "canAttack":true, "critConditionId":""}}}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"sea"}, "Map_Tile_5_11":{"terrain":"beach"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"beach"}, "Map_Tile_14_5":{"terrain":"beach"}, "Map_Tile_13_3":{"terrain":"wall"}, "Map_Tile_14_3":{"terrain":"wall"}, "Map_Tile_14_2":{"terrain":"wall"}, "Map_Tile_14_0":{"terrain":"wall"}, "Map_Tile_18_5":{"terrain":"beach"}, "Map_Tile_4_14":{"terrain":"beach"}, "Map_Tile_7_11":{"terrain":"beach"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"beach"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"bridge"}, "Map_Tile_7_8":{"terrain":"sea"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"bridge"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"wall"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"sea"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_11_10":{"terrain":"sea"}, "Map_Tile_13_5":{"terrain":"beach"}, "Map_Tile_13_0":{"terrain":"wall"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"sea"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_18_0":{"terrain":"wall"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"sea"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"sea"}, "Map_Tile_9_2":{"terrain":"sea"}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_7_9":{"terrain":"sea"}, "Player_3":{"recruit_warship":false, "recruit_kraken":false, "recruit_rifleman":false, "recruit_travelboat":false, "recruit_dog":true, "recruit_trebuchet":false, "recruit_giant":false, "recruit_soldier":true, "team":2, "recruit_caravel":false, "recruit_harpoonship":false, "recruit_ballista":false, "recruit_turtle":true, "recruit_griffin_walking":true, "recruit_spearman":true, "recruit_wagon":false, "recruit_archer":true, "recruit_dragon":false, "recruit_mage":true, "recruit_harpy":false, "recruit_balloon":false, "recruit_knight":false, "recruit_merman":false, "recruit_frog":true, "recruit_witch":false, "gold":0, "recruit_thief":true}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"sea"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_18_2":{"terrain":"wall"}, "Map_Tile_12_9":{"terrain":"sea"}, "Map_Tile_10_5":{"terrain":"bridge"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_12_3":{"terrain":"beach"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"sea"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"sea"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"sea"}, "Map_Tile_9_16":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"sea"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_16_16":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"sea"}, "Map_Tile_12_5":{"terrain":"beach"}, "Map_Tile_6_12":{"terrain":"beach"}, "Map_Tile_14_7":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_7_15":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"sea"}, "Map_Tile_10_9":{"terrain":"sea"}, "Map_Tile_11_6":{"terrain":"sea"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_5_16":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"sea"}, "Map_Tile_9_15":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_3_16":{"terrain":"bridge"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"beach"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_6_11":{"terrain":"beach"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Swimming_at_the_Docks.json b/worlds/wargroove2/levels/Swimming_at_the_Docks.json index a5d315894524..acacfc43a850 100644 --- a/worlds/wargroove2/levels/Swimming_at_the_Docks.json +++ b/worlds/wargroove2/levels/Swimming_at_the_Docks.json @@ -1 +1 @@ -{"Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"beach"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Name":"Swimming at the Docks", "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"beach"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"beach"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"beach"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"beach"}, "Map_Tile_13_11":{"terrain":"beach"}, "Map_Tile_1_6":{"terrain":"beach"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Objectives":["Kill a knight with a dog.", "Build 2 riverboats.", "Win with standard conditions."], "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"beach"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Flags":{}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Triggers":[{"actions":[{"enabled":true, "parameters":["67190", "Swimming at the Docks", "Fly Sniper", "Kill a knight with a dog.", "Build 2 riverboats.", "", "Win with standard conditions."], "id":"ap_export"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"AP: Export", "isIntro":false, "conditions":{}, "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "isIntro":false, "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "isIntro":false, "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["0", "-3", "0", "75", "25", "0", "0", "0", "0"], "id":"map_randomize"}, {"enabled":true, "parameters":["3", "0", "0", "0", "0", "0", "2", "5", "0"], "id":"position_asymmetric_randomize"}, {"enabled":true, "parameters":["port", "0", "neutral", "1", "1", "4", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["soldier", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["dog", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["spearman", "3", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["mage", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["soldier", "4", "P2", "1", "1", "4", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["frog", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["merman", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["knight", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "3", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["barracks", "4", "P2", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"Generate Map", "isIntro":false, "conditions":{}, "enabled":true}, {"actions":[{"enabled":true, "parameters":["253042"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"end_of_match", "id":"P1 Victorious (253042)", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["happy", "caesar", "Woof!", "1", "Dog"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["253043"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Dog Kills Knight (253043)", "isIntro":false, "conditions":[{"enabled":true, "parameters":["dog", "current", "knight", "P2", "-1"], "id":"unit_killed"}], "enabled":true}, {"actions":[{"enabled":true, "parameters":["253044"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"P1 has 2 Riverboats (253044)", "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "4", "2", "caravel", "-1"], "id":"unit_presence"}], "enabled":true}], "Map_Tile_1_1":{"terrain":"beach"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"beach"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"beach"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"beach"}, "Map_Tile_13_7":{"terrain":"beach"}, "Map_Size":{"y":13, "x":15}, "Map_Tile_12_11":{"terrain":"beach"}, "Map_Tile_6_1":{"terrain":"beach"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"beach"}, "Map_Tile_13_6":{"terrain":"beach"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"beach"}, "Map_Tile_8_1":{"terrain":"beach"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"beach"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"beach"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"beach"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"beach"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Locations":{"1":{"setArea":null, "name":"P1 Stronghold Location", "getArea":null, "id":1, "centre":{"y":6, "x":1}, "interactable":false, "positions":[{"y":4, "x":0}, {"y":4, "x":1}, {"y":4, "x":2}, {"y":5, "x":2}, {"y":6, "x":2}, {"y":7, "x":2}, {"y":8, "x":2}, {"y":8, "x":1}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":5, "x":0}, {"y":5, "x":1}, {"y":6, "x":1}, {"y":7, "x":1}]}, "2":{"setArea":null, "name":"P2 Stronghold Location", "getArea":null, "id":2, "centre":{"y":6, "x":13}, "interactable":false, "positions":[{"y":4, "x":12}, {"y":5, "x":12}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":8, "x":13}, {"y":8, "x":14}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":6, "x":13}, {"y":5, "x":13}, {"y":6, "x":14}, {"y":5, "x":14}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":11}, {"y":5, "x":11}, {"y":6, "x":11}, {"y":7, "x":11}, {"y":8, "x":11}]}, "3":{"setArea":null, "name":"P1 Starting Zone", "getArea":null, "id":3, "centre":{"y":2, "x":5}, "interactable":false, "positions":[{"y":0, "x":3}, {"y":1, "x":3}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":1, "x":5}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":1, "x":4}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":7}, {"y":2, "x":6}, {"y":0, "x":7}, {"y":2, "x":7}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":5}, {"y":3, "x":6}, {"y":3, "x":7}]}, "4":{"setArea":null, "name":"P2 Starting Zone", "getArea":null, "id":4, "centre":{"y":10, "x":12}, "interactable":false, "positions":[{"y":8, "x":11}, {"y":8, "x":12}, {"y":8, "x":13}, {"y":8, "x":14}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":14}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":9, "x":10}, {"y":9, "x":11}, {"y":9, "x":12}, {"y":10, "x":12}, {"y":10, "x":11}, {"y":9, "x":13}, {"y":10, "x":13}, {"y":11, "x":12}, {"y":11, "x":11}, {"y":11, "x":10}, {"y":10, "x":10}, {"y":11, "x":9}, {"y":12, "x":7}, {"y":7, "x":11}, {"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}]}, "0":{"setArea":null, "name":"Sea", "getArea":null, "id":0, "centre":{"y":6, "x":7}, "interactable":false, "positions":[{"y":10, "x":12}, {"y":10, "x":11}, {"y":10, "x":10}, {"y":10, "x":9}, {"y":10, "x":8}, {"y":10, "x":7}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":6, "x":2}, {"y":5, "x":2}, {"y":4, "x":2}, {"y":3, "x":2}, {"y":2, "x":2}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":2, "x":6}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":3, "x":12}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":9, "x":12}, {"y":9, "x":10}, {"y":9, "x":9}, {"y":9, "x":8}, {"y":9, "x":7}, {"y":9, "x":6}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":8, "x":10}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":7, "x":11}, {"y":6, "x":11}, {"y":5, "x":11}, {"y":4, "x":11}, {"y":3, "x":11}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":4, "x":7}, {"y":4, "x":6}, {"y":4, "x":5}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":10}, {"y":3, "x":6}, {"y":3, "x":9}, {"y":4, "x":4}, {"y":3, "x":3}, {"y":4, "x":3}, {"y":5, "x":3}, {"y":6, "x":3}, {"y":7, "x":3}, {"y":7, "x":4}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":8, "x":7}, {"y":8, "x":8}, {"y":8, "x":3}, {"y":7, "x":8}, {"y":7, "x":9}, {"y":7, "x":10}, {"y":8, "x":9}, {"y":6, "x":10}, {"y":5, "x":10}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":5, "x":8}, {"y":5, "x":9}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":7, "x":7}, {"y":7, "x":6}, {"y":6, "x":6}, {"y":6, "x":5}, {"y":6, "x":4}, {"y":5, "x":4}, {"y":5, "x":5}, {"y":7, "x":5}, {"y":5, "x":6}]}}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"beach"}, "Map_Tile_1_11":{"terrain":"beach"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"beach"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"beach"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"beach"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"beach"}, "Map_Tile_4_1":{"terrain":"beach"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"beach"}, "Map_Tile_11_11":{"terrain":"beach"}, "Player_1":{"recruit_travelboat":true, "recruit_balloon":true, "recruit_frog":true, "recruit_kraken":true, "recruit_thief":true, "recruit_knight":true, "recruit_archer":true, "recruit_soldier":true, "recruit_ballista":true, "recruit_wagon":true, "recruit_turtle":true, "recruit_dog":true, "recruit_dragon":true, "team":0, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_trebuchet":true, "recruit_mage":true, "recruit_caravel":true, "recruit_witch":true, "gold":100, "recruit_giant":true, "recruit_warship":true, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_griffin_walking":true}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"beach"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"beach"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"beach"}, "Map_Tile_2_11":{"terrain":"beach"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Player_2":{"recruit_travelboat":false, "recruit_balloon":true, "recruit_frog":true, "recruit_kraken":false, "recruit_thief":true, "recruit_knight":true, "recruit_archer":true, "recruit_soldier":true, "recruit_ballista":false, "recruit_wagon":false, "recruit_turtle":true, "recruit_dog":true, "recruit_dragon":true, "team":1, "recruit_rifleman":true, "recruit_merman":true, "recruit_spearman":true, "recruit_trebuchet":false, "recruit_mage":true, "recruit_caravel":true, "recruit_witch":true, "gold":100, "recruit_giant":true, "recruit_warship":false, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_griffin_walking":true}, "Author":"Fly Sniper", "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"beach"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"beach"}, "Map_Tile_1_10":{"terrain":"beach"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"beach"}, "Counters":{}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}} \ No newline at end of file +{"Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"plains"}, "Flags":{}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"beach"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"beach"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"beach"}, "Map_Tile_1_2":{"terrain":"beach"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"beach"}, "Map_Tile_4_11":{"terrain":"beach"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"beach"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"beach"}, "Map_Tile_4_1":{"terrain":"beach"}, "Map_Name":"Swimming at the Docks", "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"beach"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_3_11":{"terrain":"beach"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Counters":{}, "Map_Tile_2_9":{"terrain":"plains"}, "Triggers":[{"isIntro":false, "id":"AP: Export", "recurring":"start_of_match", "conditions":{}, "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"ap_export", "parameters":["67190", "Swimming at the Docks", "Fly Sniper", "Kill a knight with a dog.", "Build 2 riverboats.", "", "Win with standard conditions."]}]}, {"isIntro":false, "id":"$trigger_default_defeat_no_units", "recurring":"oncePerPlayer", "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "id":"$trigger_default_defeat_commander", "recurring":"oncePerPlayer", "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "id":"$trigger_default_defeat_hq", "recurring":"oncePerPlayer", "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"isIntro":false, "id":"$trigger_default_victory", "recurring":"oncePerPlayer", "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}]}, {"isIntro":false, "id":"Generate Map", "recurring":"start_of_match", "conditions":{}, "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"remove_units", "parameters":["*unit_structure", "-1", "any", "1", "1"]}, {"enabled":true, "id":"map_randomize", "parameters":["0", "-3", "0", "75", "25", "0", "0", "0", "0"]}, {"enabled":true, "id":"position_asymmetric_randomize", "parameters":["3", "0", "0", "0", "0", "0", "2", "5", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["port", "0", "neutral", "1", "1", "4", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "3", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["*commander", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "1", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["hq", "2", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "3", "P1", "1", "1", "3", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["dog", "3", "P1", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["spearman", "3", "P1", "1", "1", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["mage", "3", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["soldier", "4", "P2", "1", "1", "4", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["frog", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["merman", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["knight", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "3", "P1", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["barracks", "4", "P2", "1", "1", "1", "1", "undefined", "centre"]}]}, {"isIntro":false, "id":"P1 Victorious (253042)", "recurring":"end_of_match", "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["current"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253042"]}]}, {"isIntro":false, "id":"Dog Kills Knight (253043)", "recurring":"once", "conditions":[{"enabled":true, "id":"unit_killed", "parameters":["dog", "current", "knight", "P2", "-1"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"dialogue_box_simple", "parameters":["happy", "caesar", "Woof!", "1", "Dog"]}, {"enabled":true, "id":"ap_location_send", "parameters":["253043"]}]}, {"isIntro":false, "id":"P1 has 2 Riverboats (253044)", "recurring":"once", "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "4", "2", "caravel", "-1"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253044"]}]}], "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"beach"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"beach"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"beach"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"beach"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"beach"}, "Map_Tile_9_11":{"terrain":"beach"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"beach"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"beach"}, "Player_1":{"recruit_dragon":true, "recruit_warship":true, "recruit_giant":true, "recruit_ballista":true, "recruit_caravel":true, "recruit_travelboat":true, "recruit_balloon":true, "team":0, "recruit_mage":true, "recruit_witch":true, "gold":100, "recruit_soldier":true, "recruit_rifleman":true, "recruit_trebuchet":true, "recruit_griffin_walking":true, "recruit_frog":true, "recruit_archer":true, "recruit_harpy":true, "recruit_kraken":true, "recruit_harpoonship":true, "recruit_knight":true, "recruit_thief":true, "recruit_spearman":true, "recruit_dog":true, "recruit_turtle":true, "recruit_merman":true, "recruit_wagon":true}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"beach"}, "Map_Tile_6_1":{"terrain":"beach"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"beach"}, "Map_Tile_11_11":{"terrain":"beach"}, "Map_Tile_13_4":{"terrain":"beach"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"beach"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"beach"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"beach"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"beach"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"beach"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"beach"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains", "unit":{"grooveId":"", "hasBeenKilled":false, "health":100, "factionOverride":"", "hadTurn":false, "loadedUnits":{}, "miniGrooveId":"", "grooveCharge":0, "playerId":1, "setHealth":null, "canBeAttacked":true, "attackerPlayerId":-1, "unitClassId":"barracks", "id":1, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "killedByLosing":false, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "damageTakenPercent":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "setGroove":null, "unitClass":{"weaponIds":{}, "inAir":false, "inWater":false, "canAttack":true, "weapons":{}, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "aliasId":"", "isStructure":true, "recruitingCostMultiplier":1.0, "resourceCost":1, "tags":["structure"], "maxGroove":0, "movementType":"land_building", "loadCapacity":0, "isRecruitable":true, "passiveMultiplier":1.0, "moveRange":0, "transportTags":{}, "isCommander":false, "isAttackable":true, "canBeActivated":false, "canReinforce":true, "id":"barracks", "reinforceMultiplier":1.0, "maxHealth":100, "cost":500, "critConditionId":"", "canBeCaptured":true}, "stunned":false, "canBeAttackedFromDistance":true, "attachedFlagId":-1, "tentacled":false, "state":{}, "underwater":false, "itemId":"", "garrisonClassId":"garrison", "inTransport":false, "startPos":{"facing":0, "y":9, "x":10}, "blessings":{}, "merchantDiscountMultiplier":0.0, "itemDropNumber":0, "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "items":{}, "attackerUnitClass":"", "pos":{"facing":0, "y":9, "x":10}}}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"sea", "unit":{"grooveId":"", "hasBeenKilled":false, "health":100, "factionOverride":"", "hadTurn":false, "loadedUnits":{}, "miniGrooveId":"", "grooveCharge":0, "playerId":1, "setHealth":null, "canBeAttacked":true, "attackerPlayerId":-1, "unitClassId":"port", "id":2, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "killedByLosing":false, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "damageTakenPercent":100, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "setGroove":null, "unitClass":{"weaponIds":{}, "inAir":false, "inWater":false, "canAttack":true, "weapons":{}, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "aliasId":"", "isStructure":true, "recruitingCostMultiplier":1.0, "resourceCost":1, "tags":["structure"], "maxGroove":0, "movementType":"river_sea_building", "loadCapacity":0, "isRecruitable":true, "passiveMultiplier":1.0, "moveRange":0, "transportTags":{}, "isCommander":false, "isAttackable":true, "canBeActivated":false, "canReinforce":true, "id":"port", "reinforceMultiplier":1.0, "maxHealth":100, "cost":500, "critConditionId":"", "canBeCaptured":true}, "stunned":false, "canBeAttackedFromDistance":true, "attachedFlagId":-1, "tentacled":false, "state":{}, "underwater":false, "itemId":"", "garrisonClassId":"garrison", "inTransport":false, "startPos":{"facing":1, "y":8, "x":10}, "blessings":{}, "merchantDiscountMultiplier":0.0, "itemDropNumber":0, "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "items":{}, "attackerUnitClass":"", "pos":{"facing":1, "y":8, "x":10}}}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Locations":{"1":{"positions":[{"y":4, "x":0}, {"y":4, "x":1}, {"y":4, "x":2}, {"y":5, "x":2}, {"y":6, "x":2}, {"y":7, "x":2}, {"y":8, "x":2}, {"y":8, "x":1}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":5, "x":0}, {"y":5, "x":1}, {"y":6, "x":1}, {"y":7, "x":1}], "name":"P1 Stronghold Location", "centre":{"y":6, "x":1}, "getArea":null, "interactable":false, "setArea":null, "id":1}, "2":{"positions":[{"y":4, "x":12}, {"y":5, "x":12}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":8, "x":13}, {"y":8, "x":14}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":6, "x":13}, {"y":5, "x":13}, {"y":6, "x":14}, {"y":5, "x":14}, {"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":11}, {"y":5, "x":11}, {"y":6, "x":11}, {"y":7, "x":11}, {"y":8, "x":11}], "name":"P2 Stronghold Location", "centre":{"y":6, "x":13}, "getArea":null, "interactable":false, "setArea":null, "id":2}, "3":{"positions":[{"y":0, "x":3}, {"y":1, "x":3}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":1, "x":5}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":1, "x":4}, {"y":0, "x":6}, {"y":1, "x":6}, {"y":1, "x":7}, {"y":2, "x":6}, {"y":0, "x":7}, {"y":2, "x":7}, {"y":3, "x":4}, {"y":3, "x":3}, {"y":3, "x":5}, {"y":3, "x":6}, {"y":3, "x":7}], "name":"P1 Starting Zone", "centre":{"y":2, "x":5}, "getArea":null, "interactable":false, "setArea":null, "id":3}, "4":{"positions":[{"y":8, "x":11}, {"y":8, "x":12}, {"y":8, "x":13}, {"y":8, "x":14}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":14}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":9, "x":10}, {"y":9, "x":11}, {"y":9, "x":12}, {"y":10, "x":12}, {"y":10, "x":11}, {"y":9, "x":13}, {"y":10, "x":13}, {"y":11, "x":12}, {"y":11, "x":11}, {"y":11, "x":10}, {"y":10, "x":10}, {"y":11, "x":9}, {"y":12, "x":7}, {"y":7, "x":11}, {"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}], "name":"P2 Starting Zone", "centre":{"y":10, "x":12}, "getArea":null, "interactable":false, "setArea":null, "id":4}, "0":{"positions":[{"y":10, "x":12}, {"y":10, "x":11}, {"y":10, "x":10}, {"y":10, "x":9}, {"y":10, "x":8}, {"y":10, "x":7}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":6, "x":2}, {"y":5, "x":2}, {"y":4, "x":2}, {"y":3, "x":2}, {"y":2, "x":2}, {"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":2, "x":6}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":3, "x":12}, {"y":4, "x":12}, {"y":5, "x":12}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":8, "x":12}, {"y":9, "x":12}, {"y":9, "x":10}, {"y":9, "x":9}, {"y":9, "x":8}, {"y":9, "x":7}, {"y":9, "x":6}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}, {"y":8, "x":10}, {"y":8, "x":11}, {"y":9, "x":11}, {"y":7, "x":11}, {"y":6, "x":11}, {"y":5, "x":11}, {"y":4, "x":11}, {"y":3, "x":11}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":4, "x":7}, {"y":4, "x":6}, {"y":4, "x":5}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":3, "x":10}, {"y":3, "x":6}, {"y":3, "x":9}, {"y":4, "x":4}, {"y":3, "x":3}, {"y":4, "x":3}, {"y":5, "x":3}, {"y":6, "x":3}, {"y":7, "x":3}, {"y":7, "x":4}, {"y":8, "x":4}, {"y":8, "x":5}, {"y":8, "x":6}, {"y":8, "x":7}, {"y":8, "x":8}, {"y":8, "x":3}, {"y":7, "x":8}, {"y":7, "x":9}, {"y":7, "x":10}, {"y":8, "x":9}, {"y":6, "x":10}, {"y":5, "x":10}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":5, "x":8}, {"y":5, "x":9}, {"y":6, "x":9}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":7, "x":7}, {"y":7, "x":6}, {"y":6, "x":6}, {"y":6, "x":5}, {"y":6, "x":4}, {"y":5, "x":4}, {"y":5, "x":5}, {"y":7, "x":5}, {"y":5, "x":6}], "name":"Sea", "centre":{"y":6, "x":7}, "getArea":null, "interactable":false, "setArea":null, "id":0}}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"beach"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"beach"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"beach"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"beach"}, "Map_Tile_5_11":{"terrain":"beach"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"beach"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Objectives":["Kill a knight with a dog.", "Build 2 riverboats.", "Win with standard conditions."], "Map_Tile_1_8":{"terrain":"beach"}, "Map_Tile_1_1":{"terrain":"beach"}, "Author":"Fly Sniper", "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"beach"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Size":{"y":13, "x":15}, "Player_2":{"recruit_dragon":true, "recruit_warship":false, "recruit_giant":false, "recruit_ballista":false, "recruit_caravel":true, "recruit_travelboat":false, "recruit_balloon":true, "team":1, "recruit_mage":true, "recruit_witch":true, "gold":100, "recruit_soldier":true, "recruit_rifleman":true, "recruit_trebuchet":false, "recruit_griffin_walking":true, "recruit_frog":true, "recruit_archer":true, "recruit_harpy":true, "recruit_kraken":false, "recruit_harpoonship":true, "recruit_knight":false, "recruit_thief":true, "recruit_spearman":true, "recruit_dog":true, "recruit_turtle":true, "recruit_merman":true, "recruit_wagon":false}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"beach"}, "Map_Tile_2_0":{"terrain":"plains"}} \ No newline at end of file From 996bcf764309ae3997d46cdaec07dc86b0722d78 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Fri, 15 Nov 2024 13:51:25 -0500 Subject: [PATCH 52/89] Wargroove 2: Updated doc to match 1.1 changes. --- worlds/wargroove2/docs/en_Wargroove 2.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md index a94589bdc92c..a59afd1e0b19 100644 --- a/worlds/wargroove2/docs/en_Wargroove 2.md +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -20,7 +20,8 @@ from that faction. 3. Income, Groove and Commander Defense boosts that provide the player with extra income, extra commander groove or extra commander defense. 4. Special map events like the Bridges Event or the Walls Event, which perform special actions in certain levels. -5. 28 levels are shuffled into 4 branching paths. A random final level will appear at the end of each path. +5. 28 levels are shuffled into 4 branching paths. One of 4 starting levels will be at the beginning of each path. +One of 4 final levels will be at the ending of each path. ## Which items can be in another player's world? From 1c2a84ac85bbe28deba3082a43e47132e4f3a94a Mon Sep 17 00:00:00 2001 From: FlySniper Date: Sat, 16 Nov 2024 10:56:06 -0500 Subject: [PATCH 53/89] Wargroove 2: Removed unnecessary RegionFilter method. Changed Objective Locations option description. Removed Wargroove 2 Logic Mixin. Removed deathlink from presets. --- worlds/wargroove2/Options.py | 2 +- worlds/wargroove2/Presets.py | 6 ++---- worlds/wargroove2/RegionFilter.py | 3 --- worlds/wargroove2/Rules.py | 4 ---- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index a8d063939f41..22613714aeba 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -12,7 +12,7 @@ class VictoryLocations(Range): class ObjectiveLocations(Range): - """How many checks are sent per level completed.""" + """How many checks are sent per side objective completed.""" display_name = "Objective Locations" range_start = 1 range_end = 5 diff --git a/worlds/wargroove2/Presets.py b/worlds/wargroove2/Presets.py index 42fad1bd8944..9cef9c93706c 100644 --- a/worlds/wargroove2/Presets.py +++ b/worlds/wargroove2/Presets.py @@ -10,8 +10,7 @@ "commander_defense_boost": 5, "groove_boost": 10, "commander_choice": CommanderChoice.option_random_starting_faction, - "final_levels": 1, - "death_link": False + "final_levels": 1 }, "Hard": { @@ -21,7 +20,6 @@ "commander_defense_boost": 0, "groove_boost": 0, "commander_choice": CommanderChoice.option_locked_random, - "final_levels": 4, - "death_link": True + "final_levels": 4 }, } diff --git a/worlds/wargroove2/RegionFilter.py b/worlds/wargroove2/RegionFilter.py index cd4e7928141c..bc90fb9e6db9 100644 --- a/worlds/wargroove2/RegionFilter.py +++ b/worlds/wargroove2/RegionFilter.py @@ -21,6 +21,3 @@ def has_any(self, items: List[str], player: int) -> bool: if item in self.items: return True return False - - def can_reach(self, region, kind, player: int) -> bool: - return True diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index 3c49cc75913b..737e12b31417 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -5,10 +5,6 @@ from . import Wargroove2World -class Wargroove2Logic(LogicMixin): - pass - - def set_rules(world: "Wargroove2World") -> None: level_list = world.level_list final_levels = world.final_levels From 39cea5e3619e1a79760b1ff706d374af2e86605e Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 16:32:17 +0000 Subject: [PATCH 54/89] Remove redundant region access checks from location rules The locations are added to these regions. The first part to checking if a location is reachable is to check whether its `.parent_region` is reachable, so checking for access to the `.parent_region` within the location access rule is redundant. Also changed an instance of `rule=rule` to `current_rule=rule` to make PyCharm complain less about variables with the same name in the same scope. --- worlds/wargroove2/Levels.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 947de286890a..507ec8e3efbc 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -58,8 +58,7 @@ def __init__(self, name: str, file_name: str, location_rules: dict, victory_loca def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=lambda state: True) -> None: for location_name, rule in self.location_rules.items(): set_rule(world.get_location(location_name), lambda state, current_rule=rule: - state.can_reach_region(self.region_name, player) and current_rule(state, player)() and - additional_rule(state)) + current_rule(state, player)() and additional_rule(state)) loc_id = location_table.get(location_name, 0) extras = 1 if loc_id is not None and location_name.endswith("Victory"): @@ -67,8 +66,8 @@ def define_access_rules(self, world: "Wargroove2World", player: int, additional_ elif loc_id is not None: extras = world.options.objective_locations.value for i in range(1, extras): - set_rule(world.get_location(location_name + f" Extra {i}"), lambda state, rule=rule: - state.can_reach_region(self.region_name, player) and rule(state, player)() and additional_rule(state)) + set_rule(world.get_location(location_name + f" Extra {i}"), lambda state, current_rule=rule: + current_rule(state, player)() and additional_rule(state)) region = world.get_region(self.region_name) set_region_exit_rules(region, world, self.victory_locations, operator='and') From e0eac16bde81d1191c9da87508ca83b3740d3fb0 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 16:38:22 +0000 Subject: [PATCH 55/89] Create player-specific location rules once instead of on every call The location rules were specified as lambdas that create `CollectionRule` compatible lambdas. This meant that every time a location's access rule was checked, it would create a new lambda, call that new lambda to determine access, and then discard that new lambda. By modifying the arguments for the lambdas set in each `Wargroove2Level` to take only a single `player` argument, these lambdas can be used as factory functions that produce player-specific rule lambdas that can be set on each location. --- worlds/wargroove2/Levels.py | 297 +++++++++++++++--------------------- 1 file changed, 122 insertions(+), 175 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 507ec8e3efbc..769c0c2ffcd9 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -56,9 +56,10 @@ def __init__(self, name: str, file_name: str, location_rules: dict, victory_loca self.victory_locations = [name + ': Victory'] def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=lambda state: True) -> None: - for location_name, rule in self.location_rules.items(): + for location_name, rule_factory in self.location_rules.items(): + rule = rule_factory(player) set_rule(world.get_location(location_name), lambda state, current_rule=rule: - current_rule(state, player)() and additional_rule(state)) + current_rule(state) and additional_rule(state)) loc_id = location_table.get(location_name, 0) extras = 1 if loc_id is not None and location_name.endswith("Victory"): @@ -67,7 +68,7 @@ def define_access_rules(self, world: "Wargroove2World", player: int, additional_ extras = world.options.objective_locations.value for i in range(1, extras): set_rule(world.get_location(location_name + f" Extra {i}"), lambda state, current_rule=rule: - current_rule(state, player)() and additional_rule(state)) + current_rule(state) and additional_rule(state)) region = world.get_region(self.region_name) set_region_exit_rules(region, world, self.victory_locations, operator='and') @@ -102,27 +103,21 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Cherrystone Landing", file_name="Cherrystone_Landing.json", location_rules={ - "Cherrystone Landing: Victory": lambda state, player: lambda - state=state: state.has_all(["Warship", "Barge", "Landing Event"], - player), - "Cherrystone Landing: Smacked a Trebuchet": lambda state, player: lambda - state=state: state.has_all( - ["Warship", "Barge", "Landing Event", "Golem"], player), - "Cherrystone Landing: Smacked a Fortified Village": lambda state, player: lambda - state=state: state.has_all( - ["Barge", "Landing Event", "Golem"], player) + "Cherrystone Landing: Victory": lambda player: lambda + state: state.has_all(["Warship", "Barge", "Landing Event"], player), + "Cherrystone Landing: Smacked a Trebuchet": lambda player: lambda + state: state.has_all(["Warship", "Barge", "Landing Event", "Golem"], player), + "Cherrystone Landing: Smacked a Fortified Village": lambda player: lambda + state: state.has_all(["Barge", "Landing Event", "Golem"], player) } ), Wargroove2Level( name="Spire Fire", file_name="Spire_Fire.json", location_rules={ - "Spire Fire: Victory": lambda state, player: lambda state=state: state.has_any( - ["Mage", "Witch"], player), - "Spire Fire: Kill Enemy Sky Rider": lambda state, player: lambda - state=state: state.has("Witch", player), - "Spire Fire: Win without losing your Dragon": lambda state, player: lambda - state=state: state.has_any(["Mage", "Witch"], player) + "Spire Fire: Victory": lambda player: lambda state: state.has_any(["Mage", "Witch"], player), + "Spire Fire: Kill Enemy Sky Rider": lambda player: lambda state: state.has("Witch", player), + "Spire Fire: Win without losing your Dragon": lambda player: lambda state: state.has_any(["Mage", "Witch"], player) }, has_ocean=False ), @@ -130,13 +125,10 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Nuru's Vengeance", file_name="Nuru_Vengeance.json", location_rules={ - "Nuru's Vengeance: Victory": lambda state, player: lambda state=state: state.has( - "Knight", player), - "Nuru's Vengeance: Defeat all Dogs": lambda state, player: lambda - state=state: state.has("Knight", player), - "Nuru's Vengeance: Spearman Destroys the Gate": lambda state, player: lambda - state=state: state.has_all( - ["Knight", "Spearman"], player) + "Nuru's Vengeance: Victory": lambda player: lambda state: state.has("Knight", player), + "Nuru's Vengeance: Defeat all Dogs": lambda player: lambda state: state.has("Knight", player), + "Nuru's Vengeance: Spearman Destroys the Gate": lambda player: lambda + state: state.has_all(["Knight", "Spearman"], player) }, has_ocean=False ), @@ -144,8 +136,8 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Slippery Bridge", file_name="Slippery_Bridge.json", location_rules={ - "Slippery Bridge: Victory": lambda state, player: lambda state=state: state.has("Frog", player), - "Slippery Bridge: Control all Sea Villages": lambda state, player: lambda state=state: + "Slippery Bridge: Victory": lambda player: lambda state: state.has("Frog", player), + "Slippery Bridge: Control all Sea Villages": lambda player: lambda state: state.has("Merfolk", player), } ), @@ -153,20 +145,18 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Den-Two-Away", file_name="Den-Two-Away.json", location_rules={ - "Den-Two-Away: Victory": lambda state, player: lambda state=state: state.has("Harpy", player), - "Den-Two-Away: Commander Captures the Lumbermill": lambda state, player: lambda - state=state: state.has_all(["Harpy", "Balloon"], player), + "Den-Two-Away: Victory": lambda player: lambda state: state.has("Harpy", player), + "Den-Two-Away: Commander Captures the Lumbermill": lambda player: lambda + state: state.has_all(["Harpy", "Balloon"], player), } ), Wargroove2Level( name="Skydiving", file_name="Skydiving.json", location_rules={ - "Skydiving: Victory": lambda state, player: lambda state=state: state.has_all( - ["Balloon", "Airstrike Event"], player), - "Skydiving: Dragon Defeats Stronghold": lambda state, player: lambda - state=state: state.has_all( - ["Balloon", "Airstrike Event", "Dragon"], player), + "Skydiving: Victory": lambda player: lambda state: state.has_all(["Balloon", "Airstrike Event"], player), + "Skydiving: Dragon Defeats Stronghold": lambda player: lambda + state: state.has_all(["Balloon", "Airstrike Event", "Dragon"], player), }, has_ocean=False ), @@ -174,37 +164,30 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Sunken Forest", file_name="Sunken_Forest.json", location_rules={ - "Sunken Forest: Victory": lambda state, player: lambda state=state: state.has_any( - ["Mage", "Harpoon Ship"], player), - "Sunken Forest: High Ground": lambda state, player: lambda state=state: state.has( - "Archer", player), - "Sunken Forest: Coastal Siege": lambda state, player: lambda state=state: state.has( - "Warship", player) and state.has_any( - ["Mage", "Harpoon Ship"], player), + "Sunken Forest: Victory": lambda player: lambda state: state.has_any(["Mage", "Harpoon Ship"], player), + "Sunken Forest: High Ground": lambda player: lambda state: state.has("Archer", player), + "Sunken Forest: Coastal Siege": lambda player: lambda state: + state.has("Warship", player) and state.has_any(["Mage", "Harpoon Ship"], player), } ), Wargroove2Level( name="Tenri's Mistake", file_name="Tenris_Mistake.json", location_rules={ - "Tenri's Mistake: Victory": lambda state, player: lambda state=state: state.has_any( + "Tenri's Mistake: Victory": lambda player: lambda state: state.has_any( ["Balloon", "Air Trooper"], player), - "Tenri's Mistake: Mighty Barracks": lambda state, player: lambda - state=state: state.has_any(["Balloon", "Air Trooper"], player), - "Tenri's Mistake: Commander Arrives": lambda state, player: lambda - state=state: state.has("Balloon", player), + "Tenri's Mistake: Mighty Barracks": lambda player: lambda + state: state.has_any(["Balloon", "Air Trooper"], player), + "Tenri's Mistake: Commander Arrives": lambda player: lambda state: state.has("Balloon", player), } ), Wargroove2Level( name="Enmity Cliffs", file_name="Enmity_Cliffs.json", location_rules={ - "Enmity Cliffs: Victory": lambda state, player: lambda state=state: state.has_all( - ["Spearman", "Bridges Event"], player), - "Enmity Cliffs: Spear Flood": lambda state, player: lambda state=state: state.has( - "Spearman", player), - "Enmity Cliffs: Across the Gap": lambda state, player: lambda - state=state: state.has_any(["Archer", "Rifleman"], player), + "Enmity Cliffs: Victory": lambda player: lambda state: state.has_all(["Spearman", "Bridges Event"], player), + "Enmity Cliffs: Spear Flood": lambda player: lambda state: state.has("Spearman", player), + "Enmity Cliffs: Across the Gap": lambda player: lambda state: state.has_any(["Archer", "Rifleman"], player), }, has_ocean=False ), @@ -212,32 +195,30 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Terrible Tributaries", file_name="Terrible_Tributaries.json", location_rules={ - "Terrible Tributaries: Victory": lambda state, player: lambda state=state: state.has( + "Terrible Tributaries: Victory": lambda player: lambda state: state.has( "River Boat", player), - "Terrible Tributaries: Swimming Knights": lambda state, player: lambda - state=state: state.has_all(["Merfolk", "River Boat"], player), - "Terrible Tributaries: Steal Code Names": lambda state, player: lambda - state=state: state.has_all(["Thief", "River Boat"], player), + "Terrible Tributaries: Swimming Knights": lambda player: lambda + state: state.has_all(["Merfolk", "River Boat"], player), + "Terrible Tributaries: Steal Code Names": lambda player: lambda + state: state.has_all(["Thief", "River Boat"], player), } ), Wargroove2Level( name="Beached", file_name="Beached.json", location_rules={ - "Beached: Victory": lambda state, player: lambda state=state: state.has("Knight", player), - "Beached: Turtle Power": lambda state, player: lambda state=state: state.has_all( - ["Turtle", "Knight"], player), - "Beached: Happy Turtle": lambda state, player: lambda state=state: state.has_all( - ["Turtle", "Knight"], player), + "Beached: Victory": lambda player: lambda state: state.has("Knight", player), + "Beached: Turtle Power": lambda player: lambda state: state.has_all(["Turtle", "Knight"], player), + "Beached: Happy Turtle": lambda player: lambda state: state.has_all(["Turtle", "Knight"], player), } ), Wargroove2Level( name="Portal Peril", file_name="Portal_Peril.json", location_rules={ - "Portal Peril: Victory": lambda state, player: lambda state=state: state.has("Wagon", player), - "Portal Peril: Unleash the Hounds": lambda state, player: lambda state=state: state.has("Wagon", player), - "Portal Peril: Overcharged": lambda state, player: lambda state=state: state.has("Wagon", player), + "Portal Peril: Victory": lambda player: lambda state: state.has("Wagon", player), + "Portal Peril: Unleash the Hounds": lambda player: lambda state: state.has("Wagon", player), + "Portal Peril: Overcharged": lambda player: lambda state: state.has("Wagon", player), }, has_ocean=False ), @@ -245,12 +226,11 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Riflemen Blockade", file_name="Riflemen_Blockade.json", location_rules={ - "Riflemen Blockade: Victory": lambda state, player: lambda state=state: state.has( - "Rifleman", player), - "Riflemen Blockade: From the Mountains": lambda state, player: lambda - state=state: state.has_all(["Rifleman", "Harpy"], player), - "Riflemen Blockade: To the Road": lambda state, player: lambda - state=state: state.has_all(["Rifleman", "Dragon"], player), + "Riflemen Blockade: Victory": lambda player: lambda state: state.has("Rifleman", player), + "Riflemen Blockade: From the Mountains": lambda player: lambda + state: state.has_all(["Rifleman", "Harpy"], player), + "Riflemen Blockade: To the Road": lambda player: lambda + state: state.has_all(["Rifleman", "Dragon"], player), }, has_ocean=False ), @@ -258,10 +238,10 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Towers of the Abyss", file_name="Towers_of_the_Abyss.json", location_rules={ - "Towers of the Abyss: Victory": lambda state, player: lambda state=state: state.has("Ballista", player), - "Towers of the Abyss: Siege Master": lambda state, player: lambda state=state: + "Towers of the Abyss: Victory": lambda player: lambda state: state.has("Ballista", player), + "Towers of the Abyss: Siege Master": lambda player: lambda state: state.has_all(["Ballista", "Trebuchet"], player), - "Towers of the Abyss: Perfect Defense": lambda state, player: lambda state=state: + "Towers of the Abyss: Perfect Defense": lambda player: lambda state: state.has_all(["Ballista", "Walls Event"], player), }, has_ocean=False @@ -270,23 +250,18 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Kraken Strait", file_name="Kraken_Strait.json", location_rules={ - "Kraken Strait: Victory": lambda state, player: lambda state=state: - state.has_all(["Frog", "Kraken"], player), - "Kraken Strait: Well Defended": lambda state, player: lambda state=state: - state.has_all(["Frog", "Kraken"], player), - "Kraken Strait: Clipped Wings": lambda state, player: lambda state=state: state.has("Harpoon Ship", player), + "Kraken Strait: Victory": lambda player: lambda state: state.has_all(["Frog", "Kraken"], player), + "Kraken Strait: Well Defended": lambda player: lambda state: state.has_all(["Frog", "Kraken"], player), + "Kraken Strait: Clipped Wings": lambda player: lambda state: state.has("Harpoon Ship", player), } ), Wargroove2Level( name="Gnarled Mountaintop", file_name="Gnarled_Mountaintop.json", location_rules={ - "Gnarled Mountaintop: Victory": lambda state, player: lambda state=state: state.has( - "Harpy", player), - "Gnarled Mountaintop: Watch the Watchtower": lambda state, player: lambda state=state: state.has( - "Harpy", player), - "Gnarled Mountaintop: Vine Skip": lambda state, player: lambda state=state: state.has( - "Air Trooper", player), + "Gnarled Mountaintop: Victory": lambda player: lambda state: state.has("Harpy", player), + "Gnarled Mountaintop: Watch the Watchtower": lambda player: lambda state: state.has("Harpy", player), + "Gnarled Mountaintop: Vine Skip": lambda player: lambda state: state.has("Air Trooper", player), }, has_ocean=False ), @@ -294,13 +269,13 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Gold Rush", file_name="Gold_Rush.json", location_rules={ - "Gold Rush: Victory": lambda state, player: lambda state=state: state.has("Thief", player) and + "Gold Rush: Victory": lambda player: lambda state: state.has("Thief", player) and state.has_any( ["Rifleman", "Merfolk", "Warship"], player), - "Gold Rush: Lumber Island": lambda state, player: lambda state=state: state.has_any( + "Gold Rush: Lumber Island": lambda player: lambda state: state.has_any( ["Merfolk", "River Boat", "Barge"], player), - "Gold Rush: Starglass Rush": lambda state, player: lambda state=state: state.has_any( + "Gold Rush: Starglass Rush": lambda player: lambda state: state.has_any( ["River Boat", "Barge"], player), } ), @@ -308,36 +283,33 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Finishing Blow", file_name="Finishing_Blow.json", location_rules={ - "Finishing Blow: Victory": lambda state, player: lambda state=state: state.has( - "Witch", player), - "Finishing Blow: Mass Destruction": lambda state, player: lambda - state=state: state.has("Witch", player), - "Finishing Blow: Defortification": lambda state, player: lambda - state=state: state.has("Thief", player), + "Finishing Blow: Victory": lambda player: lambda state: state.has("Witch", player), + "Finishing Blow: Mass Destruction": lambda player: lambda state: state.has("Witch", player), + "Finishing Blow: Defortification": lambda player: lambda state: state.has("Thief", player), } ), Wargroove2Level( name="Frantic Inlet", file_name="Frantic_Inlet.json", location_rules={ - "Frantic Inlet: Victory": lambda state, player: lambda state=state: state.has( + "Frantic Inlet: Victory": lambda player: lambda state: state.has( "Turtle", player) and state.has_any(["Barge", "Knight"], player), - "Frantic Inlet: Plug the Gap": lambda state, player: lambda state=state: state.has("Spearman", player), - "Frantic Inlet: Portal Detour": lambda state, player: lambda - state=state: state.has_all(["Turtle", "Barge"], player), + "Frantic Inlet: Plug the Gap": lambda player: lambda state: state.has("Spearman", player), + "Frantic Inlet: Portal Detour": lambda player: lambda + state: state.has_all(["Turtle", "Barge"], player), } ), Wargroove2Level( name="Operation Seagull", file_name="Operation_Seagull.json", location_rules={ - "Operation Seagull: Victory": lambda state, player: lambda state=state: state.has( + "Operation Seagull: Victory": lambda player: lambda state: state.has( "Merfolk", player) and state.has_any(["Harpoon Ship", "Witch"], player) and state.has_any( ["Turtle", "Harpy"], player), - "Operation Seagull: Crack the Crystal": lambda state, player: lambda - state=state: state.has_any(["Warship", "Kraken"], player), - "Operation Seagull: Counter Break": lambda state, player: lambda - state=state: state.has("Dragon", player) and + "Operation Seagull: Crack the Crystal": lambda player: lambda + state: state.has_any(["Warship", "Kraken"], player), + "Operation Seagull: Counter Break": lambda player: lambda + state: state.has("Dragon", player) and state.has_all(["Harpoon Ship", "Witch"], player), } ), @@ -345,24 +317,20 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Air Support", file_name="Air_Support.json", location_rules={ - "Air Support: Victory": lambda state, player: lambda state=state: state.has_all( - ["Dragon", "Bridges Event"], player), - "Air Support: Roadkill": lambda state, player: lambda state=state: state.has_all( - ["Dragon", "Bridges Event"], player), - "Air Support: Flight Economy": lambda state, player: lambda - state=state: state.has_all(["Air Trooper", "Bridges Event"], player), + "Air Support: Victory": lambda player: lambda state: state.has_all(["Dragon", "Bridges Event"], player), + "Air Support: Roadkill": lambda player: lambda state: state.has_all(["Dragon", "Bridges Event"], player), + "Air Support: Flight Economy": lambda player: lambda + state: state.has_all(["Air Trooper", "Bridges Event"], player), } ), Wargroove2Level( name="Fortification", file_name="Fortification.json", location_rules={ - "Fortification: Victory": lambda state, player: lambda state=state: state.has_all( + "Fortification: Victory": lambda player: lambda state: state.has_all( ["Golem", "Walls Event"], player) and state.has_any(["Archer", "Trebuchet"], player), - "Fortification: Hyper Repair": lambda state, player: lambda - state=state: state.has_all(["Golem", "Walls Event"], player), - "Fortification: Defensive Artillery": lambda state, player: lambda - state=state: state.has("Trebuchet", player), + "Fortification: Hyper Repair": lambda player: lambda state: state.has_all(["Golem", "Walls Event"], player), + "Fortification: Defensive Artillery": lambda player: lambda state: state.has("Trebuchet", player), }, has_ocean=False ), @@ -370,24 +338,20 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="A Ribbitting Time", file_name="A_Ribbitting_Time.json", location_rules={ - "A Ribbitting Time: Victory": lambda state, player: lambda state=state: state.has( - "Frog", player), - "A Ribbitting Time: Leap Frog": lambda state, player: lambda state=state: state.has( - "Frog", player), - "A Ribbitting Time: Frogway Robbery": lambda state, player: lambda - state=state: state.has_all(["Frog", "Thief"], player), + "A Ribbitting Time: Victory": lambda player: lambda state: state.has("Frog", player), + "A Ribbitting Time: Leap Frog": lambda player: lambda state: state.has("Frog", player), + "A Ribbitting Time: Frogway Robbery": lambda player: lambda state: state.has_all(["Frog", "Thief"], player), } ), Wargroove2Level( name="Precarious Cliffs", file_name="Precarious_Cliffs.json", location_rules={ - "Precarious Cliffs: Victory": lambda state, player: lambda state=state: state.has_all( + "Precarious Cliffs: Victory": lambda player: lambda state: state.has_all( ["Airstrike Event", "Archer"], player), - "Precarious Cliffs: No Crit for You": lambda state, player: lambda - state=state: state.has("Airstrike Event", player), - "Precarious Cliffs: Out Ranged": lambda state, player: lambda - state=state: state.has_all(["Airstrike Event", "Archer"], player), + "Precarious Cliffs: No Crit for You": lambda player: lambda state: state.has("Airstrike Event", player), + "Precarious Cliffs: Out Ranged": lambda player: lambda + state: state.has_all(["Airstrike Event", "Archer"], player), }, has_ocean=False ), @@ -395,24 +359,20 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Split Valley", file_name="Split_Valley.json", location_rules={ - "Split Valley: Victory": lambda state, player: lambda state=state: state.has( + "Split Valley: Victory": lambda player: lambda state: state.has( "Trebuchet", player) and state.has_any(["Bridges Event", "Air Trooper"], player), - "Split Valley: Longshot": lambda state, player: lambda state=state: state.has( - "Trebuchet", player), - "Split Valley: Ranged Trinity": lambda state, player: lambda - state=state: state.has_all(["Trebuchet", "Archer", "Ballista"], - player), + "Split Valley: Longshot": lambda player: lambda state: state.has("Trebuchet", player), + "Split Valley: Ranged Trinity": lambda player: lambda + state: state.has_all(["Trebuchet", "Archer", "Ballista"], player), } ), Wargroove2Level( name="Bridge Brigade", file_name="Bridge_Brigade.json", location_rules={ - "Bridge Brigade: Victory": lambda state, player: lambda state=state: state.has_all( - ["Warship", "Spearman"], player), - "Bridge Brigade: From the Depths": lambda state, player: lambda - state=state: state.has("Kraken", player), - "Bridge Brigade: Back to the Depths": lambda state, player: lambda state=state: + "Bridge Brigade: Victory": lambda player: lambda state: state.has_all(["Warship", "Spearman"], player), + "Bridge Brigade: From the Depths": lambda player: lambda state: state.has("Kraken", player), + "Bridge Brigade: Back to the Depths": lambda player: lambda state: state.has_all(["Warship", "Spearman", "Kraken"], player), }, has_ocean=False @@ -421,11 +381,10 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Grand Theft Village", file_name="Grand_Theft_Village.json", location_rules={ - "Grand Theft Village: Victory": lambda state, player: lambda state=state: state.has( + "Grand Theft Village: Victory": lambda player: lambda state: state.has( "Thief", player) and state.has_any(["Mage", "Ballista"], player), - "Grand Theft Village: Stand Tall": lambda state, player: lambda - state=state: state.has("Golem", player), - "Grand Theft Village: Pillager": lambda state, player: lambda state=state: True, + "Grand Theft Village: Stand Tall": lambda player: lambda state: state.has("Golem", player), + "Grand Theft Village: Pillager": lambda player: lambda state: True, }, has_ocean=False ), @@ -433,11 +392,9 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Wagon Freeway", file_name="Wagon_Freeway.json", location_rules={ - "Wagon Freeway: Victory": lambda state, player: lambda state=state: state.has_all( - ["Wagon", "Spearman"], player), - "Wagon Freeway: All Mine Now": lambda state, player: lambda state=state: True, - "Wagon Freeway: Pigeon Carrier": lambda state, player: lambda state=state: - state.has("Air Trooper", player), + "Wagon Freeway: Victory": lambda player: lambda state: state.has_all(["Wagon", "Spearman"], player), + "Wagon Freeway: All Mine Now": lambda player: lambda state: True, + "Wagon Freeway: Pigeon Carrier": lambda player: lambda state: state.has("Air Trooper", player), }, has_ocean=False ), @@ -448,7 +405,7 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Disastrous Crossing", file_name="Disastrous_Crossing.json", location_rules={"Disastrous Crossing: Victory": - lambda state, player: lambda state=state: state.has_any( + lambda player: lambda state: state.has_any( ["Merfolk", "River Boat"], player) and state.has_any( ["Knight", "Kraken"], @@ -458,7 +415,7 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Dark Mirror", file_name="Dark_Mirror.json", location_rules={ - "Dark Mirror: Victory": lambda state, player: lambda state=state: state.has( + "Dark Mirror: Victory": lambda player: lambda state: state.has( "Archer", player) and state.has_any(["Mage", "Ballista"], player) and state.has_any( ["Harpy", "Dragon"], player)}, has_ocean=False @@ -467,16 +424,14 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Doomed Metropolis", file_name="Doomed_Metropolis.json", location_rules={ - "Doomed Metropolis: Victory": lambda state, player: lambda state=state: state.has_all( - ["Mage", "Knight"], player)}, + "Doomed Metropolis: Victory": lambda player: lambda state: state.has_all(["Mage", "Knight"], player)}, has_ocean=False ), Wargroove2Level( name="Dementia Castle", file_name="Dementia_Castle.json", location_rules={"Dementia Castle: Victory": - lambda state, player: lambda state=state: state.has_all( - ["Merfolk", "Mage", "Golem", "Harpy"], player)} + lambda player: lambda state: state.has_all(["Merfolk", "Mage", "Golem", "Harpy"], player)} ), ] @@ -486,44 +441,36 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Swimming at the Docks", file_name="Swimming_at_the_Docks.json", location_rules={ - "Swimming at the Docks: Victory": lambda state, player: lambda state=state: True, - "Swimming at the Docks: Dogs Counter Knights": lambda state, player: lambda - state=state: True, - "Swimming at the Docks: Kayaking": lambda state, player: lambda - state=state: state.has("River Boat", player), + "Swimming at the Docks: Victory": lambda player: lambda state: True, + "Swimming at the Docks: Dogs Counter Knights": lambda player: lambda state: True, + "Swimming at the Docks: Kayaking": lambda player: lambda state: state.has("River Boat", player), } ), Wargroove2Level( name="Ancient Discoveries", file_name="Ancient_Discoveries.json", location_rules={ - "Ancient Discoveries: Victory": lambda state, player: lambda state=state: True, - "Ancient Discoveries: So many Choices": lambda state, player: lambda - state=state: True, - "Ancient Discoveries: Height Advantage": lambda state, player: lambda - state=state: state.has("Golem", player), + "Ancient Discoveries: Victory": lambda player: lambda state: True, + "Ancient Discoveries: So many Choices": lambda player: lambda state: True, + "Ancient Discoveries: Height Advantage": lambda player: lambda state: state.has("Golem", player), } ), Wargroove2Level( name="Observation Isle", file_name="Observation_Isle.json", location_rules={ - "Observation Isle: Victory": lambda state, player: lambda state=state: True, - "Observation Isle: Become the Watcher": lambda state, player: lambda - state=state: True, - "Observation Isle: Execute the Watcher": lambda state, player: lambda - state=state: state.has("Walls Event", player), + "Observation Isle: Victory": lambda player: lambda state: True, + "Observation Isle: Become the Watcher": lambda player: lambda state: True, + "Observation Isle: Execute the Watcher": lambda player: lambda state: state.has("Walls Event", player), } ), Wargroove2Level( name="Majestic Mountain", file_name="Majestic_Mountain.json", location_rules={ - "Majestic Mountain: Victory": lambda state, player: lambda state=state: True, - "Majestic Mountain: Mountain Climbing": lambda state, player: lambda - state=state: True, - "Majestic Mountain: Legend of the Mountains": lambda state, player: lambda - state=state: state.has("Air Trooper", player), + "Majestic Mountain: Victory": lambda player: lambda state: True, + "Majestic Mountain: Mountain Climbing": lambda player: lambda state: True, + "Majestic Mountain: Legend of the Mountains": lambda player: lambda state: state.has("Air Trooper", player), } ), ] @@ -532,8 +479,8 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No name="Humble Beginnings Rebirth", file_name="", location_rules={ - "Humble Beginnings Rebirth: Victory": lambda state, player: lambda state=state: True, - "Humble Beginnings Rebirth: Talk to Nadia": lambda state, player: lambda state=state: True, - "Humble Beginnings Rebirth: Good Dog": lambda state, player: lambda state=state: True + "Humble Beginnings Rebirth: Victory": lambda player: lambda state: True, + "Humble Beginnings Rebirth: Talk to Nadia": lambda player: lambda state: True, + "Humble Beginnings Rebirth: Good Dog": lambda player: lambda state: True } ) From 8f72f4be7558e0e80b268ef85a52d8d4b55e9da9 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 16:43:31 +0000 Subject: [PATCH 56/89] Re-use the same rule for a location and its extras --- worlds/wargroove2/Levels.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 769c0c2ffcd9..8cf2b64fc95e 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -57,9 +57,10 @@ def __init__(self, name: str, file_name: str, location_rules: dict, victory_loca def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=lambda state: True) -> None: for location_name, rule_factory in self.location_rules.items(): - rule = rule_factory(player) - set_rule(world.get_location(location_name), lambda state, current_rule=rule: - current_rule(state) and additional_rule(state)) + def combined_rule(state, current_rule=rule_factory(player)): + return current_rule(state) and additional_rule(state) + + set_rule(world.get_location(location_name), combined_rule) loc_id = location_table.get(location_name, 0) extras = 1 if loc_id is not None and location_name.endswith("Victory"): @@ -67,8 +68,7 @@ def define_access_rules(self, world: "Wargroove2World", player: int, additional_ elif loc_id is not None: extras = world.options.objective_locations.value for i in range(1, extras): - set_rule(world.get_location(location_name + f" Extra {i}"), lambda state, current_rule=rule: - current_rule(state) and additional_rule(state)) + set_rule(world.get_location(location_name + f" Extra {i}"), combined_rule) region = world.get_region(self.region_name) set_region_exit_rules(region, world, self.victory_locations, operator='and') From 79d579e84c4782f636c6dc47d8252609555104d0 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 16:48:48 +0000 Subject: [PATCH 57/89] Only combine rules when there is a rule to combine --- worlds/wargroove2/Levels.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 8cf2b64fc95e..5bf73b486c9e 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -55,12 +55,16 @@ def __init__(self, name: str, file_name: str, location_rules: dict, victory_loca else: self.victory_locations = [name + ': Victory'] - def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=lambda state: True) -> None: + def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=None) -> None: for location_name, rule_factory in self.location_rules.items(): - def combined_rule(state, current_rule=rule_factory(player)): - return current_rule(state) and additional_rule(state) + if additional_rule is None: + rule = rule_factory(player) + else: + # Combine both rules into one function. + def rule(state, current_rule=rule_factory(player)): + return current_rule(state) and additional_rule(state) - set_rule(world.get_location(location_name), combined_rule) + set_rule(world.get_location(location_name), rule) loc_id = location_table.get(location_name, 0) extras = 1 if loc_id is not None and location_name.endswith("Victory"): @@ -68,7 +72,7 @@ def combined_rule(state, current_rule=rule_factory(player)): elif loc_id is not None: extras = world.options.objective_locations.value for i in range(1, extras): - set_rule(world.get_location(location_name + f" Extra {i}"), combined_rule) + set_rule(world.get_location(location_name + f" Extra {i}"), rule) region = world.get_region(self.region_name) set_region_exit_rules(region, world, self.victory_locations, operator='and') From 440ee4731607177407892d7cdbd4a2ee66a7370f Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 16:53:08 +0000 Subject: [PATCH 58/89] Rename confusing `extras` variables to `total_locations` The variable was not the number of extra locations to create, but rather the total number of locations to be created for the current level, which is 1 more than the number of extra locations to create. --- worlds/wargroove2/Levels.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 5bf73b486c9e..eb6fb0b37524 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -66,12 +66,12 @@ def rule(state, current_rule=rule_factory(player)): set_rule(world.get_location(location_name), rule) loc_id = location_table.get(location_name, 0) - extras = 1 + total_locations = 1 if loc_id is not None and location_name.endswith("Victory"): - extras = world.options.victory_locations.value + total_locations = world.options.victory_locations.value elif loc_id is not None: - extras = world.options.objective_locations.value - for i in range(1, extras): + total_locations = world.options.objective_locations.value + for i in range(1, total_locations): set_rule(world.get_location(location_name + f" Extra {i}"), rule) region = world.get_region(self.region_name) set_region_exit_rules(region, world, self.victory_locations, operator='and') @@ -84,12 +84,12 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No loc_id = location_table.get(location, 0) wg2_location = Wargroove2Location(player, location, loc_id, region) region.locations.append(wg2_location) - extras = 1 + total_locations = 1 if loc_id is not None and location.endswith("Victory"): - extras = multiworld.worlds[player].options.victory_locations.value + total_locations = multiworld.worlds[player].options.victory_locations.value elif loc_id is not None: - extras = multiworld.worlds[player].options.objective_locations.value - for i in range(1, extras): + total_locations = multiworld.worlds[player].options.objective_locations.value + for i in range(1, total_locations): extra_location = location + f" Extra {i}" loc_id = location_table.get(extra_location, 0) wg2_location = Wargroove2Location(player, extra_location, loc_id, region) From 4e26d041ceb1c0c99362d59372fb2bc755763780 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 16:56:56 +0000 Subject: [PATCH 59/89] Pass Wargroove2World to `define_region` for better options type hints --- worlds/wargroove2/Levels.py | 10 +++++----- worlds/wargroove2/Regions.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index eb6fb0b37524..0c855b30c1df 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -1,6 +1,6 @@ from typing import List, TYPE_CHECKING -from BaseClasses import Region, Entrance, MultiWorld +from BaseClasses import Region, Entrance from .Locations import location_table, Wargroove2Location from worlds.generic.Rules import set_rule @@ -76,9 +76,9 @@ def rule(state, current_rule=rule_factory(player)): region = world.get_region(self.region_name) set_region_exit_rules(region, world, self.victory_locations, operator='and') - def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=None) -> Region: + def define_region(self, name: str, world: "Wargroove2World", player: int, exits=None) -> Region: self.region_name = name - region = Region(name, player, multiworld) + region = Region(name, player, world.multiworld) if self.location_rules.keys(): for location in self.location_rules.keys(): loc_id = location_table.get(location, 0) @@ -86,9 +86,9 @@ def define_region(self, name: str, multiworld: MultiWorld, player: int, exits=No region.locations.append(wg2_location) total_locations = 1 if loc_id is not None and location.endswith("Victory"): - total_locations = multiworld.worlds[player].options.victory_locations.value + total_locations = world.options.victory_locations.value elif loc_id is not None: - total_locations = multiworld.worlds[player].options.objective_locations.value + total_locations = world.options.objective_locations.value for i in range(1, total_locations): extra_location = location + f" Extra {i}" loc_id = location_table.get(extra_location, 0) diff --git a/worlds/wargroove2/Regions.py b/worlds/wargroove2/Regions.py index 30ac4caac781..c1655a5bec06 100644 --- a/worlds/wargroove2/Regions.py +++ b/worlds/wargroove2/Regions.py @@ -14,7 +14,7 @@ def create_regions(world: "Wargroove2World") -> None: menu_region = Region('Menu', player, multiworld) menu_region.exits.append(Entrance(player, 'Menu exits to Humble Beginnings Rebirth', menu_region)) - first_level_region = first_level.define_region("Humble Beginnings Rebirth", multiworld, player, + first_level_region = first_level.define_region("Humble Beginnings Rebirth", world, player, exits=[region_names[0], region_names[1], region_names[2], region_names[3]]) multiworld.regions += [menu_region, first_level_region] @@ -22,7 +22,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 1s for level_num in range(0, 4): next_level = level_num * 3 + 4 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, player, exits=[ + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, player, exits=[ region_names[next_level], region_names[next_level + 1], region_names[next_level + 2] @@ -30,7 +30,7 @@ def create_regions(world: "Wargroove2World") -> None: # Define Level 2s for level_num in range(4, 16): next_level = level_num + 12 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, player, + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, player, exits=[region_names[next_level]])] # Define Level 3s for level_num in range(16, 28): @@ -41,14 +41,14 @@ def create_regions(world: "Wargroove2World") -> None: final_level_name = FINAL_LEVEL_3 elif level_num >= 19: final_level_name = FINAL_LEVEL_2 - multiworld.regions += [level_list[level_num].define_region(region_names[level_num], multiworld, player, + multiworld.regions += [level_list[level_num].define_region(region_names[level_num], world, player, exits=[final_level_name])] # Define Final Levels - multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, multiworld, player), - final_levels[1].define_region(FINAL_LEVEL_2, multiworld, player), - final_levels[2].define_region(FINAL_LEVEL_3, multiworld, player), - final_levels[3].define_region(FINAL_LEVEL_4, multiworld, player)] + multiworld.regions += [final_levels[0].define_region(FINAL_LEVEL_1, world, player), + final_levels[1].define_region(FINAL_LEVEL_2, world, player), + final_levels[2].define_region(FINAL_LEVEL_3, world, player), + final_levels[3].define_region(FINAL_LEVEL_4, world, player)] # # link up our regions with the entrances world.get_entrance("Menu exits to Humble Beginnings Rebirth").connect( From 67ce3408e339584fad8153ac0e43a37a139ce08e Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:01:37 +0000 Subject: [PATCH 60/89] Remove unused `operator` argument from `set_region_exit_rules` The argument changed nothing about the function --- worlds/wargroove2/Levels.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 0c855b30c1df..b20c7d75d1c8 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -25,13 +25,9 @@ FINAL_LEVEL_COUNT = 4 -def set_region_exit_rules(region: Region, world: "Wargroove2World", locations: List[str], operator: str = "or") -> None: - if operator == "or": - exit_rule = lambda state: any( - world.get_location(location).access_rule(state) for location in locations) - else: - exit_rule = lambda state: all( - world.get_location(location).access_rule(state) for location in locations) +def set_region_exit_rules(region: Region, world: "Wargroove2World", locations: List[str]) -> None: + exit_rule = lambda state: any( + world.get_location(location).access_rule(state) for location in locations) for region_exit in region.exits: region_exit.access_rule = exit_rule @@ -74,7 +70,7 @@ def rule(state, current_rule=rule_factory(player)): for i in range(1, total_locations): set_rule(world.get_location(location_name + f" Extra {i}"), rule) region = world.get_region(self.region_name) - set_region_exit_rules(region, world, self.victory_locations, operator='and') + set_region_exit_rules(region, world, self.victory_locations) def define_region(self, name: str, world: "Wargroove2World", player: int, exits=None) -> Region: self.region_name = name From 16699e45ff5e3be2dfb488a02877baeeefe508e1 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:05:18 +0000 Subject: [PATCH 61/89] Replace exit_rule lambda and generate with function and for loop --- worlds/wargroove2/Levels.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index b20c7d75d1c8..fb90acfc9b47 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -26,8 +26,14 @@ def set_region_exit_rules(region: Region, world: "Wargroove2World", locations: List[str]) -> None: - exit_rule = lambda state: any( - world.get_location(location).access_rule(state) for location in locations) + rules = [world.get_location(location_name).access_rule for location_name in locations] + + def exit_rule(state): + for rule in rules: + if rule(state): + return True + return False + for region_exit in region.exits: region_exit.access_rule = exit_rule From 12a4736142273a7f1027f1f4bfbb4daa66f65f7c Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:07:49 +0000 Subject: [PATCH 62/89] Replace mutable default argument in Wargroove2Level.__init__ --- worlds/wargroove2/Levels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index fb90acfc9b47..241b112927bd 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -46,7 +46,7 @@ class Wargroove2Level: victory_locations: List[str] has_ocean: bool = True - def __init__(self, name: str, file_name: str, location_rules: dict, victory_locations: List[str] = [], + def __init__(self, name: str, file_name: str, location_rules: dict, victory_locations: List[str] | None = None, has_ocean: bool = True): self.name = name self.file_name = file_name From 816f55d973236eb3cfaf8d835afcb223edc6eba9 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:10:07 +0000 Subject: [PATCH 63/89] Add type hints for `Wargroove2Level.location_rules` --- worlds/wargroove2/Levels.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 241b112927bd..818309283cb3 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -1,8 +1,8 @@ -from typing import List, TYPE_CHECKING +from typing import List, TYPE_CHECKING, Callable from BaseClasses import Region, Entrance from .Locations import location_table, Wargroove2Location -from worlds.generic.Rules import set_rule +from worlds.generic.Rules import set_rule, CollectionRule if TYPE_CHECKING: from . import Wargroove2World @@ -38,16 +38,19 @@ def exit_rule(state): region_exit.access_rule = exit_rule +LocationRules = dict[str, Callable[[int], CollectionRule]] + + class Wargroove2Level: name: str file_name: str - location_rules: dict + location_rules: LocationRules region_name: str victory_locations: List[str] has_ocean: bool = True - def __init__(self, name: str, file_name: str, location_rules: dict, victory_locations: List[str] | None = None, - has_ocean: bool = True): + def __init__(self, name: str, file_name: str, location_rules: LocationRules, + victory_locations: List[str] | None = None, has_ocean: bool = True): self.name = name self.file_name = file_name self.location_rules = location_rules From 7280086e35f611388d85361b32de17aa9f4c467c Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:16:36 +0000 Subject: [PATCH 64/89] Replace lists in rules with tuples tuples are faster to create and more memory efficient. For tuples containing only literals, the difference is even greater. --- worlds/wargroove2/Levels.py | 108 ++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 818309283cb3..092160d5bf31 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -113,20 +113,20 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Cherrystone_Landing.json", location_rules={ "Cherrystone Landing: Victory": lambda player: lambda - state: state.has_all(["Warship", "Barge", "Landing Event"], player), + state: state.has_all(("Warship", "Barge", "Landing Event"), player), "Cherrystone Landing: Smacked a Trebuchet": lambda player: lambda - state: state.has_all(["Warship", "Barge", "Landing Event", "Golem"], player), + state: state.has_all(("Warship", "Barge", "Landing Event", "Golem"), player), "Cherrystone Landing: Smacked a Fortified Village": lambda player: lambda - state: state.has_all(["Barge", "Landing Event", "Golem"], player) + state: state.has_all(("Barge", "Landing Event", "Golem"), player) } ), Wargroove2Level( name="Spire Fire", file_name="Spire_Fire.json", location_rules={ - "Spire Fire: Victory": lambda player: lambda state: state.has_any(["Mage", "Witch"], player), + "Spire Fire: Victory": lambda player: lambda state: state.has_any(("Mage", "Witch"), player), "Spire Fire: Kill Enemy Sky Rider": lambda player: lambda state: state.has("Witch", player), - "Spire Fire: Win without losing your Dragon": lambda player: lambda state: state.has_any(["Mage", "Witch"], player) + "Spire Fire: Win without losing your Dragon": lambda player: lambda state: state.has_any(("Mage", "Witch"), player) }, has_ocean=False ), @@ -137,7 +137,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= "Nuru's Vengeance: Victory": lambda player: lambda state: state.has("Knight", player), "Nuru's Vengeance: Defeat all Dogs": lambda player: lambda state: state.has("Knight", player), "Nuru's Vengeance: Spearman Destroys the Gate": lambda player: lambda - state: state.has_all(["Knight", "Spearman"], player) + state: state.has_all(("Knight", "Spearman"), player) }, has_ocean=False ), @@ -156,16 +156,16 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= location_rules={ "Den-Two-Away: Victory": lambda player: lambda state: state.has("Harpy", player), "Den-Two-Away: Commander Captures the Lumbermill": lambda player: lambda - state: state.has_all(["Harpy", "Balloon"], player), + state: state.has_all(("Harpy", "Balloon"), player), } ), Wargroove2Level( name="Skydiving", file_name="Skydiving.json", location_rules={ - "Skydiving: Victory": lambda player: lambda state: state.has_all(["Balloon", "Airstrike Event"], player), + "Skydiving: Victory": lambda player: lambda state: state.has_all(("Balloon", "Airstrike Event"), player), "Skydiving: Dragon Defeats Stronghold": lambda player: lambda - state: state.has_all(["Balloon", "Airstrike Event", "Dragon"], player), + state: state.has_all(("Balloon", "Airstrike Event", "Dragon"), player), }, has_ocean=False ), @@ -173,10 +173,10 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Sunken Forest", file_name="Sunken_Forest.json", location_rules={ - "Sunken Forest: Victory": lambda player: lambda state: state.has_any(["Mage", "Harpoon Ship"], player), + "Sunken Forest: Victory": lambda player: lambda state: state.has_any(("Mage", "Harpoon Ship"), player), "Sunken Forest: High Ground": lambda player: lambda state: state.has("Archer", player), "Sunken Forest: Coastal Siege": lambda player: lambda state: - state.has("Warship", player) and state.has_any(["Mage", "Harpoon Ship"], player), + state.has("Warship", player) and state.has_any(("Mage", "Harpoon Ship"), player), } ), Wargroove2Level( @@ -184,9 +184,9 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Tenris_Mistake.json", location_rules={ "Tenri's Mistake: Victory": lambda player: lambda state: state.has_any( - ["Balloon", "Air Trooper"], player), + ("Balloon", "Air Trooper"), player), "Tenri's Mistake: Mighty Barracks": lambda player: lambda - state: state.has_any(["Balloon", "Air Trooper"], player), + state: state.has_any(("Balloon", "Air Trooper"), player), "Tenri's Mistake: Commander Arrives": lambda player: lambda state: state.has("Balloon", player), } ), @@ -194,9 +194,9 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Enmity Cliffs", file_name="Enmity_Cliffs.json", location_rules={ - "Enmity Cliffs: Victory": lambda player: lambda state: state.has_all(["Spearman", "Bridges Event"], player), + "Enmity Cliffs: Victory": lambda player: lambda state: state.has_all(("Spearman", "Bridges Event"), player), "Enmity Cliffs: Spear Flood": lambda player: lambda state: state.has("Spearman", player), - "Enmity Cliffs: Across the Gap": lambda player: lambda state: state.has_any(["Archer", "Rifleman"], player), + "Enmity Cliffs: Across the Gap": lambda player: lambda state: state.has_any(("Archer", "Rifleman"), player), }, has_ocean=False ), @@ -207,9 +207,9 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= "Terrible Tributaries: Victory": lambda player: lambda state: state.has( "River Boat", player), "Terrible Tributaries: Swimming Knights": lambda player: lambda - state: state.has_all(["Merfolk", "River Boat"], player), + state: state.has_all(("Merfolk", "River Boat"), player), "Terrible Tributaries: Steal Code Names": lambda player: lambda - state: state.has_all(["Thief", "River Boat"], player), + state: state.has_all(("Thief", "River Boat"), player), } ), Wargroove2Level( @@ -217,8 +217,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Beached.json", location_rules={ "Beached: Victory": lambda player: lambda state: state.has("Knight", player), - "Beached: Turtle Power": lambda player: lambda state: state.has_all(["Turtle", "Knight"], player), - "Beached: Happy Turtle": lambda player: lambda state: state.has_all(["Turtle", "Knight"], player), + "Beached: Turtle Power": lambda player: lambda state: state.has_all(("Turtle", "Knight"), player), + "Beached: Happy Turtle": lambda player: lambda state: state.has_all(("Turtle", "Knight"), player), } ), Wargroove2Level( @@ -237,9 +237,9 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= location_rules={ "Riflemen Blockade: Victory": lambda player: lambda state: state.has("Rifleman", player), "Riflemen Blockade: From the Mountains": lambda player: lambda - state: state.has_all(["Rifleman", "Harpy"], player), + state: state.has_all(("Rifleman", "Harpy"), player), "Riflemen Blockade: To the Road": lambda player: lambda - state: state.has_all(["Rifleman", "Dragon"], player), + state: state.has_all(("Rifleman", "Dragon"), player), }, has_ocean=False ), @@ -249,9 +249,9 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= location_rules={ "Towers of the Abyss: Victory": lambda player: lambda state: state.has("Ballista", player), "Towers of the Abyss: Siege Master": lambda player: lambda state: - state.has_all(["Ballista", "Trebuchet"], player), + state.has_all(("Ballista", "Trebuchet"), player), "Towers of the Abyss: Perfect Defense": lambda player: lambda state: - state.has_all(["Ballista", "Walls Event"], player), + state.has_all(("Ballista", "Walls Event"), player), }, has_ocean=False ), @@ -259,8 +259,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Kraken Strait", file_name="Kraken_Strait.json", location_rules={ - "Kraken Strait: Victory": lambda player: lambda state: state.has_all(["Frog", "Kraken"], player), - "Kraken Strait: Well Defended": lambda player: lambda state: state.has_all(["Frog", "Kraken"], player), + "Kraken Strait: Victory": lambda player: lambda state: state.has_all(("Frog", "Kraken"), player), + "Kraken Strait: Well Defended": lambda player: lambda state: state.has_all(("Frog", "Kraken"), player), "Kraken Strait: Clipped Wings": lambda player: lambda state: state.has("Harpoon Ship", player), } ), @@ -280,12 +280,12 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= location_rules={ "Gold Rush: Victory": lambda player: lambda state: state.has("Thief", player) and state.has_any( - ["Rifleman", "Merfolk", "Warship"], + ("Rifleman", "Merfolk", "Warship"), player), "Gold Rush: Lumber Island": lambda player: lambda state: state.has_any( - ["Merfolk", "River Boat", "Barge"], player), + ("Merfolk", "River Boat", "Barge"), player), "Gold Rush: Starglass Rush": lambda player: lambda state: state.has_any( - ["River Boat", "Barge"], player), + ("River Boat", "Barge"), player), } ), Wargroove2Level( @@ -302,10 +302,10 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Frantic_Inlet.json", location_rules={ "Frantic Inlet: Victory": lambda player: lambda state: state.has( - "Turtle", player) and state.has_any(["Barge", "Knight"], player), + "Turtle", player) and state.has_any(("Barge", "Knight"), player), "Frantic Inlet: Plug the Gap": lambda player: lambda state: state.has("Spearman", player), "Frantic Inlet: Portal Detour": lambda player: lambda - state: state.has_all(["Turtle", "Barge"], player), + state: state.has_all(("Turtle", "Barge"), player), } ), Wargroove2Level( @@ -313,23 +313,23 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Operation_Seagull.json", location_rules={ "Operation Seagull: Victory": lambda player: lambda state: state.has( - "Merfolk", player) and state.has_any(["Harpoon Ship", "Witch"], player) and state.has_any( - ["Turtle", "Harpy"], player), + "Merfolk", player) and state.has_any(("Harpoon Ship", "Witch"), player) and state.has_any( + ("Turtle", "Harpy"), player), "Operation Seagull: Crack the Crystal": lambda player: lambda - state: state.has_any(["Warship", "Kraken"], player), + state: state.has_any(("Warship", "Kraken"), player), "Operation Seagull: Counter Break": lambda player: lambda state: state.has("Dragon", player) and - state.has_all(["Harpoon Ship", "Witch"], player), + state.has_all(("Harpoon Ship", "Witch"), player), } ), Wargroove2Level( name="Air Support", file_name="Air_Support.json", location_rules={ - "Air Support: Victory": lambda player: lambda state: state.has_all(["Dragon", "Bridges Event"], player), - "Air Support: Roadkill": lambda player: lambda state: state.has_all(["Dragon", "Bridges Event"], player), + "Air Support: Victory": lambda player: lambda state: state.has_all(("Dragon", "Bridges Event"), player), + "Air Support: Roadkill": lambda player: lambda state: state.has_all(("Dragon", "Bridges Event"), player), "Air Support: Flight Economy": lambda player: lambda - state: state.has_all(["Air Trooper", "Bridges Event"], player), + state: state.has_all(("Air Trooper", "Bridges Event"), player), } ), Wargroove2Level( @@ -337,8 +337,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Fortification.json", location_rules={ "Fortification: Victory": lambda player: lambda state: state.has_all( - ["Golem", "Walls Event"], player) and state.has_any(["Archer", "Trebuchet"], player), - "Fortification: Hyper Repair": lambda player: lambda state: state.has_all(["Golem", "Walls Event"], player), + ("Golem", "Walls Event"), player) and state.has_any(("Archer", "Trebuchet"), player), + "Fortification: Hyper Repair": lambda player: lambda state: state.has_all(("Golem", "Walls Event"), player), "Fortification: Defensive Artillery": lambda player: lambda state: state.has("Trebuchet", player), }, has_ocean=False @@ -349,7 +349,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= location_rules={ "A Ribbitting Time: Victory": lambda player: lambda state: state.has("Frog", player), "A Ribbitting Time: Leap Frog": lambda player: lambda state: state.has("Frog", player), - "A Ribbitting Time: Frogway Robbery": lambda player: lambda state: state.has_all(["Frog", "Thief"], player), + "A Ribbitting Time: Frogway Robbery": lambda player: lambda state: state.has_all(("Frog", "Thief"), player), } ), Wargroove2Level( @@ -357,10 +357,10 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Precarious_Cliffs.json", location_rules={ "Precarious Cliffs: Victory": lambda player: lambda state: state.has_all( - ["Airstrike Event", "Archer"], player), + ("Airstrike Event", "Archer"), player), "Precarious Cliffs: No Crit for You": lambda player: lambda state: state.has("Airstrike Event", player), "Precarious Cliffs: Out Ranged": lambda player: lambda - state: state.has_all(["Airstrike Event", "Archer"], player), + state: state.has_all(("Airstrike Event", "Archer"), player), }, has_ocean=False ), @@ -369,20 +369,20 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Split_Valley.json", location_rules={ "Split Valley: Victory": lambda player: lambda state: state.has( - "Trebuchet", player) and state.has_any(["Bridges Event", "Air Trooper"], player), + "Trebuchet", player) and state.has_any(("Bridges Event", "Air Trooper"), player), "Split Valley: Longshot": lambda player: lambda state: state.has("Trebuchet", player), "Split Valley: Ranged Trinity": lambda player: lambda - state: state.has_all(["Trebuchet", "Archer", "Ballista"], player), + state: state.has_all(("Trebuchet", "Archer", "Ballista"), player), } ), Wargroove2Level( name="Bridge Brigade", file_name="Bridge_Brigade.json", location_rules={ - "Bridge Brigade: Victory": lambda player: lambda state: state.has_all(["Warship", "Spearman"], player), + "Bridge Brigade: Victory": lambda player: lambda state: state.has_all(("Warship", "Spearman"), player), "Bridge Brigade: From the Depths": lambda player: lambda state: state.has("Kraken", player), "Bridge Brigade: Back to the Depths": lambda player: lambda state: - state.has_all(["Warship", "Spearman", "Kraken"], player), + state.has_all(("Warship", "Spearman", "Kraken"), player), }, has_ocean=False ), @@ -391,7 +391,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Grand_Theft_Village.json", location_rules={ "Grand Theft Village: Victory": lambda player: lambda state: state.has( - "Thief", player) and state.has_any(["Mage", "Ballista"], player), + "Thief", player) and state.has_any(("Mage", "Ballista"), player), "Grand Theft Village: Stand Tall": lambda player: lambda state: state.has("Golem", player), "Grand Theft Village: Pillager": lambda player: lambda state: True, }, @@ -401,7 +401,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Wagon Freeway", file_name="Wagon_Freeway.json", location_rules={ - "Wagon Freeway: Victory": lambda player: lambda state: state.has_all(["Wagon", "Spearman"], player), + "Wagon Freeway: Victory": lambda player: lambda state: state.has_all(("Wagon", "Spearman"), player), "Wagon Freeway: All Mine Now": lambda player: lambda state: True, "Wagon Freeway: Pigeon Carrier": lambda player: lambda state: state.has("Air Trooper", player), }, @@ -415,9 +415,9 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Disastrous_Crossing.json", location_rules={"Disastrous Crossing: Victory": lambda player: lambda state: state.has_any( - ["Merfolk", "River Boat"], player) and + ("Merfolk", "River Boat"), player) and state.has_any( - ["Knight", "Kraken"], + ("Knight", "Kraken"), player)} ), Wargroove2Level( @@ -425,22 +425,22 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Dark_Mirror.json", location_rules={ "Dark Mirror: Victory": lambda player: lambda state: state.has( - "Archer", player) and state.has_any(["Mage", "Ballista"], player) and state.has_any( - ["Harpy", "Dragon"], player)}, + "Archer", player) and state.has_any(("Mage", "Ballista"), player) and state.has_any( + ("Harpy", "Dragon"), player)}, has_ocean=False ), Wargroove2Level( name="Doomed Metropolis", file_name="Doomed_Metropolis.json", location_rules={ - "Doomed Metropolis: Victory": lambda player: lambda state: state.has_all(["Mage", "Knight"], player)}, + "Doomed Metropolis: Victory": lambda player: lambda state: state.has_all(("Mage", "Knight"), player)}, has_ocean=False ), Wargroove2Level( name="Dementia Castle", file_name="Dementia_Castle.json", location_rules={"Dementia Castle: Victory": - lambda player: lambda state: state.has_all(["Merfolk", "Mage", "Golem", "Harpy"], player)} + lambda player: lambda state: state.has_all(("Merfolk", "Mage", "Golem", "Harpy"), player)} ), ] From d9fd0a51e5210b13c61e6080308f252e09f33204 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:25:43 +0000 Subject: [PATCH 65/89] Don't add rules when there are none Replacing setting/combining `lambda state: True` with not setting/combining the rule in the first place. --- worlds/wargroove2/Levels.py | 47 ++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 092160d5bf31..b3b92ed3a764 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -38,7 +38,7 @@ def exit_rule(state): region_exit.access_rule = exit_rule -LocationRules = dict[str, Callable[[int], CollectionRule]] +LocationRules = dict[str, Callable[[int], CollectionRule] | None] class Wargroove2Level: @@ -62,12 +62,21 @@ def __init__(self, name: str, file_name: str, location_rules: LocationRules, def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=None) -> None: for location_name, rule_factory in self.location_rules.items(): - if additional_rule is None: - rule = rule_factory(player) + if rule_factory is None: + if additional_rule is None: + # No rules to set. + continue + else: + # Only the additional rule to set. + rule = additional_rule else: - # Combine both rules into one function. - def rule(state, current_rule=rule_factory(player)): - return current_rule(state) and additional_rule(state) + if additional_rule is None: + # Only the location's rule to set. + rule = rule_factory(player) + else: + # Combine both rules into one function and set the combined function. + def rule(state, current_rule=rule_factory(player)): + return current_rule(state) and additional_rule(state) set_rule(world.get_location(location_name), rule) loc_id = location_table.get(location_name, 0) @@ -393,7 +402,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= "Grand Theft Village: Victory": lambda player: lambda state: state.has( "Thief", player) and state.has_any(("Mage", "Ballista"), player), "Grand Theft Village: Stand Tall": lambda player: lambda state: state.has("Golem", player), - "Grand Theft Village: Pillager": lambda player: lambda state: True, + "Grand Theft Village: Pillager": None, }, has_ocean=False ), @@ -402,7 +411,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= file_name="Wagon_Freeway.json", location_rules={ "Wagon Freeway: Victory": lambda player: lambda state: state.has_all(("Wagon", "Spearman"), player), - "Wagon Freeway: All Mine Now": lambda player: lambda state: True, + "Wagon Freeway: All Mine Now": None, "Wagon Freeway: Pigeon Carrier": lambda player: lambda state: state.has("Air Trooper", player), }, has_ocean=False @@ -450,8 +459,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Swimming at the Docks", file_name="Swimming_at_the_Docks.json", location_rules={ - "Swimming at the Docks: Victory": lambda player: lambda state: True, - "Swimming at the Docks: Dogs Counter Knights": lambda player: lambda state: True, + "Swimming at the Docks: Victory": None, + "Swimming at the Docks: Dogs Counter Knights": None, "Swimming at the Docks: Kayaking": lambda player: lambda state: state.has("River Boat", player), } ), @@ -459,8 +468,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Ancient Discoveries", file_name="Ancient_Discoveries.json", location_rules={ - "Ancient Discoveries: Victory": lambda player: lambda state: True, - "Ancient Discoveries: So many Choices": lambda player: lambda state: True, + "Ancient Discoveries: Victory": None, + "Ancient Discoveries: So many Choices": None, "Ancient Discoveries: Height Advantage": lambda player: lambda state: state.has("Golem", player), } ), @@ -468,8 +477,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Observation Isle", file_name="Observation_Isle.json", location_rules={ - "Observation Isle: Victory": lambda player: lambda state: True, - "Observation Isle: Become the Watcher": lambda player: lambda state: True, + "Observation Isle: Victory": None, + "Observation Isle: Become the Watcher": None, "Observation Isle: Execute the Watcher": lambda player: lambda state: state.has("Walls Event", player), } ), @@ -477,8 +486,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Majestic Mountain", file_name="Majestic_Mountain.json", location_rules={ - "Majestic Mountain: Victory": lambda player: lambda state: True, - "Majestic Mountain: Mountain Climbing": lambda player: lambda state: True, + "Majestic Mountain: Victory": None, + "Majestic Mountain: Mountain Climbing": None, "Majestic Mountain: Legend of the Mountains": lambda player: lambda state: state.has("Air Trooper", player), } ), @@ -488,8 +497,8 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= name="Humble Beginnings Rebirth", file_name="", location_rules={ - "Humble Beginnings Rebirth: Victory": lambda player: lambda state: True, - "Humble Beginnings Rebirth: Talk to Nadia": lambda player: lambda state: True, - "Humble Beginnings Rebirth: Good Dog": lambda player: lambda state: True + "Humble Beginnings Rebirth: Victory": None, + "Humble Beginnings Rebirth: Talk to Nadia": None, + "Humble Beginnings Rebirth: Good Dog": None } ) From d24bc911ef83c07d79c26a5ad3f16c1f5dd62191 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:33:41 +0000 Subject: [PATCH 66/89] Remove unused `victory_locations` argument There is a single victory location for each region (possible plus some extras), but they each have the same access rules, so `set_region_exit_rules` now only needs to take a single location name argument, simplifying the code. --- worlds/wargroove2/Levels.py | 25 +++++++------------------ worlds/wargroove2/__init__.py | 2 +- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index b3b92ed3a764..2c0352ec2577 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -25,17 +25,10 @@ FINAL_LEVEL_COUNT = 4 -def set_region_exit_rules(region: Region, world: "Wargroove2World", locations: List[str]) -> None: - rules = [world.get_location(location_name).access_rule for location_name in locations] - - def exit_rule(state): - for rule in rules: - if rule(state): - return True - return False - +def set_region_exit_rules(region: Region, world: "Wargroove2World", victory_location_name: str) -> None: + victory_location = world.get_location(victory_location_name) for region_exit in region.exits: - region_exit.access_rule = exit_rule + region_exit.access_rule = victory_location.access_rule LocationRules = dict[str, Callable[[int], CollectionRule] | None] @@ -46,19 +39,15 @@ class Wargroove2Level: file_name: str location_rules: LocationRules region_name: str - victory_locations: List[str] + victory_location: str has_ocean: bool = True - def __init__(self, name: str, file_name: str, location_rules: LocationRules, - victory_locations: List[str] | None = None, has_ocean: bool = True): + def __init__(self, name: str, file_name: str, location_rules: LocationRules, has_ocean: bool = True): self.name = name self.file_name = file_name self.location_rules = location_rules self.has_ocean = has_ocean - if victory_locations: - self.victory_locations = victory_locations - else: - self.victory_locations = [name + ': Victory'] + self.victory_location = name + ': Victory' def define_access_rules(self, world: "Wargroove2World", player: int, additional_rule=None) -> None: for location_name, rule_factory in self.location_rules.items(): @@ -88,7 +77,7 @@ def rule(state, current_rule=rule_factory(player)): for i in range(1, total_locations): set_rule(world.get_location(location_name + f" Extra {i}"), rule) region = world.get_region(self.region_name) - set_region_exit_rules(region, world, self.victory_locations) + set_region_exit_rules(region, world, self.victory_location) def define_region(self, name: str, world: "Wargroove2World", player: int, exits=None) -> Region: self.region_name = name diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 5bd8911713d6..bdf4731364a7 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -129,7 +129,7 @@ def create_items(self) -> None: victory = Wargroove2Item("Wargroove 2 Victory", self.player) for i in range(0, 4): final_level = self.final_levels[i] - self.get_location(final_level.victory_locations[0]).place_locked_item(victory) + self.get_location(final_level.victory_location).place_locked_item(victory) # Placing victory event at final location self.multiworld.completion_condition[self.player] = lambda state: \ state.has("Wargroove 2 Victory", self.player, self.options.final_levels.value) From 2701c178ebb228ebfa62ea13fb91f6755b413cc1 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:39:47 +0000 Subject: [PATCH 67/89] Crash on trying to get the ID for a non-existent location This changes the current code that silently falls back to assuming an ID of `0` for a location that cannot be found. But all locations should exist, so this could hide bugs. --- worlds/wargroove2/Levels.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 2c0352ec2577..659438603b87 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -68,7 +68,7 @@ def rule(state, current_rule=rule_factory(player)): return current_rule(state) and additional_rule(state) set_rule(world.get_location(location_name), rule) - loc_id = location_table.get(location_name, 0) + loc_id = location_table[location_name] total_locations = 1 if loc_id is not None and location_name.endswith("Victory"): total_locations = world.options.victory_locations.value @@ -84,7 +84,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= region = Region(name, player, world.multiworld) if self.location_rules.keys(): for location in self.location_rules.keys(): - loc_id = location_table.get(location, 0) + loc_id = location_table[location] wg2_location = Wargroove2Location(player, location, loc_id, region) region.locations.append(wg2_location) total_locations = 1 @@ -94,7 +94,7 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= total_locations = world.options.objective_locations.value for i in range(1, total_locations): extra_location = location + f" Extra {i}" - loc_id = location_table.get(extra_location, 0) + loc_id = location_table[extra_location] wg2_location = Wargroove2Location(player, extra_location, loc_id, region) region.locations.append(wg2_location) From 8ba37881b34abc36652cc710c93b995689c35ce7 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:44:40 +0000 Subject: [PATCH 68/89] Refactor use of `loc_id` in creation and rule setting of Extra locations --- worlds/wargroove2/Levels.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/worlds/wargroove2/Levels.py b/worlds/wargroove2/Levels.py index 659438603b87..074126a0a86d 100644 --- a/worlds/wargroove2/Levels.py +++ b/worlds/wargroove2/Levels.py @@ -68,11 +68,15 @@ def rule(state, current_rule=rule_factory(player)): return current_rule(state) and additional_rule(state) set_rule(world.get_location(location_name), rule) + + # Add rules to Extra locations. loc_id = location_table[location_name] - total_locations = 1 - if loc_id is not None and location_name.endswith("Victory"): + if loc_id is None: + # Extra locations are not created for event locations. + continue + if location_name.endswith("Victory"): total_locations = world.options.victory_locations.value - elif loc_id is not None: + else: total_locations = world.options.objective_locations.value for i in range(1, total_locations): set_rule(world.get_location(location_name + f" Extra {i}"), rule) @@ -87,10 +91,14 @@ def define_region(self, name: str, world: "Wargroove2World", player: int, exits= loc_id = location_table[location] wg2_location = Wargroove2Location(player, location, loc_id, region) region.locations.append(wg2_location) - total_locations = 1 - if loc_id is not None and location.endswith("Victory"): + + # Create Extra locations. + if loc_id is None: + # Extra locations are not created for event locations. + continue + if location.endswith("Victory"): total_locations = world.options.victory_locations.value - elif loc_id is not None: + else: total_locations = world.options.objective_locations.value for i in range(1, total_locations): extra_location = location + f" Extra {i}" From dc34062a2450acc02c4dea5616717fa358d4523a Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:48:59 +0000 Subject: [PATCH 69/89] Cleanup: Use list comprehension instead of list(generator) Very minor, because the lists being iterated only have 4 values, but would be worse for much larger lists. --- worlds/wargroove2/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index bdf4731364a7..31e00f7bd160 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -81,8 +81,8 @@ def generate_early(self) -> None: random.shuffle(non_starting_levels) self.level_list = low_victory_checks_levels_copy[0:4] + non_starting_levels - final_levels_no_ocean = list(level for level in final_levels if not level.has_ocean) - final_levels_ocean = list(level for level in final_levels if level.has_ocean) + final_levels_no_ocean = [level for level in final_levels if not level.has_ocean] + final_levels_ocean = [level for level in final_levels if level.has_ocean] random.shuffle(final_levels_no_ocean) random.shuffle(final_levels_ocean) non_north_levels = final_levels_ocean + final_levels_no_ocean[1:] From 47018c31ad4868d3e1f487d59252d6c9455dbbc3 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 6 Jan 2025 17:55:06 +0000 Subject: [PATCH 70/89] Prefer self.random to self.multiworld.random If there is another world in the multiworld with some nondeterministic behaviour that calls `self.multiworld.random`, then any other world that uses `self.multiworld.random` would also appear to be nondeterministic. Because other worlds should not use the `.random` belonging to a different world, this should prevent a nondeterministic world from making Wargroove 2 also appear nondeterministic. --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 31e00f7bd160..c43962820e29 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -92,7 +92,7 @@ def generate_early(self) -> None: # Selecting a random starting faction if self.options.commander_choice == "random_starting_faction": factions = [faction for faction in faction_table.keys() if faction != "Starter"] - starting_faction = Wargroove2Item(self.multiworld.random.choice(factions) + ' Commanders', self.player) + starting_faction = Wargroove2Item(self.random.choice(factions) + ' Commanders', self.player) self.multiworld.push_precollected(starting_faction) def create_items(self) -> None: From c9649727258903f2af48bf76b6b6efbe81dec08b Mon Sep 17 00:00:00 2001 From: FlySniper Date: Wed, 8 Jan 2025 18:47:07 -0500 Subject: [PATCH 71/89] Wargroove 2: PR suggestions --- Wargroove2Client.py | 2 +- worlds/wargroove2/Client.py | 757 ++++++++++++++++++++++++++++++++++++ worlds/wargroove2/client.py | 11 +- 3 files changed, 764 insertions(+), 6 deletions(-) create mode 100644 worlds/wargroove2/Client.py diff --git a/Wargroove2Client.py b/Wargroove2Client.py index 6f827df15b0f..57c41155954a 100644 --- a/Wargroove2Client.py +++ b/Wargroove2Client.py @@ -3,7 +3,7 @@ import ModuleUpdate ModuleUpdate.update() -from worlds.wargroove2.client import launch +from worlds.wargroove2.Client import launch import Utils if __name__ == "__main__": diff --git a/worlds/wargroove2/Client.py b/worlds/wargroove2/Client.py new file mode 100644 index 000000000000..364f8f3fe739 --- /dev/null +++ b/worlds/wargroove2/Client.py @@ -0,0 +1,757 @@ +from __future__ import annotations + +import atexit +import os +import sys +import asyncio +import pkgutil +import random +import typing +import Utils +import json +import logging +import ModuleUpdate +from typing import Tuple, List, Iterable, Dict + +from settings import get_settings +from . import Wargroove2World +from .Items import item_table, faction_table, CommanderData, ItemData, item_id_name + +from .Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, \ + low_victory_checks_levels, high_victory_checks_levels, \ + FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, final_levels +from .Locations import location_table, location_id_name +from .RegionFilter import Wargroove2LogicFilter +from NetUtils import ClientStatus +from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \ + CommonContext, server_loop + +ModuleUpdate.update() + +if __name__ == "__main__": + Utils.init_logging("Wargroove2Client", exception_logger="Client") + +wg2_logger = logging.getLogger("WG2") + + +class Wargroove2ClientCommandProcessor(ClientCommandProcessor): + def _cmd_resync(self): + """Manually trigger a resync.""" + self.output(f"Syncing items.") + self.ctx.syncing = True + + def _cmd_commander(self, *commander_name: Iterable[str]): + """Set the current commander to the given commander.""" + if commander_name: + self.ctx.set_commander(' '.join(commander_name[0])) + else: + if self.ctx.can_choose_commander: + commanders = self.ctx.get_commanders() + wg2_logger.info('Unlocked commanders: ' + + ', '.join((commander.name for commander, unlocked in commanders if unlocked))) + wg2_logger.info('Locked commanders: ' + + ', '.join((commander.name for commander, unlocked in commanders if not unlocked))) + else: + wg2_logger.error('Cannot set commanders in this game mode.') + + +class Wargroove2Context(CommonContext): + command_processor = Wargroove2ClientCommandProcessor + game = "Wargroove 2" + items_handling = 0b111 # full remote + current_commander: CommanderData = faction_table["Starter"][0] + can_choose_commander: bool = False + commander_defense_boost_multiplier: int = 0 + income_boost_multiplier: int = 0 + starting_groove_multiplier: int = 0 + victory_locations: int = 1 + objective_locations: int = 1 + has_death_link: bool = False + has_death_link: bool = False + final_levels: int = 1 + level_shuffle_seed: int = 0 + slot_data: dict + stored_finale_key: str = "" + completed_final_regions: list = [] + faction_item_ids = { + 'Starter': 0, + 'Cherrystone': 252034, + 'Felheim': 252035, + 'Floran': 252036, + 'Heavensong': 252037, + 'Requiem': 252038, + 'Pirate': 252039, + 'Faahri': 252040 + } + buff_item_ids = { + 'Income Boost': 252032, + 'Commander Defense Boost': 252033, + 'Groove Boost': 252041, + } + + def __init__(self, server_address, password): + super(Wargroove2Context, self).__init__(server_address, password) + self.send_index = 0 + self.syncing = False + self.awaiting_bridge = False + # self.game_communication_path: files go in this path to pass data between us and the actual game + if "appdata" in os.environ: + options = get_settings() + root_directory = os.path.join(options["wargroove2_options"]["root_directory"]) + self.level_directory = "levels" + appdata_wargroove = os.path.expandvars(os.path.join("%APPDATA%", "Chucklefish", "Wargroove2")) + if not os.path.isfile(os.path.join(root_directory, "win64_bin", "wargroove64.exe")): + print_error_and_close("Wargroove2Client couldn't find wargroove64.exe. " + "Unable to infer required game_communication_path") + self.game_communication_path = os.path.join(root_directory, "AP") + if not os.path.exists(self.game_communication_path): + os.makedirs(self.game_communication_path) + self.remove_communication_files() + atexit.register(self.remove_communication_files) + if not os.path.isdir(appdata_wargroove): + print_error_and_close("Wargroove2Client couldn't find Wargoove 2 in appdata!" + "Boot Wargroove 2 and then close it to attempt to fix this error") + mods_directory = os.path.join(appdata_wargroove, "mods", "ArchipelagoMod") + save_directory = os.path.join(appdata_wargroove, "save") + + # Wargroove 2 doesn't always create the mods directory, so we have to do it + if not os.path.isdir(mods_directory): + os.makedirs(mods_directory) + resources = [os.path.join("data", "mods", "ArchipelagoMod", "maps.dat"), + os.path.join("data", "mods", "ArchipelagoMod", "mod.dat"), + os.path.join("data", "mods", "ArchipelagoMod", "modAssets.dat"), + os.path.join("data", "save", "campaign-45747c660b6a2f09601327a18d662a7d.cmp"), + os.path.join("data", "save", "campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak")] + file_paths = [os.path.join(mods_directory, "maps.dat"), + os.path.join(mods_directory, "mod.dat"), + os.path.join(mods_directory, "modAssets.dat"), + os.path.join(save_directory, "campaign-45747c660b6a2f09601327a18d662a7d.cmp"), + os.path.join(save_directory, "campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak")] + for i in range(0, len(resources)): + file_data = pkgutil.get_data("worlds.wargroove2", resources[i]) + if file_data is None: + print_error_and_close("Wargroove2Client couldn't find Wargoove 2 mod and save files in install!") + with open(file_paths[i], 'wb') as f: + f.write(file_data) + f.close() + else: + print_error_and_close("Wargroove2Client couldn't detect system type. " + "Unable to infer required game_communication_path") + + def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: + with open(os.path.join(self.game_communication_path, "deathLinkReceive"), 'w+') as f: + text = data.get("cause", "") + if text: + f.write(f"DeathLink: {text}") + else: + f.write(f"DeathLink: Received from {data['source']}") + f.close() + super(Wargroove2Context, self).on_deathlink(data) + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super(Wargroove2Context, self).server_auth(password_requested) + await self.get_username() + await self.send_connect() + + async def connection_closed(self): + await super(Wargroove2Context, self).connection_closed() + self.remove_communication_files() + self.checked_locations.clear() + self.server_locations.clear() + self.finished_game = False + + @property + def endpoints(self): + if self.server: + return [self.server] + else: + return [] + + async def shutdown(self): + await super(Wargroove2Context, self).shutdown() + self.remove_communication_files() + self.checked_locations.clear() + self.server_locations.clear() + self.finished_game = False + + def remove_communication_files(self): + for root, dirs, files in os.walk(self.game_communication_path): + for file in files: + os.remove(root + "/" + file) + + def on_package(self, cmd: str, args: dict): + if cmd in {"Connected"}: + self.slot_data = args["slot_data"] + self.victory_locations = self.slot_data["victory_locations"] + self.objective_locations = self.slot_data["objective_locations"] + self.has_death_link = self.slot_data["death_link"] + self.has_death_link = self.slot_data["death_link"] + self.final_levels = self.slot_data["final_levels"] + self.level_shuffle_seed = self.slot_data["level_shuffle_seed"] + filename = f"AP_settings.json" + with open(os.path.join(self.game_communication_path, filename), 'w') as f: + json.dump(args["slot_data"], f) + self.can_choose_commander = self.slot_data["commander_choice"] != 0 + self.starting_groove_multiplier = self.slot_data["groove_boost"] + self.income_boost_multiplier = self.slot_data["income_boost"] + self.commander_defense_boost_multiplier = self.slot_data["commander_defense_boost"] + f.close() + for ss in self.checked_locations: + filename = f"send{ss}" + with open(os.path.join(self.game_communication_path, filename), 'w') as f: + f.close() + + self.stored_finale_key = f"wargroove_2_{self.slot}_{self.team}" + self.set_notify(self.stored_finale_key) + self.update_commander_data() + self.ui.update_ui() + + random.seed(str(self.seed_name) + str(self.slot)) + # Our indexes start at 0 and we have ?? levels + for i in range(0, 100): + filename = f"seed{i}" + with open(os.path.join(self.game_communication_path, filename), 'w') as f: + f.write(str(random.randint(0, 4294967295))) + f.close() + for i in range(0, LEVEL_COUNT): + filename = f"AP_{i + 1}.map" + level_file_name = self.slot_data[f"Level File #{i}"] + file_data = pkgutil.get_data("worlds.wargroove2", os.path.join(self.level_directory, level_file_name)) + if file_data is None: + print_error_and_close("Wargroove2Client couldn't find Wargoove 2 level files in install!") + else: + with open(os.path.join(self.game_communication_path, filename), 'wb') as f: + f.write(file_data) + f.close() + for i in range(0, FINAL_LEVEL_COUNT): + filename = f"AP_{i + LEVEL_COUNT + 1}.map" + level_file_name = self.slot_data[f"Final Level File #{i}"] + file_data = pkgutil.get_data("worlds.wargroove2", os.path.join(self.level_directory, level_file_name)) + if file_data is None: + print_error_and_close("Wargroove2Client couldn't find Wargoove 2 level files in install!") + else: + with open(os.path.join(self.game_communication_path, filename), 'wb') as f: + f.write(file_data) + f.close() + + if cmd in {"RoomInfo"}: + self.seed_name = args["seed_name"] + + if cmd in {"ReceivedItems"}: + received_ids = [item.item for item in self.items_received] + for network_item in self.items_received: + filename = f"AP_{str(network_item.item)}.item" + path = os.path.join(self.game_communication_path, filename) + + # Newly-obtained items + if not os.path.isfile(path): + open(path, 'w').close() + # Announcing commander unlocks + item_name = self.item_names.lookup_in_slot(network_item.item) + if item_name in faction_table.keys(): + for commander in faction_table[item_name]: + logger.info(f"{commander.name} has been unlocked!") + + with open(path, 'w') as f: + item_count = received_ids.count(network_item.item) + if self.buff_item_ids["Income Boost"] == network_item.item: + f.write(f"{item_count * self.income_boost_multiplier}") + elif self.buff_item_ids["Commander Defense Boost"] == network_item.item: + f.write(f"{item_count * self.commander_defense_boost_multiplier}") + elif self.buff_item_ids["Groove Boost"] == network_item.item: + f.write(f"{item_count * self.starting_groove_multiplier}") + else: + f.write(f"{item_count}") + f.close() + + print_filename = f"AP_{str(network_item.item)}.item.print" + print_path = os.path.join(self.game_communication_path, print_filename) + if not os.path.isfile(print_path): + open(print_path, 'w').close() + with open(print_path, 'w') as f: + f.write("Received " + + self.item_names.lookup_in_slot(network_item.item) + + " from " + + self.player_names[network_item.player]) + f.close() + self.update_commander_data() + self.ui.update_ui() + + if cmd in {"RoomUpdate"}: + if "checked_locations" in args: + for ss in self.checked_locations: + filename = f"send{ss}" + with open(os.path.join(self.game_communication_path, filename), 'w') as f: + f.close() + self.ui.update_ui() + + if cmd in {"Retrieved"}: + self.ui.update_levels() + + def run_gui(self): + """Import kivy UI system and start running it as self.ui_task.""" + from kvui import GameManager + from kivy.uix.tabbedpanel import TabbedPanelItem + from kivy.lang import Builder + from kivy.uix.togglebutton import ToggleButton + from kivy.uix.boxlayout import BoxLayout + from kivy.uix.gridlayout import GridLayout + from kivy.uix.label import Label + import pkgutil + + class TrackerLayout(BoxLayout): + pass + + class LevelsLayout(BoxLayout): + pass + + class CommanderSelect(BoxLayout): + pass + + class CommanderButton(ToggleButton): + pass + + class FactionBox(BoxLayout): + pass + + class CommanderGroup(BoxLayout): + pass + + class ItemTracker(BoxLayout): + pass + + class LevelTracker(BoxLayout): + pass + + class ItemLabel(Label): + pass + + class Wargroove2Manager(GameManager): + logging_pairs = [ + ("Client", "Archipelago"), + ("WG2", "WG2 Console"), + ] + base_title = "Archipelago Wargroove 2 Client" + ctx: Wargroove2Context + unit_tracker: ItemTracker + level_tracker: LevelTracker + level_1_Layout: GridLayout(cols=1) + level_2_Layout: GridLayout(cols=1) + level_3_Layout: GridLayout(cols=1) + level_4_Layout: GridLayout(cols=1) + trigger_tracker: BoxLayout + boost_tracker: BoxLayout + commander_buttons: Dict[str, List[CommanderButton]] + tracker_items = { + "Swordsman": ItemData(None, "Unit"), + "Dog": ItemData(None, "Unit"), + **item_table + } + + def build(self): + container = super().build() + panel = TabbedPanelItem(text="WG2 Tracker") + panel.content = self.build_tracker() + self.tabs.add_widget(panel) + panel = TabbedPanelItem(text="WG2 Levels") + panel.content = self.build_levels() + self.tabs.add_widget(panel) + return container + + def build_levels(self) -> LevelsLayout: + levels_layout = LevelsLayout(orientation="horizontal") + try: + level_tracker = LevelTracker(padding=[0, 20]) + self.level_1_Layout = GridLayout(cols=1) + self.level_2_Layout = GridLayout(cols=1) + self.level_3_Layout = GridLayout(cols=1) + self.level_4_Layout = GridLayout(cols=1) + level_tracker.add_widget(self.level_1_Layout) + level_tracker.add_widget(self.level_2_Layout) + level_tracker.add_widget(self.level_3_Layout) + level_tracker.add_widget(self.level_4_Layout) + levels_layout.add_widget(level_tracker) + self.update_levels() + except Exception as e: + print(e) + return levels_layout + + def update_levels(self): + received_names = [item_id_name[item.item] for item in self.ctx.items_received] + levels = low_victory_checks_levels + high_victory_checks_levels + level_rules = {level.name: level.location_rules for level in levels} + region_filter = Wargroove2LogicFilter(received_names) + self.level_1_Layout.clear_widgets() + self.level_2_Layout.clear_widgets() + self.level_3_Layout.clear_widgets() + self.level_4_Layout.clear_widgets() + level_counter = 1 + unreachable_levels = list(range(5, 28 + 1)) + for region_name in region_names: + fully_beaten_text = "" + level_name_text = "\n" + status_color = (0.6, 0.2, 0.2, 1) + is_fully_beaten = True + is_victory_reached = False + if level_counter <= LEVEL_COUNT and hasattr(self.ctx, 'slot_data'): + level_name = self.ctx.slot_data[region_name] + level_name_text = f"\n{level_name}" + for location_name in level_rules[level_name].keys(): + is_beatable = level_rules[level_name][location_name](region_filter, self.ctx.slot)() + is_fully_beaten = is_fully_beaten and \ + location_table[location_name] in self.ctx.checked_locations + if location_name.endswith(": Victory"): + if location_table[location_name] in self.ctx.checked_locations: + is_victory_reached = True + status_color = (1.0, 1.0, 1.0, 1) + if level_counter <= 4: + next_level = (level_counter - 1) * 4 + 6 - level_counter + unreachable_levels.remove(next_level) + unreachable_levels.remove(next_level + 1) + unreachable_levels.remove(next_level + 2) + elif level_counter <= 16: + unreachable_levels.remove(level_counter + 12) + elif level_counter in unreachable_levels: + status_color = (0.35, 0.2, 0.2, 1) + level_name_text = "" + break + elif is_beatable: + status_color = (0.6, 0.6, 0.2, 1) + elif is_beatable and location_table[location_name] not in self.ctx.checked_locations: + fully_beaten_text = "*" + + if is_fully_beaten and is_victory_reached: + fully_beaten_text = " (100%)" + + label = ItemLabel(text=region_name + fully_beaten_text + level_name_text, color=status_color) + if level_counter == 1: + self.level_1_Layout.add_widget(label) + elif level_counter == 2: + self.level_2_Layout.add_widget(label) + elif level_counter == 3: + self.level_3_Layout.add_widget(label) + elif level_counter == 4: + self.level_4_Layout.add_widget(label) + elif level_counter <= 7: + self.level_1_Layout.add_widget(label) + elif level_counter <= 10: + self.level_2_Layout.add_widget(label) + elif level_counter <= 13: + self.level_3_Layout.add_widget(label) + elif level_counter <= 16: + self.level_4_Layout.add_widget(label) + elif level_counter <= 19: + self.level_1_Layout.add_widget(label) + elif level_counter <= 22: + self.level_2_Layout.add_widget(label) + elif level_counter <= 25: + self.level_3_Layout.add_widget(label) + else: + self.level_4_Layout.add_widget(label) + level_counter += 1 + + final_level_rules = {final_level.name: final_level.location_rules for final_level in final_levels} + final_level_1_name = None + final_level_2_name = None + final_level_3_name = None + final_level_4_name = None + level_name_text = "\n" + if self.ctx.stored_finale_key in self.ctx.stored_data.keys(): + stored_data = self.ctx.stored_data[self.ctx.stored_finale_key] + final_level_1_name = self.ctx.slot_data[FINAL_LEVEL_1] + final_level_2_name = self.ctx.slot_data[FINAL_LEVEL_2] + final_level_3_name = self.ctx.slot_data[FINAL_LEVEL_3] + final_level_4_name = self.ctx.slot_data[FINAL_LEVEL_4] + else: + stored_data = None + if stored_data is not None and final_level_1_name in stored_data: + level_name_text = f"\n{final_level_1_name}" + status_color = (1.0, 1.0, 1.0, 1) + elif final_level_1_name is not None and region_filter.has_all(["Final North", "Final Center"], + self.ctx.slot): + level_name_text = f"\n{final_level_1_name}" + is_beatable = final_level_rules[final_level_1_name] \ + [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() + if is_beatable: + status_color = (0.6, 0.6, 0.2, 1) + else: + status_color = (0.6, 0.2, 0.2, 1) + else: + status_color = (0.35, 0.2, 0.2, 1) + label = ItemLabel(text=FINAL_LEVEL_1 + level_name_text, color=status_color) + self.level_1_Layout.add_widget(label) + level_name_text = "\n" + if stored_data is not None and final_level_2_name in stored_data: + level_name_text = f"\n{final_level_2_name}" + status_color = (1.0, 1.0, 1.0, 1) + elif final_level_2_name is not None and region_filter.has_all(["Final East", "Final Center"], + self.ctx.slot): + level_name_text = f"\n{final_level_2_name}" + is_beatable = final_level_rules[final_level_2_name] \ + [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() + if is_beatable: + status_color = (0.6, 0.6, 0.2, 1) + else: + status_color = (0.6, 0.2, 0.2, 1) + else: + status_color = (0.35, 0.2, 0.2, 1) + label = ItemLabel(text=FINAL_LEVEL_2 + level_name_text, color=status_color) + self.level_2_Layout.add_widget(label) + level_name_text = "\n" + if stored_data is not None and final_level_3_name in stored_data: + level_name_text = f"\n{final_level_3_name}" + status_color = (1.0, 1.0, 1.0, 1) + elif final_level_3_name is not None and region_filter.has_all(["Final South", "Final Center"], + self.ctx.slot): + level_name_text = f"\n{final_level_3_name}" + is_beatable = final_level_rules[final_level_3_name] \ + [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() + if is_beatable: + status_color = (0.6, 0.6, 0.2, 1) + else: + status_color = (0.6, 0.2, 0.2, 1) + else: + status_color = (0.35, 0.2, 0.2, 1) + label = ItemLabel(text=FINAL_LEVEL_3 + level_name_text, color=status_color) + self.level_3_Layout.add_widget(label) + level_name_text = "\n" + if stored_data is not None and final_level_4_name in stored_data: + level_name_text = f"\n{final_level_4_name}" + status_color = (1.0, 1.0, 1.0, 1) + elif final_level_4_name is not None and region_filter.has_all(["Final West", "Final Center"], + self.ctx.slot): + level_name_text = f"\n{final_level_4_name}" + is_beatable = final_level_rules[final_level_4_name] \ + [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() + if is_beatable: + status_color = (0.6, 0.6, 0.2, 1) + else: + status_color = (0.6, 0.2, 0.2, 1) + else: + status_color = (0.35, 0.2, 0.2, 1) + label = ItemLabel(text=FINAL_LEVEL_4 + level_name_text, color=status_color) + self.level_4_Layout.add_widget(label) + + def build_tracker(self) -> TrackerLayout: + tracker = TrackerLayout(orientation="horizontal") + try: + commander_select = CommanderSelect(orientation="vertical") + self.commander_buttons = {} + + for faction, commanders in faction_table.items(): + faction_box = FactionBox(size_hint=(None, None), width=100 * len(commanders), height=70) + commander_group = CommanderGroup() + commander_buttons = [] + for commander in commanders: + commander_button = CommanderButton(text=commander.name, group="commanders") + if faction == "Starter": + commander_button.disabled = False + commander_button.bind(on_press=lambda instance: self.ctx.set_commander(instance.text)) + commander_buttons.append(commander_button) + commander_group.add_widget(commander_button) + self.commander_buttons[faction] = commander_buttons + faction_box.add_widget( + Label(text=faction, size_hint_x=None, pos_hint={'left': 1}, size_hint_y=None, height=10)) + faction_box.add_widget(commander_group) + commander_select.add_widget(faction_box) + item_tracker = ItemTracker(padding=[0, 20]) + self.unit_tracker = BoxLayout(orientation="vertical") + other_tracker = BoxLayout(orientation="vertical") + self.trigger_tracker = BoxLayout(orientation="vertical") + self.boost_tracker = BoxLayout(orientation="vertical") + other_tracker.add_widget(self.trigger_tracker) + other_tracker.add_widget(self.boost_tracker) + item_tracker.add_widget(self.unit_tracker) + item_tracker.add_widget(other_tracker) + tracker.add_widget(commander_select) + tracker.add_widget(item_tracker) + self.update_tracker() + return tracker + except Exception as e: + print(e) + return tracker + + def update_tracker(self): + received_ids = [item.item for item in self.ctx.items_received] + for faction, item_id in self.ctx.faction_item_ids.items(): + for commander_button in self.commander_buttons[faction]: + commander_button.disabled = not (faction == "Starter" or item_id in received_ids) + self.unit_tracker.clear_widgets() + self.trigger_tracker.clear_widgets() + for name, item in self.tracker_items.items(): + if item.type in ("Unit", "Trigger"): + status_color = (1, 1, 1, 1) if item.code is None or item.code in received_ids else ( + 0.6, 0.2, 0.2, 1) + label = ItemLabel(text=name, color=status_color) + if item.type == "Unit": + self.unit_tracker.add_widget(label) + else: + self.trigger_tracker.add_widget(label) + self.boost_tracker.clear_widgets() + extra_income = received_ids.count(252032) * self.ctx.income_boost_multiplier + extra_defense = received_ids.count(252033) * self.ctx.commander_defense_boost_multiplier + extra_groove = received_ids.count(252041) * self.ctx.starting_groove_multiplier + income_boost = ItemLabel(text="Extra Income: " + str(extra_income)) + defense_boost = ItemLabel(text="Comm Defense: " + str(100 + extra_defense)) + groove_boost = ItemLabel(text="Starting Groove: " + str(extra_groove)) + self.boost_tracker.add_widget(income_boost) + self.boost_tracker.add_widget(defense_boost) + self.boost_tracker.add_widget(groove_boost) + + def update_ui(self): + self.update_tracker() + self.update_levels() + + self.ui = Wargroove2Manager(self) + data = pkgutil.get_data(Wargroove2World.__module__, "Wargroove2.kv").decode() + Builder.load_string(data) + self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + + def update_commander_data(self): + if self.can_choose_commander: + data = { + "commander": self.current_commander.internal_name + } + else: + data = { + "commander": "seed" + } + filename = 'commander.json' + with open(os.path.join(self.game_communication_path, filename), 'w') as f: + json.dump(data, f) + if self.ui: + self.ui.update_ui() + + def set_commander(self, commander_name: str) -> bool: + """Sets the current commander to the given one, if possible""" + if not self.can_choose_commander: + wg2_logger.error("Cannot set commanders in this game mode.") + return False + match_name = commander_name.lower() + for commander, unlocked in self.get_commanders(): + if commander.name.lower() == match_name or commander.alt_name and commander.alt_name.lower() == match_name: + if unlocked: + self.current_commander = commander + self.syncing = True + wg2_logger.info(f"Commander set to {commander.name}.") + self.update_commander_data() + return True + else: + wg2_logger.error(f"Commander {commander.name} has not been unlocked.") + return False + else: + wg2_logger.error(f"{commander_name} is not a recognized Wargroove 2 commander.") + return False + + def get_commanders(self) -> List[Tuple[CommanderData, bool]]: + """Gets a list of commanders with their unlocked status""" + commanders = [] + received_ids = [item.item for item in self.items_received] + for faction in faction_table.keys(): + unlocked = faction == 'Starter' or self.faction_item_ids[faction] in received_ids + commanders += [(commander, unlocked) for commander in faction_table[faction]] + return commanders + + +async def game_watcher(ctx: Wargroove2Context): + while not ctx.exit_event.is_set(): + if ctx.syncing: + sync_msg = [{'cmd': 'Sync'}] + if ctx.locations_checked: + sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) + await ctx.send_msgs(sync_msg) + ctx.syncing = False + sending: list = [] + victory = False + await ctx.update_death_link(ctx.has_death_link) + for root, dirs, files in os.walk(ctx.game_communication_path): + for file in files: + if file.find("send") > -1: + st = int(file.split("send", -1)[1]) + loc_name = location_id_name[st] + extras = 1 + if loc_name is not None and loc_name.endswith("Victory"): + extras = ctx.victory_locations + elif loc_name is not None and \ + st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: + extras = ctx.objective_locations + for i in range(1, extras): + sending = sending + [location_table[loc_name + f" Extra {i}"]] + sending = sending + [st] + + os.remove(os.path.join(ctx.game_communication_path, file)) + if file == "deathLinkSend" and ctx.has_death_link: + with open(os.path.join(ctx.game_communication_path, file), 'r') as f: + failed_mission = f.read() + if ctx.slot is not None: + await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") + f.close() + os.remove(os.path.join(ctx.game_communication_path, file)) + if file == "victory": + with open(os.path.join(ctx.game_communication_path, file), 'r') as f: + victory_level = f.read() + final_level_list = [] + if ctx.stored_finale_key in ctx.stored_data.keys(): + final_level_list = ctx.stored_data[ctx.stored_finale_key] + if final_level_list is None: + final_level_list = [] + + if victory_level not in final_level_list: + final_level_list.append(victory_level) + ctx.stored_data[ctx.stored_finale_key] = final_level_list + + message = [{"cmd": 'Set', "key": ctx.stored_finale_key, + "default": final_level_list, + "want_reply": True, + "operations": [{"operation": "replace", "value": final_level_list}]}] + await ctx.send_msgs(message) + final_levels_won = len(ctx.stored_data[ctx.stored_finale_key]) + completed_levels = ", ".join(final_level_list) + logger.info(f"({final_levels_won}/{ctx.final_levels}) final levels conquered! Completed: " + f"{completed_levels}") + if final_levels_won >= ctx.final_levels: + victory = True + f.close() + os.remove(os.path.join(ctx.game_communication_path, file)) + ctx.ui.update_levels() + ctx.locations_checked = sending + message = [{"cmd": 'LocationChecks', "locations": sending}] + await ctx.send_msgs(message) + if not ctx.finished_game and victory: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + await asyncio.sleep(0.1) + + +def print_error_and_close(msg): + logger.error("Error: " + msg) + Utils.messagebox("Error", msg, error=True) + sys.exit(1) + + +def launch(): + async def main(args): + ctx = Wargroove2Context(args.connect, args.password) + ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop") + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + progression_watcher = asyncio.create_task( + game_watcher(ctx), name="Wargroove2ProgressionWatcher") + + await ctx.exit_event.wait() + ctx.server_address = None + + await progression_watcher + + await ctx.shutdown() + + import colorama + + parser = get_base_parser(description="Wargroove 2 Client, for text interfacing.") + + args, rest = parser.parse_known_args() + colorama.init() + asyncio.run(main(args)) + colorama.deinit() diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 364f8f3fe739..cdd7718289b4 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -398,7 +398,8 @@ def update_levels(self): level_name = self.ctx.slot_data[region_name] level_name_text = f"\n{level_name}" for location_name in level_rules[level_name].keys(): - is_beatable = level_rules[level_name][location_name](region_filter, self.ctx.slot)() + rule_factory = level_rules[level_name][location_name] + is_beatable = rule_factory is None or rule_factory(self.ctx.slot)(region_filter) is_fully_beaten = is_fully_beaten and \ location_table[location_name] in self.ctx.checked_locations if location_name.endswith(": Victory"): @@ -472,7 +473,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_1_name}" is_beatable = final_level_rules[final_level_1_name] \ - [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_1_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -489,7 +490,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_2_name}" is_beatable = final_level_rules[final_level_2_name] \ - [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_2_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -506,7 +507,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_3_name}" is_beatable = final_level_rules[final_level_3_name] \ - [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_3_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -523,7 +524,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_4_name}" is_beatable = final_level_rules[final_level_4_name] \ - [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_4_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: From 2cbfbbaaf28d1d8240d5d631f597048b786c1788 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Wed, 8 Jan 2025 18:58:00 -0500 Subject: [PATCH 72/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index cdd7718289b4..53a8ad23a58c 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -265,7 +265,7 @@ def on_package(self, cmd: str, args: dict): f.write(f"{item_count}") f.close() - print_filename = f"AP_{str(network_item.item)}.item.print" + print_filename = f"AP_{network_item.item}.item.print" print_path = os.path.join(self.game_communication_path, print_filename) if not os.path.isfile(print_path): open(print_path, 'w').close() From b54b04c903827bac6a8a5218f2767b87832defd0 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Wed, 8 Jan 2025 18:59:06 -0500 Subject: [PATCH 73/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 53a8ad23a58c..c30e2bb94b99 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -666,7 +666,7 @@ async def game_watcher(ctx: Wargroove2Context): sending: list = [] victory = False await ctx.update_death_link(ctx.has_death_link) - for root, dirs, files in os.walk(ctx.game_communication_path): + for _root, _dirs, files in os.walk(ctx.game_communication_path): for file in files: if file.find("send") > -1: st = int(file.split("send", -1)[1]) From 2c6f180cd04dc3c97b1ef0c9adb9c6521d035b99 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Wed, 8 Jan 2025 18:59:44 -0500 Subject: [PATCH 74/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index c30e2bb94b99..f7f5a1080ee4 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -675,7 +675,7 @@ async def game_watcher(ctx: Wargroove2Context): if loc_name is not None and loc_name.endswith("Victory"): extras = ctx.victory_locations elif loc_name is not None and \ - st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: + st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore extras = ctx.objective_locations for i in range(1, extras): sending = sending + [location_table[loc_name + f" Extra {i}"]] From 49a1b598dc0f96c4cfa791a0f46d2c2ee8672ddc Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:19:48 -0500 Subject: [PATCH 75/89] Update worlds/wargroove2/Rules.py Co-authored-by: Mysteryem --- worlds/wargroove2/Rules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/Rules.py b/worlds/wargroove2/Rules.py index 737e12b31417..49752bb569ab 100644 --- a/worlds/wargroove2/Rules.py +++ b/worlds/wargroove2/Rules.py @@ -1,5 +1,4 @@ from typing import TYPE_CHECKING -from worlds.AutoWorld import LogicMixin from .Levels import first_level if TYPE_CHECKING: from . import Wargroove2World From 1d94e5dd6f590f49732967f36c64b46b6ac97063 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:20:17 -0500 Subject: [PATCH 76/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index f7f5a1080ee4..82c015e648ed 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -678,8 +678,8 @@ async def game_watcher(ctx: Wargroove2Context): st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore extras = ctx.objective_locations for i in range(1, extras): - sending = sending + [location_table[loc_name + f" Extra {i}"]] - sending = sending + [st] + sending.append(location_table[loc_name + f" Extra {i}"]) + sending.append(st) os.remove(os.path.join(ctx.game_communication_path, file)) if file == "deathLinkSend" and ctx.has_death_link: From 2e078b9efa536a181c000c7301f89b67e0840f73 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:21:29 -0500 Subject: [PATCH 77/89] Update worlds/wargroove2/__init__.py Co-authored-by: Mysteryem --- worlds/wargroove2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index c43962820e29..9bd03c48877a 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -108,7 +108,7 @@ def create_items(self) -> None: item = Wargroove2Item(name, self.player) pool.append(item) - for i in range(0, 5): + for _ in range(5): pool.append(Wargroove2Item("Commander Defense Boost", self.player)) pool.append(Wargroove2Item("Income Boost", self.player)) From abcc648902fc9433b48585e1948b9f943bb79ed1 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:22:15 -0500 Subject: [PATCH 78/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 82c015e648ed..7710f8e6f57e 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -246,7 +246,6 @@ def on_package(self, cmd: str, args: dict): # Newly-obtained items if not os.path.isfile(path): - open(path, 'w').close() # Announcing commander unlocks item_name = self.item_names.lookup_in_slot(network_item.item) if item_name in faction_table.keys(): From e0a5d63941b336db5a2d358aaa8f7e295baf1c63 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:24:45 -0500 Subject: [PATCH 79/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 7710f8e6f57e..bbc061428042 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -67,7 +67,6 @@ class Wargroove2Context(CommonContext): victory_locations: int = 1 objective_locations: int = 1 has_death_link: bool = False - has_death_link: bool = False final_levels: int = 1 level_shuffle_seed: int = 0 slot_data: dict From fa23d08739f3964a646b5a9921515d790a9355e3 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:25:35 -0500 Subject: [PATCH 80/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index bbc061428042..8d79a6bcb722 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -376,7 +376,7 @@ def build_levels(self) -> LevelsLayout: return levels_layout def update_levels(self): - received_names = [item_id_name[item.item] for item in self.ctx.items_received] + received_names = {item_id_name[item.item] for item in self.ctx.items_received} levels = low_victory_checks_levels + high_victory_checks_levels level_rules = {level.name: level.location_rules for level in levels} region_filter = Wargroove2LogicFilter(received_names) From 1e62367ef1fbebaa81e6a4b4109e0410a1c47749 Mon Sep 17 00:00:00 2001 From: Fly Hyping Date: Thu, 9 Jan 2025 17:26:21 -0500 Subject: [PATCH 81/89] Update worlds/wargroove2/client.py Co-authored-by: Mysteryem --- worlds/wargroove2/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 8d79a6bcb722..924afe2f49b5 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -266,7 +266,6 @@ def on_package(self, cmd: str, args: dict): print_filename = f"AP_{network_item.item}.item.print" print_path = os.path.join(self.game_communication_path, print_filename) if not os.path.isfile(print_path): - open(print_path, 'w').close() with open(print_path, 'w') as f: f.write("Received " + self.item_names.lookup_in_slot(network_item.item) + From 8805921513bc5860f206afade65fe6c99fbf2d6d Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 9 Jan 2025 17:51:00 -0500 Subject: [PATCH 82/89] Wargroove 2: PR fixes --- worlds/wargroove2/Options.py | 3 ++- worlds/wargroove2/RegionFilter.py | 10 +++++----- worlds/wargroove2/client.py | 27 ++++++++------------------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index 22613714aeba..297d8c531cc3 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -44,10 +44,11 @@ class GrooveBoost(Range): class LevelShuffleSeed(Range): - """What seed to use for level shuffling. 0 uses the multiworld seed.""" + """What seed to use for level shuffling. 0 uses the world seed.""" display_name = "Level Shuffle Seed" range_start = 0 range_end = 0xFFFFFFFF + random = 0 default = 0 diff --git a/worlds/wargroove2/RegionFilter.py b/worlds/wargroove2/RegionFilter.py index bc90fb9e6db9..28581eec1555 100644 --- a/worlds/wargroove2/RegionFilter.py +++ b/worlds/wargroove2/RegionFilter.py @@ -1,22 +1,22 @@ -from typing import List +from typing import Set class Wargroove2LogicFilter: - items: List[str] + items: set[str] - def __init__(self, items: List[str]): + def __init__(self, items: Set[str]): self.items = items def has(self, item: str, player: int) -> bool: return item in self.items - def has_all(self, items: List[str], player: int) -> bool: + def has_all(self, items: Set[str], player: int) -> bool: for item in items: if item not in self.items: return False return True - def has_any(self, items: List[str], player: int) -> bool: + def has_any(self, items: Set[str], player: int) -> bool: for item in items: if item in self.items: return True diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 924afe2f49b5..8bce4bf6c7c9 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -11,7 +11,7 @@ import json import logging import ModuleUpdate -from typing import Tuple, List, Iterable, Dict +from typing import Tuple, List, Iterable, Dict, Any from settings import get_settings from . import Wargroove2World @@ -69,9 +69,8 @@ class Wargroove2Context(CommonContext): has_death_link: bool = False final_levels: int = 1 level_shuffle_seed: int = 0 - slot_data: dict + slot_data: dict[str, Any] stored_finale_key: str = "" - completed_final_regions: list = [] faction_item_ids = { 'Starter': 0, 'Cherrystone': 252034, @@ -132,7 +131,6 @@ def __init__(self, server_address, password): print_error_and_close("Wargroove2Client couldn't find Wargoove 2 mod and save files in install!") with open(file_paths[i], 'wb') as f: f.write(file_data) - f.close() else: print_error_and_close("Wargroove2Client couldn't detect system type. " "Unable to infer required game_communication_path") @@ -144,7 +142,6 @@ def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: f.write(f"DeathLink: {text}") else: f.write(f"DeathLink: Received from {data['source']}") - f.close() super(Wargroove2Context, self).on_deathlink(data) async def server_auth(self, password_requested: bool = False): @@ -195,11 +192,10 @@ def on_package(self, cmd: str, args: dict): self.starting_groove_multiplier = self.slot_data["groove_boost"] self.income_boost_multiplier = self.slot_data["income_boost"] self.commander_defense_boost_multiplier = self.slot_data["commander_defense_boost"] - f.close() for ss in self.checked_locations: filename = f"send{ss}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.close() + pass self.stored_finale_key = f"wargroove_2_{self.slot}_{self.team}" self.set_notify(self.stored_finale_key) @@ -212,7 +208,6 @@ def on_package(self, cmd: str, args: dict): filename = f"seed{i}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: f.write(str(random.randint(0, 4294967295))) - f.close() for i in range(0, LEVEL_COUNT): filename = f"AP_{i + 1}.map" level_file_name = self.slot_data[f"Level File #{i}"] @@ -222,7 +217,6 @@ def on_package(self, cmd: str, args: dict): else: with open(os.path.join(self.game_communication_path, filename), 'wb') as f: f.write(file_data) - f.close() for i in range(0, FINAL_LEVEL_COUNT): filename = f"AP_{i + LEVEL_COUNT + 1}.map" level_file_name = self.slot_data[f"Final Level File #{i}"] @@ -232,7 +226,6 @@ def on_package(self, cmd: str, args: dict): else: with open(os.path.join(self.game_communication_path, filename), 'wb') as f: f.write(file_data) - f.close() if cmd in {"RoomInfo"}: self.seed_name = args["seed_name"] @@ -261,7 +254,6 @@ def on_package(self, cmd: str, args: dict): f.write(f"{item_count * self.starting_groove_multiplier}") else: f.write(f"{item_count}") - f.close() print_filename = f"AP_{network_item.item}.item.print" print_path = os.path.join(self.game_communication_path, print_filename) @@ -271,7 +263,6 @@ def on_package(self, cmd: str, args: dict): self.item_names.lookup_in_slot(network_item.item) + " from " + self.player_names[network_item.player]) - f.close() self.update_commander_data() self.ui.update_ui() @@ -280,7 +271,7 @@ def on_package(self, cmd: str, args: dict): for ss in self.checked_locations: filename = f"send{ss}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.close() + pass self.ui.update_ui() if cmd in {"Retrieved"}: @@ -660,7 +651,7 @@ async def game_watcher(ctx: Wargroove2Context): sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) await ctx.send_msgs(sync_msg) ctx.syncing = False - sending: list = [] + sending: set = set() victory = False await ctx.update_death_link(ctx.has_death_link) for _root, _dirs, files in os.walk(ctx.game_communication_path): @@ -675,8 +666,8 @@ async def game_watcher(ctx: Wargroove2Context): st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore extras = ctx.objective_locations for i in range(1, extras): - sending.append(location_table[loc_name + f" Extra {i}"]) - sending.append(st) + sending.add(location_table[loc_name + f" Extra {i}"]) + sending.add(st) os.remove(os.path.join(ctx.game_communication_path, file)) if file == "deathLinkSend" and ctx.has_death_link: @@ -684,7 +675,6 @@ async def game_watcher(ctx: Wargroove2Context): failed_mission = f.read() if ctx.slot is not None: await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") - f.close() os.remove(os.path.join(ctx.game_communication_path, file)) if file == "victory": with open(os.path.join(ctx.game_communication_path, file), 'r') as f: @@ -710,11 +700,10 @@ async def game_watcher(ctx: Wargroove2Context): f"{completed_levels}") if final_levels_won >= ctx.final_levels: victory = True - f.close() os.remove(os.path.join(ctx.game_communication_path, file)) ctx.ui.update_levels() ctx.locations_checked = sending - message = [{"cmd": 'LocationChecks', "locations": sending}] + message = [{"cmd": "LocationChecks", "locations": list(sending)}] await ctx.send_msgs(message) if not ctx.finished_game and victory: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) From e5e498941c465a75e23d4dba66c8db37fbe58f81 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 9 Jan 2025 17:51:51 -0500 Subject: [PATCH 83/89] Wargroove 2: PR fixes --- worlds/wargroove2/client.py | 49 +++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 8bce4bf6c7c9..364f8f3fe739 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -11,7 +11,7 @@ import json import logging import ModuleUpdate -from typing import Tuple, List, Iterable, Dict, Any +from typing import Tuple, List, Iterable, Dict from settings import get_settings from . import Wargroove2World @@ -67,10 +67,12 @@ class Wargroove2Context(CommonContext): victory_locations: int = 1 objective_locations: int = 1 has_death_link: bool = False + has_death_link: bool = False final_levels: int = 1 level_shuffle_seed: int = 0 - slot_data: dict[str, Any] + slot_data: dict stored_finale_key: str = "" + completed_final_regions: list = [] faction_item_ids = { 'Starter': 0, 'Cherrystone': 252034, @@ -131,6 +133,7 @@ def __init__(self, server_address, password): print_error_and_close("Wargroove2Client couldn't find Wargoove 2 mod and save files in install!") with open(file_paths[i], 'wb') as f: f.write(file_data) + f.close() else: print_error_and_close("Wargroove2Client couldn't detect system type. " "Unable to infer required game_communication_path") @@ -142,6 +145,7 @@ def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: f.write(f"DeathLink: {text}") else: f.write(f"DeathLink: Received from {data['source']}") + f.close() super(Wargroove2Context, self).on_deathlink(data) async def server_auth(self, password_requested: bool = False): @@ -192,10 +196,11 @@ def on_package(self, cmd: str, args: dict): self.starting_groove_multiplier = self.slot_data["groove_boost"] self.income_boost_multiplier = self.slot_data["income_boost"] self.commander_defense_boost_multiplier = self.slot_data["commander_defense_boost"] + f.close() for ss in self.checked_locations: filename = f"send{ss}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: - pass + f.close() self.stored_finale_key = f"wargroove_2_{self.slot}_{self.team}" self.set_notify(self.stored_finale_key) @@ -208,6 +213,7 @@ def on_package(self, cmd: str, args: dict): filename = f"seed{i}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: f.write(str(random.randint(0, 4294967295))) + f.close() for i in range(0, LEVEL_COUNT): filename = f"AP_{i + 1}.map" level_file_name = self.slot_data[f"Level File #{i}"] @@ -217,6 +223,7 @@ def on_package(self, cmd: str, args: dict): else: with open(os.path.join(self.game_communication_path, filename), 'wb') as f: f.write(file_data) + f.close() for i in range(0, FINAL_LEVEL_COUNT): filename = f"AP_{i + LEVEL_COUNT + 1}.map" level_file_name = self.slot_data[f"Final Level File #{i}"] @@ -226,6 +233,7 @@ def on_package(self, cmd: str, args: dict): else: with open(os.path.join(self.game_communication_path, filename), 'wb') as f: f.write(file_data) + f.close() if cmd in {"RoomInfo"}: self.seed_name = args["seed_name"] @@ -238,6 +246,7 @@ def on_package(self, cmd: str, args: dict): # Newly-obtained items if not os.path.isfile(path): + open(path, 'w').close() # Announcing commander unlocks item_name = self.item_names.lookup_in_slot(network_item.item) if item_name in faction_table.keys(): @@ -254,15 +263,18 @@ def on_package(self, cmd: str, args: dict): f.write(f"{item_count * self.starting_groove_multiplier}") else: f.write(f"{item_count}") + f.close() - print_filename = f"AP_{network_item.item}.item.print" + print_filename = f"AP_{str(network_item.item)}.item.print" print_path = os.path.join(self.game_communication_path, print_filename) if not os.path.isfile(print_path): + open(print_path, 'w').close() with open(print_path, 'w') as f: f.write("Received " + self.item_names.lookup_in_slot(network_item.item) + " from " + self.player_names[network_item.player]) + f.close() self.update_commander_data() self.ui.update_ui() @@ -271,7 +283,7 @@ def on_package(self, cmd: str, args: dict): for ss in self.checked_locations: filename = f"send{ss}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: - pass + f.close() self.ui.update_ui() if cmd in {"Retrieved"}: @@ -366,7 +378,7 @@ def build_levels(self) -> LevelsLayout: return levels_layout def update_levels(self): - received_names = {item_id_name[item.item] for item in self.ctx.items_received} + received_names = [item_id_name[item.item] for item in self.ctx.items_received] levels = low_victory_checks_levels + high_victory_checks_levels level_rules = {level.name: level.location_rules for level in levels} region_filter = Wargroove2LogicFilter(received_names) @@ -386,8 +398,7 @@ def update_levels(self): level_name = self.ctx.slot_data[region_name] level_name_text = f"\n{level_name}" for location_name in level_rules[level_name].keys(): - rule_factory = level_rules[level_name][location_name] - is_beatable = rule_factory is None or rule_factory(self.ctx.slot)(region_filter) + is_beatable = level_rules[level_name][location_name](region_filter, self.ctx.slot)() is_fully_beaten = is_fully_beaten and \ location_table[location_name] in self.ctx.checked_locations if location_name.endswith(": Victory"): @@ -461,7 +472,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_1_name}" is_beatable = final_level_rules[final_level_1_name] \ - [f"{final_level_1_name}: Victory"](self.ctx.slot)(region_filter) + [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -478,7 +489,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_2_name}" is_beatable = final_level_rules[final_level_2_name] \ - [f"{final_level_2_name}: Victory"](self.ctx.slot)(region_filter) + [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -495,7 +506,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_3_name}" is_beatable = final_level_rules[final_level_3_name] \ - [f"{final_level_3_name}: Victory"](self.ctx.slot)(region_filter) + [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -512,7 +523,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_4_name}" is_beatable = final_level_rules[final_level_4_name] \ - [f"{final_level_4_name}: Victory"](self.ctx.slot)(region_filter) + [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -651,10 +662,10 @@ async def game_watcher(ctx: Wargroove2Context): sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) await ctx.send_msgs(sync_msg) ctx.syncing = False - sending: set = set() + sending: list = [] victory = False await ctx.update_death_link(ctx.has_death_link) - for _root, _dirs, files in os.walk(ctx.game_communication_path): + for root, dirs, files in os.walk(ctx.game_communication_path): for file in files: if file.find("send") > -1: st = int(file.split("send", -1)[1]) @@ -663,11 +674,11 @@ async def game_watcher(ctx: Wargroove2Context): if loc_name is not None and loc_name.endswith("Victory"): extras = ctx.victory_locations elif loc_name is not None and \ - st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore + st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: extras = ctx.objective_locations for i in range(1, extras): - sending.add(location_table[loc_name + f" Extra {i}"]) - sending.add(st) + sending = sending + [location_table[loc_name + f" Extra {i}"]] + sending = sending + [st] os.remove(os.path.join(ctx.game_communication_path, file)) if file == "deathLinkSend" and ctx.has_death_link: @@ -675,6 +686,7 @@ async def game_watcher(ctx: Wargroove2Context): failed_mission = f.read() if ctx.slot is not None: await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") + f.close() os.remove(os.path.join(ctx.game_communication_path, file)) if file == "victory": with open(os.path.join(ctx.game_communication_path, file), 'r') as f: @@ -700,10 +712,11 @@ async def game_watcher(ctx: Wargroove2Context): f"{completed_levels}") if final_levels_won >= ctx.final_levels: victory = True + f.close() os.remove(os.path.join(ctx.game_communication_path, file)) ctx.ui.update_levels() ctx.locations_checked = sending - message = [{"cmd": "LocationChecks", "locations": list(sending)}] + message = [{"cmd": 'LocationChecks', "locations": sending}] await ctx.send_msgs(message) if not ctx.finished_game and victory: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) From 806de64a7c87b7f19563a7c3b7d21b6532e09f15 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 9 Jan 2025 17:52:52 -0500 Subject: [PATCH 84/89] Wargroove 2: PR fixes --- worlds/wargroove2/client.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/client.py index 364f8f3fe739..fe2a7b0ba7d2 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/client.py @@ -246,7 +246,6 @@ def on_package(self, cmd: str, args: dict): # Newly-obtained items if not os.path.isfile(path): - open(path, 'w').close() # Announcing commander unlocks item_name = self.item_names.lookup_in_slot(network_item.item) if item_name in faction_table.keys(): @@ -265,7 +264,7 @@ def on_package(self, cmd: str, args: dict): f.write(f"{item_count}") f.close() - print_filename = f"AP_{str(network_item.item)}.item.print" + print_filename = f"AP_{network_item.item}.item.print" print_path = os.path.join(self.game_communication_path, print_filename) if not os.path.isfile(print_path): open(print_path, 'w').close() @@ -665,7 +664,7 @@ async def game_watcher(ctx: Wargroove2Context): sending: list = [] victory = False await ctx.update_death_link(ctx.has_death_link) - for root, dirs, files in os.walk(ctx.game_communication_path): + for _root, _dirs, files in os.walk(ctx.game_communication_path): for file in files: if file.find("send") > -1: st = int(file.split("send", -1)[1]) @@ -674,11 +673,11 @@ async def game_watcher(ctx: Wargroove2Context): if loc_name is not None and loc_name.endswith("Victory"): extras = ctx.victory_locations elif loc_name is not None and \ - st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: + st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore extras = ctx.objective_locations for i in range(1, extras): - sending = sending + [location_table[loc_name + f" Extra {i}"]] - sending = sending + [st] + sending.append(location_table[loc_name + f" Extra {i}"]) + sending.append(st) os.remove(os.path.join(ctx.game_communication_path, file)) if file == "deathLinkSend" and ctx.has_death_link: From d8dd35c4ac7d0cb2aac79aa7dafd978842df1f2f Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 9 Jan 2025 17:59:21 -0500 Subject: [PATCH 85/89] Wargroove 2: PR fixes --- worlds/wargroove2/Client.py | 757 ------------------------------------ 1 file changed, 757 deletions(-) delete mode 100644 worlds/wargroove2/Client.py diff --git a/worlds/wargroove2/Client.py b/worlds/wargroove2/Client.py deleted file mode 100644 index 364f8f3fe739..000000000000 --- a/worlds/wargroove2/Client.py +++ /dev/null @@ -1,757 +0,0 @@ -from __future__ import annotations - -import atexit -import os -import sys -import asyncio -import pkgutil -import random -import typing -import Utils -import json -import logging -import ModuleUpdate -from typing import Tuple, List, Iterable, Dict - -from settings import get_settings -from . import Wargroove2World -from .Items import item_table, faction_table, CommanderData, ItemData, item_id_name - -from .Levels import LEVEL_COUNT, FINAL_LEVEL_COUNT, region_names, \ - low_victory_checks_levels, high_victory_checks_levels, \ - FINAL_LEVEL_1, FINAL_LEVEL_2, FINAL_LEVEL_3, FINAL_LEVEL_4, final_levels -from .Locations import location_table, location_id_name -from .RegionFilter import Wargroove2LogicFilter -from NetUtils import ClientStatus -from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \ - CommonContext, server_loop - -ModuleUpdate.update() - -if __name__ == "__main__": - Utils.init_logging("Wargroove2Client", exception_logger="Client") - -wg2_logger = logging.getLogger("WG2") - - -class Wargroove2ClientCommandProcessor(ClientCommandProcessor): - def _cmd_resync(self): - """Manually trigger a resync.""" - self.output(f"Syncing items.") - self.ctx.syncing = True - - def _cmd_commander(self, *commander_name: Iterable[str]): - """Set the current commander to the given commander.""" - if commander_name: - self.ctx.set_commander(' '.join(commander_name[0])) - else: - if self.ctx.can_choose_commander: - commanders = self.ctx.get_commanders() - wg2_logger.info('Unlocked commanders: ' + - ', '.join((commander.name for commander, unlocked in commanders if unlocked))) - wg2_logger.info('Locked commanders: ' + - ', '.join((commander.name for commander, unlocked in commanders if not unlocked))) - else: - wg2_logger.error('Cannot set commanders in this game mode.') - - -class Wargroove2Context(CommonContext): - command_processor = Wargroove2ClientCommandProcessor - game = "Wargroove 2" - items_handling = 0b111 # full remote - current_commander: CommanderData = faction_table["Starter"][0] - can_choose_commander: bool = False - commander_defense_boost_multiplier: int = 0 - income_boost_multiplier: int = 0 - starting_groove_multiplier: int = 0 - victory_locations: int = 1 - objective_locations: int = 1 - has_death_link: bool = False - has_death_link: bool = False - final_levels: int = 1 - level_shuffle_seed: int = 0 - slot_data: dict - stored_finale_key: str = "" - completed_final_regions: list = [] - faction_item_ids = { - 'Starter': 0, - 'Cherrystone': 252034, - 'Felheim': 252035, - 'Floran': 252036, - 'Heavensong': 252037, - 'Requiem': 252038, - 'Pirate': 252039, - 'Faahri': 252040 - } - buff_item_ids = { - 'Income Boost': 252032, - 'Commander Defense Boost': 252033, - 'Groove Boost': 252041, - } - - def __init__(self, server_address, password): - super(Wargroove2Context, self).__init__(server_address, password) - self.send_index = 0 - self.syncing = False - self.awaiting_bridge = False - # self.game_communication_path: files go in this path to pass data between us and the actual game - if "appdata" in os.environ: - options = get_settings() - root_directory = os.path.join(options["wargroove2_options"]["root_directory"]) - self.level_directory = "levels" - appdata_wargroove = os.path.expandvars(os.path.join("%APPDATA%", "Chucklefish", "Wargroove2")) - if not os.path.isfile(os.path.join(root_directory, "win64_bin", "wargroove64.exe")): - print_error_and_close("Wargroove2Client couldn't find wargroove64.exe. " - "Unable to infer required game_communication_path") - self.game_communication_path = os.path.join(root_directory, "AP") - if not os.path.exists(self.game_communication_path): - os.makedirs(self.game_communication_path) - self.remove_communication_files() - atexit.register(self.remove_communication_files) - if not os.path.isdir(appdata_wargroove): - print_error_and_close("Wargroove2Client couldn't find Wargoove 2 in appdata!" - "Boot Wargroove 2 and then close it to attempt to fix this error") - mods_directory = os.path.join(appdata_wargroove, "mods", "ArchipelagoMod") - save_directory = os.path.join(appdata_wargroove, "save") - - # Wargroove 2 doesn't always create the mods directory, so we have to do it - if not os.path.isdir(mods_directory): - os.makedirs(mods_directory) - resources = [os.path.join("data", "mods", "ArchipelagoMod", "maps.dat"), - os.path.join("data", "mods", "ArchipelagoMod", "mod.dat"), - os.path.join("data", "mods", "ArchipelagoMod", "modAssets.dat"), - os.path.join("data", "save", "campaign-45747c660b6a2f09601327a18d662a7d.cmp"), - os.path.join("data", "save", "campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak")] - file_paths = [os.path.join(mods_directory, "maps.dat"), - os.path.join(mods_directory, "mod.dat"), - os.path.join(mods_directory, "modAssets.dat"), - os.path.join(save_directory, "campaign-45747c660b6a2f09601327a18d662a7d.cmp"), - os.path.join(save_directory, "campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak")] - for i in range(0, len(resources)): - file_data = pkgutil.get_data("worlds.wargroove2", resources[i]) - if file_data is None: - print_error_and_close("Wargroove2Client couldn't find Wargoove 2 mod and save files in install!") - with open(file_paths[i], 'wb') as f: - f.write(file_data) - f.close() - else: - print_error_and_close("Wargroove2Client couldn't detect system type. " - "Unable to infer required game_communication_path") - - def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: - with open(os.path.join(self.game_communication_path, "deathLinkReceive"), 'w+') as f: - text = data.get("cause", "") - if text: - f.write(f"DeathLink: {text}") - else: - f.write(f"DeathLink: Received from {data['source']}") - f.close() - super(Wargroove2Context, self).on_deathlink(data) - - async def server_auth(self, password_requested: bool = False): - if password_requested and not self.password: - await super(Wargroove2Context, self).server_auth(password_requested) - await self.get_username() - await self.send_connect() - - async def connection_closed(self): - await super(Wargroove2Context, self).connection_closed() - self.remove_communication_files() - self.checked_locations.clear() - self.server_locations.clear() - self.finished_game = False - - @property - def endpoints(self): - if self.server: - return [self.server] - else: - return [] - - async def shutdown(self): - await super(Wargroove2Context, self).shutdown() - self.remove_communication_files() - self.checked_locations.clear() - self.server_locations.clear() - self.finished_game = False - - def remove_communication_files(self): - for root, dirs, files in os.walk(self.game_communication_path): - for file in files: - os.remove(root + "/" + file) - - def on_package(self, cmd: str, args: dict): - if cmd in {"Connected"}: - self.slot_data = args["slot_data"] - self.victory_locations = self.slot_data["victory_locations"] - self.objective_locations = self.slot_data["objective_locations"] - self.has_death_link = self.slot_data["death_link"] - self.has_death_link = self.slot_data["death_link"] - self.final_levels = self.slot_data["final_levels"] - self.level_shuffle_seed = self.slot_data["level_shuffle_seed"] - filename = f"AP_settings.json" - with open(os.path.join(self.game_communication_path, filename), 'w') as f: - json.dump(args["slot_data"], f) - self.can_choose_commander = self.slot_data["commander_choice"] != 0 - self.starting_groove_multiplier = self.slot_data["groove_boost"] - self.income_boost_multiplier = self.slot_data["income_boost"] - self.commander_defense_boost_multiplier = self.slot_data["commander_defense_boost"] - f.close() - for ss in self.checked_locations: - filename = f"send{ss}" - with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.close() - - self.stored_finale_key = f"wargroove_2_{self.slot}_{self.team}" - self.set_notify(self.stored_finale_key) - self.update_commander_data() - self.ui.update_ui() - - random.seed(str(self.seed_name) + str(self.slot)) - # Our indexes start at 0 and we have ?? levels - for i in range(0, 100): - filename = f"seed{i}" - with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.write(str(random.randint(0, 4294967295))) - f.close() - for i in range(0, LEVEL_COUNT): - filename = f"AP_{i + 1}.map" - level_file_name = self.slot_data[f"Level File #{i}"] - file_data = pkgutil.get_data("worlds.wargroove2", os.path.join(self.level_directory, level_file_name)) - if file_data is None: - print_error_and_close("Wargroove2Client couldn't find Wargoove 2 level files in install!") - else: - with open(os.path.join(self.game_communication_path, filename), 'wb') as f: - f.write(file_data) - f.close() - for i in range(0, FINAL_LEVEL_COUNT): - filename = f"AP_{i + LEVEL_COUNT + 1}.map" - level_file_name = self.slot_data[f"Final Level File #{i}"] - file_data = pkgutil.get_data("worlds.wargroove2", os.path.join(self.level_directory, level_file_name)) - if file_data is None: - print_error_and_close("Wargroove2Client couldn't find Wargoove 2 level files in install!") - else: - with open(os.path.join(self.game_communication_path, filename), 'wb') as f: - f.write(file_data) - f.close() - - if cmd in {"RoomInfo"}: - self.seed_name = args["seed_name"] - - if cmd in {"ReceivedItems"}: - received_ids = [item.item for item in self.items_received] - for network_item in self.items_received: - filename = f"AP_{str(network_item.item)}.item" - path = os.path.join(self.game_communication_path, filename) - - # Newly-obtained items - if not os.path.isfile(path): - open(path, 'w').close() - # Announcing commander unlocks - item_name = self.item_names.lookup_in_slot(network_item.item) - if item_name in faction_table.keys(): - for commander in faction_table[item_name]: - logger.info(f"{commander.name} has been unlocked!") - - with open(path, 'w') as f: - item_count = received_ids.count(network_item.item) - if self.buff_item_ids["Income Boost"] == network_item.item: - f.write(f"{item_count * self.income_boost_multiplier}") - elif self.buff_item_ids["Commander Defense Boost"] == network_item.item: - f.write(f"{item_count * self.commander_defense_boost_multiplier}") - elif self.buff_item_ids["Groove Boost"] == network_item.item: - f.write(f"{item_count * self.starting_groove_multiplier}") - else: - f.write(f"{item_count}") - f.close() - - print_filename = f"AP_{str(network_item.item)}.item.print" - print_path = os.path.join(self.game_communication_path, print_filename) - if not os.path.isfile(print_path): - open(print_path, 'w').close() - with open(print_path, 'w') as f: - f.write("Received " + - self.item_names.lookup_in_slot(network_item.item) + - " from " + - self.player_names[network_item.player]) - f.close() - self.update_commander_data() - self.ui.update_ui() - - if cmd in {"RoomUpdate"}: - if "checked_locations" in args: - for ss in self.checked_locations: - filename = f"send{ss}" - with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.close() - self.ui.update_ui() - - if cmd in {"Retrieved"}: - self.ui.update_levels() - - def run_gui(self): - """Import kivy UI system and start running it as self.ui_task.""" - from kvui import GameManager - from kivy.uix.tabbedpanel import TabbedPanelItem - from kivy.lang import Builder - from kivy.uix.togglebutton import ToggleButton - from kivy.uix.boxlayout import BoxLayout - from kivy.uix.gridlayout import GridLayout - from kivy.uix.label import Label - import pkgutil - - class TrackerLayout(BoxLayout): - pass - - class LevelsLayout(BoxLayout): - pass - - class CommanderSelect(BoxLayout): - pass - - class CommanderButton(ToggleButton): - pass - - class FactionBox(BoxLayout): - pass - - class CommanderGroup(BoxLayout): - pass - - class ItemTracker(BoxLayout): - pass - - class LevelTracker(BoxLayout): - pass - - class ItemLabel(Label): - pass - - class Wargroove2Manager(GameManager): - logging_pairs = [ - ("Client", "Archipelago"), - ("WG2", "WG2 Console"), - ] - base_title = "Archipelago Wargroove 2 Client" - ctx: Wargroove2Context - unit_tracker: ItemTracker - level_tracker: LevelTracker - level_1_Layout: GridLayout(cols=1) - level_2_Layout: GridLayout(cols=1) - level_3_Layout: GridLayout(cols=1) - level_4_Layout: GridLayout(cols=1) - trigger_tracker: BoxLayout - boost_tracker: BoxLayout - commander_buttons: Dict[str, List[CommanderButton]] - tracker_items = { - "Swordsman": ItemData(None, "Unit"), - "Dog": ItemData(None, "Unit"), - **item_table - } - - def build(self): - container = super().build() - panel = TabbedPanelItem(text="WG2 Tracker") - panel.content = self.build_tracker() - self.tabs.add_widget(panel) - panel = TabbedPanelItem(text="WG2 Levels") - panel.content = self.build_levels() - self.tabs.add_widget(panel) - return container - - def build_levels(self) -> LevelsLayout: - levels_layout = LevelsLayout(orientation="horizontal") - try: - level_tracker = LevelTracker(padding=[0, 20]) - self.level_1_Layout = GridLayout(cols=1) - self.level_2_Layout = GridLayout(cols=1) - self.level_3_Layout = GridLayout(cols=1) - self.level_4_Layout = GridLayout(cols=1) - level_tracker.add_widget(self.level_1_Layout) - level_tracker.add_widget(self.level_2_Layout) - level_tracker.add_widget(self.level_3_Layout) - level_tracker.add_widget(self.level_4_Layout) - levels_layout.add_widget(level_tracker) - self.update_levels() - except Exception as e: - print(e) - return levels_layout - - def update_levels(self): - received_names = [item_id_name[item.item] for item in self.ctx.items_received] - levels = low_victory_checks_levels + high_victory_checks_levels - level_rules = {level.name: level.location_rules for level in levels} - region_filter = Wargroove2LogicFilter(received_names) - self.level_1_Layout.clear_widgets() - self.level_2_Layout.clear_widgets() - self.level_3_Layout.clear_widgets() - self.level_4_Layout.clear_widgets() - level_counter = 1 - unreachable_levels = list(range(5, 28 + 1)) - for region_name in region_names: - fully_beaten_text = "" - level_name_text = "\n" - status_color = (0.6, 0.2, 0.2, 1) - is_fully_beaten = True - is_victory_reached = False - if level_counter <= LEVEL_COUNT and hasattr(self.ctx, 'slot_data'): - level_name = self.ctx.slot_data[region_name] - level_name_text = f"\n{level_name}" - for location_name in level_rules[level_name].keys(): - is_beatable = level_rules[level_name][location_name](region_filter, self.ctx.slot)() - is_fully_beaten = is_fully_beaten and \ - location_table[location_name] in self.ctx.checked_locations - if location_name.endswith(": Victory"): - if location_table[location_name] in self.ctx.checked_locations: - is_victory_reached = True - status_color = (1.0, 1.0, 1.0, 1) - if level_counter <= 4: - next_level = (level_counter - 1) * 4 + 6 - level_counter - unreachable_levels.remove(next_level) - unreachable_levels.remove(next_level + 1) - unreachable_levels.remove(next_level + 2) - elif level_counter <= 16: - unreachable_levels.remove(level_counter + 12) - elif level_counter in unreachable_levels: - status_color = (0.35, 0.2, 0.2, 1) - level_name_text = "" - break - elif is_beatable: - status_color = (0.6, 0.6, 0.2, 1) - elif is_beatable and location_table[location_name] not in self.ctx.checked_locations: - fully_beaten_text = "*" - - if is_fully_beaten and is_victory_reached: - fully_beaten_text = " (100%)" - - label = ItemLabel(text=region_name + fully_beaten_text + level_name_text, color=status_color) - if level_counter == 1: - self.level_1_Layout.add_widget(label) - elif level_counter == 2: - self.level_2_Layout.add_widget(label) - elif level_counter == 3: - self.level_3_Layout.add_widget(label) - elif level_counter == 4: - self.level_4_Layout.add_widget(label) - elif level_counter <= 7: - self.level_1_Layout.add_widget(label) - elif level_counter <= 10: - self.level_2_Layout.add_widget(label) - elif level_counter <= 13: - self.level_3_Layout.add_widget(label) - elif level_counter <= 16: - self.level_4_Layout.add_widget(label) - elif level_counter <= 19: - self.level_1_Layout.add_widget(label) - elif level_counter <= 22: - self.level_2_Layout.add_widget(label) - elif level_counter <= 25: - self.level_3_Layout.add_widget(label) - else: - self.level_4_Layout.add_widget(label) - level_counter += 1 - - final_level_rules = {final_level.name: final_level.location_rules for final_level in final_levels} - final_level_1_name = None - final_level_2_name = None - final_level_3_name = None - final_level_4_name = None - level_name_text = "\n" - if self.ctx.stored_finale_key in self.ctx.stored_data.keys(): - stored_data = self.ctx.stored_data[self.ctx.stored_finale_key] - final_level_1_name = self.ctx.slot_data[FINAL_LEVEL_1] - final_level_2_name = self.ctx.slot_data[FINAL_LEVEL_2] - final_level_3_name = self.ctx.slot_data[FINAL_LEVEL_3] - final_level_4_name = self.ctx.slot_data[FINAL_LEVEL_4] - else: - stored_data = None - if stored_data is not None and final_level_1_name in stored_data: - level_name_text = f"\n{final_level_1_name}" - status_color = (1.0, 1.0, 1.0, 1) - elif final_level_1_name is not None and region_filter.has_all(["Final North", "Final Center"], - self.ctx.slot): - level_name_text = f"\n{final_level_1_name}" - is_beatable = final_level_rules[final_level_1_name] \ - [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() - if is_beatable: - status_color = (0.6, 0.6, 0.2, 1) - else: - status_color = (0.6, 0.2, 0.2, 1) - else: - status_color = (0.35, 0.2, 0.2, 1) - label = ItemLabel(text=FINAL_LEVEL_1 + level_name_text, color=status_color) - self.level_1_Layout.add_widget(label) - level_name_text = "\n" - if stored_data is not None and final_level_2_name in stored_data: - level_name_text = f"\n{final_level_2_name}" - status_color = (1.0, 1.0, 1.0, 1) - elif final_level_2_name is not None and region_filter.has_all(["Final East", "Final Center"], - self.ctx.slot): - level_name_text = f"\n{final_level_2_name}" - is_beatable = final_level_rules[final_level_2_name] \ - [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() - if is_beatable: - status_color = (0.6, 0.6, 0.2, 1) - else: - status_color = (0.6, 0.2, 0.2, 1) - else: - status_color = (0.35, 0.2, 0.2, 1) - label = ItemLabel(text=FINAL_LEVEL_2 + level_name_text, color=status_color) - self.level_2_Layout.add_widget(label) - level_name_text = "\n" - if stored_data is not None and final_level_3_name in stored_data: - level_name_text = f"\n{final_level_3_name}" - status_color = (1.0, 1.0, 1.0, 1) - elif final_level_3_name is not None and region_filter.has_all(["Final South", "Final Center"], - self.ctx.slot): - level_name_text = f"\n{final_level_3_name}" - is_beatable = final_level_rules[final_level_3_name] \ - [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() - if is_beatable: - status_color = (0.6, 0.6, 0.2, 1) - else: - status_color = (0.6, 0.2, 0.2, 1) - else: - status_color = (0.35, 0.2, 0.2, 1) - label = ItemLabel(text=FINAL_LEVEL_3 + level_name_text, color=status_color) - self.level_3_Layout.add_widget(label) - level_name_text = "\n" - if stored_data is not None and final_level_4_name in stored_data: - level_name_text = f"\n{final_level_4_name}" - status_color = (1.0, 1.0, 1.0, 1) - elif final_level_4_name is not None and region_filter.has_all(["Final West", "Final Center"], - self.ctx.slot): - level_name_text = f"\n{final_level_4_name}" - is_beatable = final_level_rules[final_level_4_name] \ - [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() - if is_beatable: - status_color = (0.6, 0.6, 0.2, 1) - else: - status_color = (0.6, 0.2, 0.2, 1) - else: - status_color = (0.35, 0.2, 0.2, 1) - label = ItemLabel(text=FINAL_LEVEL_4 + level_name_text, color=status_color) - self.level_4_Layout.add_widget(label) - - def build_tracker(self) -> TrackerLayout: - tracker = TrackerLayout(orientation="horizontal") - try: - commander_select = CommanderSelect(orientation="vertical") - self.commander_buttons = {} - - for faction, commanders in faction_table.items(): - faction_box = FactionBox(size_hint=(None, None), width=100 * len(commanders), height=70) - commander_group = CommanderGroup() - commander_buttons = [] - for commander in commanders: - commander_button = CommanderButton(text=commander.name, group="commanders") - if faction == "Starter": - commander_button.disabled = False - commander_button.bind(on_press=lambda instance: self.ctx.set_commander(instance.text)) - commander_buttons.append(commander_button) - commander_group.add_widget(commander_button) - self.commander_buttons[faction] = commander_buttons - faction_box.add_widget( - Label(text=faction, size_hint_x=None, pos_hint={'left': 1}, size_hint_y=None, height=10)) - faction_box.add_widget(commander_group) - commander_select.add_widget(faction_box) - item_tracker = ItemTracker(padding=[0, 20]) - self.unit_tracker = BoxLayout(orientation="vertical") - other_tracker = BoxLayout(orientation="vertical") - self.trigger_tracker = BoxLayout(orientation="vertical") - self.boost_tracker = BoxLayout(orientation="vertical") - other_tracker.add_widget(self.trigger_tracker) - other_tracker.add_widget(self.boost_tracker) - item_tracker.add_widget(self.unit_tracker) - item_tracker.add_widget(other_tracker) - tracker.add_widget(commander_select) - tracker.add_widget(item_tracker) - self.update_tracker() - return tracker - except Exception as e: - print(e) - return tracker - - def update_tracker(self): - received_ids = [item.item for item in self.ctx.items_received] - for faction, item_id in self.ctx.faction_item_ids.items(): - for commander_button in self.commander_buttons[faction]: - commander_button.disabled = not (faction == "Starter" or item_id in received_ids) - self.unit_tracker.clear_widgets() - self.trigger_tracker.clear_widgets() - for name, item in self.tracker_items.items(): - if item.type in ("Unit", "Trigger"): - status_color = (1, 1, 1, 1) if item.code is None or item.code in received_ids else ( - 0.6, 0.2, 0.2, 1) - label = ItemLabel(text=name, color=status_color) - if item.type == "Unit": - self.unit_tracker.add_widget(label) - else: - self.trigger_tracker.add_widget(label) - self.boost_tracker.clear_widgets() - extra_income = received_ids.count(252032) * self.ctx.income_boost_multiplier - extra_defense = received_ids.count(252033) * self.ctx.commander_defense_boost_multiplier - extra_groove = received_ids.count(252041) * self.ctx.starting_groove_multiplier - income_boost = ItemLabel(text="Extra Income: " + str(extra_income)) - defense_boost = ItemLabel(text="Comm Defense: " + str(100 + extra_defense)) - groove_boost = ItemLabel(text="Starting Groove: " + str(extra_groove)) - self.boost_tracker.add_widget(income_boost) - self.boost_tracker.add_widget(defense_boost) - self.boost_tracker.add_widget(groove_boost) - - def update_ui(self): - self.update_tracker() - self.update_levels() - - self.ui = Wargroove2Manager(self) - data = pkgutil.get_data(Wargroove2World.__module__, "Wargroove2.kv").decode() - Builder.load_string(data) - self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") - - def update_commander_data(self): - if self.can_choose_commander: - data = { - "commander": self.current_commander.internal_name - } - else: - data = { - "commander": "seed" - } - filename = 'commander.json' - with open(os.path.join(self.game_communication_path, filename), 'w') as f: - json.dump(data, f) - if self.ui: - self.ui.update_ui() - - def set_commander(self, commander_name: str) -> bool: - """Sets the current commander to the given one, if possible""" - if not self.can_choose_commander: - wg2_logger.error("Cannot set commanders in this game mode.") - return False - match_name = commander_name.lower() - for commander, unlocked in self.get_commanders(): - if commander.name.lower() == match_name or commander.alt_name and commander.alt_name.lower() == match_name: - if unlocked: - self.current_commander = commander - self.syncing = True - wg2_logger.info(f"Commander set to {commander.name}.") - self.update_commander_data() - return True - else: - wg2_logger.error(f"Commander {commander.name} has not been unlocked.") - return False - else: - wg2_logger.error(f"{commander_name} is not a recognized Wargroove 2 commander.") - return False - - def get_commanders(self) -> List[Tuple[CommanderData, bool]]: - """Gets a list of commanders with their unlocked status""" - commanders = [] - received_ids = [item.item for item in self.items_received] - for faction in faction_table.keys(): - unlocked = faction == 'Starter' or self.faction_item_ids[faction] in received_ids - commanders += [(commander, unlocked) for commander in faction_table[faction]] - return commanders - - -async def game_watcher(ctx: Wargroove2Context): - while not ctx.exit_event.is_set(): - if ctx.syncing: - sync_msg = [{'cmd': 'Sync'}] - if ctx.locations_checked: - sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) - await ctx.send_msgs(sync_msg) - ctx.syncing = False - sending: list = [] - victory = False - await ctx.update_death_link(ctx.has_death_link) - for root, dirs, files in os.walk(ctx.game_communication_path): - for file in files: - if file.find("send") > -1: - st = int(file.split("send", -1)[1]) - loc_name = location_id_name[st] - extras = 1 - if loc_name is not None and loc_name.endswith("Victory"): - extras = ctx.victory_locations - elif loc_name is not None and \ - st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: - extras = ctx.objective_locations - for i in range(1, extras): - sending = sending + [location_table[loc_name + f" Extra {i}"]] - sending = sending + [st] - - os.remove(os.path.join(ctx.game_communication_path, file)) - if file == "deathLinkSend" and ctx.has_death_link: - with open(os.path.join(ctx.game_communication_path, file), 'r') as f: - failed_mission = f.read() - if ctx.slot is not None: - await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") - f.close() - os.remove(os.path.join(ctx.game_communication_path, file)) - if file == "victory": - with open(os.path.join(ctx.game_communication_path, file), 'r') as f: - victory_level = f.read() - final_level_list = [] - if ctx.stored_finale_key in ctx.stored_data.keys(): - final_level_list = ctx.stored_data[ctx.stored_finale_key] - if final_level_list is None: - final_level_list = [] - - if victory_level not in final_level_list: - final_level_list.append(victory_level) - ctx.stored_data[ctx.stored_finale_key] = final_level_list - - message = [{"cmd": 'Set', "key": ctx.stored_finale_key, - "default": final_level_list, - "want_reply": True, - "operations": [{"operation": "replace", "value": final_level_list}]}] - await ctx.send_msgs(message) - final_levels_won = len(ctx.stored_data[ctx.stored_finale_key]) - completed_levels = ", ".join(final_level_list) - logger.info(f"({final_levels_won}/{ctx.final_levels}) final levels conquered! Completed: " - f"{completed_levels}") - if final_levels_won >= ctx.final_levels: - victory = True - f.close() - os.remove(os.path.join(ctx.game_communication_path, file)) - ctx.ui.update_levels() - ctx.locations_checked = sending - message = [{"cmd": 'LocationChecks', "locations": sending}] - await ctx.send_msgs(message) - if not ctx.finished_game and victory: - await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) - ctx.finished_game = True - await asyncio.sleep(0.1) - - -def print_error_and_close(msg): - logger.error("Error: " + msg) - Utils.messagebox("Error", msg, error=True) - sys.exit(1) - - -def launch(): - async def main(args): - ctx = Wargroove2Context(args.connect, args.password) - ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop") - if gui_enabled: - ctx.run_gui() - ctx.run_cli() - progression_watcher = asyncio.create_task( - game_watcher(ctx), name="Wargroove2ProgressionWatcher") - - await ctx.exit_event.wait() - ctx.server_address = None - - await progression_watcher - - await ctx.shutdown() - - import colorama - - parser = get_base_parser(description="Wargroove 2 Client, for text interfacing.") - - args, rest = parser.parse_known_args() - colorama.init() - asyncio.run(main(args)) - colorama.deinit() From 00a9f2677e6fad898826cd00916b4ad127a5d6be Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 9 Jan 2025 18:28:37 -0500 Subject: [PATCH 86/89] Wargroove 2: PR Fixes --- worlds/wargroove2/{client.py => Client.py} | 38 ++++++++-------------- worlds/wargroove2/Presets.py | 2 +- 2 files changed, 15 insertions(+), 25 deletions(-) rename worlds/wargroove2/{client.py => Client.py} (96%) diff --git a/worlds/wargroove2/client.py b/worlds/wargroove2/Client.py similarity index 96% rename from worlds/wargroove2/client.py rename to worlds/wargroove2/Client.py index fe2a7b0ba7d2..02cfdb4e2812 100644 --- a/worlds/wargroove2/client.py +++ b/worlds/wargroove2/Client.py @@ -11,7 +11,7 @@ import json import logging import ModuleUpdate -from typing import Tuple, List, Iterable, Dict +from typing import Tuple, List, Iterable, Dict, Any from settings import get_settings from . import Wargroove2World @@ -70,9 +70,8 @@ class Wargroove2Context(CommonContext): has_death_link: bool = False final_levels: int = 1 level_shuffle_seed: int = 0 - slot_data: dict + slot_data: dict[str, Any] stored_finale_key: str = "" - completed_final_regions: list = [] faction_item_ids = { 'Starter': 0, 'Cherrystone': 252034, @@ -133,7 +132,6 @@ def __init__(self, server_address, password): print_error_and_close("Wargroove2Client couldn't find Wargoove 2 mod and save files in install!") with open(file_paths[i], 'wb') as f: f.write(file_data) - f.close() else: print_error_and_close("Wargroove2Client couldn't detect system type. " "Unable to infer required game_communication_path") @@ -145,7 +143,6 @@ def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: f.write(f"DeathLink: {text}") else: f.write(f"DeathLink: Received from {data['source']}") - f.close() super(Wargroove2Context, self).on_deathlink(data) async def server_auth(self, password_requested: bool = False): @@ -196,11 +193,10 @@ def on_package(self, cmd: str, args: dict): self.starting_groove_multiplier = self.slot_data["groove_boost"] self.income_boost_multiplier = self.slot_data["income_boost"] self.commander_defense_boost_multiplier = self.slot_data["commander_defense_boost"] - f.close() for ss in self.checked_locations: filename = f"send{ss}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.close() + pass self.stored_finale_key = f"wargroove_2_{self.slot}_{self.team}" self.set_notify(self.stored_finale_key) @@ -213,7 +209,6 @@ def on_package(self, cmd: str, args: dict): filename = f"seed{i}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: f.write(str(random.randint(0, 4294967295))) - f.close() for i in range(0, LEVEL_COUNT): filename = f"AP_{i + 1}.map" level_file_name = self.slot_data[f"Level File #{i}"] @@ -223,7 +218,6 @@ def on_package(self, cmd: str, args: dict): else: with open(os.path.join(self.game_communication_path, filename), 'wb') as f: f.write(file_data) - f.close() for i in range(0, FINAL_LEVEL_COUNT): filename = f"AP_{i + LEVEL_COUNT + 1}.map" level_file_name = self.slot_data[f"Final Level File #{i}"] @@ -233,7 +227,6 @@ def on_package(self, cmd: str, args: dict): else: with open(os.path.join(self.game_communication_path, filename), 'wb') as f: f.write(file_data) - f.close() if cmd in {"RoomInfo"}: self.seed_name = args["seed_name"] @@ -262,7 +255,6 @@ def on_package(self, cmd: str, args: dict): f.write(f"{item_count * self.starting_groove_multiplier}") else: f.write(f"{item_count}") - f.close() print_filename = f"AP_{network_item.item}.item.print" print_path = os.path.join(self.game_communication_path, print_filename) @@ -273,7 +265,6 @@ def on_package(self, cmd: str, args: dict): self.item_names.lookup_in_slot(network_item.item) + " from " + self.player_names[network_item.player]) - f.close() self.update_commander_data() self.ui.update_ui() @@ -282,7 +273,7 @@ def on_package(self, cmd: str, args: dict): for ss in self.checked_locations: filename = f"send{ss}" with open(os.path.join(self.game_communication_path, filename), 'w') as f: - f.close() + pass self.ui.update_ui() if cmd in {"Retrieved"}: @@ -397,7 +388,8 @@ def update_levels(self): level_name = self.ctx.slot_data[region_name] level_name_text = f"\n{level_name}" for location_name in level_rules[level_name].keys(): - is_beatable = level_rules[level_name][location_name](region_filter, self.ctx.slot)() + rule_factory = level_rules[level_name][location_name] + is_beatable = rule_factory is None or rule_factory(self.ctx.slot)(region_filter) is_fully_beaten = is_fully_beaten and \ location_table[location_name] in self.ctx.checked_locations if location_name.endswith(": Victory"): @@ -471,7 +463,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_1_name}" is_beatable = final_level_rules[final_level_1_name] \ - [f"{final_level_1_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_1_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -488,7 +480,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_2_name}" is_beatable = final_level_rules[final_level_2_name] \ - [f"{final_level_2_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_2_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -505,7 +497,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_3_name}" is_beatable = final_level_rules[final_level_3_name] \ - [f"{final_level_3_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_3_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -522,7 +514,7 @@ def update_levels(self): self.ctx.slot): level_name_text = f"\n{final_level_4_name}" is_beatable = final_level_rules[final_level_4_name] \ - [f"{final_level_4_name}: Victory"](region_filter, self.ctx.slot)() + [f"{final_level_4_name}: Victory"](self.ctx.slot)(region_filter) if is_beatable: status_color = (0.6, 0.6, 0.2, 1) else: @@ -661,7 +653,7 @@ async def game_watcher(ctx: Wargroove2Context): sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)}) await ctx.send_msgs(sync_msg) ctx.syncing = False - sending: list = [] + sending: set = set() victory = False await ctx.update_death_link(ctx.has_death_link) for _root, _dirs, files in os.walk(ctx.game_communication_path): @@ -676,8 +668,8 @@ async def game_watcher(ctx: Wargroove2Context): st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore extras = ctx.objective_locations for i in range(1, extras): - sending.append(location_table[loc_name + f" Extra {i}"]) - sending.append(st) + sending.add(location_table[loc_name + f" Extra {i}"]) + sending.add(st) os.remove(os.path.join(ctx.game_communication_path, file)) if file == "deathLinkSend" and ctx.has_death_link: @@ -685,7 +677,6 @@ async def game_watcher(ctx: Wargroove2Context): failed_mission = f.read() if ctx.slot is not None: await ctx.send_death(f"{ctx.player_names[ctx.slot]} failed {failed_mission}") - f.close() os.remove(os.path.join(ctx.game_communication_path, file)) if file == "victory": with open(os.path.join(ctx.game_communication_path, file), 'r') as f: @@ -711,11 +702,10 @@ async def game_watcher(ctx: Wargroove2Context): f"{completed_levels}") if final_levels_won >= ctx.final_levels: victory = True - f.close() os.remove(os.path.join(ctx.game_communication_path, file)) ctx.ui.update_levels() ctx.locations_checked = sending - message = [{"cmd": 'LocationChecks', "locations": sending}] + message = [{"cmd": "LocationChecks", "locations": list(sending)}] await ctx.send_msgs(message) if not ctx.finished_game and victory: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) diff --git a/worlds/wargroove2/Presets.py b/worlds/wargroove2/Presets.py index 9cef9c93706c..1133ba8dec33 100644 --- a/worlds/wargroove2/Presets.py +++ b/worlds/wargroove2/Presets.py @@ -1,6 +1,6 @@ from typing import Dict, Any -from .Options import * +from .Options import CommanderChoice wargroove2_option_presets: Dict[str, Dict[str, Any]] = { "Easy": { From 91e1b7f3dc724d839551558db6b90f9ce6b89114 Mon Sep 17 00:00:00 2001 From: FlySniper Date: Thu, 9 Jan 2025 20:00:33 -0500 Subject: [PATCH 87/89] Wargroove 2: PR Fixes --- worlds/wargroove2/Options.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/wargroove2/Options.py b/worlds/wargroove2/Options.py index 297d8c531cc3..4b718cf7d537 100644 --- a/worlds/wargroove2/Options.py +++ b/worlds/wargroove2/Options.py @@ -48,7 +48,6 @@ class LevelShuffleSeed(Range): display_name = "Level Shuffle Seed" range_start = 0 range_end = 0xFFFFFFFF - random = 0 default = 0 From 4520271b430ae3ac131bde96e4d2758ae6ff451d Mon Sep 17 00:00:00 2001 From: FlySniper Date: Wed, 15 Jan 2025 07:46:34 -0500 Subject: [PATCH 88/89] Wargroove 2: Removed client from root. script_name removed when starting the client component. game_name added to as a parameter to starting the client component. --- Wargroove2Client.py | 11 ----------- worlds/wargroove2/__init__.py | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 Wargroove2Client.py diff --git a/Wargroove2Client.py b/Wargroove2Client.py deleted file mode 100644 index 57c41155954a..000000000000 --- a/Wargroove2Client.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -import ModuleUpdate -ModuleUpdate.update() - -from worlds.wargroove2.Client import launch -import Utils - -if __name__ == "__main__": - Utils.init_logging("Wargroove2Client", exception_logger="Client") - launch() diff --git a/worlds/wargroove2/__init__.py b/worlds/wargroove2/__init__.py index 9bd03c48877a..0a0b2ea7e153 100644 --- a/worlds/wargroove2/__init__.py +++ b/worlds/wargroove2/__init__.py @@ -20,11 +20,11 @@ def launch_client(): - from .client import launch + from .Client import launch launch_subprocess(launch, name="Wargroove2Client") -components.append(Component("Wargroove 2 Client", "Wargroove2Client", func=launch_client, component_type=Type.CLIENT)) +components.append(Component("Wargroove 2 Client", game_name="Wargroove 2", func=launch_client, component_type=Type.CLIENT)) class Wargroove2Settings(settings.Group): From 0871462c25b1c5a81e208c329de7251d9eab986a Mon Sep 17 00:00:00 2001 From: FlySniper Date: Sun, 19 Jan 2025 23:05:18 -0500 Subject: [PATCH 89/89] Wargroove 2: Rename extras to total_locations. --- worlds/wargroove2/Client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/wargroove2/Client.py b/worlds/wargroove2/Client.py index 02cfdb4e2812..f7efccdf3181 100644 --- a/worlds/wargroove2/Client.py +++ b/worlds/wargroove2/Client.py @@ -661,13 +661,13 @@ async def game_watcher(ctx: Wargroove2Context): if file.find("send") > -1: st = int(file.split("send", -1)[1]) loc_name = location_id_name[st] - extras = 1 + total_locations = 1 if loc_name is not None and loc_name.endswith("Victory"): - extras = ctx.victory_locations + total_locations = ctx.victory_locations elif loc_name is not None and \ st < location_table["Humble Beginnings Rebirth: Talk to Nadia Extra 1"]: # type: ignore - extras = ctx.objective_locations - for i in range(1, extras): + total_locations = ctx.objective_locations + for i in range(1, total_locations): sending.add(location_table[loc_name + f" Extra {i}"]) sending.add(st)

      T7vt?tqkY^LMQTj(J9idCk*n~d<$~M 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%0D~~sk2r-7;>%B}WvEs<#&7hq4MN^cyD6z~h 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 diff --git a/worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak b/worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak index 95cac9ac173669e314a5a05064e3b278fd93547d..4cba54b7d9af6c118080ea0df6ede35ad545c9e5 100644 GIT binary patch literal 220960 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

    gp$uzmg#uw3eVMqt`rYmWJFoFV!T1`Z24q!``XL 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¯yc4%x>rW!Wt_N#0OMq(aR z@nkK}orck_bPK*>8*+B(WK;qpC-~o%i|poq3{RudVOB(YFdF{lelk8oOP_0)#C_6Y zD2#xo2L)ltx**O?Dj)O%^$b#otF~}Q2+{p1+#BhEA+ytQoG5<|jxg*bDYIHuuV*L&{sj~?Y4sbgjfY7T)d6G1Nr}&M^c*WwQWYRASbh**$k*gE^5-x2)ipJ`yAmnnk?p5OiM3Er_E7x?5u6UZC+WYidOZLWyw+trZ0*owX|r-!F;-?GuwQu6 zbwznR4*l?!fY=#|uvC#O@-bdnB=(mR>)W6W!vuEAY&|^)IZYQimA6z&x&nYaZ&_5 zmdK>KnJB81GzUW4YBbTO_3Eo%eiHEUNTttvaCDor)v6q}0t5X6qQZps9hg{-Gss^o z#kC1dJ-YRg$cqEb226xr)p)Dx$q2-GOveE!HTN{Xt9{?;kTEa&hn{cKupUrbIW^Q# zRA~ywYUP<&9^qSy6|EvtASd$ryI?o$^4WH!CBWOvSl##I({N;VOB)QrE)DSm={g;k zf|xkDWHn9L9MG}03(SsZ$rInOvuH1JfZjf>ipxUTzC-_!S8V}@ckFG(iz$|*5MF(A zvL9lKlk9j`2{l#2f-Kk%Fc)R_F%jgL5Z#QLSctI`0+Gd)_4wL;QqeSV0&Z5}Q!wqb zT4`IAw_w~sIHEa-E~4cI38CC+0YxU}J}N8aJcl^h8$B{#b0yMe@CvO1=(Fz^m8!bA zebkfoQD~MSO4RfX&lgwgC!{pbdvEfN!EAOPDAk9%BoH zs#$+hdf)UAS-%pna?sLpjU$JgOX{O@!gWQ-#u8EDIB$gf%bCkKHIKk`59%^-+G994 zAY57{f=<+o<`$H>i=)&yw;f8rW{~*KEn+=twdEEmbLO7Y%tvV_Bx7w_PC% zjTWI9x>}MhiKmL)NVqwyY3chevi_cxUv26C8i&l6l?mJ<08LUAM89lC=|wo%(B@_# z#+r8ix@g~}Qs_)~G~j97YzyxR74&x6a!v%vO)%4P&#a)4uSg65bG23w!PD3tKgDvS zIEBj*6Ev(Go{j20YAKM1yH2-`VSLI{{9$FRQdkPc}iqcurpc#UlGjY zh`XZlOuVw>#B43B8+aJnFW0N>!3(}$@2Oj|Az<}=ydT=-4Xr*pjcWVxZFL) zJ;Ytxhw)fj^+`yPA?ouoR(QA%CW6;drN7r5(&BBinwGZCGhmy7j`Gg%!uI<4FKza5 z+9u#^vY|AcIb3K>y0?tGV}Rt;VgMKfwd6O~_Y*rEyTMuqAlmY;) zi=FCYxrK2UXSWtH9j^)NcBrIO%7JYw#QmIA+U49p=>{A>%NDskuTX8P(%9K`V4tg% z63wT2FHuo?y!yeHx6SdY6SCLoh5g*${T8SXFpED#`#uhVKZ9J;WwHl394g$r44CF( z-DR#J5&G&)Sj4WGO(ybA7UtK&*9yW=cs-ELFY%c~)W5FhXLK20T|h*~8EKe?bftxW zWUGs=NNUP1`5MLAl(kegwq;@yItxdZgd5zmvk%uA7gQ-}g@HMfu^A_F`yXs2 zkd2Xf1T%A{-|uNb-g!_sB<-EnStC}ex0V~u8wJ>nun0l)MdZo1$*}8_DHsm07&1ia zkF8B1EYrDXdlGz6i9ChRN-;sXo(PA@F|$sToDmBfsc!F235o1rDYF5797C!!kN%zN zdADBW%V(h}Wf}}g`*s8(4Fjwtsh-fj9kP#w9y&N8ZVMIW`L4)+fe(BbNgv>yMs>Yu zTO*#5T&)hiwgkjHJ{D4>8~KKd+pmP;uzDnyH+W#DxqSl$iqTj=cpE*a zeHNiHX(RQ7z)5(xzn2k8WJ1yxv3&mq_P|{#Qov5>WI;uf&Kp6>;mI)($x+^U>=-SA zJewg4(4}Q_+_HN30>y)Pb+O@*Z5RHp?@`mMI;V_-yDMwuau`E*xP}I*A7m!2UgN(i zrm)MSPg8YE$wJH3qnZoG5E!JbRze^WFSQJ>W{fT!qp=y9Dy1P%2xSuAy6P7k?Jh&U zQ0|l>dLVeffy+b*(^0C|WFNcl3xaIuge>)IK%qg}k6pB?%2PdzD6`=4*O3q;GaY|K znWViE>qvq48RPJ|{dqSOSj!a6NE#qfyE-u?BsmRv#L;4l!ErnSrPm zVD4N18%gOsj1?|)$6mf7!O)|A7r|S!1SU2wX8aB8 zG~|=U?oi8!B2WI1X=_UQYslGjN?=KKc|W*wnQZA3vsOQ@NF|rAFJ-o7Yhe_Ku6Bf5 zvl_ZByI-ZF^dVP2%&|G$8Ev){JcTOfS1Aoa;M*&9^q@gtaZ+t|mf~2Ti>(BniiqAI zem~3rvZRtXiDBJtAQGU-)^Tq9{~6Lzl&gkKBf+CTS7Y%^=}~6H{`O`n(4sq(88cm5 zxbEnOK?Qw36(fz#Jr+N$*fn$WWU#qVk4nSDN%=2QH4?1g7hg&Xm$^4p+WOQ{0{5uX zQcpWdX%o7+@}>}k$ydZlax!vC(yR4h>)|n1!87t!0WOsze%x|~{GAtLZkp_Uoq@pyZF!{br5jyycvwmu8tu7?!{W;!l4Twe{8`vQURkF%XYVP-;`XE(fq)8<_kH4^Z>@|laaSe| z`UzWE6YYtmog3#dXE)U{4>O$PCWu=;?0o9Z=hC$(uyjk1-xZfxtkZ58K=fLLj(KBxN zMnsf7=N}@0OWSv$z{O|RMzY%f1t(T6jZW5Q#>qPYA;)WDw6ty)ISVRux zETfEV>dfT(B;0u;%qST*qrpRpkUPDN3iZ#DcZ* z3zf5*CnG_df}9Rf2uAOUL&qowO#B|e3s2=Tsry*)XQ<@VaU50Fo){baFR4_QWmDvUeAehQKlX$sV zaFi)VxA`k`5&k91k-EFY+c;Ho<@A`e79{Pl?votUhe(TGpx6&v`#Y9`KZ5VCyfkY8 zchl!4_mv+WR@J*Z$cS)(YSMt{sJbW27ku=?!bYR+LC%n{NRJHqol|#i+9J)}Yg8&@ zu}XMOJXt+Z0n}*}$C46cOtKtktARbuvudUlid$EoOxhf z7#BSxnLdc(7^l0GI;@a+>~ltG~X-$^x&svM7KPtK)D9~kR@T& zjngIp(j4+wT_xC3OV!tNT69LLy4Ryw(a^ZtqG=-#6dTrhb+T;)F8yW*m^Vjhd-AUc zv>y^(gtjn7s?nUv$dC-AyJvawRgV$a{AXyQIEbH4L2YLApH!tLYI7G)`OLlal6b_A;|zMjTph ziYo3K%p*}&Ul9=DgR-&|A$>xqj~RRT)$|8;b9~KA`~vSgC7hk-zG{LInbwy9+hIlP zy!Bs1xj^j02l!7(^@38XRP5?aZo{1w_Gv-;eubHvSHo%PGR@xm;`|j1`?hx~^;=yFCWh_HhO@kUDfFg;;z} zO$n6*Z<6~m3FG@Lu*_^=41W6_6aeH`Tk6h-W%z$qSP$qybr2>hZlM}UxMCL4IWiQb ztf^wuplA4dYfzRemXO-%1(e+R&v#>=iT%h{^LZJfT*NQPsTyKsWg}M8=lFmqxVkQ# zS}*Aq*5iwdYVBC+pgO@^59uh{bC^;LO03L8v}P{MeWarYh>~Wd+YV=;_hp3CN{UvW zdn=nrY!ZqO?Ul!AbuG;sk@s~PQU!a90<4}5a4|UzzBDzFCMvEFRA+^$e+BSsZM}f1 zhz-ywLVemlWN%^(@tyy2EFm;lZaiW6rRI_$Jh3AvVwGEaiUi}A9kz54t*P8fE%DYR zI>rHV>zfYy(hBKcMP3DY-R!qbFS_s}&9A~nnvrb&hbSF$bf#_6lmJsmsB~y>qjz`X z-fm{%B{gQq%^=(ob*mMeoi7OjCFZDyVm;G@B)li^NKMILK9eKcZPNJS zT4ZYu?E3lNigHgPNwPVH5Y4K5>IRm7dvFhW^3rL@7Z^Ns#jzP$vY(-WXx=lo2|IGD7eC?tZMUo!_~RK*&5oivZ#7|h<+?Am@i zJ9J}Gfb!Pr66ND)7>eF$^Z%>CDy>Cr9)D^e?}gA#_`QO*8+7J{XM}0nNsZlaPz{Yl zWI~xaXNJ&Y2qyP>zZF0l0t;O$V@5*h$jjQY*Q+E9F|i!^zr#_Drb!VYQ1uS$#H8sFu>{?TM8#ABEau+6eM(`Y8 zt;D`YOYj1cl|mGe#=mPkFofC|W-i`Bd72-OgM>sPS?}C&)~Ts4SrY67B6)U88R4!^ z-*`!W3y@qwP1f-|9lhO#NyWM7VtgV>p+8rE4ekEd3Xg!_lwo$fR0lWGO(IB{E~IHX z`!%zPv>v!cL{P6ca;sI+d69F3j-Poip?=>>1TgMjRY%5`-=XO(T-2n4Qo=QzNzyE* zUwFNv{!$ug?&bo}<26_a5r%{v>Glt8Iy2~=I_-O|Df#f#O_ycO*(od@mREVpu20tbVp#%S&An6>XM-%X43qLo7|dEQ z6l(voP?w$9p2_3&fMXm`lAp%PLv&9Decc{-srhT7mLr_R@YiLHAvbWM$vr|X65(c? zb;?Up4jMvoyf!P1TlpY#;ky5!=O}qfXeF;-}pyP0a42rAESeDXr&ts9d z34w3IENV;f*2(0^hEC)T)nE2VnIV*B9WWKEAlm=0X0a0;4)1n6kEM)Fp$z?{s&@9I zj)W8$U42{qX)~wUihS@MJztR^l+|ULh~v3lFBZ=a0FNFy* z3%g-_*{~cjVzehg6k#6N<)*hD2&yY*mMO!TCa0c%+ zLLDk*QHGq1S8pBsCSK%rm5e&sXJ5x~8=pbZu;krypXJur?dQqDF= zcuy#Ed$J1E*;uA=g{Ip1eWWua!>DhhJjL6~DEn1185#E)joGz>Yw&gRDFLG=uZGXR zfZC$WkO&l9)+HbMNpxd?F!kUF#Ce?LzAu9Geb{lv8KPwZ2$PtN*Y6HtSojn}+*mve za3Ll_-3b+1X-i2Mr2!P8#-|`B3AVE4Y9dNpSCgl}G*7WO_H)>|a>45ljjZPl^aSuq zr3DuRg~WG#PsV>1sx>6B_CS10_&8LM;E6i}HV*iC4!j=fu(7bv+lCp;agMir;u}PY z(E`~Om~%Elt_>5@zKfL2lN(LvCAsAzkx{o%fWR4Zn(^PNua}ihSd>=SUvU9%@ZdHyLB<({`KzJhV|oWtJwvYSfh_88YEA@cI|tIkv!bQW%7vU{!3- z(0YJfCA8yCS6q)0j_^Y$XIs{7Q{5jeqS%Z-94Z5DNvPybvU{Uln@lI7FbUtsmy*N5!%zp%7c@VC3juHGH~ zEcYB3$H&s8z_v52mDX0n+}-EZvO-vzbp%HXeR1KV@c@7wO?E6al!0k^nfUxUoNc)= zJNXNrPW8>1$<5%!c%;5nat@W>67$^XJ++e)pqndJdGI_v<&-I4_||o-mf2G`V^TJZ z<}V2sXmqx1oq!hlg7;w2K#+vA22Ir2^s@x_qVYa#ZE~4SG#>df28W0_-|l%d!XDge z2fKW^sMn^QXutq)SoRjy5`iii#wrJ(P6dsWFH^q4*Qg7!clvAqNo@FMRYA$wZF}kO z0dTXt#8&RrsirJT{?hc~`WuVGu)D@`*`k>xcCHV<1`mnIzhYSd*}K5{l?4*buYw{H z=oidrEtZ^32EQX|cWfJs#1?KD$xDwLkOFRHtHWR_$a`TQn^c0m>YzL zQ#e+C5+c7OjJ&Hqs=~QMJ1j~hWd0D2rZTtx%y+{;F61)mor(GERC~&eSWos%W6n`8 z*)?U*bAG56oj+FAN4*v4*hGr?(cNtx(_FAb#I4bPYQk+EbReh$*UE}1?`&2fYpCZ= z&uv8_Q{aoBi?lTwGEbOm6q@kMkl$9b%H(?Ns|s37zbxPp?E+?ML!+jnHP!jx*{Jp~q&a{<7 zgw37AO?%jUQ($e$_`^0BUteina%Qid%AtwI|FR|p)6r4#8#*otrORfXo+a`rsj+au ze1O-?DZvQ^xRyveq{n8jat(d#G6tBT&r}h2y+= zc{LBtyYe(G5DjAg+tbA5P&IN+s+EpoC6d6qB>m*zBi006{n7dxZDWLkgNXgAoUfkM z-uWbEzq4%GRiQHnspee9gdHT>=pSc5Jq2VvFwXZMb4*RPwhXMmcNc(J5A=GIK_GAg zb2&fGf`JXh!sg}_vpL{Zwl`9~5PntV)+utNecv4Itc}H{C;9M{R6gV4KUea{{Og~^ ze2uFd<$M-@SjKc6Fd1(cA|e!7OY!VehBaCBo0@-?v=x(aw73GxxTazise4m<_H(SC z~Zq2^0qdka5*&$POY|37k?`n}#5(jW(un-oA0&BN{OPHo*HcQm*n zYMyPLb~}P3DmJgM4+1`at&4?X+LKQFo!4Y^^V_;@tpGFPK^zgFX~@J&%hNKtQ`_W+jA$^;I9a~&LWE@mrd=mHG`FNAJ+-&!> z&x{I>rsJA{E#=5wrF?RC?A8j!OG8od{C1#nL<(?f}q5;ucXRCLi#Q}QK zGck(_Js@od`;-mig%*WlW>U^Gm#(2$e>^HvDf;iCGGvED7=BOA)P zl**ZOYtY}NSLJSVMwg)B1CVlBATwAQ?2DQj7NI% zT_psshX*e-8%{SRrsSUox~L+0-J7&H+W*z|U`Qe3?Y97Eg3W0alUdRP`_TWLNLBsZzmRqP~ill^6u#i}6 zp9UqoZQ#i0eBO2_kNd>oinj+!)O>MiOJPcHZo{qy~Q%8LmC z%hoYm;n12y{3j3;A%&lHW!|JK=Cl|X4X(n-t{MXn+lgs8lKMX=@t56>-fNK?_Dp$t zzu{%&_Qe2lZ~2!jlZJsx^yjD0^NCLrVxD(4nh7WISc%0nBy=Sd|D6M`>FgE)wbIOoK#hPTIYuBpO3{)Kf!m0QAko_^nWo!^?Q>g6gBY z7@sxMeO+!45HIFqA#~+Lnv6Nh!nW&qE%yrGanh={-x*jCy`z5 zwQUa?KZlD;{D?4BhyqN6^F9z7w(ZjFBHU%z0~z5-bqo%h2xa>{z>SFOs_{hd%_Fh| zMLN$rT91#Kg^f!!`1%Duuh!`T8`?~w=aV0saQ9X;vM?)XrFlGlC=XU~;hQaVMj1HA z({(94{np6NwvgvAr3q0r7+@S84rce{P9yWU%3Y)k1|NRCUX`6s^#ac?z#c^Hjt{j9 z^bj5qn4TkbAuVOuYX)>zQ$Fl}P-B!&!PUh}`jV`k<`}o|y+4=k<1s%y_qDmuziGx> z*ZZ|V<}R~h_^)?)ubPf?Jq|}aktrE{e@j?p{6p4WiRk-X_Ph5*b)@s+&3lOARLwG$ zLY-hI04i{x0kWTz45KvbQoVmNX+lJ1l#&j`IL*4z{tNFGf?;$5ox>KUM1E~LLp;y) z21p(y7|o0I0yfKspidchB#ZZDNG6oi2=b`>35se$H{GSKG&O=c)-Qj!msx@6rg69K z*4=QA{Z)^ay2J(e$rHK0$OmEHQqvorC@ZW3i%=lZAFuyN@g6m zkzO2UkaJc|ZlaP-kBt?|-DQDRxEubCfbS`CrF3ZWE$>7Vi`m1_1UzOM8TsNf3 z=?0dJ%n++`)eilI;bkxT%s$nU$oMAhd?z*5!5;GVa2XFt0L;T2kuHds*zu_f_U|%S zc|Euv;ngH?pHUj9!uHjfY!sOKWVgfTstzW>5>%*Q$NC|G@{~F0PKqj`)&Cwg+!fqb zyD`09wXsXh9mLhWs@0|q`oQMl&M6iL#&xM_hK-L4Pc6(;0Q$azIY%%B?2MaRJA?C) zpwNid>QnS&FS>J1&3%&KKJxU5^Nxj{DE3m*H9kQ^-NNrPfdV3jT&O{-C$y<$J7LnVRb+nv>$ z0vv>N4X{Q35&P;!ri|_(g*`>0cEz=C(Vq*zKBxH0%oI!HED(TZK>DM|qEb>B9tFosIgq!F`)$R(0V{CFnCPjp6 zb)RMJNp0|8KH91Qc*^+v0X6!S(L|&05d?$ zzp-f~l?1)U(kSe>Ha@8d-GrbSi(`rBNAYo8tvR)m9Q=30VL+CV(}|lYV&&=g$(V55 zX?o0_(>I74#B_`^2a!Njwc>l=;3+(I!ypsKhw2~bm}7k%A7v zlI^aU%hos{1|d8&UgJ_Y%c$D_lT&!qAVeEs$Wd!l zBIw%IAo9PV(FJ#UM*i$imu@}!0p3`-dqp?3d{*fqf;?g|G^Cf~k@!_RS*iGA07f<^ zbsSizzx+8d9fo|X2gZQBR;J4WPwNDOv#BU8sauMsrh=4w_4+g?|L^T znzMf0CAqWmZnF*ydYu?E+8W5W(=+FFgN9Hp$@x#nEnx6cr?ac-;u}E3y9q5AD9CizmhVA+u}J!ugn_KO>DZMvip~= z)o=9<%F&3%m80KwCf+e`{~n{-N9hux7tv{7*9Mte-}k_F7)X9-b%yyZU|ONBAsB5b zv12_xz~S?jwB)u4>6L>IQ&pmciYa;r>H!TU^JoF|rf32zZ%dZ^EfJRdrXcy|{Fo#z z$Wb()zHRZNFk5F|;hIRbh=@EV7V-{h-Tul;1?snKg};LDiG0n3>}57h zzWwBw0YQYmTN3+Km~g~lhF&Zk`wwhfi+pD@Z?1(@e=HA)96>>(vJ>{F2$MM%nWu9w zwEa4|i7o^8&%WUIR}JszkCkrCPgA883%(T24PqBC9?gv*Y*)#%K?=*EsBcI7(~pS<3PW94IEd&m~JP%if0*r)osPBYWcEb(CiS=|>AJbb- zy%2q;w}t%rX)YAg@cz#)`hD=~rLb-fy1kl~Ci)e|iw=MNAw-W*aN$G9q%pZok-@^9 zoT-X5mJQ3n0klxr2DY2aNT6U9f1lLMXHU}Hd1=p?A@_8ng~v33`NgHwB!jlh>mw4u zdB_+dU*xGkgq2@@dM-6up;F_Hd8<=R+KCM^cumd@ z8WI0x5z(JT5{rfrosTxYK5junD5ZkB#UR~pN=t)OZ~4}e<_2^sgq*M~{Im;=*J2qg zvdFPOg)VV+5!af)eKjDrc(jxzju`6dk}S1+OIcBe5DXqhtc6&J<^XvHXJ6~;G&{)C zR}EbfHyWCpIs}2%rLw4w=wEpP&aBfhHVHAVJP*#uh}cOk$)tm{Kco!*`gw!NjeeCt z9cI`z9+!YjDA(`GzdPJpRA4b&8bxFCZ5Afv_R@ddkk~m2+#Hi_ZmpvYGV$|V%5Yci zYIc;snw`Zvb-#=s%=xQAl(>5)H+Jw!ZG!$b%58Md4rhPb1Gr_JjwHNmTKwcdx)(1I z6IqOPUS{|GRM5Av73!;!jS8_))Y$Rmn>t(r0}3rz`>j?smC^~AJKq1ga2}W%+bX!g ziBvv~g+whA2TLJdg!P8C8*iDaE2F<; zL}mcK^Vfr$ptDUxu7{7#bT&&qS@$@7k;;`qrtS(8T@OxD#ZU2k2;Aepe_= zP2!n80}d}{aUBAmDBf*EWg=G_rnH>6J6Hh;aYgyK6)Sv9XFUHx$nNCgAC)qw;2al; zHajiD@2O=fKxSbDp&~$Dq5Cc734p|Cm^%`Kmq72jgc2*&AO4T~)xFNEAz$;4`rm<$ z20ay3#860IE}gD5Atp@Sp?oafx6UBL60|fCEQXQZZ55k~$Xl3mnZX>d+4grw=D)-m z5?j-a$|C`0ccS_6tk?ExWC-ub`mJe^ulmO8NQJa(2n>{Okm!9RLQ1iB*k)#iJ{~DJxvw2JQ(pMpqpR~~F zJiDleA=_fKn`mm2B~xD4ht08W1MOhK`3S?vKu(Qkk|$|G7>YQNQBMIMgpL5E7OB*8 zS-$jJE$$ogFO&>$Fj2CXM8{%)S=z36Kyn#iis%3!#wVkX-w3r4c1ify1zW#K4mE-6L9+x%sCpUJS60+bzaPqupD5~O9A0|_g@tkIH+p<>~ zEe&T)CIcUM-S_ik8oc%N&tcP+A~tXv3_U%yIl)p5#LRN6(~nV9W^Rt9$46 zKe60p<1FY7y=%n?nt~B~{EoP1FK{HcZPk5vG6#!#6PwUo3Sv9|;Tm6@~5~$zu z7NO_9<7{R*BK@sueo3Nn&B5WhV%JKBS}duzz)+q_vMn%Mam}{UpP=26RJoTxkEdQO zj;INyCmhJ?N^e*Oc!(t>SLZ!>w^qW&yQ)^&-H-21pllM^#eSA94^T866l7j2&_brZ zqfD1)=E4G)LDf|cP{ki!J}m0&EY12?=|PYs+gtbcv0-C#GOFw)g6RO(aZwZJ zQ}d@=5hh6b0JQ+X2}qr7jPcsi??Z41=)|vpvjP<)m62n#43Z*XoyfIYrWu-A9qFv@ z`AY(|EWn>E#0D8M$Ku8tn_o%XgB+tKF4oF zhEd44oO<9_#!u1?5~`y4bXPuWnotvDz{F=3H;xGST|-l91dA4_nW6}3p~8>S87|8L z5ku~j%lYd>@nEq0rusIaSkWxX^SqPK%hEh3l*a6%Zyx;G)|$oDAkaBvDT|^t6@$lF z4~w5imz!T=(Ati|cSPCEk{J6G7uizhbddLRjr_q6ih_HWa9xUdIWY3*R@#EYoj82+ z+(_5#J%>EV))2V$F#9c_0^v3X@jem$_$kYZQ*)=;gRDQ3q5V&L+>wovLB1yfK%=tO zhqECQ!MkOMzxDKQR|eZ!+N{38B2sFQHi?p3Eu}&a{IU&kQ<1@Pr@G6+?H>^Ju?rN3 z-^>yv8@_ct)L-kHb~$&4vY$R{eUS^IAEZ6c^SY0`a*%uB*?iuhO1pROm^)Ax^cz!Yzz66e2<(WK%j&eOtOC2?lLBVlt5y~LlFnh`L9O0tN>vV~OVATV5 z68}r}M)4@Nf#-bN!2iz@y8^v=T?c(Xk}1o3LKl`Zgc=V$R16g?LwDiY><}RL<@d!n zeZ0EP*XQ02oCzv1N>E3MmBQ6vK&_*EtD}?dd8@Oloq)!}mWNZ2a8>9xi*&$XzJ-JG zH?*G2^f7{NH)o_R=6jSKqS zn7!#A@w@9sHdjc0Dxr=eo!7Tt^=5fl-QZM@2+#Cjl0I%RJ z6@VM2KMGS4v=789>%M2U{wBkvx!&L_PNz~}nR(~^d|^#JXy|(=1=l+b=JJg$VH*X#r57J3|!!*4xT<(8g#WBRYUHvVw;hfV;2@^alz6)T5Ro8kh zWeN>*W!w+EFDAf}%)y#I{(9YXy#el$A0Ipof+|(XQAg;m@)XRD;Br{dM8RyDDeui) zK$U&ofL0M1GSNF|O0^lJc`d@yunbx~Kuq2NjLrf?Z$e_6V2t1c6rm^QiqX%QJ3I9? z%S$zRo%l7IWJE_GM>$|p{{or%jbfwD4i&E}#8FVg55br?_)#{}n?hKjO&9aL?EJGw zl86mXs~@ZG_q39BSdY=Mj)9H4c!+fZA{N8gt_ebS?#?L?4zq+|Q?TBhRYQWImAi+c zN;ZK?iaz{6D06Gie9|*Vo7Z&PB;Spf@D2u>CQ|`>+-+=P+}mAd?X>$YJFS&@lk{U_ za}i|Jzcc2Kt4eBdYejj9VnR)7RlBV3n#SAcM_$#*`2LE0C+zZJO?U<(}|ueOIyrw+l--;`5y4o#$Q zUI+C;cpC< z6Tyy}BOXx3gs@g@KBXEehL1xP-vUEda{{CFYtO{8cq+wv42ox`$xw-vl9XXTH(I<# zq$yL8<-uwk00nACdZ*2^gR?BPqXZ&Xi8q0pexciRue}i>Bg2i?N$h&Ov1vp=zZ~!t zSSop-=w!_Pfp(y95lCS<4Q3(QVBN0?Sz2c4L zHNCf2aV1W;X{dUJewME6w)@GcL2Ru}Brh1p!@7ny-s7+cd%t7BBaSfCNT_L(uURfK zYn@zk!(+U)>V1}EPMo^8D$_RK&Zr@FWzW94w~`0t*UD&c#nFAU$i4JzF%-naBdDlF z+6bJvby_=3^T(__Hs0qW!N@|v=o7QyzcN%gR$G)i45`UYOQq|;M!PN6qCJFLd7-De z%fn3rZuMqElpkXh?k^j}Xi*%or1~_ga0S26>u~(9ky%P+&j;_$U+v^RtLrCXL-X`l zP4&TQSHfH^63797Vu`~yhl%RPz6rVL+V<{OAos#THCqxk*%VP$CWje8RTpn2$DrMn zVQ)4@4#>#26@NpvdZKn5WXM73ml+h%yXIsrrK0#SQ}LkOCvY{uf5n#;*6AN9^i_~lzc_rEXme@xccucMQFmcQ@wg}w%-padXQ<{J$?a-NrQUY zQxtbJ0d*((t3n2ejY<|9pR4peIrfr#W=PFJnClu}7R)e>pJ635sC}I1Z`i4Yg<+u% z$(Ps=()oDs4wuqd(3#JV-5Gmgma8tW4Z=TB4Dp62&^wraA1=mD-kzaztGHqj1JzHk zgEj1&l3Oc*PPn@79VLVJEX35o@P8{c+y{f1XxqskV9Jgf@%Y76%<-~f)}FnUSpFyj zLfI3>WGk4c^=o$_s|9{Zv=5ZomP2b-NM{;0h~oMn#c-7OEM3|4+htE(mo$ZAz^LFy z3>PNJ{8#j-c;K?(|2=M1Rz>W)kM>`fqk6CB^4f<+`6H2@D_c#{ghCu|&0*edz`*5( zhOp)3Z_C+JOuyO9FeBQ$&@U4mX8f-h#lFb(+nmY~#|m9WbQ!CDn7b7f#j=T?ohw^Ykw+_uL1mQIZ20@&8H@(Fw7LBFRXXeDGuzMQx~e^8Q=a_GTPVjx^w1{sM1LF8wO@YW!_A%{+ z2Xw@MmTh;4D+M*u?eg|aFB}G6^a8gF3thOhjUk%_wy|-(xo^17+0uzIu2{B?eMID3 z0|+*md!e+7#53x*aV#<{LvQOx&hRuj<~RRmjw2b*(32hseB?wl=u~LTsCvm{mIwzw z%)xGR`!to*2{j@wyfy{C#3AgT5DC70;QXbP5Y&(M1$_jjgW#`iP z+g6H>L!J|$9znV#2NKk7?&qMxe=ynV0fiQw*05f{Bgf-DR@J-Au2)-2e_=R@>Q4N2 zy+0fl2+I-=HWKME+4x&fjA$f_GAaO{b7Z|kCZ9;W(z2*}_)?)?DMWI4A5TUGlat>a z0KC4CSfPXIZCOP$TvIzu62IvvRy?Z*f+==B6t%hzn>Q#V=|XK^&G*IsB#pW=Py|w@ z>{KqCL=|-eO^cl1>&RC81|kXSC+huQq59xXfXytX!wBW4>d=2dpA8<$7egA}c;Bp@ z*;E2#Dw}}uQx>5>bfABw7g&VRJm{5t9dpQs4U}c0Gy?D#JGUgg#XNekY-IYcMk9b7 zL=w681i&6vY^lJBw~Xm)PESD>gUP}Kgl`DnJ2PO~Y27nsE>C|?6xk$)ZdnSH zAMH9Z>8l!a&%_(ei2u4X5u<^=enhP>QCUIlpB8Xv(E!`+X1Ot4))(L^@uyu7?|7v8 zN(48U#JL=CtA{o}l}oa;aGM9VG;=R>fzCfIS+%;=L{dg&%Sd;XNROH#}n+_yi*h0v7)2=^!@;D75pZ~PTP=KvBae-I5{3+K=<&M6x8C3M9V3bCl z@Ce}L0=xH+wZV>$e+PEt<+K`cpC=+ZIlG2O4UWs8BMt{GHEIC9<7(UH^}^JgMLEy% zB^GyUYQw1RW#BMS>x084Dfxxq-R<7DKT*U#^DK!ikF#(^G$*2UJ!=EMBU%CWE>ehW z7tiGu0%of``o|FMrcsuDyqQQ6j*u+c>p+?l^;YvUw##d<3l(pb2^hbPW z3?Q<=+|i^cFsLxIkQ$pTma{|uIV__iIx!h`wRYdD zzOzDyeQGu3ms&YCp0xL&=kv4G52CLCMpP%#?|Q%XSsJ|7LM%il{35DjO;T!>TACRm z5ClGIN%o*SO+n@T;px?@NDsqVD;aSaH}525Dgl|#BKLWaU=3_CY{gyUB&02{nn2!a zl2%HhO`Rl2vePOjvt&Ih$_;6P;Bn6bn7vO5mGeU`$?eCp$4Odw(ecjjlKAC3HF>!O zx7+LyXBqES34l@yD(LAZj7`Y};Huq=>nr?;x7C)*>$o@i90gwM@EB-^)8g?ebxAxE zHMnw#j=`)Y>zZJ1|JWl8Yo?jb`ur1oSp75p)ib%W3}Q%d=WL|4+q%hk8ALZd^# zU)t2Fne@5NaT7r?A98_SXg6b3t$X_hm-z7gO4Q%{O;w80wa)PJZ9%z-D_nx&;gJNo z`!u8&aNEDg#mpGlzfRT>_A*S^vUdtlV&FvW#FJgwg6G34%?q|3lz1lbo@hGPhSU9% z+@a5B$1aqA&)~BI%R&!%yXr5sG&vM5Zsg9dsfH@apL|exwh5DEgwb)+U>w)(9vQA; zb*3BAH&^}Mx_n0BG&5ov$?VD}4L?l399eN@B5E?Q;=S*$|n}|IJpSFT9l;BMWq~G%*DR@AhG`^C zcHXEYtF(?#dIE0y2GR?T5D*O9RvAKyl3$6j1Sb_j(Ohrv;a*q`GkB%5B1QdWOJ1u$}%bb_GrZn zIm*F>sbzwHf&SW8R7CHS@dw!v<3la!BHtykYI!r)*|zSkIv%c;4swipPsV{gi$OhA zMHHd3qk=@!4}oClhS4OLJ;!kMkhwt7F4@!W8#qV{M0c=nv5=U;QC{Yq>T`>}m4BWP z^3g!cEmxnZM>rJYa>^pW!czBiYxF!$!1{vBdw(TAlhh63_+xv7tX4d4aht&ZGAtKk zz|-1WA(ccShX=zW_5ni1kje<^ks?(r6uj?>fsNX2*#{O0>|=$BafN+&g{Nv5K)qD}iNzxRw@SUfw46_h5;q69)DnXZ> zQ6`j$xun4VZTJOvl8XuOU7rl|pT$YwxaSG!@=r@gpt)+McIZlFnd#hA3qc#ib|G>z zj_$mN&X{}WBfK^_G&ro>5)hK10?%7DG&zrd>l& zqMG^9i8xNLK(3Dl(v9wz6;1VG7i4xkM7LsgVNK|sPPQO#Z_#77C$MnjqRlFKs)a{)AyBLC+iLFrZ%);WG^$1!AIJ-T&p@MX zY;BhkzW?Vwz*`dMasDj3xoT1u?_SL1g!W4I3%0ZUEoorhB!4F?oEfKZb(Oye|YIoLDdLj@68Ap1>E#9|z&xP`jF1F*saMEZEAM{t}G#=#u>g zI~2{X=-ADBybzK&Lv@8HH48-bp~2ZViB;znZ+P7M)1sEF@fsHxpwG-JbiYMyi} zht+EEU(01FT?J3o!fiE9)%(tBq&}_nEp=^`9n{zbF$8Y$2~~v#nK6)Mow``q(^(@E zsjX#!3W(q#779uX+0DZ<+~z@<*P>5DHrs6@@$fX_vF}^@1)UF{MM`|y?kmcTS?F?$ zj&9`EsIfAlY}vC#d{UHM!+EmHHXDN7K&K%fMDo!7O@WBwBt`pXBuFK)mm#q}XbeFrqk(7_!fKT7R&1AbRTR}7!=k6yAGo)W_}BIrQ9=yoNGSnMjNKNpib zLrBR;2j2W0`)sDd^Og&0!>QCV_&UR8&d1`0Wy`p7N6}6b$$qbJ8S|n+hzZ44(Xgme z1!==N5*l)!$qjT0g=*I_jxx|{Il$DjsyVUdMg<(MuTE^El(K$=5V6;TIajJ?pc&^G z0iN4aO1++H{$s;D)Zvlli{T_J9=aODmTOrIG3|S$8^1U8*(ID0h_zKQ!95B-G8{1)Zwd!0oQQFvJeYp%N6v|(9b?B5IT=WJ zivyK}+p8&;0bZk+ocam7L*EkxNjHeNP}(W=ZLj>B@{Vshg!t9OR(f;K_-=6agkwzUquNhj#6?G0~=&~+O!&PS3x3Rzfo z?NE`H(Q0uopmXi4mc z-_VF)^t#e33@k0gWvZ8s**#=KkHS1Jp7?c0Sw(8OAM&@U3C*OF_vP9LI7-i**qe0{ z6x;n8ctIjt1=e5ictGC~=epUSUM3QRWG4Q`T9U@=_6lvcuQPGuh~!n8e2*)cips@t zd0}u=t7(V6F*sItD|LXx4O``&Qd90qVCDE#U&1MP^$kKmXjAbKJA{zqj!<`EnNl!TPPUnF(!iTn;5psuJ1hK@#4gL73! z#^A&Z>m*kh0XigR(|i@PmbFgcfxX*f(;CwxLH~mzh_!hi0IZ~`1E=3y9KgEjx(#PZW zvK?T`^=e-?#igxXB!l;s<@jYgtU(ftb-~?vFaSFs!VMf~Ya;kkfb>9ly4}<9Hy!^X z{p{-q{i^K?U<04fRF_yPB?+>8!${a@yg@rUbn&_{^wh32RO&Zv2n>0dDa122Af%5M zV2flWH4>RVJChqUhZx#bE1dqPfd;aRpOElW1y6-#gs-*)2{VNF$Z>L#SAw&wVjiKv z4x|>J6Z<$G<5n^iM-vZ*)Xk~nFhsSbBL1c&P6elgz)KceI9}e%az|V92IB$OvGA(hyE(^WJ5^}Ojvqk&5qF7S>2|tR3R=~8Ws|` z1o9)bFlLK|_s%YG0VW$e3gc%XIh4B0g6+v3mJAO({1Z}IkBQf*^RaK~k1Ip(*3j+1 z@GQ*366`YGgOVug_IpQo>VWga06DT2p4Uj<)SgtHMYw3u3>6@D7i=sT^u*KKGIyx< z4GgJJp(w%#g*5)JPRCl?;c2GRj>me)xX)vfJF?9j(dfmNxVu$l^@to9^L2b-%z-U%tr7D#2-@SW;+GnI7lo4dFGd2M0iK}K#8}WJNlnPhq=>Rn<7^AKOU*bK(c)Q5J*AAKZFW3GWmxrNQhs+!zx5=mmHOQK>a~*oyKm2Rh50uIfK*-J+PO) zwNKFy5th6H4HNn>Yr@MBTLYlLw@UjV?bxjg{A4&pDa+%ZQbYq;25K9P8EzW*5(f9z zbxxG_vlmVOp%=IKP-`c1lzV+8#GwkBhm_=E;MueFuB9{}Q3{l>wK#w_LvzY;T)0H- z#cr`wUYkbi!Jba`XedFs^=fLhJs`t2=vl(EkDfB3IIXE5({xYyO1W$3l6jAPz#JA7 zTFZO>?8`gTz#@5kJ#}3_;rE}%cz7E=gU|sDJaxO&HKt<`Hre?Z+XY;XRzLxhzGHD_ zVsDvpSDS1!lA++P0D=T_5clr`PcgcRY3)>q5}fC`aBdjiYczLhzfewVs4Yhs0ilCm z204fn(P~_l1+%RI&NuZg+jTtno-C1~x`BD~jXVGH2lW%5FO$5i2#N{PAa>z?;*_2S z8-zFa)UmKSC|BWT*mq&ZZWH9cQE7_xAR!3}z8MEY?;5JK?$#qW2nl)FL2AhHG@!7k z^$bQADgXp@A9R*#le39o*7lub-B=mdeB2}49^n4LGX6USDII5zK+2Z~HrvSiG{bmL zG^V9rnK&-xsxdp17Tz_w%d-mFON%M7N@d50a-I>!FqksB0icyW42T;^sFG@8fnA-M z7l(aiZw5JXpgt9J5aI~KqAgcd695aTJYoojkGhtPG|8{?oVIjVxIv87r~H;ZQgs?67``OPG@!QEi* zc@Wc}bxfGgIm}}!;a#%9@-^&G%&usOAsuPQ;tH4@X4{3F^u5M#9qpkl(&bflj)_Hm4{1oX|J6DHp8DRaCvqr;n{qtMYI|u=Q77{N<1wCSh=ZIaF)s6BEuw6=-l$&^$fe;c(1W=3tIS{UD!ZRHpZ06}O zg=*?2O?W$U73rvR4_h*hB_tHVaI63q7kx45PTGup!hMwQd`5j2ZE#otrP}xYBnm(t zOs99;q7ZPThZnMMN^u01gQ1ObR*vQ;sjB>nKly%yJV*Vhmzdq$P*|F*vB1RLLqkGL z%ssaOL}wd?>Jat%mJ$H^=Cr!b)xdC<0^r;JGTFXJBQ0c^#UlR2H>Hv>9Kc4Ss-FQJ zW{0}Vnp-LzlYbpqO(kH(4XVqEoN)9m<=7}NR>N=)coWwOS}4cULmO_aH|&Ao6)CGu zUK0|o#Jg5B6Gn0?If@|dV`3fyDu<{i4$KEHZu7Ic9p;;pAbq1()-v3YbjmaK3>2iG zo^bfDDy*70X3#~^fb@LQ`xtXV?1wV+Qc9i)kP6uT zZfgf0h1m=49!z#1u7ibfxnaYKr7}t;jbBw5L+CC#LBBtf=EJJv-w70R+G2;t#l>W= z@mEEd|4=Vk8#C+0OTp@!s~g7YPAmL)z%vssf0S-=Pp;l2<9-KsrmgYUNih` zzZ}%^L+T^zwOzRpH@tb?kk}Tmy6V!hZmGnY#3XH5X?<1!`SJA%W0L4om{vi{gD>7$ z2VJ1$lmlV4)^ftG35Ro=0cF$1rfuk4+_QJ$xT>j#g&NFXz{h3GtpG<8Yp!4N8!4W{ zcb?)J{um-jo`uQT=U&M$RS@`Va1m^e`_qYE3e`F{f6N|egF^7pF0v+^&gFSe4l7fG zSA^3ku!USC-)-XlRMQ8bqZd34x=w4(F%z+Y1UQ21=DOVt^}#TNVY!L0Kql@bz*cJv zMm0rtW5)q+pPR$!;$EnnbdyWwK5e#m)8s0T9oxv7-7t4>vIfJ4d6@3_AEKZAPHt5O zlp@3LL`f(5SbY#bnTLZP{MzZ|vy<*jtotq8#CYMrfDK`5)j~t}gQ_1w6EbZ8%#`ug zcsc?#|2@U7T)ESRA8j!0oyF+?)!7zjh=aWtC3z%zRXVm}YT1fsT-HK%X#*ufUPKRd zy>xwM_m1X_EGfLH2-7x*$k7C!{hjCT^2wgqWZ1}TvqrOWR*v_MCSz_Sm;W8$>ziprDfn_e@mxDj?+kw8y4H_iA!=f#%MnI z2I#>wC!9*rFpG^E6GK0XxL5<7yP`}8m@2*mqkmid;TyG=V9nNX=+Y2x{rR*@Y+`? zUdJtb>O~>JWYK2hqYQRHS#P%6<+tRmkG!aphw@}CAWS?kLm?1a4;KI{c;shsMt}#% zgqP#1vVC)~lxVwM4qK#-|36hoG#!0)MxZV5^W3VAtXsV+zsApYb&TXv<{zTw!iEFl zhjA$s?$!YP^*U4x2$@8~%aE6p7G6hZ`2s)nxCS;Yv8*5|FBKzSeM`HkKP z2A}?QEjkMuLpsIxs)G+P^*__X!xi^y2_jzTEI|CWrXcAhkiZ{Djxw}HL~K}(=Nuq5 z=SHEOk{{cRn9+N8xVrqC_kLj&rPZXfY6;h`b~Jtt!YK* zurgF)qShkWbqu+IdM)+YeWlXCyEN!ADNj!!NTXzgM8cbq3R^D~^xk3_nZ5U8cyEd= zgG8o&nB3K0jFRA(qMUz^5T!MUmclbmoXE0}?Q1?8uk;wE6fB0y?&WY-3|8u4qm>%_ zKLZ&$Y<{_1I$kcI`~qeuz=8N=!Jr^A>MUU|$M>KIt5=d#5264e2z5K$=${TWkv)=H zqI_}D zubS>*m}<^3RJLNxzrGb>M+edL-0_$R-OUrgyduUEXsc4LvM-xX9_xj;WVb;zO#7qX zS}?EG-;%#2b%GDf2S|mjIS|!mX}4XFho6DZ%ce=$f+zfYl>fnF@E0A`1yg@qEqcMn zUn*KCAveAMd^x{Ytxp~~^e&R_dl&`2({P->Y!Z`&y(@`NEQHjA%BS~u&8=jYeD^~- zv&$XgJ9^gSx!d3{k+_^cRvg@-K{e&cRy@wVb+5ujF)dR}jx~sBZzv{Ofqi{t+y{JD z_%(p!;~$;Eu#X&^5z`A`3Ti`CSG*1AvT!8C9Mn+DuunpAv_B4x`Wqr@h{G~lQ+EWo z!fJomOVlJ0>Xt!buA8^BSd8*f^kFC!v7s3I0xyJ7n4N!4@A zhI(tiNMY9~)W+m_PP2Fx*5M!fH_2~q=AD300uX)pPVby`IT9{IWgdNkc*f6|8d5Dd ziu$96J}GxR?$mwjsEHtckz0{(wV_Nr=NvfFQ9-(sZkZ}ou|jIFiM`6 zmhFMunV~)wI|YF72$wFo7jD_cY#b-TpixC!yU}7tg$mJ0bg`cDZ$hoy>AL-cJuXwp zJwSyn&3HR1D^{smT99(peJXx}^$mf>(#{^jWI)Fs^4|Zh?C}|}pt{SMe#F-JrN835 z1d6;?1O?`=HmN&yghXfKL|1F)W6_T%EK@0X@Vu7h`EPk_$~rQQm|~yOW-X|n>qI18 z09aFrDy3h)p@8x7jZ6gQ8jR1I`N#UM`2=W4+l&B_1Pe1=3tm#1&MS>>HPjzzIJgL5 zh|Ua$_Up31{b93WSX0x}@_%+evSx|@6y|m+@c#|LS0OcpYE=;heQ+zBA{SsFVk5sW zvEQ(F&LSgpe;EN_)=^UoB%FJG;I+Ic-p(4!LtNBSI1Na8S#PlKY7hj1(0N1p3KkhJ z!~1D^*GwL(a{-m1tqV>wrG((<4q?7wbqZLh4##%KdAY~`5rGXKxXQf9m2Wo80H)QA z(p0;=3pkiX5X&4yRC=6^^cXl`if!;iUu1&V+(d-{lYD4o{l9}lu;`Fq!?a&8Z<|nX zBle~*0P)s#rlf79edJ8c*g}LU_!O*+718_a0J)IZinZYY$gWPCSq8+;yubN@%W2ag_&Y z8now!Ssjt5r1Fy?%TNGA? zLBaB`2k2K)`_m^*9S^gz?&Y2pIt$I0mh!FRK8cZH-koUI)}BKJ-^zGZm-8J{IY&KY zSb+f9mH42%r|KvCF@)wN_bohE$$TtK2e5n@ceFHMx;5AP7&7F>`bmE>6`xDY?}@8uq*3iZ zvG8N#rqT%T@~l^Nd87fAv7eJU46uc=UAF?xk%&+=ia2bv1I{63Ak{6xSrj~Mq~fsn z8>_DU_;U_Yb@K1k+gs)Aa{mp@1!>aZ@`W#0GA&oXyBssN zN=XZMKM&=+edSh-iWL?(+dbmUOveHNq}}(lGU5Op-C?$|zxF`xj`Ua6G5MsszeY+s zdKY4lJ?gk+fM~lY@zP#b@lLiBR%Q&SZN4;@{AIb7*Xc3(h-4Yq6Rw7Snl^R#+?k!r zsT#U?qrNrN_bAjBFb!02SES&s>l=DHq>7V9(8VJk$GrnZ=Fom%@^J$!&3lnBve%EW zJT9vYx9C^$Mg1=*MhP>P*NsWsAI*MOhin@Kfz=H&QARAwR>na67@MzQ(xm+lCS0$q z^#YLyI6?r?9ilT_6Kr-+Y4TkiFk~w6Vy?Mp*xT0O;`$C+cf@3>0Cqg3Sf~t~<5pHV zBt26NzEkQ0uN0ot&zVp1LOy+fo0r(m0tup$YQ-`{?lW2yf!fvLW&ShmXH`Z<_0}K% zqR;F;&j$%z69uunO>xtuH9oH-)vPz$rvP+%O$I&L zkO%YomZmLB6$c`T+{wAZmIcG}l>aFr#R#$s@Dw(%L&idGr92sb!xID0F8&I;&l{wH zcy#Cgs4L_p@n0mJeYa{iFW`}}J6!&pk%ma~`RP??PA(7;Go)ata4r0-A1@OerdDbG z(aRL)Z~PaQ|fb{C)I?fHY}i5P*9J;|W`f&g^-$;$|v7Ml}Jn7*mSU zV!GD$^N2LuE;cHK>w;cv&a{e=P|)>7d&0h2ae77Z_8d_t!dS+rAzeOHEdpLWq4I>gnVwvF^ z<1>kd1rC;L(SMvtPX27ch{w+geVhQE4hLZcD(MdckKp86`r&q34KZhSD9{j6-Wy~< zoi#Mm3$$SUeJ>uX*S<_@cUCsdf-p5HDT$S;A5!2PbI^L2Z{{+y4cwY4i7}PRf1{y6 z%YhW3C*(Wq^=1b_1pno!cN6$sxO1eLIjrdcrCt#HGqNMOIPvh=MQW6tNB6PIK@3o$y=Q zu@KvR>uEIgNrvnEg$0VUM?bt~$j)_^m>kxP_>+Y4Y}L1W^Nov*S;iGv5vuoD#hqoY zwK8gR&Cm0xvge4eaf(~36PW8^mgW_JizTP>HMJ&3;O_-qn2l+l40S}30@PXThQ&6Y zpXKF-gk_D{GeEo^FBF329pZA6&3C9PS|f5imRtjLU4LyU6MbrwZQ4t;JTak=mjT^5naJQ8VAR;07K2 ze(g3}$3>59vbD=jk>Bju$iaJM8Bir!qEMyXa10xa$IR`aMI_O3nnCVs|mP#`k8dG8Z~RmZe@m- z4{ZUunT8wve(}gf7v-Ldps=(`%+55%^O-Y44lAWyq?jQOm8{+e(<NrZg}d=B{yfFo~I_ zr010>b!TOU=lR-(FOKG6%AlCT9R)W5v4Ah+R&+n49$9bCNlSA#Z46maA~xmxEeiEL zxv_=kJDy{e@WZG={C2anY_%WtEE-S+In(5ETeYx=u!0bOtIz{UCisiJrIQ?iT*~tq zcpc{&6Rt=QnLUkLsn=&l&8?hqh2wz}42S{=I&lYoHS=A~-MyDDV(%=q!c%ym*U+(cI?|KV9FP?3R z%J@n+tW7SDDI@BPQzunm@h%X}2wz1Dc$}(xoVlN&@H@;TpgZc`HiNl*gnf3DNqAo38`I$fdN&#z#-3ZyThi2GbCQ3q{p zOI07cvTb*FJ4Z^wbWFA$ajTodt6e2pmVJeh$QzS1Yw@&fwn$6`I;6PVN#CtN*Xtd? z@B?7Z3@*avv-vXzl!W!Oe_dJc(m@llj413=dUb?vR9EmKLaA&;oEF2w`GA3LQSo_4 z@;EGcO)`wF>;x$;dS#IsQc!qq7yDQPp~|h}P&j=Pv$M5*zK5e47BS`iuixuUh{{lk zyBMEtJRMa`gng+V_`X>4__8NEEf6lvg61wmgpVsuDXp#6<$G3-du^ct4Z0^X3qPZY z8UE*iXq7?T9t}ts#30P!Rsx%fIbwt(O0qdD#19c({3qgH0JSAzIDuFk%%y>OkeaX< z+KDwRzVMZ|9s0uOST5YmyI@{&-vTvjZtroo^to6CT+KV-7)S7TBRAu5`JAjeJZjJv zZ<3?riWmGScj+G!_0qGU|D&laPx-{My?YB@^J93(7V+*%Ays%&|14+En_>k)5L`*?QzbeLrD-GWB4& z!`h(M1MC16Yyg*#f;qp!FvU=rr3L=2anH!Nv5ycV5G-2)9mtu54mir(=BdF(0{w4# zOY!h^(T=uga~u@n;`VfmLeGhl%1>N^!Vic9G3cCb4O?JuK04iGfofoR5=#V_kckp& z9YvtmR@He(?G!KD+UWhnKwy>xE4RuPafvOYmqeC$8X=(lGNVKXUE2b}U9u7^>4Wi~{nZozo$gFZW*NkovPaQ41dEY*qoWi+ z_-SZpBydg+>!<}`t;<~u0X2UmVx1O3r( zGxjcYKM*dDY85)^7yN^k;!YqU&BS3Dd8`$qNTAaj7C;9Q+{{e`_TG?ij=_1x{ZE2B zoxB8Fp2ys)wH@s3M331{PP_0;9SqCN2j|e^6qoG}Oi~dO!y;FnTZx|R@%FRGkE-N^ zUy~yctihb0(>*H;ND14yryGe^!oM?%BodR;ZG|9T z8B_IfC~ExlI|pVk%b_o!94g^x?Hv6^0(q0zS>B0L*e$T?>a)JOY_vw%S5hcsX66;4 zxt_n9ijOc`%lVBRle)Q%aF!aJX~ZxPh?yTBPlAnyBsXGiw^p3bkAu_x>I#yjl4(!K z2i;7^sqSNiP=Eo$xgsXDpDs4Yh|6BEs|b3Lqrxfz$`<$ym;S?HL=yg_X=~ujL_=U$ zQ)*?|kbvD-uguor9YDjrc8;7o{|gD){+N!!Th*gf$1_1FJ#@G7*7;7L3hO8z#-M7E z@8VG0PoRu{g~TwQEg3M~3u=U`FxO(8@O~XZd3yFd*3jhCR60vTxv&FupDF(% z@87Q1=LWI2v^3RhiX(I)M5MQ4Vy_aVM=%}+(|<)5)Mab&bDTs}7jCDmvvnpntu;D* za%yD1MDqZ;p^RP1-w9z6PnUyiDUol&zcdquUU%r>y+KFU={4yc)Li!0Mm48(DGj#f zfTUK6wg`oYi>E2(WEf<6^y=gN*CC{@4N@S3jKcdSPc;Od2|n?D6IbRrxSjYOQ$g*& z2e0}&l^u}mQ1?gcGMKB!*ih=_H1BhNq0UP86Pg-o#6sBe;?}mj^sTMGBPD~C3Tk!E z`i>tqS3e8atgGNKk5$6Fy`F=Vo6P-#IBl%(C8C_K3l2xjRN42K$Fxs+G02ME&0SS# zIv;Sw`z}pJuGNlTg6bvS^Q=<)VGX+DKPg-z8Ave1DACVFFIFD%99_z186RHccw5-m zgUMM9)oFqsj@5k4j}a#7M>~{iiEZFjWBpxEsYlqO`3w{BC1*#UOpQHuL$CdfRmZAc8=$T}IVXgmQyja+|o;KE%f^M6n0UmR!ja(V2OYhMvdiMj#p$Y=3Cl zo>zP~z$c*ah16u~bS$Uc_7}XtkC}bZSp!V~bJQF=n51BViSczzlwM4iBM{J;1XZ;= z^Gxg~5dvpC68_t~6nVJ!WOq>{|Eb@Y3t4IlmLVLvsCyN4(0aXD3K3d*_&W-E#C4Uo zna6upMBoed-`Uu(&H_JyW3zX0L2_Li@$?+Os@0tq#sIeL_O-=Y4GHN#cmGOraU53 z?OM0_^Bp@SjIJ}yewhn}>g3FD+@YdlB?K!hyB1x~vEusd?Vhvd)h0a^8NH&_`HGq7 zAuI-kMf)o#LVb(|e91DjH3J_}>davDb-m`RH~Ro8ki<^C+HxRPcDsXzT?+aN_mUt+ z;d$8{^?H~}PzedRYIEpKaC`21Xrz~ko-(zWB&mnN%$a?}X2^tjpih?#oYBpF+~gm^ zEzpVLY^R^vyI2udmG;C@}dzKt2bVQ8%u$;E7HuFU&QA!pXJs!?An05`Yn8=1Q1R@H}zzPnCp7s^kg-I#F=OeT>R9lG34%5I{EuuC$4|$loHoh z8_Wg^YoJhUNYF}vJ~qvg7H=^Q?PvvZ9(A^9r;S4W1BnW1Z^O^2PR%CeY&7coBaEBj zlGk4QWyhYmvrAAsBOtc8Zk@Bwd^0|qnhs`Ew9_6jxHWO~j2np+_0DsZ)LCMVM<%{{ z@`s#Kc~El8K9pfCw6AFQ5}1f&LY|*CBdlc$Vsf8YMa?m#ckeN>;T=gZ2>$Ps#iW=sjAvmi9Q-rI|7JgEC$Iu@Uey2--r$WHERx*F zFG8*>WZIWR zXL%uR^!#0U0M$Wblupsg5(s_zLvLqms*+S3oJUquua`ghwL=V}G=hgqerj=J4y2Ib zQ#j1NSuSF#%q)vdZKcNe9>lR21oH(q5&x@;e5wD^JGs4pNxryp&4UlEcEcz&^3mY4 z?-OF?^C$qxH!g%j{I4%eGzJON8O+d`rD=_GmelVHwcz$+yt#llc^>lfM zI{Hiw8q#NXuycpc#t&z~)x`E4a9q=F3mFD>jY7J^ywY)E5Na6B+No8ir70L5<>&R< z+d+IjHbvhJFsg$#?DKRK`pmH>n0;K-Zm67-saa5&v0o}aekD4-IaYyeGgDeCox;}O zmPyGpt`)NeB!s`r)%03twdPto3R$C}xbux26Mg94*!?BIB#ufJ{@1bUeK!5B)}1#g zjvF+&)xfOTTrov9c&f_Up%?@}Rfqr{nsIlcsAQnz=4_ptYW-GkYtp~$!dvd-!I<-x z!e*x<=^n1mOPA`7{eRwC2}m(dRr4_iENq%slh%)t$IEeR%0HSfV1*828o$u)0Wne9 z<6)vN_N+7*WwUp9VwCIxyeiuJ0{8Gt-TU@P{mZFFXiemf;$Y$^kVf ztdJ)N;sYZUno{|p5-gmwdcfV#QCqAv<*$pSFy7a}w%1;Q*xoq-ic`fqi^fy!XUg9l z>HJrS}vNir8PmY*O!xoj4!H9vyQIyzK4bz+2yXrS-(~`R%~mWXCb|sud=m6lQuem#;~6 zC`s{jD-BvMj5uY@^SlvFHD1F)(7V$L9KaI13QbKl>t{5B>CSm0OGM{*_W}Rv04e~* z2@5o;jS#;MY6&Y;@XPq~d=L{k-Qo1^vVw1OG0#MH^z~C%%UCU&`RxI!0pKS4p3g;* z2_D2^%<+RXHFZY2Y`A^y*63=1-tl?Ga{22GgW)41wJY`~A;w6!od$;yX9fEFy|Zb< zf)=kVo>MnE?K1s!oX!MHJFo_a7^QFrJnTMXN;&F+{a<_Rq62FUAauIw%1t7G1+!GvC5Uq2feEIxk`(t$q%JwKme{;)Z%Yi zeDML8hv)34WD`gi<^w1(DP;Brbc{T;ap@OQ@}A;PF47|)vwn}7T$+bEBb5$>CT0zd zr}d{^n-dAfhG+iVV-Q=PAM=S^hCDey~#qLS$CKy zA{azJ?6ZD`V`EzJfp4tGSLm@$bu%PIf@;E#4*!#?n8D*hUe?*ynWLMz2f>@qItm^R zk<6(f)7TLPgSy`)y7UpGiqaFv1V*5eDsK-romPj*Tl(kxHuG=um3JLH|KPuj_dX?L zs91TB-K39l@E~JI%;*13fgRyta$wvAq$z&Y{41O{*6CU?_iGSuJs0T91fy{HQ-&$@ z=|K#KYRu^;7Un1tj!ZYD4kFOUyu#ppG5AS2ENFqOByoRf`t z`j1qsQXijhrW(@Z*?O)E^W)T@I$4S#A3>Hpw2VEG6m3n_U(J)*0@y8U2^Nl=xP(mB zw}c`%*rxisi|#^#DH-{+vbxfKVkuI&!>tKrMdIEe)KnR}GmnHwb$N}_$&fZD+X|co zeU)Y^zBe=CWQ3Wr&80T5`KsOuv{=?Wja*t2O&Fl(=k48+t5f?7JEn@`c-5W}=e|Is zL4Oe&uZ5{7KFlXGyczHj4K_g2sUZ7rWJ~ooz%ryYc$~lq>(VJDk?g_mJ9F_i_#!b) zIE4UPy}n6%Y2QrGLu?!~$lgf0O(m~Kq(z#vBQ>M!ItQXW z*6+H%ME+<5S!wCa6AWKNnx4@JABZ6pOzDCF)kaG=5_NlBcrB?v_4;MLF3z?UdFU26tKF*$2TFZ*l0om~cy485 zRu-N;R)aY&hO2m^o3Q8_V2Hq0sFx5g44zZ;_N+S=)pC@}WV9g5 z{T$ocTWPW1eW>uzSyU&y#<4J!SId#P;BXO{7i@Sm@%F=( zCX$w%-bd>$L#E1LqNq$w()4dNL$b-c=yhx_c1${e1tNNX~E_r55G{)MT{>WHnrI9mz0AGlQuMw% zOjD$?=N}&mmS2~jtoTFG1)yZU?{c-11bn3C7npdks+UaLH{iE21}?qOU> zb4=st#|zv^rZ}j|mD}4D92Svm6Ahu^wo6z%e*dAK%+UG!F{w|S!Al?~;lZ*VptG=X z?KHJ8Q#=Hkg>Q0CMrcjm+YAt2w4YH4s8+T|9-iqTE8!lhhbzss%1NaLQAtsFYoI`^ z7ySzgs-c|>CatUanLMiocj(s}8bt#=bck40LZI{D{COFHm!-x+_1VVDC!bMxz$iGp z2U|=1ld1nenS^(X%P_8^-4{vn3T6rfe)0@-2c4JVbP)g=`vbOd_Mg|6GCNrf(HB>d zuNy@Xqpb&LK8|@-Qw7JR|q?PU6lARP$Qlq^8f5c@w z5CeLCu9$?7v-^krFk10b2^mUn6qd7#atI8y7zTOjj6VJL_U^sUqz@J}1u8qC2tE`K zb2t5rHmTkIVSqCdkwzRaJ6rNzU__9=!1uP&sUBvWj#`dj+!WGD*X|f0@j?Ci6!&|T z>Gq&CF2q%q&uIDY#03jJ&xNe$5*XJK z)1*La#M)*eDzw~2$|Fpfy;-z!FcH{4VMV(FK40r7tcAi37!d_p ze*~MWQODpW6+iC8=1Lv+df+8JhJaq%!#1!GL6Wusl94uMZ-oU@&W_Wi6hwcu;dMwA>eshujEF5Ah^PR@IN8{UtzwqX_HPJH0Jd_iAr#!)uG zBaBS&Pn_#)t*vt8cD#I2_t|k?hFozS}xx>!SB>UhAEe)YZ`S zxGF+N#HjgDtWWsaJ#U`KX$GX2i&ak1?3eYImaPFlpAN}ElPRFRtakIy7By#Srs|{5 zH8H-G#Q48m@b`k$OxQf_-DKf778QXP82v@W1FjFK^Y`Y{$#0NzwiZsz1nIdJ-nu29 z;T%8<;p1 zkM-Flg4h-2KAbWP-W^+Utzz<D^gao@%ciF_j1YVM%qDavvR+ zdo~aDPz@iG=8|)oP#;yB`Sqp$5{v=fqQ(TldyZiH3VG|zN!7IQ^sE|)Yc8;a2z1Lp zeeLDVRxCtzLGE%6c4dpolkB&l(TXH3_P_tJtgd4}&jkoaB*_Ogz&~4-;z&mE?hDCl z#D!qG+By-63rY!zN2x;TLDYlrSqJ$2uHoue8oXt`qQ+M2TMlWX)oQ(Zb*bsOfqY`F z+zDFxbwyJFMV~=8%HZUCs(WFcn z+P9$R-K|HAlnd=Z4GoO=dOKkJZ50Ci>wqnvVeQseLBg07&l+J;eO6*apGZmar(S1) z>Vp?!>lDX3Q=IyIoejfn^-dQkmaTMQ#J`k`yM$hpg9^v9_>uMrj#zGml4Xcr#-up! zFIy?>Q0(d=klxj6f_elRF7EEZ!di~wOkCvFvngK}Ztr4D+e*)OpabEu_zX4uVDt*u zvL-D3oHmBgMJSltPXVr%O^i))46JP93ijFFh0ZJzD4Hg;#rm5cZD=xN$HJRxD#Uu8 zCK>Odz09JjCa(s5gU2lmSy|PGtS!D68p@nKxh3yQEnfjVed}9ytQe%!ndDs&y0;s2(ad$lQ zxUjU2KGNQ^3#KA#bA@#ca+d2{*ap)?3^^7IOfIw?Xsvwe;psQl9zsavKUK@f)Lr`) zC)aV)@p>dn7sDQw?wMOhFIHUC=>1@6L+*h9qPuI4q!2ziI&2zI;^3N&^KUyCB7=Ub zqTgzr<3CbZ zc@dG{jS$p0e~|Kw)&NG|DPZoqF~1KAe{zM`XF5V)EKEu(@~i|;}v+UFxQvlGUIL{XZyHS#MQIGQ_Iy`DFq$;u!jvcdI*4!!9&M< zD(rMoV{`%oQ!>E@o+|iS_XSB1h6jl3Q|{|0rdjQ#X65miIJri?;}+K*e>(p*3|M$G zLp&u{DtOx6b%iM}HdX`aJes5dc2VN`$a$`eMg2CmfdeBmC`-dSXeKPqQKTxDMu72v z$J|NE2x7s6a0_t{yE*5~gqny7^Tp5JxSb+$?+77=$Gx0I7fh_R351Uv(@P=weH?oN zpj@@d&}G3kaye%^W^4sc$p03c_bD*_2UhjXL)%k#nBEQ&Decm1{W(eZwP+3M5E?4% ztr7jXBD8B#Gw};QO@~^q36zL0P9IDq*x0anP%j|u6#F!n%B-sn-m4m|3 zk8M22z%TV+_-F(%9#}>KM`Ej5aB?%tzoN6INwv~}3D~PqPNMkoPBYL1C*V%4MKC|u zYN&}mRjpsUpqHdP%Q}?x8^k9U%vtyreHpzC!L8~)7kfLZbYPEOlT!(4W3F<}4{?E& zkV>>R7(Ox^oeC-c9C$SIxq73AEI{LJ;g~{#yv;Nm55S@ny*ydL^w;{GoC*D9D83SN z+3=|_1`JBiu})p1yn~=w$P*8fa4<>#^H7WSP$S6@Bcmt_OD{X~BPLRo zGe>QYVlfvH3eHU2N;e zy|TawLVtnc^RfyjILsjGkmx22+l#%F18b36;y>z~*2$lopkiYt946h&hTO~rh0>D! zP;2$i$-;^zHON2Zvi1XXpd3q0Ja^$KAor>o6C45*VEKX1iiHJzenNveKX4G`XgVGX z{^~>~c`U9848V?xGeaWO%BNc_0;i9*4e>wq9OItu3fuFlw@T}V1rp^}ZlbXs(5}2z zn$Icooj)Bay6CW8hGYAGgkw2W8~^z9zSRpu&ruQPbRqOF zZYOntXP82x8!b&o!rQT{=fi@hv%kH)69!feP*y~UFa4{4P9+2+f}7%!T) zs3&bB;Ija=(XKOUu9mPj8z@wu@hO^gYfmEwH=hji|W{-ylDcV}lPsJR4F{Z*qgh<{pwE?<;&t7evt zn>+Q!JA2Ib0$-%crPIBNIt;n&D77F~!k&KF+w0%7*0W3-eKlS>wen-G>a=H&;vtAW zT^ikMJZK4V9L*Oz2j3`Ch3}Qp@*3^d(_RVlJnD^L@^OflrbL&oi>P}p^39bQ#NNvw z`|WkhS-6BL6HXOh^`VD-x^%T8P_y;Y0HbT8U zU}K_+jb`Lw=jfT#<(y`JeHdY*OM9FFuJ~NSqrdCCgDMLuuhM(-Q zk6zQf$&IC-4v|NAqfNIbDVTE%C>CgM+7g&bqM{F+6(Ng$;#-uqoZzTg*7ub;*nq-x!)|pg<4`_&8?_lXlg}r;@k{@iuRwx8-w29$ zEJ8SNG4Kcg*ts)~le18oU5?bh!!j(R^*Buj48xkk+iL2NMU8KCkPXXs{`g`oSJVrl z(ic)X2IyDo^|o8Dd;^=H&paD2`5E%8%m3@9Y3?1g;h_T zd?PaExorbEJxIqTM&g3A=PynK_ON>@j9l8x?oa%s_C?TSu&(_C+*lcqehteQ_wxCl zWhd7`3#(9v1+@6@^gk{H=b9BPw)Zh`EOrK;#VoAq@`LxDnC9Vv1h5Vf1eJ!jYvm#m zb=f$3|H4q1l(q(BXclu<;=TroVATOMnFHK3tx%^MG`RJwesQ9X-8nE`nkzi&web$y z*G~!ze;?{aKyxI>5Xg3i$wbsU&@z9vUk0J98yF+yViaO#9)@#+Bwx>V7vSeac9Yn=IKD}ai`@c+& zjhYLMRBdxEtAU@8@fo#N$=Z)E#%VXn?hD4WrjsgcTXC>dGNmrapF4@qq-*K`Fk(EX zg!R9nc*#t7tl6bj{iYRoe3<$tClCg~BAt@YhfxOkLYfpRjz|g#b=pw}3~M2>_#BJt zp{Y|6jSPeWS7$A5U{p#!!5#t5Wxj&!aZd>JGpp(23;UoGEzaqdcVHIgQ7en5Yj(6T zlzmLD;?};bI>7U-mhwh+jO!0I& zR7Lk@rIOzAQwAclIBNUfyJy58O>D0Sr&YTod}f%9zj~O}{$O5{lTYzrKkipCMvoRZ z=F}VM$m8i+-gu9>BG9kE&+a70UJ{XU1J@?y9UVKCoB3F1(3qP9#Oy_zS`40u=F_CS zALde5d;85!YSE;WHpOC|OAbDO4sGS3M3brVEI*4l|${Xshk)Oh3Cqgi0EAW&}?*_TuSnNhfD50eE-jEdP|DqFkrMdEKN zC5&b<>v2e+JJe6X?gz%jU!JV-*vER%TXH{e7fKA{^4fCQNX$6!flc;*OLkM5+Oxe9 zFXr4)lXvw=BB=hwc@y93@j6_3d&+-%c$xqEq79>?HM1dh!f^F`h(HRF)`b zF}r*z7ac+A*vB|Y?p>7i3tNtANg$f*b9H8l?m@Fv&Cb< zXDie@hfIt`2*J;nl9D}dmgqCWo<)B)jJWkv8{v3e zrIXJWs+y6fPJi0JP_If|DGhXRA~msbX9Ly57W$+C<5q(>B4<)XQ`>Tls>zJ$G2QEN zS7zzY&r)7z?qA(NwCOQK3AT+V4M6UFkU;-A{JkoRWaCOBy>u=dziiJNr ztkt!X)X0Pgvne+RJeGaznNa+cYWEM?oU+xRJ_G#hy_7o+s!Of30>=*%PqBx~xj_7( z)K<-a0$=38OB}2(<5@VxAuD=9-F96utP+#b$phK6;D5@puBWJTdBd0As{QAyW_XGe z;Z9_9#Q8k6dT1dToeX`!eQ-8Z9<}ty44RY8Q(Sioqv#aqKqko7A3wn?vyL;b_k=V6 z$uKX=R2N>~Sm=tFom$2_c%=y=2DqyBhJxe7+aC>d-Fo;zt*3T}#GL$2LU!dYpQ()e z*Sz1zuIfD}T~Jwk%3d<{nP)n@zhj7cQ34gy4Dl!x{6=YTCl~k_ZpJs@#^kscet7Jf zQmf7RQg|O%#~DP<5D-_HMiVI>EDmcmiT!o8Fj8}iawaME9GwXe&3xA$Nh+-cyLdhL zrTyg;Eib%5OJGZcD0}Ja)%q@kw*WM}&6tXQLOh^fo4xpbWW>St$ zO*S`$WOIkD$0VAD`h^kjT$$*{Q!2ofl07w9HD)fExavTEU4f|m=(aHXqt=Q_En=cE ztHpyQ?#wbo1-xrdviY#1c>A7p2?!RoN==;H7uQ-2vwWZC;8F1T6e>eWM&+?`h<#Hs zPW=)gl+pzA3c8LvkS#;ol~#Y@X~3_!9D@8?a(*EI^;O-csxJzM^HZBuVegAI*aRFe z)1^hrjDtFGh?WnEiSi(JFrUkOp_#DAUWIg{qB{h!dpY5K6&%V!>&jDa;kUES_SPiN zxzkxu!X##}JMYyBBJXF=bi~7TWx@%$0+nFL)kq+eXz8F=jcmri5HjJtZoX=-Qj6|l zo%SdzU&`Kf+(tmhqXiu2(~}5`CmRLbTjrHatWe82x;oCcGjf+etfk|*a>Up^?XU$p z(cGxa05jUUBRZ4XG_~(q`7|F|)7B7i$aqeBk8qOro}(8!Q^1uoH4?BX;sw>HzgBju z!7&#t-bKWs-ia*qrfP2Zb$+6~lKbgd;y?OMm1_ZoTooLx3C+IhyfhNi-~Sg^VdytLc;!jqyT!6cG@x0hnf z^sW3B$v%-NOYxvkk;Ue~99iF2ij@rwGO5o>dZAmUM9m@wXU->5Kdl;i9gCz^uhv5X zM${E|CO4wuSO3K2O9kxAdF$|2alHp!l4UmIZ6e-hDuYL;17=oFwU>keh|AjXZvmaQX~v1$0h4IIs-PyA{v>Em%z-@zDotViq08d z2nf1(r;vhIZu_;sw9g3G^INz$|E(yauF-&2FP_&(<@7+))Y^!^4kH*b0G0N6?{n4B z+7AILb2nBb;a^Qn-dXu^k)*i70*|4(NV|2cY(95nxuD-u77ul}TSXaZKrf@>Y&Re* zoGMCcP-;N|R=7O15B|=;jB|s*uFUYKwED`5w-DcjMv#2jO;MOhp0k%AYt8ti(FzgV z-9VVhV_HL>lKZaLFR46a#gKZ`6%r8qO!^R|W_;p(xb1Y#O$fUmS3+LA2|mtusa@>_ z3ZKWEcPzqnNumWb50H*6a+is_AK;2klboUM)UB1Ky`Wy)0@3o9JoDEJe+V1Dc~0_V zUy%Oc5!hF&kcYM2Q_b>_qfG+w6k{5!E&No=?8$y%V7oNlW{tjsHO61EM|}zcD>-ZBt6|x|#$VD`r#$|wIEOhf& zs=v~=*;9x6+XDt{>kq*I$iFpXVY>L#Mm4fhcQRu2z!1TAyA_&6INz&*t)H0qzcByprGPx zSDx=Bhs=5r#z|95@dm`;DCh}@vN#N1H0W(3rGgJ|! zPwAYLMX{G52nswm9^Nq-p@@dKsNnP@AaAmZ8*S(HtFrZXXbM4Fj0CB( zpy`NLh^ac>R+;*qt9Ax!$n``-W8cj!@xA;UxuMmNy-1K=x>XLyB_$o@0$D;dT%SEcDB5Gobgr7 zoI)+S>Dnzjoi>s^71PZKfS|dC<)sKYhG$Y0RYqF$@D#wX$XX=LMZz;JGJOmtRXa(Q zBi7v`ICUX-il={{e}i1%SCZ$+l3)&CzsDC@4^@mtu$VE~TTAkWN^XNK@zh&Vi#toX zt4J_L%!KbTQ;Cn<7v4$xH&!K(tK6e2DK$=-#C*YtiWP_}l*0eEnB^Pq+-xvzyMr)^ zb1X{`q^E>l(c_m)=AXpGbOtk!MnT_Q#}NCf-ZRlAHvIg;UEwxm43B2{ga{;;;byY7 zNV*kS|J;^a<7;@XxPjsXT}X{yb$kvJ$H8AjRB%Wc6J#sTk3zt_m-vu>DY@qJniSfx zD0I5ff=4(W?$a1ON)>^(ZFya;ix?LZOA;V58$f&t5hdbe^bCLfVi(dDKDtc<}+!fm*%K zpPN3Q!#Rvx!9|83&~=5dNQ-dYsA9`$@x>tk5#YEfXB?UYKS^PAP#~y-SbQF4$L}rH zfgBe%h4{6Pi|Rd&xv77u`9`7H4LFVU5JT$uP#4+}8zNB{7=tMHsv-#wpEN9ZQHE*< zgT(1qWEwy)%nKRgblpkpH1UK}c;3&jmAVU5@72vEeQ8TR6{BUeejhE~ZDM1+^xAKK zc)L=JHfXlF%D=L0X2S`&Ra|~Ynd+yZnO@%Y_kqQ)>8R%2+qF)_w4tosqhoSraPsL7 zR&iMoI^jgkqpW^6`!tV2hYoYSJ{N&_-YZ?caX5-VYW0iCmcq8x)!{`Oh?WXO>+*E9 z@*TXb8F!^%$qqyL{s#F}%vEDR9rU0_%SCjw)9xpKNcPCjZ@Y3NKswVP0rf^i*>GC= zsT0N#w?tx{^MiwFczOP=tjr}Mw>KEEX@68gp*T&zv?3MZm;3Js+(J=$I6PF06%N7n z0)Q56BM%l51{I^!{B|_EI`Tyso#oSit)^7B<~2`}*Y_VU&ow7In4k(EEtL zOB-BqvgYe?|Eq$NWPmzZ;o)78@w>Caux(|9#|sesl2Ij{fmLx#!$umYNGFU0?o!FWNyN4j%I^?%0$rXUu1N%bsb2EG z?!TFp+otdHp~aU%MLR>S+WH8<8B5Ie$aUBM`POnmOs~0a!TLSUKUP#gOF3X;S$M>j zKd8C40^6P;6xaIF0xa04SC~GrhJT~b1{|?VP&qN7!-fN4@oi6x0H_vS=o^lM!ol9l z2OQ}A8gP>vUxpXlogX3$SYk(RhPrO07fTLsxfj5|mP}Aggs<*}>EzZ|&!&xlOjxLB zs&@a7^dAs>?0G>h%7bnO;MThFd$=ta)dqg6&s{rUBXg33N9Z| z#eu`CQDuTbk+d9R{%yr4VRzJ$&_8ATi@;8j1Zca;l4;DG>+v+T1 zgjQJG0R=wFGNWvy@s<^!3|KMV2)Y$cX-~Mag(_DT*=@o90dC8e2bO#phl{NyeQN$} z=I&Ix_ieBKGxz~uQFkjLxhhh6)QNjp+pzTKEGdnTbfBO{mcU3T8iY&4v=emuSw;wZ zDO%T)jjg6CJpKERMc$iOh_zeTIm9{3W<_78NWLiIAdica7#EE!gc#k&XA(bFT7Bv* zTN|ukPh?){rmI4yutzh<*I4)nz$nVp7jz61&Pf{~gDEt7v-6l|PRTwC0q@|rL za~16tbhb9YeMNWNLKX0H&PU!(zS402xhsFNh8;o9VXc3_2jV6Zv6$Oik*Y1!7{7B0 zqY)3}Pg8tkrv(q)E5IX@proXK?a{~G*_3Vz-Hqz@Vw_Np>bm)Qe$E6nu0yMQ^ zi{-sE%D1Dm>S%qW%7~M;X#CbqlI2CP zC>-hBd~)F^;0?&Qluj=36^@6HG0G_Wg8OEl&{!njIcNOO(6!iK4J&9;UK$G>$mH}R z>vE$oeh*Gkp@~F|Pc7hIIbncos=l25;3Fg1hmeim2S7Qq%#-k*`*{SMpb%3?D@3`S zEZ*bNEG~5u%>PjCKLmpYun=%KMDDov? zX{L&JNT{akv-?J3?)7ajTbGc+0cUOeIh!!4oav7Q$x>llL@I9jY zQY8tREf($Ev(=Ey{*M`i6-_^bHJQ2<3TMDE^pD8XhBU5rG2~ zf+AuDtM9?rnS;Igb>uq}%W(p;mh z)kBqO&jByfRE#Dy`z7zng-ZO7Px;k5F>$K4!xdR!69s~P0oVB3ZxF1xgNY=le0Wj_ z_Q_I6w@~=6QidsQ!+WFA@W^fc#;ub0hn|7PBcwuq3$`nY-AezL<0sLn)dEUqDnJOT zg~Jdm_`Fky@&JxTXmui$FR4!-=VBEO*+#jnv79p~j-l_ASf#e*+W82I7OLV~7x8%; zyH17IU1rvv@pw!ESR8Nz;~?ynf`F%5G95^ir?GXZF?isKA8A518R)h;%gYxlG(~2Z zda(VGul2dF$(7`doVb#7Ki?HM;LEyD1$Vd~VBx(!nS4J#OBj~e*;wqn3R#qi5rpj~zq7Ylh;5Dfxr`!Q^0(P{&2 zSi~OHR3ukZ(L?}ndBgY=I7Yw2%NIh%&saGP=i{I&F{vJTa^GelqwQkFTS(0p-|!5* zD@eh1==M66J}~0I$Ezg3|Avm|Nw>GBLqiI@N!&mw*KJ;}vq?2+*LdlE0X@ z_QW~9!H*Ad30t;Su@VZS;Kh~29DViT?~15b^q%aZxkAzsywvdT1XB0!b?Dn^fp8Dv zqu!Poo5SY7G_uI8smlSnQtF3~fUpCN(VM+s(|+``^s;=xqQBjx~i*wsXzPCsC=_`$( ztL67P$5qzF-IQ|L07U;%DGrSeg#!T1cExMsYd3hu3nvL#M))P+=3Dv zs^*UEjI+A8*F|P8Ri0IFc&Nk21Va_lfjJA5I2L@c^CZ}k6OM47KW!>h^uNob@cUFR zwC=)mjoDp-U!2WP0WEtx)Qbr584jV+A(R`ZgY(f3;Jex2({(G6o-5YY=41; zhTN=3MvD!E)Jh3f5uP}2SJ0udp2xdB0#8IiCv>Y}u9SXc^rQ7KXt0d? z2q_FX$N@DJ^@>06wePOrf#0Cjk0dkVMZP<5))R=@3kXGE^$E&{6}R{*;@Gw%605xG zb8}=f04qS$zgX(AR&t(>x4S}O+q7P$W|G|gd^}>={ACq=y56J!i8y;DXw3-sBR=G; zfdA=Djx{(xmYJ@}+{rJdsb5HV;{FlF}bNUm@i}|J%@xMH6)PSg-;ki++&b4%uwpIh_|c6ZCbTu zRsp>g6L{3?Dt=P*vBamn0(?llwwde`Bz(gk@ur$!_*1quQ%!e5$4z&oc$+zkfukz@ zSmkDX`|GL%_J1w=6-J)VL$s7{$ptcrnS6A~-v4(% zWc-As2d!`nZpIJMm_uEn8%JU~VSaHj-WqL@{BSgi_OtDV*24T*W!sx9S9^HX*~zp$Hnq88-_7%NmuRPhn9a0R|N2S5p2gdH`yNfjXv=WjXad&`Cry%) zo-TDgC;zK=MRBM2;D+16js!GE5D3cs@dP?hY74iehXgY1ut1AsVT$8S_;^Xph!r-U||H8H4*g65=#)KbTz?Mp+s{fykfay5KGY88`}CNv@C8 z`+A#0Cyic&k`of5L4ba-ka@t9ZAF)?+$d(o$DI!pdm214oC#^-;;Z7&e@M4aIappP zMM8BB@~ZWSqJj)m{wUsC(^ci86+3(~I#u{x=K04sX)WeWrf`3sE0JSov|_hx1t$jE z2$|zz4@aX0{8xE!#WrsB%9H9+RBF)?Z_C~=C3Ax7{D@2VN83pG*cUxjV_CXmjr+$E z0alAK!@F#sX>Te&E{QZ6hR{Z82(J|p0DIz3DU@dpUNA<*kJOF!m0Lkzt-wlXQ(4U7?;%&3-S3EM6 zc1z?1771gzBcCm~7L7NTPie%XU?;co`z+ssEF*FI=lF(Nbp~Erps`8F@vZ2Hh)ZgGD_WqW`|HM8p053*|HX<@zpD_Fe9_o#vovdFcOPly}(&xk+j6z_Cpp-`M#lA)Tfs)lHaSTdKzDl zaxK3`%akdK-06Zy6bq`Bc5-_E&tQZT6ah=cgh%O`q+n6FThP&%%K0+0kFMy=1i_0q zrYNRIbrh~UO1V|vGmAtQuBa*NZ%_8D%H7aui`t6)BWSi0hpdaD>)dfcpkV>7xpIq| z`scaSHuTMW4~{UjkjwgeOGIh&498vQJ$>e0V$PoTnF2gWscCzp=E)M>mjBiethhVE zQ5FI4dOjtl5_nn|B*gN99Rg7!LjH}-BBwueho+GwcmdnEUUc?ei3=Oj6n%O<%1-wA z@Z%^41dMWa4h}spA77hJbU38<5E;$kQvz=S!YAn(i^&w6eISTptYP)e5n_GI*01FR zBdXW>1~vL{d+)K7em{5u>=xr}+_TtM|MY-q7Gj$bv~1HvzzYY!!yfS}fulk`vft35X{A`-#wd0(nmtA`I#AS-}v_WO8jNa)NIh>ibIt*;K-mk%vqTRakbS- zSN&ne2t@O7*QCwbuam(Hp|Kd>%jigoT;6MRd}ZFMk%Lj)5#?@Hkn2HxI3z5PNJ6F2 zGq5}c46qgr+>V`V!X{vKZi`@v$Nsdq@z;BJQ`BwJoJw5}-k0BMXa(KP`7|*|*lQh{ z5@9D|uE)h}Yao6O`pz^(AQBDlf{RotctCEWk=_ubHYBu?u+Ty`ajmJ%b$6wCnK0n+ z7xba`O5D>20|&=b{z_-sq6j_ zxIOk?{KZCmPS*S8_FoI%!@h6{-g#$E-`dSQ$|&t@-b;q)cNu_P$gv&&*aJ5un6qMx z>bmZM6po>}FsiUCZBZL;$%kaEEEbk?sh@H4d5+;qrtjUOc-;+@0N&a1FpnXk!_ z?%z5lFu#1CX%YxV?>YHLZS^s^u6W`8bv&{!zUKI%9nR&4c}k zogLIU?0;)fEQC*xDhLHFv<{s#PVdmyn=+)J%$7*8=MjWNzJ(~tSLj$i97VG=-EEP6 zw(8!-msrnu$SI#`xX}8*(eqwBo!j1=PcqJ)I*mj<n};)O;_!cnS*Ae(tjt9 zCk$VBEbPnVt^$yt^bC}Jf5l1F8I0wmpwP~GOqIhNQ)y_TU?S;^sz-iUQ%d3ELWBpE zJ_EEW4?BG>$!wb`@CktP3ll2n7$7w&h3(StN0MmAV?kx~udGIR8v9t1ZV0A4p;7OZ zZ!A+Y|3&Xeg~HUNXJedzqzySua?ms=@;DJ0W^&w?#r5JyByjUJ~n~yuk{SF zD${Xc>tC~&6Xpoa%j|A{e^Okq#Jc9bDQ)eXua3kSBVe0Q5zTXh6NAcmt}XxXfR=ix zDg^b5Gs!5$ne~W3D2^`68kHl}SZmOO(uUmiL{;09qf!bbxUERaH!5^1<%v>=hF}lf zDq$h8>fiOT)wNCpIg%D~?-|+0bP#xFOq(R?1a1cE4qaxAUrTL+2fY+-=qKx1;c4tz zA)-NCxXK)f^$EFRhF5QOys0OL>WBhYkZQH#s}ZTnf1T zo!&*So^xP;SY*~24#T_hK&m3EKc!hw~?Mn%S2PZLJ7z;1K{tD)_j|B7~> zYr~P{jssrH!20gI^~v5xguQhyQwAgSSXy?--SW$RhzMxNv9_s4RUVqCBYK4)8@lnyeIGj3y-6 zjLqR!`e-YKg*$CTMLuZwq2-P_ci*md!Oz(`fOa2i5=p9CJ`Y3rj>O~rT&8x?F|E+z zl+{5|(WdvL;Wd8oBs#Vp_`dcxa0rhMHFn#&5ZI^0sHD{@!S6SiHDXH#X1a}ouY{u1 z#?*Cs!|(-5S8S+t5lJyqh>eC!kqQ8L6^($_Z~g{f;tYOD`0i_hV%g4(-woo|s^&9W z@%6^;EXCfeo}W9CxPt4bU%*Em)PO>)QMGsD8hZp66{@(+MoCgpf+4AOf=`uxnqDho znx)eqx7^&38vDxIGqNOyWoIEk#iDkaH|wuZ{Vt(2pET%f>_ zb#c+B{#V6RE4jx0S~y11uu+<%xCkfD$uOKL5fS1zWC&(#Mg5=gG-dFjCrHUSzQ9Ct zYXSV|__;MXx*wqaqC#9#E4OBcd-LreET=D5#;NV#43;c8d>! zUhx|OL*s&oB15zk$!rg@c%V_~BO)-RaTBW|O;}b-F-Mr_S8xPJykc=yjjJ>H zhvtFYs~syO#TI_*;X+pO>CAi7Zz$>i&DVyfTSk}AFCr=|7nnf3I0b_R)Ci4SKFN!S zY;eLRd&^5X48;)-r+tQlJqD+9^qkplJHpvBe<0xo1=JL|#qdpUpZUdYS<%#3g6biydQ19m!^}Dg%>)YE+5hG+IKpBOEuRb)y-W4BS5TJ|i zMbKjwmfknfe3qC1e=N1LJqSeq(k5>N$%oL08*&8?L?Y9 z{HS-3!RQ0(%i;7%hQ9IOzSn^uJ5aDNcRh)YRN{x^8%g?T*cf+|x)ENMu%L{-Dc$wA zV_2dM%B)+34Rg7KfW(+Wml+(fvb5Y*SLm97h+zO6Ogej<22$&^7%cRwacaD=X5y}h z5S=a)87xKY-+*99DsX9PZPd~+-xm*+xj(DR!~}jZ#Gk$ZLQN5_!PMUFEVXl3pLgN8 zN4A%NtRCjJWuhrpn(}*}{N>g>vy6WWHYH#T-wCm92uuErD2nK_m3NM#u>FSr z)0SmC3mfcv+qrjeXilRA7;|o9+O>yg$3q(Hc?O*h&WFg9G8lMMOllYLNQpb+(LeKv zoHbwGd;Vkx^eu7bz!^3OQMYVgmWBOAk*X&6X|M+(K3_{Hqc_+3_ci<3Dx}Gq^9I`# zvV=q9MQ|%>Om?(*AtIwg8mz-hGeCBZ93t^|CQD{~!?}*NN1jc{RV8yKyY+ZVd`)9f z(S$VQ^B^ykQodBkxXQkseH2XH+!jv?j`g?i-8~{?40Rmq&tKjpH%kmWpGH>??}X`+ zE1(>3AJ0tz@Dga5BSuiI!^npkx+Gby83E#n@hL7aew_&IX4_lC?A1b&CA4;ExsO-% zs__1C!C0^PLq3Y(O-Lazln#I31l1`TrN+Yz>CYv|>G+L4h@cN*Vp&X~d#+uOVjQI{ zN-eb5Tkk_^30ASvn>N|5-}BQ-zrIo08&zePbVpH_8OK((MRv8>|1azkr0m<1#vPSA zufw@fSZVNrj8vA&zESyv#1R@>cow)(po&a|hbrd$y$ua@WbJi5E4Vt*Hny5Guyf8j zl*yyuFnh2rS?{7=03`y>eD^7*F1fC_c}Qiar{IsKSklIx^aoGCT@KNFbZ!G_L4cJe zKG!ZDeX|Ul2R}~;C^I)iYYbZ9W{!Y}0p~Aa53PwEHII`h4{S}vNxE{Mm^BU^9>ptv zMW`e#K$dO#j8$h+TI2Fofw1xpgaRJyU9i;$lVgeR`8y5bew0PvEnj4%)vVU1T4F+q})ZUbPX`9wK7sLC7BuE?!alI!um5?%O#^U;y^hYl(0CUj(Q(vvLn`C^Mt-s4;ia~FfBzv0f#|7>J zr(9*t;@&pvKVw(W)970w`KUB6TvOL0#eoR+M3Sg+E7phGQAxCijV7JbV?ETTHr|Dy zCVQi~>yV~Yo0PR0u2_n=g>a%XDovgz;~iG2g6yIan>&WI#P+8`FC zG;hcD&zkdVC$~hnhZi8jeN7l68tdho0@=R z)^Ai~`K#ZU5^yBjNx5liZY9!{jdL(k2|ztn1-^-948jnc^!R5-eL&d|0ZA+$mnKiO zBAK$XqA1RFGxAfWsj@qYj<-ti0z5uwN%I9fzuE3^O3SI4QiWcn-#Sgg^|5mQMVL_r zo;Gf`NqHrFq<(8cH~|*wXC_v=5)su6@vCb&rUGaZA#v)EK~p*EGy9+K{4xP0)K@T7 z>_Gxd>TnFx;k3IJc&kzelsVCRk6z5VXSV=TSnk3zA(_=sCdsbb*-ThQtyEE7@kx@f z??}MqfnbDDrZiym@-rMIrp_mHX78jeJOOLiVJ%s|tOqOu7?W{$#z~?F;2rxSzq^-6 zp1k*_Pc-sH#!$Xt3Ot{J`w0x3918N~RH@EuHlhqL=)Pn>cOniVzX>-x)`sJ4jSmp_ z6SO~ci6yWX8H;0=9BFBUF~|7izr^>mD;Ad^_~nJlGBY2ZtBiw`P-*Eo%SI8CnrLeA z(Pwa$LwE)DUr1(CXI7fygIrJ|)(_5B9x|!`hRPb0HGB zC<1kg6j5L?Po7lkn5AnuwkDChkwbz&?aA#yXw};|)JIkcuaKzmHX#URbM5gXcUpy| z>9-n2FzmJqmPJbB|5(nb!MB|xdBgtR)#UGLwo0THE!eXaDc&q;s5?WTQST_~KcEt( zI()fvZeuY)0WRAhoK@KCk|WyuwQ67goTe{%I8NGrtNRB^9kgSoKFBTkCwlj5W#E`d9fClN@#UC|W*7~0L0e(* zDZhd*eIFF8UP?m?)XfHGgu3am%1wX5@sCwMOYwY!uGqxo#kwBo!0E}tEO)d!LDfM7 zWk<0%P*=9!wG+Cq%pk56he`sxk{4MzESZB-4H`8y{BwVl=$j?e%atGtm_BrF(LUzJ zK4W%uN(wA$VCF?z4#~kC&ieXZ8>fUC%9wdX-$$rC zF8*OCxh4c7G=M~fn{e6P8d*-5r^{Bw=0UtKR!kvWEED8L+L>UjR>|Efkl4ZHXn;w}cCNcK-5G8ePMS*;*wc$^S=u_O2XSS`(i^&&Y_$#xE zJ>8_NxqJ0C0wB_jZ}ST?6bTzoXn6>e5Dg&Z)w?%0H}1o>aez>Ju#xb;cmds&w79~X z^o=Po#oL2IoJ8suJ4GdZbccg1=s0@6RM@vs;y22(+SZuXO40Z(LV*Jjn5#XPN(3_J z>900%{7WWA-~X6S&%JuIETNgP`bb=MTmU;Y`A_Z6=c-=i7;7_x@cOEGzv{z1Z#4<~cKnsJI+PF8&lMJ}-5@*7-b z(59|A20fcpRN$AnTI#7iP|6>%r)Irm>f3VA7(d{;%`O;lj&pX-Z^wJM<0O8vr)~mn zP=!GQ!sKmiIZ-jeLM;+#%6iOuOdG3q#HA(8P|$xqAElCpBU zJ{5!kCBj^VJ7Y-zV9+Qumha$-r?+s3$FqUT^gj3;XzI~Z3QI zdy=SGRfxcDOszUu1(o`9!E+~8IFzO0J59yoaoq2&<)uiQ(NFSJs==6Tsr9a%$p07> zawMB6dJ?6?AVyh0vv&ilkR3;8^jBVK)Q)wT#jJ`iiFFf@40CDPJyqgs`JBH=cC!3K zHP7VgkWmbgEg3)6I3u*~Q}<=|_SL!c8CPhI^aF)Bss`N%ZprQ%5=IKb`lo=5Q-0RY z0wqm>m^Qb~2eo7r%4b}TVz`xpt#bbg%M`jn^^P&5drV^|`H!!d>p zPz$j0d*6kZS>#Ex+D*>|`L%2PW2>2U^~zhw*2ei&jlMxz(~*q@z)$gKLpH|0v70nR zn-+x}%teW;&5_jcd0y4)7|#%oNsUOch-f@o*R5(wFnNUQ$I+QUZzMJ8md)jkbK3>=5QfOeAu{p^A#TFDv|2g^G z5t3{R_q*5*3B(S0ZlgSX5asl%$&We|y2p1RynwOH=N1wc8#^)#4rUi#k2ra?K%z*c zevre3*V$NB-rlelVZ)DZh(6D-ER5bPY0}TXy=v30l|{u-P9N z`XDBh$tS+vF(VW5uuI;?eCRfwOubZls9H0t;kHVkCo_z%dSr)NWF4PAjl^e7 zvA6>W5$L_B9lBgh{-9aom9MCawLiY_KvJ~kXUxUdLL`MEG!y1p3U@MlQT3gWMJ*7x zCjfSdDy%YNM$AiQR?yqPqsCK3#OEn;r5St*qY!dgn>jfOISjN5ykyItz`RH!x`@})0=WKN`{U&lYhfP z1XxJ3ENExe4W9HH3EGj&OY#)3Jx?T40=~Xd!3kSmYZ>Bi{Efz;3Dt-hNwo!1>-pCB z8l#_Yv5IqCzmVd@Rm46ja9NHg55;&d4{$yezNB^MeNEVb7g{ZsW205=C6bCR_ns|6 zIcIm_H}rixRhj9Lr<%nn^Dq)RtfPvov~;TefkwW_JR*7*v0c`DsfvDgV@Qx6&A+F9 zpK2|w1AM#oRt9tJxbjTsJpqeoq~C{(G^WTUHw@rAM45r+1okC)AqarLySpKh526_x zL$fOBjH1eSUxbqdTcx%bjo^RfS3Sppuy;zAfK>&yTSQ!T$cwzmOdDaV`u~Eivo>{; zryUG!-KBbqYlCXu$V5s9JLp($vyu#G7Zgv#|K?+gdI;CW_Xj%Zx_w#z`t=~i6ctq; zEgsjJnZ>GIg2UAr-aw((_x@f~2%Fm51#FQJw{88y+0DwZOTA&_x2*?ahAlq2b zySeHi(WX>{YJYDPFEAfbC*UGeWTH}%;Ehho#}Rv& zsoYsJA&8$mM%K6-5#!ZZ^LZT9+qjLp)v=2$RDlvqRNN|)Bj!O?^^e%vqPdmqrBgxv z(OUF^w46b7sScs6?1-Oqf#Z_wXQ=y$b7%v11bRG7SI?~av-MeeAJD~<-gPRG-C#=ZZGmaY_{o<Fs*NAF%jsoOR;b)405rq*gOh0W10fTO!ItY{#J zrhjX_-{2%n6!osOY^3(?I7}7Y1&4=q{r*zlDNc}}r)_MaoHDr6_|mFtMX}tXJH_Y% zS5c87330I2gq7<{6@XRu0G!WIgCvEIFQO7Fg50NM*Ua{6gfbwhoy6QUccB!3b2?69 zIgBr`gU4$vK}G@@#`3;W@h?W(Srl42xCbU~l10|L_nl9WDy*(Hk=E#keakhecRl>( zq#xe(zTdB?@oqCXyIOh>r6BdK+;0&Y26~q{Fxkrxc)3~{90B9`LJxz2WN;a3hcN1e ziCd>r6KsO6R@$gn5?e6zVMl~bM$m{VL@!Z1>{?t0>;YR+l%}X;u2lm7t8K8fxU1d_ z*O5XL!r6gOs)6CU18kT7%h$l0J@>QO$W*(pl2exkVI6rrtM(&7`WvkN_b-uBA_ZpdK94VDo(IsA*J|~fXwtSGakm` zmf<73btoJL9Sc`3P>wT7T_btG8l(>nd_U1wBxm~5T5%2ufYfgP#EAbH3mRMl1}iZZ zlEs?R(jX*rfCMyZQYM>)Q*Cbx&joV?HRJwu<*q}p30oFQUHuaAP8mSZ01Oohhp$@4 zK|5dGqp)Fp#&ZEk9 zjK2m^%W8t%0MxnC>x_*|$DL~h_I5NE8YTP#K|j$N*09U=X!S>ZksFyR4rcv<2fwwX z*QTN5BDWpp`w_8C5nBBY8xFbN$5M$EW1z!LbvFQQ%B^kLr6ANDN>%woJ`Hp~N=d3j zwKDw?%bEpJYfTS>u?!+B@ZF>W31#iq#mqdc6mC%E5wrE#csNUyV8lwX6MI-`<}qPF z$Uwl*;eZl@Z5@j=#R$nIir4`#uS6hL5E>Z+f4Xyd)O7`f8~Zlimh)0Zlb^B4WD#_Tk^BqExY7TUqiDdqYRQ8(yBAWy5UE=uxc`n5^ z8z_8JZ<@uLy?KRd#fCscAwDFo1&#hSxi(y|7j*ZTwc_693hQo!22X}MTCRDn3zLa$ zeFDzq_Mz5;=}piOUs}t~86Fm04OTpri9-M1^=5-WuELK|s|#?vi2bTW@1P!QOgQ8 zv}6gw4%Si zRt;?q8H73Os5>M*Vld@*16Qz5OP2|AL<%W zA1I~WhUiQUVN@QLaONjd>NG%jTr_UzaZud>)YUw4zqz$AO|zi~153qowis69#w#G- zxn074dP61*iv@ZqgH<1YSh?Y@U#rAt#x5@YHrlvfLe^-rOCpDHl8{jru2SHn`;zoE z2Oc&3P9-rUUusSA876J2e$@jz*~?{eT0x`v-}{EHm82QvpQMc21l%}b_X zD&yU4d25_t)9i1RtIB55jTN&6*n|k+KdZE1wBB46Xm4!vpP2rF4k@ay(qk@IM7&5k z>?rE%v?oOHuOj^d-Fl@qkTEXDJso+4k)Vh?-ON53cgF=}6GukBw6zA4->rkP%*x_u z`alj2E3~}+8S_MvA_(a@o#IX8rlDQ@yPONI6SVAY1~A^u*r(ZEJhx`1z0)o~bn(y{ zRD)RS0Fe}`)~j)b$i|DBKrg;HuRE9yVWc|t_cGr$P~u*{4oh*TCGMM!u<7d>$h19%xEh_M+kgCEKu5*8 z&6XIRw7^K`j)sg@31rvhHN-2WY7h$?yj;fxd4V$Z`i)93Oa*o`g zo1P{Anw(lKi(wY01gv5Z-;pMA7>Yo(j-9h$tR6TQ2s-7v$1dn^FV{O}4*;D>>A9^Ig)SZ(d#i39A1sUEfBa*( z<$RhVFOofXvQ^Ksgq{2+lJ#6jWTHNfE+q^$D;nG}j_K-=EJ6{F-uM)jz`OpC- zo41?RHMe60B$SAm172D_hF9E9({|>6U%)y$Q1s1MN$OA+*`h?~DJ%Ow-v*oQ?^Uvq?Muf8q~i_Bw{p%S`atL!`J!|Ms?ya%>N)QXz_p_zbYwWh1}!f#^#wB;kS&zChVmdmF)4 z)LoQDX+IIF3ChUsKJkK`>t>GD*Fsy4N|2NbYf5`XOz+<2^$f+V7x-6&AC&)Rsw7D@zpttHJpbv(Qv6{As%&ohUYsn!A|4wG(oq$ zA`0{mFxIV?vO`lKm9ectQeld46&!TI{F#!>Fd4I_Pby51GEtuib~YDdF`Y4dr?1m; z@s9s~MV;NnMnh?fs*QEb5|C~{L=-1$0Eyb!ZQm>|<~MGoPIR`j|U@u6l?D5}014W%I&OUB2!9;K29y?tZNM7!>@60hdi^2Y> zs?HgE;y!L>&nCMzLF?(2TZABFEnY9BmqyS=_;k9+PaH59))uuM~D3?-J*_&Sw9?@-D*u55KKV|$B$PwMHdQD?4PwftyBG~fnR2LXNfm7_L$ zqQYTWwk0VBJnk@d#n9=e-c709Fw01fXq!`4JvJF-u1qosS4lR83Co(+LjfDkvCYUHS)v}{-Ov_ykx=j+T|$t4x3o#$(SRtv z`e3Qmn(YG}HJ=SF_4-kBm5fz0<5oR^dM6TK=L-GXb<8Ff|J{N zc>JohZ0iZL2T9XgS138;cK7Uxn%qQQ)&AhppG3mM!D51q*{`#0%@ z8wNYUqTjs3^A0GM2eo6OdL6P7K6md6rK=FS@pyU5pjIA=B+1J2dN5Q7#TOlH8iYdH z?o|Z2o_})VdnAeASY)|qXtqqCnkblCHmol%h10~tNP0y`do<{y-G!V{4cNa(8IMK^ zi+1o~+R;9T?J<&W`FYMOE9s2^0VSeiqHwX86?^)SZlSreyY&sIg|k(pQX;M}OD9qU zj{1c`n;mO$NQ&2`H$WI8Ii}zBqIFDsIUy1nx!Z*t!e?MZpRu^b`n8yw$_TpmpMYUM zH+gUBb}2z7jPO?I>I3rE6k)Fft(Mtr_QBq1^OF}P)M*&-}wl7{c ze*)X@FA~V=|Ib2r>SRe<8^E$4oo9_bYmP7K4r!b=S$Y9ZcD}nrqS!R!147?(aZ9_e zgX6-DxQx*$QYV?izbm$bq~Jqe0G4nQZ=K=#y+kkLlDHF!8llI9(|jlU@3!C#b<4DK<)8WhZz@D)&1m1xg zE(p-yVoBU{%86?50A4GgxumArnz zx;F-01wu%1DV`EUn*QVUS~*qUV*v8NVE_G<#9KK3dmRc8umT1J-j_6SNUy-vE}dH{ z3R~QD}Bk`+8s>cLr z0S6Coz5-a65wzp#d#b0tvn%8+OuNZO6CtkjrOY|0B=P<Ji_o zwL+<=EEh$7u`Awz@DaOl^VJzWJ-|C<`Es^QH7O;I1Cy$g{v3S(gEqRvYpsQ;aKSgLIaksdJaNW*4sKuz+J85wfiTMF>o22?*m>b6( zME$qw=W46HdYDaLSBT=dulrUcfUv*q@AzVuA2^J$BG7eF#sBdP>gLZvW!KYWHAF_( z1x#FJ(G@Cd!PMAh)97?wkfWzWp0bM?NEuLN7#?}U08_5Ai)Y!Ge1VC|0r=DRAb@KV zuv`6J(qpwi1jec2|=TotyDW0$c|Umw4Q!p`SY_lUBKCGQb% zar!GnE(2kSBcs~gs#{?a>6z?G%P|~XUkg(v*`Z~Syq`o2zclPNL9ewEYHE|5oHP1T zCd1(%bD)v_fQl|<8+_w~jasX(e4|L_;pn@hNirtciMfdw;3Ejn5nROQ;A;p!&fbxf z8(=BM%qG9OoM?9c_sOeHBZ=v_=99*#KpA5J8vVVQkIC%w@P;^b;JYsd zeu|dY{YxIR=4KU^zA83we?;9(c_FH`!Ys*3FgS`y**f8sJKf7zb3A-INNwh^| zYuz$tAmUzWs_T_#c3Lh4@7}1kZojrhD*F0XmFSl`uh7hLMmXsK3E$Co! zSOb2>Sg2hXkp{H{NW<*_BGNEZfa&tyr6FBHa|52!Y=xwy);6jQ7@WzB)JTN9U1e<0 zlow=k+I)|TBH{BKGXrT0l>%d2%)1>E~pA zneeMEim{LS462&VMmTXCzctsrXaj6>$&;R)Fz5!soHljqm=Bj3S9`n?^DE0xsqz(h zZV=Q}z39(VPJd5~1RF2nk^Q@I$4z`I+q8=6cl&8t{yK>Xxl5Sm#YN1r7xh5;U)V|H zm7X)Ig<0{MDXw(giNis_;*B&7&As6&*9GJ;M6e#5YJk!dfqND{O)v3EM%jbD1 z#C=Cbc$p~jiY04djm4g_?5{5Lf`s%8oV=Y$+c_UUyn+5`yo#=4b6K2d=O4^^w9Fvb zHzj0CyXYAziHgDcEogB;GSRjHyt9pflLEuMG6F2sX&yg$O7b_%aNRh5*qsND$ju$x z(x@Bv^A`ad0L9K~8Za(5yyMenBW{C9+ z)@>H=R}=cp)HTYbRrlQl3`GD_BE(cf0QY?d|FJk`Dp)C9l$(<&Y6#V*F00ZmVl~3A zOmrgP$OzqReJ|2SZyD+E{f>F_vT_4RW(n~8pTq0ugf=mqeCKHndU*CE!brFQjTTax zGD@v%DS~v5`AGU48lAi`2@e-`OtC&cdqmIM(rz;N-U+UT?i;TfLZ8BEbnTK;qIgk9 zc)&XbOfYw#pPsVQV9;5CX>S;=Dw61KjJss1l{H#qMPG~# zzNBXOY_Pn_>})EEr6*|k>rdGc~wKGaFeO?F5{MqL502Um)3^X}1AUJlk6NeU zK>*>(Jdd6QYxf5|aqi5kN)g7Wy#2|d#lSY8mqFov&{c=1$??#7tVgMHeJN5h?fx-% zD}TUn_4&lsX_woa^Pkw2x7s9X44pp%W2$H-ut>*LDwfs60 z`{p1k?^vG2YXuEpcx206DO(%t}$7#FJX*B zCc&VEm22XnR+GiS)lPT)z72FvyH!B!t_riLrf}0un^LpMo*|0=c()akUWwH>`&uA~ zRc0mz^0cqF^_?|F*Ld&GmsU?QImCMwG3w&ANQMZA;_w;Kzr^)wUdY>e+ANxeXhpyR z(?K=+fhRIc{3w2h9Rof1R#9XDbEwLw!Q!}$%*&rA0pMvPX|fXGCzbe2eE{mmHvJ)) ziQOMAvkDX^jL8D<{=fb4nB*+ujr-WaKp)J@ZS=K!VrdSm55>b!)I_3>g$s9$~hPD$O0kBGbCkp2RSku0Znz)n8Fkd-pcqQRumfR2p zcf0D9E^q|)h-;vX_*@--NOq^Q-4f%oD&uI!hGU;)Std%kOI)zic`)sHTYVCX2b@pP zC*yn^i8W?Z&j;dfBd^5>X*Y& zx;ayuIH-=(fwWrl#nt1?^HSSGJMhbRv>BE4-JyGnVyQ^nTf35M(&bA@a=x_)OZ9w& zz|gC*-E4hfHIfA6Uw<|L6Te5`o=_4FNW$Newe#b(r><)VNqzv!rOG~%6M|jPQ}erh z?Ww1OOLZ8HS$Ch5onc*t-k{c-I?xX_$R4mNX}6E!MXW5f>ey1V(&@Yy{-;iD;V%tc zomeR8bx-qm_Icw=jtq3G3eh)OYdO{d=;;N3^r=_%i5ewCwddoK<`IW25h*>?A5RO$`m%I6hxk|7l@bqYXy8MSW+5 zS`{nn1jjx6=xmMy`08Qx_jy85(tptLG+<8UA2x9`U>20k5te-rEz^IW_p%~*l4pNA z_aLz!07F2$zm?XF!O^5(tR<2beyYUR-Bb7OJ#Rz8Rq^5kzLak3*MDV{QflD=fd-@; z*YOXV;6#5W&uQ4^fjf;UH|oaIHCIWXSHKc$w#&wBm*2I8WVB#Q(bymy7iv`Vyj`bm(*;WS@Zmqx!5XVHdceqryZQMM-$#5-S!V250c*mD3+A_I5+}=nU9A z8?RR_rPZ)yOEa1lwxLKU--iG_C~x#!`*Ofy*qkZ3kZCa|s}W2z$rCBT*|Mct-mg0s zXMv{-L7XpTZ&ecEhr^MCevKv^(|(sI%$c&ay_g+q8}Gco=u@941I17k99y%>2v%l+ zoc}NA-0|LgD$BT-s008Kr+MPX%>3tZsXuXHS#@X&w1RV*{K;0LYqdYC|0yx;S#(jV zM2g+R8mp#rA*NZ}+iv*ljX@hJp`a>>sWPaGmwX4o;wQ+=RQe?i>n&SLuUH;_KVNpe zOJ1xDyFI$WKle?pnZ;jV+zzLdw?wduKcau(NW^RK zw*$rU5C{GIHSbMT+j&W)H||7t%JnEe#xr_9K?k2vkC~ib7j9ufSMGsQNh@@%q?eX| zI~7XS2Gl=3(|utDKx218Sk$L=@?B4yjXbay$^Y^kuvEb=xuM^2X`R{#Ej||BpQ60| z-pS-}aI7W?(t3ps-R)45#^(nc ziL)`vF$OVd-w~7n3nL-WY8VhO(T;b;atB@fG-JLobftn!f**2b%B=c-2|W@Wj&K^1 z7^o8SEV$iE-1}G)!7RjSpuI()Y&1W`5K7|cJX?6Gn?ft`+I4k9_1q1&_znY z$}3_q?D+0d3J!CwZcGG8tSZ%{CGHXU`PR}XeH8-(+`sXn75S%>1-(-32^G7Wtls#M z)yoLtNryG8IVjUlJ0HT5|Ba1Nl%fS*Q2Z;cJo8^%Wc=3SE)JOme<*UXS%-cq)D>8z zZX0d8bZSv-**W20D_nRV8Glh~988tzIt=hE)_^8OSFHCmahl63E#r9A5YItcCu8wl zf_A~Axv0EtA(JJOg{bg(f+l++tJyi%u<^KBjNEMn#@8sj;6`zitR7gSdsz#%70^P0 zYLg->yyP3`GS*f$!;=u>w`F23JSAH>wfIvk;{)N!RXVkUPLJQV&;mZJ zjg!n_INcUDa;jf2<8Q=pV3_C?SL;Evi#1aAaHU#pO4#98)r9bub;2YU900veIl|=( z(xwgeiQ1ga+!R}fMZ=j>lCsY%T!(V=njJ^X#@H$y}=M z0lMn1T?-_`U#bwjQvY`x@ToSZM3eSMs%EU;aZFZ_84Fj~*7h>3)zErha$(3ykGk*vOB71=?PD(LxfOzYmxC~vEO3&{BZiJp|-BmRc^}%7sd)f?2~E;pqGeAz1e-;Zk%eo ztKCa%PW%|j9jxZCe?E3sY9U*ARB8_!ln$wCP=QSF0HQh+Ba;lg9}@D?&fRj|6H!xH ziMa9Z8EdZW?LAidFBg37SLqca-`Bi_Ura5zZF7h-!-O`Q4vG@HtCb#eW`|1Rg0 zvpY1D!Wuk9IbX-r5hbB??0Q_ zdx2w&1^)go2YDBQ71`8iJi{431gPEmS7;1p836M9&Rq;4cuwZXn^^HmrJS&DB zzy-|aORX7Von_5WEvWjX{?Brxjvm8Zwm-HmON9kW7?0uVu5u(04@*+?R_mCZ6jSN& zr28+narlJ>ZE%ly7oiWk0Y}gB^CNW%^}!TMPd{O}tnnt;1$LeyU&wX5MQ@_kJRC-P zxHOm7*tS8xq;hP)dsia#p{{yM`&n+xnUxZU6j>t4yY!yy-7c^b*1o;Eu!D3faPzK8 zs0wc*7LzI$R8gctqrGYEK1w5_Iz+??R$$q@TKx$+uZ36oE;U5l_^j$s6S6;UH;3F@cq4(LByH|txux*wn{t_} zC|-j33pTYM6}$%tz85P@Ww>|3H_4)5@HhId8$4C-lgP%UyNd#7z6C9h%aZoHZ9EzW zkvJFr!TY-K;|C={`}5vGtS_VZQd*pHEiB-0Ij9Gu4VFd6TzyLgYekQp3J-KOwFH{| z|G3PnC6Dd~I^N!po@IS2nfU7TIQ}bU3-6@4qOj^n&hlJPDWaF1BeN3N%hjH8Eryz zbbfqrY>0mrs_KnmRg{gX3 zotV7kGCC+KxZ1RxRSwh3hqOt~!-XPxr=yW)?3*PH=4)t|k4S+9#)zYc=w(evC&M?- zvbXkV&LD2O(+?0LUdlAbNg)oGvx5&zdZ#d33Pu8I^fdUgue+PI39Kd6|z^? z67bMH`mn8pGuYCpTwTs&K-I{qT;G0^Kg*K_wuw;Gx)^}MHPu7rv+PoZJ`j*fSpg{KK6I{Gv zI5Y@GO$4qgm_q;~k{}+VvF1CJZJ|7YG4p!wEi_!D$nG{Sb5KSd<9mqqj!SpEm(kE2 zViiz=FO54yBfS`3Yzb(6=WJa%Lj&c%UPv=?MiM|FEdF7>it)KgiT_{<1Kh!N zuM;S%+?W^XV_8B+MT(u0MMj4n^x%w_pF{^a*MXg)`3KQ=_NBe2n=-;X1xDee6RQXa z#i%?BKX^lcA`2e`-qPoKX3lw}*_Li}^Y*dD>i?7v~;~v(# z-_rLcQ1ANyGRJ7n+v%j{^9^504ONUsF)vMS3ppVc!+%m%ro&4vX>9=n0nK6i-@Ec7 zxO5M6UH7c97`fkH83DlFp@2DU5^Pf`BLRtBZmZLliFp zGo?=vlJ61-)(YJ%F7t8=ERKs;30Wl~{v`XDRDVj9jUP7LJpCXMF>V-d790L`S&z#z zL_RY2M@Pt#w;ZFJp_yUn7BCo1ur1MJ-GHCWDSb^&m47$|v~pt0Rp=OI`Db4x<))gu z2-uv0Nbq9oP&A{~$4O}~8L*?N)N5^Mh_+AOvEb_29x$yT#Gus%@!u@>4`HsnW6LRPHRjSz43cf}p8Il-6YPhP0odP|C-dk{4e1 z<8%E<&EBLWa*qV#fimgV=k|8CucVQ1%x6n1y>38;VFu(6w}SDjx$fIf{O#oERV;|I zTO4L)8gd?6a)3~f9)vSn?STQDI)};KfW{v9gR1HT&@se>{G1vua^eGzU&E<1Uq^@4+V^rkOW3!W4 z%_3?{N^L#Qj+;4#rsyZMuvw0<&5PE4-PS+>ckr=w{|ol^FONgxKm!jP zZv%1;4)pGB>+3_&q=WEDsZZE6h-VwF%uP^h24Q;a+P4=Wk~kJOXtu2>d#X9$Mybp6 z4r6<{Kc)iqL5`5Msq-nEVzh4L8fI-$j}#E;Yh^?o7D_>VeoTBHtZ z9Pc7fELq7u9d&-8r~f9TZ4Z~+lIg6qYN%Xvw#p7(TjX30SEFMc@T`L5dM;ANMWeC6 zYUEbM^WRZ_OqWcry>Jfs+@a>|kho~SoYNMl#bs5Dt#sPf zy-YL%4qDY|`aDF@{;`!>p23(A=IVsolZ;48X2y4;xv!07t7+H8f=nYf&&`8xI09`fB|+1SaRtQO_Xe74vk$9ARDX}5i5~-()L;$aZM>C zVv%6cNfSPzyQ4qmPUx5sdt&I50c+-{|+}7ZBCP@TLf0G zK)U}2>^HAOCsXzBWMn_&{KZ3bPoyKfakYzy-X$N~Rd=pR!4i{Q+^OF`b-#@JWqLbc zy8|GQgAZ?6g>iE!xub#4TQ;7ssFSEh9DQmY*Rm?)mc~je>X{eo5J1ahh5s_78TY8A zEv?40pc1eA`8!ENzL0RRv{}P0d7V_1)X6Ift-q=m1B!6MwZ6Qym`oMXD&%qp5|kB_ z#1rbrIMV1(IVI|U_VCAZAT1;q&>%dWYpSnV_~|6L{#jm;PP1Rz4p~p%{k0q{dV*IH z#4#-hS$PN%wT$i|r&aB>sEy`C!-gzXqWfheImBtEG(xx;2hs8&)=c79%zsWnt5b6{ zSBY70xmIN<2#%g9ctQ?Smz|U7@H?GR=5$p?%7$}&a*XRjML)y_>Rr4} z!g?ya-6fjN%jB30+uhZ9vyq?PIiC}*G=7pd6jX@Eg(UIrOImTP;fnOIZ*~MBBc#5T zt0z|n*WHWOjD%PPVO6T=;;c|}>(G*?RP!)j<>L`h6kBGVD3n%&eQ%48#^x!_ z-0Ak;eW!k^Y`P9;;rB%k8|1A0&NC6ohi;LVE7*#|Ztmp1&V4oU=^u9il6$k}+~fPo zj(Z1p-qoIK)Ypa9{^Y?f)L%IS?ue7HBoQ6s{43jKcEY{ZkzQ};@uT#Y`^KWj*y^Tk zth1=6`dG{jkUz^SE~xaW>D1DJo*FPqt`0QFf)_Zp#(YvC@Ef%*0rr=%liJExf!G~l zfMri@ivMcL$fX`F^b%N=;7cxnVfl-^NF+55jR_gAMLusy&v1EwZFH7JxO|r%_HOFn zC^^sCH&Gb19lwykajE>=19Cta8m7;Q0++yUu`(Q@th}f$VaL3r&l`mF_Plia-|K)7 zuzbkZnv``vPvRG*8NeI{eF@zhf~x=ybq;Ghb&QQs&EY-dEiWqC)6Bz5gnZrQ1|DvW zN#iLDbj(|U2t@UWd@E040^dBtW~K~)s>hS65mGROFjIERh$$j~iZzy2 zMLOi3I2JvquXDM;xzz<5`v6mt%}Bz4P96!${zk(nOF^ites3t^lt-GQO)2I@P6mE4bXkP3EK}T%z;)ac87%D04Q9Ws9#Osfo?}MS^~nq@EepKePx%+6B2H36l1<(u!{Kkp~m~BP`Q6TMY`J z-#-9SyA8xOdcM;5ZjugV|Ja_1S$#k%i-R)wl=0f9cZ!}*x4e|8;u?8vi`j5-d@*{UH zm)c_>YST{_5MzhmVSr{$UF%Wi83^q=eaV#+X1D!$jUKIxIYZrh;QZ+aP&xj-s2!_| zBw`pC7dWz&GvR5P|JlA^ZY@7p;rJ^p34lJ=b!Eivd(X@!!^QcR8YX?(GV)bcy$XW$Ntl{|CgjJsKd+>=)1C z_=&MmGIESg*E%Wt`O>+j$jt)j9Y9M(c6?l5+$6tG?tVJL?Yl%3$+a(ga%j(;tpr*)=|)FBj+-LvZPt#jq_QftNT~n!I#<+| z&?d|W)u*G{4jj07)-k*cYJdP6bV`gG40pU~kN4y?z+!@)L4Rm>dxMDEN2L>j~Tl}^7M)1r3@PohGZ5M3g`S5;HsQzI~aBKt^?Mj zqUUjlP__^K-^}#}I-}~Mp9E&rw2~b@{Matn)R;5c{Pp{Jm7LvXwarHFwykefz_#{5 zs5M5p3n|CQT*Cf`WU$iz)*~Hpu6JS`xSp;dP_GUUF)E5ycdgiE_sBW~4?PMBSof$- zwo_n~-$wLWFxPonb>9LEy{Q@Mom_f9ofu1X98}lcP15($9>51i{{imjekf=_=VNSO zlyks>Kbs(*w4?exfxgivE@{qJ(@f6nEis4p{dsxoT$m9Sy<(kj-bGbQTb-X^>&bO` zXj(q zJ|1vJ|FixB%UEIBR-a>`z<)e2*ZSv(jYgXCoPCkhW9MmYX=Is{T6c%+iH&J&;p-MRa z)By-eJ1`wCAjY&KEg3?Cn=V( zbAB9iR>NtWUouBlb=low3@{l9niq z5S)5;+L>`5p344pt5NVlT6$aeCGZ?@^m)V-lFsZ5<9p>vr}eAr)AEP*AwtKKx}h4r z#{9+ulsgWh>0`l}vz>z!q3TO7Nrfx~U%Z{d7fpDd69}EL_MRs6o1N&U_AKFzf0Hn( zwD=#IDJrs-b_@&7gek)c(3#|Rx8U--OcnT4uB-TlKkcn@Dqjl%vr+2K?aOPNN;q;Q zDcfEfbKJdkAHDO%OlnUKawMRb^949Tk!R<-KTxP+CK-!Ct?z`Y$R#euz=L1-ZxdT$ zK&BlG*r4iQQQg#bXj0jzEU0DIf$!!*I~YH|CRfS!Of+A0Ot0Ynp9MZS>2 zOf%2w5VFI`JGU}`71uSXUnsi~7(cFdW;s~(Wi=iNug2t5k1*B;<;D59jd1zf+iH4C za@R?snJE>|R63^`waRW#`3GVi`yY~A*@CE$82OVJ=cik)#f2obX8lJ&|1@;*bd!{B zuM?_gH=S$BMuQmlu*x*Ei?PJd0+!*y5CDQw4FM5<3FNzy@9mlr_?^=UnSxZI2Ywsq zTRbrbfe=O(V-MXF-*bGc+aG_g*g&>r5B&O0J(RzYnz{z~Jq51#w5eaW-~Yp6K!)Pf zieIKBO_c;2@RXUaVNKoOPQ(29-TEscEm#8s<^uKKKUL5z(nko+6vDSQaj;d%FrGQ( zowJWwA*o?e7*L$Bm5U5J_{L5hHhXXKQcTN_3AnpvG_=rTb3i!lDRpYI5e%H@6|OAt zEHer=wmE*XRJ1&EaxNY#R@h%;^$K#Ks9iHRaN2A^1w30BF<)Kxr`kR+;$swi2j^I5 zjie#49(`4a5^KO2#d6mJ2gi>R?&IfBAn)TkH{j6unL!jd&a5w^z}IzAt%qoL_-u5; z-PnBGOTzH7rXDw%ZopEwH~_k>JVwu-+^}|vRx@x|>*v+dTeGfG_3aln)(BlIH?cUl zi`ABiL#bNASG?W44ekY??pG0nSSF1Hq7N@Hl?>ohax{BR`r6Bg5lSFv85$cpJr8G# z_HX7R2u9$=BB1=Z>T=ClAFw-_@DqiN-VZzjeIU^nt$ct4j*FX5%HsR;D!$?2l328H z(!{>JgMd`%f$z!Q^EUv){jqm4^1g87^SAneOEAys@1HW-tM>H2x_NOF!%(**XEf=> zvzNaPrf>v~9KbL9SG4824hgGgw%W|O)9MjE%QO!4a`m0{@6aDV(({$PIOXo8Y#+y3Wno&BFDq1V9nyx@-TsIOhzq?lE8%IB(NHot2V_J{ zW!OflGO7aWTQIFH-^R7QQJhXKy>28SdHg~d3CHCFIh8x@YiJheBqdzGp)P_WLxxpi`U&=l@pG zgB@{*$Y2t>HbTqU6kbU@hs_Xe2)=mKtKyxMg^bPgh|oTaOP%MME>Y9{_)HNO)x2O~ zOoHiW{6B5g4^{VxHTjOd=lT9=ygm7Wq(49+qP^Cu3>s4mI1Fw0IJ@x`WgO$5SioY~ zK9pt=LN!)W;2)Q}jPW>LckO~PM<+9ZcMvPv2xd6V_i_h~>mMJ2EE+}@H}k5m|8gk} zZLGV?KJUYJ3S^Kz1EQWS^Z~M^S6QyRCCno!RxDI))S9~fG+gp}|HNmlUo%8b8};@m zPrDQz`RrW-hrzsH9Q>7xxJM;PlkKjH<(myY__;f;Yt?*-h%+F(ZNmW%`5*iiT=m>~ zB@r@?y(5Qz=?QShZ#vZNi3RH@F zTo68*RzI2z`-&v>aSi~UUPI=z?r*Ogu#St=`amR@`ci_r;v;Iw0Do6fa zN&}DrpLVB%4uMyxTsQbv?d!NAu4`bGb!LS(*qwX6OVd3+um@9}bnm=ljdt6rt>j9G z$hSbPrJW+sAEn1=^?gI;F{pnm1^V^E2e6w8 zd)%KQGdkJuCkk;mCBL=m4x-)}Z5R@h_||Eoi9S9dOEmx-V4z|}`Lh*|aN)q7iEFPL zxc!OGI}^*~q0X+-*RmGbe#rIdObSy`e$v{6Sk%I`{*Z5;?UUtn{(J7jTC(>}pn5_# zN)G=+vgM5-MP4@MQKj&`^s7mTg_&*vYS8~O*_x{l6U%yb=zICcq#4OXbf?|sUXtRC zX)4O231dwQNR;JJbtCq7vXtfihhvTFQ8*)XI1o~6LmfB1;_#EhxOu_$bWl4%EW2BV zy$PIakO)7l_&32_#C=qtcVI9^Y-#MkWS0W7;S}qhE*;xbIbF`8%idqJvpadnvzUv2 zho4r+>8{BrPM)ntW|R7(M72v40e88XHoRmKG_BH3o3RRY0pl+5(#>x=cWq0d>NK_4 zElim~Q&J=I9>6mrG-NbBM+v9S|5kU#D2eWsiT$s&ZUbjAKvQt%Wl1f4tsn$PJ68Sw zF57<#FmQ(l=f$v4Z#z7vtqW<8-Yt)4g!+%pO{sz1>gQ9L-0KVEwo`idqy0$?K898? z%|~Y#-RR%@FR^o>z;=|PLJU-&w&7Zh_65bh`3rjUk&K(%3zKlpI08gJfg@ul$q9be zNyc}xn57XAIbV_!Me2eO*`rm&I=}ZCRDfapMQYr@wq9Gx$y2ow#GP2aRRr?JS|o4Y zj5z8@*Ugkj5s4e}o{1E4g(f^0c*?%G{-WO14T3P#cm#m7d#^%5$l5o-1KuTxxi|IW zhAo8NfhpF^!gMGsq?aqnu|s8|Sr}(b3()1#5OJK=1MMc+;uY4AJ5}tg$$+V1>R{=Q zFO2ZOkqu*(YwMcI_TM9m#;foPzR1XbE934)LQmx2U$u^;V{@lI!R-h3V?CZ5AxF13 zRWKaX!6I7HANiB6=pCD!EsojE6xYC&nCIHBB%v{he_odpkEc-!{7Wbm+{h;7E^&|6-zi`mR1`OW|9vfepIz^yLH@3bX~vMp9}bvRFWon z*+Kx`LMAxd)jxOTHr>>ZxlL24f97v$bK+b|4DlNb_F{1cI6jfv=vnVyqz)*4baUMj?WAo+fU-Hbr8gGYc4_&5x<7}15qaO z?+vi=&jc`}=&%hCl;zVX^2{KC-H=Q0*ri$XlpK~MFRwL9U>K$En>O-O7KkT`f6PZ- z?@E7Dd?cXrUE@>pdh0Lr^+g5csg{OHW82?%B5r2VQYNf`)SE$`d#j2VjustwTAy53 zY5PHB7EFvHEhr6SvhKEi>q!B)xx@&MPFd|iN%Sy9kVWo*J)puu#~&J zv2^m=z0f=ob|NZCqtroPI~T{Q5u4u=`f3M*nPh}hM!^XTmPnvWpK}vZuxFZoAHD|# zcb3}eZKxa!8G6fMj|Wgml6ZXpQH9!0Hc<%`cJRX62c*XtgoLjbsHo0wvMssGyE!SXJ2O`%AOQelT<1W6m2a_N2#Qy#~fq zWa{+amdWFxQgJ`?bel%Cd0Uf^5&S)5(01Y`o*6&-`kfcrG|GE>(Hya$s~uE;cC znHR6#X@J~SVR=$s8@v8%1D%i=Kmgz6gF~)$XONA+x-WBGosb5wH7V(VZxM6*0@S7_7*M6rV3jnLKg<)?s@aazaI{RMim3iQZO zsqaD~7P8N8r^IkBU=f11m~<=UjPc)?mqr(i$DNFjqjb8buUAcE&mn!wZ{ok$=S5}) z=OE#n)5q)kJW`Njxx7P$@(M0a1RW58t$jP&(0QXXJG@j|r?Z=lN;0_7#|}5nT)@G^ z>(HhI%Z5(bynhdD%i=7lx`e{gcI-LJ*Rc;P3Sy)bK z?KTU|blegbjWcS?Tv4FmgJ%m_&AIugx+ZfQKA0;sb`v8?SX99vlSqaEa@AxY!Kud^ z<~|FpX`^{|-b&aIt#8wh&iMzscIIn-la$N57v0fD3U6|LSr3Wu94=g1{4o;yH~X2< zQ{|lM<`CTZ!sdrZ2&?dq!}gOZD0_0TSM5hdSiw-RK7~tJ$7&f_YrZLBWn9}z%kU6r z-0Ih5rDq$(P!V?>3dV^({pi-X6cJO|H6EHW(p2`+Ne>tremk=j>kdumZxA--2yDHE9t& z0M9;GxI!^uJpO`O=9y&Y=|Ioe_>Eg<%p1q}%jNvhW>|Z>vpG;YbY2FNUili@ek#@fSn``GR z`GXg@SoQ0<2W!^LnggU>tsn7bmEN@?&PJgx!wj!MjIX+cTL+#ZMhds}vd3zc1tDLP?J+%!Mg(sHGY6T=?hGt^k!->;b&l{5N_Yr{!V zAYn9@%gE20v}~Jag*53IJVGYXa_m11O3zjHrRC}-zJ@5^Mc^`nOZlFCekv}37(;zj zSsHp@0>?&*QCR#UfNp9R{k#%3uGV^_pvaSDxHvNxy)=32 z%rLDI`0L+3h~Rba{5>!2$4Oo%XqFPLRykU#^yl*uj9wYVv>I_4H4q@OJ1~Y+s+IFf zGk+Z|&2rawS1!jIqa`Hmkkqbh-RBz|~otWY1r9%B4kEnskJUeDC5O;+Ys+6;l{@NmUT%f!RwsV-eOhmI7tw>! z{8xBFDON-!Fo4r(<;|*L8>Xb>uS887HA6oYD_yhTsGHgX#Y-b!z!vbphzjdL3+ur7cA4c-@da6N^J7$@gI28L- z-ZsHh^mHefl9^JVA8I6o=zJ4gg{|VTdtR`n`q+h(H)v)-QLp^G_+s0Pd*(4N*@i{t z!r=?j4M{?~iS-a!zuE{_@oj#cNyv_LxC1urLkd!uSd|eO=y&5dZ3((9b#T>M2aE87 zlyzCMAN=F?d$#-)j?o*+@3kS3oF}*i^&W9htyN3Hj=P)3hideJMxWrnQdMMWOUH&x zcpeQqWK%$8zME&H(hhfcRA>TVjtK+=@c!}!h=_iT*F_q6TIpe55{HeeAi5v1_$b_V zm1E_Ktbv+GAnkn1m(}BsF7E?+haR?cV7@Wq_ex3}p91tWUuvWFT`kF9|F60r8r%;? zPO4b2jSmZy8o)26+ywXUc%PEeAh{Oys$%Wh5fzps(dtt)4P#|k%IE+Qy7yaI!u7d+ z;5A$?xzf=@R_Vk2VR5{|UbBDK+iTW>W&cG={zPo(jE|yit0>lzm}lLu7Q#;|5!6!; zhT-xtsd@T7bGo=do0XaBGI@^oj?_>!Wl1qcE~ZX#1&0yJsg9FyV1EpFo)VvxCt+}o zENojq;wpwrOs<{>iz|PSUsru5trogG#-7ugzP*-T9TIYdMlD=Jhj9m=2{~>(6PX7v zi?%s0uq!O=CWV%YaLt-^Nvk%hca_?Lt)X`gqez7;yS~A-BH+@f+ST%Z5Ni|g&F>Y$ z0#)4pbL1ND#GCo-l6!h1uCVo54sXk=0YNfzT1V^OZFkrL-ljm8Sh*fx`cujVlOdCW z;og2^?WFv(T#unu+I=-#2aWRCliQk6!TcZ;iX zG}oPjd{D|VTWdM_y!#p_Qd)M_{=ssSCm2A>p{-Y=Hr~B6*~t72h_6GeXa1IkEbfte zZnBO!|8`n)Lvh)53VzkK5uoIBhbK!MF3Z(@GwrfBr_wa)a=Trk?gJIGTK+X4>q*584RcC3)OG2tec$lj`$tA zTlV_k_nk=xGiVts+pIA~BIN_mUYf*K43*kE&M|JAI##P*wArVQt#3Zv2DOVuuf~Az z#=1XsbUS2h`cOzgeq@WkJp98rC6%W4?hFpLw^4>>TUs)uHn zp>{cDcZ!KCYw}X&Yue!h2Gz3wnv*L*q8=hFFxK{|B^Ip`N z%XhJ`S4ZXm@io1G;-vn%(^CSN@vQ8}LKv{e{ghIn&xO9KWL_D3vzoR*iVS+*4jL=l z#&K3vE#KwDXAN!(cRIN9C){+BAHiS!Aw56l8G1sSwAj)-g8A3JYq<|HlMcIciFm;U zSs`_3QhAYRN3SYsz5U{^&j$}0rRbLVI3zCPicNfn47%Uwcbhn&R0`ym2mAdoaFg~5 zKf#}otBwG?^DkB+(;vCo_qn@QRv!4h^9$vktI*M1cT4-FoQx92!-)5zZVuoi10!yH zGTo2LW(~9%`LP!z+)0gbWftdpxT!LoXTv+vqI%6eK9$FdysO2K|B-@bP*Rmmi(Ivs zv6k^#pB$9Ej^8y9VniiV9&Plq@=oIKP*`6MZS~IpXQj1ShgB;)2L+0Up}6TzK{V`M zduUQ#!_YWoyKO^J3!0qoMeKkx0Loi7c>9l@58I;v&t04wRqS*9_e0wmj=DuZk&wE z-K_OmJ}33Ux|n(k8IsZ5omx9D{_4pt(M6ZZic^)CJR@2hwMEIp&`6FI{|H;K^i*Bom+o zj02N{GxcG73Yligy(w8O8%;RsW`A^ivo5AxwMNKb~f03vB&Y9UZzdT-Jkqb&K!6rAqe5>d;_$C80FqB zoSxN*tu{iJPKR-f<_|{@+whcAgz-~+zj>7;GApK#DYb!PiDJJ645$BWrpz0b>;63b z*BFR_4^tI=Vtf9s6=D#*nklz$HViAsvEcZ=X`Wo{BO#QO!=I#$ANlX7s=B+w6d|#W zH?OPbSTE=5^rOMK>kE2;b<}o{$h}u*RpOYhyq7b7l7NPCIo>J1K%&?WBO&5l*STAk zg0bU&(S;UUE__Tud_P;2(%8eqHs%T>>$xO>tDmh{f1xN09Y78Ki{582*E+SW@7a9C z%-Ny73m#X>)|zlVwXKlm3FY;7SrM)>bqGXXcr?RQ^)2PJ7_K~D?vzAVj}TLbB#{rf zt6wdZ`_)C`{D1~=0vUM^Sz1pGl7V9}?sUEYXiVCo>(c3zm+!Pj>pD#TN1|z}&piNL z4PPnIcTW&wR0oLqASRPz=>_Rp{kKkgqT{vssV9rPkkahqGtqk|188|MYAe{eXloa| zuqa>Ywl`(MYK#$3M{H-G?q-Vf6Y~Y}&a4$kbxW`7By^#9`Qsm_)js>uA9|uk=pGo& z%QjTR5XJ~PhRKj9Q~I%p8oZ3IKhqc0gKlo2GoK{pWng#YEiGk2ElUvzPrnix@s-CI zbf{9Qf_eBHO*;WueY98z1LDq$vfx1&N0Sa2VV~n3fe>vbPE=?&;C1(xLk34;@nzv0 zwe|+65X|R!y^07=9a`{IyRUSVbbp)gLl&Y4mUo!e&^d&`GbGcH<}8hVV*PfZPsNOw zgBBlz4a%;jHd@&?ry+RcgW8xBHEFAjb!!{UmeQC)&eNV1U}6SaB=zrT13v}Ff}L3q zW{5r||1BD~XTqKV#9l^}eOE2|GW}(NQoUIK%VI|Xd0l7i9i-@mL^)zsLN{?V56HeR zv7vcbIm7lYm?S1=sg9!UvN;}_EO8vZ?JSVixEd&hf}zKFiinGk-B9y;7IVRd0+7}k zpa<(9At*?tgAa}s*1HCRzYW`AieKkrhIuYdz(nM#bsewL+=zi{(_`w(2*gXlz#-B< z%Bh-ZYoOzcke%}jTo@!)zR`aJMZu@?Nq1FExzp@VQ^K2zyqMiWrTat%mjNl|kFA9< z{XPq9V6ek(JuWu?lJ-d(A}DtvfqxkaSXRPBK}A-DkeJA|iMe&+7xWXg>j?&`YSAxu z&9k~}BqP@fR#*K+D?Ng^5>SDQFYD8WtSRQn8@I)CUrA=`xUU&GV0UhqN-NYn-F+xl*7eAR^Bp zb{8I^lFCIh>-7NxL$TJ;%-xX8^;cnRM8FT|J5@)`51`f(IBm}%47-SzEdnmSo3BZI3*F!=hc(6}tg|=Y z>|tdc7?J-K1t6v_t~Thq3hni}xhWEuymFdjb>4#P=e5n>tq4hHDpZNY&&5S%jv_Ed zOjKHycrH|+AHogiQ+I2*cLTQA9r$KBlj zKbF=RSSKt+;+NLRD~GzmR+m2LIc^YaQ1pQZe}#ZCPu2#t)Mvh;cz4*HRz_#t@ge#t zK?{Q6j&5DDsQ7WqNe>4uq#qqJm2l2k6(kCOxuTQKR*+UP<1Y0mIb0`0d?4uB#NMgW za-{SPEQhM)}ashBqyJQPR&Yi?K&{bxBvkr>sRJvXmv8XU_N^L~_n zqfXu?T^_0~3oO&Dkr9bEBh_yabU2RS56O0ByE3r1IpU#?brC;xpT2*S;EhbXH3nr& zLq<8FEr;}iHZfbvxWv>W01(}YJ@Y^uPrcK=gM~PGmc!~HN8S!Gf0Y2}pM;9$!rW|S zz#P@PkKz96bg)~l`)Rm(^p$(J6JosqguF$406##$zX|)-73+eDNS`fT*fwFK+#8uP zXnG+&X6il>6ZleM+KX(kLco}}y0_rTcuC|4%P>pf#6p#+9WuWRz|xBmG9T|sF%_XW zL6qgGn!aK6CcL_Q3ahws#pnw)ec%Qwco00=l zZ4%!{-12^}k`Vutds`^me5eS~cNQAVIpo|FZKZ%IFvkHUfzFhV7dfSk-U9 zSV?*p6rLePOHbmnauhy$JG2;z<2luS?)2uifa#zz>x+$-)h?#XWR1s-U^o`lCc zv0BBNV!sGt8sArTw*^^QgRJ$r5tg7t-H)?Q1UQ;XawL*YzcC3j`M0W{s4qE3@g*tuNuBitjt@+ zC{0u={?rm5l(vu4rwEQZ{vky@C@}!f3+O|N2bf=e+W!VMujJUgvD7(ZOlpKjwe`S~ zEOfhklnnvWQ&gs{OhUKwB7%Xm;u$Sh^ZNRbM_TV|JC)*V^lPQh=TdsMa1+3178{QjVU&A^NeHTqQ4 z^tIP#LuigfBNqv0?HtYuEtZOypy<>yIFg`^Vq5s+#CkjJRN1L@<`ImF>2hJQa^Y<- zUIs;OvZy?z+M1V~PmfFKb z#GIV#esNv82@eLl4onfIh0Vf+n{}T@aw|&+Hdq#TpG>IS^ms2SO^a1wRwGdEDH{MZ z75St{-Y^eVsY`=k#q4`iu;G}O(jNSZDhk~?;&VM{(z{(eSP8mK3ZpO82su(pz4>|+ zikLZQBnnRxc#hQm)Rg#A#oviPZBYxZo9o{F!=Zfi25>s;zc{ErS(_rH-X|mE5ICyp zFZVj#oL%&(wPM`!Mgq7ikzmJVl*+ddpP5@6j|Eb!hfqmyN~Tc6A_d@X^MKHZb8{P; zqkqKLD)Czy4zPCkeJz5G>1_F2<$L1Sf-}-hZLpv@EH(*UJwyg?Y`K@2_?BEJb=%`^ zrayAN$MSY)kip}t%d_CK>mEcJ%z66o?jVbGBdyrH*M2BC^OYDW8Ic&-L|%l1EFAyS zQcd0Bnz;83kiH;7i0%Ha}Bh)?;NcQ>f zlU&_Jwo8l!b+~x=zNZ6|n4MJO;Sy#5>s;N~?zbl#oxk+9!>|OiHdr92J+_W5=!Sa` z|Bd3(gIDr&5w-_)U$Lsik!4 z6Cb}psjEl*|A9L+)~XwJDeUd70b_O>-TVz2gu-gxoq2%qo-AMlqQ*z}r(CMgA5BA? zOUC-9Fr)=mip&QSXe6WptF7p0wD(GeUo#x2&TeXYM6H&xRUZvDlP5yS2M8rMGon1;razOK!&$O*t5vJb+T8JZNfgzEOP|x~S7~gxBme zj(jqyk@CK(Dvv8-{zed2{Z&pfW(?PN{IwrWYZLhjf+fAG?y0%C!FxhLa+<`QH3+F6g$bRJB05Nt>)F6VsX5EffViB z$f(HoOtZJ<0Q@pvODGEQXGO}LG1O5~IEvQ6ezn4C)s7x71#n=f`={4mrkvmI>g(eq zGD7Q3F$D+{sfk0e`(HuI9VJ=gTNO#EDs7#Gjn?=*YauEyNS^xrSfyl$uKf=`Vg&> zXKp3_$fV>{0vY65e$IpT;n_=kJf|^^5et&To_FO3KgFd;^dRIk7k^5&9g7+LQ~u}> z3(_@;WHSZ6aPOw`X9IZwlqgeD|#x|&h8I4+;MAXq$q|kZF1}&QaTXVfPT!a=>{8mvAK&m%hA|yF00R9kA(;kDAWwaUksA;^@KxhJA-n-- z<|A8Pe=i}7N$zic9pu!PQ!R%`kn*@P`0{pR5%S!J{ta5iS=^ z-MhplFILGph`l~u$xx=oP5t`LmA+&0>}PzWuc+n106(c(4#+W7dsYlR@hP79!%Hm4 z4Ojnmmpx|LEPa=9Z@LU5F$VQiKh6^PLsd%V`@04gt$_3`5tdro(tLrsM=5R?cJb>5 z`VsnJpIWZwcIu@XkajhuVmda6u57Up)eFg)Qx%>ibBc};ruJIer5NYxn@iVB@4R7U zAKL88wb}%I5(K7jPRmY!U0geGjX$_Ti?23MTLqQ2D=nw1&dCEq;M}v*rf=2*Q>*54 z%q6g95JcHfg5VU`>c(`HUM>?-jDA04NE0arUQRgKlX_d~S8u*6>Q^0r56zx0O=Zv3 z#w!()b?n`-d(wvEdRG&)2;Ty;eUW?LoR)%lT@53KQr<%XMK*os=}w0k>dDX)bOt6< zo%{}?!BD0?Y)BkR$QjfT1PqH$VN|lk_uBR`cR-jKbqhiu{0%n=IT(OE%`J+*-Ut#X zT89SYz6kfE_+cv`G_Hg_xT@=o@}=kguD|5v zy_)=4f01KZrdI{d2w{>hQqfA!NsnLfc_b(GIbh<-LujG_OS=O_KJjb=BwgU%}`jPe*p48XRs?o9-- z+3mgO)tRT<4hd2wIoO8=gEPod-f87rk_SLyU{%;u+4pF11qXA!hh2sg$5)(MD?bDY z;5pI5kM1@+|Fb|5ov{RiAB*p#ezN1-^p#5W+!oMEy3Tja^Ny7sIubDbvcbr=Kk`p+ zEZ6f)cQ1Za#(y4=|>C(-8%-8TCtvDD9cVh zVmj@3Lm_jpvZK$H8I!Y;zO4~-xQ+W;x*PC6ECKzIYB^Ln4rC;Sz~3N3vU1oCy}rqP zi7)tMBO#l$d|Fe!1q2@4CYtp%X(P{mTS}G#{G>WNWSmIKjWsKQ?GW}+!0_Sw+tN^> zPYp2=O@0EMD@${}E92<{(D72{0`i>wzXB(=s1$@$yv*5^nqa4+8I_yBU^fL zFp5=X_5iseVJd62#WWd>hxuWF+Do7O+AteAP=FYSAtni^5+<9T7{e>!b9rmahJHvE zSk2k(`w~Lqukkm=i+VlPUS*WyFO&t=yF|n2oLkauh>Xf4AEiGO{h4Ur;$V4@QJG62 zSi^j3&4pe*fo+~IUt+moPC}G<_z;;LYs0h$JJp5)b~FAE{YCf%msA7jqwO@KL4vA; zp6{*H+a#Xo@hvwQR(C6m=@Fh_XDbdLWr+IK)nr~;j_kS&xV6n>7@8RD!#;XX1jLw^sPaFU z=>t}q-#S)%&P|)>7Z_hJH(Kurbn<%JxvO$sAOlymX$4T5C8fh7DzEE z@gPXx*fCs^MwI4k=4--m98z1SfMlwG5;+f)O`~;mT_POtpi|vN40QcRb`pSD!08>W z$SACk)FPQMqb*S(fWa?G5@72QEFC;}Z>fm_r6HzbpIkI2r~Zl&BjbG=H2zCeKIHk5 zC_Va%?;Zr)aLA%L5S|Wnb94lzN48FUysC2_$fPTsk;hp^-`xp1 z!1PCCW!Wq+EqTL)^lP^+S=6S#YATULFMD3f!cJ;N`a4^>kuN2mC~(sx^U!Ipnm>t$ z*z99hxU9)i*k|W{Zj`jBqjKtBlv5kj+(ezUI9D6F1CXtIY!t|SKT2q4ZDg=0E@lx{ zWYv(;m;cZOMnBj=be?xG@icrW=Bx$k77W|B!g(blk`vH3bQmijLyL># z6Su=jiO_v-9PFu7nl(Y`hN|=}Z$M9K_)~!uMW7xr@nr)-s5L&?LQ_8${fS66vC$)Z z21Jy2o0P9gM@28BtExhLMshNcxg-*$3wjrv+;2RazXLmY`;*8>Vu?1aNDWu&7I6(X zJh^b%VC8|d`{M#Uv+tpr7M814LGQ)7(&&+HmW$-=rkC5*k9A`n%ossP|M8~7?#i-m z3B2B>W#M?1x}47CZ`UCl2aD>3h`7RN4v@rn^G%N-H0qLoMX;h$Yf?JkMtiLVoErz~ zL>+qt=T_BU%};i`l^bDoX-|YQQ_p%vfELT;1tdAx?^YamO%pM8GDGjkK_>59p9KSn zpRwpNnmTS+Pg$o5Gp_J|!iA0VaAp6buXq@A%C9M2AM@2S2M}>~z5L|#!l&Y6ET+Ry zO*0`>+3&{vqQjS}tuAlwG$D2xtiVj|c{%4W+lA=OG2M94?r?qbz+8SZ?4EU8X|fTl zsA~nf5<4J4@0Cjj@SX`pu;;SHIcKZse|Y7GjtB&! zoQFo=)8LnTEb$CvZ0^HX7=W21&}dd5hTfg?n87Qve9^V__HPtxxTpwFaCOPQ4x+uj-}Z@LoxTBp9pSV8CiJ;1~8FENn_e&-}=- zdNuJGI*quWwASERAu@wE>Rlvr-dpdM@OzkN~Q0 z(-OOgeJ3#*;TiUK+8x5UR7f^MQe>WRmEkH_cG`zX-svPe0963VI^HXuD_XbFlgk=< zO^b1?r1k)1Hx%~?W!$g1Cvvo>Pb;5h951bwbTmJf+%U33^YjCWN=t9qcgFHujKa}J zU-O4{+%F~)x8CM3Mb#SVTwH4;KZU@Jmnb;6fxrZ6e-J|_WOhFydDof zH4?5hlkPyNIPz63GYWER;zzTEA$%#J)RkuE@OhnCf$3Y&gS=HmjG%Ubjd!pY#*@k( z+ZJiw6~5g`ypH+t!afcK%$cy!_f$($ao?UmQEl^UcN~keOQR*T}f#9G%F)Fw%(#Q|aL2N7Tx zk_j0BvibIHy9t7j^n%Ho-B)E=Z~F`OU(OT`1PKemU8$dwHUe5`lpE2nMPSEMCi-5v zKltY;Lqkd>FMrad8?Q>VsS;WWHyE(;Na!`sG^jwk5CMmU|PPrss@O0r?u9y(Z6@Mkwqm{VJi*8j-v78le>gBT zr}~59hjqWD%%^=As!`n;_kt=lzn{3TvLz|HbwQnSbCMIN##3mlL6%3ekE>0->n&NIm$Xaj4x!b9|p{0l;v z_hBB2JbO)Zk}fHSrNCo^tB6i#=vnHX0?L&PDUUm(ex!_>M?~On9h|?ALgZ3#4Zaty zrcMtqLyeE`H{)<2|Ht_h4Xi05js8}1;tXRd_&nNS&}jw=tn|U)XNX!U9aoy+vZr~z zu~XJu*86P!@-1W(9#crx{#%KE2gH>cwV7BrS6{VIvUx9Jx|yAdR6r2e;B2HkhoiX& zng#=dvD=B+GTwk5gcONNKNv2gDQwA&U5GSCh*BL(VoOfaEyjoIXA@6{8wS5GzVQ=F ze6oSIM}(&KDLP&Xj6T59Dz02279j-s5I9GUiSrT$KbWi-8Uwk|h>?inC9vcGk%V#;u(7+F05?T1yawRAoZbCozUsj~NYrg_8F|EmlmgZHM z1IHu+ivUbwi;}w|Fdhb^-8TgSD}=psS~sMV!dVi#Q%oJ`41;Lmv&Of?b9j25c zm@bQVS7j`79Ds|64d?h}EB@DLGq!T{ipvgAd~>s6ZHX*5?OT35RJr7T5-&dZ5J&*? zBv)P%ZiS=J^--o~H$g8(T)8-L_AyaQd7AUn#8}UhChn$^+?FMNC!>Fd?qzlu$M(42Zi0|bQI zyZ8fnJ`Jw0V4;M`?O0)00DXgwLduN~Anc`xRjitB1LlDS~7E6gGzS^=N^T^g!sobA9p`eT++F* zMaaUdkuonCIixAQ(igef?-t4n`$jvVq5Xo#?VZxn<9ZE3Odt=X*7vcq4f--qbk0tp zzKBf`sRhriZhC}Wm&kj1yb+z=i zbGJ0&I@HHylA2Vd)27-M)*f2Ckq8_n7y9x`bNEb`fi&NeI6b6dQ~sh?ltl2*I`S0F zxdF>*=ZUJN=P->qSc0e}QDpGSm=7ThZQJM>$$dhPMBi@kd%qxu$SQrZs9fu#1sW`Kb3Hi^RD-vrdyL6&;qAM#>)^b*TUeLq5WVY899pc5*{>#h$k!eVn)K*OWJ z%3yn!7MUJOs=;6^b~pXS^pynf16=&;$c3k8|83M1yXVF=%z@lnLg}OjJab?ciV?Tb z3685$%u(oL&FENu6)P32=xN{!LVgggT}@}CFP#2uDiOEaA6o9*^vMx)y{WDb{4>Kj zQjSHpi(Mk{gZv+Jo6(#>gs4RZ@}7@OQsFjUmq4sMkh)MvLw#Hp*h zuD~5auo}~erlj4>Kqx`a>ewp1AL+WS>C3DeyI!bD{4#wbeiKi0bl%*2p}{46@~!*$ zttuXHUi8Ne5*8M7fES-YBVA(8|# zA)_#Ql3U}L+Ts0Yk?va&|JX3pOlDrTr*~PU6GZ$sy!8lwS;c7`VS5aybNpA0`;+hc z3JyZrUpe!rPS7owJ55LnL8q;CsQ0k0$YtCpU}nR7uNeMl(~=|d6bkT}g&XD32IS&5 zL`Z=6Wa2qKTL&RnowSf+D9{P-JV8%C`!hw=(jxKtA=ktsE)AsxO z=+h@sXMOux&?aLvgdO#+Xgpi?60|<01Dv88=1d|4Xd9R=E9Rs!J}j|px3T;P+c-i( zP(!>u)B=_V(_^#OR0G7dB^xh)($XmX%!6oX~2qhsJLK|pofUI z^qliyYTl%bl6P%ofDT$R8!=i2P0`W8VkTnJ_y|C6MacDCxL8Y`VS?KwEzZKDP#RlS zCH~9Eh&x>rIbJdue$esQ@oIf9tQ|SIjb|I9LJv<<0r5B1E(9?^9;}${U7F?1f#UW! z`E3Yp2?Oe1At~e-23GFZReK|RJ4oxNbmb3Mjj@-cZCm1rgiK9xVlKz-^>XtNyk??v z>dz$4B%74X4ADy9{UE6qw@{K`;Y_>%G6T~7L9f^(@Vfj&QX|Ect$=}C0sd*_H0Msa z!Kepb0wA82x~hzrC78^Jv!1tUS+bj(el;rHoL)RQPvFI%=R+LytcA?Y z5J?mU!TGf_RBJA&KJo6Xj6$ngFP_ZZ1?&Q>jq11t!_pUE#aQCj=MwNK7%~yJBUMvT zjjgO?PEAO5Yv=e8>R8Vr--RRpu-`hFnGDyD&fb+|N&ZK^9j@{GzD$-T^o8UD{#F}L$M+*9XJ8->aK3Cp2lg;{$oCr2LXR|p9K`+ zr2*6Mra|a1XffXiTqp$$s>rI9A}JGXv>luAY4!q{M%8Y?Cz*=p@trR?sSk*j^v zmN6{oXU_^95QT|VcB!CSRhcS3wH-R-SIG!Ma^R~@B)sgWylIs%3^%M6D>AXkanw^3 zY6||{sIS{k;gf*}u(9MMMrf?qJE>nqlCDmU-}D70<9prdCj1F*p5OBAexQ2F%cu5w z06)@Fs(n5M+8A;Dr41QVaj_pkTV&{uXIS9+;xd^R9y#ucM55O05)@>e5oxS+zK5Dr zO5u!MO2zmA`;q-haV_A77b~ikMCV*Zj^W_0lXeydgW!p@!_3ROuPM=;Js|!H>r-Rd z>QM#Xvu3I4?{?*jh7vSH98|WvBu`fI8oM6^hm>2wV|{zVOOem8iRYyKLeCSqnUgw6 zGMDs)HugtOC?01_a)2PB4KWU}#a(^qX*9Wdfs`wt3%fHmyh75`)vU4kcSQqz$1FLp zsDe`l>_JKlfsV2d=T>IxzFT3;W)AH0x`Mlkzk2AAv;J(>ZB5Z5$%tt4ZT0tiPAazE zuHuG08nok6eT6cpNVURd z(v4wQI@kO>TJ$$dG#3itJL{{(KyMpr@r*^E($YB?y2$A=ZztF_6iQpWbcCm%p zI4R*0OHj;UMXuuh7KyNT#v2PpLqIInC*PB}edv+;FkD(&l9m`TDvHzU^HGgFBGB(O zMQ9}kJcPg0&AjSdl1GmpL~kV|9oJm>2s1BGCKl*KG+Dql0#=iS-&xaj07aRTW4AKo;WKm}(oMY#VJ`xQ3N`-^3fG~cqWF>-&t|N7@tD09()dn;hE#@nOB&;IWA(X=rGPn!PDo9l?CfW@1(eltU#VMnFF2EK3v@j*>nTl3Vv zk9c?}^c2eVOSEzt#`m9o&MyBj%C)%368gY#rz8 z?MG!vEU^C&*Oth~B1D|Nxr(4kayc~mM}Z@hsrW}(ERF3K0kNPk)Roi0lm5n!43A#n z%38zt%gpi)hG|U4xd_U6HkB}3Ba0Rcm-?Dh2DXe-RC(M2jw~9WJ%p#v8ISo-3_T&nhE=v3_aF%sJF}?X5S(k{@vwq@mMrm> zX$YZCw~@A;q45{*CussqqM|Hw9>TEo=Vc?P2|`GPjvT8}UC!YK-DeSxnVs_QaUo82#=m8kY|o zBnu2VC3x>GNUBz#~%RfD&Vgl~!J*li{lan%k7&eOD2Kt(LS}pD*MV z-o_}YFgaav_vepbZp_Ro$7mr*D{A$XfcrI2WUj=k-YhoX4!?cc(7VIP73m*;0i z*%Xnwrl*lnW4Ag=!ki`553%%q$-S^*ww40l?5+E{3u|R&v!vzYPIAYKgxyE|_6bki z7_u25n!GU>#oatr+No{_i{v2Hv3i_##}P>+G#NRLzDN>rYK$&5kfKrR^;^9RGS|&J zpc?4T$|siA6#TmtQ_D;u zW~(ur&VADYY4%zd+>aE)h^MNBBp=$rLty>~C~(e`m%nGDybu;u`7DS607>x&3(?TQ4zP0w<3-%VssE%H<=IE^uwRMY|y9B3e zW$9BW^t38jw3V@IeKeWhT=dN&rkkCqX$q11?M9?7aXU}0w+9qiPTLJYn&+JuP&M_u za;f(fnMncIlL*ycCk7t3`FYMLaMeX46A00h2fLDDU~ToSc@g+ZH7LIJchW~oMGxRF zGYQ5NoEh?4TL#u`4^OhRn!hM8JAteL8LPh$F@5Ehmr!0=5?u`F#<-arY|G8Fal#1q z`>J$}4qIa#`t)k2!c+-i=GbQuc~1X!^h+J>jMUADB;uMK{^<<*vHQQ(v3$xRX11GU zyg9^PKx{3m=(QnzQ^OLHFuaF45bEZ`3dpidOxytqy;0`0jmT4iPt=LnVeH`-^}_n$d}Hg zOsfpjHAgngZAfe_&^;amdXfx49D5w+Rq#2$sG61=jB(;zBP|bh26S=bM%wAU+Ac`T zn0(-JV#LV{B=WQNzf5^wRl*|GpdxWL!AaSp9)xaSDn=tF#AKrHMb434yrJ`)=*#XE zKG0Bv1(VD>@prMlTk2QWA$@ILf(DsfFeV4VDONta!!-FC^8U^0-?>>Xg3Fo3-}2>U z5c>7HQ00Az`YnC2$xX?i+%oC(1Q?v~3hRsesOd(QA3E3}kwlQ{crQx{9 z4kWa~C@}dAC5M&QM9f6|m3&*{A1lou(jG8wMpKdZOEr13;27DeigzY`J)Pc_S?BaQO$`NO2G;aoK1ZNIj0~sUts#_o z1M{)veemDDE)y_9DK9}nGg&E)uVmtBu7FuNS|~Gzjj{;5D6|HVqljM`7E#}wp&?TF z9*}F`JG}Puavfd` z3SovtlD{a&iCZEy>qk7x*{@ri)%Aff`%W-?a|Hr1qA+j-C?#H@r8pGj67xt67 z_C%T;FRo$BdVs%2B;?9i{MWyx+3kDPXgKk93&T;AOnsYT7FdrUQ;s<26aDh-gjOA< z7rh^tJRW#?EPE|A1Dx`2Mcn}6sz`0KdXOMez9@^OYM-XeYWCGOF+X9^{8hW%kpHvQ zNAGizws|3ClmUxp4}v^HLOz=e4db7tgOQQ24{OOOp z;!YHN&loi;ZvxEVTrOI!1h^i#h&Fjt*nUTSH7X?iEgHB`c7*FO+SCPI&<-tUQ?X{l#cwk;Zc??-iRoI1atKrY? zwMS>!jiQ(;l}<@)c&IGP1Iwa;W})Ncp&uOi`q1J?one`6$;<(dWJ<`g$fje6-$hC^ z-n0*t=a5$7USlyA@`7~kxhfF#+w+_o zb3|1io=Ym!l_;+yuZl|R?*4x_Qw3rgwW};dHUJ3%>D5_~+>Gm;Gxtui20}GXA0H;P zhn2C$aq6jt7REWNg;4Run1DM*Rj{L^M?a-a2S0n zyY{f+kIFj%1GNrr0BNEWr(5^z6W9>Z-OAK92x(}ELX_IquC}He@B30il-MIR3|le^s~&sPUZ{{R zDnQixe#Cqfqj@IopndH^_4vTsF56#|p#f>lyWHv`BNP{9ep{ z4*zU_jXmuc-yEJ;VlC^rrV?>@tL=FnC}b?$Q|f;Eu9%r)$RfaB2zTia)SO@3|pD?uP_nuL2H*s#A#T;=L5OU3nrns_0^@&!bxc@L4yz|wsq zd5_OOxs6U6M-B37nxzUG1XSIQsgvp_K$`lUbA}v@)zg>rd*on#H13jmD9Hj0H5o$CO33eAykwat6-_>x}8-%)&HD`a)HTV}m2}h)Xjn z)!yk!)PnNmM5XoggK7dCx=d(~!DGkyWT`YplvdTSr3&DmC*S)j0MGE&a|xa%;vQh!er~m4)y1^GKztQ++{Mf z9OcsmIYsTF1}HyoZZtxV>SraaxC|{e8~hCqm%m-rat34-QkCzV7DyimL&%b)Jns*q zJQ`jT4;Hy=4WIZM$>7}Ncig138hBOHcZ($sSy#Yr6E67RKm#V(L8&{faDbE##8(va z@tzenkHcplm!z#oanC3X!lhfagOG_zIXolb@vK<#5z;>U(0IWl>JOOM4wYJR?SnBO zb~|3+a4)AG^l@5EWU|>Qf0Bp zI4-Q?P^UBRR=!}nQs8j-#zdA^wrAzt2n`}84(BTLzHV-RJ3y-A3ZDqBS#FDDY2Gs& zaxLXB!ZbTTj-LOs$WWsf{+?eN9``HxE|F`JFtX{7xb{TuTQtv8##3M*UC@DJR@&Bh zoI}>cjy(4GV4Lzr{?fmU>WL{s%uRuSwm$sQWY1JYO*!Upy62D{G3;pBoEGw~QPORp zoWXEOA-tVlh*Ykr7GwjJiuSnF>`jKuL5kYS>1h4iw7-z-UYx^p(R3YmrvW?#{B1sA z`5r%UFOlhy-?xKsmChXk5=i95AYUlwS4|iI?;2f=>;$#4qYUb8gSc^)gHNP|9KH}1 zo)UJ{nz=)H`_rq>{>wSaQ?KjKL0gv2e}`pi_ee&%tRS-K(fp68YWy5O;($Le*T#F zQ8)!SL-sA;qe++1p-n<|?Ng#TdQirxJHxTwzAb=JGfArsaTA&rl? z@OYI}cKWw05L*L8H--$Qh=S4r$T3O1{pCb6+XV64O*7}pifs!gU zkcH(KX?c#sv|)$B%x5Q&Ft-Ls^q%oQx$aPN3YTYZRSEXhbiwslR;e{QJnN_jx3-qW&CYa(T6ETy6m&Vh?(_ZP8lWH66a!U?G*N-Sf zk>>s7!_F@SfAcFE9z1t{lcA%&cT}OJfu4d+io$RL*R5wi8me^h6)?>7%ebx$8mY@9 znkg8R`$H<=@3>=JfW!zkDSPRLQ0;zQNj7YkN`a*$CC7I^!MykNup1PvsX8jB5jLy? zzA*QJXgS>W6TdR5n^Z#K7C zTS~e(?k-9BJCLMjuZyFWGJR5?^V&3z@EKn)PQ7INeBM(r1M(bCcy>lTnbtC=#78uD zvuZTR>&YpphIP?6d-G>Ei~F8UyjpZ}sq9^lz%_1~0#2;$rj)TZ1_LhQQ2jZ3-qE{} z>#(g%$PgFL8QA!8=BCJrN0BE;hu`~?FvZrTgHgGAjqZ|0p)(bv?;-L*^9kX^b2IF_ zd-;iN`HD4k5H9h6Wj*;nkv$Z_xIceztcVpBdynNXlb#iWqK5)CRIFwnmpzo6UDA7q z+Khq6qxdojR6{I91{$4h+F~7(2??3r-v@vQ4BC3wvF{Xh`%_%?nUTHI=4EcxD#)(Z;SQqD z241~!8MLQq3%4{^!T~Qw(TteJf7MoSs?_F%tz?5ADvXovBYv^Cl48ht>5MwYQyDB= zz#*6Baz}~PT^k=%Pc4ZKNVQ8O;`EZn*F>XX^Ms(o!0>jr`Co-#pJI0*-@38RLiT_N z`dvTb5uZ{ztjC8L-0^*=K2=(R4?=6sdQK-|qxb=0vhySqpPl>;iR77yZY0XB{l#x0 zgO_?AF!Jn9AJAG2ibjB@!O9;>fRs26LA~e5-C1F0u@&;`4wT$nx==%$u`ShZx>ByP ze8fF)%e$NFvzt*y-ZaMd!KFYW(wW5Qf%smP3Ct73a3!|%Qnxko=?J0*uC7pari9Q$ zQx;3a`vy%f&iX=pae{lu`5lC=@{=X$$b+ z#we;^I}+c2^)3n$&f9CriS;=8ZMWNT-|r8fqw_dA6F;pOSJt@=E_rlJoGHDmH2<#w z>SQT;8egw5G~1w9OH$ED69$Y*&Q-#nXBmTLPg~}32IRnzS}lh7u<8~$m}XykWCR9q zcP~N*B#o|yE=(d9-PD>R7V)g1B${5yi$1XOG~;T1zqKMtA$cxH_A!%G!q>q>u_nKv zIxFeZ;b+>qF3WxgxP~$l12`AN6EtJeur_!(t^6I<-X2b{`A*lL91>^Q#!UX2mP!u) z-tuGIB^n+<_-)u-6AchKVt0l^)tcVvk;+&3*hihzyb!{4way$#PzKS4H2O>~3e(zo zLsPSWS=rL!T@D#T{uE{_H8_b{9b>fa54^#CaR#b?Pu0n3D`M$s!&;&&SF6BvXApb$ zKf6w^NfL;xf4OGIn9}0j>uOoGuaBW4sja9HIQLkaR&JmBXRV+oMUv#13+qhuX?&Bz z_ary{Ry{1D1<6$v+Y6i^N5o9FoQrW7Eed*5M1Gw6>{`m-?=XNzY@uQ&$DMK&Q^91Q z$`C6$_5}?+CqZZQ@-1=fNzzBudRPT#ni1_bQch7D^92rMbt_@Z}?kj9`zGmBbAL z8!|S(HPTYKjbCS6LNSeZCpz!%YnO3BO01^PnpA0L0RUBlxsoT{hlT_n1A&CzXT33H z!(aJX&0@nJvH2EaL@58?PRqs~>#My|DF4!Xq{!j#B6j*J$oXj@%lS;c3H6QCYBtFV zxLzS&(?ko?4SHV^o7}C+>RAq9mfYHg;gAviDXJYj|G9RdXXx2p31xT(}(p{28UfqKFzR<+2QsP9ej&7^yVN3audtpyWj#GzDwu zAWzfRyu2O7vcz3V9DXR5sEd%YuSVP;ZQp_3con06tybnOiX4!*1qZUb@tvluKiUGA zR9AhM+Dlfg`h$w9kQpq@J8eWbQT#+m&~BEEB@~P#1@bd{nnxTcG(_niYy~CSjL;OU ze{?a(52c^`fKNBO993unf=KBD>Wm1FrPNJG#0c~=p(wU>x=9ge4zWI_2hUXBR`c?GGPs;LcUybX=+scO@eVGFo+EFH!cfy&A{<^>>Qlu7D37m(^bo-n_Hc^*{AVyesZ& z=xKKR-$dxAy>ivn_8@czUm8J9Mu10nZ#T z<86S@AmUpR75!EX)}Zl+O+pjmVbghwN;#kkM0YXARQ7Egh#(wg5&N1R1vN^Es?>m% znOZI*KICdED)1%PU|0xpqx67MCOV8t(%A-f*=m)|^b)Qj_MlYvKe-{F)(zGkxheul z`DRXW52JN+0{>D*v0X>Z8aDbBJpfZ*=@DwCdoa}neYRA5#Fnw7@;6tnR)^X`K>oWw z>JK}?;Mws_OsVcAovhOQ?+0!VcCdBN-I7UE`lHa(2DLkyvUu>5Q{=Gowj1aFTP>mq zXFCfA?j~T!t=4#daJwtaZ~Zk0H~*E{$vEyv^l$Z{TPLn$!EA;`x+Xh6b-8`FK=aCP z#@2-a1wdQ8G3`23DZb%ZTSzl8KxdL%G~TS{OhvB(fH(hiADJ*Yi}!dQQYQBRa-3B~ z$zFXNqvcU&+-V_5M-UNR8acc1YPG8PjA8w%e&>6m0YRYhbb z26RQec4fMIyt@GpD342=e2>{)Ri0>G;xrcUu)`BlwR|_fCUHb_^NhH$?=MnW!MDMo zuUbYBJRCzdqvY6786%LJ`%a>LmjBIXRjG{18&8raEoquot-JVo$hBE`Mj4i^`-^#% zB3dHClEkW}>Ykf5r`v*F)C|P+@$GC;Y0e20q+j|1UPiDZE&BDO?hBE&=cBNgD0E&? z@L3?n9EO#6d#@j2FQ{zqw)F88CpdV&nhK#S)iXFX|2T*;&oef`pG?$?`*zyA){_Ss zGD$`fmWyf6+;35du~zkNE~F&Lja{l?CkK{P7jA#-Qv$00`gmge-&dl7@KCoR&7M#S zUxLTR^*c=i{BZSYjpzT#YpZ&X0)r&0a}L#`Q;&dB55EjV^jAbiSkV);VH&VbdBfrzI>0C+_3J*g}(CNuHY7o9LmHPSmK=zIVTxhdb30Vq>@lF1ms98c$u@G`0>O zS`ElIRHOEU$rVb7h?w6MttetKNV0x;tMd^}}hyh^Ce^ ziGDn4q(nu3zV7-d!rTe^=Ak(60)2kJBpn6CgwGU%1##w_5pc@r3ip)<`eXXejzv)6 z0WpJRH0GajbvGxFF8Ixd1o~sS;NQKL%^+~~eqw-yloZQq6?{iI2ZT2g%`_3$BU5U0# z%ZWF<{G}^8J5it4Fb?*(9|abHuMo=guwCHRVYaq7m?om7m>6=LdF)ZYiZSHZCb7bL zk@$?+X-*hOtIk`#UffqGWV~SPpoXtIFu^QR^W3gB-dfmN(xp7d{IyKW!S-T{(wE}l zkx3{e)vh0)xbmxjWA#Y&YlnKOO(RFx7qyv60moGfQ1Hu~m$@*MzL)W*=Z#(9MTfs} z^J&+M(qloKgZMbsqsO|OyJgv!o;r^ibPS;6#gubc%sep{Tac@G${=s zoe1T+|0LoFY>Ob(F&L~a@`)uDMqHbW&`SjaFa2uf-IWV-p1xbA2Oi#L5?AN1zIWt- zuuf43<|fz)3CX<6#-4bRdLF-*H$bmh9eOyA@p^9KUcMCZ95-cqDrA5N5|J%*`Q@Ci zM2Po8u^!PSc(?B&A;U;kNbCj%6@KP)2D1VrrRf9zLgELeqEfW%342w5iUkIbD*7ka zxGOx@`Rc0$Eo#_A3|gpju;d5VrgF9bh0GFv=KjP#>-OM_P`W{?bVz%(e36S^VU%E+ z6B`l0(j*4zqp?Aw0P`E7FqPE)Vqj`HT9Lg$E2geDSBZDNVSlZE7gDK@P3(Z@2Dsrd zmCOe?sw*g{LRVI3L&N=ayxH-$EUY$nMVQY|^n-M%(dko?BN7lInqqIn$NwN(0GZl} z(~om;yh9FtSP%Hg%Zi}+lHkXrqy%9LB4{%&WAC#eL+EMDNm_BxG7PJMvQEipy=|jB zBimI9ArM<@inlGzRDXA~pU<)$PW;eJaoj9@qL{EpLZwcl1poDZBw!^)IW)S0Lp$LJ zq863!lwW6PLKm%;zJ~;xNRKp9;|!#vpR84nxouhaZfT}qLu`m)4eYh0|MyCfWrgSq zX72qGDJAx5p!S*5L%(;vf`+UtD@oF!LRx&7GO`Cb_3=6fcW&SE%a>DiaP;ZQ{}D*V zhIUR_>?A1#zsPHWNi(yBWN053bY$-FGoV2+L z-`FvQhOz62mvP+ypxYN^-6PB&2JLtgl?`Q++%aS!ZJ8m~{dxzYD+GXV%xiNMdsGKL zGp(!kTl972HvBUHa`<1eR(D;BZ|*RU7_=5z89m-h)%}QWVJ#iVwsE~RXgNM(w7HL2 znI6rJVBEOb1E--t)8^k`!VFz4S_}M?873*B`|@>U7R!QT8aV1jTlK}5LnX50LCt;* zAD ztXcL(RVbaqkw`QesO>O)ZW_sBO^7WwJSj}YBUKEI4Aywp=1ztDo)y6z)Fu?ijI|@$`eetV4l8&MP8`dw!NYF?&kuz^EkV__&{J`P?4Wq0a@k z_mF*i0{Eu#lm@_ew_HSodm$W%z)}iBu!#|ioo&KS6=Y#ESIGOrt`=$vGB5WC@iC9! zxxYMk;?aE{IW&Yo>~EC>>tva?WoBPq!|XBR_(FO-DlxZb3~L7bw%+L@8`FVxTcbA> zt##?=HR6u+KE#Y*W#wRC?X2?npp=J`snqzat9?{B=4c)hBoag?Y=ERPpOqm(A7Cyy ze4!24nA;JqOPNu-*l*O6P`R&(wUf@_kD|}a58mIDR!*E;fCfax3@(eHAbJUZLY`F~ z?z8BC7UG*oycKv-QpeA5dH)tK&!*QMfv8)Irbkm{`a~HIw*76lzazgm^N`+q`Zt!& z@+ymP&h_{f+Z$;&PXE4wShvBmd@A+3yK<32Vk@#Dz=j;#y}%19B!|&VXeuCEcONkYRWk# z)jRbFN7+E$s`2^*JQ+AL+%=~|bytg8u9=Bigl6iec@m!j*Y29lZxl~1TTG+emoS$S zZ%rOh^6u1P4&-ABR4)>tJf{JpAASiIu~=VAfmUL%+WWC71F7AV`aeT+`L{tJ%#2n}=9jf4;XM~@G|nBURlMw1Z@y3QLnDSvf!1=U!Y z5rA&2XghB?L2uvW?m(yS+Z{oKdJ{}xi9%s2-I}#V;R?Q^$`{g*SIHv9E{}gpXgkh| zZG@Egwf9T?zBhRx8(8;Q!p12!4-c9@*mXx*7O``uzwzBOIk5RCt@c-QWLdyT)Zn~` ziv4@RM=UH}Z`->qgg|{8s1ypsjrBJ+zXpoE%g#V{{0?>1WZhgHCA zpEB_}YnDvML~N6qRH29lpmOg=Tn>^psBqr#58P78SP&(ixdx13cTE`>W)ZbgQ!NpA z`ZH;BR6CXR`AK(?88yv?{N!JH*aFaXDeca7B#lWVK}<<7J&V5>-1{5e$(Kq5azgXz z;aUhRXY0o;Hpvyq=KRG@LYO3-kGA@t$ivZPJ)l~issC0)`?^lzbhu=}+B94&rK4n1 zXsv5nm6Ad0LEe8dB=^4)9F%qhfiOU$baEnbXG|D?Y( zDHf5RiVf6Ne8NQO18sq{lk4yaHBeAK6jp-tXqvxNm9kVpaqoP5<(%OZR2SwdCI=t% zl_^EZs(F2SUvl4$sbTByXDrqkj^2$j^FxS5g`N z!8#pEj&A^>nowI7&`@V6-V=h!{$d`3?ELd+W%@k1&dg>n$-}q7afgv@ZM_4`aPBpD zD7Z@AK#&P!cvfl>&#DF(Ev55vyit%mWihcz4Vf_p>>vF*PM6Q7-QQ1U%5MBI#y%hQ z0c|eo#aiTdSjjDN#!kQj$9gH!&f|bj*|kmU7oyHUjD%n>l0>Kn4dDu%+}{Phg-o$H zDTDlg&2gxAP2Kg==!z1?^$TxKN@K_OBins~gHqsLaHwDByF_VnX-%iT>uchf;xUqn ztHpyrsb!*&=o%Msf&e_N0RfQ`Oytbgxb-o^#w4LzoFw2JGy$k7N$2mvbj!==k8cC`{~W-F>F_;B-sBG|TaMnreXDeyU0?Exy!^O6}%{9i0d z{Oiq(k5v$iFNxpt`0pQ7I;oK^5?ka#sy)8mi8_*F4@FUZ5(2ku@=6}Pq-~hxwjS4& z&U%hB4A?QnToJ)tif)|mNMP*8@lC6g<(toA9XB_!qhgn0ZiB<~C1ah|HCG^^S*xwq zm14)mHuAFT#ymlmHyVWe^$|gZW>ULe_^p|whwL#xXjtA=gVa=RnV%R)vuL(&M0aAz zwBq_4@izyX*9Z+AD6G7ZI9`$frHc?t z128b}hsPJMy7(7PxMo7fNMjP?IUH;EU_1GfLhRZ;eVn@ps8(q8pf5mARG@b(=(wHJ z!uV=It^{yJ`Bj5RzrjVFkGGxw_Vd44J1`Cn{OP*;TR+Q7-+tWY zeHc4J3&g&Qn(5TvlHyQ5_8%T{GP9-#MQTrw6%ug+K5(pNyoBQ=7)NerB_)J_0S;%c z%c`!$AE1~Kuw=;}|E+?>Wl85kscY+R+htpwpQL`XS*vc0=1WUkq=TuH4WH?_gyiTK z@Q#j}IAhqDR+0zCc4TJ=3T$Y+{;X1_n9ruBLbpQHbhtklHE&j6E^p+Rjc_2L58iv! z<=i6MRGgVd{w>|*{V`>4wi$vlfLa=G9TM>d+F<#}yIR6zkUYV~lC8811!uzpeiUSB zr<4D?nFjY0cUc->ph4g-Ij~=VP;(sAf3{CpbhDWQhNEV7StID+E-lZFEqMiRFJ&&Z z^Pk)4JbE~{Adna^A^$$OY<1qB5Y!zXraf+++f;rT_iM#0^(E=PlVxSLRr++rVia3n zXTF)x4d$sAaCZ=B_S2lbOp-&7rQ;=hII_mzj)dOwm_C{Ql&Ft1x=`f2YuVO)7qGfM znLVi@-NinoblL~=kI}MN-vEh~oyTH^&0N~t>Df8? zw5;SmQDtM?&eXkm3J=|S-M189 z^mtP|HNV(^#BE9=j7~$q!00P~2-h5luI;5dWS#Mb+sI=eK0@R{aR5$)F zwu0=|TFpK4wZcy_J^oQfB$tH}xgd(`?G<#w2>+%-p0YQf7<~YY(l$bO zW&-w=>ZTffjd=D~^|j-Tt9);^yxg4q5wt}R?b^C_)<_F(a3g!`>%}ZqSx)27B}3Hy zuLR@wHogVR6>sWc0|CfY=pKBC#j{(Xb?#e*7wZ9Diu~}W*Xx$+wUQJi^8lwWayJN) z*m1Vsn-+({3}GgSuLnT#ox(NkHQ#Au%7ysGEuUEL|h`o_Hfqg&uInK5T2vDx41-j4yEXB|(u zapJO3&u_NRs1)B>IW6+nnIcbCw5jqf2L~jwO(YN{>KP3LU$;=S%W~`P<%eMp${xK zjcq|vq-~VoqApXjF3M^1Oeg)}hb5WJGa8FWDbzIE2j%*UIFBTkNehc4(mMIO<3f@pbD{N3*N`$}oK9)Ld*Po)LR7 zgN(p-$NJ``MX`MrSXD~_SV|5*LU-L!0UsjxUBCZQmgor-{Dtq)(CsBi)rZ|2KKn@3 zn9k7PJa9g(kb#~I&`H}lp_6>a5ZkW#PlNKk3JKD#xDEY#W@uh%0rwe^m(s~hxFe(3 z@3nCRR?1vd!?5F1&gH7pwksC;@rL6c*~he$udcmzw)_MoAp9hF62T+dw{HOH;7r00 z0#fZdfGx`HeZe_RonCid3d)Do@UsPJCXMZW;7YC5%k4roHnZ_77mr_B9wj$Oj10hpe0oX1k2O;y zAy!5<1o;F1$ul`mHwn*GRAMgADcINnBDJLEF=TtLc?7oebrGMao(y{95*nR9NG8bb zG3xr(Vvz`}4&W>t`4^G&@P@E&?7BRJ9hb#?uw1-F@g%F zx3jH_h%1XeCW#%!kNRjQ4C4?QN{zB#Y0Y;2d2b%-)poD%JaQHyT`>6mytBg}Uyi@q zvr=|*tQjhhgK)ODeZ1u*RWKQ)FUze`%cLir#lPs4ba9wb_3cMDuRlf-9`LL`t1`-J ziY^jD@t@Y7IaBTvpv!jZB&bBrxs+T(h~I+H(7$md7o9Qmm^$`WW6f>UsT6|t9lPsL`hqv=COLC9Thi-eYC0|=x%}7%7&hzsCGFtM5JoZ{ literal 0 HcmV?d00001 diff --git a/worlds/wargroove2/docs/en_Wargroove 2.md b/worlds/wargroove2/docs/en_Wargroove 2.md new file mode 100644 index 000000000000..95982541c2ca --- /dev/null +++ b/worlds/wargroove2/docs/en_Wargroove 2.md @@ -0,0 +1,59 @@ +# Wargroove 2 (Steam, Windows) + +## Where is the settings page? + +The [player settings page for this game](../player-options) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? + +This randomizer shuffles units, map events, factions and boosts. It features a custom, non-linear campaign with 4 +final levels and 4 branching paths. The player cannot beat the final levels without specific items scattered throughout +the branching paths. Certain levels on these paths may require specific units or items in order to progress. +Where levels appear in the campaign are randomized. + +## What items and locations get shuffled? + +1. Every buildable unit in the game (except for soldiers and dogs, which are free). +2. Commanders available to certain factions. If the player acquires the Floran Commanders, they can select any commander +from that faction. +3. Income, Groove and Commander Defense boosts that provide the player with extra income, extra commander groove +or extra commander defense. +4. Special map events like the Bridges Event or the Walls Event, which perform special actions in certain levels. +5. 28 levels are shuffled into 4 branching paths. A random final level will appear at the end of each path. + +## Which items can be in another player's world? + +Any of the above items can be in another player's world. + +## How does Death Link work in Wargroove 2? + +A player will send a death link if they are defeated after turn 1 and on an AI's turn. +Certain side goals can be reached without the ability to complete the level. Resigning to exit after completing a side +goal is standard practice. When the player receives a death link from another player, they will be eliminated when the +next move is made. + +## When the player receives an item, what happens? + +When the player receives an item, a message will appear in Wargroove 2 with the item name and sender name, once an +action is taken in game. + +## What is the goal of this game when randomized? + +The goal is to beat 1-4 final levels ending with the name `Finale` by finding the `Final North`, `Final East`, +`Final South`, or `Final West` depending on which Finale the player is playing. +All final levels require the `Final Center`. The `Northern Finale` for example, requires `Final North` and +`Final Center`, but the `Western Finale` requires `Final West` and `Final Center`. + +## Contributing levels to the randomizer + +Anybody can contribute levels to the Wargroove 2 randomizer. +A contributor's guide can be found +[here](https://docs.google.com/documen/d/1ovGGTKYJsJcLH4kLZ2k2FQes82sQ96C35gF6j_akCf4/edit?usp=sharing). + +## Unique Local Commands + +The following commands are only available when using the Wargroove2Client to play with Archipelago. + +- `/resync` Manually trigger a resync. +- `/commander` Set the current commander to the given commander. diff --git a/worlds/wargroove2/docs/wargroove2_en.md b/worlds/wargroove2/docs/wargroove2_en.md new file mode 100644 index 000000000000..fcfc3cdeb7d6 --- /dev/null +++ b/worlds/wargroove2/docs/wargroove2_en.md @@ -0,0 +1,92 @@ +# Wargroove 2 Setup Guide + +## Required Files + +- Wargroove 2 installed through Steam on Windows + - Only the Steam Windows version is supported. MAC and Switch are not supported. +- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) + +## Backup playerProgress files +`playerProgress` and `playerProgress.bak` contain save data for all of your Wargroove 2 campaigns. +Backing up these files is strongly recommended in case they become corrupted. +1. Type `%appdata%\Chucklefish\Wargroove2\save` in the file browser and hit enter. +2. Copy the `playerProgress` and `playerProgress.bak` files and paste them into a backup directory. + +## Update host.yaml to include the Wargroove 2 root directory + +1. Look for your Archipelago install files. By default, the installer puts them in `C:\ProgramData\Archipelago`. +2. Open the `host.yaml` file in your favorite text editor (Notepad will work). +3. Put your Wargroove 2 root directory in the `root_directory:` under the `wargroove2_options:` section. + - The Wargroove 2 root directory can be found by going to + `Steam->Right Click Wargroove 2->Properties->Installed Files->Browse` and copying the path in the address bar. + - Paste the path in between the quotes next to `root_directory:` in the `host.yaml`. + - You may have to replace all single \\ with \\\\. +4. Start the Wargroove 2 client. + +## Installing the Archipelago Wargroove 2 Mod and Campaign files + +1. Shut down Wargroove 2 if it is open. +2. Start the ArchipelagoWargroove2Client.exe from the Archipelago installation. +This should install the mod and campaign for you. +3. Start Wargroove 2. + +## Verify the campaign can be loaded + +1. Start Wargroove 2 from Steam. +2. Go to `Story->Campaign->Custom->Archipelago 2` and click play. You should see the first level. + +## Starting a Multiworld game + +1. Start the Wargroove 2 Client and connect to the server. Enter your username from your +[settings file.](/games/Wargroove/player-settings) +2. Start Wargroove 2 and play the Archipelago 2 campaign by going to `Story->Custom->Archipelago 2`. + +## Ending a Multiworld game +It is strongly recommended that you delete your campaign progress after finishing a multiworld game. +This can be done by going to the level selection screen in the Archipelago 2 campaign, hitting `ESC` and clicking the +`Delete Progress` button. The main menu should now be visible. + +## Updating to a new version of the Wargroove 2 mod or downloading new campaign files +First, delete your campaign progress by going to the level selection screen in the Archipelago campaign, +hitting `ESC` and clicking the `Delete Progress` button. +Next, go to `Custom Content->Create->Campaign`, click the `Archipelago 2` campaign and click the `Delete` button. + +Follow the `Installing the Archipelago Wargroove 2 Mod and Campaign files` steps again, but look for the latest version +to download. In addition, follow the steps outlined in +`Wargroove 2 crashes when trying to run the Archipelago 2 campaign` when attempting to update the +campaign files and the mod. + +## Troubleshooting + +### The game is too hard +`Go to the campaign overview screen->Hit escape on the keyboard->Click adjust difficulty->Adjust the setttings` + +### The mod doesn't load +Double-check the mod installation under `%appdata%\Chucklefish\Wargroove2\mods`. There should be 3 `.dat` files in +`%appdata%\Chucklefish\Wargroove2\mods\ArchipelagoMod`. Otherwise, follow +`Installing the Archipelago Wargroove 2 Mod and Campaign files` steps once more. + +### Wargroove 2 crashes or there is a lua error +Wargroove 2 is finicky, but there could be several causes for this. If it happens often or can be reproduced, +please submit a bug report in the tech-support channel on the [discord](https://discord.gg/archipelago). +Wargroove 2 may report an error when retrying a level. This is currently a bug in the game and not the mod. + +### Wargroove 2 crashes when trying to run the Archipelago 2 campaign +This is caused by not deleting campaign progress before updating the mod and campaign files. +1. Go to `Custom Content->Create->Campaign->Archipelago 2->Edit` and attempt to update the mod. +2. Wargroove 2 will give an error message. +3. Go back to `Custom Content->Create->Campaign->Archipelago 2->Edit` and attempt to update the mod again. +4. Wargroove 2 crashes. +5. Go back to `Custom Content->Create->Campaign->Archipelago 2->Edit` and attempt to update the mod again. +6. In the edit menu, hit `ESC` and click `Delete Progress`. +7. In the edit menu, hit `ESC` and click `Mods`. +8. Uncheck the `Archipelago Mod` box, check it again and then click `Save and Reload Map` +9. If the above steps do not allow you to start the campaign from `Story->Campaign->Custom->Archipelago 2` replace +`playerProgress` and `playerProgress.bak` with your previously backed up files. + +### Mod is out of date when trying to run the Archipelago campaign +Please follow the above steps in `Wargroove 2 crashes when trying to run the Archipelago 2 campaign`. + +### Using undo turn ignores the income boost or causes bugs +Undoing a turn is bugged in Wargroove 2 and not supported in the randomizer. +There is no way to change how many times the undo action can be used. \ No newline at end of file diff --git a/worlds/wargroove2/levels/A_Ribbitting_Time.json b/worlds/wargroove2/levels/A_Ribbitting_Time.json new file mode 100644 index 000000000000..fe4430a8d18b --- /dev/null +++ b/worlds/wargroove2/levels/A_Ribbitting_Time.json @@ -0,0 +1 @@ +{"Map_Tile_3_14":{"terrain":"sea"}, "Map_Tile_1_11":{"unit":{"id":16, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"commander_mercia", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":11, "x":1}, "grooveId":"heal_aura", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":11, "x":1}, "unitClass":{"id":"commander_mercia", "transportTags":{}, "weaponIds":["merciaSword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":500, "weapons":[{"id":"merciaSword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["commander", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":false, "resourceCost":3, "isCommander":true, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":250, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"sea"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"ocean"}, "Map_Tile_8_6":{"terrain":"beach"}, "Map_Tile_2_12":{"unit":{"id":22, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"dog", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":12, "x":2}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":12, "x":2}, "unitClass":{"id":"dog", "transportTags":{}, "weaponIds":["bite"], "moveRange":5, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":150, "weapons":[{"id":"bite", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["dog", "type.ground.light", "animal"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"beach"}, "Map_Tile_1_3":{"terrain":"ocean"}, "Map_Tile_2_10":{"terrain":"beach"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_13_11":{"unit":{"id":5, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":11, "x":13}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":11, "x":13}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"bridge"}, "Map_Tile_9_6":{"terrain":"beach"}, "Map_Tile_4_9":{"terrain":"ocean"}, "Map_Tile_0_6":{"terrain":"beach"}, "Map_Tile_9_13":{"terrain":"ocean"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"plains"}, "Objectives":["Make a bridge using a frog.", "Steal from the village (Requires Thief and Frog).", "Defeat all enemy Trebuchets (Requires Frogs)."], "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"ocean"}, "Map_Tile_0_9":{"terrain":"road"}, "Map_Tile_2_11":{"unit":{"id":17, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":11, "x":2}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":11, "x":2}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"beach"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"beach"}, "Counters":{}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_13_12":{"unit":{"id":2, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":12, "x":13}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":12, "x":13}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_4_2":{"terrain":"ocean"}, "Map_Tile_5_12":{"unit":{"id":7, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"hq", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":12, "x":5}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"garrison", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":12, "x":5}, "unitClass":{"id":"hq", "transportTags":{}, "weaponIds":{}, "moveRange":0, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":3000, "weapons":{}, "tags":["structure"], "aliasId":"", "movementType":"land_building", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":true, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_3_13":{"terrain":"beach"}, "Map_Tile_9_5":{"terrain":"beach"}, "Map_Tile_3_11":{"terrain":"forest"}, "Map_Tile_13_6":{"terrain":"road"}, "Map_Tile_14_2":{"terrain":"road"}, "Map_Tile_12_12":{"unit":{"id":3, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":12, "x":12}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":12, "x":12}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_13_1":{"terrain":"bridge"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"ocean"}, "Map_Tile_14_10":{"terrain":"ocean"}, "Map_Tile_7_11":{"terrain":"ocean"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"ocean"}, "Map_Tile_9_1":{"terrain":"road"}, "Map_Tile_4_0":{"terrain":"ocean"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"road"}, "Map_Tile_7_5":{"terrain":"beach"}, "Map_Tile_4_7":{"terrain":"bridge"}, "Map_Tile_1_12":{"unit":{"id":18, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":12, "x":1}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":12, "x":1}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"sea"}, "Map_Tile_1_10":{"unit":{"id":21, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":10, "x":1}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":10, "x":1}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_13_4":{"unit":{"id":13, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":4, "x":13}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":4, "x":13}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_0_12":{"unit":{"id":19, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":12, "x":0}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":12, "x":0}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"beach"}, "Map_Tile_14_3":{"unit":{"id":10, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"trebuchet", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":3, "x":14}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":3, "x":14}, "unitClass":{"id":"trebuchet", "transportTags":{}, "weaponIds":["trebuchetSling"], "moveRange":5, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":1100, "weapons":[{"id":"trebuchetSling", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":false, "terrainExclusion":{}, "maxRange":5, "blockedByEnemies":false, "minRange":3, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["trebuchet", "type.ground.heavy"], "aliasId":"", "movementType":"wheels", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":3, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"beach"}, "Map_Tile_13_14":{"terrain":"road"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_0_2":{"unit":{"id":26, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"fortified_city", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":2, "x":0}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"fortified_garrison", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":2, "x":0}, "unitClass":{"id":"fortified_city", "transportTags":{}, "weaponIds":{}, "moveRange":0, "canReinforce":true, "canBeCaptured":true, "verbCostMultiplier":1.0, "cost":1000, "weapons":{}, "tags":["fortified_city"], "aliasId":"", "movementType":"land_building", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":true, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_5_4":{"terrain":"ocean"}, "Map_Tile_13_10":{"terrain":"bridge"}, "Map_Tile_13_9":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"beach"}, "Map_Tile_9_4":{"terrain":"ocean"}, "Map_Tile_2_4":{"terrain":"ocean"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"beach"}, "Locations":{"1":{"id":1, "positions":[{"y":11, "x":12}, {"y":12, "x":12}, {"y":13, "x":12}, {"y":12, "x":13}, {"y":11, "x":13}, {"y":14, "x":12}, {"y":14, "x":13}, {"y":12, "x":11}, {"y":13, "x":11}, {"y":14, "x":11}, {"y":12, "x":14}, {"y":13, "x":14}, {"y":14, "x":14}], "interactable":false, "setArea":null, "getArea":null, "name":"P2 Shuffle 1", "centre":{"y":13, "x":12}}, "2":{"id":2, "positions":[{"y":4, "x":14}, {"y":4, "x":13}, {"y":4, "x":12}, {"y":3, "x":12}, {"y":3, "x":13}, {"y":3, "x":14}, {"y":2, "x":14}, {"y":2, "x":13}, {"y":1, "x":14}, {"y":1, "x":13}, {"y":1, "x":12}, {"y":1, "x":11}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":0, "x":10}, {"y":0, "x":9}, {"y":2, "x":9}], "interactable":false, "setArea":null, "getArea":null, "name":"P2 Shuffle 2", "centre":{"y":2, "x":12}}, "3":{"id":3, "positions":[{"y":14, "x":9}, {"y":14, "x":8}, {"y":13, "x":8}, {"y":12, "x":8}, {"y":12, "x":9}, {"y":11, "x":9}, {"y":13, "x":9}, {"y":11, "x":8}, {"y":11, "x":7}, {"y":12, "x":6}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":12, "x":7}, {"y":11, "x":6}, {"y":14, "x":7}, {"y":14, "x":6}, {"y":14, "x":5}], "interactable":false, "setArea":null, "getArea":null, "name":"P1 Frog Spawn", "centre":{"y":13, "x":7}}, "4":{"id":4, "positions":[{"y":4, "x":3}], "interactable":false, "setArea":null, "getArea":null, "name":"Frog Bridge 1", "centre":{"y":4, "x":3}}, "0":{"id":0, "positions":[{"y":12, "x":2}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":12, "x":0}, {"y":11, "x":0}, {"y":10, "x":0}, {"y":10, "x":1}, {"y":11, "x":1}, {"y":11, "x":2}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":9, "x":1}, {"y":9, "x":0}], "interactable":false, "setArea":null, "getArea":null, "name":"P1 Shuffle", "centre":{"y":11, "x":1}}, "5":{"id":5, "positions":[{"y":4, "x":2}], "interactable":false, "setArea":null, "getArea":null, "name":"Frog Bridge 2", "centre":{"y":4, "x":2}}}, "Map_Tile_9_3":{"terrain":"beach"}, "Map_Tile_4_4":{"terrain":"ocean"}, "Map_Tile_13_0":{"terrain":"beach"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"beach"}, "Map_Tile_13_3":{"unit":{"id":11, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"archer", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":3, "x":13}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":3, "x":13}, "unitClass":{"id":"archer", "transportTags":{}, "weaponIds":["bow"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":500, "weapons":[{"id":"bow", "horizontalAndVerticalExtraWidth":0, "canAttackAir":true, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":3, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["archer", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.3500000238419, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_12_14":{"terrain":"road"}, "Map_Tile_11_9":{"terrain":"road"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_12_11":{"unit":{"id":6, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":11, "x":12}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":11, "x":12}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"bridge"}, "Map_Tile_7_12":{"terrain":"sea"}, "Map_Tile_4_5":{"terrain":"ocean"}, "Map_Tile_12_10":{"terrain":"bridge"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_0_11":{"unit":{"id":20, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":11, "x":0}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":11, "x":0}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"beach"}, "Map_Tile_5_14":{"terrain":"sea"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_8_12":{"terrain":"ocean"}, "Map_Tile_5_2":{"terrain":"ocean"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"forest"}, "Map_Tile_11_10":{"terrain":"beach"}, "Map_Tile_4_13":{"terrain":"beach"}, "Map_Tile_9_14":{"terrain":"ocean"}, "Map_Tile_5_13":{"terrain":"beach"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"ocean"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_12_4":{"unit":{"id":14, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":4, "x":12}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":4, "x":12}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_8_8":{"terrain":"beach"}, "Map_Tile_7_13":{"terrain":"reef"}, "Map_Tile_10_9":{"terrain":"road"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"road"}, "Map_Tile_12_1":{"terrain":"bridge"}, "Map_Tile_6_3":{"terrain":"mountain"}, "Map_Size":{"y":15, "x":15}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_12_13":{"unit":{"id":4, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":13, "x":12}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":13, "x":12}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_11_6":{"unit":{"id":9, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"knight", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":6, "x":11}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":6, "x":11}, "unitClass":{"id":"knight", "transportTags":{}, "weaponIds":["lance"], "moveRange":6, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":600, "weapons":[{"id":"lance", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["knight", "type.ground.heavy"], "aliasId":"", "movementType":"riding", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":2, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"road"}, "Map_Tile_8_11":{"terrain":"ocean"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"beach"}, "Map_Tile_6_12":{"terrain":"sea"}, "Map_Tile_11_2":{"terrain":"beach"}, "Map_Tile_0_8":{"terrain":"beach"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"beach"}, "Map_Tile_3_3":{"terrain":"beach"}, "Map_Tile_2_3":{"terrain":"beach"}, "Map_Tile_6_13":{"terrain":"sea"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"beach"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_10_12":{"unit":{"id":8, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"trebuchet", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":12, "x":10}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":12, "x":10}, "unitClass":{"id":"trebuchet", "transportTags":{}, "weaponIds":["trebuchetSling"], "moveRange":5, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":1100, "weapons":[{"id":"trebuchetSling", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":false, "terrainExclusion":{}, "maxRange":5, "blockedByEnemies":false, "minRange":3, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["trebuchet", "type.ground.heavy"], "aliasId":"", "movementType":"wheels", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":3, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_1_8":{"terrain":"bridge"}, "Map_Tile_10_10":{"terrain":"beach"}, "Map_Tile_14_1":{"terrain":"road"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"beach"}, "Map_Tile_7_10":{"terrain":"ocean"}, "Map_Tile_10_4":{"terrain":"beach"}, "Map_Tile_13_5":{"terrain":"road"}, "Flags":{"0":0}, "Map_Tile_2_14":{"terrain":"beach"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"ocean"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Name":"A Ribbitting Time", "Map_Tile_4_6":{"terrain":"beach"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"beach"}, "Map_Tile_9_7":{"terrain":"reef"}, "Map_Tile_10_2":{"terrain":"beach"}, "Map_Tile_10_3":{"terrain":"beach"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_9_0":{"unit":{"id":25, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"archer", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":0, "x":9}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":0, "x":9}, "unitClass":{"id":"archer", "transportTags":{}, "weaponIds":["bow"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":500, "weapons":[{"id":"bow", "horizontalAndVerticalExtraWidth":0, "canAttackAir":true, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":3, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["archer", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.3500000238419, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_8_14":{"terrain":"ocean"}, "Player_Count":2, "Map_Tile_8_13":{"terrain":"ocean"}, "Map_Tile_7_14":{"terrain":"sea"}, "Map_Tile_8_9":{"terrain":"bridge"}, "Map_Tile_5_5":{"terrain":"beach"}, "Map_Tile_10_1":{"terrain":"bridge"}, "Map_Tile_0_4":{"terrain":"ocean"}, "Map_Tile_1_14":{"terrain":"beach"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_4_3":{"terrain":"ocean"}, "Map_Tile_12_3":{"unit":{"id":15, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"spearman", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":3, "x":12}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":3, "x":12}, "unitClass":{"id":"spearman", "transportTags":{}, "weaponIds":["spear"], "moveRange":3, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":250, "weapons":[{"id":"spear", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["spearman", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_4_1":{"terrain":"ocean"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"reef"}, "Map_Tile_6_9":{"terrain":"road"}, "Map_Tile_8_5":{"terrain":"beach"}, "Map_Tile_0_14":{"terrain":"beach"}, "Author":"Fly Sniper", "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_14_4":{"unit":{"id":12, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"soldier", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":4, "x":14}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":4, "x":14}, "unitClass":{"id":"soldier", "transportTags":{}, "weaponIds":["sword"], "moveRange":4, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":100, "weapons":[{"id":"sword", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["soldier", "type.ground.light"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_1_13":{"unit":{"id":23, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"wagon", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":13, "x":1}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":13, "x":1}, "unitClass":{"id":"wagon", "transportTags":["type.ground.light", "type.amphibious.light", "type.ground.hideout", "airtrooper"], "weaponIds":{}, "moveRange":12, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":300, "weapons":{}, "tags":["wagon", "type.ground.heavy", "transport"], "aliasId":"", "movementType":"wheels", "loadCapacity":1, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":2, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"beach"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"beach"}, "Triggers":[{"id":"Export", "actions":[{"id":"ap_export", "parameters":["51243", "A Ribbitting Time", "Fly Sniper", "Make a bridge using a frog.", "Steal from the village (Requires Thief and Frog).", "", "Defeat all enemy Trebuchets (Requires Frogs)."], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"start_of_match", "isIntro":false, "conditions":{}}, {"id":"Init", "actions":[{"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true}, {"id":"set_location_highlight", "parameters":["4", "ground_pulse_only", "green"], "enabled":true}, {"id":"set_location_highlight", "parameters":["5", "ground_pulse_only", "green"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"start_of_match", "isIntro":false, "conditions":{}}, {"id":"$trigger_default_defeat_no_units", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"oncePerPlayer", "isIntro":false, "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true}]}, {"id":"$trigger_default_defeat_commander", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"oncePerPlayer", "isIntro":false, "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}]}, {"id":"$trigger_default_defeat_hq", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"oncePerPlayer", "isIntro":false, "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}]}, {"id":"Defeat (No Trebs)", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"oncePerPlayer", "isIntro":false, "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "trebuchet", "-1"], "enabled":true}]}, {"id":"$trigger_default_victory", "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"oncePerPlayer", "isIntro":false, "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}]}, {"id":"P1 Victory (253030)", "actions":[{"id":"ap_location_send", "parameters":["253030"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"once", "isIntro":false, "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}]}, {"id":"Frog Bridge 1", "actions":[{"id":"remove_units", "parameters":["frog", "4", "P1", "1", "1"], "enabled":true}, {"id":"activate_flood", "parameters":["4", "plains", "default", "", "0", "1", "1"], "enabled":true}, {"id":"set_location_highlight", "parameters":["4", "ground_pulse_only", "green"], "enabled":true}, {"id":"set_map_flag", "parameters":["0", "1"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"once", "isIntro":false, "conditions":[{"id":"unit_presence", "parameters":["P1", "0", "1", "frog", "4"], "enabled":true}]}, {"id":"Frog Bridge 2", "actions":[{"id":"remove_units", "parameters":["frog", "5", "P1", "1", "1"], "enabled":true}, {"id":"activate_flood", "parameters":["5", "plains", "default", "", "0", "1", "1"], "enabled":true}, {"id":"set_location_highlight", "parameters":["5", "ground_pulse_only", "green"], "enabled":true}, {"id":"set_map_flag", "parameters":["0", "1"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"once", "isIntro":false, "conditions":[{"id":"unit_presence", "parameters":["P1", "0", "1", "frog", "5"], "enabled":true}]}, {"id":"Frog Bridge Constructed (253031)", "actions":[{"id":"dialogue_box_simple", "parameters":["happy", "sweetcheeks", "Hop on the frog's back and that village is ours!", "1", "Master Frogger"], "enabled":true}, {"id":"ap_location_send", "parameters":["253031"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"once", "isIntro":false, "conditions":[{"id":"check_map_flag", "parameters":["0", "1"], "enabled":true}]}, {"id":"Village Robbed (253032)", "actions":[{"id":"ap_location_send", "parameters":["253032"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"once", "isIntro":false, "conditions":[{"id":"unit_presence", "parameters":["current", "0", "1", "thief_with_gold", "-1"], "enabled":true}]}, {"id":"Spawn Frogs", "actions":[{"id":"ap_spawn_unit", "parameters":["frog", "3", "P1", "1", "1", "3", "1", "undefined", "centre"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "recurring":"start_of_match", "isIntro":false, "conditions":[{"id":"ap_has_item", "parameters":["252019", "0", "1"], "enabled":true}]}], "Map_Tile_0_13":{"terrain":"beach"}, "Map_Tile_7_9":{"terrain":"road"}, "Map_Tile_0_3":{"terrain":"ocean"}, "Map_Tile_7_2":{"terrain":"plains"}, "Player_2":{"recruit_frog":true, "recruit_balloon":true, "recruit_spearman":true, "gold":100, "recruit_thief":true, "recruit_griffin_walking":true, "recruit_soldier":true, "recruit_dog":true, "recruit_giant":true, "team":1, "recruit_merman":true, "recruit_dragon":true, "recruit_wagon":true, "recruit_turtle":true, "recruit_kraken":true, "recruit_caravel":true, "recruit_harpy":true, "recruit_witch":true, "recruit_rifleman":true, "recruit_harpoonship":true, "recruit_warship":true, "recruit_trebuchet":true, "recruit_ballista":true, "recruit_knight":true, "recruit_mage":true, "recruit_archer":true, "recruit_travelboat":true}, "Map_Tile_8_3":{"terrain":"beach"}, "Map_Tile_0_10":{"unit":{"id":24, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"dog", "loadedUnits":{}, "playerId":0, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":10, "x":0}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":10, "x":0}, "unitClass":{"id":"dog", "transportTags":{}, "weaponIds":["bite"], "moveRange":5, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":150, "weapons":[{"id":"bite", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "terrainExclusion":{}, "maxRange":1, "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["dog", "type.ground.light", "animal"], "aliasId":"", "movementType":"walking", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"beach"}, "Map_Tile_7_3":{"terrain":"forest"}, "Map_Tile_4_10":{"terrain":"beach"}, "Map_Tile_0_5":{"terrain":"ocean"}, "Map_Tile_3_8":{"terrain":"sea"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_3_9":{"terrain":"sea"}, "Map_Tile_6_11":{"terrain":"ocean"}, "Player_1":{"recruit_frog":true, "recruit_balloon":true, "recruit_spearman":true, "gold":100, "recruit_thief":true, "recruit_griffin_walking":true, "recruit_soldier":true, "recruit_dog":true, "recruit_giant":true, "team":0, "recruit_merman":true, "recruit_dragon":true, "recruit_wagon":true, "recruit_turtle":true, "recruit_kraken":true, "recruit_caravel":true, "recruit_harpy":true, "recruit_witch":true, "recruit_rifleman":true, "recruit_harpoonship":true, "recruit_warship":true, "recruit_trebuchet":true, "recruit_ballista":true, "recruit_knight":true, "recruit_mage":true, "recruit_archer":true, "recruit_travelboat":true}, "Map_Tile_2_7":{"terrain":"road"}, "Map_Tile_2_8":{"terrain":"beach"}, "Map_Tile_5_8":{"terrain":"road"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"ocean"}, "Map_Tile_12_0":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"beach"}, "Map_Tile_6_10":{"terrain":"ocean"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"ocean"}, "Map_Tile_6_7":{"terrain":"forest"}, "Map_Tile_6_5":{"terrain":"beach"}, "Map_Tile_6_4":{"terrain":"beach"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_13_13":{"unit":{"id":1, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"trebuchet", "loadedUnits":{}, "playerId":1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":3, "y":13, "x":13}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":3, "y":13, "x":13}, "unitClass":{"id":"trebuchet", "transportTags":{}, "weaponIds":["trebuchetSling"], "moveRange":5, "canReinforce":false, "canBeCaptured":false, "verbCostMultiplier":1.0, "cost":1100, "weapons":[{"id":"trebuchetSling", "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":false, "terrainExclusion":{}, "maxRange":5, "blockedByEnemies":false, "minRange":3, "canAttackSubmerged":false, "canCounterAttack":true, "unitIdWhenAttacking":""}], "tags":["trebuchet", "type.ground.heavy"], "aliasId":"", "movementType":"wheels", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":false, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":3, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.5, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":{}, "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"ocean"}, "Map_Tile_2_5":{"terrain":"beach"}, "Map_Tile_3_7":{"terrain":"road"}, "Map_Tile_7_4":{"terrain":"beach"}, "Map_Tile_13_8":{"terrain":"road"}, "Map_Tile_2_0":{"unit":{"id":27, "grooveCharge":0, "canBeAttackedFromDistance":true, "hasBeenKilled":false, "health":100, "merchantDiscounts":{}, "blessings":{}, "killedByLosing":false, "setHealth":null, "transportedBy":-1, "unitClassId":"hideout", "loadedUnits":{}, "playerId":-1, "state":{}, "itemDropNumber":0, "setGroove":null, "startPos":{"facing":0, "y":0, "x":2}, "grooveId":"", "items":{}, "merchantDiscountMultiplier":0.0, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "tentacled":false, "underwater":false, "attackerUnitClass":"", "inTransport":false, "factionOverride":"", "garrisonClassId":"garrison", "hadTurn":false, "rangedDamageTakenPercent":100, "itemId":"", "recruitDiscountMultiplier":0.0, "attackerId":-1, "pos":{"facing":0, "y":0, "x":2}, "unitClass":{"id":"hideout", "transportTags":{}, "weaponIds":{}, "moveRange":0, "canReinforce":true, "canBeCaptured":true, "verbCostMultiplier":1.0, "cost":500, "weapons":{}, "tags":["structure"], "aliasId":"", "movementType":"land_building", "loadCapacity":0, "isDamagingParentUnit":false, "isStructure":true, "inAir":false, "isAttackable":true, "canBeActivated":false, "isRecruitable":true, "resourceCost":1, "isCommander":false, "reinforceMultiplier":1.0, "maxHealth":100, "inWater":false, "passiveMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "maxGroove":0, "critConditionId":""}, "miniGrooveId":"", "damageTakenPercent":100, "recruits":["thief", "rifleman"], "recruitDiscounts":{}, "canChargeGroove":true, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_5_7":{"terrain":"road"}, "Map_Tile_8_1":{"terrain":"forest"}, "Map_Tile_2_9":{"terrain":"beach"}, "Map_Tile_4_14":{"terrain":"reef"}, "Map_Tile_1_4":{"terrain":"ocean"}, "Map_Tile_9_9":{"terrain":"bridge"}, "Map_Tile_11_1":{"terrain":"bridge"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Air_Support.json b/worlds/wargroove2/levels/Air_Support.json new file mode 100644 index 000000000000..d82d4b9edb7d --- /dev/null +++ b/worlds/wargroove2/levels/Air_Support.json @@ -0,0 +1 @@ +{"Map_Tile_5_7":{"terrain":"reef"}, "Map_Tile_12_11":{"terrain":"road"}, "Map_Tile_1_4":{"terrain":"bridge"}, "Map_Tile_14_0":{"terrain":"mountain"}, "Map_Tile_14_9":{"terrain":"road"}, "Map_Tile_14_11":{"terrain":"road"}, "Map_Tile_19_6":{"terrain":"sea"}, "Map_Tile_0_3":{"terrain":"sea"}, "Map_Tile_17_1":{"terrain":"mountain"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"mountain"}, "Map_Tile_14_5":{"terrain":"sea"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"sea"}, "Map_Tile_9_5":{"terrain":"road"}, "Map_Tile_14_2":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":24, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"tower", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"tower", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "grooveId":"", "health":100, "startPos":{"x":14, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":14, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"road"}, "Map_Tile_11_0":{"terrain":"mountain"}, "Map_Tile_2_0":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":47, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":2, "y":0, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":2, "y":0, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_11_3":{"terrain":"sea"}, "Map_Tile_18_10":{"terrain":"sea"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"sea"}, "Map_Tile_15_12":{"terrain":"road"}, "Map_Tile_18_14":{"terrain":"sea"}, "Map_Tile_8_11":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":20, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":8, "y":11, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":8, "y":11, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_17_3":{"terrain":"mountain"}, "Map_Tile_14_12":{"terrain":"road"}, "Map_Tile_5_10":{"terrain":"road"}, "Map_Tile_12_4":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"road"}, "Map_Tile_0_0":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":43, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"frog", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":2, "weaponIds":["frog_tongue"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"frog_tongue", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":600, "moveRange":7, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"amphibious", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"frog", "tags":["frog", "type.amphibious.heavy"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":0, "y":0, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":0, "y":0, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"reef"}, "Map_Tile_15_13":{"terrain":"sea"}, "Map_Tile_15_2":{"terrain":"mountain"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"road"}, "Counters":{"0":0}, "Map_Tile_10_1":{"terrain":"forest"}, "Map_Tile_7_3":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":1, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"knight", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":2, "weaponIds":["lance"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"lance", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":600, "moveRange":6, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"riding", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"knight", "tags":["knight", "type.ground.heavy"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":7, "y":3, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":7, "y":3, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_13_11":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":23, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":13, "y":11, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":13, "y":11, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_18_8":{"terrain":"sea"}, "Map_Tile_10_10":{"terrain":"river"}, "Flags":{}, "Map_Tile_9_2":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"river"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"sea"}, "Map_Tile_10_2":{"terrain":"sea"}, "Map_Tile_7_4":{"terrain":"road"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_12_12":{"terrain":"road"}, "Map_Tile_6_10":{"terrain":"road"}, "Map_Tile_4_14":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":19, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":4, "y":14, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":4, "y":14, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_7_5":{"terrain":"beach"}, "Map_Tile_15_6":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":39, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":15, "y":6, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":15, "y":6, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_19_13":{"terrain":"sea"}, "Map_Tile_2_10":{"terrain":"road"}, "Map_Tile_2_11":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":30, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"archer", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.3500000238419, "inWater":false, "resourceCost":1, "weaponIds":["bow"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":3, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"bow", "canMoveAndAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":3, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"archer", "tags":["archer", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":2, "y":11, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":2, "y":11, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_1_2":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":31, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":1, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":1, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_6_7":{"terrain":"reef"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"road"}, "Objectives":["Defeat 5 enemy units on Roads. (Requires Dragon and Bridges Event)", "Capture all of the Fortified Villages. (Requires Air Trooper and Bridges Event)", "Rout the enemy. (Requires Dragon and Bridges Event)"], "Map_Tile_0_6":{"terrain":"forest"}, "Map_Tile_17_6":{"terrain":"sea"}, "Map_Tile_17_9":{"terrain":"sea"}, "Map_Tile_12_7":{"terrain":"road"}, "Map_Tile_0_5":{"terrain":"sea"}, "Map_Tile_7_7":{"terrain":"reef"}, "Map_Tile_5_1":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":28, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"giant", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":2.5, "inWater":false, "resourceCost":3, "weaponIds":["giantSlam"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"giantSlam", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1200, "moveRange":5, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"riding", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"giant", "tags":["giant", "type.ground.heavy", "tall"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":5, "y":1, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":5, "y":1, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_0_10":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":18, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":0, "y":10, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":0, "y":10, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Player_1":{"gold":100, "recruit_rifleman":true, "recruit_wagon":true, "recruit_thief":true, "recruit_ballista":true, "recruit_kraken":true, "recruit_mage":true, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_witch":true, "recruit_turtle":true, "recruit_spearman":true, "recruit_archer":true, "recruit_caravel":true, "recruit_griffin_walking":true, "recruit_trebuchet":true, "recruit_knight":true, "recruit_dog":true, "recruit_dragon":true, "recruit_balloon":true, "recruit_frog":true, "recruit_merman":true, "recruit_travelboat":true, "recruit_giant":true, "team":0, "recruit_warship":true, "recruit_soldier":true}, "Map_Tile_5_2":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":5, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"archer", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.3500000238419, "inWater":false, "resourceCost":1, "weaponIds":["bow"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":3, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"bow", "canMoveAndAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":3, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"archer", "tags":["archer", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":5, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":5, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_15_4":{"terrain":"forest"}, "Map_Tile_4_8":{"terrain":"beach"}, "Map_Tile_6_2":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":2, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"giant", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":2.5, "inWater":false, "resourceCost":3, "weaponIds":["giantSlam"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"giantSlam", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1200, "moveRange":5, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"riding", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"giant", "tags":["giant", "type.ground.heavy", "tall"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":6, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_3_6":{"terrain":"bridge"}, "Map_Tile_7_11":{"terrain":"road"}, "Map_Tile_15_1":{"terrain":"mountain"}, "Map_Tile_10_9":{"terrain":"forest"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_0_7":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":8, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":0, "y":7, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":0, "y":7, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_5":{"terrain":"bridge"}, "Map_Tile_6_5":{"terrain":"beach"}, "Map_Tile_19_14":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":11, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":19, "y":14, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":19, "y":14, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_2_4":{"terrain":"beach"}, "Map_Tile_6_13":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":35, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"barracks", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"barracks", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveId":"", "health":100, "startPos":{"x":6, "y":13, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":13, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_19_2":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":26, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":19, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":19, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_9":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":38, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"hq", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":3000, "moveRange":0, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"hq", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":3, "y":9, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":3, "y":9, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_16_13":{"terrain":"sea"}, "Map_Tile_2_6":{"terrain":"beach"}, "Map_Tile_19_1":{"terrain":"mountain"}, "Map_Tile_13_2":{"terrain":"mountain"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"reef"}, "Map_Tile_6_1":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":32, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":6, "y":1, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":1, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_17_2":{"terrain":"mountain"}, "Map_Tile_18_1":{"terrain":"mountain"}, "Map_Tile_8_7":{"terrain":"sea"}, "Map_Tile_18_7":{"terrain":"sea"}, "Map_Tile_1_5":{"terrain":"bridge"}, "Map_Tile_1_6":{"terrain":"bridge"}, "Map_Tile_1_12":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":12, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"commander_mercia", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":3, "weaponIds":["merciaSword"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"merciaSword", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":250, "verbCostMultiplier":1.0, "isCommander":true, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":4, "canReinforce":false, "isRecruitable":false, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"commander_mercia", "tags":["commander", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"heal_aura", "health":100, "startPos":{"x":1, "y":12, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":1, "y":12, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_19_9":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":10, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":19, "y":9, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":19, "y":9, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_4_10":{"terrain":"road"}, "Map_Tile_7_8":{"terrain":"reef"}, "Map_Tile_4_7":{"terrain":"sea"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_3_12":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":16, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"spearman", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":1, "weaponIds":["spear"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"spear", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":250, "moveRange":3, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"spearman", "tags":["spearman", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":3, "y":12, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":3, "y":12, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_2_12":{"terrain":"road"}, "Map_Size":{"y":15, "x":20}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"sea"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_12_3":{"terrain":"forest"}, "Map_Tile_10_6":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"road"}, "Map_Tile_11_5":{"terrain":"beach"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_19_0":{"terrain":"mountain"}, "Map_Tile_13_12":{"terrain":"road"}, "Map_Tile_10_4":{"terrain":"beach"}, "Map_Tile_19_5":{"terrain":"sea"}, "Map_Tile_13_7":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":50, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":13, "y":7, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":13, "y":7, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_15_0":{"terrain":"mountain"}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_1_1":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":44, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"frog", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":2, "weaponIds":["frog_tongue"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"frog_tongue", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":600, "moveRange":7, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"amphibious", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"frog", "tags":["frog", "type.amphibious.heavy"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":1, "y":1, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":1, "y":1, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_10_11":{"terrain":"river"}, "Map_Tile_13_6":{"terrain":"road"}, "Map_Tile_14_4":{"terrain":"forest"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"road"}, "Map_Tile_19_4":{"terrain":"sea"}, "Map_Tile_17_4":{"terrain":"sea"}, "Map_Tile_11_11":{"terrain":"forest"}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_9_7":{"terrain":"beach"}, "Map_Tile_15_7":{"terrain":"road"}, "Map_Tile_6_9":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":13, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":6, "y":9, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":9, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_3_3":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":4, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"archer", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.3500000238419, "inWater":false, "resourceCost":1, "weaponIds":["bow"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":3, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"bow", "canMoveAndAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":3, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"archer", "tags":["archer", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":3, "y":3, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":3, "y":3, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_19_11":{"terrain":"sea"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"beach"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"road"}, "Map_Tile_8_8":{"terrain":"sea"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_15_5":{"terrain":"sea"}, "Triggers":[{"id":"Export (Always on Top)", "actions":[{"id":"ap_export", "parameters":["1", "Air Support", "Magnemania", "Defeat 5 enemy units on Roads. (Requires Dragon and Bridges Event)", "Capture all of the Fortified Villages. (Requires Air Trooper and Bridges Event)", "", "Rout the enemy. (Requires Dragon and Bridges Event)"], "enabled":true}], "conditions":{}, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "isIntro":false, "enabled":true}, {"id":"Set AI", "actions":[{"id":"ai_set_profile", "parameters":["P2", "aggressive"], "enabled":true}, {"id":"ai_set_restriction", "parameters":["*commander", "-1", "current", "reckless", "1"], "enabled":true}, {"id":"unit_faction_override", "parameters":["*unit", "P1", "7", "faahri"], "enabled":true}], "conditions":{}, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "isIntro":false, "enabled":true}, {"id":"Shuffle Units", "actions":[{"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true}], "conditions":{}, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "isIntro":false, "enabled":true}, {"id":"Bridge Stuck", "actions":[{"id":"dialogue_box_simple", "parameters":["sad", "phil", "What happened to my gunpowder? I was going to blow this bridge!", "0", ""], "enabled":true}], "conditions":[{"id":"ap_has_item", "parameters":["252023", "0", "0"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "isIntro":false, "enabled":true}, {"id":"Bridge Sabotaged", "actions":[{"id":"centre_camera", "parameters":["7", "0"], "enabled":true}, {"id":"dialogue_box_simple", "parameters":["neutral", "phil", "Hold on..", "0", ""], "enabled":true}, {"id":"play_sound_effect", "parameters":["explosion_big", "7"], "enabled":true}, {"id":"screenshake", "parameters":["50", "2", "2", "5"], "enabled":true}, {"id":"activate_flood", "parameters":["5", "reef", "default", "", "0", "0", "0"], "enabled":true}, {"id":"dialogue_box_simple", "parameters":["happy", "phil", "Boom! They won't be crossing that bridge anytime soon.", "0", ""], "enabled":true}], "conditions":[{"id":"ap_has_item", "parameters":["252023", "1", "0"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "isIntro":false, "enabled":true}, {"id":"Kill Unit on Road", "actions":[{"id":"modify_counter", "parameters":["0", "1", "1"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["*unit", "current", "8"], "enabled":true}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "recurring":"repeat", "isIntro":false, "enabled":true}, {"id":"Kill 5 Road Units (Check 253361)", "actions":[{"id":"ap_location_send", "parameters":["253361"], "enabled":true}], "conditions":[{"id":"check_map_counter", "parameters":["0", "4", "5"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "isIntro":false, "enabled":true}, {"id":"Capture the Fortified Villages (Check 253362)", "actions":[{"id":"ap_location_send", "parameters":["253362"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["current", "4", "3", "fortified_city", "-1"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "isIntro":false, "enabled":true}, {"id":"Defeat (No Units)", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit", "-1"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true}, {"id":"$trigger_default_defeat_commander", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true}, {"id":"$trigger_default_defeat_hq", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true}, {"id":"$trigger_default_victory", "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true}, {"id":"P1 Wins (Check 253360)", "actions":[{"id":"ap_location_send", "parameters":["253360"], "enabled":true}], "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "isIntro":false, "enabled":true}], "Map_Tile_9_10":{"terrain":"river"}, "Map_Tile_6_11":{"terrain":"forest"}, "Map_Tile_19_12":{"terrain":"sea"}, "Map_Tile_19_10":{"terrain":"sea"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"forest"}, "Map_Tile_19_8":{"terrain":"sea"}, "Map_Tile_19_7":{"terrain":"sea"}, "Map_Tile_16_2":{"terrain":"mountain"}, "Map_Tile_19_3":{"terrain":"sea"}, "Map_Tile_18_13":{"terrain":"sea"}, "Map_Tile_18_12":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":25, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":18, "y":12, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":18, "y":12, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_17_10":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":9, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":17, "y":10, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":17, "y":10, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_4":{"terrain":"bridge"}, "Map_Tile_4_6":{"terrain":"sea"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_10_7":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":49, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"barracks", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"barracks", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveId":"", "health":100, "startPos":{"x":10, "y":7, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":10, "y":7, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_8_9":{"terrain":"mountain"}, "Map_Tile_5_3":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":29, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"archer", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.3500000238419, "inWater":false, "resourceCost":1, "weaponIds":["bow"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":3, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"bow", "canMoveAndAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":3, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"archer", "tags":["archer", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":5, "y":3, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":5, "y":3, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_2_9":{"terrain":"road"}, "Map_Tile_7_10":{"terrain":"road"}, "Map_Tile_18_6":{"terrain":"sea"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_18_5":{"terrain":"sea"}, "Map_Tile_12_13":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":21, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":12, "y":13, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":12, "y":13, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_18_4":{"terrain":"sea"}, "Map_Tile_12_2":{"terrain":"forest"}, "Map_Tile_10_8":{"terrain":"forest"}, "Map_Tile_15_8":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":22, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":15, "y":8, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":15, "y":8, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_18_3":{"terrain":"sea"}, "Map_Tile_18_2":{"terrain":"mountain"}, "Map_Tile_18_0":{"terrain":"mountain"}, "Map_Tile_17_14":{"terrain":"sea"}, "Map_Tile_17_13":{"terrain":"sea"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_17_12":{"terrain":"sea"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_17_11":{"terrain":"sea"}, "Map_Tile_17_8":{"terrain":"sea"}, "Map_Tile_13_4":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":41, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"fortified_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"fortified_garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1000, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"fortified_city", "tags":["fortified_city"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":13, "y":4, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":13, "y":4, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_17_5":{"terrain":"sea"}, "Map_Tile_8_10":{"terrain":"river"}, "Map_Tile_14_3":{"terrain":"mountain"}, "Map_Tile_10_0":{"terrain":"mountain"}, "Map_Tile_17_0":{"terrain":"mountain"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"mountain"}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_2_7":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":14, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"soldier", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":1, "weaponIds":["sword"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"sword", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":100, "moveRange":4, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"soldier", "tags":["soldier", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":2, "y":7, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":2, "y":7, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"road"}, "Player_Count":2, "Map_Tile_16_8":{"terrain":"sea"}, "Map_Tile_4_0":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":48, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":4, "y":0, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":4, "y":0, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Tile_5_12":{"terrain":"road"}, "Map_Tile_16_5":{"terrain":"sea"}, "Map_Tile_5_5":{"terrain":"sea"}, "Map_Tile_3_14":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":17, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"spearman", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":1, "weaponIds":["spear"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"spear", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":250, "moveRange":3, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"spearman", "tags":["spearman", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":3, "y":14, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":3, "y":14, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_1_3":{"terrain":"road"}, "Map_Tile_10_3":{"terrain":"sea"}, "Map_Tile_10_5":{"terrain":"road"}, "Map_Tile_7_14":{"terrain":"forest"}, "Map_Tile_16_4":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":42, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"fortified_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"fortified_garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1000, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"fortified_city", "tags":["fortified_city"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":16, "y":4, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":16, "y":4, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_5_9":{"terrain":"sea"}, "Author":"Magnemania", "Map_Tile_16_3":{"terrain":"mountain"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_1_0":{"terrain":"sea"}, "Map_Tile_10_14":{"terrain":"forest"}, "Map_Tile_11_12":{"terrain":"road"}, "Map_Tile_0_14":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":27, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":0, "y":14, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":0, "y":14, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_11_8":{"terrain":"forest"}, "Map_Tile_0_2":{"terrain":"sea"}, "Map_Tile_4_3":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":3, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"giant", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":2.5, "inWater":false, "resourceCost":3, "weaponIds":["giantSlam"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"giantSlam", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1200, "moveRange":5, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"riding", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"giant", "tags":["giant", "type.ground.heavy", "tall"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":4, "y":3, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":4, "y":3, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"road"}, "Map_Tile_9_6":{"terrain":"beach"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"beach"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"forest"}, "Map_Tile_15_3":{"terrain":"mountain"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_14_14":{"terrain":"sea"}, "Map_Tile_6_12":{"terrain":"road"}, "Map_Tile_14_10":{"terrain":"road"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"road"}, "Map_Tile_14_1":{"terrain":"mountain"}, "Map_Tile_6_4":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":7, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"knight", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":2, "weaponIds":["lance"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"lance", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":600, "moveRange":6, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"riding", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"knight", "tags":["knight", "type.ground.heavy"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":6, "y":4, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":4, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_0_1":{"terrain":"sea"}, "Map_Tile_0_12":{"terrain":"road"}, "Map_Tile_4_4":{"terrain":"sea"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"road"}, "Map_Tile_18_9":{"terrain":"sea"}, "Map_Tile_13_3":{"terrain":"forest"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"sea"}, "Map_Tile_18_11":{"terrain":"sea"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_12_10":{"terrain":"road"}, "Map_Tile_0_4":{"terrain":"sea"}, "Map_Tile_3_2":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":6, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"giant", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":2.5, "inWater":false, "resourceCost":3, "weaponIds":["giantSlam"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"giantSlam", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1200, "moveRange":5, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"riding", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"giant", "tags":["giant", "type.ground.heavy", "tall"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":3, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":3, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Player_2":{"gold":100, "recruit_rifleman":true, "recruit_wagon":true, "recruit_thief":true, "recruit_ballista":true, "recruit_kraken":true, "recruit_mage":true, "recruit_harpy":true, "recruit_harpoonship":true, "recruit_witch":true, "recruit_turtle":true, "recruit_spearman":true, "recruit_archer":true, "recruit_caravel":true, "recruit_griffin_walking":true, "recruit_trebuchet":true, "recruit_knight":true, "recruit_dog":true, "recruit_dragon":true, "recruit_balloon":true, "recruit_frog":true, "recruit_merman":true, "recruit_travelboat":true, "recruit_giant":true, "team":1, "recruit_warship":true, "recruit_soldier":true}, "Map_Tile_11_6":{"terrain":"road"}, "Map_Tile_12_6":{"terrain":"road"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_2_5":{"terrain":"sea"}, "Map_Tile_5_8":{"terrain":"reef"}, "Map_Tile_13_1":{"terrain":"mountain"}, "Map_Name":"Air Support", "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_11_4":{"terrain":"beach"}, "Map_Tile_7_1":{"terrain":"sea"}, "Map_Tile_11_2":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":40, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"fortified_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"fortified_garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":1000, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"land_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"fortified_city", "tags":["fortified_city"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":11, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":11, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_10":{"terrain":"road"}, "Map_Tile_4_12":{"terrain":"road"}, "Map_Tile_11_1":{"terrain":"forest"}, "Map_Tile_10_12":{"terrain":"road"}, "Map_Tile_4_2":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":45, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"frog", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":2, "weaponIds":["frog_tongue"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"frog_tongue", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":600, "moveRange":7, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"amphibious", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"frog", "tags":["frog", "type.amphibious.heavy"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":4, "y":2, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":4, "y":2, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_15_14":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_6_6":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":36, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":6, "y":6, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":6, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_8_1":{"terrain":"sea"}, "Locations":{"1":{"id":1, "centre":{"y":2, "x":4}, "getArea":null, "setArea":null, "positions":[{"y":3, "x":2}, {"y":3, "x":3}, {"y":3, "x":4}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":2, "x":3}, {"y":1, "x":4}, {"y":1, "x":5}, {"y":2, "x":6}, {"y":3, "x":5}, {"y":1, "x":3}], "interactable":false, "name":"Enemy Army Shuffle"}, "2":{"id":2, "centre":{"y":12, "x":2}, "getArea":null, "setArea":null, "positions":[{"y":11, "x":2}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":13, "x":2}, {"y":12, "x":3}, {"y":14, "x":3}, {"y":13, "x":3}], "interactable":false, "name":"Allied Army Shuffle"}, "3":{"id":3, "centre":{"y":3, "x":12}, "getArea":null, "setArea":null, "positions":[{"y":0, "x":9}, {"y":2, "x":11}, {"y":4, "x":13}, {"y":4, "x":16}], "interactable":false, "name":"Shuffle2"}, "4":{"id":4, "centre":{"y":10, "x":18}, "getArea":null, "setArea":null, "positions":[{"y":9, "x":19}, {"y":12, "x":18}, {"y":14, "x":19}, {"y":8, "x":18}, {"y":11, "x":19}, {"y":13, "x":17}, {"y":14, "x":16}, {"y":5, "x":19}, {"y":6, "x":17}, {"y":7, "x":19}], "interactable":false, "name":"Shuffle3"}, "5":{"id":5, "centre":{"y":5, "x":2}, "getArea":null, "setArea":null, "positions":[{"y":5, "x":1}, {"y":6, "x":1}, {"y":4, "x":1}, {"y":4, "x":3}, {"y":5, "x":3}, {"y":6, "x":3}], "interactable":false, "name":"Drawbridge"}, "7":{"id":7, "centre":{"y":7, "x":2}, "getArea":null, "setArea":null, "positions":[{"y":7, "x":2}], "interactable":false, "name":"Saboteur"}, "8":{"id":8, "centre":{"y":8, "x":8}, "getArea":null, "setArea":null, "positions":[{"y":2, "x":3}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":2, "x":6}, {"y":3, "x":6}, {"y":3, "x":7}, {"y":4, "x":7}, {"y":4, "x":8}, {"y":4, "x":9}, {"y":5, "x":9}, {"y":5, "x":10}, {"y":6, "x":10}, {"y":6, "x":11}, {"y":6, "x":12}, {"y":6, "x":13}, {"y":6, "x":14}, {"y":7, "x":14}, {"y":8, "x":14}, {"y":9, "x":14}, {"y":10, "x":14}, {"y":11, "x":14}, {"y":12, "x":14}, {"y":12, "x":15}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":11, "x":12}, {"y":10, "x":12}, {"y":9, "x":12}, {"y":8, "x":12}, {"y":7, "x":12}, {"y":7, "x":15}, {"y":12, "x":11}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":12, "x":2}, {"y":12, "x":1}, {"y":12, "x":0}, {"y":11, "x":2}, {"y":10, "x":2}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":7, "x":2}, {"y":7, "x":1}, {"y":7, "x":3}, {"y":11, "x":7}, {"y":10, "x":7}, {"y":10, "x":6}, {"y":10, "x":5}, {"y":10, "x":4}, {"y":10, "x":3}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":3, "x":1}], "interactable":false, "name":"Roads"}, "0":{"id":0, "centre":{"y":10, "x":14}, "getArea":null, "setArea":null, "positions":[{"y":8, "x":15}, {"y":10, "x":15}, {"y":11, "x":15}, {"y":11, "x":13}, {"y":13, "x":14}, {"y":13, "x":12}, {"y":7, "x":13}, {"y":9, "x":13}], "interactable":false, "name":"Shuffle1"}}, "Map_Tile_9_9":{"terrain":"river"}, "Map_Tile_6_3":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":37, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"commander_valder", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":3, "weaponIds":["valderSpell"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"valderSpell", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":250, "verbCostMultiplier":1.0, "isCommander":true, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":4, "canReinforce":false, "isRecruitable":false, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"commander_valder", "tags":["commander", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"raise_dead", "health":100, "startPos":{"x":6, "y":3, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":6, "y":3, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_9_4":{"terrain":"road"}, "Map_Tile_9_1":{"terrain":"sea"}, "Map_Tile_9_3":{"unit":{"playerId":-1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":46, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"water_city", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "resourceCost":1, "weaponIds":{}, "maxHealth":100, "isDamagingParentUnit":false, "weapons":{}, "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":500, "moveRange":0, "canReinforce":true, "isRecruitable":true, "isAttackable":true, "movementType":"sea_building", "reinforceMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"water_city", "tags":["structure"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":9, "y":3, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":9, "y":3, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"sea"}, "Map_Tile_8_6":{"terrain":"beach"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"sea"}, "Map_Tile_2_13":{"unit":{"playerId":0, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":15, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"ballista", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":1, "weaponIds":["ballistaBolt"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":6, "canAttackSubmerged":false, "minRange":2, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"ballistaBolt", "canMoveAndAttack":false, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":800, "moveRange":6, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"wheels", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"ballista", "tags":["ballista", "type.ground.heavy"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":2, "y":13, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":2, "y":13, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_1":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":34, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"soldier", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":1, "weaponIds":["sword"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"sword", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":100, "moveRange":4, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"soldier", "tags":["soldier", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":3, "y":1, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":3, "y":1, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_4_1":{"unit":{"playerId":1, "stunned":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "factionOverride":"", "id":33, "attackerPlayerId":-1, "canChargeGroove":true, "unitClassId":"soldier", "hadTurn":false, "rangedDamageTakenPercent":100, "merchantDiscounts":{}, "transportedBy":-1, "miniGrooveId":"", "garrisonClassId":"", "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "tentacled":false, "setGroove":null, "canBeAttacked":true, "setHealth":null, "itemId":"", "canBeAttackedFromDistance":true, "attackerId":-1, "inTransport":false, "recruitDiscounts":{}, "attachedFlagId":-1, "underwater":false, "unitClass":{"canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "inWater":false, "resourceCost":1, "weaponIds":["sword"], "maxHealth":100, "isDamagingParentUnit":false, "weapons":[{"maxRange":1, "canAttackSubmerged":false, "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"sword", "canMoveAndAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false}], "maxGroove":0, "verbCostMultiplier":1.0, "isCommander":false, "inAir":false, "aliasId":"", "transportTags":{}, "cost":100, "moveRange":4, "canReinforce":false, "isRecruitable":true, "isAttackable":true, "movementType":"walking", "reinforceMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canBeActivated":false, "recruitingCostMultiplier":1.0, "id":"soldier", "tags":["soldier", "type.ground.light"]}, "state":{}, "itemDropNumber":0, "attackerUnitClass":"", "recruits":{}, "grooveId":"", "health":100, "startPos":{"x":4, "y":1, "facing":0}, "killedByLosing":false, "grooveCharge":0, "pos":{"x":4, "y":1, "facing":0}, "items":{}, "loadedUnits":{}, "damageTakenPercent":100}, "terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Beached.json b/worlds/wargroove2/levels/Beached.json new file mode 100644 index 000000000000..80ee6cc73a25 --- /dev/null +++ b/worlds/wargroove2/levels/Beached.json @@ -0,0 +1 @@ +{"Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"beach"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"forest"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"beach"}, "Map_Tile_13_13":{"terrain":"beach"}, "Map_Tile_13_12":{"terrain":"beach"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"beach"}, "Map_Tile_6_12":{"terrain":"beach"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"mountain"}, "Map_Tile_2_10":{"terrain":"beach"}, "Map_Tile_7_8":{"terrain":"beach"}, "Map_Tile_2_9":{"terrain":"forest"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":0, "x":11, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":2, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":0, "x":11, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"warship", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["warshipCannon"], "aliasId":"", "cost":1000, "moveRange":8, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["warship", "type.sea.heavy"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"warship", "isCommander":false, "isRecruitable":true, "movementType":"sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":3, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":4, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":3, "id":"warshipCannon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Player_2":{"recruit_harpy":true, "recruit_turtle":true, "recruit_knight":true, "recruit_frog":true, "recruit_wagon":true, "team":1, "recruit_balloon":true, "recruit_archer":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_spearman":true, "recruit_merman":true, "recruit_witch":true, "gold":0, "recruit_giant":true, "recruit_mage":true, "recruit_warship":true, "recruit_kraken":true, "recruit_caravel":true, "recruit_soldier":true, "recruit_trebuchet":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_thief":true, "recruit_dragon":true}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"beach"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Flags":{}, "Counters":{}, "Map_Tile_11_11":{"terrain":"beach"}, "Map_Tile_11_12":{"terrain":"beach"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Locations":{"1":{"positions":[{"x":14, "y":6}], "setArea":null, "name":"Goal", "interactable":false, "centre":{"x":14, "y":6}, "getArea":null, "id":1}, "2":{"positions":[{"x":14, "y":10}, {"x":13, "y":10}, {"x":12, "y":10}, {"x":12, "y":9}, {"x":13, "y":9}, {"x":11, "y":10}, {"x":11, "y":11}, {"x":12, "y":11}, {"x":12, "y":12}, {"x":11, "y":12}, {"x":14, "y":8}, {"x":13, "y":8}, {"x":13, "y":7}, {"x":13, "y":6}, {"x":14, "y":6}, {"x":12, "y":8}, {"x":14, "y":4}, {"x":13, "y":4}, {"x":12, "y":4}, {"x":11, "y":4}], "setArea":null, "name":"P2 TP", "interactable":false, "centre":{"x":13, "y":8}, "getArea":null, "id":2}, "0":{"positions":[{"x":0, "y":0}, {"x":1, "y":0}, {"x":1, "y":1}, {"x":1, "y":2}, {"x":2, "y":2}, {"x":2, "y":3}, {"x":2, "y":4}, {"x":2, "y":5}, {"x":2, "y":6}, {"x":2, "y":7}, {"x":1, "y":7}, {"x":1, "y":6}, {"x":0, "y":6}, {"x":0, "y":5}, {"x":0, "y":4}, {"x":0, "y":3}, {"x":0, "y":2}, {"x":0, "y":1}, {"x":1, "y":3}, {"x":1, "y":4}, {"x":1, "y":5}, {"x":0, "y":7}, {"x":0, "y":8}, {"x":0, "y":9}, {"x":0, "y":10}, {"x":0, "y":11}, {"x":0, "y":12}, {"x":0, "y":13}, {"x":1, "y":13}, {"x":1, "y":14}, {"x":1, "y":12}, {"x":1, "y":11}, {"x":1, "y":10}, {"x":1, "y":9}, {"x":1, "y":8}, {"x":2, "y":8}, {"x":2, "y":9}, {"x":2, "y":10}, {"x":2, "y":11}, {"x":2, "y":12}, {"x":2, "y":14}, {"x":2, "y":13}, {"x":0, "y":14}, {"x":2, "y":1}, {"x":2, "y":0}], "setArea":null, "name":"P1 TP", "interactable":false, "centre":{"x":1, "y":7}, "getArea":null, "id":0}}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"beach"}, "Map_Tile_11_4":{"terrain":"beach"}, "Map_Tile_12_12":{"terrain":"beach"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"beach"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"beach"}, "Map_Tile_14_10":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":10, "x":14, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":7, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":10, "x":14, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"warship", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["warshipCannon"], "aliasId":"", "cost":1000, "moveRange":8, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["warship", "type.sea.heavy"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"warship", "isCommander":false, "isRecruitable":true, "movementType":"sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":3, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":4, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":3, "id":"warshipCannon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_9_4":{"terrain":"beach"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"forest"}, "Map_Tile_5_14":{"terrain":"forest"}, "Map_Tile_14_4":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":4, "x":14, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":1, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":4, "x":14, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"warship", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["warshipCannon"], "aliasId":"", "cost":1000, "moveRange":8, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["warship", "type.sea.heavy"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"warship", "isCommander":false, "isRecruitable":true, "movementType":"sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":3, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":4, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":3, "id":"warshipCannon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"beach"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_7_7":{"terrain":"forest"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"forest"}, "Map_Tile_10_1":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":1, "x":10, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":6, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":1, "x":10, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"caravel", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["caravelWeapon"], "aliasId":"", "cost":250, "moveRange":5, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["caravel", "type.sea.light"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"caravel", "isCommander":false, "isRecruitable":true, "movementType":"river_sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":1, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"caravelWeapon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_6_13":{"terrain":"beach"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"beach"}, "Player_1":{"recruit_harpy":true, "recruit_turtle":true, "recruit_knight":true, "recruit_frog":true, "recruit_wagon":true, "team":0, "recruit_balloon":true, "recruit_archer":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_spearman":true, "recruit_merman":true, "recruit_witch":true, "gold":100, "recruit_giant":true, "recruit_mage":true, "recruit_warship":true, "recruit_kraken":true, "recruit_caravel":true, "recruit_soldier":true, "recruit_trebuchet":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_thief":true, "recruit_dragon":true}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"forest"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"beach"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"forest"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_13_7":{"terrain":"beach"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"beach"}, "Map_Tile_9_1":{"terrain":"beach"}, "Map_Tile_0_2":{"terrain":"forest"}, "Map_Tile_3_3":{"terrain":"beach"}, "Map_Tile_13_4":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":4, "x":13, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":5, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":4, "x":13, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"caravel", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["caravelWeapon"], "aliasId":"", "cost":250, "moveRange":5, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["caravel", "type.sea.light"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"caravel", "isCommander":false, "isRecruitable":true, "movementType":"river_sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":1, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"caravelWeapon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_4_10":{"terrain":"beach"}, "Map_Tile_13_9":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":9, "x":13, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":9, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":9, "x":13, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"caravel", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["caravelWeapon"], "aliasId":"", "cost":250, "moveRange":5, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["caravel", "type.sea.light"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"caravel", "isCommander":false, "isRecruitable":true, "movementType":"river_sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":1, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"caravelWeapon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"beach"}, "Map_Tile_2_2":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"forest"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"beach"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"beach"}, "Map_Tile_12_10":{"terrain":"beach"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"beach"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"forest"}, "Map_Tile_3_13":{"terrain":"beach"}, "Map_Tile_8_2":{"terrain":"beach"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":8, "x":0, "facing":0}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":4, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":8, "x":0, "facing":0}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"commander_mercia", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["merciaSword"], "aliasId":"", "cost":500, "moveRange":4, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["commander", "type.ground.light"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"commander_mercia", "isCommander":true, "isRecruitable":false, "movementType":"walking", "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":false, "canBeActivated":false, "resourceCost":3, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":250, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"merciaSword"}]}, "recruits":{}, "grooveId":"heal_aura", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":0, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":10, "x":13, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":8, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":10, "x":13, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"caravel", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["caravelWeapon"], "aliasId":"", "cost":250, "moveRange":5, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["caravel", "type.sea.light"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"caravel", "isCommander":false, "isRecruitable":true, "movementType":"river_sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":1, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"caravelWeapon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"forest"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"mountain"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"beach"}, "Triggers":[{"conditions":{}, "isIntro":false, "recurring":"start_of_match", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["-150920294", "Beached", "Fly Sniper", "Kill any unit with the turtle.", "Get the Turtle to the Goal.", "", "Get the Thief to the Goal to win."], "id":"ap_export"}], "id":"Export"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "isIntro":false, "recurring":"repeat", "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["warship", "0", "current", "0", "0", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit"}], "id":"Spawn Another Enemy"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "id":"$trigger_default_defeat_no_units"}, {"conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "id":"$trigger_default_defeat_commander"}, {"conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "id":"$trigger_default_defeat_hq"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "id":"$trigger_default_victory"}, {"conditions":{}, "isIntro":false, "recurring":"start_of_match", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["1", "flag3", "1"], "id":"set_location_highlight"}], "id":"Init"}, {"conditions":{}, "isIntro":false, "recurring":"start_of_match", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["*commander", "P1", "0", "0", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "P2", "2", "2", "1"], "id":"unit_random_teleport"}], "id":"TP"}, {"conditions":[{"enabled":true, "parameters":["252004", "0", "1"], "id":"ap_has_item"}], "isIntro":false, "recurring":"start_of_match", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["knight", "0", "current", "0", "1", "4", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["knight", "P1", "-1", "faahri"], "id":"unit_faction_override"}, {"enabled":true, "parameters":["happy", "tago", "Have no fear! We will protect you!", "1", "Valliant Knight"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["neutral", "janjak", "Perfect! I should just need to sneak past the Warships. To that flag over there.", "1", "Code Names Thief"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["happy", "tago", "Oh? Warships... Well... Uh.. No.. I mean... Yes. Yes! I am Valliant after all.", "1", "\"Valliant\" Knight"], "id":"dialogue_box_simple"}], "id":"Spawn Knights"}, {"conditions":[{"enabled":true, "parameters":["252016", "0", "1"], "id":"ap_has_item"}], "isIntro":false, "recurring":"start_of_match", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["turtle", "0", "current", "0", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["neutral", "janjak", "Where did you come from?", "1", "Code Names Thief"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["happy", "sourcheeks", "Hey man, I'm just here to have a good time.", "1", "Brave Little Turtle"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["neutral", "janjak", "Whatever, so long as we get there before my allergy kicks in.", "1", "Code Names Thief"], "id":"dialogue_box_simple"}], "id":"Spawn Turtle"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "1", "thief", "1"], "id":"unit_presence"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["happy", "janjak", "Well color me impressed, I'm outa here!", "1", "Code Names Thief"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["253018"], "id":"ap_location_send"}, {"enabled":true, "parameters":["current"], "id":"victory"}], "id":"P1 Wins (253018)"}, {"conditions":[{"enabled":true, "parameters":["turtle", "P1", "*unit_structure", "P2", "-1"], "id":"unit_killed"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["253019"], "id":"ap_location_send"}], "id":"Turtle Gets a Kill (253019)"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "1", "turtle", "1"], "id":"unit_presence"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["neutral", "janjak", "...", "1", "Code Names Thief"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["happy", "sourcheeks", ":-)", "1", "Happy Turtle"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["253020"], "id":"ap_location_send"}], "id":"Turtle on Goal (253020)"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0", "thief", "-1"], "id":"unit_presence"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "id":"P1 Loses"}], "Map_Tile_11_10":{"terrain":"beach"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"beach"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"beach"}, "Map_Tile_6_6":{"terrain":"beach"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"beach"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Size":{"x":15, "y":15}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"forest"}, "Map_Tile_9_5":{"terrain":"beach"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"beach", "unit":{"state":{}, "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":1, "x":11, "facing":3}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":3, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":1, "x":11, "facing":3}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"warship", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":["warshipCannon"], "aliasId":"", "cost":1000, "moveRange":8, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["warship", "type.sea.heavy"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"warship", "isCommander":false, "isRecruitable":true, "movementType":"sailing", "loadCapacity":0, "passiveMultiplier":1.5, "inWater":true, "canBeCaptured":false, "canBeActivated":false, "resourceCost":3, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":[{"unitIdWhenAttacking":"", "canAttackAir":false, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "maxRange":4, "directionality":"omni", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":3, "id":"warshipCannon"}]}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":1, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"forest"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"beach"}, "Map_Tile_3_9":{"terrain":"beach"}, "Map_Tile_2_12":{"terrain":"beach"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"forest"}, "Map_Tile_10_2":{"terrain":"beach"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"beach"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"beach"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"forest"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"beach"}, "Map_Tile_3_2":{"terrain":"beach"}, "Map_Tile_8_8":{"terrain":"beach"}, "Map_Tile_5_6":{"terrain":"beach"}, "Map_Tile_1_11":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"beach"}, "Map_Tile_13_6":{"terrain":"beach"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"beach"}, "Map_Name":"Beached", "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"beach"}, "Map_Tile_8_7":{"terrain":"beach"}, "Map_Tile_8_3":{"terrain":"beach"}, "Map_Tile_5_10":{"terrain":"beach"}, "Map_Tile_6_9":{"terrain":"beach"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"forest"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"beach"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"beach"}, "Map_Tile_7_4":{"terrain":"forest"}, "Map_Tile_6_5":{"terrain":"plains"}, "Author":"Fly Sniper", "Map_Tile_6_14":{"terrain":"beach"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"beach"}, "Map_Tile_4_6":{"terrain":"beach"}, "Map_Tile_2_3":{"terrain":"plains"}, "Objectives":["Kill any unit with the turtle.", "Get the Turtle to the Goal.", "Get the Thief to the Goal to win."], "Map_Tile_3_10":{"terrain":"beach"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"beach"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"forest"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"beach"}, "Map_Tile_5_9":{"terrain":"beach"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"beach"}, "Map_Tile_1_6":{"terrain":"plains", "unit":{"state":[{"value":"0", "key":"gold"}], "merchantDiscounts":{}, "setHealth":null, "grooveCharge":0, "startPos":{"y":6, "x":1, "facing":1}, "canBeAttackedFromDistance":true, "damageTakenPercent":100, "garrisonClassId":"", "id":10, "inTransport":false, "attackerPlayerId":-1, "pos":{"y":6, "x":1, "facing":1}, "stunned":false, "hasBeenKilled":false, "rangedDamageTakenPercent":100, "itemId":"", "merchantDiscountMultiplier":0.0, "hadTurn":false, "unitClassId":"thief", "canChargeGroove":true, "transportedBy":-1, "setGroove":null, "blessings":{}, "unitClass":{"isAttackable":true, "isStructure":false, "canAttack":true, "weaponIds":{}, "aliasId":"", "cost":400, "moveRange":6, "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "tags":["thief", "type.ground.hideout"], "isDamagingParentUnit":false, "recruitingCostMultiplier":1.0, "id":"thief", "isCommander":false, "isRecruitable":true, "movementType":"walking", "loadCapacity":0, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":false, "canBeActivated":false, "resourceCost":1, "inAir":false, "maxHealth":100, "canReinforce":false, "transportTags":{}, "maxGroove":0, "critConditionId":"", "weapons":{}}, "recruits":{}, "grooveId":"", "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "attachedFlagId":-1, "loadedUnits":{}, "playerId":0, "tentacled":false, "killedByLosing":false, "underwater":false, "itemDropNumber":0, "recruitDiscounts":{}, "items":{}, "miniGrooveId":"", "health":100, "factionOverride":"", "attackerUnitClass":"", "attackerId":-1}}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"beach"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Bridge_Brigade.json b/worlds/wargroove2/levels/Bridge_Brigade.json new file mode 100644 index 000000000000..1e249822f002 --- /dev/null +++ b/worlds/wargroove2/levels/Bridge_Brigade.json @@ -0,0 +1 @@ +{"Author":"Fly Sniper","Counters":{},"Flags":{},"Locations":{"0":{"id":0,"setArea":null,"name":"Spear Spawn","centre":{"y":10,"x":6},"interactable":false,"getArea":null,"positions":[{"y":8,"x":6},{"y":8,"x":5},{"y":9,"x":5},{"y":10,"x":5},{"y":11,"x":5},{"y":12,"x":5},{"y":12,"x":6},{"y":12,"x":7},{"y":12,"x":4},{"y":11,"x":4},{"y":10,"x":4},{"y":10,"x":6},{"y":10,"x":7},{"y":11,"x":7},{"y":11,"x":6},{"y":9,"x":6},{"y":9,"x":7}]},"1":{"id":1,"setArea":null,"name":"Port 1","centre":{"y":15,"x":1},"interactable":false,"getArea":null,"positions":[{"y":15,"x":1},{"y":14,"x":1},{"y":15,"x":0},{"y":15,"x":2},{"y":16,"x":1}]},"2":{"id":2,"setArea":null,"name":"Port 2","centre":{"y":17,"x":9},"interactable":false,"getArea":null,"positions":[{"y":17,"x":9},{"y":16,"x":9},{"y":17,"x":8},{"y":17,"x":10},{"y":18,"x":9}]},"3":{"id":3,"setArea":null,"name":"Port 3","centre":{"y":15,"x":12},"interactable":false,"getArea":null,"positions":[{"y":15,"x":12},{"y":15,"x":11},{"y":14,"x":12},{"y":15,"x":13},{"y":16,"x":12}]}},"Map_Name":"Bridge Brigade","Map_Size":{"y":20,"x":14},"Map_Tile_0_0":{"terrain":"ocean"},"Map_Tile_0_1":{"terrain":"ocean"},"Map_Tile_0_2":{"terrain":"sea"},"Map_Tile_0_3":{"terrain":"sea"},"Map_Tile_0_4":{"terrain":"sea"},"Map_Tile_0_5":{"terrain":"sea"},"Map_Tile_0_6":{"terrain":"sea"},"Map_Tile_0_7":{"terrain":"sea"},"Map_Tile_0_8":{"terrain":"sea"},"Map_Tile_0_9":{"terrain":"sea"},"Map_Tile_0_10":{"terrain":"reef"},"Map_Tile_0_11":{"terrain":"reef"},"Map_Tile_0_12":{"terrain":"ocean"},"Map_Tile_0_13":{"terrain":"ocean"},"Map_Tile_0_14":{"terrain":"ocean"},"Map_Tile_0_15":{"terrain":"sea"},"Map_Tile_0_16":{"terrain":"plains"},"Map_Tile_0_17":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":14,"factionOverride":"","pos":{"x":0,"y":17,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"hq","transportedBy":-1,"unitClass":{"passiveMultiplier":1,"tags":["structure"],"canBeActivated":false,"movementType":"land_building","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":{},"verbCostMultiplier":1,"weapons":{},"critConditionId":"","moveRange":0,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":3000,"aliasId":"","id":"hq","resourceCost":1,"isStructure":true,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"garrison","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":0,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":0,"y":17,"facing":0}},"terrain":"plains"},"Map_Tile_0_18":{"terrain":"plains"},"Map_Tile_0_19":{"terrain":"beach"},"Map_Tile_1_0":{"terrain":"ocean"},"Map_Tile_1_1":{"terrain":"ocean"},"Map_Tile_1_2":{"terrain":"plains"},"Map_Tile_1_3":{"terrain":"plains"},"Map_Tile_1_4":{"terrain":"sea"},"Map_Tile_1_5":{"terrain":"sea"},"Map_Tile_1_6":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":16,"factionOverride":"","pos":{"x":1,"y":6,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"spearman","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["spearman","type.ground.light"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["spear"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"spear","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":3,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":250,"aliasId":"","id":"spearman","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":1,"y":6,"facing":0}},"terrain":"forest"},"Map_Tile_1_7":{"terrain":"plains"},"Map_Tile_1_8":{"terrain":"plains"},"Map_Tile_1_9":{"terrain":"sea"},"Map_Tile_1_10":{"terrain":"reef"},"Map_Tile_1_11":{"terrain":"reef"},"Map_Tile_1_12":{"terrain":"ocean"},"Map_Tile_1_13":{"terrain":"ocean"},"Map_Tile_1_14":{"terrain":"ocean"},"Map_Tile_1_15":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":17,"factionOverride":"","pos":{"x":1,"y":15,"facing":0},"recruits":["travelboat","caravel","merman","turtle","harpoonship","frog","kraken","warship"],"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"port","transportedBy":-1,"unitClass":{"passiveMultiplier":1,"tags":["structure"],"canBeActivated":false,"movementType":"river_sea_building","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":{},"verbCostMultiplier":1,"weapons":{},"critConditionId":"","moveRange":0,"canReinforce":true,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":true,"transportTags":{},"cost":500,"aliasId":"","id":"port","resourceCost":1,"isStructure":true,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"garrison","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":0,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":1,"y":15,"facing":0}},"terrain":"sea"},"Map_Tile_1_16":{"terrain":"bridge"},"Map_Tile_1_17":{"terrain":"bridge"},"Map_Tile_1_18":{"terrain":"bridge"},"Map_Tile_1_19":{"terrain":"beach"},"Map_Tile_2_0":{"terrain":"sea"},"Map_Tile_2_1":{"terrain":"plains"},"Map_Tile_2_2":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":12,"factionOverride":"","pos":{"x":2,"y":2,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"dog","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["dog","type.ground.light","animal"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["bite"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"bite","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":150,"aliasId":"","id":"dog","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":2,"y":2,"facing":0}},"terrain":"forest"},"Map_Tile_2_3":{"terrain":"plains"},"Map_Tile_2_4":{"terrain":"plains"},"Map_Tile_2_5":{"terrain":"beach"},"Map_Tile_2_6":{"terrain":"plains"},"Map_Tile_2_7":{"terrain":"plains"},"Map_Tile_2_8":{"terrain":"forest"},"Map_Tile_2_9":{"terrain":"sea"},"Map_Tile_2_10":{"terrain":"sea"},"Map_Tile_2_11":{"terrain":"sea"},"Map_Tile_2_12":{"terrain":"sea"},"Map_Tile_2_13":{"terrain":"sea"},"Map_Tile_2_14":{"terrain":"beach"},"Map_Tile_2_15":{"terrain":"beach"},"Map_Tile_2_16":{"terrain":"bridge"},"Map_Tile_2_17":{"terrain":"forest"},"Map_Tile_2_18":{"terrain":"bridge"},"Map_Tile_2_19":{"terrain":"sea"},"Map_Tile_3_0":{"terrain":"sea"},"Map_Tile_3_1":{"terrain":"beach"},"Map_Tile_3_2":{"terrain":"plains"},"Map_Tile_3_3":{"terrain":"plains"},"Map_Tile_3_4":{"terrain":"plains"},"Map_Tile_3_5":{"terrain":"beach"},"Map_Tile_3_6":{"terrain":"beach"},"Map_Tile_3_7":{"terrain":"beach"},"Map_Tile_3_8":{"terrain":"plains"},"Map_Tile_3_9":{"terrain":"beach"},"Map_Tile_3_10":{"terrain":"beach"},"Map_Tile_3_11":{"terrain":"plains"},"Map_Tile_3_12":{"terrain":"mountain"},"Map_Tile_3_13":{"terrain":"sea"},"Map_Tile_3_14":{"terrain":"plains"},"Map_Tile_3_15":{"terrain":"beach"},"Map_Tile_3_16":{"terrain":"bridge"},"Map_Tile_3_17":{"terrain":"forest"},"Map_Tile_3_18":{"terrain":"bridge"},"Map_Tile_3_19":{"terrain":"sea"},"Map_Tile_4_0":{"terrain":"sea"},"Map_Tile_4_1":{"terrain":"beach"},"Map_Tile_4_2":{"terrain":"beach"},"Map_Tile_4_3":{"terrain":"beach"},"Map_Tile_4_4":{"terrain":"beach"},"Map_Tile_4_5":{"terrain":"beach"},"Map_Tile_4_6":{"terrain":"reef"},"Map_Tile_4_7":{"terrain":"beach"},"Map_Tile_4_8":{"terrain":"ocean"},"Map_Tile_4_9":{"terrain":"ocean"},"Map_Tile_4_10":{"terrain":"plains"},"Map_Tile_4_11":{"terrain":"plains"},"Map_Tile_4_12":{"terrain":"plains"},"Map_Tile_4_13":{"terrain":"plains"},"Map_Tile_4_14":{"terrain":"plains"},"Map_Tile_4_15":{"terrain":"beach"},"Map_Tile_4_16":{"terrain":"bridge"},"Map_Tile_4_17":{"terrain":"bridge"},"Map_Tile_4_18":{"terrain":"bridge"},"Map_Tile_4_19":{"terrain":"sea"},"Map_Tile_5_0":{"terrain":"sea"},"Map_Tile_5_1":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":20,"factionOverride":"","pos":{"x":5,"y":1,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"dog","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["dog","type.ground.light","animal"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["bite"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"bite","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":150,"aliasId":"","id":"dog","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":5,"y":1,"facing":0}},"terrain":"beach"},"Map_Tile_5_2":{"terrain":"beach"},"Map_Tile_5_3":{"terrain":"sea"},"Map_Tile_5_4":{"terrain":"beach"},"Map_Tile_5_5":{"terrain":"road"},"Map_Tile_5_6":{"terrain":"road"},"Map_Tile_5_7":{"terrain":"road"},"Map_Tile_5_8":{"terrain":"bridge"},"Map_Tile_5_9":{"terrain":"bridge"},"Map_Tile_5_10":{"terrain":"road"},"Map_Tile_5_11":{"terrain":"road"},"Map_Tile_5_12":{"terrain":"road"},"Map_Tile_5_13":{"terrain":"road"},"Map_Tile_5_14":{"terrain":"bridge"},"Map_Tile_5_15":{"terrain":"bridge"},"Map_Tile_5_16":{"terrain":"road"},"Map_Tile_5_17":{"terrain":"road"},"Map_Tile_5_18":{"terrain":"road"},"Map_Tile_5_19":{"terrain":"sea"},"Map_Tile_6_0":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":4,"factionOverride":"","pos":{"x":6,"y":0,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"giant","transportedBy":-1,"unitClass":{"passiveMultiplier":2.5,"tags":["giant","type.ground.heavy","tall"],"canBeActivated":false,"movementType":"riding","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["giantSlam"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"giantSlam","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":1200,"aliasId":"","id":"giant","resourceCost":3,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":6,"y":0,"facing":0}},"terrain":"road"},"Map_Tile_6_1":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":1,"factionOverride":"","pos":{"x":6,"y":1,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"knight","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["knight","type.ground.heavy"],"canBeActivated":false,"movementType":"riding","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["lance"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"lance","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":6,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":600,"aliasId":"","id":"knight","resourceCost":2,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":6,"y":1,"facing":0}},"terrain":"road"},"Map_Tile_6_2":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":6,"factionOverride":"","pos":{"x":6,"y":2,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"dog","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["dog","type.ground.light","animal"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["bite"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"bite","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":150,"aliasId":"","id":"dog","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":6,"y":2,"facing":0}},"terrain":"bridge"},"Map_Tile_6_3":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":10,"factionOverride":"","pos":{"x":6,"y":3,"facing":0},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"soldier","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["soldier","type.ground.light"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["sword"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"sword","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":4,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":100,"aliasId":"","id":"soldier","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":6,"y":3,"facing":0}},"terrain":"bridge"},"Map_Tile_6_4":{"terrain":"road"},"Map_Tile_6_5":{"terrain":"road"},"Map_Tile_6_6":{"terrain":"road"},"Map_Tile_6_7":{"terrain":"road"},"Map_Tile_6_8":{"terrain":"bridge"},"Map_Tile_6_9":{"terrain":"bridge"},"Map_Tile_6_10":{"terrain":"road"},"Map_Tile_6_11":{"terrain":"road"},"Map_Tile_6_12":{"terrain":"road"},"Map_Tile_6_13":{"terrain":"road"},"Map_Tile_6_14":{"terrain":"bridge"},"Map_Tile_6_15":{"terrain":"bridge"},"Map_Tile_6_16":{"terrain":"road"},"Map_Tile_6_17":{"terrain":"road"},"Map_Tile_6_18":{"terrain":"road"},"Map_Tile_6_19":{"terrain":"sea"},"Map_Tile_7_0":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":5,"factionOverride":"","pos":{"x":7,"y":0,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"dog","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["dog","type.ground.light","animal"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["bite"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"bite","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":150,"aliasId":"","id":"dog","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":7,"y":0,"facing":3}},"terrain":"road"},"Map_Tile_7_1":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":7,"factionOverride":"","pos":{"x":7,"y":1,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"trebuchet","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["trebuchet","type.ground.heavy"],"canBeActivated":false,"movementType":"wheels","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["trebuchetSling"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":3,"blockedByEnemies":false,"canMoveAndAttack":false,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":5,"canCounterAttack":true,"id":"trebuchetSling","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":1100,"aliasId":"","id":"trebuchet","resourceCost":3,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":7,"y":1,"facing":3}},"terrain":"road"},"Map_Tile_7_2":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":8,"factionOverride":"","pos":{"x":7,"y":2,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"soldier","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["soldier","type.ground.light"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["sword"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"sword","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":4,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":100,"aliasId":"","id":"soldier","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":7,"y":2,"facing":3}},"terrain":"bridge"},"Map_Tile_7_3":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":9,"factionOverride":"","pos":{"x":7,"y":3,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"soldier","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["soldier","type.ground.light"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["sword"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"sword","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":4,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":100,"aliasId":"","id":"soldier","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":7,"y":3,"facing":3}},"terrain":"bridge"},"Map_Tile_7_4":{"terrain":"road"},"Map_Tile_7_5":{"terrain":"road"},"Map_Tile_7_6":{"terrain":"road"},"Map_Tile_7_7":{"terrain":"ocean"},"Map_Tile_7_8":{"terrain":"ocean"},"Map_Tile_7_9":{"terrain":"bridge"},"Map_Tile_7_10":{"terrain":"beach"},"Map_Tile_7_11":{"terrain":"beach"},"Map_Tile_7_12":{"terrain":"beach"},"Map_Tile_7_13":{"terrain":"plains"},"Map_Tile_7_14":{"terrain":"beach"},"Map_Tile_7_15":{"terrain":"beach"},"Map_Tile_7_16":{"terrain":"plains"},"Map_Tile_7_17":{"terrain":"plains"},"Map_Tile_7_18":{"terrain":"plains"},"Map_Tile_7_19":{"terrain":"sea"},"Map_Tile_8_0":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":2,"factionOverride":"","pos":{"x":8,"y":0,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"rifleman","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["rifleman","type.ground.hideout"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["musket"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":true,"canMoveAndAttack":false,"terrainExclusion":["forest"],"unitIdWhenAttacking":"","directionality":"omni","maxRange":9,"canCounterAttack":true,"id":"musket","horizontalAndVerticalOnly":true,"canAttackAir":false,"horizontalAndVerticalExtraWidth":1}],"critConditionId":"","moveRange":4,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":650,"aliasId":"","id":"rifleman","resourceCost":2,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":[{"key":"ammo","value":"3"}],"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":8,"y":0,"facing":3}},"terrain":"plains"},"Map_Tile_8_1":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":3,"factionOverride":"","pos":{"x":8,"y":1,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"giant","transportedBy":-1,"unitClass":{"passiveMultiplier":2.5,"tags":["giant","type.ground.heavy","tall"],"canBeActivated":false,"movementType":"riding","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["giantSlam"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"giantSlam","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":1200,"aliasId":"","id":"giant","resourceCost":3,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":8,"y":1,"facing":3}},"terrain":"plains"},"Map_Tile_8_2":{"terrain":"beach"},"Map_Tile_8_3":{"terrain":"sea"},"Map_Tile_8_4":{"terrain":"beach"},"Map_Tile_8_5":{"terrain":"beach"},"Map_Tile_8_6":{"terrain":"sea"},"Map_Tile_8_7":{"terrain":"ocean"},"Map_Tile_8_8":{"terrain":"ocean"},"Map_Tile_8_9":{"terrain":"road"},"Map_Tile_8_10":{"terrain":"beach"},"Map_Tile_8_11":{"terrain":"sea"},"Map_Tile_8_12":{"terrain":"sea"},"Map_Tile_8_13":{"terrain":"beach"},"Map_Tile_8_14":{"terrain":"ocean"},"Map_Tile_8_15":{"terrain":"ocean"},"Map_Tile_8_16":{"terrain":"forest"},"Map_Tile_8_17":{"terrain":"plains"},"Map_Tile_8_18":{"terrain":"plains"},"Map_Tile_8_19":{"terrain":"sea"},"Map_Tile_9_0":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":11,"factionOverride":"","pos":{"x":9,"y":0,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"mage","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["mage","type.ground.light","spellcaster"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["lightning"],"verbCostMultiplier":0.5,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"lightning","horizontalAndVerticalOnly":false,"canAttackAir":true,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":400,"aliasId":"","id":"mage","resourceCost":2,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":9,"y":0,"facing":3}},"terrain":"forest"},"Map_Tile_9_1":{"terrain":"beach"},"Map_Tile_9_2":{"terrain":"beach"},"Map_Tile_9_3":{"terrain":"beach"},"Map_Tile_9_4":{"terrain":"plains"},"Map_Tile_9_5":{"terrain":"plains"},"Map_Tile_9_6":{"terrain":"beach"},"Map_Tile_9_7":{"terrain":"beach"},"Map_Tile_9_8":{"terrain":"plains"},"Map_Tile_9_9":{"terrain":"road"},"Map_Tile_9_10":{"terrain":"forest"},"Map_Tile_9_11":{"terrain":"beach"},"Map_Tile_9_12":{"terrain":"beach"},"Map_Tile_9_13":{"terrain":"beach"},"Map_Tile_9_14":{"terrain":"sea"},"Map_Tile_9_15":{"terrain":"beach"},"Map_Tile_9_16":{"terrain":"plains"},"Map_Tile_9_17":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":18,"factionOverride":"","pos":{"x":9,"y":17,"facing":0},"recruits":["travelboat","caravel","merman","turtle","harpoonship","frog","kraken","warship"],"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"port","transportedBy":-1,"unitClass":{"passiveMultiplier":1,"tags":["structure"],"canBeActivated":false,"movementType":"river_sea_building","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":{},"verbCostMultiplier":1,"weapons":{},"critConditionId":"","moveRange":0,"canReinforce":true,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":true,"transportTags":{},"cost":500,"aliasId":"","id":"port","resourceCost":1,"isStructure":true,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"garrison","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":0,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":9,"y":17,"facing":0}},"terrain":"sea"},"Map_Tile_9_18":{"terrain":"sea"},"Map_Tile_9_19":{"terrain":"sea"},"Map_Tile_10_0":{"terrain":"sea"},"Map_Tile_10_1":{"terrain":"sea"},"Map_Tile_10_2":{"terrain":"sea"},"Map_Tile_10_3":{"terrain":"plains"},"Map_Tile_10_4":{"terrain":"forest"},"Map_Tile_10_5":{"terrain":"plains"},"Map_Tile_10_6":{"terrain":"beach"},"Map_Tile_10_7":{"terrain":"beach"},"Map_Tile_10_8":{"terrain":"forest"},"Map_Tile_10_9":{"terrain":"road"},"Map_Tile_10_10":{"terrain":"plains"},"Map_Tile_10_11":{"terrain":"beach"},"Map_Tile_10_12":{"terrain":"forest"},"Map_Tile_10_13":{"terrain":"plains"},"Map_Tile_10_14":{"terrain":"plains"},"Map_Tile_10_15":{"terrain":"beach"},"Map_Tile_10_16":{"terrain":"beach"},"Map_Tile_10_17":{"terrain":"sea"},"Map_Tile_10_18":{"terrain":"reef"},"Map_Tile_10_19":{"terrain":"sea"},"Map_Tile_11_0":{"terrain":"sea"},"Map_Tile_11_1":{"terrain":"sea"},"Map_Tile_11_2":{"terrain":"sea"},"Map_Tile_11_3":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":13,"factionOverride":"","pos":{"x":11,"y":3,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"dog","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["dog","type.ground.light","animal"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["bite"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"bite","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":5,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":150,"aliasId":"","id":"dog","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":11,"y":3,"facing":3}},"terrain":"plains"},"Map_Tile_11_4":{"terrain":"plains"},"Map_Tile_11_5":{"terrain":"beach"},"Map_Tile_11_6":{"terrain":"beach"},"Map_Tile_11_7":{"terrain":"beach"},"Map_Tile_11_8":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":15,"factionOverride":"","pos":{"x":11,"y":8,"facing":3},"recruits":{},"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"spearman","transportedBy":-1,"unitClass":{"passiveMultiplier":1.5,"tags":["spearman","type.ground.light"],"canBeActivated":false,"movementType":"walking","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":["spear"],"verbCostMultiplier":1,"weapons":[{"canAttackSubmerged":false,"minRange":1,"blockedByEnemies":false,"canMoveAndAttack":true,"terrainExclusion":{},"unitIdWhenAttacking":"","directionality":"omni","maxRange":1,"canCounterAttack":true,"id":"spear","horizontalAndVerticalOnly":false,"canAttackAir":false,"horizontalAndVerticalExtraWidth":0}],"critConditionId":"","moveRange":3,"canReinforce":false,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":false,"transportTags":{},"cost":250,"aliasId":"","id":"spearman","resourceCost":1,"isStructure":false,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":1,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":11,"y":8,"facing":3}},"terrain":"plains"},"Map_Tile_11_9":{"terrain":"road"},"Map_Tile_11_10":{"terrain":"plains"},"Map_Tile_11_11":{"terrain":"beach"},"Map_Tile_11_12":{"terrain":"beach"},"Map_Tile_11_13":{"terrain":"plains"},"Map_Tile_11_14":{"terrain":"forest"},"Map_Tile_11_15":{"terrain":"plains"},"Map_Tile_11_16":{"terrain":"sea"},"Map_Tile_11_17":{"terrain":"ocean"},"Map_Tile_11_18":{"terrain":"ocean"},"Map_Tile_11_19":{"terrain":"ocean"},"Map_Tile_12_0":{"terrain":"ocean"},"Map_Tile_12_1":{"terrain":"ocean"},"Map_Tile_12_2":{"terrain":"sea"},"Map_Tile_12_3":{"terrain":"plains"},"Map_Tile_12_4":{"terrain":"plains"},"Map_Tile_12_5":{"terrain":"beach"},"Map_Tile_12_6":{"terrain":"ocean"},"Map_Tile_12_7":{"terrain":"ocean"},"Map_Tile_12_8":{"terrain":"sea"},"Map_Tile_12_9":{"terrain":"reef"},"Map_Tile_12_10":{"terrain":"sea"},"Map_Tile_12_11":{"terrain":"ocean"},"Map_Tile_12_12":{"terrain":"ocean"},"Map_Tile_12_13":{"terrain":"plains"},"Map_Tile_12_14":{"terrain":"plains"},"Map_Tile_12_15":{"unit":{"stunned":false,"blessings":{},"canChargeGroove":true,"merchantDiscountMultiplier":0,"hasBeenKilled":false,"recruitDiscounts":{},"miniGrooveId":"","killedByLosing":false,"loadedUnits":{},"recruitDiscountMultiplier":0,"rangedDamageTakenPercent":100,"merchantDiscounts":{},"attackerUnitClass":"","id":19,"factionOverride":"","pos":{"x":12,"y":15,"facing":0},"recruits":["travelboat","caravel","merman","turtle","harpoonship","frog","kraken","warship"],"attackerPlayerId":-1,"grooveId":"","setGroove":null,"setHealth":null,"unitClassId":"port","transportedBy":-1,"unitClass":{"passiveMultiplier":1,"tags":["structure"],"canBeActivated":false,"movementType":"river_sea_building","isDamagingParentUnit":false,"canAttack":true,"isCommander":false,"weaponIds":{},"verbCostMultiplier":1,"weapons":{},"critConditionId":"","moveRange":0,"canReinforce":true,"inWater":false,"maxGroove":0,"maxHealth":100,"reinforceMultiplier":1,"isAttackable":true,"inAir":false,"isRecruitable":true,"loadCapacity":0,"canBeCaptured":true,"transportTags":{},"cost":500,"aliasId":"","id":"port","resourceCost":1,"isStructure":true,"recruitingCostMultiplier":1},"inTransport":false,"canBeAttackedFromDistance":true,"attackerId":-1,"canBeAttacked":true,"hadTurn":false,"garrisonClassId":"garrison","itemDropNumber":0,"underwater":false,"attachedFlagId":-1,"state":{},"itemId":"","items":{},"tentacled":false,"playerId":0,"health":100,"damageTakenPercent":100,"grooveCharge":0,"startPos":{"x":12,"y":15,"facing":0}},"terrain":"sea"},"Map_Tile_12_16":{"terrain":"sea"},"Map_Tile_12_17":{"terrain":"ocean"},"Map_Tile_12_18":{"terrain":"ocean"},"Map_Tile_12_19":{"terrain":"ocean"},"Map_Tile_13_0":{"terrain":"ocean"},"Map_Tile_13_1":{"terrain":"ocean"},"Map_Tile_13_2":{"terrain":"sea"},"Map_Tile_13_3":{"terrain":"sea"},"Map_Tile_13_4":{"terrain":"sea"},"Map_Tile_13_5":{"terrain":"sea"},"Map_Tile_13_6":{"terrain":"ocean"},"Map_Tile_13_7":{"terrain":"ocean"},"Map_Tile_13_8":{"terrain":"sea"},"Map_Tile_13_9":{"terrain":"sea"},"Map_Tile_13_10":{"terrain":"sea"},"Map_Tile_13_11":{"terrain":"ocean"},"Map_Tile_13_12":{"terrain":"ocean"},"Map_Tile_13_13":{"terrain":"sea"},"Map_Tile_13_14":{"terrain":"sea"},"Map_Tile_13_15":{"terrain":"sea"},"Map_Tile_13_16":{"terrain":"sea"},"Map_Tile_13_17":{"terrain":"ocean"},"Map_Tile_13_18":{"terrain":"ocean"},"Map_Tile_13_19":{"terrain":"ocean"},"Objectives":["Build a Kraken.","Win with a Kraken still alive (Requires Warship, Kraken and Spearman).","Win by defeating all enemies or surviving for 10 turns (Requires Warship and Spearman)."],"Player_1":{"recruit_wagon":true,"recruit_merman":true,"recruit_turtle":true,"recruit_griffin_walking":true,"recruit_dog":true,"recruit_warship":true,"recruit_archer":true,"recruit_soldier":true,"team":0,"recruit_trebuchet":true,"recruit_caravel":true,"recruit_mage":true,"gold":3000,"recruit_harpy":true,"recruit_kraken":true,"recruit_knight":true,"recruit_witch":true,"recruit_travelboat":true,"recruit_balloon":true,"recruit_harpoonship":true,"recruit_giant":true,"recruit_thief":true,"recruit_frog":true,"recruit_ballista":true,"recruit_spearman":true,"recruit_dragon":true,"recruit_rifleman":true},"Player_2":{"recruit_wagon":true,"recruit_merman":true,"recruit_turtle":true,"recruit_griffin_walking":true,"recruit_dog":true,"recruit_warship":true,"recruit_archer":true,"recruit_soldier":true,"team":1,"recruit_trebuchet":true,"recruit_caravel":true,"recruit_mage":true,"gold":100,"recruit_harpy":true,"recruit_kraken":true,"recruit_knight":true,"recruit_witch":true,"recruit_travelboat":true,"recruit_balloon":true,"recruit_harpoonship":true,"recruit_giant":true,"recruit_thief":true,"recruit_frog":true,"recruit_ballista":true,"recruit_spearman":true,"recruit_dragon":true,"recruit_rifleman":true},"Player_Count":2,"Triggers":[{"players":[1,1,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":{},"recurring":"start_of_match","id":"Export","actions":[{"id":"ap_export","enabled":true,"parameters":["421","Bridge Brigade","Fly Sniper","Build a Kraken.","Win with a Kraken still alive (Requires Warship, Kraken and Spearman).","","Win by defeating all of the enemies (Requires Warship and Spearman)."]}]},{"players":[1,1,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_presence","enabled":true,"parameters":["current","0","0","*unit_structure","-1"]}],"recurring":"oncePerPlayer","id":"$trigger_default_defeat_no_units","actions":[{"id":"eliminate","enabled":true,"parameters":["current"]}]},{"players":[1,1,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_lost","enabled":true,"parameters":["*commander","current","-1"]}],"recurring":"oncePerPlayer","id":"$trigger_default_defeat_commander","actions":[{"id":"eliminate","enabled":true,"parameters":["current"]}]},{"players":[1,1,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_lost","enabled":true,"parameters":["hq","current","-1"]}],"recurring":"oncePerPlayer","id":"$trigger_default_defeat_hq","actions":[{"id":"eliminate","enabled":true,"parameters":["current"]}]},{"players":[1,1,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"number_of_opponents","enabled":true,"parameters":["current","0","0"]}],"recurring":"oncePerPlayer","id":"$trigger_default_victory","actions":[{"id":"victory","enabled":true,"parameters":["current"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"current_turn_number","enabled":true,"parameters":["0","10"]}],"recurring":"once","id":"Turns Up","actions":[{"id":"victory","enabled":true,"parameters":["current"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"player_victorious","enabled":true,"parameters":["current"]}],"recurring":"once","id":"P1 Victory (253039)","actions":[{"id":"ap_location_send","enabled":true,"parameters":["253039"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_presence","enabled":true,"parameters":["current","1","0","kraken","-1"]}],"recurring":"once","id":"Kraken is Built (253040)","actions":[{"id":"ap_location_send","enabled":true,"parameters":["253040"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"player_victorious","enabled":true,"parameters":["current"]},{"id":"unit_presence","enabled":true,"parameters":["current","1","0","kraken","-1"]}],"recurring":"once","id":"P1 Victory With Kraken (253041)","actions":[{"id":"ap_location_send","enabled":true,"parameters":["253041"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_presence","enabled":true,"parameters":["current","1","0","*unit","1"]}],"recurring":"once","id":"Build Removes Port 1","actions":[{"id":"remove_units","enabled":true,"parameters":["*structure","1","current","0","0"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_presence","enabled":true,"parameters":["current","1","0","*unit","2"]}],"recurring":"once","id":"Build Removes Port 2","actions":[{"id":"remove_units","enabled":true,"parameters":["*structure","2","current","0","0"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"unit_presence","enabled":true,"parameters":["current","1","0","*unit","3"]}],"recurring":"once","id":"Build Removes Port 3","actions":[{"id":"remove_units","enabled":true,"parameters":["*structure","3","current","0","0"]}]},{"players":[1,0,0,0,0,0,0,0],"isIntro":false,"enabled":true,"conditions":[{"id":"ap_has_item","enabled":true,"parameters":["252000","0","1"]}],"recurring":"start_of_match","id":"Init","actions":[{"id":"ap_spawn_unit","enabled":true,"parameters":["spearman","0","P1","1","1","6","1","undefined","centre"]},{"id":"dialogue_box_simple","enabled":true,"parameters":["happy","phil","The Bridge Brigade is here!","1","Will die on this bridge"]}]}]} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Cherrystone_Landing.json b/worlds/wargroove2/levels/Cherrystone_Landing.json new file mode 100644 index 000000000000..4deda97866b8 --- /dev/null +++ b/worlds/wargroove2/levels/Cherrystone_Landing.json @@ -0,0 +1 @@ +{"Map_Tile_21_3":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"beach"}, "Map_Tile_22_2":{"terrain":"beach"}, "Map_Tile_27_5":{"terrain":"ocean"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_22_3":{"terrain":"beach"}, "Map_Tile_13_0":{"terrain":"beach"}, "Map_Tile_29_4":{"terrain":"ocean"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_18_2":{"terrain":"beach"}, "Map_Tile_17_2":{"terrain":"beach"}, "Map_Tile_29_2":{"terrain":"ocean"}, "Map_Tile_26_6":{"terrain":"ocean"}, "Map_Tile_23_6":{"terrain":"sea"}, "Map_Tile_9_4":{"terrain":"bridge"}, "Map_Tile_16_8":{"terrain":"beach"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_20_6":{"terrain":"beach"}, "Map_Tile_26_0":{"terrain":"ocean"}, "Map_Tile_17_6":{"terrain":"road"}, "Map_Tile_9_8":{"terrain":"beach"}, "Map_Tile_14_9":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":9, "x":14, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":3, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":9, "x":14, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_7_5":{"terrain":"beach"}, "Map_Tile_17_4":{"terrain":"beach"}, "Map_Tile_17_1":{"terrain":"beach"}, "Map_Tile_17_3":{"terrain":"beach"}, "Map_Tile_25_2":{"terrain":"ocean"}, "Map_Size":{"y":10, "x":30}, "Map_Tile_15_3":{"terrain":"road"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_27_2":{"terrain":"ocean"}, "Map_Tile_10_8":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":8, "x":10, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":5, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":8, "x":10, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"beach"}, "Map_Tile_17_9":{"terrain":"road"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"beach"}, "Map_Tile_11_2":{"terrain":"beach"}, "Map_Tile_29_8":{"terrain":"ocean"}, "Map_Tile_24_1":{"terrain":"sea"}, "Map_Tile_2_9":{"terrain":"forest"}, "Map_Tile_5_3":{"terrain":"plains"}, "Objectives":["Defeat a Trebuchet with your Golem.", "Defeat the Fortified Village with your Golem.", "Win with standard conditions (Requires Landing Event, Barge and Warship)."], "Map_Tile_26_2":{"terrain":"ocean"}, "Map_Tile_6_2":{"terrain":"beach"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_22_1":{"terrain":"beach"}, "Map_Tile_19_6":{"terrain":"beach"}, "Map_Tile_8_3":{"terrain":"forest"}, "Map_Tile_13_1":{"terrain":"beach"}, "Map_Tile_11_1":{"terrain":"beach"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_28_7":{"terrain":"ocean"}, "Map_Tile_0_0":{"terrain":"plains"}, "Author":"Fly Sniper", "Map_Tile_12_0":{"terrain":"beach"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"beach"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_22_8":{"terrain":"beach"}, "Map_Tile_8_1":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":1, "x":8, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":6, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":1, "x":8, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_10_4":{"terrain":"bridge"}, "Map_Tile_20_2":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_23_8":{"terrain":"sea"}, "Map_Tile_23_2":{"terrain":"sea"}, "Map_Tile_20_7":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"walking", "tags":["soldier", "type.ground.light"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":100, "loadCapacity":0, "weaponIds":["sword"], "isStructure":false, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.5, "weapons":[{"maxRange":1, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"sword", "canCounterAttack":true, "blockedByEnemies":false, "canAttackAir":false, "canMoveAndAttack":true, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "minRange":1, "canAttackSubmerged":false}], "canBeActivated":false, "id":"soldier", "transportTags":{}, "moveRange":4, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":7, "x":20, "facing":3}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":13, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":7, "x":20, "facing":3}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"soldier", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_19_1":{"terrain":"mountain"}, "Player_1":{"recruit_ballista":true, "recruit_thief":true, "recruit_turtle":true, "recruit_trebuchet":true, "gold":300, "recruit_dragon":true, "recruit_wagon":true, "recruit_dog":true, "recruit_giant":true, "recruit_knight":true, "team":0, "recruit_soldier":true, "recruit_travelboat":true, "recruit_harpy":true, "recruit_kraken":true, "recruit_frog":true, "recruit_archer":true, "recruit_mage":true, "recruit_witch":true, "recruit_rifleman":true, "recruit_warship":true, "recruit_balloon":true, "recruit_spearman":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_griffin_walking":true, "recruit_caravel":true}, "Map_Tile_23_9":{"terrain":"sea"}, "Map_Tile_14_6":{"terrain":"beach"}, "Map_Tile_6_3":{"terrain":"beach"}, "Map_Tile_10_2":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"river_sea_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"port_ap", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":2, "x":10, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":1, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":2, "x":10, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"port_ap", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"ocean"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_18_0":{"terrain":"beach"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_18_7":{"terrain":"beach"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_29_6":{"terrain":"ocean"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"beach"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"road"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"beach"}, "Map_Tile_4_4":{"terrain":"road"}, "Map_Tile_22_5":{"terrain":"sea"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"forest"}, "Map_Tile_25_7":{"terrain":"ocean"}, "Map_Tile_6_4":{"terrain":"road"}, "Map_Tile_27_3":{"terrain":"ocean"}, "Map_Tile_18_4":{"terrain":"beach"}, "Map_Tile_18_9":{"terrain":"plains"}, "Map_Tile_8_4":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"wheels", "tags":["trebuchet", "type.ground.heavy"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":1100, "loadCapacity":0, "weaponIds":["trebuchetSling"], "isStructure":false, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.5, "weapons":[{"maxRange":5, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"trebuchetSling", "canCounterAttack":true, "blockedByEnemies":false, "canAttackAir":false, "canMoveAndAttack":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "minRange":3, "canAttackSubmerged":false}], "canBeActivated":false, "id":"trebuchet", "transportTags":{}, "moveRange":5, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":3}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":4, "x":8, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":17, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":4, "x":8, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"trebuchet", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_17_0":{"terrain":"plains"}, "Map_Tile_21_1":{"terrain":"plains"}, "Map_Tile_24_9":{"terrain":"sea"}, "Map_Tile_7_8":{"terrain":"beach"}, "Map_Tile_19_0":{"terrain":"plains"}, "Map_Tile_19_2":{"terrain":"beach"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"beach"}, "Map_Tile_5_4":{"terrain":"road"}, "Map_Tile_17_8":{"terrain":"bridge"}, "Map_Tile_20_0":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"walking", "tags":["soldier", "type.ground.light"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":100, "loadCapacity":0, "weaponIds":["sword"], "isStructure":false, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.5, "weapons":[{"maxRange":1, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"sword", "canCounterAttack":true, "blockedByEnemies":false, "canAttackAir":false, "canMoveAndAttack":true, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "minRange":1, "canAttackSubmerged":false}], "canBeActivated":false, "id":"soldier", "transportTags":{}, "moveRange":4, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":0, "x":20, "facing":3}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":12, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":0, "x":20, "facing":3}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"soldier", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_17_7":{"terrain":"bridge"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_23_0":{"terrain":"beach"}, "Map_Tile_16_7":{"terrain":"beach"}, "Map_Tile_0_9":{"terrain":"mountain"}, "Player_2":{"recruit_ballista":false, "recruit_thief":true, "recruit_turtle":true, "recruit_trebuchet":true, "gold":100, "recruit_dragon":true, "recruit_wagon":true, "recruit_dog":true, "recruit_giant":true, "recruit_knight":true, "team":1, "recruit_soldier":true, "recruit_travelboat":true, "recruit_harpy":true, "recruit_kraken":true, "recruit_frog":false, "recruit_archer":true, "recruit_mage":true, "recruit_witch":true, "recruit_rifleman":true, "recruit_warship":true, "recruit_balloon":true, "recruit_spearman":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_griffin_walking":true, "recruit_caravel":true}, "Map_Tile_12_9":{"terrain":"beach"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_23_1":{"terrain":"beach"}, "Map_Tile_4_1":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"walking", "tags":["soldier", "type.ground.light"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":100, "loadCapacity":0, "weaponIds":["sword"], "isStructure":false, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.5, "weapons":[{"maxRange":1, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"sword", "canCounterAttack":true, "blockedByEnemies":false, "canAttackAir":false, "canMoveAndAttack":true, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "minRange":1, "canAttackSubmerged":false}], "canBeActivated":false, "id":"soldier", "transportTags":{}, "moveRange":4, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":1, "x":4, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":15, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":1, "x":4, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"soldier", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_12_7":{"terrain":"beach"}, "Map_Tile_23_5":{"terrain":"sea"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_11_8":{"terrain":"forest"}, "Map_Tile_22_9":{"terrain":"beach"}, "Map_Tile_8_0":{"terrain":"beach"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"road"}, "Map_Tile_24_7":{"terrain":"sea"}, "Map_Tile_29_1":{"terrain":"ocean"}, "Map_Tile_28_9":{"terrain":"ocean"}, "Map_Tile_20_9":{"terrain":"beach"}, "Map_Tile_12_5":{"terrain":"beach"}, "Map_Tile_14_4":{"terrain":"beach"}, "Map_Tile_16_0":{"terrain":"forest"}, "Map_Tile_14_5":{"terrain":"beach"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"beach"}, "Map_Tile_14_1":{"terrain":"beach"}, "Map_Tile_27_0":{"terrain":"ocean"}, "Map_Tile_21_5":{"terrain":"reef"}, "Map_Tile_3_6":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":3000, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"hq", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":6, "x":3, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":8, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":6, "x":3, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"hq", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_13_6":{"terrain":"beach"}, "Map_Tile_21_9":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_9_1":{"terrain":"beach"}, "Map_Tile_15_0":{"terrain":"forest"}, "Map_Tile_19_9":{"terrain":"beach"}, "Map_Tile_10_6":{"terrain":"beach"}, "Map_Tile_21_7":{"terrain":"beach"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"road"}, "Map_Tile_12_8":{"terrain":"beach"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_21_0":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"forest"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"forest"}, "Map_Tile_14_8":{"terrain":"beach"}, "Map_Tile_15_1":{"terrain":"beach"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"forest"}, "Player_Count":2, "Map_Tile_19_8":{"terrain":"beach"}, "Map_Tile_8_5":{"terrain":"beach"}, "Map_Tile_29_7":{"terrain":"ocean"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"bridge"}, "Counters":{"0":0}, "Map_Tile_18_5":{"terrain":"beach"}, "Map_Tile_18_3":{"terrain":"forest"}, "Map_Tile_7_4":{"terrain":"bridge"}, "Locations":{"1":{"positions":[{"y":7, "x":20}], "interactable":false, "setArea":null, "name":"Soldier To Spearman", "id":1, "centre":{"y":7, "x":20}, "getArea":null}, "2":{"positions":[{"y":1, "x":4}], "interactable":false, "setArea":null, "name":"Soldier to Mage", "id":2, "centre":{"y":1, "x":4}, "getArea":null}, "3":{"positions":[{"y":3, "x":7}], "interactable":false, "setArea":null, "name":"Soldier to Knight", "id":3, "centre":{"y":3, "x":7}, "getArea":null}, "4":{"positions":[{"y":8, "x":6}], "interactable":false, "setArea":null, "name":"Soldier to Giant", "id":4, "centre":{"y":8, "x":6}, "getArea":null}, "0":{"positions":[{"y":0, "x":27}, {"y":1, "x":27}, {"y":2, "x":27}, {"y":3, "x":27}, {"y":4, "x":28}, {"y":5, "x":28}, {"y":6, "x":28}, {"y":7, "x":28}, {"y":8, "x":28}, {"y":9, "x":28}, {"y":9, "x":27}, {"y":8, "x":27}, {"y":7, "x":27}, {"y":6, "x":27}, {"y":5, "x":27}, {"y":4, "x":27}, {"y":0, "x":28}, {"y":1, "x":28}, {"y":1, "x":29}, {"y":2, "x":29}, {"y":3, "x":29}, {"y":5, "x":29}, {"y":6, "x":29}, {"y":7, "x":29}, {"y":8, "x":29}, {"y":9, "x":29}, {"y":3, "x":28}, {"y":2, "x":28}, {"y":0, "x":29}, {"y":4, "x":29}, {"y":9, "x":26}, {"y":9, "x":25}, {"y":8, "x":25}, {"y":8, "x":26}, {"y":7, "x":26}, {"y":7, "x":25}, {"y":6, "x":25}, {"y":5, "x":25}, {"y":4, "x":25}, {"y":3, "x":25}, {"y":2, "x":25}, {"y":1, "x":25}, {"y":0, "x":25}, {"y":0, "x":26}, {"y":1, "x":26}, {"y":2, "x":26}, {"y":3, "x":26}, {"y":4, "x":26}, {"y":5, "x":26}, {"y":6, "x":26}], "interactable":false, "setArea":null, "name":"Fleet Location", "id":0, "centre":{"y":5, "x":27}, "getArea":null}}, "Map_Tile_29_9":{"terrain":"ocean"}, "Map_Tile_29_5":{"terrain":"ocean"}, "Map_Tile_29_3":{"terrain":"ocean"}, "Map_Tile_6_1":{"terrain":"beach"}, "Map_Tile_23_4":{"terrain":"sea"}, "Map_Tile_29_0":{"terrain":"ocean"}, "Map_Tile_28_8":{"terrain":"ocean"}, "Map_Tile_28_6":{"terrain":"ocean"}, "Map_Tile_19_3":{"terrain":"beach"}, "Map_Tile_28_4":{"terrain":"ocean"}, "Map_Tile_27_9":{"terrain":"ocean"}, "Map_Tile_28_2":{"terrain":"ocean"}, "Map_Tile_28_1":{"terrain":"ocean"}, "Map_Tile_9_0":{"terrain":"forest"}, "Map_Tile_28_0":{"terrain":"ocean"}, "Map_Tile_28_3":{"terrain":"ocean"}, "Map_Tile_11_5":{"terrain":"beach"}, "Map_Tile_27_8":{"terrain":"ocean"}, "Map_Tile_14_2":{"terrain":"beach"}, "Map_Tile_7_1":{"terrain":"beach"}, "Map_Tile_7_0":{"terrain":"beach"}, "Map_Tile_27_7":{"terrain":"ocean"}, "Map_Tile_23_7":{"terrain":"sea"}, "Map_Tile_27_4":{"terrain":"ocean"}, "Map_Tile_27_1":{"terrain":"ocean"}, "Map_Tile_26_9":{"terrain":"ocean"}, "Map_Tile_28_5":{"terrain":"ocean"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"beach"}, "Map_Tile_18_1":{"terrain":"beach"}, "Map_Tile_24_6":{"terrain":"sea"}, "Map_Tile_21_8":{"terrain":"plains"}, "Map_Tile_26_7":{"terrain":"ocean"}, "Map_Tile_26_5":{"terrain":"ocean"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_26_4":{"terrain":"ocean"}, "Map_Tile_26_3":{"terrain":"ocean"}, "Map_Tile_26_1":{"terrain":"ocean"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_25_9":{"terrain":"ocean"}, "Map_Tile_25_8":{"terrain":"ocean"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"beach"}, "Map_Tile_6_6":{"terrain":"forest"}, "Map_Tile_25_5":{"terrain":"ocean"}, "Map_Tile_25_4":{"terrain":"ocean"}, "Map_Tile_15_6":{"terrain":"beach"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"beach"}, "Map_Tile_25_3":{"terrain":"ocean"}, "Map_Tile_25_1":{"terrain":"ocean"}, "Map_Tile_22_0":{"terrain":"forest"}, "Map_Tile_20_1":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["fortified_city"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":1000, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"fortified_city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":1, "x":20, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":2, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":1, "x":20, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"fortified_garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"fortified_city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_24_8":{"terrain":"sea"}, "Map_Tile_5_8":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":8, "x":5, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":10, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":8, "x":5, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_11_0":{"terrain":"forest"}, "Map_Tile_21_6":{"terrain":"beach"}, "Map_Tile_24_5":{"terrain":"sea"}, "Map_Tile_24_4":{"terrain":"sea"}, "Map_Tile_24_3":{"terrain":"sea"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_24_2":{"terrain":"sea"}, "Map_Tile_21_2":{"terrain":"plains"}, "Map_Tile_20_4":{"terrain":"beach"}, "Map_Tile_24_0":{"terrain":"sea"}, "Map_Tile_9_9":{"terrain":"beach"}, "Map_Tile_16_1":{"terrain":"beach"}, "Triggers":[{"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["30000"], "enabled":true, "id":"ap_export"}], "isIntro":false, "conditions":{}, "enabled":true, "id":"Export", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "isIntro":false, "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "enabled":true, "id":"$trigger_default_defeat_no_units", "recurring":"oncePerPlayer"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "isIntro":false, "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "enabled":true, "id":"$trigger_default_defeat_commander", "recurring":"oncePerPlayer"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "isIntro":false, "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "enabled":true, "id":"$trigger_default_defeat_hq", "recurring":"oncePerPlayer"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "isIntro":false, "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "enabled":true, "id":"$trigger_default_victory", "recurring":"oncePerPlayer"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["253010"], "enabled":true, "id":"ap_location_send"}], "isIntro":false, "conditions":[{"parameters":["P1"], "enabled":true, "id":"player_victorious"}], "enabled":true, "id":"P1 Victory", "recurring":"once"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["neutral", "valder", "Sigrid! I did not order an attack on Cherrystone's Harbor! Retreat at once! (Requires Landing Event to sneak past Valder)", "1", "Valder"], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["P1"], "enabled":true, "id":"eliminate"}], "isIntro":false, "conditions":[{"parameters":["252025", "0", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Check for Landing Event", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["travelboat", "0", "P1", "0", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["252000", "0"], "enabled":true, "id":"ap_count_item"}, {"parameters":["3", "0", "0", "0"], "enabled":true, "id":"conditional_skip_actions"}, {"parameters":["spearman", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["spearman", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["2"], "enabled":true, "id":"skip_actions"}, {"parameters":["soldier", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["soldier", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["0"], "enabled":true, "id":"wait"}], "isIntro":false, "conditions":[{"parameters":["252013", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Spawn Barge Spearmen", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["travelboat", "0", "P1", "0", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["252002", "0"], "enabled":true, "id":"ap_count_item"}, {"parameters":["3", "0", "0", "0"], "enabled":true, "id":"conditional_skip_actions"}, {"parameters":["mage", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["mage", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["2"], "enabled":true, "id":"skip_actions"}, {"parameters":["dog", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["dog", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["0"], "enabled":true, "id":"wait"}], "isIntro":false, "conditions":[{"parameters":["252013", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Spawn Barge Mage", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["travelboat", "0", "P1", "0", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["252004", "0"], "enabled":true, "id":"ap_count_item"}, {"parameters":["3", "0", "0", "0"], "enabled":true, "id":"conditional_skip_actions"}, {"parameters":["knight", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["knight", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["2"], "enabled":true, "id":"skip_actions"}, {"parameters":["soldier", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["soldier", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["0"], "enabled":true, "id":"wait"}], "isIntro":false, "conditions":[{"parameters":["252013", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Spawn Barge Knight", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["travelboat", "0", "P1", "0", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["252007", "0"], "enabled":true, "id":"ap_count_item"}, {"parameters":["3", "0", "0", "0"], "enabled":true, "id":"conditional_skip_actions"}, {"parameters":["giant", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["giant", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["2"], "enabled":true, "id":"skip_actions"}, {"parameters":["dog", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["dog", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["0"], "enabled":true, "id":"wait"}], "isIntro":false, "conditions":[{"parameters":["252013", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Spawn Barge Golem", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["travelboat", "0", "P1", "0", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["*commander", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}, {"parameters":["soldier", "-9", "P1"], "enabled":true, "id":"spawn_unit_inside"}], "isIntro":false, "conditions":[{"parameters":["252013", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Spawn Barge Commander", "recurring":"start_of_match"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["warship", "0", "P1", "0", "1", "3", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}], "isIntro":false, "conditions":[{"parameters":["252018", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Spawn Warships", "recurring":"start_of_match"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["*non_commander", "P2", "1", "spearman"], "enabled":true, "id":"change_unit_type"}], "isIntro":false, "conditions":[{"parameters":["252000", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Soldier to Spearman", "recurring":"start_of_match"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["*non_commander", "P2", "2", "mage"], "enabled":true, "id":"change_unit_type"}], "isIntro":false, "conditions":[{"parameters":["252002", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Soldier to Mage", "recurring":"start_of_match"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["*non_commander", "P2", "3", "knight"], "enabled":true, "id":"change_unit_type"}], "isIntro":false, "conditions":[{"parameters":["252004", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Soldier to Knight", "recurring":"start_of_match"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["*non_commander", "P2", "4", "giant"], "enabled":true, "id":"change_unit_type"}], "isIntro":false, "conditions":[{"parameters":["252007", "1", "0"], "enabled":true, "id":"ap_has_item"}], "enabled":true, "id":"Soldier to Giant", "recurring":"start_of_match"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["253008"], "enabled":true, "id":"ap_location_send"}], "isIntro":false, "conditions":[{"parameters":["current"], "enabled":true, "id":"player_turn"}, {"parameters":["giant", "P1", "trebuchet", "P2", "-1"], "enabled":true, "id":"unit_killed"}], "enabled":true, "id":"Giant Kill Trebuchet", "recurring":"once"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "actions":[{"parameters":["253009"], "enabled":true, "id":"ap_location_send"}], "isIntro":false, "conditions":[{"parameters":["current"], "enabled":true, "id":"player_turn"}, {"parameters":["giant", "P1", "fortified_city", "P2", "-1"], "enabled":true, "id":"unit_killed"}], "enabled":true, "id":"Giant Kill Fortified Village", "recurring":"once"}], "Map_Tile_27_6":{"terrain":"ocean"}, "Map_Tile_1_1":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"barracks", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":1, "x":1, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":7, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":1, "x":1, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"barracks", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_23_3":{"terrain":"sea"}, "Map_Tile_7_3":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"walking", "tags":["soldier", "type.ground.light"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":100, "loadCapacity":0, "weaponIds":["sword"], "isStructure":false, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.5, "weapons":[{"maxRange":1, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"sword", "canCounterAttack":true, "blockedByEnemies":false, "canAttackAir":false, "canMoveAndAttack":true, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "minRange":1, "canAttackSubmerged":false}], "canBeActivated":false, "id":"soldier", "transportTags":{}, "moveRange":4, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":3, "x":7, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":16, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":3, "x":7, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"soldier", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"beach"}, "Map_Tile_12_3":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":3, "x":12, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":4, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":3, "x":12, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_15_4":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":4, "x":15, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":11, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":4, "x":15, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"road"}, "Map_Tile_7_7":{"terrain":"beach"}, "Map_Tile_15_5":{"terrain":"beach"}, "Map_Tile_13_4":{"terrain":"forest"}, "Map_Tile_22_7":{"terrain":"beach"}, "Map_Tile_22_6":{"terrain":"sea"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"beach"}, "Map_Tile_22_4":{"terrain":"beach"}, "Map_Tile_13_3":{"terrain":"road"}, "Map_Tile_4_2":{"terrain":"road"}, "Map_Tile_25_6":{"terrain":"ocean"}, "Map_Tile_25_0":{"terrain":"ocean"}, "Map_Tile_10_1":{"terrain":"beach"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_26_8":{"terrain":"ocean"}, "Map_Tile_12_1":{"terrain":"beach"}, "Map_Tile_7_9":{"terrain":"beach"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_19_7":{"terrain":"beach"}, "Map_Tile_21_4":{"terrain":"beach"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"beach"}, "Map_Tile_11_3":{"terrain":"beach"}, "Map_Name":"Cherrystone Landing", "Map_Tile_9_7":{"terrain":"beach"}, "Map_Tile_15_8":{"terrain":"beach"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_20_8":{"terrain":"forest"}, "Map_Tile_8_8":{"terrain":"forest"}, "Map_Tile_20_5":{"terrain":"beach"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"road"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_6_8":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"walking", "tags":["soldier", "type.ground.light"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":100, "loadCapacity":0, "weaponIds":["sword"], "isStructure":false, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.5, "weapons":[{"maxRange":1, "terrainExclusion":{}, "unitIdWhenAttacking":"", "id":"sword", "canCounterAttack":true, "blockedByEnemies":false, "canAttackAir":false, "canMoveAndAttack":true, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "minRange":1, "canAttackSubmerged":false}], "canBeActivated":false, "id":"soldier", "transportTags":{}, "moveRange":4, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":false, "critConditionId":"", "canBeCaptured":false, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":8, "x":6, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":14, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":8, "x":6, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"soldier", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_19_5":{"terrain":"beach"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"beach"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"forest"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"beach"}, "Flags":{}, "Map_Tile_9_2":{"terrain":"beach"}, "Map_Tile_15_2":{"terrain":"beach"}, "Map_Tile_8_6":{"terrain":"beach"}, "Map_Tile_8_2":{"terrain":"beach"}, "Map_Tile_4_3":{"terrain":"road"}, "Map_Tile_1_4":{"unit":{"inTransport":false, "itemId":"", "unitClass":{"maxGroove":0, "canAttack":true, "movementType":"land_building", "tags":["structure"], "reinforceMultiplier":1.0, "verbCostMultiplier":1.0, "recruitingCostMultiplier":1.0, "cost":500, "loadCapacity":0, "weaponIds":{}, "isStructure":true, "isDamagingParentUnit":false, "aliasId":"", "isCommander":false, "isAttackable":true, "maxHealth":100, "passiveMultiplier":1.0, "weapons":{}, "canBeActivated":false, "id":"city", "transportTags":{}, "moveRange":0, "inAir":false, "isRecruitable":true, "inWater":false, "canReinforce":true, "critConditionId":"", "canBeCaptured":true, "resourceCost":1}, "items":{}, "recruits":{}, "merchantDiscounts":{}, "canBeAttacked":true, "setGroove":null, "grooveId":"", "startPos":{"y":4, "x":1, "facing":0}, "factionOverride":"", "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "id":9, "transportedBy":-1, "canBeAttackedFromDistance":true, "hadTurn":false, "recruitDiscountMultiplier":0.0, "itemDropNumber":0, "tentacled":false, "loadedUnits":{}, "miniGrooveId":"", "setHealth":null, "attackerUnitClass":"", "pos":{"y":4, "x":1, "facing":0}, "health":100, "hasBeenKilled":false, "attachedFlagId":-1, "grooveCharge":0, "garrisonClassId":"garrison", "stunned":false, "underwater":false, "state":{}, "playerId":1, "recruitDiscounts":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "blessings":{}, "unitClassId":"city", "canChargeGroove":true, "killedByLosing":false, "attackerPlayerId":-1}, "terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Dark_Mirror.json b/worlds/wargroove2/levels/Dark_Mirror.json new file mode 100644 index 000000000000..dd078ec8d6c3 --- /dev/null +++ b/worlds/wargroove2/levels/Dark_Mirror.json @@ -0,0 +1 @@ +{"Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"sea"}, "Map_Tile_1_8":{"terrain":"road"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"sea"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Size":{"x":15, "y":14}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"sea"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":10, "facing":0, "y":10}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":10, "facing":0, "y":10}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":4, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_6_7":{"terrain":"forest"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"road"}, "Map_Tile_6_6":{"terrain":"mountain"}, "Map_Tile_5_1":{"terrain":"sea"}, "Map_Tile_14_12":{"terrain":"sea"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"road"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"sea"}, "Map_Tile_1_1":{"terrain":"sea"}, "Map_Tile_8_11":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":8, "facing":0, "y":11}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":8, "facing":0, "y":11}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":5, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_1_3":{"terrain":"beach"}, "Map_Tile_8_7":{"terrain":"forest"}, "Map_Tile_2_2":{"terrain":"bridge", "item":{"type":"heavy_armor", "pos":{"x":2, "y":2}, "unitTypeRestriction":{}, "isConsumable":false, "itemId":29}}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_4_12":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "recruitDiscounts":{}, "playerId":0, "startPos":{"x":4, "facing":0, "y":12}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":4, "facing":0, "y":12}, "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"barracks", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":10, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_11_1":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":11, "facing":0, "y":1}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":11, "facing":0, "y":1}, "unitClassId":"cursed_structure", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"cursed_structure", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":16, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"sea"}, "Map_Tile_9_1":{"terrain":"road"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":6, "facing":0, "y":8}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":6, "facing":0, "y":8}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":25, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_4_5":{"terrain":"plains"}, "Player_1":{"recruit_thief":true, "recruit_trebuchet":true, "recruit_rifleman":true, "recruit_warship":true, "recruit_wagon":true, "recruit_kraken":true, "recruit_balloon":true, "recruit_archer":true, "team":0, "recruit_harpy":true, "recruit_travelboat":true, "recruit_frog":true, "recruit_mage":true, "recruit_caravel":true, "recruit_harpoonship":true, "recruit_soldier":true, "recruit_turtle":true, "recruit_griffin_walking":true, "recruit_spearman":true, "gold":100, "recruit_giant":true, "recruit_knight":true, "recruit_ballista":true, "recruit_dragon":true, "recruit_merman":true, "recruit_witch":true, "recruit_dog":true}, "Map_Tile_8_13":{"terrain":"plains"}, "Player_2":{"recruit_thief":true, "recruit_trebuchet":true, "recruit_rifleman":true, "recruit_warship":true, "recruit_wagon":true, "recruit_kraken":true, "recruit_balloon":true, "recruit_archer":true, "team":1, "recruit_harpy":true, "recruit_travelboat":true, "recruit_frog":true, "recruit_mage":true, "recruit_caravel":true, "recruit_harpoonship":true, "recruit_soldier":true, "recruit_turtle":true, "recruit_griffin_walking":true, "recruit_spearman":true, "gold":100, "recruit_giant":true, "recruit_knight":true, "recruit_ballista":true, "recruit_dragon":true, "recruit_merman":true, "recruit_witch":true, "recruit_dog":true}, "Map_Tile_7_1":{"terrain":"plains"}, "Flags":{"0":0}, "Map_Tile_2_0":{"terrain":"sea"}, "Counters":{"1":0, "0":0}, "Map_Tile_1_7":{"terrain":"forest"}, "Map_Tile_1_4":{"terrain":"plains"}, "Locations":{"1":{"positions":[{"x":0, "y":0}, {"x":13, "y":1}, {"x":13, "y":13}], "setArea":null, "id":1, "centre":{"x":9, "y":5}, "interactable":false, "getArea":null, "name":"Enemy Army Shuffle"}, "2":{"positions":{}, "setArea":null, "id":2, "centre":{"x":0, "y":0}, "interactable":false, "getArea":null, "name":"Swordsman Balloon"}, "3":{"positions":[{"x":10, "y":2}], "setArea":null, "id":3, "centre":{"x":10, "y":2}, "interactable":false, "getArea":null, "name":"Shuffle2"}, "4":{"positions":[{"x":1, "y":9}, {"x":2, "y":10}, {"x":3, "y":11}, {"x":4, "y":12}, {"x":1, "y":12}, {"x":0, "y":11}, {"x":10, "y":0}, {"x":11, "y":1}, {"x":12, "y":2}, {"x":13, "y":3}, {"x":14, "y":4}], "setArea":null, "id":4, "centre":{"x":6, "y":7}, "interactable":false, "getArea":null, "name":"Production"}, "5":{"positions":{}, "setArea":null, "id":5, "centre":{"x":0, "y":0}, "interactable":false, "getArea":null, "name":"Allied Training"}, "6":{"positions":[{"x":14, "y":5}, {"x":13, "y":4}, {"x":12, "y":3}, {"x":11, "y":2}, {"x":10, "y":1}, {"x":9, "y":0}], "setArea":null, "id":6, "centre":{"x":12, "y":3}, "interactable":false, "getArea":null, "name":"Enemy Copying"}, "0":{"positions":[{"x":14, "y":8}, {"x":8, "y":11}, {"x":6, "y":4}, {"x":3, "y":6}, {"x":9, "y":7}, {"x":1, "y":4}], "setArea":null, "id":0, "centre":{"x":7, "y":7}, "interactable":false, "getArea":null, "name":"Shuffle1"}}, "Map_Tile_14_13":{"terrain":"beach"}, "Map_Tile_7_7":{"terrain":"forest"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"mountain", "item":{"type":"skeletal_gauntlet", "pos":{"x":11, "y":12}, "unitTypeRestriction":{}, "isConsumable":false, "itemId":27}}, "Map_Tile_13_10":{"terrain":"bridge"}, "Map_Tile_8_5":{"terrain":"forest"}, "Map_Tile_4_3":{"terrain":"mountain"}, "Map_Tile_2_10":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "recruitDiscounts":{}, "playerId":0, "startPos":{"x":2, "facing":0, "y":10}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":2, "facing":0, "y":10}, "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"barracks", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":8, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_14_11":{"terrain":"sea"}, "Map_Tile_14_10":{"terrain":"sea"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"sea"}, "Map_Tile_14_8":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":14, "facing":0, "y":8}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":14, "facing":0, "y":8}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":20, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"bridge"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"road"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":14, "facing":0, "y":4}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":14, "facing":0, "y":4}, "unitClassId":"cursed_structure", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"cursed_structure", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":19, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"sea"}, "Map_Tile_0_8":{"terrain":"road"}, "Map_Tile_14_0":{"terrain":"sea"}, "Map_Tile_10_4":{"terrain":"road"}, "Map_Tile_2_13":{"terrain":"sea"}, "Objectives":["Defeat the enemy Commander."], "Map_Tile_13_12":{"terrain":"bridge"}, "Map_Tile_5_3":{"terrain":"plains", "item":{"type":"guiding_light", "pos":{"x":5, "y":3}, "unitTypeRestriction":{}, "isConsumable":true, "itemId":26}}, "Map_Tile_13_11":{"terrain":"bridge"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"bridge"}, "Map_Tile_4_4":{"terrain":"forest"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"road"}, "Map_Tile_13_6":{"terrain":"road"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "recruitDiscounts":{}, "playerId":0, "startPos":{"x":1, "facing":0, "y":12}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":1, "facing":0, "y":12}, "unitClassId":"tower", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"tower", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":12, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_2_1":{"terrain":"sea"}, "Map_Tile_12_4":{"terrain":"road", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":12, "facing":3, "y":4}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":12, "facing":3, "y":4}, "unitClassId":"giant", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":false, "inWater":false, "weapons":[{"maxRange":1, "horizontalAndVerticalExtraWidth":0, "unitIdWhenAttacking":"", "canCounterAttack":true, "minRange":1, "id":"giantSlam", "terrainExclusion":{}, "canAttackSubmerged":false, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":false, "directionality":"omni", "canMoveAndAttack":true}], "canBeActivated":false, "id":"giant", "verbCostMultiplier":1.0, "tags":["giant", "type.ground.heavy", "tall"], "loadCapacity":0, "transportTags":{}, "moveRange":5, "passiveMultiplier":2.5, "resourceCost":3, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"riding", "inAir":false, "maxHealth":100, "weaponIds":["giantSlam"], "isCommander":false, "cost":1200, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":3, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_12_0":{"terrain":"sea"}, "Map_Tile_0_0":{"terrain":"sea"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"sea"}, "Author":"Magnemania", "Map_Tile_13_1":{"terrain":"sea", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":13, "facing":3, "y":1}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":13, "facing":3, "y":1}, "unitClassId":"harpy", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":false, "inWater":false, "weapons":[{"maxRange":1, "horizontalAndVerticalExtraWidth":0, "unitIdWhenAttacking":"", "canCounterAttack":true, "minRange":1, "id":"harpyClaws", "terrainExclusion":{}, "canAttackSubmerged":false, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":true, "directionality":"omni", "canMoveAndAttack":true}], "canBeActivated":false, "id":"harpy", "verbCostMultiplier":1.0, "tags":["harpy", "type.air"], "loadCapacity":0, "transportTags":{}, "moveRange":6, "passiveMultiplier":1.25, "resourceCost":2, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"flying", "inAir":true, "maxHealth":100, "weaponIds":["harpyClaws"], "isCommander":false, "cost":600, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":24, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_13_0":{"terrain":"sea"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "recruitDiscounts":{}, "playerId":0, "startPos":{"x":3, "facing":0, "y":11}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":3, "facing":0, "y":11}, "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"barracks", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":9, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_12_1":{"terrain":"sea"}, "Map_Tile_12_10":{"terrain":"sea"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"bridge"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"forest"}, "Map_Tile_12_8":{"terrain":"plains", "item":{"type":"groove_boost", "pos":{"x":12, "y":8}, "unitTypeRestriction":{}, "isConsumable":true, "itemId":28}}, "Map_Tile_8_10":{"terrain":"beach"}, "Map_Tile_13_3":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":13, "facing":0, "y":3}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":13, "facing":0, "y":3}, "unitClassId":"cursed_structure", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"cursed_structure", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":18, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_3_1":{"terrain":"sea"}, "Map_Tile_8_8":{"terrain":"sea"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"forest"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"beach"}, "Map_Tile_6_4":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":6, "facing":0, "y":4}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":6, "facing":0, "y":4}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":13, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_7_5":{"terrain":"sea"}, "Map_Tile_4_9":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":0, "startPos":{"x":4, "facing":0, "y":9}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":4, "facing":0, "y":9}, "unitClassId":"commander_mercia", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":false, "inWater":false, "weapons":[{"maxRange":1, "horizontalAndVerticalExtraWidth":0, "unitIdWhenAttacking":"", "canCounterAttack":true, "minRange":1, "id":"merciaSword", "terrainExclusion":{}, "canAttackSubmerged":false, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":false, "directionality":"omni", "canMoveAndAttack":true}], "canBeActivated":false, "id":"commander_mercia", "verbCostMultiplier":1.0, "tags":["commander", "type.ground.light"], "loadCapacity":0, "transportTags":{}, "moveRange":4, "passiveMultiplier":1.0, "resourceCost":3, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":false, "movementType":"walking", "inAir":false, "maxHealth":100, "weaponIds":["merciaSword"], "isCommander":true, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":250, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":7, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"heal_aura", "transportedBy":-1}}, "Map_Tile_11_10":{"terrain":"sea"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"forest"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":11, "facing":3, "y":3}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":11, "facing":3, "y":3}, "unitClassId":"commander_valder", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":false, "inWater":false, "weapons":[{"maxRange":1, "horizontalAndVerticalExtraWidth":0, "unitIdWhenAttacking":"", "canCounterAttack":true, "minRange":1, "id":"valderSpell", "terrainExclusion":{}, "canAttackSubmerged":false, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":false, "directionality":"omni", "canMoveAndAttack":true}], "canBeActivated":false, "id":"commander_valder", "verbCostMultiplier":1.0, "tags":["commander", "type.ground.light"], "loadCapacity":0, "transportTags":{}, "moveRange":4, "passiveMultiplier":1.0, "resourceCost":3, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":false, "movementType":"walking", "inAir":false, "maxHealth":100, "weaponIds":["valderSpell"], "isCommander":true, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":250, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":1, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"raise_dead", "transportedBy":-1}}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"sea"}, "Map_Tile_10_9":{"terrain":"bridge"}, "Map_Tile_10_8":{"terrain":"sea"}, "Map_Tile_10_7":{"terrain":"forest"}, "Map_Tile_10_6":{"terrain":"forest"}, "Map_Tile_13_13":{"terrain":"road"}, "Map_Tile_4_0":{"terrain":"sea"}, "Map_Tile_10_2":{"terrain":"road", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":10, "facing":3, "y":2}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":10, "facing":3, "y":2}, "unitClassId":"mage", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":false, "inWater":false, "weapons":[{"maxRange":1, "horizontalAndVerticalExtraWidth":0, "unitIdWhenAttacking":"", "canCounterAttack":true, "minRange":1, "id":"lightning", "terrainExclusion":{}, "canAttackSubmerged":false, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":true, "directionality":"omni", "canMoveAndAttack":true}], "canBeActivated":false, "id":"mage", "verbCostMultiplier":0.5, "tags":["mage", "type.ground.light", "spellcaster"], "loadCapacity":0, "transportTags":{}, "moveRange":5, "passiveMultiplier":1.5, "resourceCost":2, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"walking", "inAir":false, "maxHealth":100, "weaponIds":["lightning"], "isCommander":false, "cost":400, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":6, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_10_0":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":10, "facing":0, "y":0}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":10, "facing":0, "y":0}, "unitClassId":"cursed_structure", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"cursed_structure", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":15, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_1_2":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "recruitDiscounts":{}, "playerId":0, "startPos":{"x":1, "facing":0, "y":9}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":1, "facing":0, "y":9}, "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"barracks", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":2, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"sea"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"forest"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"forest"}, "Map_Tile_9_2":{"terrain":"road"}, "Map_Tile_9_0":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":1, "startPos":{"x":12, "facing":0, "y":2}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":12, "facing":0, "y":2}, "unitClassId":"cursed_structure", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"cursed_structure", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":17, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_3_3":{"terrain":"beach"}, "Map_Tile_8_6":{"terrain":"forest"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"bridge"}, "Map_Tile_12_11":{"terrain":"sea"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"sea"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"road", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":0, "startPos":{"x":3, "facing":0, "y":8}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":3, "facing":0, "y":8}, "unitClassId":"hq", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"hq", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":false, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":3000, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":false}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":23, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_8_0":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":8, "facing":0, "y":0}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":8, "facing":0, "y":0}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":22, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_7_13":{"terrain":"mountain"}, "Map_Tile_0_7":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":0, "facing":0, "y":7}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":0, "facing":0, "y":7}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":21, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"sea"}, "Triggers":[{"id":"Export (Always on Top)", "isIntro":false, "actions":[{"id":"ap_export", "parameters":["1", "Dark Mirror", "Magnemania", "", "", "", "Defeat the enemy Commander."], "enabled":true}], "recurring":"start_of_match", "conditions":{}, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"Set AI", "isIntro":false, "actions":[{"id":"modify_gold", "parameters":["current", "0", "3000"], "enabled":true}, {"id":"set_map_flag", "parameters":["0", "1"], "enabled":true}, {"id":"modify_gold", "parameters":["P2", "0", "1000"], "enabled":true}, {"id":"ai_set_profile", "parameters":["P2", "aggressive"], "enabled":true}, {"id":"modify_health", "parameters":["gate", "-1", "any", "0", "30"], "enabled":true}], "recurring":"start_of_match", "conditions":{}, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"Shuffle Units", "isIntro":false, "actions":[{"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true}], "recurring":"start_of_match", "conditions":{}, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"Unit Copy", "isIntro":false, "actions":[{"id":"play_sound_effect", "parameters":["bellToll", "-4"], "enabled":true}, {"id":"spawn_unit", "parameters":["last_recruit", "6", "P2", "0", "0", "1", "1", "undefined", "centre"], "enabled":true}], "recurring":"repeat", "conditions":[{"id":"check_map_flag", "parameters":["0", "1"], "enabled":true}, {"id":"player_turn", "parameters":["current"], "enabled":true}, {"id":"end_of_unit_turn", "parameters":{}, "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"Turn Start", "isIntro":false, "actions":[{"id":"set_unit_spent", "parameters":["*commander", "-1", "current", "1"], "enabled":true}, {"id":"dialogue_box_simple", "parameters":["sad", "darkmercia", "CHOOSE YOUR CHAMPIONS.", "0", ""], "enabled":true}], "recurring":"once", "conditions":[{"id":"player_turn", "parameters":["current"], "enabled":true}, {"id":"start_of_turn", "parameters":{}, "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"Remove Production Structures", "isIntro":false, "actions":[{"id":"play_sound_effect", "parameters":["gate_die", "4"], "enabled":true}, {"id":"remove_units", "parameters":["*structure", "4", "any", "1", "0"], "enabled":true}, {"id":"set_map_flag", "parameters":["0", "0"], "enabled":true}, {"id":"wait", "parameters":["1000"], "enabled":true}], "recurring":"once", "conditions":[{"id":"player_turn", "parameters":["current"], "enabled":true}, {"id":"end_of_turn", "parameters":{}, "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"AI Aggression", "isIntro":false, "actions":[{"id":"ai_set_restriction", "parameters":["*unit", "-1", "current", "reckless", "1"], "enabled":true}], "recurring":"once", "conditions":[{"id":"current_turn_number", "parameters":["0", "5"], "enabled":true}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"$trigger_default_defeat_no_units", "isIntro":false, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"$trigger_default_defeat_commander", "isIntro":false, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"$trigger_default_defeat_hq", "isIntro":false, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"$trigger_default_victory", "isIntro":false, "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true}, {"id":"P1 Wins (AP Victory)", "isIntro":false, "actions":[{"id":"ap_victory", "parameters":{}, "enabled":true}], "recurring":"once", "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true}], "Map_Tile_1_13":{"terrain":"sea"}, "Map_Tile_7_8":{"terrain":"mountain"}, "Map_Tile_7_6":{"terrain":"forest"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"sea"}, "Map_Tile_4_2":{"terrain":"sea"}, "Map_Tile_3_0":{"terrain":"sea"}, "Map_Tile_7_3":{"terrain":"forest"}, "Map_Tile_2_8":{"terrain":"road"}, "Map_Tile_6_1":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":{}, "recruitDiscounts":{}, "playerId":-1, "startPos":{"x":6, "facing":0, "y":1}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":6, "facing":0, "y":1}, "unitClassId":"city", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"city", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":14, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Player_Count":2, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains", "unit":{"inTransport":false, "state":{}, "blessings":{}, "attackerId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "garrisonClassId":"garrison", "merchantDiscounts":{}, "canBeAttacked":true, "miniGrooveId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "recruitDiscounts":{}, "playerId":0, "startPos":{"x":0, "facing":0, "y":11}, "hasBeenKilled":false, "attackerUnitClass":"", "attachedFlagId":-1, "setHealth":null, "items":{}, "pos":{"x":0, "facing":0, "y":11}, "unitClassId":"tower", "recruitDiscountMultiplier":0.0, "unitClass":{"aliasId":"", "isStructure":true, "inWater":false, "weapons":{}, "canBeActivated":false, "id":"tower", "verbCostMultiplier":1.0, "tags":["structure"], "loadCapacity":0, "transportTags":{}, "moveRange":0, "passiveMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "isDamagingParentUnit":false, "critConditionId":"", "canBeCaptured":true, "isAttackable":true, "isRecruitable":true, "movementType":"land_building", "inAir":false, "maxHealth":100, "weaponIds":{}, "isCommander":false, "cost":500, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "canReinforce":true}, "attackerPlayerId":-1, "canBeAttackedFromDistance":true, "factionOverride":"", "canChargeGroove":true, "setGroove":null, "damageTakenPercent":100, "tentacled":false, "loadedUnits":{}, "grooveCharge":0, "itemDropNumber":0, "hadTurn":false, "stunned":false, "health":100, "underwater":false, "id":11, "merchantDiscountMultiplier":0.0, "itemId":"", "grooveId":"", "transportedBy":-1}}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Name":"Dark Mirror", "Map_Tile_5_7":{"terrain":"forest"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"bridge"}, "Map_Tile_0_1":{"terrain":"sea"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Dementia_Castle.json b/worlds/wargroove2/levels/Dementia_Castle.json new file mode 100644 index 000000000000..fe8d20a08234 --- /dev/null +++ b/worlds/wargroove2/levels/Dementia_Castle.json @@ -0,0 +1 @@ +{"Map_Tile_12_10":{"terrain":"road"}, "Map_Tile_6_12":{"terrain":"river"}, "Map_Tile_7_10":{"terrain":"wall"}, "Map_Tile_17_14":{"terrain":"river"}, "Map_Tile_7_2":{"terrain":"road"}, "Map_Tile_1_1":{"terrain":"ocean"}, "Map_Tile_5_0":{"terrain":"road"}, "Map_Tile_17_11":{"terrain":"river"}, "Map_Tile_14_7":{"terrain":"wall"}, "Map_Tile_1_4":{"terrain":"ocean"}, "Map_Tile_6_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":51, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":6, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":6, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"road"}, "Counters":{}, "Map_Tile_6_13":{"terrain":"bridge"}, "Map_Tile_17_15":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":40, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":17, "facing":0, "y":15}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":17, "facing":0, "y":15}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"river_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"river_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"river"}, "Map_Tile_6_15":{"terrain":"beach"}, "Map_Tile_1_15":{"terrain":"ocean"}, "Map_Tile_1_9":{"terrain":"sea"}, "Map_Tile_3_0":{"terrain":"sea"}, "Map_Tile_3_8":{"terrain":"beach"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_2_1":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":38, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":2, "facing":0, "y":1}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":2, "facing":0, "y":1}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_3_2":{"terrain":"sea"}, "Map_Tile_7_17":{"terrain":"river"}, "Map_Tile_8_5":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":5, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":8, "facing":0, "y":5}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":8, "facing":0, "y":5}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"portal", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":false, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"portal", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_8_2":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":12, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":8, "facing":0, "y":2}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":8, "facing":0, "y":2}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_6_5":{"terrain":"wall"}, "Map_Tile_19_17":{"terrain":"wall"}, "Map_Tile_10_1":{"terrain":"road"}, "Map_Tile_8_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":9, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":8, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":8, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"barracks", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_10_10":{"terrain":"road"}, "Map_Tile_5_8":{"terrain":"sea"}, "Map_Tile_4_8":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":13, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":4, "facing":0, "y":8}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":4, "facing":0, "y":8}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"portal", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":false, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"portal", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_15_4":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":22, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":15, "facing":0, "y":4}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":15, "facing":0, "y":4}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_8_16":{"terrain":"forest"}, "Map_Tile_7_15":{"terrain":"river"}, "Map_Tile_4_9":{"terrain":"road"}, "Author":"Magnemania", "Map_Tile_4_17":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":28, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":4, "facing":0, "y":17}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":4, "facing":0, "y":17}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_5_4":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":30, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":5, "facing":0, "y":4}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":5, "facing":0, "y":4}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"sea"}, "Map_Tile_0_7":{"terrain":"ocean"}, "Map_Tile_3_14":{"terrain":"ocean"}, "Map_Tile_1_13":{"terrain":"ocean"}, "Map_Tile_10_4":{"terrain":"road"}, "Map_Tile_5_3":{"terrain":"wall"}, "Map_Tile_8_7":{"terrain":"wall"}, "Map_Tile_3_11":{"terrain":"beach"}, "Map_Tile_15_17":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"road"}, "Map_Tile_7_0":{"terrain":"road"}, "Map_Tile_18_4":{"terrain":"abyss"}, "Map_Tile_18_10":{"terrain":"abyss"}, "Map_Tile_4_10":{"item":{"type":"hans_bow", "unitTypeRestriction":{}, "isConsumable":false, "itemId":58, "pos":{"x":4, "y":10}}, "terrain":"plains"}, "Map_Size":{"x":23, "y":18}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_3_6":{"terrain":"beach"}, "Map_Tile_0_14":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":34, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":0, "facing":0, "y":14}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":0, "facing":0, "y":14}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_2_6":{"terrain":"sea"}, "Map_Tile_12_14":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["thief", "rifleman"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":46, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":12, "facing":0, "y":14}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":12, "facing":0, "y":14}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"hideout", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"hideout", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_1_8":{"terrain":"sea"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_0_12":{"terrain":"ocean"}, "Map_Tile_4_1":{"terrain":"wall"}, "Map_Tile_16_14":{"terrain":"river"}, "Map_Tile_5_1":{"terrain":"road"}, "Map_Tile_16_6":{"terrain":"wall"}, "Map_Tile_12_5":{"terrain":"road"}, "Map_Tile_7_12":{"terrain":"river"}, "Map_Tile_10_2":{"terrain":"road"}, "Map_Tile_13_9":{"terrain":"wall"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"beach"}, "Player_1":{"recruit_knight":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_trebuchet":true, "team":0, "recruit_balloon":true, "recruit_rifleman":true, "recruit_harpoonship":true, "recruit_archer":true, "recruit_dragon":true, "recruit_frog":true, "recruit_mage":true, "recruit_wagon":true, "recruit_dog":true, "recruit_travelboat":true, "recruit_merman":true, "recruit_soldier":true, "recruit_caravel":true, "recruit_warship":true, "recruit_griffin_walking":true, "recruit_spearman":true, "recruit_giant":true, "recruit_kraken":true, "recruit_turtle":true, "gold":100, "recruit_thief":true, "recruit_witch":true}, "Map_Tile_13_8":{"terrain":"wall"}, "Map_Tile_11_2":{"terrain":"road"}, "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"wall"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"wall"}, "Map_Tile_15_1":{"terrain":"road"}, "Map_Name":"Dementia Castle", "Map_Tile_6_4":{"terrain":"wall"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_0_10":{"terrain":"ocean"}, "Map_Tile_3_7":{"terrain":"beach"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"ocean"}, "Map_Tile_19_2":{"terrain":"mountain"}, "Map_Tile_8_9":{"terrain":"wall"}, "Map_Tile_16_16":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"beach"}, "Map_Tile_15_7":{"terrain":"wall"}, "Map_Tile_7_14":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":41, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":7, "facing":0, "y":14}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":7, "facing":0, "y":14}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"river_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"river_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"river"}, "Map_Tile_11_12":{"terrain":"plains"}, "Objectives":["Destroy all of the Growths, then defeat the enemy Commander."], "Map_Tile_1_10":{"terrain":"ocean"}, "Map_Tile_14_2":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":11, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":14, "facing":0, "y":2}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":14, "facing":0, "y":2}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_10_13":{"terrain":"road"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"road"}, "Player_Count":2, "Map_Tile_0_1":{"terrain":"ocean"}, "Map_Tile_3_10":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":20, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":3, "facing":0, "y":10}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":3, "facing":0, "y":10}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_13_5":{"terrain":"road"}, "Map_Tile_12_3":{"terrain":"road"}, "Map_Tile_20_13":{"terrain":"road"}, "Map_Tile_17_0":{"terrain":"road"}, "Map_Tile_0_16":{"terrain":"reef"}, "Map_Tile_5_13":{"terrain":"bridge"}, "Map_Tile_7_7":{"terrain":"wall"}, "Map_Tile_6_6":{"terrain":"wall"}, "Map_Tile_5_12":{"terrain":"bridge"}, "Map_Tile_4_16":{"terrain":"ocean"}, "Map_Tile_16_12":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":29, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":16, "facing":0, "y":12}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":16, "facing":0, "y":12}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"river_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"river_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"river"}, "Map_Tile_9_6":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":23, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":9, "facing":0, "y":6}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":9, "facing":0, "y":6}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_1_16":{"terrain":"ocean"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"road"}, "Map_Tile_13_11":{"terrain":"river"}, "Locations":{"8":{"name":"Organ", "interactable":false, "positions":[{"x":11, "y":4}], "centre":{"x":11, "y":4}, "getArea":null, "id":8, "setArea":null}, "9":{"name":"Organ Barrier", "interactable":false, "positions":[{"x":10, "y":4}, {"x":10, "y":3}, {"x":11, "y":3}, {"x":12, "y":3}, {"x":12, "y":4}, {"x":10, "y":2}, {"x":11, "y":2}, {"x":12, "y":2}], "centre":{"x":11, "y":3}, "getArea":null, "id":9, "setArea":null}, "0":{"name":"Shuffle1", "interactable":false, "positions":[{"x":16, "y":12}, {"x":17, "y":12}], "centre":{"x":17, "y":12}, "getArea":null, "id":0, "setArea":null}, "3":{"name":"Shuffle2", "interactable":false, "positions":[{"x":6, "y":14}, {"x":7, "y":14}], "centre":{"x":7, "y":14}, "getArea":null, "id":3, "setArea":null}, "4":{"name":"Shuffle3", "interactable":false, "positions":[{"x":18, "y":9}, {"x":18, "y":8}], "centre":{"x":18, "y":9}, "getArea":null, "id":4, "setArea":null}, "5":{"name":"Random Growths", "interactable":false, "positions":[{"x":15, "y":12}, {"x":7, "y":11}, {"x":8, "y":15}, {"x":18, "y":12}, {"x":4, "y":7}, {"x":17, "y":5}], "centre":{"x":12, "y":10}, "getArea":null, "id":5, "setArea":null}, "6":{"name":"Fixed Growths", "interactable":false, "positions":[{"x":21, "y":15}, {"x":22, "y":2}, {"x":2, "y":13}], "centre":{"x":15, "y":10}, "getArea":null, "id":6, "setArea":null}, "7":{"name":"Harmon Spawn", "interactable":false, "positions":[{"x":11, "y":5}], "centre":{"x":11, "y":5}, "getArea":null, "id":7, "setArea":null}}, "Map_Tile_22_17":{"terrain":"wall"}, "Map_Tile_15_13":{"terrain":"road"}, "Map_Tile_22_16":{"terrain":"wall"}, "Map_Tile_6_7":{"terrain":"wall"}, "Map_Tile_22_15":{"terrain":"wall"}, "Map_Tile_22_14":{"terrain":"wall"}, "Map_Tile_22_13":{"terrain":"road"}, "Map_Tile_22_12":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":4, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":22, "facing":0, "y":12}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":22, "facing":0, "y":12}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"barracks", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"reef"}, "Map_Tile_5_15":{"terrain":"ocean"}, "Map_Tile_22_10":{"terrain":"abyss"}, "Map_Tile_22_9":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"wall"}, "Map_Tile_9_16":{"terrain":"forest_cut"}, "Map_Tile_2_11":{"terrain":"ocean"}, "Map_Tile_22_7":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":48, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":22, "facing":0, "y":7}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":22, "facing":0, "y":7}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_2_10":{"terrain":"beach"}, "Map_Tile_17_12":{"terrain":"river"}, "Map_Tile_14_5":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":6, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":14, "facing":0, "y":5}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":14, "facing":0, "y":5}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"portal", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":false, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"portal", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_6_10":{"terrain":"beach"}, "Map_Tile_12_17":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":56, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":12, "facing":0, "y":17}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":12, "facing":0, "y":17}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["fortified_city"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"fortified_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":1000, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"fortified_garrison", "unitClassId":"fortified_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_21_4":{"terrain":"abyss"}, "Map_Tile_6_16":{"terrain":"ocean"}, "Map_Tile_18_11":{"terrain":"river"}, "Map_Tile_22_3":{"terrain":"abyss"}, "Map_Tile_20_14":{"terrain":"wall"}, "Map_Tile_22_2":{"terrain":"plains"}, "Map_Tile_22_1":{"terrain":"plains"}, "Map_Tile_22_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":15, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":22, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":22, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_0_3":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":39, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":0, "facing":0, "y":3}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":0, "facing":0, "y":3}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_2_7":{"terrain":"sea"}, "Map_Tile_4_15":{"terrain":"ocean"}, "Map_Tile_21_17":{"terrain":"wall"}, "Map_Tile_8_13":{"terrain":"road"}, "Map_Tile_21_16":{"item":{"type":"large_healing_potion", "unitTypeRestriction":{}, "isConsumable":true, "itemId":57, "pos":{"x":21, "y":16}}, "terrain":"cobblestone"}, "Map_Tile_9_5":{"terrain":"road"}, "Map_Tile_13_17":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"road"}, "Map_Tile_21_15":{"terrain":"carpet"}, "Map_Tile_1_5":{"terrain":"ocean"}, "Map_Tile_21_14":{"terrain":"wall"}, "Map_Tile_14_12":{"terrain":"forest"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"road"}, "Map_Tile_16_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":49, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":16, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":16, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Tile_19_7":{"terrain":"road"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_21_11":{"terrain":"river"}, "Map_Tile_21_10":{"terrain":"abyss"}, "Map_Tile_19_12":{"terrain":"road"}, "Map_Tile_16_1":{"terrain":"road"}, "Map_Tile_21_7":{"terrain":"mountain"}, "Map_Tile_17_16":{"terrain":"bridge"}, "Map_Tile_4_2":{"terrain":"wall"}, "Map_Tile_18_3":{"terrain":"wall"}, "Map_Tile_21_5":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"wall"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_22_5":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":47, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":22, "facing":0, "y":5}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":22, "facing":0, "y":5}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_12_7":{"terrain":"road"}, "Map_Tile_17_10":{"terrain":"plains"}, "Map_Tile_21_3":{"terrain":"abyss"}, "Map_Tile_21_2":{"terrain":"mountain"}, "Map_Tile_21_1":{"terrain":"plains"}, "Map_Tile_5_16":{"terrain":"ocean"}, "Map_Tile_21_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":14, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":21, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":21, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"tower", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"tower", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_20_5":{"terrain":"road"}, "Map_Tile_20_17":{"terrain":"wall"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_19_16":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":2, "grooveCharge":0, "canChargeGroove":true, "playerId":-2, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":19, "facing":0, "y":16}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":19, "facing":0, "y":16}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"gate", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"indoor_building", "passiveMultiplier":1.0, "canBeCaptured":false, "cost":500, "canReinforce":false, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"", "unitClassId":"gate", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"cobblestone"}, "Map_Tile_3_17":{"terrain":"ocean"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_2_17":{"terrain":"ocean"}, "Map_Tile_20_12":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"river"}, "Map_Tile_15_3":{"terrain":"road"}, "Map_Tile_20_10":{"terrain":"abyss"}, "Map_Tile_20_9":{"terrain":"abyss"}, "Map_Tile_20_8":{"terrain":"abyss"}, "Map_Tile_20_7":{"terrain":"mountain"}, "Map_Tile_20_6":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":36, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":20, "facing":0, "y":6}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":20, "facing":0, "y":6}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"tower", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"tower", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_20_4":{"terrain":"abyss_bridge"}, "Map_Tile_10_7":{"terrain":"road"}, "Map_Tile_13_13":{"terrain":"road"}, "Map_Tile_15_0":{"terrain":"road"}, "Map_Tile_20_0":{"terrain":"road"}, "Map_Tile_20_16":{"terrain":"cobblestone"}, "Map_Tile_19_15":{"terrain":"wall"}, "Map_Tile_19_14":{"terrain":"wall"}, "Map_Tile_16_10":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":32, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":16, "facing":0, "y":10}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":16, "facing":0, "y":10}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"portal", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":false, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"portal", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_14_8":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":16, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":14, "facing":3, "y":8}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":14, "facing":3, "y":8}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["ballista", "type.ground.heavy"], "weaponIds":["ballistaBolt"], "inAir":false, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":6, "canCounterAttack":true, "unitIdWhenAttacking":"", "canMoveAndAttack":false, "terrainExclusion":{}, "canAttackSubmerged":false, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":true, "id":"ballistaBolt", "minRange":2}], "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"ballista", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"wheels", "passiveMultiplier":1.5, "canBeCaptured":false, "cost":800, "canReinforce":false, "isStructure":false, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":6}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"", "unitClassId":"ballista", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_12_6":{"terrain":"road"}, "Map_Tile_9_17":{"terrain":"forest_cut"}, "Map_Tile_2_5":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":33, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":2, "facing":0, "y":5}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":2, "facing":0, "y":5}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"port", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"port", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_10_11":{"terrain":"bridge"}, "Map_Tile_21_9":{"item":{"type":"oddvars_super_staff", "unitTypeRestriction":{}, "isConsumable":false, "itemId":59, "pos":{"x":21, "y":9}}, "terrain":"abyss"}, "Map_Tile_19_11":{"terrain":"bridge"}, "Map_Tile_4_3":{"terrain":"wall"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_22_11":{"terrain":"river"}, "Map_Tile_19_9":{"terrain":"road"}, "Map_Tile_11_11":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":53, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":11, "facing":0, "y":11}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":11, "facing":0, "y":11}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"river_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"river_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"river"}, "Map_Tile_8_3":{"terrain":"road"}, "Map_Tile_6_14":{"terrain":"river"}, "Map_Tile_2_16":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":3, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":2, "facing":0, "y":16}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":2, "facing":0, "y":16}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"port", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"port", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"road"}, "Map_Tile_19_6":{"terrain":"road"}, "Map_Tile_19_5":{"terrain":"road"}, "Map_Tile_13_6":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":24, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":13, "facing":0, "y":6}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":13, "facing":0, "y":6}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_19_4":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"road"}, "Map_Tile_11_14":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":43, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":11, "facing":0, "y":14}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":11, "facing":0, "y":14}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"hq", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":false, "cost":3000, "canReinforce":false, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"hq", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_19_3":{"terrain":"abyss"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"wall"}, "Map_Tile_17_4":{"terrain":"abyss"}, "Map_Tile_16_15":{"terrain":"river"}, "Map_Tile_2_2":{"terrain":"ocean"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"river"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_18_13":{"terrain":"road"}, "Map_Tile_11_13":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":44, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"heal_aura", "inTransport":false, "startPos":{"x":11, "facing":3, "y":13}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":11, "facing":3, "y":13}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["commander", "type.ground.light"], "weaponIds":["merciaSword"], "inAir":false, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "canCounterAttack":true, "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canAttackSubmerged":false, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":false, "id":"merciaSword", "minRange":1}], "isDamagingParentUnit":false, "resourceCost":3, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":250, "id":"commander_mercia", "recruitingCostMultiplier":1.0, "isRecruitable":false, "loadCapacity":0, "isCommander":true, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"walking", "passiveMultiplier":1.0, "canBeCaptured":false, "cost":500, "canReinforce":false, "isStructure":false, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":4}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"", "unitClassId":"commander_mercia", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"road"}, "Map_Tile_19_10":{"terrain":"abyss_bridge"}, "Map_Tile_22_4":{"terrain":"abyss"}, "Map_Tile_11_1":{"terrain":"road"}, "Map_Tile_14_17":{"terrain":"plains"}, "Map_Tile_1_17":{"terrain":"ocean"}, "Map_Tile_1_3":{"terrain":"ocean"}, "Map_Tile_18_9":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":18, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":18, "facing":0, "y":9}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":18, "facing":0, "y":9}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_2_3":{"terrain":"ocean"}, "Map_Tile_18_8":{"terrain":"plains"}, "Map_Tile_7_16":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":42, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":7, "facing":0, "y":16}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":7, "facing":0, "y":16}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"river_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"river_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"river_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"river"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"road"}, "Map_Tile_0_15":{"terrain":"ocean"}, "Map_Tile_18_5":{"terrain":"forest"}, "Map_Tile_21_6":{"terrain":"plains"}, "Map_Tile_18_2":{"terrain":"wall"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"ocean"}, "Map_Tile_18_1":{"terrain":"wall"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_18_0":{"terrain":"wall"}, "Map_Tile_17_17":{"terrain":"river"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_17_9":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"ocean"}, "Map_Tile_17_6":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"ocean"}, "Map_Tile_14_3":{"terrain":"road"}, "Map_Tile_17_2":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":26, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":17, "facing":0, "y":2}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":17, "facing":0, "y":2}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"road"}, "Map_Tile_16_17":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":55, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":16, "facing":0, "y":17}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":16, "facing":0, "y":17}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_19_0":{"terrain":"mountain"}, "Map_Tile_7_6":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":8, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":7, "facing":0, "y":6}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":7, "facing":0, "y":6}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_5_5":{"terrain":"sea"}, "Map_Tile_10_6":{"terrain":"road"}, "Map_Tile_16_13":{"terrain":"bridge"}, "Map_Tile_7_1":{"terrain":"road"}, "Map_Tile_16_11":{"terrain":"river"}, "Map_Tile_11_5":{"terrain":"road"}, "Map_Tile_10_12":{"terrain":"road"}, "Map_Tile_8_8":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":17, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":8, "facing":0, "y":8}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":8, "facing":0, "y":8}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["ballista", "type.ground.heavy"], "weaponIds":["ballistaBolt"], "inAir":false, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":6, "canCounterAttack":true, "unitIdWhenAttacking":"", "canMoveAndAttack":false, "terrainExclusion":{}, "canAttackSubmerged":false, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canAttackAir":true, "id":"ballistaBolt", "minRange":2}], "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"ballista", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"wheels", "passiveMultiplier":1.5, "canBeCaptured":false, "cost":800, "canReinforce":false, "isStructure":false, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":6}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"", "unitClassId":"ballista", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_9_1":{"terrain":"road"}, "Map_Tile_10_8":{"terrain":"road"}, "Map_Tile_14_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":10, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":14, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":14, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"barracks", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_0_8":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":35, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":0, "facing":0, "y":8}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":0, "facing":0, "y":8}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"wall"}, "Map_Tile_9_13":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"plains"}, "Map_Tile_19_13":{"terrain":"road"}, "Map_Tile_16_3":{"terrain":"wall"}, "Map_Tile_0_17":{"terrain":"sea"}, "Map_Tile_12_12":{"terrain":"road"}, "Map_Tile_3_13":{"terrain":"ocean"}, "Map_Tile_21_8":{"terrain":"abyss"}, "Map_Tile_4_14":{"terrain":"ocean"}, "Map_Tile_5_14":{"terrain":"ocean"}, "Map_Tile_4_4":{"terrain":"sea"}, "Map_Tile_15_14":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":19, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":15, "facing":0, "y":14}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":15, "facing":0, "y":14}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_14_1":{"terrain":"road"}, "Map_Tile_2_0":{"terrain":"ocean"}, "Map_Tile_15_11":{"terrain":"river"}, "Map_Tile_15_9":{"terrain":"wall"}, "Map_Tile_6_1":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"road"}, "Map_Tile_12_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":50, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":12, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":12, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_22_6":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"bridge"}, "Map_Tile_20_1":{"terrain":"road"}, "Map_Tile_13_3":{"terrain":"road"}, "Map_Tile_3_1":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"bridge"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_21_13":{"terrain":"road"}, "Map_Tile_20_2":{"terrain":"road"}, "Map_Tile_16_4":{"terrain":"wall"}, "Map_Tile_13_16":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":1, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":13, "facing":0, "y":16}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":13, "facing":0, "y":16}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"barracks", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_17_3":{"terrain":"wall"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"beach"}, "Triggers":[{"isIntro":false, "conditions":{}, "enabled":true, "recurring":"start_of_match", "actions":[{"enabled":true, "id":"ap_export", "parameters":["1", "Dementia Castle", "Magnemania", "", "", "", "Destroy all of the Growths, then defeat the enemy Commander."]}], "id":"Export (Always on Top)", "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":{}, "enabled":true, "recurring":"start_of_match", "actions":[{"enabled":true, "id":"ai_set_profile", "parameters":["current", "balanced"]}, {"enabled":true, "id":"ai_set_restriction", "parameters":["gate", "-1", "any", "dont_target_this", "1"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["organ_up", "8", "current", "1", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"ai_set_restriction", "parameters":["organ_up", "-1", "current", "scripted", "1"]}, {"enabled":true, "id":"set_damage_taken", "parameters":["organ_up", "-1", "current", "0"]}, {"enabled":true, "id":"location_set_properties", "parameters":["7", "1", "0", "0"]}, {"enabled":true, "id":"location_set_properties", "parameters":["9", "1", "1", "0"]}, {"enabled":true, "id":"modify_health", "parameters":["gate", "-1", "any", "0", "50"]}], "id":"Set AI", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":{}, "enabled":true, "recurring":"start_of_match", "actions":{}, "id":"Shuffle Units", "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":{}, "enabled":true, "recurring":"once", "actions":[{"enabled":true, "id":"ap_spawn_unit", "parameters":["growth", "6", "current", "0", "0", "3", "0", "undefined", "centre"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["growth", "5", "current", "0", "0", "2", "1", "undefined", "centre"]}, {"enabled":true, "id":"set_damage_taken", "parameters":["growth", "-1", "current", "200"]}], "id":"Growths", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"check_map_flag", "parameters":["0", "0"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "commander_ragna_boss", "-1"]}, {"enabled":true, "id":"start_of_turn", "parameters":{}}, {"enabled":true, "id":"player_turn", "parameters":["current"]}], "enabled":true, "recurring":"repeat", "actions":[{"enabled":true, "id":"set_map_flag", "parameters":["1", "1"]}], "id":"Set Respawn Flag At Turn Start", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"unit_groove_tiered", "parameters":["current", "0", "1", "commander_ragna_boss", "-1", "2"]}], "enabled":true, "recurring":"repeat", "actions":[{"enabled":true, "id":"ai_set_restriction", "parameters":["commander_ragna_boss", "-1", "current", "cant_groove", "0"]}], "id":"Allow Harmon Groove", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["growth", "current", "-1"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "4", "1", "growth", "-1"]}], "enabled":true, "recurring":"repeat", "actions":[{"enabled":true, "id":"centre_camera", "parameters":["8", "0"]}, {"enabled":true, "id":"play_sound_effect", "parameters":["glass_shatter", "8"]}, {"enabled":true, "id":"modify_health", "parameters":["*unit_structure", "8", "current", "2", "20"]}], "id":"Damage Organ on Growth Destruction", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "growth", "-1"]}], "enabled":true, "recurring":"once", "actions":[{"enabled":true, "id":"centre_camera", "parameters":["8", "0"]}, {"enabled":true, "id":"set_map_flag", "parameters":["1", "1"]}, {"enabled":true, "id":"set_map_flag", "parameters":["0", "1"]}, {"enabled":true, "id":"screenshake", "parameters":["50", "5", "5", "5"]}, {"enabled":true, "id":"play_sound_effect", "parameters":["ragneHarmonShout1", "8"]}, {"enabled":true, "id":"fade_stage", "parameters":["out", "100", "1", "1"]}, {"enabled":true, "id":"play_sound_effect", "parameters":["explosion_big", "8"]}, {"enabled":true, "id":"activate_flood", "parameters":["8", "abyss", "default", "", "0", "1", "1"]}, {"enabled":true, "id":"activate_flood", "parameters":["9", "abyss", "default", "", "0", "1", "1"]}, {"enabled":true, "id":"fade_stage", "parameters":["in", "100", "1", "1"]}], "id":"Destroy Organ on Growths Lost", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "enabled":true, "recurring":"oncePerPlayer", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "id":"$trigger_default_defeat_no_units", "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}], "enabled":true, "recurring":"oncePerPlayer", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "id":"$trigger_default_defeat_commander", "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"check_map_flag", "parameters":["0", "1"]}, {"enabled":true, "id":"unit_lost", "parameters":["commander_ragna_boss", "current", "-1"]}], "enabled":true, "recurring":"oncePerPlayer", "actions":[{"enabled":true, "id":"set_map_flag", "parameters":["1", "0"]}, {"enabled":true, "id":"eliminate", "parameters":["current"]}], "id":"Defeat (Lost Commander With No Revival)", "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}], "enabled":true, "recurring":"oncePerPlayer", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "id":"$trigger_default_defeat_hq", "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}], "enabled":true, "recurring":"oncePerPlayer", "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}], "id":"$trigger_default_victory", "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["current"]}], "enabled":true, "recurring":"once", "actions":[{"enabled":true, "id":"ap_victory", "parameters":{}}], "id":"P1 Wins (AP Victory)", "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"isIntro":false, "conditions":[{"enabled":true, "id":"check_map_flag", "parameters":["1", "1"]}, {"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "commander_ragna_boss", "-1"]}], "enabled":true, "recurring":"repeat", "actions":[{"enabled":true, "id":"set_map_flag", "parameters":["1", "0"]}, {"enabled":true, "id":"ap_spawn_unit", "parameters":["commander_ragna_boss", "7", "current", "0", "0", "1", "0", "undefined", "centre"]}, {"enabled":true, "id":"modify_groove", "parameters":["commander_ragna_boss", "-1", "current", "0", "150"]}, {"enabled":true, "id":"ai_set_restriction", "parameters":["commander_ragna_boss", "-1", "current", "cant_groove", "1"]}, {"enabled":true, "id":"ai_set_restriction", "parameters":["commander_ragna_boss", "-1", "current", "reckless", "1"]}, {"enabled":true, "id":"set_unit_spent", "parameters":["commander_ragna_boss", "7", "current", "1"]}], "id":"Respawn Harmon", "players":[0, 1, 0, 0, 0, 0, 0, 0]}], "Map_Tile_14_11":{"terrain":"river"}, "Map_Tile_6_2":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["thief", "rifleman"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":37, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":6, "facing":0, "y":2}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":6, "facing":0, "y":2}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"hideout", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"hideout", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Player_2":{"recruit_knight":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_trebuchet":true, "team":1, "recruit_balloon":true, "recruit_rifleman":true, "recruit_harpoonship":true, "recruit_archer":true, "recruit_dragon":true, "recruit_frog":true, "recruit_mage":true, "recruit_wagon":true, "recruit_dog":true, "recruit_travelboat":true, "recruit_merman":true, "recruit_soldier":true, "recruit_caravel":true, "recruit_warship":true, "recruit_griffin_walking":true, "recruit_spearman":true, "recruit_giant":true, "recruit_kraken":true, "recruit_turtle":true, "gold":100, "recruit_thief":true, "recruit_witch":true}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"beach"}, "Map_Tile_7_4":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":21, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":7, "facing":0, "y":4}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":7, "facing":0, "y":4}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_6_17":{"terrain":"ocean"}, "Map_Tile_8_17":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":54, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":8, "facing":0, "y":17}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":8, "facing":0, "y":17}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_0_4":{"terrain":"ocean"}, "Map_Tile_7_9":{"terrain":"wall"}, "Map_Tile_9_11":{"terrain":"river"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"ocean"}, "Map_Tile_13_7":{"terrain":"wall"}, "Map_Tile_15_8":{"terrain":"wall"}, "Map_Tile_9_8":{"terrain":"wall"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_14_16":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"sea"}, "Map_Tile_13_2":{"terrain":"road"}, "Flags":{"1":0, "0":0}, "Map_Tile_20_15":{"terrain":"cobblestone"}, "Map_Tile_10_0":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":52, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":10, "facing":0, "y":0}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":10, "facing":0, "y":0}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_15_6":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":7, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":15, "facing":0, "y":6}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":15, "facing":0, "y":6}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_11_17":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"road"}, "Map_Tile_9_7":{"terrain":"wall"}, "Map_Tile_3_5":{"terrain":"sea"}, "Map_Tile_10_5":{"terrain":"road"}, "Map_Tile_0_13":{"terrain":"ocean"}, "Map_Tile_9_2":{"terrain":"road"}, "Map_Tile_4_5":{"terrain":"reef"}, "Map_Tile_12_11":{"terrain":"bridge"}, "Map_Tile_4_13":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":31, "grooveCharge":0, "canChargeGroove":true, "playerId":-1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":4, "facing":0, "y":13}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":4, "facing":0, "y":13}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"water_city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"sea_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"water_city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"ocean"}, "Map_Tile_3_15":{"terrain":"ocean"}, "Map_Tile_18_7":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"ocean"}, "Map_Tile_17_8":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"ocean"}, "Map_Tile_0_5":{"terrain":"ocean"}, "Map_Tile_2_15":{"terrain":"ocean"}, "Map_Tile_21_12":{"terrain":"plains"}, "Map_Tile_5_2":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":{}, "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":25, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":5, "facing":0, "y":2}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":5, "facing":0, "y":2}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"city", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"city", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_12_1":{"terrain":"road"}, "Map_Tile_15_15":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"sea"}, "Map_Tile_10_9":{"terrain":"road"}, "Map_Tile_1_0":{"terrain":"ocean"}, "Map_Tile_6_3":{"terrain":"wall"}, "Map_Tile_3_3":{"terrain":"sea"}, "Map_Tile_19_8":{"terrain":"road"}, "Map_Tile_6_8":{"terrain":"sea"}, "Map_Tile_17_1":{"terrain":"road"}, "Map_Tile_10_17":{"terrain":"forest"}, "Map_Tile_3_4":{"terrain":"sea"}, "Map_Tile_0_0":{"terrain":"ocean"}, "Map_Tile_1_12":{"terrain":"ocean"}, "Map_Tile_0_6":{"terrain":"ocean"}, "Map_Tile_7_13":{"terrain":"road"}, "Map_Tile_5_9":{"terrain":"road"}, "Map_Tile_1_2":{"terrain":"ocean"}, "Map_Tile_16_2":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":27, "grooveCharge":0, "canChargeGroove":true, "playerId":1, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":16, "facing":0, "y":2}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":16, "facing":0, "y":2}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"tower", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"tower", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_5_11":{"terrain":"bridge"}, "Map_Tile_20_3":{"terrain":"abyss_bridge"}, "Map_Tile_10_3":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"beach"}, "Map_Tile_19_1":{"terrain":"mountain"}, "Map_Tile_9_15":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"wall"}, "Map_Tile_2_13":{"terrain":"carpet"}, "Map_Tile_2_14":{"terrain":"ocean"}, "Map_Tile_18_17":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"beach"}, "Map_Tile_3_16":{"terrain":"ocean"}, "Map_Tile_1_14":{"terrain":"ocean"}, "Map_Tile_20_11":{"terrain":"river"}, "Map_Tile_16_8":{"terrain":"forest"}, "Map_Tile_10_16":{"unit":{"hadTurn":false, "factionOverride":"", "rangedDamageTakenPercent":100, "damageTakenPercent":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "blessings":{}, "canBeAttacked":true, "loadedUnits":{}, "recruitDiscounts":{}, "attachedFlagId":-1, "id":45, "grooveCharge":0, "canChargeGroove":true, "playerId":0, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "items":{}, "underwater":false, "attackerUnitClass":"", "attackerId":-1, "state":{}, "grooveId":"", "inTransport":false, "startPos":{"x":10, "facing":0, "y":16}, "setHealth":null, "merchantDiscounts":{}, "itemId":"", "pos":{"x":10, "facing":0, "y":16}, "hasBeenKilled":false, "setGroove":null, "unitClass":{"tags":["structure"], "weaponIds":{}, "inAir":false, "weapons":{}, "isDamagingParentUnit":false, "resourceCost":1, "aliasId":"", "inWater":false, "critConditionId":"", "canBeActivated":false, "maxGroove":0, "id":"barracks", "recruitingCostMultiplier":1.0, "isRecruitable":true, "loadCapacity":0, "isCommander":false, "isAttackable":true, "verbCostMultiplier":1.0, "canAttack":true, "maxHealth":100, "movementType":"land_building", "passiveMultiplier":1.0, "canBeCaptured":true, "cost":500, "canReinforce":true, "isStructure":true, "transportTags":{}, "reinforceMultiplier":1.0, "moveRange":0}, "canBeAttackedFromDistance":true, "miniGrooveId":"", "killedByLosing":false, "tentacled":false, "stunned":false, "itemDropNumber":0, "attackerPlayerId":-1, "garrisonClassId":"garrison", "unitClassId":"barracks", "recruitDiscountMultiplier":0.0, "health":100}, "terrain":"plains"}, "Map_Tile_5_17":{"terrain":"ocean"}, "Map_Tile_1_7":{"terrain":"sea"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Den-Two-Away.json b/worlds/wargroove2/levels/Den-Two-Away.json new file mode 100644 index 000000000000..f2f232d3b474 --- /dev/null +++ b/worlds/wargroove2/levels/Den-Two-Away.json @@ -0,0 +1 @@ +{"Map_Tile_14_10":{"terrain":"sea"}, "Map_Tile_10_2":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":2, "x":10, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":-1, "unitClassId":"fortified_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"fortified_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["fortified_city"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":1000, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"fortified_garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":13, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":2, "x":10, "facing":0}}, "terrain":"plains"}, "Map_Tile_5_2":{"terrain":"sea"}, "Player_1":{"recruit_dog":true, "recruit_merman":true, "recruit_knight":true, "recruit_harpy":true, "recruit_dragon":true, "recruit_wagon":true, "recruit_rifleman":true, "recruit_thief":true, "recruit_caravel":true, "recruit_turtle":true, "recruit_giant":true, "recruit_trebuchet":true, "recruit_mage":true, "recruit_spearman":true, "recruit_witch":true, "recruit_harpoonship":true, "recruit_soldier":true, "recruit_frog":true, "recruit_kraken":true, "recruit_ballista":true, "recruit_travelboat":true, "recruit_archer":true, "gold":600, "team":0, "recruit_warship":true, "recruit_balloon":true, "recruit_griffin_walking":true}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Size":{"y":12, "x":16}, "Map_Tile_6_6":{"terrain":"abyss"}, "Map_Tile_10_7":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":7, "x":10, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":-1, "unitClassId":"fortified_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"fortified_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["fortified_city"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":1000, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"fortified_garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":6, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":7, "x":10, "facing":0}}, "terrain":"plains"}, "Map_Tile_12_8":{"terrain":"forest"}, "Map_Tile_14_8":{"terrain":"beach"}, "Map_Tile_0_7":{"terrain":"sea"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"beach"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"forest"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"sea"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"mountain"}, "Map_Tile_2_5":{"terrain":"beach"}, "Map_Tile_11_7":{"terrain":"forest"}, "Map_Tile_0_1":{"terrain":"forest"}, "Map_Tile_6_1":{"terrain":"beach"}, "Map_Tile_13_11":{"terrain":"ocean"}, "Map_Tile_6_2":{"terrain":"sea"}, "Map_Tile_10_9":{"terrain":"beach"}, "Map_Tile_14_3":{"terrain":"forest"}, "Map_Tile_0_11":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":11, "x":0, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"kraken", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":false, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":[{"canAttackSubmerged":false, "canAttackAir":false, "terrainExclusion":{}, "minRange":1, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "maxRange":3, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "id":"tentacleSmack", "canCounterAttack":true, "canMoveAndAttack":true}], "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"kraken", "canBeCaptured":false, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"river_sailing", "moveRange":5, "loadCapacity":0, "canReinforce":false, "isCommander":false, "tags":["kraken", "type.sea.heavy"], "canBeActivated":false, "resourceCost":2, "inWater":true, "passiveMultiplier":1.3500000238419, "cost":850, "maxHealth":100, "aliasId":"", "weaponIds":["tentacleSmack"]}, "health":100, "garrisonClassId":"", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":17, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":11, "x":0, "facing":0}}, "terrain":"reef"}, "Map_Tile_1_2":{"terrain":"forest"}, "Map_Tile_10_1":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"forest"}, "Map_Tile_4_3":{"terrain":"sea"}, "Map_Tile_7_10":{"terrain":"sea"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"forest"}, "Map_Tile_9_8":{"terrain":"beach"}, "Map_Tile_7_4":{"terrain":"forest"}, "Map_Tile_1_0":{"terrain":"forest"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"forest"}, "Map_Tile_7_9":{"terrain":"beach"}, "Map_Tile_0_3":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"reef"}, "Map_Tile_15_7":{"terrain":"beach"}, "Map_Tile_3_6":{"terrain":"mountain"}, "Map_Tile_9_3":{"terrain":"forest"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"beach"}, "Map_Tile_4_1":{"terrain":"beach"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_13_9":{"terrain":"beach"}, "Map_Tile_9_6":{"terrain":"forest"}, "Triggers":[{"isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["321", "Den-Two-Away", "Fly Sniper", "Capture the lumbermill with your commander (Requires Balloon and Harpy).", "", "", "Win by defeating the port (Requires Harpy)."], "id":"ap_export", "enabled":true}], "id":"Export", "conditions":{}, "recurring":"start_of_match"}, {"isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "id":"$trigger_default_defeat_no_units", "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence", "enabled":true}], "recurring":"oncePerPlayer"}, {"isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "id":"$trigger_default_defeat_commander", "conditions":[{"parameters":["*commander", "current", "-1"], "id":"unit_lost", "enabled":true}], "recurring":"oncePerPlayer"}, {"isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "id":"$trigger_default_defeat_hq", "conditions":[{"parameters":["hq", "current", "-1"], "id":"unit_lost", "enabled":true}], "recurring":"oncePerPlayer"}, {"isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "id":"Defeat (Lost Tower)", "conditions":[{"parameters":["neutral", "0", "1", "tower", "-1"], "id":"unit_presence", "enabled":true}], "recurring":"oncePerPlayer"}, {"isIntro":false, "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["253011"], "id":"ap_location_send", "enabled":true}, {"parameters":["P1"], "id":"victory", "enabled":true}], "id":"Victory (Lost Port: 253011)", "conditions":[{"parameters":["port", "current", "-1"], "id":"unit_lost", "enabled":true}], "recurring":"oncePerPlayer"}, {"isIntro":false, "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["current"], "id":"victory", "enabled":true}], "id":"$trigger_default_victory", "conditions":[{"parameters":["current", "0", "0"], "id":"number_of_opponents", "enabled":true}], "recurring":"oncePerPlayer"}, {"isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["253012"], "id":"ap_location_send", "enabled":true}], "id":"Capture Lumbermill with Commander (253012)", "conditions":[{"parameters":["current", "1", "0", "lumbermill", "1"], "id":"unit_presence", "enabled":true}, {"parameters":["current", "0", "1", "*commander", "1"], "id":"unit_presence", "enabled":true}, {"parameters":["current", "0", "1", "*commander", "-5"], "id":"unit_presence", "enabled":true}], "recurring":"once"}, {"isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "id":"unit_random_teleport", "enabled":true}], "id":"Random TP", "conditions":{}, "recurring":"start_of_match"}], "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_14_5":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":5, "x":14, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":-1, "unitClassId":"fortified_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"fortified_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["fortified_city"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":1000, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"fortified_garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":5, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":5, "x":14, "facing":0}}, "terrain":"plains"}, "Map_Tile_15_2":{"terrain":"forest"}, "Map_Tile_13_10":{"terrain":"sea"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"beach"}, "Map_Tile_0_6":{"terrain":"ocean"}, "Map_Tile_15_9":{"terrain":"sea"}, "Map_Tile_1_11":{"terrain":"reef"}, "Map_Tile_2_9":{"terrain":"beach"}, "Map_Tile_1_9":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"forest"}, "Map_Tile_2_3":{"terrain":"sea"}, "Map_Tile_12_7":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":7, "x":12, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":0, "unitClassId":"tower_ap", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"tower_ap", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":12, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":7, "x":12, "facing":0}}, "terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Flags":{}, "Map_Tile_5_0":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":0, "x":5, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":-1, "unitClassId":"fortified_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"fortified_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["fortified_city"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":1000, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"fortified_garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":14, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":0, "x":5, "facing":0}}, "terrain":"plains"}, "Counters":{}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"sea"}, "Map_Tile_15_11":{"terrain":"ocean"}, "Map_Tile_15_10":{"terrain":"ocean"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"beach"}, "Map_Tile_15_6":{"terrain":"plains"}, "Map_Tile_4_6":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":6, "x":4, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"mage", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":false, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":[{"canAttackSubmerged":false, "canAttackAir":true, "terrainExclusion":{}, "minRange":1, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "maxRange":1, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "id":"lightning", "canCounterAttack":true, "canMoveAndAttack":true}], "transportTags":{}, "maxGroove":0, "verbCostMultiplier":0.5, "inAir":false, "id":"mage", "canBeCaptured":false, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"walking", "moveRange":5, "loadCapacity":0, "canReinforce":false, "isCommander":false, "tags":["mage", "type.ground.light", "spellcaster"], "canBeActivated":false, "resourceCost":2, "inWater":false, "passiveMultiplier":1.5, "cost":400, "maxHealth":100, "aliasId":"", "weaponIds":["lightning"]}, "health":100, "garrisonClassId":"", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":16, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":6, "x":4, "facing":0}}, "terrain":"plains"}, "Map_Tile_14_11":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":11, "x":14, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"water_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"water_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"sea_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":1, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":11, "x":14, "facing":0}}, "terrain":"ocean"}, "Map_Tile_13_5":{"terrain":"forest"}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_10_11":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":11, "x":10, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"water_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"water_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"sea_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":3, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":11, "x":10, "facing":0}}, "terrain":"ocean"}, "Map_Tile_3_11":{"terrain":"sea"}, "Map_Tile_14_9":{"terrain":"sea"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"sea"}, "Map_Tile_9_5":{"terrain":"mountain"}, "Map_Tile_1_6":{"terrain":"beach"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_14_2":{"terrain":"forest"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Objectives":["Capture the lumbermill with your commander (Requires Balloon and Harpy).", "Win by defeating the port (Requires Harpy)."], "Map_Tile_11_3":{"terrain":"forest"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"forest_cut"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"beach"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_13_2":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":2, "x":13, "facing":3}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":0, "unitClassId":"commander_mercia", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":false, "canAttack":true, "isStructure":false, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":[{"canAttackSubmerged":false, "canAttackAir":false, "terrainExclusion":{}, "minRange":1, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "maxRange":1, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "id":"merciaSword", "canCounterAttack":true, "canMoveAndAttack":true}], "transportTags":{}, "maxGroove":250, "verbCostMultiplier":1.0, "inAir":false, "id":"commander_mercia", "canBeCaptured":false, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"walking", "moveRange":4, "loadCapacity":0, "canReinforce":false, "isCommander":true, "tags":["commander", "type.ground.light"], "canBeActivated":false, "resourceCost":3, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":["merciaSword"]}, "health":100, "garrisonClassId":"", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"heal_aura", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":18, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":2, "x":13, "facing":3}}, "terrain":"plains"}, "Map_Tile_3_9":{"terrain":"beach"}, "Map_Tile_2_2":{"terrain":"beach"}, "Map_Tile_12_10":{"terrain":"sea"}, "Map_Tile_5_9":{"terrain":"beach"}, "Map_Tile_12_9":{"terrain":"beach"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_1_1":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":1, "x":1, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":-1, "unitClassId":"lumbermill", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"lumbermill", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":15, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":1, "x":1, "facing":0}}, "terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"ocean"}, "Map_Tile_12_5":{"terrain":"forest"}, "Map_Tile_10_0":{"terrain":"ocean"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_5_7":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":7, "x":5, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":-1, "unitClassId":"fortified_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"fortified_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["fortified_city"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":1000, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"fortified_garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":7, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":7, "x":5, "facing":0}}, "terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"forest"}, "Map_Tile_0_0":{"terrain":"forest"}, "Map_Tile_11_11":{"terrain":"ocean"}, "Map_Tile_9_11":{"terrain":"ocean"}, "Map_Tile_11_10":{"terrain":"sea"}, "Map_Tile_5_8":{"terrain":"forest_cut"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"beach"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_1_3":{"terrain":"sea"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_6_11":{"terrain":"sea"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"beach"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"forest"}, "Map_Tile_10_8":{"terrain":"beach"}, "Map_Tile_4_2":{"terrain":"beach"}, "Map_Tile_8_0":{"terrain":"beach"}, "Map_Tile_5_11":{"terrain":"sea"}, "Map_Tile_10_3":{"terrain":"forest"}, "Map_Tile_5_5":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":5, "x":5, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"spearman", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":false, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":[{"canAttackSubmerged":false, "canAttackAir":false, "terrainExclusion":{}, "minRange":1, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "maxRange":1, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "id":"spear", "canCounterAttack":true, "canMoveAndAttack":true}], "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"spearman", "canBeCaptured":false, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"walking", "moveRange":3, "loadCapacity":0, "canReinforce":false, "isCommander":false, "tags":["spearman", "type.ground.light"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.5, "cost":250, "maxHealth":100, "aliasId":"", "weaponIds":["spear"]}, "health":100, "garrisonClassId":"", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":10, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":5, "x":5, "facing":0}}, "terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Player_2":{"recruit_dog":true, "recruit_merman":true, "recruit_knight":false, "recruit_harpy":true, "recruit_dragon":true, "recruit_wagon":false, "recruit_rifleman":false, "recruit_thief":false, "recruit_caravel":false, "recruit_turtle":false, "recruit_giant":false, "recruit_trebuchet":false, "recruit_mage":true, "recruit_spearman":true, "recruit_witch":true, "recruit_harpoonship":false, "recruit_soldier":true, "recruit_frog":false, "recruit_kraken":false, "recruit_ballista":false, "recruit_travelboat":false, "recruit_archer":false, "gold":1000, "team":1, "recruit_warship":false, "recruit_balloon":true, "recruit_griffin_walking":true}, "Map_Tile_14_4":{"terrain":"forest"}, "Map_Tile_6_9":{"terrain":"beach"}, "Map_Tile_13_1":{"terrain":"forest"}, "Map_Tile_4_9":{"terrain":"beach"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_9_1":{"terrain":"sea"}, "Map_Tile_1_4":{"terrain":"sea"}, "Map_Tile_10_10":{"terrain":"sea"}, "Map_Tile_0_2":{"terrain":"forest"}, "Map_Tile_4_5":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":5, "x":4, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"spearman", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":false, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":[{"canAttackSubmerged":false, "canAttackAir":false, "terrainExclusion":{}, "minRange":1, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "maxRange":1, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "id":"spear", "canCounterAttack":true, "canMoveAndAttack":true}], "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"spearman", "canBeCaptured":false, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"walking", "moveRange":3, "loadCapacity":0, "canReinforce":false, "isCommander":false, "tags":["spearman", "type.ground.light"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.5, "cost":250, "maxHealth":100, "aliasId":"", "weaponIds":["spear"]}, "health":100, "garrisonClassId":"", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":9, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":5, "x":4, "facing":0}}, "terrain":"plains"}, "Map_Tile_0_9":{"terrain":"sea"}, "Map_Tile_3_3":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_8_11":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":11, "x":8, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"water_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"water_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"sea_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":4, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":11, "x":8, "facing":0}}, "terrain":"ocean"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_8_8":{"terrain":"beach"}, "Map_Name":"Den-Two-Away", "Map_Tile_8_1":{"terrain":"beach"}, "Map_Tile_9_0":{"terrain":"ocean"}, "Map_Tile_7_11":{"terrain":"ocean"}, "Map_Tile_4_11":{"terrain":"sea"}, "Map_Tile_2_10":{"terrain":"reef"}, "Map_Tile_2_4":{"terrain":"beach"}, "Map_Tile_1_10":{"terrain":"reef"}, "Author":"Fly Sniper", "Map_Tile_7_1":{"terrain":"beach"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"ocean"}, "Map_Tile_12_11":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":11, "x":12, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"water_city", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"water_city", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"sea_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":2, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":11, "x":12, "facing":0}}, "terrain":"ocean"}, "Map_Tile_2_8":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":8, "x":2, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"port_ap", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":{}, "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"port_ap", "canBeCaptured":true, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"river_sea_building", "moveRange":0, "loadCapacity":0, "canReinforce":true, "isCommander":false, "tags":["structure"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.0, "cost":500, "maxHealth":100, "aliasId":"", "weaponIds":{}}, "health":100, "garrisonClassId":"garrison", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":8, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":8, "x":2, "facing":0}}, "terrain":"sea"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"sea"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"sea"}, "Map_Tile_5_6":{"unit":{"merchantDiscounts":{}, "itemId":"", "attackerId":-1, "attackerUnitClass":"", "blessings":{}, "state":{}, "startPos":{"y":6, "x":5, "facing":0}, "hadTurn":false, "transportedBy":-1, "factionOverride":"", "canChargeGroove":true, "playerId":1, "unitClassId":"spearman", "itemDropNumber":0, "hasBeenKilled":false, "attackerPlayerId":-1, "items":{}, "miniGrooveId":"", "unitClass":{"isRecruitable":true, "canAttack":true, "isStructure":false, "critConditionId":"", "recruitingCostMultiplier":1.0, "weapons":[{"canAttackSubmerged":false, "canAttackAir":false, "terrainExclusion":{}, "minRange":1, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "maxRange":1, "blockedByEnemies":false, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "id":"spear", "canCounterAttack":true, "canMoveAndAttack":true}], "transportTags":{}, "maxGroove":0, "verbCostMultiplier":1.0, "inAir":false, "id":"spearman", "canBeCaptured":false, "isDamagingParentUnit":false, "reinforceMultiplier":1.0, "isAttackable":true, "movementType":"walking", "moveRange":3, "loadCapacity":0, "canReinforce":false, "isCommander":false, "tags":["spearman", "type.ground.light"], "canBeActivated":false, "resourceCost":1, "inWater":false, "passiveMultiplier":1.5, "cost":250, "maxHealth":100, "aliasId":"", "weaponIds":["spear"]}, "health":100, "garrisonClassId":"", "underwater":false, "grooveCharge":0, "canBeAttackedFromDistance":true, "grooveId":"", "setGroove":null, "setHealth":null, "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "recruits":{}, "stunned":false, "damageTakenPercent":100, "loadedUnits":{}, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "id":11, "killedByLosing":false, "inTransport":false, "recruitDiscounts":{}, "pos":{"y":6, "x":5, "facing":0}}, "terrain":"plains"}, "Map_Tile_10_5":{"terrain":"mountain"}, "Map_Tile_4_10":{"terrain":"sea"}, "Map_Tile_1_5":{"terrain":"beach"}, "Map_Tile_3_4":{"terrain":"beach"}, "Map_Tile_0_10":{"terrain":"reef"}, "Locations":{"1":{"getArea":null, "positions":[{"y":2, "x":1}, {"y":1, "x":2}, {"y":1, "x":0}, {"y":0, "x":1}, {"y":1, "x":1}], "interactable":false, "setArea":null, "id":1, "centre":{"y":1, "x":1}, "name":"Commander Capture"}, "0":{"getArea":null, "positions":[{"y":6, "x":5}, {"y":6, "x":4}, {"y":5, "x":4}, {"y":5, "x":5}, {"y":7, "x":5}, {"y":7, "x":3}, {"y":8, "x":4}, {"y":6, "x":3}, {"y":6, "x":2}, {"y":5, "x":3}, {"y":4, "x":4}, {"y":4, "x":5}, {"y":8, "x":5}, {"y":7, "x":4}, {"y":9, "x":5}, {"y":9, "x":4}, {"y":9, "x":3}], "interactable":false, "setArea":null, "id":0, "centre":{"y":7, "x":4}, "name":"Random TP"}}, "Map_Tile_3_8":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Disastrous_Crossing.json b/worlds/wargroove2/levels/Disastrous_Crossing.json new file mode 100644 index 000000000000..e1d296bcb5bb --- /dev/null +++ b/worlds/wargroove2/levels/Disastrous_Crossing.json @@ -0,0 +1 @@ +{"Locations":{"1":{"getArea":null, "centre":{"x":15, "y":7}, "interactable":false, "setArea":null, "positions":[{"x":13, "y":9}, {"x":13, "y":8}, {"x":14, "y":7}, {"x":14, "y":8}, {"x":14, "y":9}, {"x":14, "y":6}, {"x":13, "y":6}, {"x":13, "y":5}, {"x":16, "y":7}, {"x":16, "y":8}, {"x":15, "y":8}, {"x":15, "y":9}, {"x":16, "y":9}, {"x":16, "y":6}, {"x":16, "y":5}, {"x":15, "y":5}, {"x":14, "y":5}], "id":1, "name":"P2 Shuffle 1"}, "2":{"getArea":null, "centre":{"x":28, "y":7}, "interactable":false, "setArea":null, "positions":[{"x":29, "y":12}, {"x":28, "y":12}, {"x":27, "y":12}, {"x":27, "y":11}, {"x":27, "y":10}, {"x":27, "y":9}, {"x":27, "y":8}, {"x":27, "y":7}, {"x":27, "y":6}, {"x":27, "y":5}, {"x":27, "y":4}, {"x":27, "y":3}, {"x":27, "y":2}, {"x":28, "y":2}, {"x":29, "y":2}, {"x":29, "y":3}, {"x":29, "y":4}, {"x":29, "y":5}, {"x":29, "y":6}, {"x":29, "y":7}, {"x":29, "y":8}, {"x":29, "y":9}, {"x":29, "y":10}, {"x":29, "y":11}, {"x":28, "y":11}, {"x":28, "y":10}, {"x":28, "y":9}, {"x":28, "y":8}, {"x":28, "y":7}, {"x":28, "y":6}, {"x":28, "y":5}, {"x":28, "y":4}, {"x":28, "y":3}], "id":2, "name":"P2 Shuffle 2"}, "3":{"getArea":null, "centre":{"x":14, "y":7}, "interactable":false, "setArea":null, "positions":[{"x":20, "y":9}, {"x":19, "y":9}, {"x":18, "y":9}, {"x":17, "y":9}, {"x":16, "y":9}, {"x":15, "y":9}, {"x":14, "y":9}, {"x":13, "y":9}, {"x":12, "y":9}, {"x":11, "y":9}, {"x":10, "y":9}, {"x":9, "y":9}, {"x":8, "y":9}, {"x":7, "y":9}, {"x":6, "y":9}, {"x":5, "y":9}, {"x":4, "y":9}, {"x":20, "y":5}, {"x":19, "y":5}, {"x":18, "y":5}, {"x":17, "y":5}, {"x":16, "y":5}, {"x":15, "y":5}, {"x":14, "y":5}, {"x":13, "y":5}, {"x":12, "y":5}, {"x":11, "y":5}, {"x":10, "y":5}, {"x":9, "y":5}, {"x":8, "y":5}, {"x":7, "y":5}, {"x":6, "y":5}, {"x":5, "y":5}, {"x":4, "y":5}, {"x":21, "y":4}, {"x":21, "y":10}, {"x":3, "y":4}, {"x":3, "y":10}, {"x":22, "y":4}, {"x":23, "y":4}, {"x":24, "y":4}, {"x":24, "y":10}, {"x":23, "y":10}, {"x":22, "y":10}], "id":3, "name":"Ocean 1"}, "4":{"getArea":null, "centre":{"x":14, "y":7}, "interactable":false, "setArea":null, "positions":[{"x":3, "y":9}, {"x":4, "y":8}, {"x":5, "y":8}, {"x":6, "y":8}, {"x":7, "y":8}, {"x":8, "y":8}, {"x":9, "y":8}, {"x":10, "y":8}, {"x":11, "y":8}, {"x":12, "y":8}, {"x":13, "y":8}, {"x":14, "y":8}, {"x":15, "y":8}, {"x":16, "y":8}, {"x":17, "y":8}, {"x":18, "y":8}, {"x":19, "y":8}, {"x":20, "y":8}, {"x":21, "y":9}, {"x":22, "y":9}, {"x":23, "y":9}, {"x":24, "y":9}, {"x":24, "y":5}, {"x":23, "y":5}, {"x":22, "y":5}, {"x":21, "y":5}, {"x":20, "y":6}, {"x":19, "y":6}, {"x":18, "y":6}, {"x":17, "y":6}, {"x":16, "y":6}, {"x":15, "y":6}, {"x":14, "y":6}, {"x":13, "y":6}, {"x":12, "y":6}, {"x":11, "y":6}, {"x":10, "y":6}, {"x":9, "y":6}, {"x":8, "y":6}, {"x":7, "y":6}, {"x":6, "y":6}, {"x":5, "y":6}, {"x":4, "y":6}, {"x":3, "y":5}], "id":4, "name":"Ocean 2"}, "0":{"getArea":null, "centre":{"x":5, "y":7}, "interactable":false, "setArea":null, "positions":[{"x":5, "y":7}, {"x":5, "y":6}, {"x":4, "y":6}, {"x":4, "y":7}, {"x":4, "y":8}, {"x":4, "y":9}, {"x":5, "y":9}, {"x":5, "y":8}, {"x":4, "y":5}, {"x":5, "y":5}, {"x":4, "y":4}, {"x":5, "y":4}, {"x":5, "y":10}, {"x":4, "y":10}, {"x":6, "y":9}, {"x":6, "y":10}, {"x":6, "y":8}, {"x":6, "y":6}, {"x":6, "y":4}], "id":0, "name":"P1 Shuffle"}, "5":{"getArea":null, "centre":{"x":15, "y":7}, "interactable":false, "setArea":null, "positions":[{"x":24, "y":8}, {"x":23, "y":8}, {"x":22, "y":8}, {"x":21, "y":8}, {"x":24, "y":6}, {"x":23, "y":6}, {"x":22, "y":6}, {"x":21, "y":6}, {"x":20, "y":7}, {"x":19, "y":7}, {"x":18, "y":7}, {"x":17, "y":7}, {"x":16, "y":7}, {"x":15, "y":7}, {"x":14, "y":7}, {"x":13, "y":7}, {"x":12, "y":7}, {"x":11, "y":7}, {"x":10, "y":7}, {"x":9, "y":7}, {"x":8, "y":7}, {"x":7, "y":7}, {"x":6, "y":7}, {"x":5, "y":7}, {"x":4, "y":7}, {"x":3, "y":7}], "id":5, "name":"Ocean 3"}}, "Map_Tile_17_0":{"terrain":"ocean"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"beach"}, "Map_Tile_5_4":{"terrain":"beach"}, "Objectives":["Win by destroying the enemy stronghold in less than 15 turns (Requires (Merfolk or Riverboat) and (Knight or Kraken))."], "Map_Tile_28_8":{"terrain":"plains"}, "Map_Tile_17_12":{"terrain":"sea"}, "Map_Tile_11_9":{"terrain":"road"}, "Map_Tile_15_0":{"terrain":"ocean"}, "Map_Tile_16_9":{"terrain":"forest"}, "Map_Tile_11_8":{"terrain":"road"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"ocean", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":1, "y":2}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":1, "y":2}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":12}}, "Map_Tile_26_3":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"ocean"}, "Map_Tile_16_5":{"terrain":"road"}, "Map_Tile_23_3":{"terrain":"beach"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"road"}, "Map_Tile_14_4":{"terrain":"beach"}, "Map_Tile_8_6":{"terrain":"forest"}, "Map_Tile_20_12":{"terrain":"sea"}, "Map_Tile_26_9":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":6, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":6, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"hq", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":3000, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"hq", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":39}}, "Map_Tile_0_6":{"terrain":"road"}, "Map_Tile_21_13":{"terrain":"ocean"}, "Map_Tile_18_9":{"terrain":"road"}, "Map_Tile_0_9":{"terrain":"road"}, "Map_Tile_11_10":{"terrain":"beach"}, "Map_Tile_5_1":{"terrain":"sea"}, "Map_Tile_17_5":{"terrain":"forest"}, "Map_Tile_20_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":20, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":20, "y":7}, "hasBeenKilled":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"barracks", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"barracks", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":1}}, "Map_Tile_13_5":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":13, "y":5}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":13, "y":5}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"soldier", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"sword", "canAttackSubmerged":false}], "cost":100, "weaponIds":["sword"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["soldier", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"soldier", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":34}}, "Map_Tile_12_9":{"terrain":"forest"}, "Map_Tile_25_1":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":25, "y":1}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":25, "y":1}, "hasBeenKilled":false, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"port_ap", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"river_sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"port_ap", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":6}}, "Map_Tile_6_5":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":6, "y":5}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":6, "y":5}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"fortified_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":1000, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["fortified_city"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"fortified_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"fortified_garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":45}}, "Map_Tile_2_12":{"terrain":"sea"}, "Map_Tile_12_0":{"terrain":"sea"}, "Map_Tile_10_14":{"terrain":"ocean"}, "Map_Tile_0_3":{"terrain":"ocean"}, "Map_Tile_26_2":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"reef"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"ocean"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"sea"}, "Map_Tile_13_12":{"terrain":"sea"}, "Map_Tile_6_13":{"terrain":"ocean"}, "Map_Tile_2_6":{"terrain":"road"}, "Map_Tile_17_3":{"terrain":"sea"}, "Map_Tile_9_14":{"terrain":"ocean"}, "Map_Tile_1_0":{"terrain":"reef"}, "Map_Tile_27_14":{"terrain":"plains"}, "Map_Tile_21_6":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"reef"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_22_12":{"terrain":"sea"}, "Map_Tile_0_14":{"terrain":"ocean"}, "Map_Tile_29_10":{"terrain":"plains"}, "Map_Tile_24_6":{"terrain":"road"}, "Map_Tile_24_2":{"terrain":"beach"}, "Map_Tile_29_14":{"terrain":"forest"}, "Map_Tile_29_13":{"terrain":"road"}, "Map_Tile_29_12":{"terrain":"plains"}, "Map_Tile_25_9":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"sea"}, "Map_Tile_20_1":{"terrain":"sea"}, "Map_Tile_24_14":{"terrain":"ocean"}, "Counters":{}, "Map_Tile_22_2":{"terrain":"reef"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"ocean"}, "Map_Tile_6_1":{"terrain":"ocean"}, "Map_Tile_26_4":{"terrain":"forest"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_29_9":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":29, "y":9}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":29, "y":9}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"ballista", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":6, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":true, "minRange":2, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":false, "id":"ballistaBolt", "canAttackSubmerged":false}], "cost":800, "weaponIds":["ballistaBolt"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"wheels", "moveRange":6, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["ballista", "type.ground.heavy"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"ballista", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":32}}, "Map_Tile_26_1":{"terrain":"plains"}, "Map_Tile_20_11":{"terrain":"beach"}, "Map_Tile_1_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":1, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":1, "y":7}, "hasBeenKilled":false, "recruits":["thief", "rifleman"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"hideout", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"hideout", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":43}}, "Map_Tile_10_4":{"terrain":"beach"}, "Map_Tile_24_3":{"terrain":"beach"}, "Map_Tile_5_8":{"terrain":"road"}, "Map_Tile_29_6":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"beach"}, "Map_Tile_1_3":{"terrain":"ocean"}, "Map_Tile_29_4":{"terrain":"plains"}, "Map_Tile_23_0":{"terrain":"sea"}, "Map_Tile_29_3":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"forest"}, "Map_Tile_4_14":{"terrain":"ocean"}, "Map_Tile_25_14":{"terrain":"sea"}, "Map_Tile_10_2":{"terrain":"sea"}, "Map_Tile_3_7":{"item":{"isConsumable":false, "type":"fountain_of_youth", "pos":{"x":3, "y":7}, "unitTypeRestriction":{}, "itemId":47}, "terrain":"plains"}, "Map_Tile_6_4":{"terrain":"beach"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"sea"}, "Map_Tile_28_4":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":4, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":4, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"commander_mercia", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":true, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"merciaSword", "canAttackSubmerged":false}], "cost":500, "weaponIds":["merciaSword"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":false, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":3, "maxGroove":250, "tags":["commander", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"commander_mercia", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"heal_aura", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":20}}, "Player_2":{"recruit_witch":true, "recruit_dog":true, "recruit_frog":true, "recruit_archer":true, "recruit_spearman":true, "recruit_trebuchet":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_kraken":true, "recruit_wagon":true, "recruit_soldier":true, "recruit_giant":true, "recruit_ballista":true, "team":1, "recruit_balloon":true, "recruit_harpy":true, "recruit_travelboat":true, "recruit_griffin_walking":true, "recruit_thief":true, "recruit_knight":true, "recruit_dragon":true, "recruit_turtle":true, "recruit_rifleman":true, "recruit_mage":true, "recruit_caravel":true, "gold":500, "recruit_warship":true}, "Map_Tile_29_1":{"terrain":"road"}, "Map_Tile_16_0":{"terrain":"ocean"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_23_6":{"terrain":"road"}, "Map_Tile_29_0":{"terrain":"forest", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":29, "y":0}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":29, "y":0}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"witch", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":true, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"witchSpell", "canAttackSubmerged":false}], "cost":750, "weaponIds":["witchSpell"], "inAir":true, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":0.5, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":2.0, "aliasId":"", "critConditionId":"", "movementType":"flying", "moveRange":7, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":3, "maxGroove":0, "tags":["witch", "type.air", "spellcaster"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"witch", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":26}}, "Player_Count":2, "Map_Tile_24_8":{"terrain":"plains"}, "Map_Tile_21_14":{"terrain":"ocean"}, "Map_Tile_16_4":{"terrain":"beach"}, "Map_Tile_16_1":{"terrain":"ocean"}, "Map_Tile_28_13":{"terrain":"road"}, "Map_Tile_28_12":{"terrain":"plains"}, "Map_Tile_28_11":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"ocean"}, "Map_Tile_5_13":{"terrain":"ocean"}, "Map_Tile_28_10":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"road"}, "Map_Tile_5_14":{"terrain":"ocean"}, "Map_Tile_27_4":{"terrain":"road"}, "Map_Tile_25_13":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":25, "y":13}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":25, "y":13}, "hasBeenKilled":false, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"port_ap", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"river_sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"port_ap", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":7}}, "Map_Tile_28_7":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"road"}, "Map_Tile_28_6":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":28, "y":6}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":28, "y":6}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"soldier", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"sword", "canAttackSubmerged":false}], "cost":100, "weaponIds":["sword"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["soldier", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"soldier", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":30}}, "Map_Tile_28_5":{"terrain":"road"}, "Map_Tile_26_13":{"terrain":"plains"}, "Map_Tile_21_8":{"terrain":"road"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"road"}, "Map_Tile_0_13":{"terrain":"ocean", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":0, "y":13}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":0, "y":13}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":13}}, "Map_Tile_29_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":29, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":29, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":19}}, "Map_Tile_28_1":{"terrain":"road"}, "Map_Tile_17_14":{"terrain":"ocean"}, "Map_Tile_28_0":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":4, "y":8}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":4, "y":8}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"dog", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"bite", "canAttackSubmerged":false}], "cost":150, "weaponIds":["bite"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":5, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["dog", "type.ground.light", "animal"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"dog", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":22}}, "Map_Tile_23_10":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"road"}, "Map_Tile_27_12":{"terrain":"road"}, "Map_Tile_21_7":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"sea"}, "Map_Tile_22_4":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_27_10":{"terrain":"road"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"sea"}, "Map_Tile_27_9":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":27, "y":9}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":27, "y":9}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"spearman", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"spear", "canAttackSubmerged":false}], "cost":250, "weaponIds":["spear"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":3, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["spearman", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"spearman", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":28}}, "Map_Tile_26_0":{"terrain":"plains"}, "Map_Tile_27_8":{"terrain":"road"}, "Map_Tile_14_14":{"terrain":"ocean"}, "Map_Tile_8_4":{"terrain":"beach"}, "Map_Tile_27_7":{"terrain":"road"}, "Map_Tile_19_3":{"terrain":"sea"}, "Map_Tile_19_7":{"terrain":"cobblestone"}, "Map_Tile_25_4":{"terrain":"plains"}, "Map_Tile_27_3":{"terrain":"road"}, "Map_Tile_27_2":{"terrain":"road"}, "Map_Tile_27_1":{"terrain":"road"}, "Map_Tile_27_0":{"terrain":"plains"}, "Map_Tile_20_10":{"terrain":"beach"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_26_14":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"ocean"}, "Map_Tile_28_3":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"ocean"}, "Map_Tile_7_7":{"terrain":"road"}, "Map_Tile_8_10":{"terrain":"beach"}, "Map_Tile_15_3":{"terrain":"sea"}, "Map_Tile_26_8":{"terrain":"forest"}, "Map_Tile_7_11":{"terrain":"sea"}, "Map_Tile_26_7":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"cobblestone"}, "Map_Tile_26_6":{"terrain":"road"}, "Map_Tile_5_6":{"terrain":"road"}, "Map_Tile_26_5":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"ocean"}, "Map_Tile_28_9":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":28, "y":9}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":28, "y":9}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"soldier", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"sword", "canAttackSubmerged":false}], "cost":100, "weaponIds":["sword"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["soldier", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"soldier", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":31}}, "Map_Tile_25_12":{"terrain":"beach"}, "Map_Tile_5_3":{"terrain":"sea"}, "Map_Tile_25_11":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"ocean"}, "Map_Tile_22_0":{"terrain":"sea"}, "Map_Tile_29_11":{"terrain":"plains"}, "Map_Tile_25_8":{"terrain":"plains"}, "Map_Tile_25_7":{"terrain":"plains"}, "Map_Tile_23_8":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"reef"}, "Map_Tile_5_11":{"terrain":"sea"}, "Map_Tile_25_6":{"terrain":"road"}, "Map_Tile_11_11":{"terrain":"sea"}, "Map_Tile_25_5":{"terrain":"plains"}, "Map_Tile_27_5":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":27, "y":5}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":27, "y":5}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"soldier", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"sword", "canAttackSubmerged":false}], "cost":100, "weaponIds":["sword"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["soldier", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"soldier", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":29}}, "Map_Tile_7_9":{"terrain":"forest"}, "Map_Tile_8_14":{"terrain":"ocean"}, "Map_Tile_15_14":{"terrain":"ocean"}, "Map_Tile_25_2":{"terrain":"beach"}, "Map_Tile_16_3":{"terrain":"sea"}, "Map_Tile_25_0":{"terrain":"sea"}, "Map_Tile_2_13":{"terrain":"ocean"}, "Map_Tile_18_1":{"terrain":"ocean"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_23_4":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"beach"}, "Map_Tile_7_10":{"terrain":"beach"}, "Map_Tile_13_14":{"terrain":"ocean"}, "Map_Tile_24_12":{"terrain":"beach"}, "Map_Tile_4_0":{"terrain":"reef"}, "Map_Tile_24_11":{"terrain":"beach"}, "Map_Tile_6_2":{"terrain":"ocean"}, "Map_Tile_19_12":{"terrain":"sea"}, "Map_Tile_24_10":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"ocean"}, "Map_Tile_3_14":{"terrain":"ocean"}, "Map_Tile_17_9":{"terrain":"road"}, "Map_Tile_1_9":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":1, "y":9}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":1, "y":9}, "hasBeenKilled":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"barracks", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"barracks", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":3}}, "Map_Tile_12_3":{"terrain":"sea"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_24_9":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"road"}, "Map_Tile_28_2":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":28, "y":2}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":28, "y":2}, "hasBeenKilled":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"barracks", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"barracks", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":16}}, "Map_Tile_14_2":{"terrain":"sea"}, "Triggers":[{"conditions":{}, "actions":[{"parameters":["5120", "Disastrous Crossing", "Fly Sniper", "", "", "", "Win by destroying the enemy stronghold in less than 15 turns (Requires (Merfolk or Riverboat) and (Knight or Kraken))."], "enabled":true, "id":"ap_export"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"start_of_match", "id":"Export", "enabled":true}, {"conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "enabled":true}, {"conditions":[{"parameters":["current", "0", "0", "*commander", "-1"], "enabled":true, "id":"unit_presence"}], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "enabled":true}, {"conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "enabled":true}, {"conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "enabled":true}, {"conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "actions":[{"parameters":{}, "enabled":true, "id":"ap_victory"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Player 1 Victory", "enabled":true}, {"conditions":{}, "actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*commander", "2", "P2", "1", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"start_of_match", "id":"Unit Shuffle", "enabled":true}, {"conditions":[{"parameters":["0", "5"], "enabled":true, "id":"current_turn_number"}], "actions":[{"parameters":["3", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["1000"], "enabled":true, "id":"wait"}, {"parameters":["3", "none", "blue"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["3", "beach", "default", "", "0", "1", "1"], "enabled":true, "id":"activate_flood"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Ocean 1", "enabled":true}, {"conditions":[{"parameters":["0", "4"], "enabled":true, "id":"current_turn_number"}], "actions":[{"parameters":["3", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["500"], "enabled":true, "id":"wait"}, {"parameters":["neutral", "floran_captain", "The tide's coming in next turn! A new beach will appear here!", "1", "Tide Watcher"], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["500"], "enabled":true, "id":"wait"}, {"parameters":["3", "ground_pulse_only", "blue"], "enabled":true, "id":"set_location_highlight"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Ocean 1 Warn", "enabled":true}, {"conditions":[{"parameters":["0", "10"], "enabled":true, "id":"current_turn_number"}], "actions":[{"parameters":["4", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["1000"], "enabled":true, "id":"wait"}, {"parameters":["4", "none", "blue"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["4", "beach", "default", "", "0", "1", "1"], "enabled":true, "id":"activate_flood"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Ocean 2", "enabled":true}, {"conditions":[{"parameters":["0", "9"], "enabled":true, "id":"current_turn_number"}], "actions":[{"parameters":["4", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["500"], "enabled":true, "id":"wait"}, {"parameters":["neutral", "floran_captain", "The tide's coming in again!", "1", "Tide Watcher"], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["500"], "enabled":true, "id":"wait"}, {"parameters":["4", "ground_pulse_only", "blue"], "enabled":true, "id":"set_location_highlight"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Ocean 2 Warn", "enabled":true}, {"conditions":[{"parameters":["0", "15"], "enabled":true, "id":"current_turn_number"}], "actions":[{"parameters":["5", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["1000"], "enabled":true, "id":"wait"}, {"parameters":["5", "none", "blue"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["5", "beach", "default", "", "0", "1", "1"], "enabled":true, "id":"activate_flood"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Ocean 3", "enabled":true}, {"conditions":[{"parameters":["0", "14"], "enabled":true, "id":"current_turn_number"}], "actions":[{"parameters":["5", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["500"], "enabled":true, "id":"wait"}, {"parameters":["neutral", "floran_captain", "The tide will flood our HQ next turn!", "1", "Tide Watcher"], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["500"], "enabled":true, "id":"wait"}, {"parameters":["5", "ground_pulse_only", "blue"], "enabled":true, "id":"set_location_highlight"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "recurring":"once", "id":"Ocean 3 Warn", "enabled":true}], "Map_Tile_10_11":{"terrain":"sea"}, "Map_Tile_25_3":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"road"}, "Map_Tile_24_4":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_24_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":24, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":24, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"hq", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":3000, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"hq", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":46}}, "Map_Tile_13_10":{"terrain":"beach"}, "Map_Tile_24_1":{"terrain":"sea"}, "Map_Tile_13_11":{"terrain":"sea"}, "Map_Tile_23_14":{"terrain":"ocean"}, "Map_Tile_23_13":{"terrain":"ocean"}, "Map_Tile_13_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":13, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":13, "y":7}, "hasBeenKilled":false, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"tower_ap", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"tower_ap", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":41}}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"ocean"}, "Map_Tile_4_10":{"terrain":"beach"}, "Map_Tile_15_8":{"terrain":"road"}, "Map_Tile_3_13":{"terrain":"ocean"}, "Map_Tile_23_11":{"terrain":"beach"}, "Map_Tile_1_5":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":1, "y":5}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":1, "y":5}, "hasBeenKilled":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"barracks", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"barracks", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":2}}, "Map_Tile_23_9":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":23, "y":9}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":23, "y":9}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":18}}, "Map_Tile_3_3":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":3, "y":3}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":3, "y":3}, "hasBeenKilled":false, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"port", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"river_sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"port", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":5}}, "Map_Tile_23_7":{"terrain":"plains"}, "Map_Tile_19_1":{"terrain":"ocean"}, "Map_Tile_23_5":{"terrain":"plains"}, "Map_Tile_21_10":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"sea"}, "Map_Tile_23_1":{"terrain":"sea"}, "Map_Tile_22_14":{"terrain":"ocean"}, "Map_Tile_4_13":{"terrain":"ocean"}, "Map_Tile_22_13":{"terrain":"ocean"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_22_11":{"terrain":"beach"}, "Map_Tile_14_0":{"terrain":"ocean", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":14, "y":0}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":14, "y":0}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":24}}, "Map_Tile_19_4":{"terrain":"beach"}, "Map_Tile_13_3":{"terrain":"sea"}, "Map_Tile_12_10":{"terrain":"beach"}, "Map_Tile_22_7":{"terrain":"road"}, "Map_Tile_13_1":{"terrain":"ocean"}, "Map_Tile_17_8":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":17, "y":8}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":17, "y":8}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":14}}, "Map_Tile_22_5":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":22, "y":5}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":22, "y":5}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":17}}, "Map_Tile_22_3":{"terrain":"beach"}, "Map_Tile_22_1":{"terrain":"sea"}, "Map_Tile_6_14":{"terrain":"ocean"}, "Map_Tile_25_10":{"terrain":"plains"}, "Player_1":{"recruit_witch":true, "recruit_dog":true, "recruit_frog":true, "recruit_archer":true, "recruit_spearman":true, "recruit_trebuchet":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_kraken":true, "recruit_wagon":true, "recruit_soldier":true, "recruit_giant":true, "recruit_ballista":true, "team":0, "recruit_balloon":true, "recruit_harpy":true, "recruit_travelboat":true, "recruit_griffin_walking":true, "recruit_thief":true, "recruit_knight":true, "recruit_dragon":true, "recruit_turtle":true, "recruit_rifleman":true, "recruit_mage":true, "recruit_caravel":true, "gold":0, "recruit_warship":true}, "Map_Tile_10_1":{"terrain":"sea"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"sea"}, "Map_Tile_21_11":{"terrain":"beach"}, "Map_Tile_20_0":{"terrain":"sea"}, "Map_Tile_23_2":{"terrain":"sea"}, "Map_Tile_6_0":{"terrain":"ocean"}, "Map_Tile_5_7":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":5, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":5, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"knight", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"lance", "canAttackSubmerged":false}], "cost":600, "weaponIds":["lance"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"riding", "moveRange":6, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":2, "maxGroove":0, "tags":["knight", "type.ground.heavy"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"knight", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":40}}, "Map_Tile_8_11":{"terrain":"sea"}, "Map_Tile_10_3":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"ocean"}, "Map_Tile_0_0":{"terrain":"reef"}, "Map_Tile_21_5":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"ocean"}, "Map_Tile_26_12":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"ocean"}, "Map_Tile_18_7":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"ocean"}, "Map_Tile_21_2":{"terrain":"sea"}, "Map_Tile_15_11":{"terrain":"sea"}, "Map_Tile_11_13":{"terrain":"ocean"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_8_0":{"terrain":"ocean"}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_8_2":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":8, "y":2}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":8, "y":2}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":9}}, "Map_Tile_21_0":{"terrain":"sea"}, "Map_Tile_20_14":{"terrain":"ocean"}, "Map_Tile_4_2":{"terrain":"sea"}, "Map_Tile_19_11":{"terrain":"sea"}, "Map_Tile_18_5":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":13, "y":6}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":13, "y":6}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"spearman", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"spear", "canAttackSubmerged":false}], "cost":250, "weaponIds":["spear"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":3, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["spearman", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"spearman", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":38}}, "Map_Tile_20_8":{"terrain":"plains"}, "Map_Size":{"x":30, "y":15}, "Map_Tile_3_2":{"terrain":"sea"}, "Map_Tile_17_4":{"terrain":"beach"}, "Map_Tile_12_2":{"terrain":"sea"}, "Map_Tile_10_7":{"terrain":"mountain"}, "Map_Tile_22_10":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"road"}, "Map_Tile_13_8":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":13, "y":8}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":13, "y":8}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"spearman", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"spear", "canAttackSubmerged":false}], "cost":250, "weaponIds":["spear"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":3, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["spearman", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"spearman", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":37}}, "Map_Tile_24_13":{"terrain":"ocean"}, "Map_Tile_20_2":{"terrain":"sea"}, "Map_Tile_19_6":{"terrain":"cobblestone"}, "Map_Tile_19_14":{"terrain":"ocean"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_19_13":{"terrain":"ocean"}, "Map_Tile_19_9":{"terrain":"road"}, "Map_Tile_2_3":{"terrain":"ocean"}, "Map_Tile_27_6":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":27, "y":6}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":27, "y":6}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"spearman", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"spear", "canAttackSubmerged":false}], "cost":250, "weaponIds":["spear"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":3, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["spearman", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"spearman", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":27}}, "Map_Tile_19_5":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"ocean"}, "Map_Tile_7_13":{"terrain":"ocean"}, "Map_Tile_22_9":{"terrain":"forest"}, "Map_Tile_4_1":{"terrain":"sea"}, "Map_Tile_13_9":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":13, "y":9}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":13, "y":9}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"soldier", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"sword", "canAttackSubmerged":false}], "cost":100, "weaponIds":["sword"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["soldier", "type.ground.light"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"soldier", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":35}}, "Map_Tile_0_11":{"terrain":"sea"}, "Flags":{}, "Map_Tile_14_11":{"terrain":"reef"}, "Map_Tile_12_1":{"terrain":"sea"}, "Map_Tile_10_6":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":10, "y":6}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":10, "y":6}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":33}}, "Map_Tile_5_2":{"terrain":"ocean"}, "Map_Tile_18_12":{"terrain":"sea"}, "Map_Tile_6_8":{"terrain":"road"}, "Map_Tile_12_11":{"terrain":"sea"}, "Map_Tile_18_10":{"terrain":"beach"}, "Map_Tile_18_8":{"terrain":"plains"}, "Map_Tile_21_3":{"terrain":"beach"}, "Map_Tile_20_13":{"terrain":"ocean"}, "Map_Tile_1_11":{"terrain":"sea"}, "Map_Tile_1_14":{"terrain":"ocean"}, "Map_Tile_14_13":{"terrain":"ocean"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"reef"}, "Map_Tile_18_4":{"terrain":"beach"}, "Map_Tile_18_3":{"terrain":"sea"}, "Map_Tile_18_2":{"terrain":"sea"}, "Map_Tile_18_0":{"terrain":"ocean"}, "Map_Tile_17_13":{"terrain":"ocean"}, "Map_Tile_17_11":{"terrain":"sea"}, "Map_Tile_15_10":{"terrain":"beach"}, "Map_Tile_11_3":{"terrain":"sea"}, "Map_Tile_17_6":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":3, "y":11}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":3, "y":11}, "hasBeenKilled":false, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"port", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"river_sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"port", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":4}}, "Map_Tile_9_10":{"terrain":"beach"}, "Map_Tile_11_2":{"terrain":"reef"}, "Map_Tile_17_1":{"terrain":"ocean"}, "Map_Tile_16_14":{"terrain":"ocean", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":16, "y":14}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":16, "y":14}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":23}}, "Map_Tile_16_11":{"terrain":"sea"}, "Map_Tile_16_12":{"terrain":"sea"}, "Map_Tile_16_13":{"terrain":"ocean"}, "Map_Tile_16_10":{"terrain":"beach"}, "Map_Tile_9_4":{"terrain":"beach"}, "Map_Tile_20_4":{"terrain":"beach"}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_10_0":{"terrain":"sea"}, "Map_Tile_0_12":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"beach"}, "Map_Tile_2_11":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"sea"}, "Map_Tile_1_1":{"terrain":"ocean"}, "Map_Tile_28_14":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"beach"}, "Map_Tile_21_9":{"terrain":"road"}, "Map_Tile_24_5":{"terrain":"forest"}, "Map_Tile_10_5":{"terrain":"forest"}, "Map_Tile_17_2":{"terrain":"reef"}, "Map_Tile_8_1":{"terrain":"ocean"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"sea"}, "Map_Tile_3_12":{"terrain":"sea"}, "Map_Tile_9_0":{"terrain":"ocean"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"beach"}, "Map_Tile_17_10":{"terrain":"beach"}, "Map_Tile_7_8":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":7, "y":8}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":7, "y":8}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"fortified_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":1000, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["fortified_city"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"fortified_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"fortified_garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":44}}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_12_14":{"terrain":"ocean"}, "Map_Tile_7_4":{"terrain":"beach"}, "Map_Tile_13_2":{"terrain":"sea"}, "Map_Tile_26_10":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":26, "y":10}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":26, "y":10}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"fortified_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":1000, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["fortified_city"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"fortified_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"fortified_garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":15}}, "Map_Tile_15_2":{"terrain":"sea"}, "Map_Tile_9_7":{"terrain":"road"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"sea"}, "Map_Tile_12_13":{"terrain":"ocean"}, "Map_Tile_15_6":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":15, "y":6}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":15, "y":6}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"land_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":42}}, "Map_Tile_12_6":{"terrain":"road"}, "Map_Tile_14_1":{"terrain":"ocean"}, "Map_Tile_18_13":{"terrain":"ocean"}, "Author":"Fly Sniper", "Map_Tile_29_5":{"terrain":"road"}, "Map_Tile_14_10":{"terrain":"beach"}, "Map_Tile_11_6":{"terrain":"road"}, "Map_Tile_14_8":{"terrain":"road"}, "Map_Tile_15_12":{"terrain":"sea"}, "Map_Tile_1_12":{"terrain":"sea"}, "Map_Tile_15_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":3, "x":15, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":3, "x":15, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"rifleman", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":["forest"], "directionality":"omni", "maxRange":9, "horizontalAndVerticalOnly":true, "blockedByEnemies":true, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":1, "canCounterAttack":true, "canMoveAndAttack":false, "id":"musket", "canAttackSubmerged":false}], "cost":650, "weaponIds":["musket"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":4, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":2, "maxGroove":0, "tags":["rifleman", "type.ground.hideout"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"rifleman", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":[{"value":"3", "key":"ammo"}], "itemDropNumber":0, "health":100, "playerId":1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":36}}, "Map_Tile_1_6":{"terrain":"road"}, "Map_Tile_21_12":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":21, "y":12}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":21, "y":12}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":10}}, "Map_Tile_29_8":{"terrain":"plains"}, "Map_Tile_18_11":{"terrain":"reef"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"forest"}, "Map_Tile_5_12":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":5, "y":12}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":5, "y":12}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":-1, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":8}}, "Map_Tile_0_7":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":0, "y":7}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":0, "y":7}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"ballista", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":6, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":true, "minRange":2, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":false, "id":"ballistaBolt", "canAttackSubmerged":false}], "cost":800, "weaponIds":["ballistaBolt"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"wheels", "moveRange":6, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["ballista", "type.ground.heavy"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"ballista", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":25}}, "Map_Tile_20_5":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"road"}, "Map_Name":"Disastrous Crossing", "Map_Tile_16_8":{"terrain":"road"}, "Map_Tile_4_6":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":4, "y":6}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":4, "y":6}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"dog", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":[{"unitIdWhenAttacking":"", "terrainExclusion":{}, "directionality":"omni", "maxRange":1, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "canAttackAir":false, "minRange":1, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canMoveAndAttack":true, "id":"bite", "canAttackSubmerged":false}], "cost":150, "weaponIds":["bite"], "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":false, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.5, "aliasId":"", "critConditionId":"", "movementType":"walking", "moveRange":5, "canBeCaptured":false, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["dog", "type.ground.light", "animal"], "isStructure":false, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"dog", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":21}}, "Map_Tile_24_0":{"terrain":"sea"}, "Map_Tile_19_2":{"terrain":"sea", "unit":{"merchantDiscountMultiplier":0.0, "merchantDiscounts":{}, "items":{}, "stunned":false, "damageTakenPercent":100, "grooveCharge":0, "underwater":false, "canBeAttackedFromDistance":true, "canChargeGroove":true, "startPos":{"facing":0, "x":19, "y":2}, "attackerId":-1, "attackerPlayerId":-1, "recruitDiscounts":{}, "tentacled":false, "pos":{"facing":0, "x":19, "y":2}, "hasBeenKilled":false, "recruits":{}, "transportedBy":-1, "setGroove":null, "miniGrooveId":"", "unitClassId":"water_city", "blessings":{}, "inTransport":false, "killedByLosing":false, "unitClass":{"isCommander":false, "weapons":{}, "cost":500, "weaponIds":{}, "inAir":false, "canAttack":true, "isDamagingParentUnit":false, "verbCostMultiplier":1.0, "canReinforce":true, "inWater":false, "isRecruitable":true, "isAttackable":true, "passiveMultiplier":1.0, "aliasId":"", "critConditionId":"", "movementType":"sea_building", "moveRange":0, "canBeCaptured":true, "transportTags":{}, "maxHealth":100, "canBeActivated":false, "resourceCost":1, "maxGroove":0, "tags":["structure"], "isStructure":true, "recruitingCostMultiplier":1.0, "loadCapacity":0, "id":"water_city", "reinforceMultiplier":1.0}, "factionOverride":"", "canBeAttacked":true, "grooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "attachedFlagId":-1, "garrisonClassId":"garrison", "itemId":"", "hadTurn":false, "setHealth":null, "state":{}, "itemDropNumber":0, "health":100, "playerId":0, "recruitDiscountMultiplier":0.0, "loadedUnits":{}, "id":11}}, "Map_Tile_23_12":{"terrain":"sea"}, "Map_Tile_13_4":{"terrain":"beach"}, "Map_Tile_20_9":{"terrain":"road"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"ocean"}, "Map_Tile_22_6":{"terrain":"road"}, "Map_Tile_21_4":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_22_8":{"terrain":"road"}, "Map_Tile_27_11":{"terrain":"road"}, "Map_Tile_9_6":{"terrain":"road"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_18_14":{"terrain":"ocean"}, "Map_Tile_6_3":{"terrain":"sea"}, "Map_Tile_16_2":{"terrain":"reef"}, "Map_Tile_20_6":{"terrain":"plains"}, "Map_Tile_27_13":{"terrain":"road"}, "Map_Tile_21_1":{"terrain":"sea"}, "Map_Tile_11_4":{"terrain":"beach"}, "Map_Tile_4_12":{"terrain":"sea"}, "Map_Tile_10_10":{"terrain":"beach"}, "Map_Tile_6_10":{"terrain":"beach"}, "Map_Tile_7_1":{"terrain":"ocean"}, "Map_Tile_6_11":{"terrain":"sea"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Tile_29_2":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"ocean"}, "Map_Tile_4_11":{"terrain":"sea"}, "Map_Tile_9_12":{"terrain":"sea"}, "Map_Tile_8_12":{"terrain":"reef"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_26_11":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_19_0":{"terrain":"ocean"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Doomed_Metropolis.json b/worlds/wargroove2/levels/Doomed_Metropolis.json new file mode 100644 index 000000000000..1e34ad1d5e19 --- /dev/null +++ b/worlds/wargroove2/levels/Doomed_Metropolis.json @@ -0,0 +1 @@ +{"Map_Tile_19_12":{"terrain":"forest"}, "Map_Tile_16_16":{"terrain":"forest"}, "Map_Tile_18_9":{"terrain":"abyss"}, "Map_Tile_13_14":{"terrain":"forest"}, "Map_Tile_6_6":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":6, "facing":0, "y":6}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":5, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":6, "facing":0, "y":6}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_2_13":{"terrain":"forest"}, "Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"hq", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":15, "facing":0, "y":4}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":16, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":15, "facing":0, "y":4}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":3000, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"hq", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_22_3":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"road"}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"abyss"}, "Map_Tile_8_12":{"terrain":"road"}, "Map_Tile_20_0":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":15, "facing":0, "y":10}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":9, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":15, "facing":0, "y":10}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"road"}, "Map_Tile_12_9":{"terrain":"abyss"}, "Map_Tile_19_0":{"terrain":"plains"}, "Triggers":[{"enabled":true, "id":"Export", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "actions":[{"enabled":true, "id":"ap_export", "parameters":["7834", "Doomed Metropolis", "Fly Sniper", "", "", "", "Win with standard conditions (Requires Mages and Knights)."]}], "recurring":"start_of_match"}, {"enabled":true, "id":"$trigger_default_defeat_no_units", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "recurring":"oncePerPlayer"}, {"enabled":true, "id":"$trigger_default_defeat_commander", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}], "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "recurring":"oncePerPlayer"}, {"enabled":true, "id":"$trigger_default_defeat_hq", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}], "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "recurring":"oncePerPlayer"}, {"enabled":true, "id":"$trigger_default_victory", "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}], "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}], "recurring":"oncePerPlayer"}, {"enabled":true, "id":"Player Victory (AP: Victory)", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["current"]}], "actions":[{"enabled":true, "id":"ap_victory", "parameters":{}}], "recurring":"once"}, {"enabled":true, "id":"Wall Despawn", "isIntro":false, "players":[0, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"start_of_turn", "parameters":{}}], "actions":[{"enabled":true, "id":"activate_flood", "parameters":["4", "abyss_bridge", "down", "", "0", "1", "1"]}], "recurring":"repeat"}, {"enabled":true, "id":"Wall Spawn", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"start_of_turn", "parameters":{}}], "actions":[{"enabled":true, "id":"unit_random_teleport", "parameters":["*unit_structure", "P2", "4", "3", "0"]}, {"enabled":true, "id":"activate_flood", "parameters":["4", "wall", "default", "", "0", "1", "1"]}], "recurring":"repeat"}, {"enabled":true, "id":"Init", "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "actions":[{"enabled":true, "id":"unit_random_teleport", "parameters":["*structure", "any", "0", "0", "1"]}, {"enabled":true, "id":"unit_random_teleport", "parameters":["*structure", "any", "1", "1", "1"]}, {"enabled":true, "id":"unit_random_teleport", "parameters":["*structure", "any", "2", "2", "1"]}, {"enabled":true, "id":"unit_random_teleport", "parameters":["*structure", "any", "3", "3", "1"]}], "recurring":"start_of_match"}], "Map_Tile_14_9":{"terrain":"abyss"}, "Map_Tile_19_3":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"road"}, "Map_Tile_22_11":{"terrain":"road"}, "Map_Tile_6_1":{"terrain":"road"}, "Map_Tile_6_2":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":6, "facing":0, "y":2}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":15, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":6, "facing":0, "y":2}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_1_3":{"terrain":"road"}, "Map_Tile_20_9":{"terrain":"abyss"}, "Map_Tile_19_5":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":19, "facing":0, "y":5}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":26, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":19, "facing":0, "y":5}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_12_8":{"terrain":"abyss"}, "Map_Tile_19_10":{"terrain":"forest"}, "Map_Tile_1_9":{"terrain":"abyss"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_20_1":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"abyss"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"wall"}, "Map_Tile_8_15":{"terrain":"road"}, "Map_Tile_9_6":{"terrain":"cobblestone"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_17_15":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":17, "facing":0, "y":15}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":10, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":17, "facing":0, "y":15}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_4_16":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"forest"}, "Map_Tile_4_1":{"terrain":"road"}, "Map_Tile_2_10":{"terrain":"abyss"}, "Map_Tile_1_2":{"terrain":"road"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_17_4":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_5_16":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"abyss"}, "Map_Tile_0_11":{"terrain":"abyss"}, "Map_Tile_0_8":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":0, "facing":0, "y":8}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":7, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":0, "facing":0, "y":8}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_1_16":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"wall"}, "Map_Tile_15_6":{"terrain":"forest_cut"}, "Map_Tile_20_4":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_21_15":{"terrain":"mountain"}, "Map_Tile_16_0":{"terrain":"forest_cut"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"knight", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":0, "facing":0, "y":14}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":28, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":0, "facing":0, "y":14}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"", "inTransport":false, "unitClass":{"weapons":[{"canCounterAttack":true, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "terrainExclusion":{}, "canAttackSubmerged":false, "id":"lance", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "blockedByEnemies":false, "minRange":1}], "cost":600, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.5, "critConditionId":"", "tags":["knight", "type.ground.heavy"], "resourceCost":2, "weaponIds":["lance"], "movementType":"riding", "transportTags":{}, "isStructure":false, "moveRange":6, "isCommander":false, "id":"knight", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_13_6":{"terrain":"forest"}, "Map_Tile_3_9":{"terrain":"abyss"}, "Map_Tile_15_9":{"terrain":"abyss"}, "Map_Tile_1_4":{"terrain":"road"}, "Map_Tile_16_9":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"abyss"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"abyss"}, "Map_Tile_1_13":{"terrain":"road"}, "Map_Tile_11_15":{"terrain":"road"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_20_13":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"commander_mercia", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":20, "facing":3, "y":13}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":3, "miniGrooveId":"", "grooveId":"heal_aura", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":20, "facing":3, "y":13}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"", "inTransport":false, "unitClass":{"weapons":[{"canCounterAttack":true, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "terrainExclusion":{}, "canAttackSubmerged":false, "id":"merciaSword", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "blockedByEnemies":false, "minRange":1}], "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":250, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["commander", "type.ground.light"], "resourceCost":3, "weaponIds":["merciaSword"], "movementType":"walking", "transportTags":{}, "isStructure":false, "moveRange":4, "isCommander":true, "id":"commander_mercia", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":false, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_1_8":{"terrain":"road"}, "Map_Tile_20_14":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"abyss"}, "Map_Tile_10_13":{"terrain":"road"}, "Map_Tile_5_6":{"terrain":"cobblestone"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"wall"}, "Map_Tile_3_16":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":3, "facing":0, "y":16}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":11, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":3, "facing":0, "y":16}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_3_14":{"terrain":"road"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"road"}, "Map_Tile_22_14":{"terrain":"plains"}, "Map_Tile_18_5":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_5_15":{"terrain":"road"}, "Map_Tile_20_8":{"terrain":"abyss"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"abyss"}, "Map_Tile_18_11":{"terrain":"road"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"road"}, "Map_Tile_4_8":{"terrain":"road"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_6_16":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"forest_cut"}, "Map_Tile_2_1":{"terrain":"road"}, "Map_Tile_1_12":{"terrain":"road"}, "Map_Tile_6_15":{"terrain":"road"}, "Map_Tile_11_5":{"terrain":"forest"}, "Map_Tile_7_15":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"road"}, "Map_Tile_20_16":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"hq", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":5, "facing":0, "y":14}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":13, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":5, "facing":0, "y":14}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":3000, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"hq", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Flags":{}, "Map_Tile_1_0":{"terrain":"road"}, "Map_Tile_14_4":{"terrain":"plains"}, "Counters":{}, "Map_Tile_0_9":{"terrain":"abyss"}, "Map_Tile_22_2":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_6_13":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":6, "facing":1, "y":13}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":29, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":6, "facing":1, "y":13}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_22_16":{"terrain":"mountain"}, "Map_Tile_20_15":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"abyss"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"road"}, "Map_Tile_22_15":{"terrain":"mountain"}, "Map_Tile_22_13":{"terrain":"plains"}, "Map_Tile_21_2":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"barracks", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":21, "facing":0, "y":2}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":1, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":21, "facing":0, "y":2}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"barracks", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_21_13":{"terrain":"plains"}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"road"}, "Map_Tile_22_10":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"fortified_city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":22, "facing":0, "y":10}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":12, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":22, "facing":0, "y":10}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"fortified_garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":1000, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["fortified_city"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"fortified_city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_22_9":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"abyss"}, "Map_Tile_10_14":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":10, "facing":0, "y":14}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":21, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":10, "facing":0, "y":14}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_22_7":{"terrain":"abyss"}, "Map_Tile_6_4":{"terrain":"road"}, "Map_Tile_22_6":{"terrain":"plains"}, "Map_Tile_2_15":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"soldier", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":2, "facing":0, "y":15}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":20, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":2, "facing":0, "y":15}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"", "inTransport":false, "unitClass":{"weapons":[{"canCounterAttack":true, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "terrainExclusion":{}, "canAttackSubmerged":false, "id":"sword", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "blockedByEnemies":false, "minRange":1}], "cost":100, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.5, "critConditionId":"", "tags":["soldier", "type.ground.light"], "resourceCost":1, "weaponIds":["sword"], "movementType":"walking", "transportTags":{}, "isStructure":false, "moveRange":4, "isCommander":false, "id":"soldier", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_7_14":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"hideout_ap", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":7, "facing":0, "y":14}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":24, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":7, "facing":0, "y":14}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"hideout_ap", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":["thief", "rifleman"], "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_3_10":{"terrain":"abyss"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_14_14":{"terrain":"mountain"}, "Map_Tile_13_7":{"terrain":"abyss"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"road"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_22_4":{"terrain":"plains"}, "Locations":{"1":{"positions":[{"x":15, "y":10}, {"x":15, "y":12}, {"x":15, "y":13}, {"x":15, "y":14}, {"x":15, "y":15}, {"x":15, "y":16}, {"x":16, "y":16}, {"x":17, "y":16}, {"x":17, "y":15}, {"x":17, "y":14}, {"x":17, "y":13}, {"x":17, "y":12}, {"x":16, "y":12}, {"x":16, "y":13}, {"x":16, "y":14}, {"x":16, "y":15}, {"x":14, "y":12}, {"x":14, "y":13}, {"x":14, "y":15}, {"x":12, "y":11}, {"x":13, "y":10}, {"x":14, "y":10}, {"x":12, "y":12}, {"x":12, "y":14}, {"x":18, "y":15}, {"x":18, "y":16}, {"x":18, "y":14}, {"x":18, "y":12}, {"x":18, "y":10}, {"x":17, "y":10}, {"x":16, "y":10}, {"x":12, "y":10}], "centre":{"x":15, "y":13}, "id":1, "getArea":null, "setArea":null, "name":"Neutral Shuffle 1", "interactable":false}, "2":{"positions":[{"x":10, "y":14}, {"x":10, "y":11}, {"x":9, "y":11}, {"x":9, "y":12}, {"x":8, "y":12}, {"x":6, "y":13}, {"x":3, "y":16}, {"x":3, "y":15}, {"x":5, "y":12}, {"x":6, "y":12}, {"x":5, "y":16}, {"x":6, "y":16}, {"x":7, "y":16}, {"x":8, "y":16}, {"x":4, "y":16}, {"x":10, "y":16}, {"x":11, "y":16}, {"x":3, "y":13}, {"x":3, "y":12}, {"x":10, "y":12}, {"x":8, "y":11}, {"x":6, "y":15}, {"x":8, "y":15}, {"x":9, "y":13}, {"x":4, "y":13}, {"x":4, "y":15}, {"x":3, "y":14}], "centre":{"x":7, "y":14}, "id":2, "getArea":null, "setArea":null, "name":"Neutral Shuffle 2", "interactable":false}, "3":{"positions":[{"x":9, "y":6}, {"x":8, "y":6}, {"x":6, "y":6}, {"x":5, "y":6}, {"x":3, "y":6}, {"x":0, "y":6}, {"x":0, "y":8}, {"x":3, "y":8}, {"x":2, "y":8}, {"x":9, "y":5}, {"x":8, "y":5}, {"x":6, "y":5}, {"x":5, "y":5}, {"x":3, "y":5}, {"x":2, "y":5}, {"x":2, "y":6}, {"x":0, "y":5}, {"x":0, "y":3}, {"x":0, "y":2}, {"x":3, "y":0}, {"x":2, "y":0}, {"x":0, "y":0}, {"x":5, "y":0}, {"x":6, "y":0}, {"x":8, "y":0}, {"x":9, "y":0}, {"x":9, "y":2}, {"x":9, "y":3}, {"x":8, "y":3}, {"x":8, "y":2}, {"x":6, "y":3}, {"x":6, "y":2}, {"x":5, "y":2}, {"x":5, "y":3}, {"x":3, "y":3}], "centre":{"x":5, "y":4}, "id":3, "getArea":null, "setArea":null, "name":"P2 Shuffle 1", "interactable":false}, "4":{"positions":[{"x":10, "y":6}, {"x":10, "y":5}, {"x":10, "y":4}, {"x":10, "y":3}, {"x":10, "y":2}, {"x":10, "y":1}, {"x":10, "y":0}], "centre":{"x":10, "y":3}, "id":4, "getArea":null, "setArea":null, "name":"Wall Spawner", "interactable":false}, "0":{"positions":[{"x":20, "y":6}, {"x":20, "y":5}, {"x":19, "y":5}, {"x":18, "y":5}, {"x":17, "y":5}, {"x":17, "y":6}, {"x":18, "y":6}, {"x":19, "y":6}, {"x":16, "y":6}, {"x":16, "y":5}, {"x":16, "y":4}, {"x":16, "y":3}, {"x":16, "y":2}, {"x":16, "y":1}, {"x":17, "y":1}, {"x":17, "y":0}, {"x":18, "y":0}, {"x":19, "y":0}, {"x":19, "y":1}, {"x":20, "y":1}, {"x":20, "y":2}, {"x":19, "y":3}, {"x":19, "y":4}, {"x":18, "y":4}, {"x":17, "y":3}, {"x":17, "y":2}, {"x":18, "y":2}, {"x":19, "y":2}, {"x":20, "y":4}, {"x":20, "y":3}, {"x":18, "y":1}, {"x":18, "y":3}, {"x":16, "y":0}, {"x":20, "y":0}, {"x":17, "y":4}], "centre":{"x":18, "y":3}, "id":0, "getArea":null, "setArea":null, "name":"P1 Shuffle 1", "interactable":false}}, "Map_Tile_22_1":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"road"}, "Map_Tile_12_7":{"terrain":"abyss"}, "Map_Tile_16_8":{"terrain":"abyss"}, "Map_Tile_21_16":{"terrain":"mountain"}, "Map_Tile_7_6":{"terrain":"road"}, "Player_2":{"recruit_rifleman":true, "recruit_witch":true, "recruit_dog":true, "recruit_ballista":true, "recruit_mage":true, "recruit_trebuchet":true, "recruit_merman":true, "recruit_griffin_walking":true, "recruit_soldier":true, "recruit_thief":true, "recruit_kraken":true, "recruit_warship":true, "recruit_dragon":true, "recruit_travelboat":true, "team":1, "recruit_frog":true, "recruit_archer":true, "recruit_turtle":true, "recruit_harpoonship":true, "recruit_giant":true, "recruit_knight":true, "recruit_balloon":true, "recruit_spearman":true, "recruit_caravel":true, "gold":0, "recruit_harpy":true, "recruit_wagon":true}, "Map_Tile_13_16":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"tower", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":13, "facing":0, "y":16}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":25, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":13, "facing":0, "y":16}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"tower", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_3_4":{"terrain":"road"}, "Map_Tile_12_11":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":12, "facing":0, "y":11}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":27, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":12, "facing":0, "y":11}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_21_14":{"terrain":"plains"}, "Map_Tile_21_12":{"terrain":"forest"}, "Map_Tile_21_11":{"terrain":"road"}, "Map_Tile_9_11":{"terrain":"road"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_21_9":{"terrain":"abyss"}, "Map_Tile_11_13":{"terrain":"road"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"forest"}, "Map_Tile_21_7":{"terrain":"abyss"}, "Map_Tile_1_5":{"terrain":"road"}, "Map_Tile_11_14":{"terrain":"road"}, "Map_Tile_17_3":{"terrain":"forest_cut"}, "Map_Tile_21_6":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"soldier", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":3, "facing":0, "y":2}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":19, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":3, "facing":0, "y":2}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"", "inTransport":false, "unitClass":{"weapons":[{"canCounterAttack":true, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "terrainExclusion":{}, "canAttackSubmerged":false, "id":"sword", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "blockedByEnemies":false, "minRange":1}], "cost":100, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.5, "critConditionId":"", "tags":["soldier", "type.ground.light"], "resourceCost":1, "weaponIds":["sword"], "movementType":"walking", "transportTags":{}, "isStructure":false, "moveRange":4, "isCommander":false, "id":"soldier", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_21_5":{"terrain":"forest"}, "Objectives":["Win with standard conditions (Requires Mages and Knights)."], "Map_Tile_21_4":{"terrain":"plains"}, "Map_Tile_4_15":{"terrain":"road"}, "Map_Tile_21_3":{"terrain":"forest"}, "Map_Tile_9_3":{"terrain":"cobblestone"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"abyss"}, "Map_Tile_12_10":{"terrain":"forest"}, "Map_Tile_1_14":{"terrain":"road"}, "Map_Tile_7_8":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"road"}, "Map_Tile_22_12":{"terrain":"forest"}, "Map_Tile_21_1":{"terrain":"mountain"}, "Map_Tile_9_9":{"terrain":"abyss"}, "Map_Tile_21_0":{"terrain":"forest"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_20_12":{"terrain":"forest"}, "Author":"Fly Sniper", "Map_Tile_20_11":{"terrain":"road"}, "Map_Tile_9_14":{"terrain":"forest"}, "Map_Tile_0_12":{"terrain":"road", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"giant", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":0, "facing":0, "y":12}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":17, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":0, "facing":0, "y":12}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"", "inTransport":false, "unitClass":{"weapons":[{"canCounterAttack":true, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "terrainExclusion":{}, "canAttackSubmerged":false, "id":"giantSlam", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "blockedByEnemies":false, "minRange":1}], "cost":1200, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":2.5, "critConditionId":"", "tags":["giant", "type.ground.heavy", "tall"], "resourceCost":3, "weaponIds":["giantSlam"], "movementType":"riding", "transportTags":{}, "isStructure":false, "moveRange":5, "isCommander":false, "id":"giant", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_19_13":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"barracks", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":19, "facing":0, "y":13}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":2, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":19, "facing":0, "y":13}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"barracks", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_20_10":{"terrain":"forest"}, "Map_Tile_13_1":{"terrain":"forest"}, "Map_Tile_20_7":{"terrain":"abyss"}, "Map_Tile_8_8":{"terrain":"abyss"}, "Map_Tile_20_6":{"terrain":"plains"}, "Map_Tile_20_5":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"plains"}, "Map_Tile_20_2":{"terrain":"forest"}, "Map_Tile_2_16":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"road"}, "Map_Tile_1_15":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"barracks", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":1, "facing":0, "y":15}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":4, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":1, "facing":0, "y":15}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"barracks", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_19_11":{"terrain":"road"}, "Map_Tile_10_1":{"terrain":"wall"}, "Map_Tile_19_14":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_19_15":{"terrain":"mountain"}, "Map_Tile_19_9":{"terrain":"abyss"}, "Map_Tile_19_7":{"terrain":"abyss"}, "Map_Tile_11_9":{"terrain":"abyss"}, "Map_Tile_16_3":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":16, "facing":1, "y":3}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":30, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":16, "facing":1, "y":3}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"abyss"}, "Map_Tile_19_4":{"terrain":"forest_cut"}, "Map_Tile_15_13":{"terrain":"forest"}, "Map_Tile_19_1":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"abyss"}, "Map_Tile_8_13":{"terrain":"road"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"road"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_17_0":{"terrain":"plains"}, "Map_Tile_18_13":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"forest"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_18_7":{"terrain":"abyss"}, "Map_Tile_2_7":{"terrain":"road"}, "Map_Tile_14_2":{"terrain":"mountain"}, "Map_Tile_18_4":{"terrain":"forest"}, "Map_Tile_4_7":{"terrain":"road"}, "Map_Tile_9_0":{"terrain":"cobblestone"}, "Map_Tile_0_10":{"terrain":"abyss"}, "Map_Tile_18_2":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"road"}, "Map_Tile_13_11":{"terrain":"road"}, "Map_Tile_18_0":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"plains"}, "Map_Tile_17_16":{"terrain":"plains"}, "Map_Tile_17_14":{"terrain":"forest"}, "Map_Tile_17_11":{"terrain":"road"}, "Map_Tile_12_13":{"terrain":"road"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"road"}, "Map_Name":"Doomed Metropolis", "Map_Tile_17_9":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"abyss"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"abyss"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_17_6":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"abyss"}, "Map_Tile_15_11":{"terrain":"road"}, "Map_Tile_2_9":{"terrain":"abyss"}, "Map_Tile_17_2":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"road"}, "Map_Tile_16_2":{"terrain":"forest_cut"}, "Map_Tile_17_1":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"cobblestone"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"road"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"abyss"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"road"}, "Map_Tile_10_9":{"terrain":"abyss_bridge"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"wall"}, "Map_Tile_1_6":{"terrain":"road"}, "Map_Tile_19_2":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"forest"}, "Map_Tile_15_8":{"terrain":"abyss"}, "Map_Tile_13_12":{"terrain":"road"}, "Map_Tile_16_13":{"terrain":"forest"}, "Map_Tile_15_5":{"terrain":"forest"}, "Map_Tile_15_1":{"terrain":"forest"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_14_16":{"terrain":"forest"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Size":{"x":23, "y":17}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"abyss"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"road"}, "Map_Tile_16_11":{"terrain":"road"}, "Map_Tile_12_4":{"terrain":"forest"}, "Map_Tile_22_0":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"road"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"abyss"}, "Map_Tile_1_7":{"terrain":"road"}, "Map_Tile_14_7":{"terrain":"abyss"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"barracks", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":2, "facing":0, "y":2}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":14, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":2, "facing":0, "y":2}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"barracks", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_10_0":{"terrain":"wall"}, "Map_Tile_9_8":{"terrain":"abyss"}, "Map_Tile_0_0":{"terrain":"cobblestone", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"giant", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":0, "facing":0, "y":0}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":18, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":0, "facing":0, "y":0}, "state":{}, "health":30, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"", "inTransport":false, "unitClass":{"weapons":[{"canCounterAttack":true, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "terrainExclusion":{}, "canAttackSubmerged":false, "id":"giantSlam", "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "blockedByEnemies":false, "minRange":1}], "cost":1200, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":false, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":2.5, "critConditionId":"", "tags":["giant", "type.ground.heavy", "tall"], "resourceCost":3, "weaponIds":["giantSlam"], "movementType":"riding", "transportTags":{}, "isStructure":false, "moveRange":5, "isCommander":false, "id":"giant", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":false}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_7_4":{"terrain":"road"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"road"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"abyss"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"road"}, "Map_Tile_6_9":{"terrain":"abyss"}, "Map_Tile_3_15":{"terrain":"plains"}, "Map_Tile_21_10":{"terrain":"forest"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":8, "facing":0, "y":2}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":6, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":8, "facing":0, "y":2}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_9_16":{"terrain":"road"}, "Map_Tile_3_5":{"terrain":"plains"}, "Player_1":{"recruit_rifleman":true, "recruit_witch":true, "recruit_dog":true, "recruit_ballista":true, "recruit_mage":true, "recruit_trebuchet":true, "recruit_merman":true, "recruit_griffin_walking":true, "recruit_soldier":true, "recruit_thief":true, "recruit_kraken":true, "recruit_warship":true, "recruit_dragon":true, "recruit_travelboat":true, "team":0, "recruit_frog":true, "recruit_archer":true, "recruit_turtle":true, "recruit_harpoonship":true, "recruit_giant":true, "recruit_knight":true, "recruit_balloon":true, "recruit_spearman":true, "recruit_caravel":true, "gold":100, "recruit_harpy":true, "recruit_wagon":true}, "Map_Tile_9_15":{"terrain":"road"}, "Player_Count":2, "Map_Tile_5_3":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":5, "facing":0, "y":3}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":22, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":5, "facing":0, "y":3}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_19_16":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"road"}, "Map_Tile_8_9":{"terrain":"abyss"}, "Map_Tile_18_1":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":18, "facing":0, "y":1}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":8, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":18, "facing":0, "y":1}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"road"}, "Map_Tile_0_15":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"abyss"}, "Map_Tile_10_7":{"terrain":"wall"}, "Map_Tile_17_12":{"terrain":"forest"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"cobblestone"}, "Map_Tile_8_1":{"terrain":"road"}, "Map_Tile_12_15":{"terrain":"forest"}, "Map_Tile_5_7":{"terrain":"road"}, "Map_Tile_10_10":{"terrain":"abyss_bridge"}, "Map_Tile_19_6":{"terrain":"plains"}, "Map_Tile_22_5":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"road"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"cobblestone"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"cobblestone"}, "Map_Tile_15_15":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains", "unit":{"merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "unitClassId":"city", "setHealth":null, "attackerId":-1, "underwater":false, "setGroove":null, "pos":{"x":5, "facing":0, "y":5}, "tentacled":false, "factionOverride":"", "attackerPlayerId":-1, "id":23, "miniGrooveId":"", "grooveId":"", "blessings":{}, "hadTurn":false, "attackerUnitClass":"", "killedByLosing":false, "damageTakenPercent":100, "merchantDiscounts":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "startPos":{"x":5, "facing":0, "y":5}, "state":{}, "health":100, "items":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "itemId":"", "playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "garrisonClassId":"garrison", "inTransport":false, "unitClass":{"weapons":{}, "cost":500, "verbCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100, "canBeActivated":false, "inAir":false, "isDamagingParentUnit":false, "canReinforce":true, "aliasId":"", "maxGroove":0, "loadCapacity":0, "isAttackable":true, "passiveMultiplier":1.0, "critConditionId":"", "tags":["structure"], "resourceCost":1, "weaponIds":{}, "movementType":"land_building", "transportTags":{}, "isStructure":true, "moveRange":0, "isCommander":false, "id":"city", "canAttack":true, "recruitingCostMultiplier":1.0, "isRecruitable":true, "inWater":false, "canBeCaptured":true}, "grooveCharge":0, "canChargeGroove":true, "stunned":false, "recruits":{}, "transportedBy":-1, "loadedUnits":{}}}, "Map_Tile_7_0":{"terrain":"road"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"cobblestone"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"road"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_21_8":{"terrain":"abyss"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_11_0":{"terrain":"forest"}, "Map_Tile_9_7":{"terrain":"cobblestone"}, "Map_Tile_4_14":{"terrain":"road"}, "Map_Tile_10_15":{"terrain":"road"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"road"}, "Map_Tile_10_11":{"terrain":"road"}, "Map_Tile_14_11":{"terrain":"road"}, "Map_Tile_10_8":{"terrain":"abyss_bridge"}, "Map_Tile_10_5":{"terrain":"wall"}, "Map_Tile_8_11":{"terrain":"road"}, "Map_Tile_2_14":{"terrain":"road"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"road"}, "Map_Tile_3_11":{"terrain":"abyss"}, "Map_Tile_5_11":{"terrain":"abyss"}, "Map_Tile_9_1":{"terrain":"cobblestone"}, "Map_Tile_4_5":{"terrain":"road"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"cobblestone"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Enmity_Cliffs.json b/worlds/wargroove2/levels/Enmity_Cliffs.json new file mode 100644 index 000000000000..5b083b7a331f --- /dev/null +++ b/worlds/wargroove2/levels/Enmity_Cliffs.json @@ -0,0 +1 @@ +{"Map_Tile_14_12":{"terrain":"forest"}, "Map_Tile_0_6":{"terrain":"forest"}, "Map_Tile_6_0":{"terrain":"abyss"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains", "item":{"itemId":33, "isConsumable":false, "type":"hardened_armor", "pos":{"y":9, "x":5}, "unitTypeRestriction":{}}}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_4_14":{"terrain":"forest"}, "Map_Tile_11_14":{"terrain":"abyss"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"mountain"}, "Map_Tile_9_14":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":14, "facing":0, "x":9}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":14, "facing":0, "x":9}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":14, "grooveId":""}, "terrain":"plains"}, "Map_Tile_2_10":{"terrain":"forest"}, "Map_Tile_5_0":{"terrain":"road"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"abyss"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Locations":{"1":{"setArea":null, "name":"Main Island", "positions":[{"y":5, "x":5}, {"y":5, "x":4}, {"y":5, "x":6}, {"y":5, "x":7}, {"y":5, "x":8}, {"y":6, "x":8}, {"y":6, "x":7}, {"y":6, "x":6}, {"y":6, "x":5}, {"y":6, "x":4}, {"y":7, "x":4}, {"y":7, "x":5}, {"y":7, "x":6}, {"y":7, "x":7}, {"y":7, "x":8}, {"y":8, "x":8}, {"y":8, "x":7}, {"y":8, "x":6}, {"y":8, "x":5}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":9, "x":7}, {"y":9, "x":8}, {"y":9, "x":9}, {"y":9, "x":10}, {"y":9, "x":11}], "getArea":null, "centre":{"y":7, "x":7}, "id":1, "interactable":false}, "2":{"setArea":null, "name":"Allied Shuffle", "positions":[{"y":7, "x":6}, {"y":7, "x":7}, {"y":6, "x":4}, {"y":6, "x":8}, {"y":8, "x":8}, {"y":5, "x":4}, {"y":5, "x":8}, {"y":9, "x":5}, {"y":9, "x":6}, {"y":8, "x":6}, {"y":7, "x":5}, {"y":6, "x":5}, {"y":6, "x":7}, {"y":8, "x":7}], "getArea":null, "centre":{"y":7, "x":6}, "id":2, "interactable":false}, "3":{"setArea":null, "name":"Northern Structure Shuffle", "positions":[{"y":1, "x":5}, {"y":3, "x":1}, {"y":3, "x":8}, {"y":3, "x":5}, {"y":2, "x":3}, {"y":3, "x":9}], "getArea":null, "centre":{"y":3, "x":5}, "id":3, "interactable":false}, "4":{"setArea":null, "name":"Southwestern Structure Shuffle", "positions":[{"y":10, "x":0}, {"y":7, "x":1}, {"y":9, "x":3}, {"y":12, "x":8}, {"y":14, "x":7}, {"y":14, "x":9}], "getArea":null, "centre":{"y":11, "x":5}, "id":4, "interactable":false}, "5":{"setArea":null, "name":"Eastern Structure Shuffle", "positions":[{"y":5, "x":14}, {"y":5, "x":13}, {"y":6, "x":13}, {"y":6, "x":12}, {"y":7, "x":12}, {"y":7, "x":11}, {"y":7, "x":13}, {"y":6, "x":14}], "getArea":null, "centre":{"y":6, "x":13}, "id":5, "interactable":false}, "6":{"setArea":null, "name":"Enemy Territory", "positions":[{"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":1, "x":1}, {"y":2, "x":1}, {"y":3, "x":1}, {"y":3, "x":0}, {"y":2, "x":0}, {"y":1, "x":2}, {"y":1, "x":3}, {"y":2, "x":2}, {"y":2, "x":3}, {"y":3, "x":3}, {"y":3, "x":2}, {"y":4, "x":1}, {"y":4, "x":2}, {"y":4, "x":10}, {"y":4, "x":11}, {"y":4, "x":12}, {"y":4, "x":13}, {"y":4, "x":14}, {"y":3, "x":14}, {"y":3, "x":13}, {"y":3, "x":12}, {"y":3, "x":11}, {"y":3, "x":10}, {"y":3, "x":9}, {"y":3, "x":8}, {"y":3, "x":7}, {"y":3, "x":6}, {"y":3, "x":5}, {"y":3, "x":4}, {"y":4, "x":0}, {"y":2, "x":4}, {"y":2, "x":5}, {"y":2, "x":6}, {"y":2, "x":7}, {"y":2, "x":8}, {"y":2, "x":9}, {"y":2, "x":10}, {"y":2, "x":11}, {"y":2, "x":12}, {"y":2, "x":13}, {"y":2, "x":14}, {"y":1, "x":13}, {"y":1, "x":12}, {"y":1, "x":11}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":0, "x":6}, {"y":0, "x":5}, {"y":0, "x":4}, {"y":0, "x":3}, {"y":0, "x":2}, {"y":1, "x":4}, {"y":1, "x":5}, {"y":1, "x":6}, {"y":0, "x":7}, {"y":0, "x":8}, {"y":0, "x":9}, {"y":0, "x":10}, {"y":0, "x":11}, {"y":0, "x":12}, {"y":0, "x":13}, {"y":0, "x":14}, {"y":1, "x":14}, {"y":5, "x":1}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":6, "x":1}, {"y":7, "x":2}, {"y":7, "x":1}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":8, "x":1}, {"y":5, "x":2}, {"y":6, "x":2}, {"y":5, "x":10}, {"y":5, "x":11}, {"y":5, "x":12}, {"y":5, "x":13}, {"y":5, "x":14}, {"y":6, "x":14}, {"y":7, "x":14}, {"y":7, "x":13}, {"y":6, "x":13}, {"y":6, "x":12}, {"y":6, "x":11}, {"y":6, "x":10}, {"y":7, "x":10}, {"y":7, "x":11}, {"y":7, "x":12}, {"y":11, "x":13}, {"y":11, "x":12}, {"y":11, "x":11}, {"y":12, "x":11}, {"y":12, "x":12}, {"y":13, "x":12}, {"y":13, "x":13}, {"y":14, "x":13}, {"y":14, "x":14}, {"y":13, "x":14}, {"y":12, "x":14}, {"y":11, "x":14}, {"y":10, "x":14}, {"y":10, "x":13}, {"y":9, "x":13}, {"y":8, "x":13}, {"y":9, "x":14}, {"y":8, "x":14}, {"y":12, "x":13}, {"y":14, "x":12}, {"y":14, "x":11}, {"y":13, "x":11}, {"y":13, "x":10}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":11, "x":10}, {"y":11, "x":9}, {"y":11, "x":8}, {"y":11, "x":7}, {"y":11, "x":6}, {"y":11, "x":5}, {"y":11, "x":4}, {"y":11, "x":3}, {"y":10, "x":3}, {"y":9, "x":3}, {"y":9, "x":2}, {"y":8, "x":2}, {"y":9, "x":1}, {"y":9, "x":0}, {"y":10, "x":1}, {"y":11, "x":1}, {"y":12, "x":1}, {"y":13, "x":1}, {"y":14, "x":1}, {"y":14, "x":0}, {"y":13, "x":0}, {"y":12, "x":0}, {"y":11, "x":0}, {"y":10, "x":0}, {"y":11, "x":2}, {"y":12, "x":2}, {"y":13, "x":2}, {"y":14, "x":2}, {"y":10, "x":2}, {"y":12, "x":3}, {"y":12, "x":4}, {"y":13, "x":4}, {"y":13, "x":5}, {"y":12, "x":5}, {"y":12, "x":6}, {"y":12, "x":7}, {"y":12, "x":8}, {"y":14, "x":10}, {"y":14, "x":9}, {"y":14, "x":8}, {"y":14, "x":7}, {"y":14, "x":6}, {"y":14, "x":5}, {"y":14, "x":4}, {"y":14, "x":3}, {"y":13, "x":3}, {"y":13, "x":6}, {"y":13, "x":7}, {"y":13, "x":8}, {"y":13, "x":9}], "getArea":null, "centre":{"y":7, "x":7}, "id":6, "interactable":false}, "0":{"setArea":null, "name":"Bridge Spawn", "positions":[{"y":6, "x":9}, {"y":4, "x":12}, {"y":4, "x":6}, {"y":7, "x":3}, {"y":10, "x":8}, {"y":9, "x":12}, {"y":13, "x":6}, {"y":2, "x":2}], "getArea":null, "centre":{"y":7, "x":7}, "id":0, "interactable":false}}, "Map_Tile_4_6":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":6, "facing":0, "x":4}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":6, "facing":0, "x":4}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":18, "grooveId":""}, "terrain":"plains"}, "Map_Tile_7_8":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":8, "facing":3, "x":7}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":8, "facing":3, "x":7}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "canCounterAttack":true, "minRange":1, "id":"sword", "canAttackSubmerged":false}], "cost":100, "maxGroove":0, "inAir":false, "passiveMultiplier":1.5, "isStructure":false, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":["sword"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"walking", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":4, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"soldier", "tags":["soldier", "type.ground.light"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"soldier", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":28, "grooveId":""}, "terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Triggers":[{"enabled":true, "conditions":{}, "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"Export (Always on Top)", "actions":[{"enabled":true, "id":"ap_export", "parameters":["1"]}]}, {"enabled":true, "conditions":{}, "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Set AI", "actions":{}}, {"enabled":true, "conditions":{}, "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "id":"Shuffle Units", "actions":[{"enabled":true, "id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"ap_has_item", "parameters":["252023", "0", "0"]}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Bridges Not Ready", "actions":[{"enabled":true, "id":"dialogue_box_simple", "parameters":["neutral", "janjak", "No can do, mate; we're still working on these bridges. Come back later.", "0", ""]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"ap_has_item", "parameters":["252023", "1", "0"]}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Spawn Bridges", "actions":[{"enabled":true, "id":"dialogue_box_simple", "parameters":["happy", "janjak", "We've finished construction on the bridges!", "0", ""]}, {"enabled":true, "id":"activate_flood", "parameters":["0", "abyss_bridge", "default", "", "100", "0", "0"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}], "isIntro":false, "players":[1, 1, 1, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}], "isIntro":false, "players":[1, 1, 1, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}], "isIntro":false, "players":[1, 1, 1, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "1"]}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "id":"Victory (Player, One Opponents Left)", "actions":[{"enabled":true, "id":"victory", "parameters":["current"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "4", "4", "spearman", "-1"]}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Deploy 4 Spearmen (Check 253321)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253321"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"player_turn", "parameters":["current"]}, {"enabled":true, "id":"location_compare", "parameters":["-2", "overlaps", "1"]}, {"enabled":true, "id":"location_compare", "parameters":["-3", "overlaps", "6"]}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"Attack Enemy From Across Chasm (Check 253322)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253322"]}]}, {"enabled":true, "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["current"]}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "id":"P1 Wins (Check 253320)", "actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253320"]}]}], "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"abyss"}, "Map_Tile_13_2":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":2, "facing":0, "x":13}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":2, "facing":0, "x":13}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":3000, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"hq", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"hq", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":5, "grooveId":""}, "terrain":"plains"}, "Counters":{}, "Map_Tile_14_14":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":14, "facing":3, "x":14}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":2, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":14, "facing":3, "x":14}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "canCounterAttack":true, "minRange":1, "id":"lance", "canAttackSubmerged":false}], "cost":600, "maxGroove":0, "inAir":false, "passiveMultiplier":1.5, "isStructure":false, "resourceCost":2, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":["lance"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"riding", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":6, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"knight", "tags":["knight", "type.ground.heavy"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"knight", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":26, "grooveId":""}, "terrain":"forest"}, "Map_Tile_12_1":{"terrain":"road"}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_9_1":{"unit":{"recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":1, "facing":0, "x":9}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":1, "facing":0, "x":9}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"barracks", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"barracks", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":4, "grooveId":""}, "terrain":"road"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"forest"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_1_13":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":13, "facing":0, "x":1}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":2, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":13, "facing":0, "x":1}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "canCounterAttack":true, "minRange":1, "id":"greenfingerAttack", "canAttackSubmerged":false}], "cost":500, "maxGroove":250, "inAir":false, "passiveMultiplier":1.0, "isStructure":false, "resourceCost":3, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":["greenfingerAttack"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"walking", "isRecruitable":false, "critConditionId":"", "aliasId":"", "moveRange":4, "isCommander":true, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"commander_greenfinger", "tags":["commander", "type.ground.light"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"commander_greenfinger", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":25, "grooveId":"vine_wall"}, "terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_0_1":{"unit":{"recruits":["thief", "rifleman"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":1, "facing":0, "x":0}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":1, "facing":0, "x":0}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"hideout", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"hideout", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":1, "grooveId":""}, "terrain":"plains"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"abyss"}, "Map_Tile_14_7":{"terrain":"plains", "item":{"itemId":32, "isConsumable":false, "type":"heavy_armor", "pos":{"y":7, "x":14}, "unitTypeRestriction":{}}}, "Map_Tile_4_9":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"forest"}, "Map_Tile_6_14":{"terrain":"abyss"}, "Map_Tile_13_4":{"terrain":"abyss"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_14_5":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":5, "facing":0, "x":14}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":5, "facing":0, "x":14}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":9, "grooveId":""}, "terrain":"plains"}, "Map_Tile_9_0":{"terrain":"abyss"}, "Map_Tile_8_1":{"terrain":"road"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Player_1":{"recruit_mage":true, "recruit_balloon":true, "recruit_dragon":true, "recruit_knight":true, "recruit_archer":true, "recruit_frog":true, "recruit_witch":true, "recruit_turtle":true, "recruit_giant":true, "recruit_soldier":true, "recruit_dog":true, "recruit_trebuchet":true, "recruit_spearman":true, "recruit_merman":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_caravel":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_travelboat":true, "recruit_warship":true, "gold":100, "recruit_wagon":true, "recruit_kraken":true, "team":0, "recruit_harpy":true}, "Map_Tile_0_7":{"terrain":"forest"}, "Map_Tile_4_0":{"terrain":"mountain"}, "Map_Tile_7_11":{"terrain":"forest_cut"}, "Map_Tile_4_8":{"terrain":"abyss"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"forest"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_6_8":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":8, "facing":0, "x":6}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":8, "facing":0, "x":6}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "canCounterAttack":true, "minRange":1, "id":"merciaSword", "canAttackSubmerged":false}], "cost":500, "maxGroove":250, "inAir":false, "passiveMultiplier":1.0, "isStructure":false, "resourceCost":3, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":["merciaSword"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"walking", "isRecruitable":false, "critConditionId":"", "aliasId":"", "moveRange":4, "isCommander":true, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"commander_mercia", "tags":["commander", "type.ground.light"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"commander_mercia", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":27, "grooveId":"heal_aura"}, "terrain":"plains"}, "Map_Tile_13_8":{"terrain":"abyss"}, "Map_Size":{"y":15, "x":15}, "Map_Tile_8_12":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":12, "facing":0, "x":8}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":12, "facing":0, "x":8}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":13, "grooveId":""}, "terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Author":"Magnemania", "Player_2":{"recruit_mage":true, "recruit_balloon":true, "recruit_dragon":true, "recruit_knight":true, "recruit_archer":true, "recruit_frog":true, "recruit_witch":true, "recruit_turtle":true, "recruit_giant":true, "recruit_soldier":true, "recruit_dog":true, "recruit_trebuchet":true, "recruit_spearman":true, "recruit_merman":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_caravel":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_travelboat":true, "recruit_warship":true, "gold":100, "recruit_wagon":true, "recruit_kraken":true, "team":1, "recruit_harpy":true}, "Map_Tile_1_2":{"terrain":"plains"}, "Objectives":["Have 4 Spearmen deployed at once (Requires Spearman).", "Hit an enemy across the chasm from your main base. (Requires Archer or Rifleman).", "Defeat one of the two opponents. (Requires Spearman and Bridges)."], "Map_Tile_3_4":{"terrain":"abyss"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_1_12":{"unit":{"recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":12, "facing":0, "x":1}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":2, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":12, "facing":0, "x":1}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"barracks", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"barracks", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":21, "grooveId":""}, "terrain":"plains"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_8_3":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":3, "facing":0, "x":8}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":3, "facing":0, "x":8}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":8, "grooveId":""}, "terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_8_5":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":5, "facing":0, "x":8}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":5, "facing":0, "x":8}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":31, "grooveId":""}, "terrain":"plains"}, "Map_Tile_12_14":{"terrain":"abyss"}, "Map_Tile_8_6":{"unit":{"recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":6, "facing":0, "x":8}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":6, "facing":0, "x":8}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"barracks", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"barracks", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":16, "grooveId":""}, "terrain":"plains"}, "Map_Tile_12_12":{"unit":{"recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":12, "facing":0, "x":12}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":2, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":12, "facing":0, "x":12}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"tower", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"tower", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":22, "grooveId":""}, "terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"abyss"}, "Map_Tile_5_1":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":1, "facing":0, "x":5}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":1, "facing":0, "x":5}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":6, "grooveId":""}, "terrain":"road"}, "Map_Tile_9_7":{"terrain":"abyss"}, "Map_Tile_12_9":{"terrain":"abyss"}, "Map_Tile_12_8":{"terrain":"abyss"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"abyss"}, "Map_Tile_7_0":{"terrain":"abyss"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"forest_cut"}, "Map_Tile_3_6":{"terrain":"abyss"}, "Map_Tile_12_4":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"abyss"}, "Map_Tile_10_13":{"terrain":"abyss"}, "Map_Tile_6_1":{"terrain":"road"}, "Map_Tile_7_4":{"terrain":"abyss"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_12_3":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":3, "facing":0, "x":12}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":3, "facing":0, "x":12}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":2, "grooveId":""}, "terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"forest"}, "Map_Tile_2_2":{"terrain":"abyss"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"road"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_11_13":{"terrain":"abyss"}, "Map_Tile_2_0":{"terrain":"abyss"}, "Map_Tile_1_3":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":3, "facing":0, "x":1}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":3, "facing":0, "x":1}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":7, "grooveId":""}, "terrain":"plains"}, "Map_Tile_10_8":{"terrain":"abyss"}, "Map_Tile_5_13":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":13, "facing":0, "x":5}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":2, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":13, "facing":0, "x":5}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":20, "grooveId":""}, "terrain":"plains"}, "Map_Tile_5_11":{"terrain":"forest_cut"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_7_7":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":7, "facing":0, "x":7}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":7, "facing":0, "x":7}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":3000, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"hq", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"hq", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":15, "grooveId":""}, "terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Name":"Enmity Cliff", "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"abyss"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"abyss"}, "Map_Tile_8_0":{"terrain":"abyss"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_11_0":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":0, "facing":3, "x":11}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":0, "facing":3, "x":11}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "canCounterAttack":true, "minRange":1, "id":"wulfarHammer", "canAttackSubmerged":false}], "cost":500, "maxGroove":175, "inAir":false, "passiveMultiplier":1.0, "isStructure":false, "resourceCost":3, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":["wulfarHammer"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"walking", "isRecruitable":false, "critConditionId":"", "aliasId":"", "moveRange":4, "isCommander":true, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"commander_wulfar_pirate", "tags":["commander", "type.ground.light", "tall"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"commander_wulfar_pirate", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":24, "grooveId":"golf"}, "terrain":"plains"}, "Map_Tile_3_5":{"terrain":"abyss"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"road"}, "Map_Tile_10_12":{"terrain":"abyss"}, "Map_Tile_10_11":{"terrain":"abyss"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"abyss"}, "Map_Tile_1_7":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":7, "facing":0, "x":1}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":7, "facing":0, "x":1}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":11, "grooveId":""}, "terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"abyss"}, "Map_Tile_6_13":{"terrain":"abyss"}, "Map_Tile_9_8":{"terrain":"abyss"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"abyss"}, "Map_Tile_4_12":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":12, "facing":0, "x":4}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":2, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":12, "facing":0, "x":4}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":3000, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"hq", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"hq", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":19, "grooveId":""}, "terrain":"plains"}, "Map_Tile_10_0":{"terrain":"abyss"}, "Map_Tile_0_10":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":10, "facing":0, "x":0}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":10, "facing":0, "x":0}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":12, "grooveId":""}, "terrain":"plains"}, "Map_Tile_6_12":{"terrain":"abyss"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"abyss"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"abyss"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_4_4":{"terrain":"abyss"}, "Map_Tile_12_7":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":7, "facing":0, "x":12}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":-1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":7, "facing":0, "x":12}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":10, "grooveId":""}, "terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"plains"}, "Flags":{}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_8_8":{"unit":{"recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":8, "facing":0, "x":8}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":8, "facing":0, "x":8}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"barracks", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"barracks", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":17, "grooveId":""}, "terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_1_1":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":1, "facing":0, "x":1}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":1, "facing":0, "x":1}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "canCounterAttack":true, "minRange":1, "id":"lance", "canAttackSubmerged":false}], "cost":600, "maxGroove":0, "inAir":false, "passiveMultiplier":1.5, "isStructure":false, "resourceCost":2, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":["lance"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"riding", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":6, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"knight", "tags":["knight", "type.ground.heavy"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"knight", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":23, "grooveId":""}, "terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"abyss"}, "Map_Tile_13_11":{"terrain":"forest"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_14_0":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":0, "facing":0, "x":14}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":1, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":0, "facing":0, "x":14}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"city", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"city", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":3, "grooveId":""}, "terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"abyss"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"abyss"}, "Map_Tile_7_1":{"terrain":"road"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Player_3":{"recruit_mage":true, "recruit_balloon":true, "recruit_dragon":true, "recruit_knight":true, "recruit_archer":true, "recruit_frog":true, "recruit_witch":true, "recruit_turtle":true, "recruit_giant":true, "recruit_soldier":true, "recruit_dog":true, "recruit_trebuchet":true, "recruit_spearman":true, "recruit_merman":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_caravel":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_travelboat":true, "recruit_warship":true, "gold":100, "recruit_wagon":true, "recruit_kraken":true, "team":2, "recruit_harpy":true}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"mountain"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_6_7":{"unit":{"recruits":["thief", "rifleman"], "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"garrison", "startPos":{"y":7, "facing":0, "x":6}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":7, "facing":0, "x":6}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":{}, "cost":500, "maxGroove":0, "inAir":false, "passiveMultiplier":1.0, "isStructure":true, "resourceCost":1, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":1.0, "weaponIds":{}, "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":true, "movementType":"land_building", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":0, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":true, "id":"hideout", "tags":["structure"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"hideout", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":30, "grooveId":""}, "terrain":"plains"}, "Map_Tile_14_4":{"terrain":"abyss"}, "Map_Tile_5_4":{"terrain":"abyss"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_5_7":{"unit":{"recruits":{}, "grooveCharge":0, "canChargeGroove":true, "tentacled":false, "stunned":false, "itemDropNumber":0, "blessings":{}, "hadTurn":false, "recruitDiscountMultiplier":0.0, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "items":{}, "attachedFlagId":-1, "recruitDiscounts":{}, "killedByLosing":false, "state":{}, "health":100, "garrisonClassId":"", "startPos":{"y":7, "facing":0, "x":5}, "factionOverride":"", "canBeAttackedFromDistance":true, "rangedDamageTakenPercent":100, "attackerId":-1, "playerId":0, "miniGrooveId":"", "attackerUnitClass":"", "inTransport":false, "itemId":"", "hasBeenKilled":false, "merchantDiscounts":{}, "pos":{"y":7, "facing":0, "x":5}, "setHealth":null, "unitClass":{"reinforceMultiplier":1.0, "inWater":false, "weapons":[{"terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "unitIdWhenAttacking":"", "horizontalAndVerticalOnly":false, "canMoveAndAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":true, "canCounterAttack":true, "minRange":1, "id":"lightning", "canAttackSubmerged":false}], "cost":400, "maxGroove":0, "inAir":false, "passiveMultiplier":1.5, "isStructure":false, "resourceCost":2, "canAttack":true, "loadCapacity":0, "verbCostMultiplier":0.5, "weaponIds":["lightning"], "canBeActivated":false, "transportTags":{}, "isDamagingParentUnit":false, "canBeCaptured":false, "movementType":"walking", "isRecruitable":true, "critConditionId":"", "aliasId":"", "moveRange":5, "isCommander":false, "isAttackable":true, "maxHealth":100, "recruitingCostMultiplier":1.0, "canReinforce":false, "id":"mage", "tags":["mage", "type.ground.light", "spellcaster"]}, "canBeAttacked":true, "underwater":false, "attackerPlayerId":-1, "unitClassId":"mage", "transportedBy":-1, "setGroove":null, "damageTakenPercent":100, "id":29, "grooveId":""}, "terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Player_Count":3, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Finishing_Blow.json b/worlds/wargroove2/levels/Finishing_Blow.json new file mode 100644 index 000000000000..4adc8ce737c8 --- /dev/null +++ b/worlds/wargroove2/levels/Finishing_Blow.json @@ -0,0 +1 @@ +{"Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"forest"}, "Map_Tile_10_4":{"terrain":"mountain"}, "Map_Tile_22_9":{"terrain":"forest"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"mountain"}, "Map_Tile_11_2":{"terrain":"mountain"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"mountain"}, "Map_Tile_5_6":{"terrain":"mountain"}, "Flags":{}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_17_9":{"terrain":"road"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"road"}, "Map_Tile_7_6":{"terrain":"road"}, "Map_Tile_23_7":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":23, "y":7, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":23, "y":7, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":8, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_4_4":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":4, "y":4, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":4, "y":4, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":36, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_17_11":{"terrain":"road"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"road"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_0_15":{"terrain":"mountain"}, "Map_Tile_18_7":{"terrain":"mountain"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_19_1":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_22_5":{"terrain":"forest"}, "Map_Tile_11_0":{"terrain":"forest"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_20_14":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"dog", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":20, "y":14, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bite"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"bite", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["dog", "type.ground.light", "animal"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":5, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":150, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"dog"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":20, "y":14, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":22, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"mountain"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"mountain"}, "Map_Tile_6_8":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":6, "y":8, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":6, "y":8, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":15, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"mountain"}, "Map_Tile_10_12":{"terrain":"road"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_2_9":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":2, "y":9, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":2, "y":9, "facing":0}, "playerId":-1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":29, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"forest"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"forest"}, "Map_Tile_7_12":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":["thief", "rifleman"], "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"hideout", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":7, "y":12, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"hideout"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":7, "y":12, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":40, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_12_4":{"terrain":"mountain"}, "Map_Tile_20_13":{"terrain":"plains"}, "Map_Tile_19_2":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_3_1":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"trebuchet", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":3, "y":1, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["trebuchetSling"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":3, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"trebuchetSling", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":false, "maxRange":5}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["trebuchet", "type.ground.heavy"], "inAir":false, "movementType":"wheels", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1100, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"trebuchet"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":3, "y":1, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":33, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_4_13":{"terrain":"road"}, "Map_Tile_19_12":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"spearman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":19, "y":12, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["spear"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"spear", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["spearman", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":250, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"spearman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":19, "y":12, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":13, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_10_7":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"fortified_city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":10, "y":7, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["fortified_city"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":1000, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"fortified_city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":10, "y":7, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"fortified_garrison", "id":53, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_16_10":{"terrain":"forest"}, "Map_Tile_10_1":{"terrain":"mountain"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_21_7":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"frog", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":21, "y":7, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["frog_tongue"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"frog_tongue", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["frog", "type.amphibious.heavy"], "inAir":false, "movementType":"amphibious", "isStructure":false, "moveRange":7, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":600, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"frog"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":21, "y":7, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":10, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"river"}, "Map_Tile_15_5":{"terrain":"road"}, "Map_Tile_3_0":{"terrain":"mountain"}, "Map_Tile_2_11":{"terrain":"road"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_7_15":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_22_11":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"soldier", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":22, "y":11, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["sword"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"sword", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["soldier", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":4, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":100, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"soldier"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":22, "y":11, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":47, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_22_14":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"thief", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":22, "y":14, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["thief", "type.ground.hideout"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":6, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":400, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"thief"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":22, "y":14, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":16, "stunned":false, "state":[{"value":"0", "key":"gold"}], "itemDropNumber":0, "grooveId":""}, "terrain":"mountain"}, "Map_Tile_19_6":{"terrain":"river"}, "Map_Tile_22_12":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"forest"}, "Map_Tile_8_12":{"terrain":"road"}, "Map_Tile_15_11":{"terrain":"mountain"}, "Map_Tile_11_3":{"terrain":"mountain"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_2_15":{"terrain":"mountain"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"road"}, "Map_Tile_18_14":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"archer", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":18, "y":14, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bow"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"bow", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":3}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["archer", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.3500000238419, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"archer"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":18, "y":14, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":4, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"forest"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_5_11":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":["thief", "rifleman"], "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"hideout", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":5, "y":11, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"hideout"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":5, "y":11, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":27, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"mountain"}, "Map_Tile_19_7":{"terrain":"mountain"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"mountain"}, "Map_Tile_12_15":{"terrain":"forest"}, "Map_Tile_2_2":{"terrain":"forest"}, "Map_Tile_9_9":{"terrain":"plains"}, "Triggers":[{"actions":[{"enabled":true, "parameters":["1", "Finishing Blow", "Magnemania", "Defeat 7 enemies in a single turn.", "Capture a Fortified City on Turn 2.", "", "Rout the enemy."], "id":"ap_export"}], "enabled":true, "id":"Export (Always on Top)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}, "recurring":"start_of_match"}, {"actions":[{"enabled":true, "parameters":["current", "aggressive"], "id":"ai_set_profile"}, {"enabled":true, "parameters":["current", "1", "500"], "id":"modify_gold"}, {"enabled":true, "parameters":["*unit", "-1", "current", "0", "40"], "id":"modify_health"}, {"enabled":true, "parameters":["hq", "-1", "P1", "0", "40"], "id":"modify_health"}], "enabled":true, "id":"Set AI", "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}, "recurring":"once"}, {"actions":[{"enabled":true, "parameters":["*unit_structure", "any", "0", "0", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "1", "1", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "2", "2", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "3", "3", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "4", "4", "1"], "id":"unit_random_teleport"}], "enabled":true, "id":"Shuffle Units", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}, "recurring":"start_of_match"}, {"actions":[{"enabled":true, "parameters":["witch", "5", "current", "0", "0", "3", "1", "undefined", "right"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["extra", "oaracle", "Magic is money, friend; take their lives and their coin!", "0", ""], "id":"dialogue_box_simple"}], "enabled":true, "id":"Witch Spawn", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["252010", "1", "4"], "id":"ap_has_item"}], "recurring":"once"}, {"actions":[{"enabled":true, "parameters":["extra", "generic_pirates", "This is the rendezvous point...", "0", ""], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["extra", "generic_pirates", "What happened to the Witches?", "0", ""], "id":"dialogue_box_simple"}], "enabled":true, "id":"Witches Don't Spawn", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["252010", "0", "0"], "id":"ap_has_item"}], "recurring":"once"}, {"actions":[{"enabled":true, "parameters":["*unit", "current", "-1", "0"], "id":"count_units"}, {"enabled":true, "parameters":["1", "0", "0"], "id":"modify_counter"}], "enabled":true, "id":"Store Starting Enemy Count", "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":{}, "id":"start_of_turn"}], "recurring":"repeat"}, {"actions":[{"enabled":true, "parameters":["*unit", "current", "-1", "2"], "id":"count_units"}, {"enabled":true, "parameters":["1", "0", "1", "2"], "id":"counter_arithmetics"}], "enabled":true, "id":"Store Killed Count", "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}, "recurring":"repeat"}, {"actions":[{"enabled":true, "parameters":["253346"], "id":"ap_location_send"}, {"enabled":true, "parameters":["extra", "oaracle", "Seven down!", "0", ""], "id":"dialogue_box_simple"}], "enabled":true, "id":"7 Kills in One Turn (Check 253346)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["1", "4", "7"], "id":"check_map_counter"}], "recurring":"once"}, {"actions":[{"enabled":true, "parameters":["253347"], "id":"ap_location_send"}], "enabled":true, "id":"Capture Fortified City Turn 2 (Check 253347)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "4", "1", "fortified_city", "-1"], "id":"unit_presence"}, {"enabled":true, "parameters":["5", "2"], "id":"current_turn_number"}], "recurring":"once"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "enabled":true, "id":"$trigger_default_defeat_commander", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "recurring":"oncePerPlayer"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "enabled":true, "id":"$trigger_default_defeat_hq", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "recurring":"oncePerPlayer"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "enabled":true, "id":"Defeat (No Units)", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit", "-1"], "id":"unit_presence"}], "recurring":"oncePerPlayer"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "enabled":true, "id":"$trigger_default_victory", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "recurring":"oncePerPlayer"}, {"actions":[{"enabled":true, "parameters":["253345"], "id":"ap_location_send"}], "enabled":true, "id":"P1 Wins (Check 253345)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "recurring":"once"}], "Map_Tile_16_4":{"terrain":"road"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_23_8":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"forest"}, "Map_Tile_3_2":{"terrain":"forest"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Player_2":{"recruit_frog":true, "recruit_thief":true, "recruit_warship":true, "recruit_witch":true, "gold":100, "recruit_griffin_walking":true, "recruit_mage":true, "recruit_soldier":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_turtle":true, "recruit_travelboat":true, "recruit_giant":true, "recruit_spearman":true, "recruit_rifleman":true, "team":1, "recruit_wagon":true, "recruit_merman":true, "recruit_knight":true, "recruit_archer":true, "recruit_caravel":true, "recruit_dragon":true, "recruit_kraken":true, "recruit_balloon":true, "recruit_dog":true, "recruit_trebuchet":true, "recruit_harpoonship":true}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"forest"}, "Map_Tile_18_13":{"terrain":"forest"}, "Map_Tile_5_7":{"terrain":"mountain"}, "Map_Tile_17_5":{"terrain":"forest"}, "Map_Tile_3_10":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"portal", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":3, "y":10, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"portal"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":3, "y":10, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":25, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"forest_cut"}, "Map_Tile_12_12":{"terrain":"mountain"}, "Map_Tile_23_13":{"terrain":"mountain"}, "Map_Tile_5_10":{"terrain":"plains"}, "Counters":{"1":0, "2":0, "0":0}, "Map_Tile_15_6":{"terrain":"forest"}, "Map_Tile_4_0":{"terrain":"mountain"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_18_2":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"bonfire", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":18, "y":2, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"bonfire"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":18, "y":2, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":30, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_2_0":{"terrain":"mountain"}, "Map_Tile_4_8":{"terrain":"mountain"}, "Map_Tile_9_11":{"terrain":"mountain"}, "Map_Tile_1_5":{"terrain":"forest"}, "Map_Tile_14_5":{"terrain":"forest"}, "Map_Tile_7_5":{"terrain":"road"}, "Map_Tile_1_1":{"terrain":"mountain"}, "Objectives":["Defeat 7 enemies in a single turn.", "Capture a Fortified City on Turn 2.", "Rout the enemy."], "Map_Tile_12_3":{"terrain":"mountain"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"forest"}, "Map_Tile_2_10":{"terrain":"road"}, "Map_Tile_6_13":{"terrain":"road"}, "Map_Tile_7_3":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_20_2":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"merman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":20, "y":2, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bident"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"bident", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":2}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["merman", "type.amphibious.light"], "inAir":false, "movementType":"amphibious", "isStructure":false, "moveRange":5, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":350, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"merman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":20, "y":2, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":32, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_21_0":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":21, "y":0, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":21, "y":0, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":3, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_23_9":{"terrain":"forest"}, "Map_Tile_20_8":{"terrain":"river"}, "Map_Tile_2_12":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"dog", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":2, "y":12, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bite"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"bite", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["dog", "type.ground.light", "animal"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":5, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":150, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"dog"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":2, "y":12, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":38, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_22_13":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"harpy", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":22, "y":13, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["harpyClaws"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"harpyClaws", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["harpy", "type.air"], "inAir":true, "movementType":"flying", "isStructure":false, "moveRange":6, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.25, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":600, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"harpy"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":22, "y":13, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":18, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"mountain"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"road"}, "Map_Tile_7_4":{"terrain":"road"}, "Map_Tile_23_14":{"terrain":"mountain"}, "Map_Tile_23_12":{"terrain":"mountain"}, "Map_Tile_1_15":{"terrain":"mountain"}, "Map_Tile_8_1":{"terrain":"mountain"}, "Map_Tile_11_5":{"terrain":"mountain"}, "Map_Tile_14_12":{"terrain":"forest"}, "Map_Tile_12_13":{"terrain":"mountain"}, "Map_Tile_23_11":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":23, "y":11, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":23, "y":11, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":43, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_22_15":{"terrain":"mountain"}, "Map_Tile_20_3":{"terrain":"road"}, "Map_Tile_14_7":{"terrain":"road"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_23_4":{"terrain":"forest"}, "Map_Tile_6_11":{"terrain":"mountain"}, "Map_Tile_23_2":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":23, "y":2, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":23, "y":2, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":42, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_23_1":{"terrain":"mountain"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_22_1":{"terrain":"mountain"}, "Map_Tile_23_15":{"terrain":"mountain"}, "Map_Tile_16_7":{"terrain":"mountain"}, "Map_Tile_4_6":{"terrain":"mountain"}, "Map_Tile_18_9":{"terrain":"mountain"}, "Map_Tile_22_8":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"witch", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":22, "y":8, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["witchSpell"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"witchSpell", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":0.5, "isAttackable":true, "tags":["witch", "type.air", "spellcaster"], "inAir":true, "movementType":"flying", "isStructure":false, "moveRange":7, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":750, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"witch"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":22, "y":8, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":14, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"river"}, "Map_Tile_15_8":{"terrain":"road"}, "Map_Tile_21_4":{"terrain":"plains"}, "Map_Tile_22_6":{"terrain":"river"}, "Map_Tile_10_3":{"terrain":"mountain"}, "Map_Tile_22_4":{"terrain":"plains"}, "Map_Tile_22_3":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"harpy", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":22, "y":3, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["harpyClaws"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"harpyClaws", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["harpy", "type.air"], "inAir":true, "movementType":"flying", "isStructure":false, "moveRange":6, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.25, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":600, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"harpy"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":22, "y":3, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":44, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_22_2":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"commander_valder", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":22, "y":2, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["valderSpell"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"valderSpell", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["commander", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":4, "resourceCost":3, "canReinforce":false, "isRecruitable":false, "canAttack":true, "isCommander":true, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":250, "aliasId":"", "loadCapacity":0, "id":"commander_valder"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":22, "y":2, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":24, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":"raise_dead"}, "terrain":"plains"}, "Map_Tile_22_0":{"terrain":"mountain"}, "Map_Tile_21_15":{"terrain":"mountain"}, "Map_Tile_21_14":{"terrain":"mountain"}, "Map_Tile_21_13":{"terrain":"mountain"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_9_3":{"terrain":"mountain"}, "Map_Tile_5_0":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"soldier", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":5, "y":0, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["sword"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"sword", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["soldier", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":4, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":100, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"soldier"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":5, "y":0, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":41, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_21_12":{"terrain":"plains"}, "Map_Tile_20_12":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"soldier", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":20, "y":12, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["sword"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"sword", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["soldier", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":4, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":100, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"soldier"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":20, "y":12, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":46, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_21_11":{"terrain":"forest"}, "Map_Tile_21_10":{"terrain":"forest"}, "Locations":{"1":{"interactable":false, "positions":[{"y":7, "x":10}, {"y":7, "x":15}, {"y":9, "x":15}, {"y":10, "x":13}, {"y":7, "x":14}, {"y":9, "x":14}, {"y":6, "x":9}, {"y":8, "x":9}, {"y":9, "x":10}], "id":1, "setArea":null, "name":"Enemy Building Shuffle", "centre":{"y":8, "x":12}, "getArea":null}, "2":{"interactable":false, "positions":[{"y":9, "x":2}, {"y":14, "x":6}], "id":2, "setArea":null, "name":"Allied Building Shuffle", "centre":{"y":12, "x":4}, "getArea":null}, "3":{"interactable":false, "positions":[{"y":2, "x":17}, {"y":0, "x":19}, {"y":1, "x":18}, {"y":2, "x":19}, {"y":1, "x":20}, {"y":0, "x":21}, {"y":2, "x":21}, {"y":2, "x":20}, {"y":5, "x":20}, {"y":5, "x":23}, {"y":4, "x":21}, {"y":4, "x":22}, {"y":4, "x":18}, {"y":3, "x":17}, {"y":2, "x":15}, {"y":3, "x":15}, {"y":1, "x":13}, {"y":0, "x":15}], "id":3, "setArea":null, "name":"Shuffle2", "centre":{"y":2, "x":19}, "getArea":null}, "4":{"interactable":false, "positions":[{"y":1, "x":3}, {"y":1, "x":4}, {"y":0, "x":5}, {"y":1, "x":5}, {"y":2, "x":3}], "id":4, "setArea":null, "name":"Shuffle3", "centre":{"y":1, "x":4}, "getArea":null}, "5":{"interactable":false, "positions":[{"y":10, "x":1}, {"y":12, "x":3}, {"y":11, "x":2}, {"y":13, "x":4}], "id":5, "setArea":null, "name":"Witch Spawn", "centre":{"y":12, "x":3}, "getArea":null}, "0":{"interactable":false, "positions":[{"y":10, "x":22}, {"y":10, "x":20}, {"y":11, "x":20}, {"y":12, "x":21}, {"y":12, "x":19}, {"y":13, "x":19}, {"y":13, "x":16}, {"y":14, "x":18}, {"y":15, "x":15}, {"y":14, "x":16}, {"y":14, "x":20}, {"y":15, "x":19}, {"y":14, "x":15}, {"y":11, "x":21}], "id":0, "setArea":null, "name":"Shuffle1", "centre":{"y":13, "x":19}, "getArea":null}}, "Map_Tile_12_1":{"terrain":"forest"}, "Map_Tile_17_6":{"terrain":"mountain"}, "Map_Tile_18_1":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_17_15":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":17, "y":15, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":17, "y":15, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":34, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_18_8":{"terrain":"mountain"}, "Map_Tile_21_6":{"terrain":"forest"}, "Map_Tile_14_9":{"terrain":"road"}, "Map_Tile_21_5":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"harpy", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":21, "y":5, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["harpyClaws"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"harpyClaws", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["harpy", "type.air"], "inAir":true, "movementType":"flying", "isStructure":false, "moveRange":6, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.25, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":600, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"harpy"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":21, "y":5, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":9, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_22_7":{"terrain":"river"}, "Map_Tile_21_3":{"terrain":"forest"}, "Map_Tile_21_2":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"spearman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":21, "y":2, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["spear"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"spear", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["spearman", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":250, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"spearman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":21, "y":2, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":12, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_21_1":{"terrain":"mountain"}, "Map_Tile_21_9":{"terrain":"river"}, "Map_Tile_3_15":{"terrain":"mountain"}, "Map_Tile_20_15":{"terrain":"plains"}, "Map_Tile_20_11":{"terrain":"forest"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"road"}, "Map_Tile_9_7":{"terrain":"road"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_20_9":{"terrain":"river"}, "Map_Tile_17_0":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"soldier", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":17, "y":0, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["sword"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"sword", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["soldier", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":4, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":100, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"soldier"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":17, "y":0, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":45, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_10_9":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"fortified_city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":10, "y":9, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["fortified_city"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":1000, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"fortified_city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":10, "y":9, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"fortified_garrison", "id":48, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_20_5":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"spearman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":20, "y":5, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["spear"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"spear", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["spearman", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":250, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"spearman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":20, "y":5, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":7, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_4_1":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":4, "y":1, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":4, "y":1, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":35, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_21_8":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"river_city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":21, "y":8, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"river_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"river_city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":21, "y":8, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":37, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"river"}, "Map_Tile_11_7":{"terrain":"road"}, "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"mountain"}, "Map_Tile_23_6":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"mountain"}, "Map_Tile_7_7":{"terrain":"road"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"forest"}, "Map_Tile_6_7":{"terrain":"mountain"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_20_1":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":20, "y":1, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":20, "y":1, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":50, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_2_13":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"commander_mercia", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":2, "y":13, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["merciaSword"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"merciaSword", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["commander", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":4, "resourceCost":3, "canReinforce":false, "isRecruitable":false, "canAttack":true, "isCommander":true, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":250, "aliasId":"", "loadCapacity":0, "id":"commander_mercia"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":2, "y":13, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":28, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":"heal_aura"}, "terrain":"road"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_19_15":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"giant", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":19, "y":15, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["giantSlam"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"giantSlam", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["giant", "type.ground.heavy", "tall"], "inAir":false, "movementType":"riding", "isStructure":false, "moveRange":5, "resourceCost":3, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":1200, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"giant"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":19, "y":15, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":49, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_19_14":{"terrain":"plains"}, "Map_Tile_17_1":{"terrain":"forest"}, "Map_Tile_5_12":{"terrain":"mountain"}, "Map_Tile_13_9":{"terrain":"road"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"mountain"}, "Map_Tile_19_9":{"terrain":"mountain"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"mountain"}, "Map_Tile_8_10":{"terrain":"plains"}, "Author":"Magnemania", "Map_Tile_19_5":{"terrain":"forest"}, "Map_Tile_16_8":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"bonfire", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":16, "y":8, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"bonfire"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":16, "y":8, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":20, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_19_0":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"dog", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":19, "y":0, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bite"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"bite", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["dog", "type.ground.light", "animal"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":5, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":150, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"dog"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":19, "y":0, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":21, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"road"}, "Player_Count":2, "Map_Tile_18_12":{"terrain":"forest"}, "Map_Tile_18_11":{"terrain":"road"}, "Map_Tile_9_12":{"terrain":"road"}, "Map_Tile_20_4":{"terrain":"forest"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"forest_cut"}, "Map_Tile_12_8":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"hq", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":12, "y":8, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":3000, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"hq"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":12, "y":8, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":23, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_18_6":{"terrain":"mountain"}, "Map_Tile_17_12":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":17, "y":12, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":17, "y":12, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":51, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_18_5":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"forest"}, "Map_Tile_18_4":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"archer", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":18, "y":4, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bow"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"bow", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":3}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["archer", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.3500000238419, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"archer"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":18, "y":4, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":1, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_18_0":{"terrain":"plains"}, "Map_Tile_11_11":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"fortified_city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":11, "y":11, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["fortified_city"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":1000, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"fortified_city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":11, "y":11, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"fortified_garrison", "id":55, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_17_14":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"forest"}, "Map_Tile_12_6":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"portal", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":12, "y":6, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"portal"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":12, "y":6, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":26, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_17_4":{"terrain":"road"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_16_1":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"harpy", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":16, "y":1, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["harpyClaws"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"harpyClaws", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["harpy", "type.air"], "inAir":true, "movementType":"flying", "isStructure":false, "moveRange":6, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.25, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":600, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"harpy"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":16, "y":1, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":17, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_17_3":{"terrain":"forest"}, "Map_Tile_16_0":{"terrain":"plains"}, "Map_Tile_8_15":{"terrain":"road"}, "Map_Tile_20_7":{"terrain":"river"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"mountain"}, "Map_Tile_1_10":{"terrain":"road"}, "Map_Tile_14_15":{"terrain":"road"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"road"}, "Map_Tile_19_3":{"terrain":"road"}, "Map_Tile_16_5":{"terrain":"forest"}, "Map_Tile_6_1":{"terrain":"forest"}, "Map_Tile_2_3":{"terrain":"road"}, "Map_Tile_3_13":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"dog", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":3, "y":13, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bite"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"bite", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["dog", "type.ground.light", "animal"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":5, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":150, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"dog"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":3, "y":13, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":39, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_16_2":{"terrain":"forest"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_17_2":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"spearman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":17, "y":2, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["spear"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"spear", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["spearman", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":250, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"spearman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":17, "y":2, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":11, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest"}, "Map_Tile_12_2":{"terrain":"forest_cut"}, "Map_Tile_13_1":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"spearman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":13, "y":1, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["spear"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"spear", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["spearman", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":250, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"spearman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":13, "y":1, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":6, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"forest_cut"}, "Map_Tile_15_15":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"archer", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":15, "y":15, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bow"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":true, "canCounterAttack":true, "id":"bow", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":3}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["archer", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.3500000238419, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"archer"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":15, "y":15, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":2, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_15_4":{"terrain":"road"}, "Map_Tile_5_3":{"terrain":"forest_cut"}, "Map_Tile_2_6":{"terrain":"forest"}, "Map_Tile_5_13":{"terrain":"road"}, "Map_Tile_4_5":{"terrain":"mountain"}, "Map_Tile_13_12":{"terrain":"mountain"}, "Map_Tile_4_15":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"forest"}, "Map_Tile_9_2":{"terrain":"mountain"}, "Map_Tile_4_3":{"terrain":"road"}, "Map_Tile_15_7":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"fortified_city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":15, "y":7, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["fortified_city"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":1000, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"fortified_city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":15, "y":7, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"fortified_garrison", "id":54, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"mountain"}, "Map_Tile_15_9":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"fortified_city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":15, "y":9, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["fortified_city"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":1000, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"fortified_city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":15, "y":9, "facing":0}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"fortified_garrison", "id":52, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"road"}, "Map_Tile_14_4":{"terrain":"forest"}, "Map_Tile_15_1":{"terrain":"forest"}, "Map_Tile_15_10":{"terrain":"mountain"}, "Map_Tile_3_3":{"terrain":"forest"}, "Map_Tile_11_9":{"terrain":"road"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"mountain"}, "Map_Tile_13_8":{"terrain":"road"}, "Map_Tile_14_10":{"terrain":"road"}, "Map_Size":{"y":16, "x":24}, "Map_Tile_14_14":{"terrain":"forest"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_23_5":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"mountain"}, "Map_Tile_20_0":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"forest"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"mountain"}, "Map_Tile_1_14":{"terrain":"mountain"}, "Map_Tile_1_6":{"terrain":"forest"}, "Map_Tile_15_13":{"terrain":"forest"}, "Map_Tile_14_2":{"terrain":"forest"}, "Map_Tile_12_7":{"terrain":"road"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_23_0":{"terrain":"mountain"}, "Map_Tile_9_1":{"terrain":"mountain"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"forest"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"forest"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"forest"}, "Map_Tile_6_3":{"terrain":"forest_cut"}, "Map_Tile_0_10":{"terrain":"road"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_22_10":{"terrain":"forest"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"mountain"}, "Map_Tile_23_3":{"terrain":"road"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_23_10":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"mountain"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"mountain"}, "Map_Tile_19_11":{"terrain":"forest"}, "Map_Tile_12_5":{"terrain":"mountain"}, "Map_Tile_11_8":{"terrain":"road"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_20_6":{"terrain":"river"}, "Map_Tile_7_0":{"terrain":"forest"}, "Map_Name":"Finishing Blow", "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"road"}, "Map_Tile_9_15":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"city", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":9, "y":15, "facing":0}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":{}, "critConditionId":"", "weapons":{}, "verbCostMultiplier":1.0, "isAttackable":true, "tags":["structure"], "inAir":false, "movementType":"land_building", "isStructure":true, "moveRange":0, "resourceCost":1, "canReinforce":true, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.0, "inWater":false, "canBeCaptured":true, "recruitingCostMultiplier":1.0, "cost":500, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"city"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":9, "y":15, "facing":0}, "playerId":0, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"garrison", "id":19, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_6_12":{"terrain":"mountain"}, "Map_Tile_5_2":{"terrain":"forest"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"road"}, "Map_Tile_19_13":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"merman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":19, "y":13, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["bident"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"bident", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":2}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["merman", "type.amphibious.light"], "inAir":false, "movementType":"amphibious", "isStructure":false, "moveRange":5, "resourceCost":2, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":2.0, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":350, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"merman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":19, "y":13, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":31, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_8_14":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_20_10":{"unit":{"attackerUnitClass":"", "setGroove":null, "recruits":{}, "hadTurn":false, "attachedFlagId":-1, "attackerId":-1, "recruitDiscountMultiplier":0.0, "recruitDiscounts":{}, "unitClassId":"spearman", "grooveCharge":0, "health":100, "damageTakenPercent":100, "factionOverride":"", "attackerPlayerId":-1, "pos":{"x":20, "y":10, "facing":3}, "unitClass":{"reinforceMultiplier":1.0, "canBeActivated":false, "isDamagingParentUnit":false, "maxHealth":100, "transportTags":{}, "weaponIds":["spear"], "critConditionId":"", "weapons":[{"terrainExclusion":{}, "minRange":1, "blockedByEnemies":false, "canAttackSubmerged":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "canAttackAir":false, "canCounterAttack":true, "id":"spear", "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "maxRange":1}], "verbCostMultiplier":1.0, "isAttackable":true, "tags":["spearman", "type.ground.light"], "inAir":false, "movementType":"walking", "isStructure":false, "moveRange":3, "resourceCost":1, "canReinforce":false, "isRecruitable":true, "canAttack":true, "isCommander":false, "passiveMultiplier":1.5, "inWater":false, "canBeCaptured":false, "recruitingCostMultiplier":1.0, "cost":250, "maxGroove":0, "aliasId":"", "loadCapacity":0, "id":"spearman"}, "killedByLosing":false, "itemId":"", "rangedDamageTakenPercent":100, "canChargeGroove":true, "hasBeenKilled":false, "blessings":{}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "miniGrooveId":"", "startPos":{"x":20, "y":10, "facing":3}, "playerId":1, "underwater":false, "setHealth":null, "inTransport":false, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "transportedBy":-1, "merchantDiscounts":{}, "items":{}, "garrisonClassId":"", "id":5, "stunned":false, "state":{}, "itemDropNumber":0, "grooveId":""}, "terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Player_1":{"recruit_frog":true, "recruit_thief":true, "recruit_warship":true, "recruit_witch":true, "gold":600, "recruit_griffin_walking":true, "recruit_mage":true, "recruit_soldier":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_turtle":true, "recruit_travelboat":true, "recruit_giant":true, "recruit_spearman":true, "recruit_rifleman":true, "team":0, "recruit_wagon":true, "recruit_merman":true, "recruit_knight":true, "recruit_archer":true, "recruit_caravel":true, "recruit_dragon":true, "recruit_kraken":true, "recruit_balloon":true, "recruit_dog":true, "recruit_trebuchet":true, "recruit_harpoonship":true}, "Map_Tile_14_8":{"terrain":"road"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Fortification.json b/worlds/wargroove2/levels/Fortification.json new file mode 100644 index 000000000000..c5c1c1455656 --- /dev/null +++ b/worlds/wargroove2/levels/Fortification.json @@ -0,0 +1 @@ +{"Map_Tile_8_18":{"terrain":"wall"}, "Map_Tile_2_17":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_19_5":{"terrain":"plains"}, "Map_Tile_13_17":{"terrain":"road"}, "Map_Tile_18_1":{"terrain":"plains"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"bridge"}, "Map_Tile_1_17":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"hideout", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":12, "facing":0, "y":9}, "recruits":["thief", "rifleman"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":12, "facing":0, "y":9}, "stunned":false, "id":49, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"hideout", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_16_14":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":16, "facing":0, "y":14}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":16, "facing":0, "y":14}, "stunned":false, "id":31, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_1_3":{"terrain":"river"}, "Map_Tile_19_12":{"terrain":"river"}, "Map_Tile_11_5":{"terrain":"wall"}, "Map_Tile_4_15":{"terrain":"wall"}, "Map_Tile_12_12":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":12, "facing":0, "y":12}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":12, "facing":0, "y":12}, "stunned":false, "id":55, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_0":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":2, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":true, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"lightning"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":5, "weaponIds":["lightning"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"mage", "passiveMultiplier":1.5, "verbCostMultiplier":0.5, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":400, "canAttack":true, "tags":["mage", "type.ground.light", "spellcaster"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":6, "facing":0, "y":1}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":6, "facing":0, "y":1}, "stunned":false, "id":46, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"mage", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_0_17":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"barracks", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":0, "facing":0, "y":17}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":0, "facing":0, "y":17}, "stunned":false, "id":9, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"barracks", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_4_10":{"terrain":"wall"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains", "unit":{"state":[{"key":"gold", "value":"0"}], "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":6, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"thief", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":400, "canAttack":true, "tags":["thief", "type.ground.hideout"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":11, "facing":3, "y":12}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":11, "facing":3, "y":12}, "stunned":false, "id":50, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"thief", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_3_9":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":3, "facing":0, "y":9}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":3, "facing":0, "y":9}, "stunned":false, "id":36, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_0_7":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":0, "facing":0, "y":7}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":0, "facing":0, "y":7}, "stunned":false, "id":22, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_14_1":{"terrain":"mountain"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"mountain"}, "Map_Tile_19_14":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"forest"}, "Map_Tile_15_8":{"terrain":"wall"}, "Map_Tile_13_16":{"terrain":"river"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"road"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"forest"}, "Map_Tile_12_6":{"terrain":"road"}, "Map_Tile_13_7":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":13, "facing":0, "y":7}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":13, "facing":0, "y":7}, "stunned":false, "id":4, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":16, "facing":0, "y":3}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":16, "facing":0, "y":3}, "stunned":false, "id":28, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_2_0":{"terrain":"forest"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":6, "facing":0, "y":11}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":6, "facing":0, "y":11}, "stunned":false, "id":33, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_15":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":15}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":15}, "stunned":false, "id":14, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_9_6":{"terrain":"road"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_1_16":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_0_15":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"road"}, "Map_Tile_3_0":{"terrain":"forest"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"road"}, "Map_Tile_17_19":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":7, "facing":1, "y":12}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":7, "facing":1, "y":12}, "stunned":false, "id":57, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_18_4":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"barracks", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":18, "facing":0, "y":4}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":18, "facing":0, "y":4}, "stunned":false, "id":8, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"barracks", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_1_18":{"terrain":"river", "item":{"itemId":59, "unitTypeRestriction":{}, "type":"guiding_light", "isConsumable":true, "pos":{"x":1, "y":18}}}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_10_17":{"terrain":"road"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"road"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"forest_cut"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"road"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains", "item":{"itemId":60, "unitTypeRestriction":{}, "type":"guiding_light", "isConsumable":true, "pos":{"x":0, "y":4}}}, "Map_Tile_17_11":{"terrain":"river"}, "Map_Tile_5_14":{"terrain":"road"}, "Map_Tile_5_8":{"terrain":"road"}, "Map_Tile_19_6":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":6}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":6}, "stunned":false, "id":29, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"mountain"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"mountain"}, "Map_Tile_1_14":{"terrain":"forest"}, "Map_Tile_16_18":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_9_19":{"terrain":"road"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_16_16":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"wall"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"mountain"}, "Map_Tile_5_0":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":true, "maxRange":3, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"bow"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":3, "weaponIds":["bow"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"archer", "passiveMultiplier":1.3500000238419, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":500, "canAttack":true, "tags":["archer", "type.ground.light"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":5, "facing":0, "y":0}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":5, "facing":0, "y":0}, "stunned":false, "id":43, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"archer", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_15_15":{"terrain":"wall"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"road"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"road", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":6, "facing":0, "y":6}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":6, "facing":0, "y":6}, "stunned":false, "id":48, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_4_8":{"terrain":"wall"}, "Map_Tile_8_19":{"terrain":"mountain"}, "Map_Tile_14_11":{"terrain":"road"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"road"}, "Map_Tile_7_0":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":7, "facing":0, "y":0}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":7, "facing":0, "y":0}, "stunned":false, "id":17, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"road"}, "Map_Tile_9_17":{"terrain":"road"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_3_15":{"terrain":"forest"}, "Map_Tile_5_10":{"terrain":"road"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"road"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"wall"}, "Map_Tile_9_11":{"terrain":"road", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"hq", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":3000, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":9, "facing":0, "y":11}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":9, "facing":0, "y":11}, "stunned":false, "id":1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"hq", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_9":{"terrain":"river"}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_11_9":{"terrain":"plains"}, "Objectives":["Repair two breaches in one turn. (Requires Giant and Walls)", "Defeat an enemy outside the walls with a Trebuchet (Requires Trebuchet)", "Repair all of the wall breaches with Giants. (Requires Giants, Walls, and Ranged Support)"], "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_16_17":{"terrain":"plains"}, "Map_Tile_17_9":{"terrain":"river"}, "Map_Size":{"x":20, "y":20}, "Map_Tile_15_17":{"terrain":"wall"}, "Map_Tile_10_6":{"terrain":"road"}, "Map_Tile_5_15":{"terrain":"road"}, "Map_Tile_17_15":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"wall"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"bridge"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"river"}, "Map_Tile_1_5":{"terrain":"bridge"}, "Map_Tile_2_14":{"terrain":"forest_cut"}, "Map_Tile_7_9":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"barracks", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":7, "facing":0, "y":9}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":7, "facing":0, "y":9}, "stunned":false, "id":2, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"barracks", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_1_1":{"terrain":"river"}, "Map_Tile_17_4":{"terrain":"plains"}, "Map_Tile_15_19":{"terrain":"mountain"}, "Map_Tile_17_18":{"terrain":"plains"}, "Counters":{"1":0, "0":0}, "Map_Tile_3_13":{"terrain":"plains"}, "Triggers":[{"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Export (Always on Top)", "recurring":"start_of_match", "conditions":{}, "actions":[{"parameters":["1", "Fortification", "Magnemania", "Repair two breaches in one turn. (Requires Giant and Walls)", "Defeat an enemy outside the walls with a Trebuchet (Requires Trebuchet)", "", "Repair all of the wall breaches with Giants. (Requires Giants, Walls, and Ranged Support)"], "enabled":true, "id":"ap_export"}], "enabled":true, "isIntro":false}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"Set AI", "recurring":"once", "conditions":{}, "actions":[{"parameters":["current", "aggressive"], "enabled":true, "id":"ai_set_profile"}], "enabled":true, "isIntro":false}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"Shuffle Units", "recurring":"start_of_match", "conditions":{}, "actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "5", "5", "1"], "enabled":true, "id":"unit_random_teleport"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Walls Not Repairable", "recurring":"once", "conditions":[{"parameters":["current"], "enabled":true, "id":"player_turn"}, {"parameters":["252024", "1", "3"], "enabled":true, "id":"ap_has_item"}], "actions":[{"parameters":["neutral", "summer_villager2", "Our walls are breached! We have no engineers to repair them...", "0", ""], "enabled":true, "id":"dialogue_box_simple"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Reset Repair Counter", "recurring":"repeat", "conditions":[{"parameters":{}, "enabled":true, "id":"start_of_turn"}, {"parameters":["current"], "enabled":true, "id":"player_turn"}], "actions":[{"parameters":["1", "0", "0"], "enabled":true, "id":"modify_counter"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Walls Repairable", "recurring":"once", "conditions":[{"parameters":["current"], "enabled":true, "id":"player_turn"}, {"parameters":["252024", "1", "4"], "enabled":true, "id":"ap_has_item"}], "actions":[{"parameters":["0", "1"], "enabled":true, "id":"set_map_flag"}, {"parameters":["neutral", "summer_villager2", "Our engineers are ready to repair the walls! But we're low on materials...", "0", ""], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["giant", "7", "current", "0", "0", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["neutral", "summer_villager2", "We can make do with Giants! Send them into the breaches to repair the walls!", "0", ""], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["8", "safe_space", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["9", "safe_space", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["10", "safe_space", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["11", "safe_space", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["12", "safe_space", "black"], "enabled":true, "id":"set_location_highlight"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Repair Breach 1", "recurring":"once", "conditions":[{"parameters":["current", "0", "1", "giant", "8"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "1"], "enabled":true, "id":"check_map_flag"}], "actions":[{"parameters":["8", "wall", "default", "", "250", "1", "1"], "enabled":true, "id":"activate_flood"}, {"parameters":["8", "none", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["0", "1", "1"], "enabled":true, "id":"modify_counter"}, {"parameters":["1", "1", "1"], "enabled":true, "id":"modify_counter"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Repair Breach 2", "recurring":"once", "conditions":[{"parameters":["current", "0", "1", "giant", "9"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "1"], "enabled":true, "id":"check_map_flag"}], "actions":[{"parameters":["9", "wall", "default", "", "250", "1", "1"], "enabled":true, "id":"activate_flood"}, {"parameters":["9", "none", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["0", "1", "1"], "enabled":true, "id":"modify_counter"}, {"parameters":["1", "1", "1"], "enabled":true, "id":"modify_counter"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Repair Breach 3", "recurring":"once", "conditions":[{"parameters":["current", "0", "1", "giant", "10"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "1"], "enabled":true, "id":"check_map_flag"}], "actions":[{"parameters":["10", "wall", "default", "", "250", "1", "1"], "enabled":true, "id":"activate_flood"}, {"parameters":["10", "none", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["0", "1", "1"], "enabled":true, "id":"modify_counter"}, {"parameters":["1", "1", "1"], "enabled":true, "id":"modify_counter"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Repair Breach 4", "recurring":"once", "conditions":[{"parameters":["current", "0", "1", "giant", "11"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "1"], "enabled":true, "id":"check_map_flag"}], "actions":[{"parameters":["11", "wall", "default", "", "250", "1", "1"], "enabled":true, "id":"activate_flood"}, {"parameters":["11", "none", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["0", "1", "1"], "enabled":true, "id":"modify_counter"}, {"parameters":["1", "1", "1"], "enabled":true, "id":"modify_counter"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Repair Breach 5", "recurring":"once", "conditions":[{"parameters":["current", "0", "1", "giant", "12"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "1"], "enabled":true, "id":"check_map_flag"}], "actions":[{"parameters":["12", "wall", "default", "", "250", "1", "1"], "enabled":true, "id":"activate_flood"}, {"parameters":["12", "none", "black"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["0", "1", "1"], "enabled":true, "id":"modify_counter"}, {"parameters":["1", "1", "1"], "enabled":true, "id":"modify_counter"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Two Repairs in One Turn (Check 253366)", "recurring":"once", "conditions":[{"parameters":["1", "4", "2"], "enabled":true, "id":"check_map_counter"}], "actions":[{"parameters":["253366"], "enabled":true, "id":"ap_location_send"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Trebuchet Defeats Outside Walls (Check 253367)", "recurring":"once", "conditions":[{"parameters":["trebuchet", "current", "*unit", "P2", "13"], "enabled":true, "id":"unit_killed"}], "actions":[{"parameters":["253367"], "enabled":true, "id":"ap_location_send"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_commander", "recurring":"oncePerPlayer", "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_commander", "recurring":"oncePerPlayer", "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_hq", "recurring":"oncePerPlayer", "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "enabled":true, "isIntro":false}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_victory", "recurring":"oncePerPlayer", "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Victory (All Breaches Repaired)", "recurring":"oncePerPlayer", "conditions":[{"parameters":["0", "4", "5"], "enabled":true, "id":"check_map_counter"}], "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "enabled":true, "isIntro":false}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Wins (Check 253365)", "recurring":"once", "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "actions":[{"parameters":["253365"], "enabled":true, "id":"ap_location_send"}], "enabled":true, "isIntro":false}], "Locations":{"1":{"setArea":null, "id":1, "getArea":null, "centre":{"x":9, "y":7}, "interactable":false, "name":"Enemy Army Shuffle", "positions":[{"x":3, "y":1}, {"x":4, "y":0}, {"x":5, "y":0}, {"x":5, "y":1}, {"x":6, "y":1}, {"x":18, "y":2}, {"x":17, "y":1}, {"x":18, "y":17}, {"x":17, "y":18}, {"x":1, "y":16}, {"x":2, "y":17}]}, "2":{"setArea":null, "id":2, "getArea":null, "centre":{"x":10, "y":12}, "interactable":false, "name":"Allied Army Shuffle", "positions":[{"x":8, "y":10}, {"x":8, "y":11}, {"x":8, "y":12}, {"x":8, "y":13}, {"x":11, "y":10}, {"x":11, "y":11}, {"x":11, "y":12}, {"x":11, "y":13}]}, "3":{"setArea":null, "id":3, "getArea":null, "centre":{"x":9, "y":8}, "interactable":false, "name":"Shuffle2", "positions":[{"x":19, "y":6}, {"x":17, "y":6}, {"x":15, "y":2}, {"x":16, "y":3}, {"x":12, "y":4}, {"x":13, "y":4}, {"x":5, "y":4}, {"x":6, "y":4}, {"x":3, "y":10}, {"x":3, "y":9}, {"x":2, "y":18}, {"x":3, "y":17}, {"x":0, "y":15}, {"x":16, "y":17}, {"x":16, "y":14}, {"x":2, "y":4}, {"x":2, "y":6}, {"x":7, "y":2}, {"x":9, "y":2}]}, "4":{"setArea":null, "id":4, "getArea":null, "centre":{"x":9, "y":8}, "interactable":false, "name":"Hideout Shuffle", "positions":[{"x":0, "y":12}, {"x":19, "y":14}, {"x":18, "y":1}, {"x":0, "y":4}]}, "5":{"setArea":null, "id":5, "getArea":null, "centre":{"x":10, "y":12}, "interactable":false, "name":"Base Shuffle", "positions":[{"x":7, "y":9}, {"x":12, "y":9}, {"x":12, "y":14}, {"x":7, "y":14}]}, "7":{"setArea":null, "id":7, "getArea":null, "centre":{"x":9, "y":10}, "interactable":false, "name":"Walls to Repair", "positions":[{"x":4, "y":13}, {"x":4, "y":7}, {"x":9, "y":5}, {"x":15, "y":7}, {"x":15, "y":16}]}, "8":{"setArea":null, "id":8, "getArea":null, "centre":{"x":4, "y":13}, "interactable":false, "name":"Breach1", "positions":[{"x":4, "y":13}]}, "9":{"setArea":null, "id":9, "getArea":null, "centre":{"x":4, "y":7}, "interactable":false, "name":"Breach2", "positions":[{"x":4, "y":7}]}, "10":{"setArea":null, "id":10, "getArea":null, "centre":{"x":9, "y":5}, "interactable":false, "name":"Breach3", "positions":[{"x":9, "y":5}]}, "11":{"setArea":null, "id":11, "getArea":null, "centre":{"x":15, "y":7}, "interactable":false, "name":"Breach4", "positions":[{"x":15, "y":7}]}, "12":{"setArea":null, "id":12, "getArea":null, "centre":{"x":11, "y":10}, "interactable":false, "name":"Breach5", "positions":[{"x":15, "y":16}, {"x":6, "y":4}]}, "13":{"setArea":null, "id":13, "getArea":null, "centre":{"x":10, "y":8}, "interactable":false, "name":"Outside Walls", "positions":[{"x":1, "y":0}, {"x":2, "y":0}, {"x":3, "y":0}, {"x":0, "y":0}, {"x":4, "y":0}, {"x":5, "y":0}, {"x":6, "y":0}, {"x":7, "y":0}, {"x":8, "y":0}, {"x":9, "y":0}, {"x":10, "y":0}, {"x":11, "y":0}, {"x":12, "y":0}, {"x":13, "y":0}, {"x":14, "y":0}, {"x":15, "y":0}, {"x":16, "y":0}, {"x":17, "y":0}, {"x":18, "y":0}, {"x":19, "y":0}, {"x":18, "y":1}, {"x":17, "y":1}, {"x":16, "y":1}, {"x":15, "y":1}, {"x":14, "y":1}, {"x":13, "y":1}, {"x":12, "y":1}, {"x":11, "y":1}, {"x":10, "y":1}, {"x":9, "y":1}, {"x":8, "y":1}, {"x":7, "y":1}, {"x":6, "y":1}, {"x":5, "y":1}, {"x":4, "y":1}, {"x":3, "y":1}, {"x":2, "y":1}, {"x":1, "y":1}, {"x":0, "y":1}, {"x":19, "y":1}, {"x":19, "y":2}, {"x":18, "y":2}, {"x":17, "y":2}, {"x":16, "y":2}, {"x":15, "y":2}, {"x":14, "y":2}, {"x":13, "y":2}, {"x":12, "y":2}, {"x":11, "y":2}, {"x":10, "y":2}, {"x":9, "y":2}, {"x":8, "y":2}, {"x":7, "y":2}, {"x":6, "y":2}, {"x":5, "y":2}, {"x":4, "y":2}, {"x":3, "y":2}, {"x":2, "y":2}, {"x":1, "y":2}, {"x":0, "y":2}, {"x":0, "y":3}, {"x":1, "y":3}, {"x":2, "y":3}, {"x":3, "y":3}, {"x":4, "y":3}, {"x":5, "y":3}, {"x":6, "y":3}, {"x":7, "y":3}, {"x":8, "y":3}, {"x":9, "y":3}, {"x":10, "y":3}, {"x":11, "y":3}, {"x":12, "y":3}, {"x":13, "y":3}, {"x":14, "y":3}, {"x":15, "y":3}, {"x":16, "y":3}, {"x":17, "y":3}, {"x":18, "y":3}, {"x":19, "y":3}, {"x":19, "y":4}, {"x":18, "y":4}, {"x":17, "y":4}, {"x":16, "y":4}, {"x":15, "y":4}, {"x":14, "y":4}, {"x":13, "y":4}, {"x":12, "y":4}, {"x":11, "y":4}, {"x":10, "y":4}, {"x":9, "y":4}, {"x":8, "y":4}, {"x":7, "y":4}, {"x":6, "y":4}, {"x":5, "y":4}, {"x":4, "y":4}, {"x":3, "y":4}, {"x":2, "y":4}, {"x":1, "y":4}, {"x":0, "y":4}, {"x":0, "y":5}, {"x":1, "y":5}, {"x":2, "y":5}, {"x":3, "y":5}, {"x":16, "y":5}, {"x":17, "y":5}, {"x":18, "y":5}, {"x":19, "y":5}, {"x":19, "y":6}, {"x":18, "y":6}, {"x":17, "y":6}, {"x":16, "y":6}, {"x":3, "y":6}, {"x":2, "y":6}, {"x":1, "y":7}, {"x":0, "y":6}, {"x":1, "y":6}, {"x":1, "y":8}, {"x":1, "y":9}, {"x":1, "y":10}, {"x":0, "y":11}, {"x":0, "y":15}, {"x":0, "y":14}, {"x":0, "y":13}, {"x":0, "y":12}, {"x":1, "y":11}, {"x":0, "y":8}, {"x":0, "y":7}, {"x":0, "y":9}, {"x":0, "y":10}, {"x":0, "y":16}, {"x":0, "y":17}, {"x":0, "y":18}, {"x":0, "y":19}, {"x":1, "y":18}, {"x":1, "y":19}, {"x":2, "y":19}, {"x":3, "y":19}, {"x":4, "y":19}, {"x":5, "y":19}, {"x":6, "y":19}, {"x":7, "y":19}, {"x":8, "y":19}, {"x":9, "y":19}, {"x":10, "y":19}, {"x":11, "y":19}, {"x":12, "y":19}, {"x":13, "y":19}, {"x":14, "y":19}, {"x":15, "y":19}, {"x":16, "y":19}, {"x":17, "y":19}, {"x":18, "y":19}, {"x":19, "y":19}, {"x":19, "y":18}, {"x":19, "y":17}, {"x":19, "y":16}, {"x":19, "y":15}, {"x":19, "y":14}, {"x":19, "y":13}, {"x":19, "y":12}, {"x":19, "y":11}, {"x":19, "y":10}, {"x":19, "y":9}, {"x":19, "y":8}, {"x":19, "y":7}, {"x":18, "y":7}, {"x":18, "y":8}, {"x":18, "y":9}, {"x":17, "y":7}, {"x":17, "y":8}, {"x":17, "y":9}, {"x":17, "y":10}, {"x":17, "y":11}, {"x":17, "y":12}, {"x":17, "y":13}, {"x":17, "y":14}, {"x":17, "y":15}, {"x":17, "y":16}, {"x":17, "y":17}, {"x":17, "y":18}, {"x":18, "y":18}, {"x":18, "y":17}, {"x":18, "y":16}, {"x":18, "y":15}, {"x":18, "y":14}, {"x":18, "y":13}, {"x":18, "y":12}, {"x":18, "y":11}, {"x":18, "y":10}, {"x":16, "y":18}, {"x":16, "y":17}, {"x":16, "y":16}, {"x":16, "y":15}, {"x":16, "y":14}, {"x":16, "y":13}, {"x":16, "y":12}, {"x":16, "y":11}, {"x":16, "y":10}, {"x":16, "y":9}, {"x":16, "y":8}, {"x":16, "y":7}, {"x":3, "y":7}, {"x":3, "y":8}, {"x":3, "y":9}, {"x":3, "y":10}, {"x":3, "y":11}, {"x":3, "y":12}, {"x":3, "y":13}, {"x":3, "y":14}, {"x":3, "y":15}, {"x":3, "y":16}, {"x":2, "y":16}, {"x":2, "y":17}, {"x":2, "y":18}, {"x":2, "y":15}, {"x":3, "y":17}, {"x":3, "y":18}, {"x":1, "y":17}, {"x":1, "y":16}, {"x":1, "y":15}, {"x":1, "y":14}, {"x":1, "y":13}, {"x":1, "y":12}, {"x":2, "y":7}, {"x":2, "y":8}, {"x":2, "y":9}, {"x":2, "y":10}, {"x":2, "y":11}, {"x":2, "y":12}, {"x":2, "y":14}, {"x":2, "y":13}]}, "0":{"setArea":null, "id":0, "getArea":null, "centre":{"x":9, "y":12}, "interactable":false, "name":"Shuffle1", "positions":[{"x":6, "y":6}, {"x":8, "y":7}, {"x":6, "y":7}, {"x":6, "y":11}, {"x":6, "y":13}, {"x":6, "y":15}, {"x":7, "y":16}, {"x":10, "y":16}, {"x":11, "y":15}, {"x":12, "y":16}, {"x":10, "y":14}, {"x":12, "y":17}, {"x":6, "y":17}, {"x":13, "y":11}, {"x":12, "y":12}, {"x":13, "y":13}, {"x":13, "y":7}, {"x":11, "y":6}, {"x":11, "y":7}, {"x":13, "y":8}, {"x":7, "y":12}, {"x":8, "y":15}]}}, "Map_Tile_19_13":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":13}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":13}, "stunned":false, "id":32, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_19":{"terrain":"mountain"}, "Map_Tile_19_18":{"terrain":"plains"}, "Map_Tile_19_17":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"barracks", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":17}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":17}, "stunned":false, "id":11, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"barracks", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_18_13":{"terrain":"plains"}, "Map_Tile_10_18":{"terrain":"wall"}, "Map_Tile_18_12":{"terrain":"bridge"}, "Map_Tile_19_16":{"terrain":"mountain"}, "Map_Tile_19_11":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":11}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":11}, "stunned":false, "id":13, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_10":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":10}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":10}, "stunned":false, "id":12, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"river"}, "Map_Tile_19_7":{"terrain":"plains"}, "Map_Tile_17_1":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":2, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"lance"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":6, "weaponIds":["lance"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"riding", "isStructure":false, "id":"knight", "passiveMultiplier":1.5, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":600, "canAttack":true, "tags":["knight", "type.ground.heavy"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":17, "facing":3, "y":1}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":17, "facing":3, "y":1}, "stunned":false, "id":51, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"knight", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_9_0":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":9, "facing":0, "y":0}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":9, "facing":0, "y":0}, "stunned":false, "id":18, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_3":{"terrain":"plains"}, "Map_Tile_12_19":{"terrain":"mountain"}, "Map_Tile_2_19":{"terrain":"plains"}, "Map_Tile_19_2":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":19, "facing":0, "y":2}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":19, "facing":0, "y":2}, "stunned":false, "id":20, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_19_1":{"terrain":"mountain"}, "Map_Tile_16_19":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":16, "facing":0, "y":19}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":16, "facing":0, "y":19}, "stunned":false, "id":21, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_5_5":{"terrain":"wall"}, "Player_Count":2, "Map_Tile_11_2":{"terrain":"mountain"}, "Map_Tile_18_18":{"terrain":"plains", "item":{"itemId":58, "unitTypeRestriction":{}, "type":"groove_boost", "isConsumable":true, "pos":{"x":18, "y":18}}}, "Map_Tile_18_10":{"terrain":"plains"}, "Map_Tile_18_17":{"terrain":"plains"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"wall"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"forest"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"wall"}, "Map_Tile_18_11":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"bridge"}, "Map_Tile_15_2":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":15, "facing":0, "y":2}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":15, "facing":0, "y":2}, "stunned":false, "id":27, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_18_6":{"terrain":"plains"}, "Author":"Magnemania", "Map_Tile_7_6":{"terrain":"road"}, "Map_Tile_5_18":{"terrain":"wall"}, "Map_Tile_18_3":{"terrain":"plains"}, "Map_Tile_18_2":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":2, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"lance"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":6, "weaponIds":["lance"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"riding", "isStructure":false, "id":"knight", "passiveMultiplier":1.5, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":600, "canAttack":true, "tags":["knight", "type.ground.heavy"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":18, "facing":3, "y":2}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":18, "facing":3, "y":2}, "stunned":false, "id":52, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"knight", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_18_0":{"terrain":"plains"}, "Flags":{"0":0}, "Map_Tile_7_5":{"terrain":"wall"}, "Map_Tile_17_17":{"terrain":"plains"}, "Map_Tile_6_16":{"terrain":"plains"}, "Map_Tile_17_16":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_17_14":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_1_19":{"terrain":"river"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_17_12":{"terrain":"river"}, "Map_Tile_5_19":{"terrain":"mountain"}, "Map_Tile_4_18":{"terrain":"wall"}, "Map_Tile_11_1":{"terrain":"mountain"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"river"}, "Map_Tile_0_10":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":0, "facing":0, "y":10}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":0, "facing":0, "y":10}, "stunned":false, "id":15, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_17_10":{"terrain":"river"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_17_8":{"terrain":"river"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":0, "facing":0, "y":3}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":0, "facing":0, "y":3}, "stunned":false, "id":16, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Tile_6_18":{"terrain":"wall"}, "Map_Tile_2_16":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"road"}, "Map_Tile_17_2":{"terrain":"plains"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_17_0":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"wall"}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_13_19":{"terrain":"mountain"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"wall"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":7, "facing":0, "y":2}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":7, "facing":0, "y":2}, "stunned":false, "id":24, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_5_13":{"terrain":"road"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"forest"}, "Map_Tile_6_17":{"terrain":"road"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_15_18":{"terrain":"wall"}, "Map_Tile_15_12":{"terrain":"wall"}, "Map_Tile_4_17":{"terrain":"wall"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"wall"}, "Map_Tile_15_10":{"terrain":"wall"}, "Map_Tile_15_9":{"terrain":"wall"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":2, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"lance"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":6, "weaponIds":["lance"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"riding", "isStructure":false, "id":"knight", "passiveMultiplier":1.5, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":600, "canAttack":true, "tags":["knight", "type.ground.heavy"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":3, "facing":0, "y":1}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":3, "facing":0, "y":1}, "stunned":false, "id":44, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"knight", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"wall"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"road", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":11, "facing":0, "y":6}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":11, "facing":0, "y":6}, "stunned":false, "id":47, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_0_12":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"hideout", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":0, "facing":0, "y":12}, "recruits":["thief", "rifleman"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":0, "facing":0, "y":12}, "stunned":false, "id":10, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"hideout", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_18_7":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"mountain", "item":{"itemId":61, "unitTypeRestriction":{}, "type":"groove_boost", "isConsumable":true, "pos":{"x":13, "y":2}}}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":8, "facing":0, "y":7}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":8, "facing":0, "y":7}, "stunned":false, "id":23, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_14_12":{"terrain":"road"}, "Map_Tile_1_2":{"terrain":"river"}, "Map_Tile_13_5":{"terrain":"wall"}, "Map_Tile_14_18":{"terrain":"wall"}, "Map_Tile_14_17":{"terrain":"road"}, "Map_Tile_14_16":{"terrain":"bridge"}, "Map_Tile_6_5":{"terrain":"wall"}, "Map_Tile_14_14":{"terrain":"road"}, "Map_Tile_14_13":{"terrain":"road"}, "Map_Tile_14_19":{"terrain":"mountain"}, "Map_Tile_17_3":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"road"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"mountain"}, "Map_Tile_14_0":{"terrain":"mountain"}, "Map_Tile_13_18":{"terrain":"wall"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"wall"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_13_11":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":13, "facing":0, "y":11}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":13, "facing":0, "y":11}, "stunned":false, "id":34, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_15_5":{"terrain":"wall"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_10_16":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":10, "facing":0, "y":16}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":10, "facing":0, "y":16}, "stunned":false, "id":6, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"forest"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Name":"Fortification", "Map_Tile_4_0":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":3, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"valderSpell"}], "isRecruitable":false, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":4, "weaponIds":["valderSpell"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"commander_valder", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":250, "isCommander":true, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":500, "canAttack":true, "tags":["commander", "type.ground.light"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":4, "facing":0, "y":0}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":4, "facing":0, "y":0}, "stunned":false, "id":42, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"commander_valder", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"raise_dead", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_1_7":{"terrain":"river"}, "Map_Tile_12_18":{"terrain":"wall"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_12_17":{"terrain":"road", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":12, "facing":0, "y":17}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":12, "facing":0, "y":17}, "stunned":false, "id":5, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_12_14":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"barracks", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":12, "facing":0, "y":14}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":12, "facing":0, "y":14}, "stunned":false, "id":3, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"barracks", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_14_10":{"terrain":"road"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":12, "facing":0, "y":4}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":12, "facing":0, "y":4}, "stunned":false, "id":37, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_7_18":{"terrain":"wall"}, "Map_Tile_12_2":{"terrain":"mountain"}, "Map_Tile_9_1":{"terrain":"mountain"}, "Map_Tile_11_19":{"terrain":"mountain"}, "Map_Tile_8_17":{"terrain":"road"}, "Map_Tile_9_13":{"terrain":"road"}, "Map_Tile_11_18":{"terrain":"wall"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"forest"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_11_17":{"terrain":"road"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"wall"}, "Map_Tile_5_16":{"terrain":"road"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_3_17":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_4_16":{"terrain":"wall"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_18_19":{"terrain":"plains"}, "Map_Tile_17_6":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":17, "facing":0, "y":6}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":17, "facing":0, "y":6}, "stunned":false, "id":30, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_7_15":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":7, "facing":1, "y":15}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":7, "facing":1, "y":15}, "stunned":false, "id":56, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"mountain"}, "Map_Tile_4_9":{"terrain":"wall"}, "Map_Tile_10_19":{"terrain":"mountain"}, "Map_Tile_2_15":{"terrain":"forest_cut"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_3_19":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":3, "facing":0, "y":19}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":3, "facing":0, "y":19}, "stunned":false, "id":19, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_3_16":{"terrain":"forest"}, "Map_Tile_10_11":{"terrain":"road", "item":{"itemId":62, "unitTypeRestriction":{}, "type":"strong_arm", "isConsumable":false, "pos":{"x":10, "y":11}}}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"road", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":10, "facing":0, "y":10}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":10, "facing":0, "y":10}, "stunned":false, "id":53, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_9_18":{"terrain":"road"}, "Map_Tile_0_8":{"terrain":"river"}, "Map_Tile_1_8":{"terrain":"river"}, "Map_Tile_1_4":{"terrain":"river"}, "Player_1":{"recruit_caravel":true, "recruit_frog":true, "recruit_wagon":true, "recruit_spearman":true, "recruit_trebuchet":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_archer":true, "recruit_harpy":true, "gold":100, "recruit_knight":true, "recruit_dog":true, "recruit_griffin_walking":true, "recruit_rifleman":true, "team":0, "recruit_soldier":true, "recruit_witch":true, "recruit_turtle":true, "recruit_thief":true, "recruit_warship":true, "recruit_giant":true, "recruit_kraken":true, "recruit_dragon":true, "recruit_ballista":true, "recruit_travelboat":true, "recruit_mage":true, "recruit_balloon":true}, "Map_Tile_6_19":{"terrain":"mountain"}, "Map_Tile_10_5":{"terrain":"wall"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":2, "facing":0, "y":4}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":2, "facing":0, "y":4}, "stunned":false, "id":25, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_9_16":{"terrain":"road"}, "Map_Tile_9_15":{"terrain":"road"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"road", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":10, "facing":0, "y":12}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":10, "facing":0, "y":12}, "stunned":false, "id":54, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_7_17":{"terrain":"road"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_5_17":{"terrain":"road"}, "Map_Tile_13_6":{"terrain":"road"}, "Map_Tile_1_6":{"terrain":"river"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_0_18":{"terrain":"river"}, "Player_2":{"recruit_caravel":true, "recruit_frog":true, "recruit_wagon":true, "recruit_spearman":true, "recruit_trebuchet":false, "recruit_harpoonship":true, "recruit_merman":true, "recruit_archer":true, "recruit_harpy":true, "gold":100, "recruit_knight":true, "recruit_dog":true, "recruit_griffin_walking":true, "recruit_rifleman":true, "team":1, "recruit_soldier":true, "recruit_witch":true, "recruit_turtle":true, "recruit_thief":true, "recruit_warship":true, "recruit_giant":true, "recruit_kraken":true, "recruit_dragon":true, "recruit_ballista":false, "recruit_travelboat":true, "recruit_mage":true, "recruit_balloon":true}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_2_18":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":2, "facing":0, "y":18}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":2, "facing":0, "y":18}, "stunned":false, "id":35, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_5_12":{"terrain":"road"}, "Map_Tile_8_13":{"terrain":"plains", "unit":{"state":[{"key":"ammo", "value":"3"}], "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":2, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":false, "horizontalAndVerticalOnly":true, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":true, "canAttackAir":false, "maxRange":9, "horizontalAndVerticalExtraWidth":1, "terrainExclusion":["forest"], "id":"musket"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":4, "weaponIds":["musket"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"rifleman", "passiveMultiplier":1.5, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":650, "canAttack":true, "tags":["rifleman", "type.ground.hideout"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":8, "facing":0, "y":13}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":8, "facing":0, "y":13}, "stunned":false, "id":41, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"rifleman", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_18_5":{"terrain":"plains"}, "Map_Tile_4_19":{"terrain":"mountain"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_7_19":{"terrain":"mountain"}, "Map_Tile_9_12":{"terrain":"road"}, "Map_Tile_0_14":{"terrain":"forest"}, "Map_Tile_3_18":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"wall"}, "Map_Tile_4_1":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"barracks", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":4, "facing":0, "y":1}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":4, "facing":0, "y":1}, "stunned":false, "id":7, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"barracks", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_0_19":{"terrain":"mountain"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":true, "maxRange":3, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"bow"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":3, "weaponIds":["bow"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"archer", "passiveMultiplier":1.3500000238419, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":500, "canAttack":true, "tags":["archer", "type.ground.light"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":11, "facing":3, "y":10}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":11, "facing":3, "y":10}, "stunned":false, "id":39, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"archer", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"garrison", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":{}, "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":0, "weaponIds":{}, "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"land_building", "isStructure":true, "id":"city", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":true, "inAir":false, "canReinforce":true, "aliasId":"", "cost":500, "canAttack":true, "tags":["structure"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":5, "facing":0, "y":4}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":5, "facing":0, "y":4}, "stunned":false, "id":26, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"city", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":-1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_5_1":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":3, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"giantSlam"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":5, "weaponIds":["giantSlam"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"riding", "isStructure":false, "id":"giant", "passiveMultiplier":2.5, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":1200, "canAttack":true, "tags":["giant", "type.ground.heavy", "tall"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":5, "facing":0, "y":1}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":5, "facing":0, "y":1}, "stunned":false, "id":45, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"giant", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":1, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_8_10":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":3, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"merciaSword"}], "isRecruitable":false, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":4, "weaponIds":["merciaSword"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"commander_mercia", "passiveMultiplier":1.0, "verbCostMultiplier":1.0, "maxGroove":250, "isCommander":true, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":500, "canAttack":true, "tags":["commander", "type.ground.light"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":8, "facing":0, "y":10}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":8, "facing":0, "y":10}, "stunned":false, "id":38, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"commander_mercia", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"heal_aura", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_11_13":{"terrain":"plains", "unit":{"state":{}, "recruitDiscounts":{}, "garrisonClassId":"", "hasBeenKilled":false, "hadTurn":false, "canChargeGroove":true, "miniGrooveId":"", "health":100, "recruitDiscountMultiplier":0.0, "attackerUnitClass":"", "blessings":{}, "underwater":false, "setGroove":null, "items":{}, "attachedFlagId":-1, "unitClass":{"transportTags":{}, "loadCapacity":0, "inWater":false, "resourceCost":1, "weapons":[{"canAttackSubmerged":false, "minRange":1, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "directionality":"omni", "blockedByEnemies":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "id":"sword"}], "isRecruitable":true, "isDamagingParentUnit":false, "canBeActivated":false, "moveRange":4, "weaponIds":["sword"], "isAttackable":true, "critConditionId":"", "recruitingCostMultiplier":1.0, "movementType":"walking", "isStructure":false, "id":"soldier", "passiveMultiplier":1.5, "verbCostMultiplier":1.0, "maxGroove":0, "isCommander":false, "canBeCaptured":false, "inAir":false, "canReinforce":false, "aliasId":"", "cost":100, "canAttack":true, "tags":["soldier", "type.ground.light"], "maxHealth":100, "reinforceMultiplier":1.0}, "startPos":{"x":11, "facing":3, "y":13}, "recruits":{}, "factionOverride":"", "grooveCharge":0, "itemDropNumber":0, "setHealth":null, "canBeAttackedFromDistance":true, "canBeAttacked":true, "tentacled":false, "attackerPlayerId":-1, "pos":{"x":11, "facing":3, "y":13}, "stunned":false, "id":40, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "itemId":"", "unitClassId":"soldier", "inTransport":false, "damageTakenPercent":100, "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "playerId":0, "killedByLosing":false, "grooveId":"", "attackerId":-1, "transportedBy":-1}}, "Map_Tile_1_15":{"terrain":"forest"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Frantic_Inlet.json b/worlds/wargroove2/levels/Frantic_Inlet.json new file mode 100644 index 000000000000..60a315e89085 --- /dev/null +++ b/worlds/wargroove2/levels/Frantic_Inlet.json @@ -0,0 +1 @@ +{"Map_Tile_3_11":{"terrain":"cobblestone"}, "Map_Tile_24_9":{"terrain":"beach"}, "Map_Tile_13_11":{"terrain":"road"}, "Map_Tile_7_12":{"terrain":"cobblestone"}, "Map_Tile_20_1":{"terrain":"mountain"}, "Map_Tile_16_11":{"terrain":"road"}, "Map_Tile_27_2":{"terrain":"river"}, "Map_Tile_25_11":{"terrain":"beach"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"cobblestone"}, "Map_Tile_22_13":{"terrain":"sea"}, "Map_Tile_27_12":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"cobblestone"}, "Map_Tile_12_6":{"terrain":"forest"}, "Map_Tile_18_8":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"ocean"}, "Map_Tile_29_0":{"terrain":"forest"}, "Map_Tile_6_10":{"terrain":"wall"}, "Map_Tile_20_10":{"terrain":"bridge"}, "Map_Tile_27_4":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "merchantDiscounts":{}, "startPos":{"facing":0, "x":27, "y":4}, "miniGrooveId":"", "unitClassId":"barracks", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":27, "y":4}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"barracks", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":28, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_16_0":{"terrain":"sea"}, "Player_1":{"recruit_soldier":true, "recruit_travelboat":true, "recruit_frog":true, "recruit_archer":true, "recruit_turtle":true, "gold":100, "recruit_wagon":true, "recruit_trebuchet":true, "recruit_spearman":true, "recruit_harpoonship":true, "recruit_caravel":true, "recruit_knight":true, "recruit_witch":true, "recruit_warship":true, "recruit_harpy":true, "recruit_griffin_walking":true, "recruit_dog":true, "recruit_merman":true, "recruit_kraken":true, "recruit_ballista":true, "recruit_balloon":true, "recruit_dragon":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_thief":true, "team":0, "recruit_mage":true}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_11_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "merchantDiscounts":{}, "startPos":{"facing":0, "x":11, "y":5}, "miniGrooveId":"", "unitClassId":"port", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":11, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"port", "isCommander":false, "movementType":"river_sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":2, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Map_Tile_20_8":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":20, "y":8}, "miniGrooveId":"", "unitClassId":"water_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":20, "y":8}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"water_city", "isCommander":false, "movementType":"sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":19, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"wall"}, "Map_Tile_13_5":{"terrain":"ocean"}, "Map_Tile_17_11":{"terrain":"road"}, "Map_Tile_18_1":{"terrain":"mountain"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_24_2":{"terrain":"sea"}, "Map_Tile_21_6":{"terrain":"ocean"}, "Map_Tile_4_5":{"terrain":"cobblestone"}, "Map_Tile_8_12":{"terrain":"cobblestone"}, "Map_Tile_1_11":{"terrain":"wall"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_18_7":{"terrain":"reef"}, "Map_Tile_19_4":{"terrain":"ocean"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"wall"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"wall"}, "Map_Tile_13_13":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "merchantDiscounts":{}, "startPos":{"facing":0, "x":13, "y":13}, "miniGrooveId":"", "unitClassId":"barracks", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":13, "y":13}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"barracks", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":42, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_20_6":{"terrain":"ocean"}, "Map_Tile_22_10":{"terrain":"beach"}, "Map_Tile_14_2":{"terrain":"reef"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"bridge"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_25_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":25, "y":5}, "miniGrooveId":"", "unitClassId":"mage", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":25, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":400, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lightning"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lightning", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":true, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":0.5, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"mage", "isCommander":false, "movementType":"walking", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["mage", "type.ground.light", "spellcaster"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":40, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_19_3":{"terrain":"ocean"}, "Map_Tile_13_3":{"terrain":"ocean"}, "Map_Tile_9_13":{"terrain":"wall"}, "Map_Tile_5_6":{"terrain":"wall"}, "Map_Tile_22_7":{"terrain":"ocean"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_23_3":{"terrain":"sea"}, "Map_Tile_27_8":{"terrain":"river"}, "Map_Tile_13_9":{"terrain":"road"}, "Counters":{}, "Map_Tile_4_10":{"terrain":"cobblestone"}, "Map_Tile_12_1":{"terrain":"sea"}, "Flags":{}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"cobblestone"}, "Map_Tile_7_6":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":7, "y":6}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":7, "y":6}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":5, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_15_2":{"terrain":"sea"}, "Map_Tile_26_5":{"terrain":"road"}, "Map_Tile_1_1":{"terrain":"cobblestone"}, "Map_Tile_20_13":{"terrain":"sea"}, "Map_Tile_29_6":{"terrain":"forest"}, "Map_Tile_3_5":{"terrain":"cobblestone"}, "Player_2":{"recruit_soldier":true, "recruit_travelboat":true, "recruit_frog":true, "recruit_archer":true, "recruit_turtle":true, "gold":100, "recruit_wagon":true, "recruit_trebuchet":true, "recruit_spearman":true, "recruit_harpoonship":true, "recruit_caravel":true, "recruit_knight":true, "recruit_witch":true, "recruit_warship":true, "recruit_harpy":true, "recruit_griffin_walking":true, "recruit_dog":true, "recruit_merman":true, "recruit_kraken":true, "recruit_ballista":true, "recruit_balloon":true, "recruit_dragon":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_thief":true, "team":1, "recruit_mage":true}, "Map_Tile_0_7":{"terrain":"carpet"}, "Map_Tile_2_11":{"terrain":"wall"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_8_1":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":8, "y":1}, "miniGrooveId":"", "unitClassId":"mage", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":1}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":400, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lightning"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lightning", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":true, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":0.5, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"mage", "isCommander":false, "movementType":"walking", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["mage", "type.ground.light", "spellcaster"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":50, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_8_0":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":8, "y":0}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":0}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":3, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_6_12":{"terrain":"cobblestone"}, "Map_Tile_26_6":{"terrain":"road"}, "Map_Tile_24_6":{"terrain":"beach"}, "Map_Tile_20_5":{"terrain":"ocean"}, "Map_Tile_29_9":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"ocean"}, "Map_Tile_18_0":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":18, "y":0}, "miniGrooveId":"", "unitClassId":"portal_neutral", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":18, "y":0}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"portal_neutral", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":51, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"road"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_8_2":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":8, "y":2}, "miniGrooveId":"", "unitClassId":"knight", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":2}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":6, "maxGroove":0, "cost":600, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lance"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lance", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"knight", "isCommander":false, "movementType":"riding", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["knight", "type.ground.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":49, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_13_4":{"terrain":"ocean"}, "Map_Tile_4_7":{"terrain":"cobblestone"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"forest"}, "Map_Tile_24_0":{"terrain":"reef"}, "Map_Tile_22_3":{"terrain":"ocean"}, "Map_Tile_25_0":{"terrain":"beach"}, "Map_Tile_19_13":{"terrain":"sea"}, "Map_Tile_28_1":{"terrain":"road"}, "Map_Tile_13_1":{"terrain":"sea"}, "Map_Tile_29_11":{"terrain":"plains"}, "Map_Tile_20_12":{"terrain":"bridge"}, "Map_Tile_29_10":{"terrain":"mountain"}, "Map_Tile_26_13":{"terrain":"beach"}, "Map_Tile_1_6":{"terrain":"cobblestone"}, "Map_Tile_9_10":{"terrain":"wall"}, "Map_Tile_29_7":{"terrain":"plains"}, "Map_Tile_29_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":29, "y":5}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":29, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":33, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_19_1":{"terrain":"mountain"}, "Map_Tile_22_5":{"terrain":"sea"}, "Map_Tile_28_4":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":28, "y":4}, "miniGrooveId":"", "unitClassId":"commander_valder", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":28, "y":4}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":4, "maxGroove":250, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["valderSpell"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"valderSpell", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":false, "id":"commander_valder", "isCommander":true, "movementType":"walking", "resourceCost":3, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["commander", "type.ground.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"raise_dead", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":35, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_21_10":{"terrain":"forest"}, "Map_Tile_23_8":{"terrain":"ocean"}, "Map_Tile_2_12":{"terrain":"wall"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_23_4":{"terrain":"sea"}, "Map_Tile_28_13":{"terrain":"plains"}, "Map_Tile_28_12":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":28, "y":12}, "miniGrooveId":"", "unitClassId":"balloon", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":28, "y":12}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":6, "maxGroove":0, "cost":450, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":2, "weapons":{}, "transportTags":["type.ground.light", "type.amphibious.light", "type.ground.hideout", "airtrooper"], "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"balloon", "isCommander":false, "movementType":"flying", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":true, "passiveMultiplier":1.0, "tags":["balloon", "type.air"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":41, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_18_13":{"terrain":"forest"}, "Map_Tile_21_7":{"terrain":"sea"}, "Map_Tile_24_12":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":24, "y":12}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":24, "y":12}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":32, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_8_10":{"terrain":"wall"}, "Map_Tile_12_4":{"terrain":"sea"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_28_10":{"terrain":"road"}, "Map_Tile_14_6":{"terrain":"ocean"}, "Map_Tile_14_3":{"terrain":"ocean"}, "Map_Tile_28_9":{"terrain":"road"}, "Map_Tile_2_8":{"terrain":"cobblestone"}, "Map_Tile_21_4":{"terrain":"ocean"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"cobblestone"}, "Map_Tile_28_8":{"terrain":"bridge"}, "Map_Tile_28_7":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"carpet"}, "Map_Tile_28_6":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":28, "y":6}, "miniGrooveId":"", "unitClassId":"merman", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":28, "y":6}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":350, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["bident"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":2, "horizontalAndVerticalOnly":false, "id":"bident", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"merman", "isCommander":false, "movementType":"amphibious", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":2.0, "tags":["merman", "type.amphibious.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":25, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_28_5":{"terrain":"plains"}, "Map_Tile_28_3":{"terrain":"road"}, "Map_Tile_15_4":{"terrain":"ocean"}, "Map_Tile_8_13":{"terrain":"cobblestone"}, "Map_Tile_28_11":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"sea"}, "Map_Tile_16_4":{"terrain":"ocean"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_28_0":{"terrain":"road"}, "Map_Tile_27_13":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"ocean"}, "Map_Tile_0_1":{"terrain":"cobblestone"}, "Map_Tile_27_10":{"terrain":"road"}, "Map_Tile_23_0":{"terrain":"sea"}, "Map_Tile_27_7":{"terrain":"plains"}, "Map_Tile_27_6":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"sea"}, "Map_Tile_23_11":{"terrain":"plains"}, "Map_Tile_27_3":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":27, "y":3}, "miniGrooveId":"", "unitClassId":"knight", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":27, "y":3}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":6, "maxGroove":0, "cost":600, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lance"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lance", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"knight", "isCommander":false, "movementType":"riding", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["knight", "type.ground.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":34, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"road"}, "Map_Tile_1_12":{"terrain":"cobblestone"}, "Map_Tile_3_13":{"terrain":"cobblestone"}, "Map_Tile_29_3":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":29, "y":3}, "miniGrooveId":"", "unitClassId":"trebuchet", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":29, "y":3}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":1100, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["trebuchetSling"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":5, "horizontalAndVerticalOnly":false, "id":"trebuchetSling", "canMoveAndAttack":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":3}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"trebuchet", "isCommander":false, "movementType":"wheels", "resourceCost":3, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["trebuchet", "type.ground.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":37, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"road"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_27_0":{"terrain":"beach"}, "Map_Tile_29_8":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":29, "y":8}, "miniGrooveId":"", "unitClassId":"caravel", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":29, "y":8}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":250, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["caravelWeapon"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"caravelWeapon", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"caravel", "isCommander":false, "movementType":"river_sailing", "resourceCost":1, "isAttackable":true, "inWater":true, "inAir":false, "passiveMultiplier":1.5, "tags":["caravel", "type.sea.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":22, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"river"}, "Map_Tile_26_12":{"terrain":"beach"}, "Map_Tile_3_3":{"terrain":"wall"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"wall"}, "Map_Tile_2_9":{"terrain":"cobblestone"}, "Map_Tile_23_6":{"terrain":"sea"}, "Map_Tile_21_2":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":21, "y":2}, "miniGrooveId":"", "unitClassId":"water_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":21, "y":2}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"water_city", "isCommander":false, "movementType":"sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":20, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"ocean"}, "Map_Tile_17_5":{"terrain":"ocean"}, "Map_Tile_26_10":{"terrain":"beach"}, "Map_Tile_26_9":{"terrain":"road"}, "Map_Tile_1_9":{"terrain":"cobblestone"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_25_13":{"terrain":"sea"}, "Objectives":["Block the pursuing enemy with a Spearman. (Requires Spearman)", "Capture the Portal. (Requires Barge and Turtle)", "Move your Commander to the southeast escape point. (Requires Turtle and Barge\/Knight)"], "Map_Tile_16_1":{"terrain":"beach"}, "Map_Tile_26_8":{"terrain":"bridge"}, "Map_Tile_9_7":{"terrain":"road"}, "Map_Tile_26_7":{"terrain":"road"}, "Map_Tile_4_9":{"terrain":"cobblestone"}, "Map_Tile_26_4":{"terrain":"road"}, "Map_Tile_15_8":{"terrain":"forest"}, "Map_Tile_26_3":{"terrain":"road"}, "Map_Tile_7_13":{"terrain":"carpet"}, "Map_Tile_3_9":{"terrain":"cobblestone"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"ocean"}, "Map_Tile_26_0":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":26, "y":0}, "miniGrooveId":"", "unitClassId":"warship", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":26, "y":0}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":8, "maxGroove":0, "cost":1000, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["warshipCannon"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":4, "horizontalAndVerticalOnly":false, "id":"warshipCannon", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":3}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"warship", "isCommander":false, "movementType":"sailing", "resourceCost":3, "isAttackable":true, "inWater":true, "inAir":false, "passiveMultiplier":1.5, "tags":["warship", "type.sea.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":26, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"beach"}, "Map_Tile_2_6":{"terrain":"cobblestone"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_13_8":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":13, "y":8}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":13, "y":8}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":14, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_25_12":{"terrain":"beach"}, "Map_Tile_11_13":{"terrain":"mountain"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"sea"}, "Map_Tile_25_10":{"terrain":"beach"}, "Map_Tile_23_7":{"terrain":"ocean"}, "Player_Count":3, "Map_Tile_19_0":{"terrain":"road"}, "Map_Tile_25_9":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"plains"}, "Map_Tile_12_11":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":12, "y":11}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":12, "y":11}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":15, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_20_2":{"terrain":"ocean"}, "Map_Tile_19_5":{"terrain":"ocean"}, "Map_Tile_25_7":{"terrain":"plains"}, "Map_Tile_6_13":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":6, "y":13}, "miniGrooveId":"", "unitClassId":"mage", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":13}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":400, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lightning"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lightning", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":true, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":0.5, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"mage", "isCommander":false, "movementType":"walking", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["mage", "type.ground.light", "spellcaster"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":47, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"carpet"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_0_9":{"terrain":"cobblestone"}, "Map_Tile_25_6":{"terrain":"plains"}, "Map_Tile_20_9":{"terrain":"sea"}, "Map_Tile_25_4":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"cobblestone"}, "Author":"Magnemania", "Map_Tile_19_8":{"terrain":"sea"}, "Map_Tile_16_13":{"terrain":"plains"}, "Map_Tile_25_2":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":25, "y":2}, "miniGrooveId":"", "unitClassId":"river_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":25, "y":2}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"river_city", "isCommander":false, "movementType":"river_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":11, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"river"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_25_1":{"terrain":"beach"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_29_12":{"terrain":"plains"}, "Map_Tile_19_9":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"wall"}, "Map_Tile_24_13":{"terrain":"sea"}, "Map_Tile_24_11":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_27_1":{"terrain":"plains"}, "Map_Name":"Frantic Inlet", "Map_Tile_24_8":{"terrain":"sea"}, "Map_Tile_24_4":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "merchantDiscounts":{}, "startPos":{"facing":0, "x":24, "y":4}, "miniGrooveId":"", "unitClassId":"port", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":24, "y":4}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"port", "isCommander":false, "movementType":"river_sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":13, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Map_Tile_24_7":{"terrain":"beach"}, "Map_Tile_11_1":{"terrain":"beach"}, "Map_Tile_4_11":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":4, "y":11}, "miniGrooveId":"", "unitClassId":"knight", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":4, "y":11}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":6, "maxGroove":0, "cost":600, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lance"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lance", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"knight", "isCommander":false, "movementType":"riding", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["knight", "type.ground.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":45, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"cobblestone"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_24_3":{"terrain":"sea"}, "Map_Tile_24_1":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":24, "y":1}, "miniGrooveId":"", "unitClassId":"kraken", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":24, "y":1}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":850, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["tentacleSmack"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":3, "horizontalAndVerticalOnly":false, "id":"tentacleSmack", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"kraken", "isCommander":false, "movementType":"river_sailing", "resourceCost":2, "isAttackable":true, "inWater":true, "inAir":false, "passiveMultiplier":1.3500000238419, "tags":["kraken", "type.sea.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":23, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Locations":{"1":{"positions":[{"x":21, "y":5}, {"x":22, "y":5}, {"x":23, "y":5}, {"x":23, "y":4}, {"x":22, "y":6}, {"x":23, "y":3}, {"x":23, "y":6}], "getArea":null, "name":"Enemy Navy Shuffle", "interactable":false, "setArea":null, "centre":{"x":22, "y":5}, "id":1}, "2":{"positions":[{"x":6, "y":2}, {"x":7, "y":3}, {"x":7, "y":2}, {"x":8, "y":2}, {"x":8, "y":1}, {"x":9, "y":1}], "getArea":null, "name":"Allied Army Shuffle", "interactable":false, "setArea":null, "centre":{"x":8, "y":2}, "id":2}, "3":{"positions":[{"x":15, "y":1}, {"x":21, "y":2}, {"x":20, "y":8}, {"x":16, "y":5}, {"x":19, "y":6}, {"x":22, "y":7}, {"x":14, "y":6}, {"x":22, "y":0}, {"x":16, "y":0}, {"x":17, "y":8}], "getArea":null, "name":"Shuffle2", "interactable":false, "setArea":null, "centre":{"x":18, "y":4}, "id":3}, "4":{"positions":{}, "getArea":null, "name":"Shuffle3", "interactable":false, "setArea":null, "centre":{"x":0, "y":0}, "id":4}, "5":{"positions":[{"x":29, "y":12}, {"x":28, "y":12}, {"x":27, "y":12}, {"x":27, "y":13}, {"x":28, "y":13}, {"x":29, "y":13}, {"x":27, "y":11}, {"x":28, "y":11}, {"x":29, "y":11}], "getArea":null, "name":"Escape Point", "interactable":false, "setArea":null, "centre":{"x":28, "y":12}, "id":5}, "6":{"positions":[{"x":5, "y":13}, {"x":7, "y":13}, {"x":6, "y":13}], "getArea":null, "name":"Infinite Knight Spawn", "interactable":false, "setArea":null, "centre":{"x":6, "y":13}, "id":6}, "7":{"positions":[{"x":0, "y":8}, {"x":0, "y":7}, {"x":0, "y":6}], "getArea":null, "name":"Giant Spawn", "interactable":false, "setArea":null, "centre":{"x":0, "y":7}, "id":7}, "8":{"positions":[{"x":19, "y":0}, {"x":18, "y":1}, {"x":17, "y":0}], "getArea":null, "name":"Portal Spawn", "interactable":false, "setArea":null, "centre":{"x":18, "y":0}, "id":8}, "0":{"positions":[{"x":13, "y":8}, {"x":11, "y":9}, {"x":12, "y":11}, {"x":14, "y":10}, {"x":16, "y":10}, {"x":15, "y":12}, {"x":24, "y":12}, {"x":22, "y":12}], "getArea":null, "name":"Shuffle1", "interactable":false, "setArea":null, "centre":{"x":16, "y":11}, "id":0}, "9":{"positions":[{"x":7, "y":3}], "getArea":null, "name":"Start Position", "interactable":false, "setArea":null, "centre":{"x":7, "y":3}, "id":9}, "10":{"positions":[{"x":27, "y":5}, {"x":28, "y":6}, {"x":27, "y":6}, {"x":25, "y":5}, {"x":28, "y":5}], "getArea":null, "name":"Enemy Army Shuffle", "interactable":false, "setArea":null, "centre":{"x":27, "y":5}, "id":10}, "11":{"positions":[{"x":4, "y":3}, {"x":4, "y":4}, {"x":4, "y":5}], "getArea":null, "name":"Chokepoint", "interactable":false, "setArea":null, "centre":{"x":4, "y":4}, "id":11}}, "Map_Tile_23_13":{"terrain":"sea"}, "Map_Tile_23_12":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_23_10":{"terrain":"beach"}, "Map_Tile_8_8":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":8, "y":8}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":8}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":6, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_13_2":{"terrain":"sea"}, "Map_Tile_23_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":23, "y":5}, "miniGrooveId":"", "unitClassId":"warship", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":23, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":8, "maxGroove":0, "cost":1000, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["warshipCannon"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":4, "horizontalAndVerticalOnly":false, "id":"warshipCannon", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":3}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"warship", "isCommander":false, "movementType":"sailing", "resourceCost":3, "isAttackable":true, "inWater":true, "inAir":false, "passiveMultiplier":1.5, "tags":["warship", "type.sea.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":9, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Map_Tile_10_13":{"terrain":"mountain"}, "Map_Tile_15_0":{"terrain":"sea"}, "Map_Tile_23_2":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"sea"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_23_1":{"terrain":"sea"}, "Map_Tile_27_9":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":27, "y":9}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":27, "y":9}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":31, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_22_12":{"terrain":"plains"}, "Map_Tile_22_11":{"terrain":"plains"}, "Map_Tile_22_6":{"terrain":"sea"}, "Map_Tile_9_12":{"terrain":"wall"}, "Map_Tile_22_8":{"terrain":"ocean"}, "Map_Tile_17_1":{"terrain":"mountain"}, "Map_Tile_22_9":{"terrain":"sea"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_24_10":{"terrain":"beach"}, "Map_Tile_22_4":{"terrain":"reef"}, "Map_Tile_1_4":{"terrain":"cobblestone"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_29_13":{"terrain":"plains"}, "Map_Tile_22_2":{"terrain":"ocean"}, "Map_Tile_6_11":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":6, "y":11}, "miniGrooveId":"", "unitClassId":"knight", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":11}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":6, "maxGroove":0, "cost":600, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lance"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lance", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"knight", "isCommander":false, "movementType":"riding", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["knight", "type.ground.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":39, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"cobblestone"}, "Map_Tile_22_1":{"terrain":"sea"}, "Map_Tile_3_1":{"terrain":"wall"}, "Map_Tile_17_0":{"terrain":"mountain"}, "Map_Tile_6_0":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":6, "y":0}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":0}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":4, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_22_0":{"terrain":"sea"}, "Map_Tile_21_13":{"terrain":"sea"}, "Map_Tile_21_12":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"cobblestone"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_29_2":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":29, "y":2}, "miniGrooveId":"", "unitClassId":"caravel", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":29, "y":2}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":250, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["caravelWeapon"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"caravelWeapon", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"caravel", "isCommander":false, "movementType":"river_sailing", "resourceCost":1, "isAttackable":true, "inWater":true, "inAir":false, "passiveMultiplier":1.5, "tags":["caravel", "type.sea.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":21, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"river"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"cobblestone"}, "Map_Tile_17_2":{"terrain":"ocean"}, "Map_Tile_16_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":16, "y":5}, "miniGrooveId":"", "unitClassId":"water_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":16, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"water_city", "isCommander":false, "movementType":"sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":18, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"ocean"}, "Map_Tile_21_9":{"terrain":"sea"}, "Map_Tile_21_8":{"terrain":"sea"}, "Map_Tile_21_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":21, "y":5}, "miniGrooveId":"", "unitClassId":"kraken", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":21, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":850, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["tentacleSmack"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":3, "horizontalAndVerticalOnly":false, "id":"tentacleSmack", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"kraken", "isCommander":false, "movementType":"river_sailing", "resourceCost":2, "isAttackable":true, "inWater":true, "inAir":false, "passiveMultiplier":1.3500000238419, "tags":["kraken", "type.sea.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":10, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"ocean"}, "Map_Tile_3_2":{"terrain":"wall"}, "Map_Tile_21_3":{"terrain":"ocean"}, "Map_Tile_26_11":{"terrain":"beach"}, "Map_Tile_16_10":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":16, "y":10}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":16, "y":10}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":16, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_27_5":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":3, "x":27, "y":5}, "miniGrooveId":"", "unitClassId":"archer", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":3, "x":27, "y":5}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":3, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["bow"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":3, "horizontalAndVerticalOnly":false, "id":"bow", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":true, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"archer", "isCommander":false, "movementType":"walking", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.3500000238419, "tags":["archer", "type.ground.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":36, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_12_3":{"terrain":"sea"}, "Map_Tile_1_8":{"terrain":"cobblestone"}, "Map_Tile_21_0":{"terrain":"beach"}, "Map_Tile_20_11":{"terrain":"sea"}, "Map_Tile_20_7":{"terrain":"sea"}, "Map_Tile_20_4":{"terrain":"ocean"}, "Map_Tile_5_7":{"terrain":"wall"}, "Map_Tile_25_8":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":25, "y":8}, "miniGrooveId":"", "unitClassId":"river_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":25, "y":8}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"river_city", "isCommander":false, "movementType":"river_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":12, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"river"}, "Map_Tile_20_0":{"terrain":"mountain"}, "Map_Tile_19_12":{"terrain":"bridge"}, "Map_Tile_19_11":{"terrain":"sea"}, "Map_Tile_19_7":{"terrain":"sea"}, "Map_Tile_0_10":{"terrain":"cobblestone"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"ocean"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Size":{"x":30, "y":14}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_15_1":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":15, "y":1}, "miniGrooveId":"", "unitClassId":"water_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":15, "y":1}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"water_city", "isCommander":false, "movementType":"sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":17, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"ocean"}, "Map_Tile_29_4":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":29, "y":4}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":29, "y":4}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":24, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"road"}, "Map_Tile_18_11":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"cobblestone"}, "Map_Tile_16_8":{"terrain":"beach"}, "Map_Tile_18_9":{"terrain":"beach"}, "Map_Tile_18_6":{"terrain":"ocean"}, "Map_Tile_0_5":{"terrain":"cobblestone"}, "Map_Tile_2_2":{"terrain":"cobblestone"}, "Map_Tile_0_2":{"terrain":"cobblestone"}, "Map_Tile_7_0":{"terrain":"road"}, "Map_Tile_18_5":{"terrain":"ocean"}, "Map_Tile_18_4":{"terrain":"ocean"}, "Map_Tile_18_3":{"terrain":"reef"}, "Map_Tile_4_8":{"terrain":"cobblestone"}, "Map_Tile_1_3":{"terrain":"cobblestone"}, "Map_Tile_18_2":{"terrain":"ocean"}, "Map_Tile_11_2":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "merchantDiscounts":{}, "startPos":{"facing":0, "x":11, "y":2}, "miniGrooveId":"", "unitClassId":"port", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":11, "y":2}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"port", "isCommander":false, "movementType":"river_sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":27, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Map_Tile_17_12":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"wall"}, "Map_Tile_17_10":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"sea"}, "Map_Tile_17_9":{"terrain":"beach"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_17_8":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":17, "y":8}, "miniGrooveId":"", "unitClassId":"water_city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":17, "y":8}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"water_city", "isCommander":false, "movementType":"sea_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-1, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":29, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"sea"}, "Map_Tile_7_2":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":7, "y":2}, "miniGrooveId":"", "unitClassId":"commander_mercia", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":7, "y":2}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":4, "maxGroove":250, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["merciaSword"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"merciaSword", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":false, "id":"commander_mercia", "isCommander":true, "movementType":"walking", "resourceCost":3, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["commander", "type.ground.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"heal_aura", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":48, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"road"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"cobblestone"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_7_10":{"terrain":"wall"}, "Map_Tile_10_1":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":10, "y":1}, "miniGrooveId":"", "unitClassId":"frog", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":10, "y":1}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":7, "maxGroove":0, "cost":600, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["frog_tongue"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"frog_tongue", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"frog", "isCommander":false, "movementType":"amphibious", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["frog", "type.amphibious.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":30, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_17_6":{"terrain":"ocean"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_17_4":{"terrain":"ocean"}, "Map_Tile_5_12":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":5, "y":12}, "miniGrooveId":"", "unitClassId":"commander_greenfinger", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":5, "y":12}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":4, "maxGroove":250, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["greenfingerAttack"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"greenfingerAttack", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":false, "id":"commander_greenfinger", "isCommander":true, "movementType":"walking", "resourceCost":3, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["commander", "type.ground.light"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"vine_wall", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":44, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"cobblestone"}, "Map_Tile_17_3":{"terrain":"ocean"}, "Map_Tile_4_13":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":4, "y":13}, "miniGrooveId":"", "unitClassId":"mage", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":4, "y":13}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":5, "maxGroove":0, "cost":400, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lightning"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lightning", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":true, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":0.5, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"mage", "isCommander":false, "movementType":"walking", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["mage", "type.ground.light", "spellcaster"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":46, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"cobblestone"}, "Player_3":{"recruit_soldier":true, "recruit_travelboat":true, "recruit_frog":true, "recruit_archer":true, "recruit_turtle":true, "gold":100, "recruit_wagon":true, "recruit_trebuchet":true, "recruit_spearman":true, "recruit_harpoonship":true, "recruit_caravel":true, "recruit_knight":true, "recruit_witch":true, "recruit_warship":true, "recruit_harpy":true, "recruit_griffin_walking":true, "recruit_dog":true, "recruit_merman":true, "recruit_kraken":true, "recruit_ballista":true, "recruit_balloon":true, "recruit_dragon":true, "recruit_rifleman":true, "recruit_giant":true, "recruit_thief":true, "team":3, "recruit_mage":true}, "Map_Tile_0_11":{"terrain":"wall"}, "Map_Tile_8_3":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "merchantDiscounts":{}, "startPos":{"facing":0, "x":8, "y":3}, "miniGrooveId":"", "unitClassId":"barracks", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":3}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"barracks", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":1, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_5_5":{"terrain":"wall"}, "Map_Tile_25_3":{"terrain":"plains"}, "Map_Tile_3_12":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":3, "y":12}, "miniGrooveId":"", "unitClassId":"knight", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":3, "y":12}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":6, "maxGroove":0, "cost":600, "recruitingCostMultiplier":1.0, "isStructure":false, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":["lance"], "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":[{"blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "horizontalAndVerticalOnly":false, "id":"lance", "canMoveAndAttack":true, "unitIdWhenAttacking":"", "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canAttackAir":false, "directionality":"omni", "minRange":1}], "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"knight", "isCommander":false, "movementType":"riding", "resourceCost":2, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.5, "tags":["knight", "type.ground.heavy"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":38, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"cobblestone"}, "Map_Tile_3_0":{"terrain":"wall"}, "Map_Tile_29_1":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"ocean"}, "Map_Tile_5_11":{"terrain":"cobblestone"}, "Map_Tile_4_4":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":4, "y":4}, "miniGrooveId":"", "unitClassId":"gate", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":4, "y":4}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":false, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":false, "isRecruitable":true, "id":"gate", "isCommander":false, "movementType":"indoor_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":-2, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"", "stunned":false, "id":43, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"cobblestone"}, "Map_Tile_10_12":{"terrain":"mountain"}, "Map_Tile_1_7":{"terrain":"cobblestone"}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_13_0":{"terrain":"sea"}, "Map_Tile_5_8":{"terrain":"wall"}, "Map_Tile_16_3":{"terrain":"ocean"}, "Map_Tile_27_11":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"cobblestone"}, "Map_Tile_1_0":{"terrain":"wall"}, "Map_Tile_26_1":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"carpet"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_1_10":{"terrain":"cobblestone"}, "Map_Tile_15_7":{"terrain":"sea"}, "Map_Tile_2_7":{"terrain":"cobblestone"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"wall"}, "Map_Tile_2_1":{"terrain":"cobblestone"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"beach"}, "Triggers":[{"recurring":"start_of_match", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["1", "Frantic Inlet", "Magnemania", "Block the pursuing enemy with a Spearman. (Requires Spearman)", "Capture the Portal. (Requires Barge and Turtle)", "", "Move your Commander to the southeast escape point. (Requires Turtle and Barge\/Knight)"], "enabled":true, "id":"ap_export"}], "conditions":{}, "id":"Export (Always on Top)"}, {"recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["P2", "balanced"], "enabled":true, "id":"ai_set_profile"}, {"parameters":["current", "1", "500"], "enabled":true, "id":"modify_gold"}, {"parameters":["P3", "1"], "enabled":true, "id":"change_team"}, {"parameters":["P3", "aggressive"], "enabled":true, "id":"ai_set_profile"}], "conditions":{}, "id":"Set AI"}, {"recurring":"start_of_match", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "10", "10", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}], "conditions":{}, "id":"Shuffle Units"}, {"recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["5", "ground_pulse_only", "1"], "enabled":true, "id":"set_location_highlight"}, {"parameters":["5", "0"], "enabled":true, "id":"centre_camera"}, {"parameters":["2000"], "enabled":true, "id":"wait"}, {"parameters":["9", "0"], "enabled":true, "id":"centre_camera"}], "conditions":{}, "id":"Highlight Escape"}, {"recurring":"repeat", "enabled":true, "players":[0, 0, 1, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["knight", "6", "current", "0", "0", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}], "conditions":[{"parameters":{}, "enabled":true, "id":"end_of_turn"}, {"parameters":["current"], "enabled":true, "id":"player_turn"}], "id":"Spawn Knights Repeating"}, {"recurring":"once", "enabled":true, "players":[0, 0, 1, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["giant", "7", "current", "0", "0", "3", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}], "conditions":[{"parameters":["0", "4"], "enabled":true, "id":"current_turn_number"}, {"parameters":["current"], "enabled":true, "id":"player_turn"}, {"parameters":{}, "enabled":true, "id":"end_of_turn"}], "id":"Spawn Giants"}, {"recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["253351"], "enabled":true, "id":"ap_location_send"}], "conditions":[{"parameters":["current", "4", "1", "spearman", "11"], "enabled":true, "id":"unit_presence"}], "id":"Guard Chokepoint (Check 253351)"}, {"recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["253352"], "enabled":true, "id":"ap_location_send"}, {"parameters":["1000", "3", "3", "5"], "enabled":true, "id":"screenshake"}, {"parameters":["dragon", "8", "current", "0", "1", "1", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}], "conditions":[{"parameters":["current", "4", "1", "portal", "-1"], "enabled":true, "id":"unit_presence"}], "id":"Portal Captured (Check 253352)"}, {"recurring":"oncePerPlayer", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "id":"$trigger_default_defeat_commander"}, {"recurring":"oncePerPlayer", "enabled":true, "players":[1, 1, 1, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "id":"$trigger_default_defeat_hq"}, {"recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["*commander", "5", "current", "0", "0"], "enabled":true, "id":"remove_units"}, {"parameters":["*unit", "5", "current", "1", "0"], "enabled":true, "id":"remove_units"}, {"parameters":["current"], "enabled":true, "id":"victory"}], "conditions":[{"parameters":["current", "4", "1", "*commander", "5"], "enabled":true, "id":"unit_presence"}], "id":"Victory (Reached Escape)"}, {"recurring":"oncePerPlayer", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "id":"$trigger_default_victory"}, {"recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"parameters":["253350"], "enabled":true, "id":"ap_location_send"}], "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "id":"P1 Wins (Check 253350)"}], "Map_Tile_19_2":{"terrain":"ocean"}, "Map_Tile_1_2":{"terrain":"cobblestone"}, "Map_Tile_7_3":{"terrain":"road"}, "Map_Tile_4_12":{"terrain":"cobblestone"}, "Map_Tile_11_0":{"terrain":"beach"}, "Map_Tile_3_4":{"terrain":"wall"}, "Map_Tile_9_8":{"terrain":"road"}, "Map_Tile_15_5":{"terrain":"ocean"}, "Map_Tile_2_10":{"terrain":"cobblestone"}, "Map_Tile_23_9":{"terrain":"sea"}, "Map_Tile_2_5":{"terrain":"cobblestone"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_21_1":{"terrain":"beach"}, "Map_Tile_8_6":{"terrain":"road"}, "Map_Tile_14_4":{"terrain":"ocean"}, "Map_Tile_11_4":{"terrain":"beach"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_6_3":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":6, "y":3}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":3}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":8, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_0_6":{"terrain":"carpet"}, "Map_Tile_1_5":{"terrain":"cobblestone"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"cobblestone"}, "Map_Tile_3_8":{"terrain":"cobblestone"}, "Map_Tile_12_0":{"terrain":"sea"}, "Map_Tile_21_11":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_24_5":{"terrain":"sea"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"sea"}, "Map_Tile_3_6":{"terrain":"cobblestone"}, "Map_Tile_9_6":{"unit":{"inTransport":false, "transportedBy":-1, "attackerUnitClass":"", "health":100, "itemId":"", "recruits":{}, "merchantDiscounts":{}, "startPos":{"facing":0, "x":9, "y":6}, "miniGrooveId":"", "unitClassId":"city", "canBeAttacked":true, "grooveCharge":0, "hadTurn":false, "rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":6}, "recruitDiscounts":{}, "state":{}, "factionOverride":"", "setGroove":null, "unitClass":{"moveRange":0, "maxGroove":0, "cost":500, "recruitingCostMultiplier":1.0, "isStructure":true, "canBeCaptured":true, "critConditionId":"", "canAttack":true, "weaponIds":{}, "reinforceMultiplier":1.0, "aliasId":"", "loadCapacity":0, "weapons":{}, "transportTags":{}, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "canBeActivated":false, "maxHealth":100, "canReinforce":true, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "resourceCost":1, "isAttackable":true, "inWater":false, "inAir":false, "passiveMultiplier":1.0, "tags":["structure"]}, "setHealth":null, "hasBeenKilled":false, "canBeAttackedFromDistance":true, "playerId":0, "canChargeGroove":true, "items":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "tentacled":false, "grooveId":"", "attackerPlayerId":-1, "garrisonClassId":"garrison", "stunned":false, "id":7, "blessings":{}, "damageTakenPercent":100, "itemDropNumber":0, "killedByLosing":false, "attachedFlagId":-1, "loadedUnits":{}, "recruitDiscountMultiplier":0.0, "attackerId":-1}, "terrain":"plains"}, "Map_Tile_19_6":{"terrain":"ocean"}, "Map_Tile_28_2":{"terrain":"bridge"}, "Map_Tile_26_2":{"terrain":"river"}, "Map_Tile_2_3":{"terrain":"cobblestone"}, "Map_Tile_7_5":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Gnarled_Mountaintop.json b/worlds/wargroove2/levels/Gnarled_Mountaintop.json new file mode 100644 index 000000000000..0e395d760628 --- /dev/null +++ b/worlds/wargroove2/levels/Gnarled_Mountaintop.json @@ -0,0 +1 @@ +{"Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"forest"}, "Map_Tile_16_12":{"terrain":"mountain"}, "Map_Tile_4_5":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":5, "x":4}, "state":{}, "id":11, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":5, "x":4}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_11_8":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":8, "x":11}, "state":{}, "id":22, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":8, "x":11}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_16_2":{"terrain":"mountain"}, "Map_Tile_2_12":{"terrain":"mountain"}, "Map_Tile_12_14":{"terrain":"mountain"}, "Map_Tile_2_7":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":2}, "state":{}, "id":28, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":7, "x":2}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_9_0":{"terrain":"mountain"}, "Map_Tile_4_2":{"terrain":"bridge"}, "Map_Tile_10_12":{"terrain":"mountain"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"mountain"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_8_8":{"unit":{"unitClassId":"bonfire", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":8, "x":8}, "state":{}, "id":25, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"bonfire", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":false, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":8, "x":8}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"road"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"road"}, "Map_Tile_16_8":{"terrain":"mountain"}, "Map_Tile_8_10":{"terrain":"road"}, "Map_Tile_3_1":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":3}, "state":{}, "id":2, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":1, "x":3}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_13_14":{"terrain":"mountain"}, "Map_Tile_10_0":{"terrain":"mountain"}, "Map_Tile_6_6":{"terrain":"mountain"}, "Map_Tile_3_14":{"terrain":"mountain"}, "Map_Tile_4_13":{"terrain":"mountain"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"river"}, "Map_Tile_7_0":{"terrain":"mountain"}, "Map_Tile_12_6":{"terrain":"mountain"}, "Map_Tile_15_5":{"terrain":"mountain"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"mountain"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"mountain"}, "Map_Tile_2_3":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":2}, "state":{}, "id":29, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":3, "x":2}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_11_2":{"terrain":"road"}, "Map_Tile_7_10":{"terrain":"road"}, "Map_Tile_16_1":{"terrain":"mountain"}, "Map_Tile_14_7":{"unit":{"unitClassId":"hq", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":14}, "state":{}, "id":15, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":3000, "id":"hq", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":false, "tags":["structure"], "transportTags":{}, "canBeCaptured":false, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":7, "x":14}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_5_4":{"terrain":"mountain"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_0_1":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":0}, "state":{}, "id":1, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":1, "x":0}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_6_2":{"terrain":"mountain"}, "Map_Tile_12_11":{"unit":{"unitClassId":"tower", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":11, "x":12}, "state":{}, "id":17, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"tower", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "pos":{"facing":0, "y":11, "x":12}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_9_4":{"terrain":"mountain"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_8_1":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":8}, "state":{}, "id":4, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":1, "x":8}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_9_5":{"terrain":"mountain"}, "Map_Tile_0_7":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":0}, "state":{}, "id":10, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":7, "x":0}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_5_8":{"unit":{"unitClassId":"hq", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":8, "x":5}, "state":{}, "id":24, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":3000, "id":"hq", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":false, "tags":["structure"], "transportTags":{}, "canBeCaptured":false, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":8, "x":5}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_16_14":{"terrain":"mountain"}, "Map_Tile_3_11":{"terrain":"road"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_1_9":{"unit":{"unitClassId":"barracks", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":9, "x":1}, "state":{}, "id":18, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"barracks", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "pos":{"facing":0, "y":9, "x":1}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_16_10":{"terrain":"mountain"}, "Map_Tile_6_10":{"terrain":"road"}, "Map_Tile_5_11":{"terrain":"mountain"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"road"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_0_14":{"terrain":"mountain"}, "Map_Tile_4_14":{"terrain":"mountain"}, "Map_Tile_0_11":{"terrain":"road"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"mountain"}, "Map_Tile_13_13":{"terrain":"mountain"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"mountain"}, "Map_Tile_14_2":{"terrain":"forest"}, "Map_Tile_1_12":{"terrain":"mountain"}, "Map_Tile_12_2":{"terrain":"road"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"mountain"}, "Map_Tile_6_8":{"terrain":"mountain"}, "Map_Tile_8_12":{"terrain":"plains"}, "Flags":{"1":0, "0":0}, "Map_Tile_9_2":{"terrain":"road"}, "Map_Tile_12_13":{"terrain":"mountain"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"mountain"}, "Map_Tile_2_13":{"terrain":"mountain"}, "Map_Tile_16_9":{"terrain":"mountain"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"mountain"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"mountain"}, "Counters":{}, "Map_Tile_16_6":{"terrain":"mountain"}, "Map_Tile_8_4":{"terrain":"road"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"mountain"}, "Map_Tile_0_2":{"terrain":"plains"}, "Objectives":["Protect the Watch Tower for 5 turns. (Requires Harpy)", "Capture a northwestern village without destroying the vines. (Requires Air Trooper)", "Win with standard conditions. (Requires Harpy)"], "Map_Tile_7_12":{"terrain":"plains"}, "Triggers":[{"actions":[{"parameters":["1", "Gnarled Mountaintop", "Magnemania", "Protect the Watch Tower for 5 turns. (Requires Harpy)", "Capture a northwestern village without destroying the vines. (Requires Air Trooper)", "", "Win with standard conditions. (Requires Harpy)"], "enabled":true, "id":"ap_export"}], "enabled":true, "recurring":"start_of_match", "id":"Export (Always on Top)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}}, {"actions":[{"parameters":["0", "1"], "enabled":true, "id":"set_map_flag"}, {"parameters":["bonfire", "-1", "any", "0", "0"], "enabled":true, "id":"remove_units"}], "enabled":true, "recurring":"once", "id":"Watchtower Lost", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["*unit", "any", "bonfire", "current", "-1"], "enabled":true, "id":"unit_killed"}]}, {"actions":[{"parameters":["thorny_vine", "5", "current", "1", "1", "14", "1", "undefined", "centre"], "enabled":true, "id":"ap_spawn_unit"}, {"parameters":["thorny_vine", "5", "current", "cant_move", "1"], "enabled":true, "id":"ai_set_restriction"}], "enabled":true, "recurring":"start_of_match", "id":"Make Vines", "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}}, {"actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}], "enabled":true, "recurring":"start_of_match", "id":"Shuffle Units", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":{}}, {"actions":[{"parameters":["thorny_vine", "5", "current", "1", "20"], "enabled":true, "id":"modify_health"}], "enabled":true, "recurring":"repeat", "id":"Sustain Vines", "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":{}, "enabled":true, "id":"start_of_turn"}, {"parameters":["current"], "enabled":true, "id":"player_turn"}]}, {"actions":[{"parameters":["1", "1"], "enabled":true, "id":"set_map_flag"}], "enabled":true, "recurring":"once", "id":"Capture Vine Destroyed", "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["thorny_vine", "current", "7"], "enabled":true, "id":"unit_lost"}]}, {"actions":[{"parameters":["253336"], "enabled":true, "id":"ap_location_send"}], "enabled":true, "recurring":"once", "id":"Watchtower Held (Check 253336)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["0", "6"], "enabled":true, "id":"current_turn_number"}, {"parameters":["0", "0"], "enabled":true, "id":"check_map_flag"}, {"parameters":["current"], "enabled":true, "id":"player_turn"}]}, {"actions":[{"parameters":["253337"], "enabled":true, "id":"ap_location_send"}], "enabled":true, "recurring":"once", "id":"Capture Without Destroying Vine (Check 253337)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["current", "4", "1", "city", "7"], "enabled":true, "id":"unit_presence"}, {"parameters":["1", "0"], "enabled":true, "id":"check_map_flag"}]}, {"actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "enabled":true, "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}]}, {"actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "enabled":true, "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}]}, {"actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "enabled":true, "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}]}, {"actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "enabled":true, "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}]}, {"actions":[{"parameters":["253335"], "enabled":true, "id":"ap_location_send"}], "enabled":true, "recurring":"once", "id":"P1 Wins (Check 253335)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}]}], "Map_Tile_8_13":{"terrain":"mountain"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"mountain"}, "Map_Tile_11_14":{"terrain":"mountain"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"mountain"}, "Map_Tile_6_11":{"unit":{"unitClassId":"tower", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":11, "x":6}, "state":{}, "id":7, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"tower", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "pos":{"facing":0, "y":11, "x":6}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"mountain"}, "Map_Size":{"x":17, "y":15}, "Map_Tile_12_10":{"terrain":"forest"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Name":"Gnarled Mountaintop", "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"mountain"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"mountain"}, "Map_Tile_10_5":{"terrain":"mountain"}, "Map_Tile_12_8":{"terrain":"mountain"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"bridge"}, "Map_Tile_10_2":{"terrain":"road"}, "Map_Tile_4_0":{"terrain":"river"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"mountain"}, "Map_Tile_13_12":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":12, "x":13}, "state":{}, "id":8, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":12, "x":13}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Player_1":{"team":0, "recruit_wagon":true, "recruit_archer":true, "recruit_warship":true, "recruit_merman":true, "recruit_kraken":true, "recruit_thief":true, "recruit_witch":true, "recruit_dog":true, "recruit_harpy":true, "recruit_spearman":true, "gold":100, "recruit_turtle":true, "recruit_frog":true, "recruit_balloon":true, "recruit_giant":true, "recruit_griffin_walking":true, "recruit_rifleman":true, "recruit_caravel":true, "recruit_ballista":true, "recruit_soldier":true, "recruit_travelboat":true, "recruit_harpoonship":true, "recruit_dragon":true, "recruit_mage":true, "recruit_knight":true, "recruit_trebuchet":true}, "Map_Tile_11_4":{"terrain":"mountain"}, "Map_Tile_0_5":{"terrain":"mountain"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"mountain"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"road"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"mountain"}, "Map_Tile_5_0":{"terrain":"mountain"}, "Map_Tile_9_13":{"terrain":"mountain"}, "Map_Tile_4_4":{"terrain":"river"}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_4_3":{"terrain":"river"}, "Map_Tile_8_2":{"terrain":"road"}, "Map_Tile_14_14":{"terrain":"mountain"}, "Player_2":{"team":3, "recruit_wagon":true, "recruit_archer":true, "recruit_warship":true, "recruit_merman":true, "recruit_kraken":true, "recruit_thief":true, "recruit_witch":true, "recruit_dog":true, "recruit_harpy":true, "recruit_spearman":true, "gold":100, "recruit_turtle":true, "recruit_frog":true, "recruit_balloon":true, "recruit_giant":true, "recruit_griffin_walking":true, "recruit_rifleman":true, "recruit_caravel":true, "recruit_ballista":true, "recruit_soldier":true, "recruit_travelboat":true, "recruit_harpoonship":true, "recruit_dragon":true, "recruit_mage":true, "recruit_knight":true, "recruit_trebuchet":true}, "Map_Tile_3_4":{"terrain":"river"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"mountain"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"mountain"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"mountain"}, "Map_Tile_4_10":{"terrain":"road"}, "Map_Tile_16_13":{"terrain":"mountain"}, "Map_Tile_16_11":{"terrain":"mountain"}, "Map_Tile_6_14":{"terrain":"mountain"}, "Map_Tile_7_9":{"terrain":"mountain"}, "Map_Tile_7_6":{"terrain":"mountain"}, "Map_Tile_7_3":{"terrain":"mountain"}, "Map_Tile_15_6":{"unit":{"unitClassId":"barracks", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":6, "x":15}, "state":{}, "id":16, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"barracks", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "pos":{"facing":0, "y":6, "x":15}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_16_7":{"terrain":"mountain"}, "Map_Tile_0_3":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":0}, "state":{}, "id":3, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":3, "x":0}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_10_4":{"terrain":"mountain"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"mountain"}, "Map_Tile_16_5":{"terrain":"mountain"}, "Map_Tile_10_14":{"terrain":"mountain"}, "Map_Tile_16_4":{"terrain":"mountain"}, "Map_Tile_4_1":{"terrain":"river"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"road"}, "Map_Tile_2_1":{"item":{"pos":{"x":2, "y":1}, "isConsumable":false, "type":"bullrushs_lance", "unitTypeRestriction":{}, "itemId":30}, "terrain":"plains"}, "Map_Tile_6_1":{"terrain":"mountain"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"road"}, "Author":"Magnemania", "Map_Tile_14_1":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":14}, "state":{}, "id":6, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":1, "x":14}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_15_14":{"terrain":"mountain"}, "Map_Tile_15_13":{"terrain":"mountain"}, "Map_Tile_6_5":{"terrain":"mountain"}, "Map_Tile_3_3":{"terrain":"mountain"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"mountain"}, "Locations":{"0":{"positions":[{"x":14, "y":7}, {"x":10, "y":3}, {"x":9, "y":3}, {"x":8, "y":1}, {"x":9, "y":1}, {"x":7, "y":4}], "name":"Shuffle1", "setArea":null, "id":0, "centre":{"x":10, "y":3}, "getArea":null, "interactable":false}, "3":{"positions":[{"x":15, "y":7}, {"x":15, "y":8}, {"x":15, "y":10}, {"x":15, "y":11}, {"x":12, "y":12}, {"x":13, "y":12}, {"x":14, "y":12}], "name":"Shuffle2", "setArea":null, "id":3, "centre":{"x":14, "y":10}, "getArea":null, "interactable":false}, "4":{"positions":[{"x":5, "y":6}, {"x":4, "y":5}, {"x":2, "y":5}, {"x":2, "y":7}, {"x":0, "y":7}, {"x":0, "y":6}], "name":"Shuffle3", "setArea":null, "id":4, "centre":{"x":2, "y":6}, "getArea":null, "interactable":false}, "5":{"positions":[{"x":13, "y":4}, {"x":12, "y":7}, {"x":14, "y":9}, {"x":11, "y":10}, {"x":8, "y":10}, {"x":8, "y":4}, {"x":0, "y":4}, {"x":1, "y":4}, {"x":2, "y":4}, {"x":3, "y":4}, {"x":4, "y":4}, {"x":4, "y":3}, {"x":4, "y":2}, {"x":4, "y":1}, {"x":4, "y":0}], "name":"Vine Spawn", "setArea":null, "id":5, "centre":{"x":6, "y":5}, "getArea":null, "interactable":false}, "6":{"positions":{}, "name":"Chest Test", "setArea":null, "id":6, "centre":{"x":0, "y":0}, "getArea":null, "interactable":false}, "7":{"positions":[{"x":0, "y":1}, {"x":1, "y":1}, {"x":2, "y":1}, {"x":3, "y":1}, {"x":3, "y":2}, {"x":2, "y":2}, {"x":1, "y":2}, {"x":0, "y":2}, {"x":0, "y":3}, {"x":1, "y":3}, {"x":2, "y":3}, {"x":3, "y":3}, {"x":4, "y":0}, {"x":4, "y":1}, {"x":4, "y":2}, {"x":4, "y":3}, {"x":4, "y":4}, {"x":3, "y":4}, {"x":2, "y":4}, {"x":1, "y":4}, {"x":0, "y":4}], "name":"Capture Area", "setArea":null, "id":7, "centre":{"x":2, "y":2}, "getArea":null, "interactable":false}}, "Map_Tile_15_10":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":10, "x":15}, "state":{}, "id":13, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":10, "x":15}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_10_13":{"terrain":"mountain"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"mountain"}, "Map_Tile_15_7":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":15}, "state":{}, "id":14, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":7, "x":15}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_2_11":{"terrain":"road"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"mountain"}, "Map_Tile_7_5":{"terrain":"mountain"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"mountain"}, "Map_Tile_15_3":{"terrain":"mountain"}, "Map_Tile_1_13":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":13, "x":1}, "state":{}, "id":12, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":13, "x":1}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_10_3":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":10}, "state":{}, "id":20, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":3, "x":10}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_15_2":{"unit":{"unitClassId":"commander_greenfinger", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":3, "y":2, "x":15}, "state":{}, "id":26, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":["greenfingerAttack"], "maxGroove":250, "aliasId":"", "isCommander":true, "isDamagingParentUnit":false, "isStructure":false, "verbCostMultiplier":1.0, "resourceCost":3, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"commander_greenfinger", "moveRange":4, "inAir":false, "isRecruitable":false, "canReinforce":false, "tags":["commander", "type.ground.light"], "transportTags":{}, "canBeCaptured":false, "movementType":"walking", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":[{"unitIdWhenAttacking":"", "directionality":"omni", "canMoveAndAttack":true, "maxRange":1, "blockedByEnemies":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"greenfingerAttack"}]}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"", "rangedDamageTakenPercent":100, "grooveId":"vine_wall", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":3, "y":2, "x":15}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"mountain"}, "Map_Tile_15_1":{"terrain":"mountain"}, "Map_Tile_15_0":{"terrain":"mountain"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_8_14":{"terrain":"mountain"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"mountain"}, "Map_Tile_6_3":{"terrain":"mountain"}, "Map_Tile_5_13":{"terrain":"mountain"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"road"}, "Map_Tile_1_14":{"terrain":"mountain"}, "Map_Tile_11_13":{"terrain":"mountain"}, "Map_Tile_3_12":{"unit":{"unitClassId":"commander_mercia", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":12, "x":3}, "state":{}, "id":19, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":0, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":["merciaSword"], "maxGroove":250, "aliasId":"", "isCommander":true, "isDamagingParentUnit":false, "isStructure":false, "verbCostMultiplier":1.0, "resourceCost":3, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"commander_mercia", "moveRange":4, "inAir":false, "isRecruitable":false, "canReinforce":false, "tags":["commander", "type.ground.light"], "transportTags":{}, "canBeCaptured":false, "movementType":"walking", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":[{"unitIdWhenAttacking":"", "directionality":"omni", "canMoveAndAttack":true, "maxRange":1, "blockedByEnemies":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"merciaSword"}]}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"", "rangedDamageTakenPercent":100, "grooveId":"heal_aura", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":12, "x":3}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_10_11":{"terrain":"mountain"}, "Map_Tile_14_0":{"unit":{"unitClassId":"knight", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":3, "y":0, "x":14}, "state":{}, "id":23, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":["lance"], "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":false, "verbCostMultiplier":1.0, "resourceCost":2, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":600, "id":"knight", "moveRange":6, "inAir":false, "isRecruitable":true, "canReinforce":false, "tags":["knight", "type.ground.heavy"], "transportTags":{}, "canBeCaptured":false, "movementType":"riding", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":[{"unitIdWhenAttacking":"", "directionality":"omni", "canMoveAndAttack":true, "maxRange":1, "blockedByEnemies":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"lance"}]}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":3, "y":0, "x":14}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"mountain"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_11_0":{"terrain":"mountain"}, "Map_Tile_13_9":{"terrain":"mountain"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"road"}, "Map_Tile_13_1":{"terrain":"road"}, "Map_Tile_13_0":{"terrain":"road"}, "Map_Tile_5_9":{"terrain":"mountain"}, "Map_Tile_7_13":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":13, "x":7}, "state":{}, "id":9, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":13, "x":7}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_12_9":{"terrain":"mountain"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_5_6":{"unit":{"unitClassId":"city", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":6, "x":5}, "state":{}, "id":21, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":-1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"city", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":0, "y":6, "x":5}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_13_3":{"terrain":"mountain"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"mountain"}, "Map_Tile_11_12":{"unit":{"unitClassId":"spearman", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":3, "y":12, "x":11}, "state":{}, "id":27, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":["spear"], "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":false, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":250, "id":"spearman", "moveRange":3, "inAir":false, "isRecruitable":true, "canReinforce":false, "tags":["spearman", "type.ground.light"], "transportTags":{}, "canBeCaptured":false, "movementType":"walking", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":[{"unitIdWhenAttacking":"", "directionality":"omni", "canMoveAndAttack":true, "maxRange":1, "blockedByEnemies":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canAttackAir":false, "canAttackSubmerged":false, "canCounterAttack":true, "minRange":1, "id":"spear"}]}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":{}, "pos":{"facing":3, "y":12, "x":11}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"mountain"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"mountain"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"mountain"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"mountain"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_11_1":{"unit":{"unitClassId":"barracks", "canBeAttackedFromDistance":true, "setHealth":null, "blessings":{}, "setGroove":null, "factionOverride":"", "merchantDiscounts":{}, "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":11}, "state":{}, "id":5, "transportedBy":-1, "miniGrooveId":"", "hasBeenKilled":false, "underwater":false, "itemDropNumber":0, "recruitDiscountMultiplier":0.0, "canChargeGroove":true, "grooveCharge":0, "playerId":1, "health":100, "hadTurn":false, "inTransport":false, "unitClass":{"weaponIds":{}, "maxGroove":0, "aliasId":"", "isCommander":false, "isDamagingParentUnit":false, "isStructure":true, "verbCostMultiplier":1.0, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "inWater":false, "isAttackable":true, "canBeActivated":false, "cost":500, "id":"barracks", "moveRange":0, "inAir":false, "isRecruitable":true, "canReinforce":true, "tags":["structure"], "transportTags":{}, "canBeCaptured":true, "movementType":"land_building", "canAttack":true, "reinforceMultiplier":1.0, "maxHealth":100, "weapons":{}}, "recruitDiscounts":{}, "attachedFlagId":-1, "tentacled":false, "attackerId":-1, "stunned":false, "damageTakenPercent":100, "itemId":"", "garrisonClassId":"garrison", "rangedDamageTakenPercent":100, "grooveId":"", "merchantDiscountMultiplier":0.0, "loadedUnits":{}, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "pos":{"facing":0, "y":1, "x":11}, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "attackerUnitClass":""}, "terrain":"plains"}, "Map_Tile_15_9":{"terrain":"mountain"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"mountain"}, "Map_Tile_10_6":{"terrain":"mountain"}, "Map_Tile_2_4":{"terrain":"river"}, "Map_Tile_3_13":{"terrain":"mountain"}, "Map_Tile_8_0":{"terrain":"mountain"}, "Map_Tile_8_3":{"terrain":"road"}, "Player_Count":2, "Map_Tile_0_8":{"terrain":"mountain"}, "Map_Tile_16_3":{"terrain":"mountain"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Gold_Rush.json b/worlds/wargroove2/levels/Gold_Rush.json new file mode 100644 index 000000000000..5ffa8de91110 --- /dev/null +++ b/worlds/wargroove2/levels/Gold_Rush.json @@ -0,0 +1 @@ +{"Map_Tile_2_1":{"terrain":"beach"}, "Map_Tile_6_10":{"terrain":"beach"}, "Map_Tile_0_4":{"terrain":"sea"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_2_5":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":5, "x":2, "facing":0}, "startPos":{"y":5, "x":2, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":19, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_2_9":{"terrain":"sea"}, "Map_Tile_12_12":{"terrain":"road"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"sea"}, "Map_Tile_1_12":{"terrain":"sea"}, "Map_Tile_13_2":{"terrain":"road"}, "Author":"Magnemania", "Map_Tile_1_7":{"terrain":"sea"}, "Map_Tile_1_3":{"terrain":"bridge"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"sea"}, "Map_Tile_14_0":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":0, "x":14, "facing":0}, "startPos":{"y":0, "x":14, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"barracks", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"barracks", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":28, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_4_3":{"terrain":"sea"}, "Map_Tile_1_6":{"terrain":"sea"}, "Map_Tile_6_4":{"terrain":"bridge"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Triggers":[{"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "actions":[{"id":"ap_export", "enabled":true, "parameters":["1", "Gold Rush", "Magnemania", "Capture the Lumber Mill. (Requires Merfolk, Riverboat, or Barge)", "Reach the Starglass Brew by Turn 3. (Requires Riverboat or Barge)", "", "Reach 3000 gold. (Requires Thief, as well as Rifleman, Merfolk, or Warship)"]}], "recurring":"start_of_match", "id":"Export (Always on Top)", "isIntro":false, "enabled":true}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "conditions":{}, "actions":[{"id":"modify_gold", "enabled":true, "parameters":["P1", "0", "700"]}], "recurring":"once", "id":"Set AI", "isIntro":false, "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":{}, "actions":[{"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "any", "0", "0", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "any", "1", "1", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "any", "2", "2", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "any", "3", "3", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "any", "4", "4", "1"]}], "recurring":"start_of_match", "id":"Shuffle Units", "isIntro":false, "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "4", "1", "lumbermill", "-1"]}], "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253341"]}], "recurring":"once", "id":"Capture Lumbermill (Check 253341)", "isIntro":false, "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"current_turn_number", "enabled":true, "parameters":["5", "3"]}, {"id":"unit_presence", "enabled":true, "parameters":["current", "4", "1", "*unit", "5"]}], "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253342"]}], "recurring":"once", "id":"Reach Brew (Check 253342)", "isIntro":false, "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "isIntro":false, "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["*commander", "current", "-1"]}], "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "isIntro":false, "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["hq", "current", "-1"]}], "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "isIntro":false, "enabled":true}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"number_of_opponents", "enabled":true, "parameters":["current", "0", "0"]}], "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_victory", "isIntro":false, "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"check_funds", "enabled":true, "parameters":["current", "4", "3000"]}], "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["extra", "wulfar_pirate", "We've got the gold! Let's git oot o' 'ere!", "0", ""]}, {"id":"victory", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"Victory (3000 Gold)", "isIntro":false, "enabled":true}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"id":"player_victorious", "enabled":true, "parameters":["current"]}], "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253340"]}], "recurring":"once", "id":"P1 Wins (Check 253340)", "isIntro":false, "enabled":true}], "Map_Name":"Gold Rush", "Map_Tile_2_7":{"terrain":"sea"}, "Map_Tile_9_10":{"terrain":"wall"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Flags":{}, "Map_Tile_0_9":{"terrain":"sea"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_5_9":{"terrain":"sea"}, "Map_Tile_6_2":{"terrain":"forest"}, "Map_Tile_1_13":{"terrain":"plains"}, "Locations":{"1":{"name":"Water Village Shuffle", "getArea":null, "interactable":false, "centre":{"x":4, "y":7}, "id":1, "positions":[{"x":1, "y":9}, {"x":1, "y":7}, {"x":5, "y":5}, {"x":7, "y":5}], "setArea":null}, "2":{"name":"Allied Army Shuffle", "getArea":null, "interactable":false, "centre":{"x":9, "y":8}, "id":2, "positions":[{"x":8, "y":9}, {"x":9, "y":9}, {"x":9, "y":7}, {"x":10, "y":7}, {"x":9, "y":8}, {"x":8, "y":8}, {"x":10, "y":9}], "setArea":null}, "3":{"name":"Shuffle2", "getArea":null, "interactable":false, "centre":{"x":14, "y":11}, "id":3, "positions":[{"x":14, "y":8}, {"x":13, "y":10}, {"x":13, "y":13}, {"x":15, "y":12}], "setArea":null}, "4":{"name":"Shuffle3", "getArea":null, "interactable":false, "centre":{"x":13, "y":4}, "id":4, "positions":[{"x":15, "y":5}, {"x":12, "y":2}, {"x":12, "y":4}], "setArea":null}, "0":{"name":"Shuffle1", "getArea":null, "interactable":false, "centre":{"x":9, "y":12}, "id":0, "positions":[{"x":7, "y":13}, {"x":8, "y":13}, {"x":8, "y":11}, {"x":9, "y":11}, {"x":11, "y":13}, {"x":11, "y":12}], "setArea":null}, "5":{"name":"Star Brew", "getArea":null, "interactable":false, "centre":{"x":3, "y":1}, "id":5, "positions":[{"x":3, "y":1}, {"x":4, "y":1}, {"x":3, "y":0}, {"x":2, "y":1}, {"x":3, "y":2}], "setArea":null}}, "Map_Tile_2_13":{"terrain":"plains"}, "Player_1":{"recruit_griffin_walking":true, "recruit_knight":true, "recruit_archer":true, "recruit_merman":true, "recruit_turtle":true, "recruit_balloon":true, "recruit_travelboat":true, "recruit_witch":true, "recruit_rifleman":true, "recruit_mage":true, "team":0, "recruit_trebuchet":true, "recruit_kraken":true, "recruit_dragon":true, "recruit_giant":true, "recruit_dog":true, "recruit_soldier":true, "recruit_harpoonship":true, "recruit_thief":true, "gold":100, "recruit_spearman":true, "recruit_caravel":true, "recruit_wagon":true, "recruit_warship":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_frog":true}, "Map_Tile_6_6":{"terrain":"bridge"}, "Map_Tile_10_0":{"terrain":"sea"}, "Map_Tile_0_0":{"terrain":"sea"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"beach"}, "Map_Tile_0_12":{"terrain":"sea"}, "Map_Tile_1_9":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":9, "x":1, "facing":0}, "startPos":{"y":9, "x":1, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"sea_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"water_city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"water_city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":14, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"sea"}, "Player_Count":2, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_9_7":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":7, "x":9, "facing":3}, "startPos":{"y":7, "x":9, "facing":3}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["soldier", "type.ground.light"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canCounterAttack":true, "terrainExclusion":{}, "id":"sword", "maxRange":1, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.5, "movementType":"walking", "moveRange":4, "weaponIds":["sword"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"soldier", "cost":100, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"soldier", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":25, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"sea"}, "Map_Tile_12_2":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":2, "x":12, "facing":0}, "startPos":{"y":2, "x":12, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":1, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_2_11":{"terrain":"sea"}, "Map_Tile_1_5":{"terrain":"sea"}, "Map_Tile_2_0":{"terrain":"sea"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_9_9":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":9, "x":9, "facing":3}, "startPos":{"y":9, "x":9, "facing":3}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["soldier", "type.ground.light"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canCounterAttack":true, "terrainExclusion":{}, "id":"sword", "maxRange":1, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.5, "movementType":"walking", "moveRange":4, "weaponIds":["sword"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"soldier", "cost":100, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"soldier", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":24, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"beach"}, "Map_Tile_15_7":{"terrain":"river"}, "Map_Tile_0_6":{"terrain":"sea"}, "Map_Tile_7_8":{"terrain":"bridge"}, "Map_Tile_15_6":{"terrain":"river"}, "Map_Tile_6_9":{"terrain":"sea"}, "Objectives":["Capture the Lumber Mill. (Requires Merfolk, Riverboat, or Barge)", "Reach the Starglass Brew by Turn 3. (Requires Riverboat or Barge)", "Reach 3000 gold. (Requires Thief, as well as Rifleman, Merfolk, or Warship)"], "Map_Tile_8_9":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"heal_aura", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":9, "x":8, "facing":3}, "startPos":{"y":9, "x":8, "facing":3}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["commander", "type.ground.light"], "maxGroove":250, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canCounterAttack":true, "terrainExclusion":{}, "id":"merciaSword", "maxRange":1, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":3, "isDamagingParentUnit":false, "isRecruitable":false, "passiveMultiplier":1.0, "movementType":"walking", "moveRange":4, "weaponIds":["merciaSword"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":true, "transportTags":{}, "id":"commander_mercia", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"commander_mercia", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":23, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_15_3":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":3, "x":15, "facing":0}, "startPos":{"y":3, "x":15, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"hideout", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"hideout", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":["thief", "rifleman"], "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":2, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_0_13":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":13, "x":0, "facing":0}, "startPos":{"y":13, "x":0, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"barracks", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"barracks", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":3, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_15_2":{"terrain":"road"}, "Map_Tile_15_1":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":1, "x":15, "facing":3}, "startPos":{"y":1, "x":15, "facing":3}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["spearman", "type.ground.light"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canCounterAttack":true, "terrainExclusion":{}, "id":"spear", "maxRange":1, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.5, "movementType":"walking", "moveRange":3, "weaponIds":["spear"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"spearman", "cost":250, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"spearman", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":21, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"sea"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"road"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"sea"}, "Map_Size":{"x":16, "y":14}, "Map_Tile_10_6":{"terrain":"river"}, "Map_Tile_3_1":{"terrain":"plains", "item":{"type":"wind_potion", "pos":{"x":3, "y":1}, "unitTypeRestriction":{}, "isConsumable":true, "itemId":29}}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"road"}, "Map_Tile_7_2":{"terrain":"forest"}, "Map_Tile_11_13":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":13, "x":11, "facing":0}, "startPos":{"y":13, "x":11, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":6, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"road"}, "Map_Tile_8_1":{"terrain":"forest"}, "Map_Tile_4_7":{"terrain":"bridge"}, "Map_Tile_13_9":{"terrain":"road"}, "Map_Tile_14_8":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":8, "x":14, "facing":0}, "startPos":{"y":8, "x":14, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":8, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_7_13":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":13, "x":7, "facing":0}, "startPos":{"y":13, "x":7, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":5, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_5_1":{"terrain":"sea"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"road"}, "Map_Tile_1_2":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":2, "x":1, "facing":0}, "startPos":{"y":2, "x":1, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["spearman", "type.ground.light"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canCounterAttack":true, "terrainExclusion":{}, "id":"spear", "maxRange":1, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.5, "movementType":"walking", "moveRange":3, "weaponIds":["spear"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"spearman", "cost":250, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"spearman", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":20, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_8_10":{"terrain":"forest"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_4_13":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":13, "x":4, "facing":0}, "startPos":{"y":13, "x":4, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["soldier", "type.ground.light"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canCounterAttack":true, "terrainExclusion":{}, "id":"sword", "maxRange":1, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.5, "movementType":"walking", "moveRange":4, "weaponIds":["sword"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"soldier", "cost":100, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"soldier", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":22, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"beach"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"road"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_0_11":{"terrain":"sea"}, "Map_Tile_0_7":{"terrain":"sea"}, "Map_Tile_8_2":{"terrain":"forest"}, "Map_Tile_9_0":{"terrain":"sea"}, "Map_Tile_12_6":{"terrain":"river"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"bridge"}, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_8_7":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":7, "x":8, "facing":0}, "startPos":{"y":7, "x":8, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"river_sea_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"port", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"port", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":11, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"sea"}, "Map_Tile_3_9":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"sea"}, "Map_Tile_12_3":{"terrain":"forest"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"forest"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"sea"}, "Map_Tile_8_11":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":11, "x":8, "facing":0}, "startPos":{"y":11, "x":8, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":4, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_3_7":{"terrain":"bridge"}, "Map_Tile_11_9":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":9, "x":11, "facing":0}, "startPos":{"y":9, "x":11, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"hideout", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"hideout", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":["thief", "rifleman"], "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":12, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_11_8":{"terrain":"plains"}, "Player_2":{"recruit_griffin_walking":true, "recruit_knight":false, "recruit_archer":true, "recruit_merman":true, "recruit_turtle":true, "recruit_balloon":true, "recruit_travelboat":true, "recruit_witch":true, "recruit_rifleman":true, "recruit_mage":true, "team":1, "recruit_trebuchet":false, "recruit_kraken":true, "recruit_dragon":true, "recruit_giant":false, "recruit_dog":true, "recruit_soldier":true, "recruit_harpoonship":true, "recruit_thief":true, "gold":100, "recruit_spearman":true, "recruit_caravel":true, "recruit_wagon":true, "recruit_warship":true, "recruit_harpy":true, "recruit_ballista":false, "recruit_frog":true}, "Map_Tile_11_6":{"terrain":"river"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"bridge"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"sea"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"road"}, "Map_Tile_0_5":{"terrain":"sea"}, "Map_Tile_11_0":{"terrain":"sea"}, "Map_Tile_9_8":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":8, "x":9, "facing":3}, "startPos":{"y":8, "x":9, "facing":3}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["archer", "type.ground.light"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":true, "canCounterAttack":true, "terrainExclusion":{}, "id":"bow", "maxRange":3, "canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "minRange":1, "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.3500000238419, "movementType":"walking", "moveRange":3, "weaponIds":["bow"], "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":false, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"archer", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"archer", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":27, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"forest"}, "Map_Tile_5_2":{"terrain":"reef"}, "Map_Tile_10_8":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":0, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":8, "x":10, "facing":0}, "startPos":{"y":8, "x":10, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":false, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":false, "isCommander":false, "transportTags":{}, "id":"hq", "cost":3000, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"hq", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":10, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"wall"}, "Map_Tile_10_2":{"terrain":"beach"}, "Map_Tile_4_0":{"terrain":"sea"}, "Map_Tile_10_1":{"terrain":"sea"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"sea"}, "Map_Tile_2_8":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"beach"}, "Map_Tile_1_1":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":1, "x":1, "facing":0}, "startPos":{"y":1, "x":1, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":18, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_9_2":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":2, "x":9, "facing":0}, "startPos":{"y":2, "x":9, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"sea_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"water_city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"water_city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":17, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"sea"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"sea"}, "Map_Tile_5_5":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":5, "x":5, "facing":0}, "startPos":{"y":5, "x":5, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"sea_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"water_city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"water_city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":16, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"sea"}, "Map_Tile_12_7":{"terrain":"plains"}, "Map_Tile_15_5":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":5, "x":15, "facing":0}, "startPos":{"y":5, "x":15, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":9, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_7_9":{"terrain":"wall"}, "Map_Tile_9_6":{"terrain":"river"}, "Map_Tile_4_1":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"sea"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"bridge"}, "Map_Tile_3_10":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"sea"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"sea"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_6_5":{"terrain":"reef"}, "Map_Tile_9_1":{"terrain":"sea"}, "Map_Tile_15_12":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":12, "x":15, "facing":0}, "startPos":{"y":12, "x":15, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":7, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_6_3":{"terrain":"bridge"}, "Map_Tile_3_3":{"terrain":"bridge"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"bridge"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"bridge"}, "Map_Tile_3_11":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":11, "x":3, "facing":0}, "startPos":{"y":11, "x":3, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":13, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_3_0":{"terrain":"sea"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_1_0":{"terrain":"sea"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"sea"}, "Counters":{}, "Map_Tile_1_8":{"terrain":"sea"}, "Map_Tile_2_6":{"terrain":"sea"}, "Map_Tile_4_5":{"terrain":"sea"}, "Map_Tile_4_8":{"terrain":"sea"}, "Map_Tile_14_6":{"terrain":"river"}, "Map_Tile_0_3":{"terrain":"sea"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"sea"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_7_1":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":1, "x":7, "facing":0}, "startPos":{"y":1, "x":7, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"land_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"lumbermill", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"lumbermill", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":26, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"plains"}, "Map_Tile_6_8":{"terrain":"bridge"}, "Map_Tile_7_3":{"terrain":"beach"}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_6_0":{"terrain":"sea"}, "Map_Tile_5_8":{"unit":{"state":{}, "transportedBy":-1, "attackerId":-1, "grooveId":"", "playerId":-1, "hadTurn":false, "recruitDiscountMultiplier":0.0, "stunned":false, "grooveCharge":0, "rangedDamageTakenPercent":100, "itemDropNumber":0, "loadedUnits":{}, "recruitDiscounts":{}, "hasBeenKilled":false, "attackerUnitClass":"", "factionOverride":"", "canChargeGroove":true, "pos":{"y":8, "x":5, "facing":0}, "startPos":{"y":8, "x":5, "facing":0}, "tentacled":false, "killedByLosing":false, "unitClass":{"canAttack":true, "isAttackable":true, "tags":["structure"], "maxGroove":0, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "isDamagingParentUnit":false, "isRecruitable":true, "passiveMultiplier":1.0, "movementType":"sea_building", "moveRange":0, "weaponIds":{}, "critConditionId":"", "inWater":false, "canBeCaptured":true, "isStructure":true, "inAir":false, "canBeActivated":false, "canReinforce":true, "isCommander":false, "transportTags":{}, "id":"water_city", "cost":500, "maxHealth":100, "aliasId":"", "reinforceMultiplier":1.0, "loadCapacity":0}, "canBeAttackedFromDistance":true, "setGroove":null, "garrisonClassId":"garrison", "itemId":"", "health":100, "merchantDiscounts":{}, "unitClassId":"water_city", "attachedFlagId":-1, "inTransport":false, "setHealth":null, "merchantDiscountMultiplier":0.0, "recruits":{}, "canBeAttacked":true, "underwater":false, "damageTakenPercent":100, "items":{}, "id":15, "blessings":{}, "attackerPlayerId":-1, "miniGrooveId":""}, "terrain":"sea"}, "Map_Tile_5_4":{"terrain":"sea"}, "Map_Tile_10_4":{"terrain":"beach"}, "Map_Tile_13_5":{"terrain":"road"}, "Map_Tile_9_5":{"terrain":"sea"}, "Map_Tile_13_10":{"terrain":"forest"}, "Map_Tile_3_8":{"terrain":"sea"}, "Map_Tile_12_8":{"terrain":"forest"}, "Map_Tile_0_1":{"terrain":"sea"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Grand_Theft_Village.json b/worlds/wargroove2/levels/Grand_Theft_Village.json new file mode 100644 index 000000000000..2c32add8f8f2 --- /dev/null +++ b/worlds/wargroove2/levels/Grand_Theft_Village.json @@ -0,0 +1 @@ +{"Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_9_16":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":1000, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["fortified_city"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"fortified_city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":7, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":9, "y":16, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"fortified_garrison", "unitClassId":"fortified_city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":9, "y":16, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"forest"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"forest"}, "Map_Tile_13_2":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "playerId":1, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"tower", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":14, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":13, "y":2, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"tower", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":13, "y":2, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_1_1":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "playerId":1, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"tower", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":5, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":1, "y":1, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"tower", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":1, "y":1, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"road"}, "Map_Tile_2_10":{"terrain":"mountain"}, "Author":"Fly Sniper", "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"road"}, "Map_Tile_14_8":{"terrain":"cobblestone"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_0_15":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"road"}, "Map_Tile_8_11":{"terrain":"road"}, "Map_Tile_3_7":{"terrain":"forest"}, "Map_Tile_4_15":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":12, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":4, "y":15, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":4, "y":15, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_13_16":{"terrain":"plains"}, "Map_Tile_4_16":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":1000, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["fortified_city"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"fortified_city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":6, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":4, "y":16, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"fortified_garrison", "unitClassId":"fortified_city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":4, "y":16, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"road"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_7_8":{"item":{"isConsumable":false, "type":"fountain_of_youth", "pos":{"y":8, "x":7}, "unitTypeRestriction":{}, "itemId":17}, "terrain":"road"}, "Player_2":{"gold":100, "recruit_witch":true, "recruit_turtle":true, "recruit_warship":true, "recruit_knight":true, "recruit_harpoonship":true, "recruit_dragon":true, "recruit_kraken":true, "recruit_archer":true, "recruit_frog":true, "recruit_giant":true, "recruit_merman":true, "recruit_soldier":true, "recruit_balloon":true, "recruit_caravel":true, "recruit_travelboat":true, "recruit_spearman":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_wagon":true, "recruit_dog":true, "recruit_mage":true, "recruit_trebuchet":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_griffin_walking":false, "team":1}, "Map_Tile_9_15":{"terrain":"forest"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_2_13":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":["thief", "rifleman"], "playerId":0, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"hideout_ap", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":4, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":2, "y":13, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"hideout_ap", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":2, "y":13, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_2_11":{"terrain":"road"}, "Map_Tile_12_1":{"terrain":"road"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"road"}, "Map_Tile_9_8":{"terrain":"forest"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_6_16":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"mountain"}, "Map_Tile_5_6":{"terrain":"forest"}, "Map_Tile_1_9":{"terrain":"road"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"mountain"}, "Map_Tile_9_9":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"road"}, "Map_Tile_9_10":{"terrain":"forest"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"road"}, "Map_Tile_8_6":{"terrain":"mountain"}, "Map_Tile_1_16":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"road"}, "Map_Tile_8_7":{"terrain":"mountain"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"road"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"road"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"cobblestone"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"mountain"}, "Map_Tile_7_15":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":11, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":7, "y":15, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":7, "y":15, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"road"}, "Map_Tile_4_2":{"terrain":"road"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_9_11":{"terrain":"road"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"plains"}, "Triggers":[{"isIntro":false, "enabled":true, "conditions":{}, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "actions":[{"parameters":["91253", "Grand Theft Village", "Fly Sniper", "Build a Giant.", "Defeat a village (No Requirements).", "", "Win with standard conditions (Requires Thief and Anti-Air)."], "enabled":true, "id":"ap_export"}], "id":"Export"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "id":"$trigger_default_defeat_no_units"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "id":"$trigger_default_defeat_commander"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "id":"$trigger_default_defeat_hq"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "id":"$trigger_default_victory"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["P1"], "enabled":true, "id":"player_victorious"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "actions":[{"parameters":["253036"], "enabled":true, "id":"ap_location_send"}], "id":"P1 Victory (253036)"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["current", "1", "0", "giant", "-1"], "enabled":true, "id":"unit_presence"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "actions":[{"parameters":["253037"], "enabled":true, "id":"ap_location_send"}], "id":"Build Giant (253037)"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["neutral", "1", "0", "city", "-1"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "0"], "enabled":true, "id":"check_map_flag"}, {"parameters":["*unit_structure", "P1", "*structure", "P2", "1"], "enabled":true, "id":"unit_killed"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "actions":[{"parameters":["253038"], "enabled":true, "id":"ap_location_send"}, {"parameters":["0", "1"], "enabled":true, "id":"set_map_flag"}], "id":"Defeat Village (253038)"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["neutral", "1", "0", "fortified_city", "-1"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "0"], "enabled":true, "id":"check_map_flag"}, {"parameters":["*unit_structure", "P1", "*structure", "P2", "1"], "enabled":true, "id":"unit_killed"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "actions":[{"parameters":["253038"], "enabled":true, "id":"ap_location_send"}, {"parameters":["0", "1"], "enabled":true, "id":"set_map_flag"}], "id":"Defeat Fortified Village (253038)"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["neutral", "1", "0", "city", "-1"], "enabled":true, "id":"unit_presence"}, {"parameters":["0", "0"], "enabled":true, "id":"check_map_flag"}, {"parameters":["*unit_structure", "P1", "*structure", "P2", "5"], "enabled":true, "id":"unit_killed"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "actions":[{"parameters":["253038"], "enabled":true, "id":"ap_location_send"}, {"parameters":["0", "1"], "enabled":true, "id":"set_map_flag"}], "id":"Defeat Center Village (253038)"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["neutral", "1", "0", "fortified_city", "-1"], "enabled":true, "id":"unit_presence"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"repeat", "actions":[{"parameters":["fortified_city", "-1", "neutral", "1", "0"], "enabled":true, "id":"remove_units"}], "id":"Delete Fortified Village"}, {"isIntro":false, "enabled":true, "conditions":[{"parameters":["neutral", "1", "0", "city", "-1"], "enabled":true, "id":"unit_presence"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"repeat", "actions":[{"parameters":["city", "-1", "neutral", "1", "0"], "enabled":true, "id":"remove_units"}], "id":"Delete Village"}, {"isIntro":false, "enabled":true, "conditions":{}, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}], "id":"Init"}], "Map_Tile_12_7":{"terrain":"road"}, "Map_Tile_11_2":{"terrain":"road"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_3_16":{"terrain":"forest"}, "Map_Tile_10_11":{"terrain":"road"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_11_13":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "playerId":0, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"barracks_ap", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":3, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":11, "y":13, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"barracks_ap", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":11, "y":13, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"road"}, "Map_Tile_6_11":{"terrain":"road"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"road"}, "Map_Tile_11_11":{"terrain":"road"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"road"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"road"}, "Map_Tile_4_1":{"terrain":"road"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_5_16":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"forest"}, "Map_Tile_9_0":{"terrain":"forest"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_2_16":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"road"}, "Map_Tile_6_10":{"terrain":"plains"}, "Counters":{}, "Map_Tile_9_4":{"terrain":"forest"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"road"}, "Map_Tile_13_6":{"terrain":"forest"}, "Map_Tile_2_9":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"road"}, "Map_Tile_7_1":{"terrain":"forest"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_5_4":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":3000, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":false, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"hq", "reinforceMultiplier":1.0, "canBeCaptured":false}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":15, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":5, "y":4, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"hq", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":5, "y":4, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_1_15":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":1000, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["fortified_city"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"fortified_city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":8, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":1, "y":15, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"fortified_garrison", "unitClassId":"fortified_city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":1, "y":15, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_3_15":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"cobblestone"}, "Flags":{"0":0}, "Map_Tile_4_13":{"terrain":"plains"}, "Locations":{"1":{"interactable":false, "setArea":null, "getArea":null, "centre":{"y":15, "x":7}, "name":"P2 Shuffle 1", "positions":[{"y":16, "x":14}, {"y":15, "x":14}, {"y":14, "x":14}, {"y":14, "x":13}, {"y":14, "x":12}, {"y":14, "x":11}, {"y":14, "x":10}, {"y":14, "x":9}, {"y":14, "x":8}, {"y":14, "x":7}, {"y":14, "x":6}, {"y":14, "x":5}, {"y":14, "x":4}, {"y":14, "x":3}, {"y":14, "x":2}, {"y":14, "x":1}, {"y":14, "x":0}, {"y":15, "x":0}, {"y":16, "x":0}, {"y":16, "x":1}, {"y":16, "x":2}, {"y":16, "x":3}, {"y":16, "x":4}, {"y":16, "x":12}, {"y":16, "x":13}, {"y":16, "x":11}, {"y":16, "x":10}, {"y":16, "x":9}, {"y":16, "x":8}, {"y":16, "x":7}, {"y":16, "x":6}, {"y":16, "x":5}, {"y":15, "x":2}, {"y":15, "x":1}, {"y":15, "x":3}, {"y":15, "x":4}, {"y":15, "x":5}, {"y":15, "x":6}, {"y":15, "x":7}, {"y":15, "x":8}, {"y":15, "x":9}, {"y":15, "x":10}, {"y":15, "x":11}, {"y":15, "x":12}, {"y":15, "x":13}], "id":1}, "2":{"interactable":false, "setArea":null, "getArea":null, "centre":{"y":1, "x":7}, "name":"P2 Shuffle 2", "positions":[{"y":2, "x":13}, {"y":2, "x":12}, {"y":2, "x":11}, {"y":2, "x":10}, {"y":2, "x":9}, {"y":2, "x":8}, {"y":2, "x":7}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}, {"y":2, "x":3}, {"y":2, "x":2}, {"y":2, "x":1}, {"y":1, "x":1}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":0, "x":1}, {"y":2, "x":0}, {"y":1, "x":2}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":1, "x":3}, {"y":1, "x":4}, {"y":0, "x":4}, {"y":0, "x":5}, {"y":0, "x":6}, {"y":0, "x":7}, {"y":0, "x":8}, {"y":0, "x":9}, {"y":0, "x":10}, {"y":0, "x":11}, {"y":0, "x":12}, {"y":0, "x":13}, {"y":0, "x":14}, {"y":1, "x":14}, {"y":2, "x":14}, {"y":1, "x":13}, {"y":1, "x":12}, {"y":1, "x":11}, {"y":1, "x":10}, {"y":1, "x":9}, {"y":1, "x":8}, {"y":1, "x":7}, {"y":1, "x":6}, {"y":1, "x":5}], "id":2}, "3":{"interactable":false, "setArea":null, "getArea":null, "centre":{"y":4, "x":7}, "name":"P2 HQ Shuffle", "positions":[{"y":4, "x":13}, {"y":4, "x":12}, {"y":4, "x":11}, {"y":4, "x":10}, {"y":4, "x":9}, {"y":4, "x":8}, {"y":4, "x":7}, {"y":4, "x":6}, {"y":4, "x":5}, {"y":4, "x":4}, {"y":4, "x":3}, {"y":4, "x":2}, {"y":4, "x":1}], "id":3}, "4":{"interactable":false, "setArea":null, "getArea":null, "centre":{"y":10, "x":7}, "name":"P1 HQ Shuffle", "positions":[{"y":10, "x":4}, {"y":10, "x":5}, {"y":10, "x":6}, {"y":10, "x":8}, {"y":10, "x":10}, {"y":10, "x":11}], "id":4}, "0":{"interactable":false, "setArea":null, "getArea":null, "centre":{"y":13, "x":7}, "name":"P1 Shuffle", "positions":[{"y":13, "x":7}, {"y":13, "x":8}, {"y":13, "x":9}, {"y":13, "x":10}, {"y":13, "x":11}, {"y":13, "x":12}, {"y":12, "x":12}, {"y":12, "x":11}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":12, "x":8}, {"y":12, "x":7}, {"y":12, "x":6}, {"y":12, "x":5}, {"y":12, "x":4}, {"y":12, "x":3}, {"y":12, "x":2}, {"y":13, "x":2}, {"y":13, "x":3}, {"y":13, "x":4}, {"y":13, "x":5}, {"y":13, "x":6}], "id":0}, "5":{"interactable":false, "setArea":null, "getArea":null, "centre":{"y":7, "x":7}, "name":"P2 Center Village", "positions":[{"y":7, "x":7}], "id":5}}, "Map_Tile_14_16":{"terrain":"plains"}, "Map_Tile_14_15":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":10, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":14, "y":15, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":14, "y":15, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"road"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"road"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"road"}, "Map_Tile_0_6":{"terrain":"cobblestone"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"cobblestone"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"forest"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Name":"Grand Theft Village", "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_12_16":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":1000, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["fortified_city"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"fortified_city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":13, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":12, "y":16, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"fortified_garrison", "unitClassId":"fortified_city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":12, "y":16, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"road"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"road"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"forest"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"road"}, "Map_Tile_4_6":{"terrain":"forest_cut"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_13_11":{"item":{"isConsumable":true, "type":"small_healing_potion", "pos":{"y":11, "x":13}, "unitTypeRestriction":{}, "itemId":18}, "terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Objectives":["Build a Giant.", "Defeat a village (No Requirements).", "Win with standard conditions (Requires Thief and Anti-Air)."], "Map_Tile_0_11":{"terrain":"road"}, "Player_Count":2, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_5_10":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":0, "unitClass":{"cost":3000, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":false, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"hq", "reinforceMultiplier":1.0, "canBeCaptured":false}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":2, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":5, "y":10, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"hq", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":5, "y":10, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"road"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_3_4":{"terrain":"road"}, "Map_Tile_1_7":{"terrain":"road"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"road"}, "Map_Size":{"y":17, "x":15}, "Map_Tile_7_13":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":0, "unitClass":{"cost":500, "isRecruitable":false, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":[{"horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "directionality":"omni", "id":"merciaSword", "canMoveAndAttack":true, "blockedByEnemies":false, "canCounterAttack":true, "maxRange":1, "canAttackSubmerged":false, "minRange":1, "terrainExclusion":{}, "canAttackAir":false}], "canReinforce":false, "weaponIds":["merciaSword"], "movementType":"walking", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":true, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":4, "canBeActivated":false, "resourceCost":3, "tags":["commander", "type.ground.light"], "maxGroove":250, "aliasId":"", "canAttack":true, "isStructure":false, "id":"commander_mercia", "reinforceMultiplier":1.0, "canBeCaptured":false}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"heal_aura", "id":1, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":7, "y":13, "facing":3}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"", "unitClassId":"commander_mercia", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":7, "y":13, "facing":3}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Player_1":{"gold":1500, "recruit_witch":true, "recruit_turtle":true, "recruit_warship":true, "recruit_knight":true, "recruit_harpoonship":true, "recruit_dragon":true, "recruit_kraken":true, "recruit_archer":true, "recruit_frog":true, "recruit_giant":true, "recruit_merman":true, "recruit_soldier":true, "recruit_balloon":true, "recruit_caravel":true, "recruit_travelboat":true, "recruit_spearman":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_wagon":true, "recruit_dog":true, "recruit_mage":true, "recruit_trebuchet":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "team":0}, "Map_Tile_1_4":{"terrain":"road"}, "Map_Tile_7_7":{"unit":{"miniGrooveId":"", "merchantDiscounts":{}, "underwater":false, "recruits":{}, "playerId":1, "unitClass":{"cost":500, "isRecruitable":true, "passiveMultiplier":1.0, "loadCapacity":0, "verbCostMultiplier":1.0, "weapons":{}, "canReinforce":true, "weaponIds":{}, "movementType":"land_building", "isAttackable":true, "transportTags":{}, "critConditionId":"", "isDamagingParentUnit":false, "inAir":false, "maxHealth":100, "isCommander":false, "recruitingCostMultiplier":1.0, "inWater":false, "moveRange":0, "canBeActivated":false, "resourceCost":1, "tags":["structure"], "maxGroove":0, "aliasId":"", "canAttack":true, "isStructure":true, "id":"city", "reinforceMultiplier":1.0, "canBeCaptured":true}, "attachedFlagId":-1, "killedByLosing":false, "grooveId":"", "id":9, "stunned":false, "state":{}, "itemDropNumber":0, "inTransport":false, "attackerUnitClass":"", "recruitDiscounts":{}, "pos":{"x":7, "y":7, "facing":0}, "setHealth":null, "attackerPlayerId":-1, "canBeAttacked":true, "items":{}, "garrisonClassId":"garrison", "unitClassId":"city", "hasBeenKilled":false, "blessings":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "tentacled":false, "itemId":"", "hadTurn":false, "setGroove":null, "recruitDiscountMultiplier":0.0, "startPos":{"x":7, "y":7, "facing":0}, "canChargeGroove":true, "grooveCharge":0, "transportedBy":-1, "merchantDiscountMultiplier":0.0, "attackerId":-1, "damageTakenPercent":100, "loadedUnits":{}, "factionOverride":"", "health":100}, "terrain":"road"}, "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"road"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"road"}, "Map_Tile_0_16":{"terrain":"forest"}, "Map_Tile_1_6":{"terrain":"road"}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"mountain"}, "Map_Tile_4_0":{"terrain":"road"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"forest"}, "Map_Tile_1_8":{"terrain":"road"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"road"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"road"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"forest_cut"}, "Map_Tile_12_0":{"terrain":"road"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"road"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"road"}, "Map_Tile_0_8":{"item":{"isConsumable":true, "type":"swift_potion", "pos":{"y":8, "x":0}, "unitTypeRestriction":{}, "itemId":16}, "terrain":"cobblestone"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"road"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"road"}, "Map_Tile_2_15":{"terrain":"plains"}, "Map_Tile_1_14":{"terrain":"plains"}, "Map_Tile_2_12":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Kraken_Strait.json b/worlds/wargroove2/levels/Kraken_Strait.json new file mode 100644 index 000000000000..24dc2b20e9fa --- /dev/null +++ b/worlds/wargroove2/levels/Kraken_Strait.json @@ -0,0 +1 @@ +{"Map_Tile_10_8":{"terrain":"bridge"}, "Map_Tile_6_10":{"terrain":"sea"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"ocean"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"cobblestone"}, "Map_Tile_17_1":{"terrain":"ocean"}, "Map_Tile_11_6":{"terrain":"sea"}, "Map_Tile_17_6":{"terrain":"sea"}, "Map_Tile_14_11":{"terrain":"bridge"}, "Map_Tile_7_13":{"terrain":"ocean"}, "Map_Tile_20_5":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"ocean"}, "Map_Tile_6_3":{"terrain":"sea"}, "Map_Tile_3_7":{"terrain":"mountain"}, "Map_Tile_0_4":{"terrain":"mountain"}, "Map_Tile_12_5":{"terrain":"bridge"}, "Map_Tile_15_2":{"terrain":"bridge"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"mountain"}, "Map_Tile_9_1":{"terrain":"ocean"}, "Map_Tile_16_3":{"terrain":"sea"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"mountain"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_19_3":{"terrain":"cobblestone"}, "Author":"Fly Sniper", "Map_Tile_22_8":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"mountain"}, "Map_Tile_13_1":{"terrain":"ocean"}, "Map_Tile_4_1":{"terrain":"mountain"}, "Map_Tile_21_5":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"ocean"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"ocean"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"mountain"}, "Map_Tile_12_6":{"terrain":"sea"}, "Map_Tile_15_9":{"terrain":"sea"}, "Map_Tile_8_2":{"terrain":"bridge"}, "Map_Tile_9_2":{"terrain":"bridge"}, "Map_Tile_5_10":{"terrain":"sea"}, "Map_Tile_21_10":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"ocean"}, "Map_Tile_20_12":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"ocean"}, "Map_Tile_6_7":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"sea"}, "Map_Tile_3_13":{"terrain":"mountain"}, "Map_Tile_19_2":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":19, "y":2, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":-2, "pos":{"x":19, "y":2, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":1, "garrisonClassId":"", "attackerPlayerId":-1, "unitClassId":"gate", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"indoor_building", "recruitingCostMultiplier":1.0, "cost":500, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":false, "id":"gate", "resourceCost":1, "canReinforce":false, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":6, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_5_4":{"terrain":"sea"}, "Map_Tile_12_11":{"terrain":"bridge"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_21_8":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"mountain"}, "Map_Tile_7_0":{"terrain":"ocean"}, "Map_Tile_14_5":{"terrain":"bridge"}, "Map_Tile_22_7":{"terrain":"road"}, "Map_Tile_11_5":{"terrain":"bridge"}, "Map_Tile_18_3":{"terrain":"reef"}, "Map_Tile_10_10":{"terrain":"sea"}, "Map_Tile_19_1":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"mountain"}, "Map_Tile_11_8":{"terrain":"bridge"}, "Map_Tile_16_10":{"terrain":"sea"}, "Map_Tile_11_7":{"terrain":"sea"}, "Map_Tile_10_1":{"terrain":"ocean"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":19, "y":8, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":-2, "pos":{"x":19, "y":8, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":1, "garrisonClassId":"", "attackerPlayerId":-1, "unitClassId":"gate", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"indoor_building", "recruitingCostMultiplier":1.0, "cost":500, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":false, "id":"gate", "resourceCost":1, "canReinforce":false, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":3, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_15_5":{"terrain":"bridge"}, "Player_Count":2, "Map_Tile_5_11":{"terrain":"bridge"}, "Map_Tile_14_2":{"terrain":"bridge"}, "Map_Tile_9_7":{"terrain":"ocean"}, "Map_Tile_8_0":{"terrain":"ocean"}, "Map_Tile_13_9":{"terrain":"sea"}, "Map_Tile_3_0":{"terrain":"mountain"}, "Map_Tile_9_12":{"terrain":"ocean"}, "Map_Tile_2_3":{"terrain":"mountain"}, "Map_Tile_9_11":{"terrain":"bridge"}, "Map_Tile_13_11":{"terrain":"bridge"}, "Map_Tile_20_0":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"bridge"}, "Map_Tile_4_3":{"terrain":"mountain"}, "Map_Tile_7_12":{"terrain":"ocean"}, "Map_Tile_23_9":{"terrain":"forest"}, "Map_Tile_5_1":{"terrain":"sea"}, "Map_Tile_6_9":{"terrain":"sea"}, "Map_Tile_11_4":{"terrain":"ocean"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"cobblestone"}, "Map_Tile_9_6":{"terrain":"ocean"}, "Map_Tile_11_11":{"terrain":"bridge"}, "Map_Tile_6_6":{"terrain":"sea"}, "Map_Tile_2_0":{"terrain":"mountain"}, "Map_Tile_9_5":{"terrain":"bridge"}, "Map_Tile_16_1":{"terrain":"ocean"}, "Map_Tile_12_3":{"terrain":"ocean"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_19_6":{"terrain":"cobblestone"}, "Map_Tile_12_12":{"terrain":"ocean"}, "Map_Tile_2_12":{"terrain":"mountain"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_12_10":{"terrain":"ocean"}, "Map_Tile_5_8":{"terrain":"bridge"}, "Map_Tile_18_8":{"terrain":"bridge"}, "Map_Tile_23_11":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"mountain"}, "Map_Tile_20_7":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":20, "y":7, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":0, "pos":{"x":20, "y":7, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":100, "garrisonClassId":"garrison", "attackerPlayerId":-1, "unitClassId":"hq", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"land_building", "recruitingCostMultiplier":1.0, "cost":3000, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":false, "id":"hq", "resourceCost":1, "canReinforce":false, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":2, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_19_7":{"terrain":"cobblestone"}, "Map_Tile_19_11":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":19, "y":11, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":-2, "pos":{"x":19, "y":11, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":1, "garrisonClassId":"", "attackerPlayerId":-1, "unitClassId":"gate", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"indoor_building", "recruitingCostMultiplier":1.0, "cost":500, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":false, "id":"gate", "resourceCost":1, "canReinforce":false, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":4, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_10_0":{"terrain":"ocean"}, "Map_Tile_16_13":{"terrain":"ocean"}, "Map_Tile_5_13":{"terrain":"sea"}, "Map_Tile_14_7":{"terrain":"ocean"}, "Map_Tile_2_4":{"terrain":"mountain"}, "Map_Tile_8_1":{"terrain":"ocean", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":8, "y":1, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":0, "pos":{"x":8, "y":1, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":100, "garrisonClassId":"garrison", "attackerPlayerId":-1, "unitClassId":"water_city", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"sea_building", "recruitingCostMultiplier":1.0, "cost":500, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":true, "id":"water_city", "resourceCost":1, "canReinforce":true, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":1, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_16_5":{"terrain":"bridge"}, "Map_Tile_10_9":{"terrain":"sea"}, "Map_Tile_10_5":{"terrain":"bridge"}, "Map_Tile_7_5":{"terrain":"bridge"}, "Map_Tile_21_11":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"ocean"}, "Map_Tile_7_1":{"terrain":"ocean"}, "Map_Tile_4_4":{"terrain":"mountain"}, "Map_Tile_6_4":{"terrain":"sea"}, "Map_Tile_16_8":{"terrain":"bridge"}, "Map_Tile_12_8":{"terrain":"bridge"}, "Map_Tile_13_10":{"terrain":"sea"}, "Map_Tile_2_6":{"terrain":"mountain"}, "Map_Tile_10_2":{"terrain":"bridge"}, "Map_Tile_14_4":{"terrain":"sea"}, "Map_Tile_12_4":{"terrain":"ocean"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_12_2":{"terrain":"bridge"}, "Map_Size":{"x":24, "y":14}, "Map_Tile_8_6":{"terrain":"ocean"}, "Map_Tile_23_1":{"terrain":"mountain"}, "Map_Tile_13_5":{"terrain":"bridge"}, "Map_Tile_20_6":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"bridge"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_14_13":{"terrain":"ocean"}, "Map_Tile_18_10":{"terrain":"reef"}, "Player_2":{"recruit_harpy":true, "recruit_mage":true, "recruit_knight":true, "recruit_wagon":true, "recruit_dragon":true, "recruit_kraken":true, "recruit_witch":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_spearman":true, "recruit_griffin_walking":true, "recruit_giant":true, "recruit_soldier":true, "recruit_thief":true, "recruit_merman":true, "recruit_harpoonship":true, "recruit_archer":true, "recruit_balloon":true, "team":1, "recruit_frog":true, "recruit_turtle":true, "recruit_warship":true, "recruit_ballista":true, "recruit_caravel":true, "recruit_rifleman":true, "recruit_trebuchet":true, "gold":0}, "Map_Tile_3_4":{"terrain":"mountain"}, "Map_Tile_15_10":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"mountain"}, "Map_Tile_8_13":{"terrain":"ocean"}, "Flags":{}, "Counters":{}, "Triggers":[{"players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true, "isIntro":false, "id":"Export", "conditions":{}, "actions":[{"parameters":["2230", "Kraken Strait", "Fly Sniper", "Win with 2 walls still standing (Requires Frog and Kraken).", "Defeat an enemy Dragon (Requires Harpoon Ship)", "", "Defeat all the enemy giants (Requires Frog and Kraken)."], "id":"ap_export", "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true, "isIntro":false, "id":"$trigger_default_defeat_no_units", "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence", "enabled":true}], "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true, "isIntro":false, "id":"$trigger_default_defeat_commander", "conditions":[{"parameters":["*commander", "current", "-1"], "id":"unit_lost", "enabled":true}], "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true, "isIntro":false, "id":"$trigger_default_defeat_hq", "conditions":[{"parameters":["hq", "current", "-1"], "id":"unit_lost", "enabled":true}], "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}]}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true, "isIntro":false, "id":"Defeat (All Giants Dead)", "conditions":[{"parameters":["current", "0", "0", "giant", "-1"], "id":"unit_presence", "enabled":true}], "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true, "isIntro":false, "id":"$trigger_default_victory", "conditions":[{"parameters":["current", "0", "0"], "id":"number_of_opponents", "enabled":true}], "actions":[{"parameters":["current"], "id":"victory", "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true, "isIntro":false, "id":"Init", "conditions":{}, "actions":[{"parameters":["P2", "aggressive"], "id":"ai_set_profile", "enabled":true}, {"parameters":["dragon", "3", "P2", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["giant", "3", "P2", "1", "1", "12", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["water_city", "any", "1", "1", "1"], "id":"unit_random_teleport", "enabled":true}, {"parameters":["port", "2", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true, "isIntro":false, "id":"Spawn Frogs", "conditions":[{"parameters":["252019", "0", "1"], "id":"ap_has_item", "enabled":true}], "actions":[{"parameters":["happy", "generic_archer", "An abundance of frogs appear from the ocean.", "1", "Frog Enthusiast"], "id":"dialogue_box_simple", "enabled":true}, {"parameters":["frog", "2", "current", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true, "isIntro":false, "id":"Spawn Kraken", "conditions":[{"parameters":["252020", "0", "1"], "id":"ap_has_item", "enabled":true}], "actions":[{"parameters":["happy", "generic_archer", "Unleash the Krakens!", "1", "Kraken Tamer"], "id":"dialogue_box_simple", "enabled":true}, {"parameters":["kraken", "2", "current", "1", "1", "5", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true, "isIntro":false, "id":"Spawn 1 Harpoon Ship", "conditions":[{"parameters":["252017", "0", "1"], "id":"ap_has_item", "enabled":true}], "actions":[{"parameters":["harpoonship", "2", "current", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true, "isIntro":false, "id":"P1 Victory (253027)", "conditions":[{"parameters":["current"], "id":"player_victorious", "enabled":true}], "actions":[{"parameters":["253027"], "id":"ap_location_send", "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true, "isIntro":false, "id":"P1 Victory Has 2 Walls (253028)", "conditions":[{"parameters":["current"], "id":"player_victorious", "enabled":true}, {"parameters":["any", "4", "2", "gate", "-1"], "id":"unit_presence", "enabled":true}], "actions":[{"parameters":["253028"], "id":"ap_location_send", "enabled":true}]}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true, "isIntro":false, "id":"Enemy Dragon Defeated (253029)", "conditions":[{"parameters":["*unit_structure", "P1", "dragon", "current", "-1"], "id":"unit_killed", "enabled":true}], "actions":[{"parameters":["253029"], "id":"ap_location_send", "enabled":true}]}], "Map_Tile_11_2":{"terrain":"bridge"}, "Locations":{"1":{"setArea":null, "id":1, "getArea":null, "interactable":false, "positions":[{"x":8, "y":12}, {"x":9, "y":12}, {"x":9, "y":11}, {"x":9, "y":10}, {"x":9, "y":9}, {"x":8, "y":10}, {"x":8, "y":11}, {"x":8, "y":9}, {"x":8, "y":1}, {"x":8, "y":2}, {"x":9, "y":1}, {"x":9, "y":2}, {"x":9, "y":3}, {"x":9, "y":4}, {"x":9, "y":5}, {"x":9, "y":6}, {"x":9, "y":7}, {"x":9, "y":8}, {"x":8, "y":8}, {"x":8, "y":7}, {"x":8, "y":6}, {"x":8, "y":3}, {"x":8, "y":4}, {"x":8, "y":5}, {"x":10, "y":12}, {"x":10, "y":11}, {"x":10, "y":10}, {"x":10, "y":9}, {"x":10, "y":8}, {"x":10, "y":7}, {"x":10, "y":6}, {"x":10, "y":5}, {"x":10, "y":4}, {"x":10, "y":3}, {"x":10, "y":2}, {"x":10, "y":1}, {"x":11, "y":1}, {"x":11, "y":2}, {"x":11, "y":3}, {"x":11, "y":4}, {"x":11, "y":5}, {"x":11, "y":6}, {"x":11, "y":7}, {"x":11, "y":8}, {"x":11, "y":9}, {"x":11, "y":10}, {"x":11, "y":11}, {"x":11, "y":12}, {"x":12, "y":12}, {"x":12, "y":11}, {"x":12, "y":10}, {"x":12, "y":9}, {"x":12, "y":8}, {"x":12, "y":7}, {"x":12, "y":6}, {"x":12, "y":5}, {"x":12, "y":4}, {"x":12, "y":3}, {"x":12, "y":2}, {"x":12, "y":1}, {"x":13, "y":12}, {"x":13, "y":11}, {"x":13, "y":10}, {"x":13, "y":9}, {"x":13, "y":8}, {"x":13, "y":7}, {"x":13, "y":6}, {"x":13, "y":5}, {"x":13, "y":4}, {"x":13, "y":3}, {"x":13, "y":2}, {"x":13, "y":1}, {"x":14, "y":12}, {"x":14, "y":11}, {"x":14, "y":10}, {"x":14, "y":9}, {"x":14, "y":8}, {"x":14, "y":7}, {"x":14, "y":6}, {"x":14, "y":5}, {"x":14, "y":4}, {"x":14, "y":3}, {"x":14, "y":2}, {"x":14, "y":1}], "name":"Village Shuffle", "centre":{"x":11, "y":6}}, "2":{"setArea":null, "id":2, "getArea":null, "interactable":false, "positions":[{"x":16, "y":10}, {"x":15, "y":10}, {"x":14, "y":10}, {"x":13, "y":10}, {"x":12, "y":10}, {"x":11, "y":10}, {"x":10, "y":10}, {"x":9, "y":10}, {"x":8, "y":10}, {"x":8, "y":3}, {"x":9, "y":3}, {"x":10, "y":3}, {"x":11, "y":3}, {"x":12, "y":3}, {"x":13, "y":3}, {"x":14, "y":3}, {"x":15, "y":3}, {"x":16, "y":3}, {"x":16, "y":4}, {"x":16, "y":5}, {"x":16, "y":6}, {"x":16, "y":7}, {"x":16, "y":8}, {"x":16, "y":9}, {"x":15, "y":9}, {"x":14, "y":9}, {"x":13, "y":9}, {"x":12, "y":9}, {"x":11, "y":9}, {"x":10, "y":9}, {"x":9, "y":9}, {"x":8, "y":9}, {"x":8, "y":8}, {"x":8, "y":7}, {"x":8, "y":6}, {"x":8, "y":5}, {"x":8, "y":4}, {"x":10, "y":4}, {"x":11, "y":4}, {"x":12, "y":4}, {"x":13, "y":4}, {"x":14, "y":4}, {"x":15, "y":4}, {"x":15, "y":5}, {"x":15, "y":6}, {"x":15, "y":7}, {"x":15, "y":8}, {"x":14, "y":8}, {"x":13, "y":8}, {"x":12, "y":8}, {"x":11, "y":8}, {"x":10, "y":8}, {"x":9, "y":8}, {"x":9, "y":7}, {"x":9, "y":6}, {"x":9, "y":5}, {"x":9, "y":4}, {"x":10, "y":5}, {"x":11, "y":5}, {"x":11, "y":6}, {"x":12, "y":6}, {"x":13, "y":6}, {"x":13, "y":7}, {"x":12, "y":7}, {"x":11, "y":7}, {"x":10, "y":7}, {"x":10, "y":6}, {"x":12, "y":5}, {"x":13, "y":5}, {"x":14, "y":5}, {"x":14, "y":6}, {"x":14, "y":7}, {"x":8, "y":1}, {"x":9, "y":1}, {"x":10, "y":1}, {"x":11, "y":1}, {"x":12, "y":1}, {"x":13, "y":1}, {"x":14, "y":1}, {"x":15, "y":1}, {"x":16, "y":1}, {"x":16, "y":12}, {"x":15, "y":12}, {"x":14, "y":12}, {"x":13, "y":12}, {"x":12, "y":12}, {"x":11, "y":12}, {"x":10, "y":12}, {"x":9, "y":12}, {"x":8, "y":12}, {"x":8, "y":11}, {"x":9, "y":11}, {"x":10, "y":11}, {"x":11, "y":11}, {"x":12, "y":11}, {"x":13, "y":11}, {"x":14, "y":11}, {"x":15, "y":11}, {"x":16, "y":11}, {"x":16, "y":2}, {"x":15, "y":2}, {"x":14, "y":2}, {"x":13, "y":2}, {"x":12, "y":2}, {"x":11, "y":2}, {"x":10, "y":2}, {"x":9, "y":2}, {"x":8, "y":2}], "name":"P1 Spawns", "centre":{"x":12, "y":7}}, "3":{"setArea":null, "id":3, "getArea":null, "interactable":false, "positions":[{"x":0, "y":13}, {"x":0, "y":12}, {"x":0, "y":11}, {"x":0, "y":10}, {"x":0, "y":9}, {"x":0, "y":8}, {"x":0, "y":7}, {"x":0, "y":6}, {"x":0, "y":5}, {"x":0, "y":4}, {"x":0, "y":3}, {"x":0, "y":2}, {"x":0, "y":1}, {"x":0, "y":0}, {"x":1, "y":11}, {"x":1, "y":8}, {"x":1, "y":5}, {"x":1, "y":2}, {"x":2, "y":2}, {"x":2, "y":5}, {"x":2, "y":8}, {"x":2, "y":11}, {"x":1, "y":10}, {"x":1, "y":9}, {"x":1, "y":12}, {"x":1, "y":13}, {"x":1, "y":7}, {"x":1, "y":6}, {"x":1, "y":4}, {"x":1, "y":3}, {"x":1, "y":1}, {"x":1, "y":0}], "name":"P2 Spawns", "centre":{"x":1, "y":7}}, "0":{"setArea":null, "id":0, "getArea":null, "interactable":false, "positions":[{"x":20, "y":7}], "name":"HQ 1%", "centre":{"x":20, "y":7}}}, "Map_Tile_19_0":{"terrain":"plains"}, "Map_Tile_23_13":{"terrain":"mountain"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_20_4":{"terrain":"plains"}, "Map_Tile_23_12":{"terrain":"mountain"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_23_10":{"terrain":"plains"}, "Map_Tile_23_8":{"terrain":"plains"}, "Map_Tile_23_7":{"terrain":"road"}, "Map_Tile_23_6":{"terrain":"plains"}, "Map_Tile_23_5":{"terrain":"plains"}, "Map_Tile_22_9":{"terrain":"forest"}, "Map_Tile_17_5":{"terrain":"bridge"}, "Map_Tile_23_4":{"terrain":"forest"}, "Map_Tile_20_10":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"sea"}, "Map_Tile_9_10":{"terrain":"sea", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":9, "y":10, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":0, "pos":{"x":9, "y":10, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":100, "garrisonClassId":"garrison", "attackerPlayerId":-1, "unitClassId":"water_city", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"sea_building", "recruitingCostMultiplier":1.0, "cost":500, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":true, "id":"water_city", "resourceCost":1, "canReinforce":true, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":7, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_17_8":{"terrain":"bridge"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"ocean"}, "Map_Tile_21_3":{"terrain":"plains"}, "Map_Tile_23_2":{"terrain":"plains"}, "Map_Tile_23_0":{"terrain":"mountain"}, "Map_Tile_17_2":{"terrain":"bridge"}, "Map_Tile_20_9":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"bridge"}, "Map_Tile_17_12":{"terrain":"ocean"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_22_13":{"terrain":"mountain"}, "Map_Tile_13_3":{"terrain":"sea"}, "Map_Tile_22_12":{"terrain":"mountain"}, "Map_Tile_5_9":{"terrain":"sea"}, "Map_Tile_22_11":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_11_9":{"terrain":"ocean"}, "Map_Tile_22_10":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"sea"}, "Map_Tile_19_4":{"terrain":"cobblestone"}, "Map_Tile_22_6":{"terrain":"plains"}, "Map_Tile_22_5":{"terrain":"plains"}, "Map_Tile_22_1":{"terrain":"mountain"}, "Map_Tile_22_4":{"terrain":"forest"}, "Map_Tile_10_12":{"terrain":"ocean"}, "Map_Tile_22_3":{"terrain":"plains"}, "Map_Tile_22_2":{"terrain":"plains"}, "Objectives":["Win with 2 walls still standing (Requires Frog and Kraken).", "Defeat an enemy Dragon (Requires Harpoon Ship)", "Defeat all the enemy giants (Requires Frog and Kraken)."], "Map_Tile_22_0":{"terrain":"mountain"}, "Map_Tile_21_13":{"terrain":"mountain"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Tile_6_5":{"terrain":"bridge"}, "Map_Tile_21_12":{"terrain":"mountain"}, "Map_Tile_21_9":{"terrain":"forest"}, "Map_Tile_18_13":{"terrain":"reef"}, "Map_Tile_10_11":{"terrain":"bridge"}, "Map_Tile_18_2":{"terrain":"bridge"}, "Map_Tile_21_2":{"terrain":"plains"}, "Map_Tile_21_1":{"terrain":"mountain"}, "Map_Tile_5_3":{"terrain":"sea"}, "Map_Tile_20_13":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"bridge"}, "Map_Tile_6_0":{"terrain":"ocean"}, "Map_Tile_6_1":{"terrain":"ocean"}, "Map_Tile_14_9":{"terrain":"sea"}, "Map_Tile_13_13":{"terrain":"ocean"}, "Map_Tile_12_1":{"terrain":"ocean"}, "Map_Tile_19_5":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "itemDropNumber":0, "damageTakenPercent":100, "merchantDiscounts":{}, "inTransport":false, "merchantDiscountMultiplier":0.0, "grooveCharge":0, "items":{}, "tentacled":false, "startPos":{"x":19, "y":5, "facing":0}, "recruitDiscounts":{}, "miniGrooveId":"", "recruits":{}, "killedByLosing":false, "playerId":-2, "pos":{"x":19, "y":5, "facing":0}, "blessings":{}, "attackerUnitClass":"", "health":1, "garrisonClassId":"", "attackerPlayerId":-1, "unitClassId":"gate", "hasBeenKilled":false, "loadedUnits":{}, "grooveId":"", "setHealth":null, "setGroove":null, "transportedBy":-1, "unitClass":{"weaponIds":{}, "tags":["structure"], "loadCapacity":0, "inAir":false, "critConditionId":"", "moveRange":0, "canBeActivated":false, "weapons":{}, "maxGroove":0, "transportTags":{}, "verbCostMultiplier":1.0, "isCommander":false, "isDamagingParentUnit":false, "movementType":"indoor_building", "recruitingCostMultiplier":1.0, "cost":500, "isAttackable":true, "aliasId":"", "isRecruitable":true, "passiveMultiplier":1.0, "canAttack":true, "reinforceMultiplier":1.0, "canBeCaptured":false, "id":"gate", "resourceCost":1, "canReinforce":false, "maxHealth":100, "isStructure":true, "inWater":false}, "canChargeGroove":true, "itemId":"", "attachedFlagId":-1, "canBeAttacked":true, "stunned":false, "underwater":false, "canBeAttackedFromDistance":true, "id":5, "factionOverride":"", "recruitDiscountMultiplier":0.0, "hadTurn":false, "attackerId":-1, "state":{}}}, "Map_Tile_11_10":{"terrain":"ocean"}, "Map_Tile_14_10":{"terrain":"sea"}, "Map_Tile_20_2":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"sea"}, "Map_Tile_8_12":{"terrain":"ocean"}, "Map_Tile_20_1":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"mountain"}, "Map_Tile_19_13":{"terrain":"plains"}, "Map_Tile_19_12":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"cobblestone"}, "Map_Tile_19_9":{"terrain":"cobblestone"}, "Map_Tile_20_3":{"terrain":"plains"}, "Map_Tile_21_7":{"terrain":"road"}, "Map_Tile_4_12":{"terrain":"mountain"}, "Map_Tile_12_9":{"terrain":"ocean"}, "Map_Tile_18_12":{"terrain":"reef"}, "Map_Tile_18_11":{"terrain":"bridge"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"reef"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_18_7":{"terrain":"reef"}, "Map_Tile_0_10":{"terrain":"mountain"}, "Map_Tile_15_4":{"terrain":"sea"}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_18_5":{"terrain":"bridge"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_18_4":{"terrain":"reef"}, "Map_Tile_0_3":{"terrain":"mountain"}, "Map_Tile_3_3":{"terrain":"mountain"}, "Map_Tile_13_8":{"terrain":"bridge"}, "Map_Tile_21_4":{"terrain":"forest"}, "Map_Tile_6_8":{"terrain":"bridge"}, "Map_Tile_18_0":{"terrain":"reef"}, "Map_Tile_17_13":{"terrain":"ocean"}, "Map_Tile_17_11":{"terrain":"bridge"}, "Map_Tile_17_10":{"terrain":"sea"}, "Map_Tile_17_9":{"terrain":"sea"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"mountain"}, "Map_Tile_4_9":{"terrain":"mountain"}, "Map_Tile_9_0":{"terrain":"ocean"}, "Map_Tile_10_13":{"terrain":"ocean"}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_12_0":{"terrain":"ocean"}, "Map_Tile_4_11":{"terrain":"cobblestone"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"ocean"}, "Map_Tile_6_13":{"terrain":"ocean"}, "Map_Tile_7_11":{"terrain":"bridge"}, "Map_Tile_4_0":{"terrain":"mountain"}, "Map_Tile_21_6":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"sea"}, "Map_Tile_11_0":{"terrain":"ocean"}, "Map_Tile_17_3":{"terrain":"sea"}, "Map_Tile_21_0":{"terrain":"mountain"}, "Map_Tile_3_10":{"terrain":"mountain"}, "Map_Tile_6_2":{"terrain":"bridge"}, "Map_Tile_10_3":{"terrain":"sea"}, "Map_Tile_15_12":{"terrain":"ocean"}, "Map_Tile_16_9":{"terrain":"sea"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"mountain"}, "Map_Tile_3_12":{"terrain":"mountain"}, "Map_Tile_15_1":{"terrain":"ocean"}, "Map_Tile_16_11":{"terrain":"bridge"}, "Map_Tile_13_0":{"terrain":"ocean"}, "Map_Tile_7_8":{"terrain":"bridge"}, "Map_Tile_5_12":{"terrain":"sea"}, "Map_Tile_4_8":{"terrain":"cobblestone"}, "Map_Tile_1_12":{"terrain":"mountain"}, "Player_1":{"recruit_harpy":true, "recruit_mage":true, "recruit_knight":true, "recruit_wagon":true, "recruit_dragon":true, "recruit_kraken":true, "recruit_witch":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_spearman":true, "recruit_griffin_walking":true, "recruit_giant":true, "recruit_soldier":true, "recruit_thief":true, "recruit_merman":true, "recruit_harpoonship":true, "recruit_archer":true, "recruit_balloon":true, "team":0, "recruit_frog":true, "recruit_turtle":true, "recruit_warship":true, "recruit_ballista":true, "recruit_caravel":true, "recruit_rifleman":true, "recruit_trebuchet":true, "gold":800}, "Map_Tile_1_13":{"terrain":"mountain"}, "Map_Tile_16_4":{"terrain":"sea"}, "Map_Tile_16_2":{"terrain":"bridge"}, "Map_Tile_17_4":{"terrain":"sea"}, "Map_Tile_3_6":{"terrain":"mountain"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"ocean"}, "Map_Tile_15_13":{"terrain":"ocean"}, "Map_Tile_15_8":{"terrain":"bridge"}, "Map_Tile_12_13":{"terrain":"ocean"}, "Map_Tile_7_10":{"terrain":"sea"}, "Map_Name":"Kraken Strait", "Map_Tile_1_0":{"terrain":"mountain"}, "Map_Tile_15_7":{"terrain":"ocean"}, "Map_Tile_7_2":{"terrain":"bridge"}, "Map_Tile_18_1":{"terrain":"reef"}, "Map_Tile_9_13":{"terrain":"ocean"}, "Map_Tile_4_13":{"terrain":"mountain"}, "Map_Tile_15_0":{"terrain":"ocean"}, "Map_Tile_2_10":{"terrain":"mountain"}, "Map_Tile_13_2":{"terrain":"bridge"}, "Map_Tile_18_6":{"terrain":"reef"}, "Map_Tile_2_7":{"terrain":"mountain"}, "Map_Tile_20_11":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"ocean"}, "Map_Tile_0_6":{"terrain":"mountain"}, "Map_Tile_10_4":{"terrain":"sea"}, "Map_Tile_14_0":{"terrain":"ocean"}, "Map_Tile_20_8":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"bridge"}, "Map_Tile_5_5":{"terrain":"bridge"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"ocean"}, "Map_Tile_23_3":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"sea"}, "Map_Tile_8_5":{"terrain":"bridge"}, "Map_Tile_15_3":{"terrain":"sea"}, "Map_Tile_17_0":{"terrain":"ocean"}, "Map_Tile_9_8":{"terrain":"bridge"}, "Map_Tile_12_7":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"mountain"}, "Map_Tile_11_12":{"terrain":"ocean"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"mountain"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"bridge"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Nuru_Vengeance.json b/worlds/wargroove2/levels/Nuru_Vengeance.json new file mode 100644 index 000000000000..34bcb8b6f86c --- /dev/null +++ b/worlds/wargroove2/levels/Nuru_Vengeance.json @@ -0,0 +1 @@ +{"Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"road"}, "Map_Tile_14_2":{"terrain":"forest"}, "Map_Size":{"x":21, "y":15}, "Map_Tile_18_5":{"terrain":"forest"}, "Map_Tile_20_11":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"forest"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_13_2":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"fortified_garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"fortified_city", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "cost":1000}, "hadTurn":false, "startPos":{"x":13, "y":2, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"fortified_city", "stunned":false, "damageTakenPercent":100, "id":29, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":13, "y":2, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"cobblestone"}, "Map_Tile_17_1":{"terrain":"road"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"river"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_3_14":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":3, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"pistilAttack", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":false, "weaponIds":["pistilAttack"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.0, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":true, "isStructure":false, "id":"commander_pistil", "inWater":false, "canBeCaptured":false, "maxGroove":225, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["commander", "type.ground.light"], "cost":500}, "hadTurn":false, "startPos":{"x":3, "y":14, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"commander_pistil", "stunned":false, "damageTakenPercent":100, "id":28, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"chain_reaction", "canBeAttacked":true, "pos":{"x":3, "y":14, "facing":0}, "setGroove":null}, "terrain":"cobblestone"}, "Map_Tile_4_12":{"terrain":"road"}, "Map_Tile_19_14":{"terrain":"plains"}, "Map_Tile_0_11":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":0, "y":11, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":25, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":0, "y":11, "facing":0}, "setGroove":null}, "terrain":"cobblestone"}, "Map_Tile_13_7":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":13, "y":7, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":17, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":13, "y":7, "facing":3}, "setGroove":null}, "terrain":"forest"}, "Map_Tile_17_3":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"road"}, "Flags":{}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"cobblestone"}, "Map_Tile_11_3":{"terrain":"road"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_20_2":{"terrain":"plains"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"river"}, "Map_Tile_0_13":{"terrain":"cobblestone"}, "Map_Tile_12_9":{"terrain":"road"}, "Map_Tile_2_14":{"terrain":"cobblestone"}, "Map_Tile_3_9":{"terrain":"wall"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_13_6":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":13, "y":6, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":19, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":13, "y":6, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"road"}, "Map_Name":"Nuru's Vengeance", "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"cobblestone"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"river"}, "Map_Tile_11_13":{"terrain":"road"}, "Map_Tile_1_12":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":1, "y":12, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":26, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":1, "y":12, "facing":0}, "setGroove":null}, "terrain":"carpet"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_14_6":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":14, "y":6, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":20, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":14, "y":6, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_8_4":{"terrain":"forest"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_19_13":{"terrain":"road"}, "Map_Tile_17_6":{"terrain":"plains"}, "Map_Tile_17_4":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"road"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"carpet"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"cobblestone"}, "Map_Tile_6_1":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":6, "y":1, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":16, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":6, "y":1, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_8_1":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":8, "y":1, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":13, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":8, "y":1, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_5_12":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"forest"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_19_6":{"terrain":"forest"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"cobblestone"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_17_0":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_5_2":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"city", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":5, "y":2, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"city", "stunned":false, "damageTakenPercent":100, "id":6, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":5, "y":2, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_19_0":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"road"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_19_7":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_20_4":{"terrain":"forest"}, "Map_Tile_18_0":{"terrain":"plains"}, "Player_2":{"recruit_dragon":true, "recruit_merman":true, "recruit_giant":false, "recruit_dog":true, "recruit_turtle":true, "recruit_ballista":false, "recruit_harpoonship":true, "recruit_thief":true, "recruit_caravel":true, "recruit_mage":true, "recruit_frog":true, "recruit_balloon":true, "recruit_griffin_walking":true, "recruit_warship":true, "gold":1000, "recruit_spearman":true, "recruit_rifleman":true, "recruit_witch":true, "recruit_trebuchet":true, "recruit_knight":true, "recruit_soldier":true, "team":1, "recruit_travelboat":true, "recruit_harpy":true, "recruit_wagon":false, "recruit_archer":false, "recruit_kraken":true}, "Map_Tile_2_10":{"terrain":"cobblestone"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_19_5":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"road"}, "Author":"Fly Sniper", "Map_Tile_1_8":{"terrain":"mountain"}, "Map_Tile_8_8":{"terrain":"forest"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"forest"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"forest"}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"mountain"}, "Map_Tile_7_2":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":7, "y":2, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":15, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":7, "y":2, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_0_1":{"terrain":"plains"}, "Objectives":["Destroy the gate with your spearman (Requires Spearman and Knight).", "Defeat all enemy dogs on the map. (Requires Knight).", "Win with standard conditions. (Requires Knight)."], "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains"}, "Map_Tile_17_9":{"terrain":"road"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"road"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_9_4":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":9, "y":4, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":32, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":9, "y":4, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_4_1":{"terrain":"forest"}, "Map_Tile_4_7":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"forest"}, "Map_Tile_9_6":{"terrain":"forest"}, "Map_Tile_8_14":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"city", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":8, "y":14, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"city", "stunned":false, "damageTakenPercent":100, "id":7, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":8, "y":14, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_2_6":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"barracks", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":2, "y":6, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"barracks", "stunned":false, "damageTakenPercent":100, "id":3, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":2, "y":6, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"forest"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"forest"}, "Map_Tile_2_8":{"terrain":"mountain"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_20_12":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"plains"}, "Counters":{}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"road"}, "Map_Tile_2_9":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":50, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-2, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"indoor_building", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"gate", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":2, "y":9, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"gate", "stunned":false, "damageTakenPercent":100, "id":1, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":2, "y":9, "facing":0}, "setGroove":null}, "terrain":"cobblestone"}, "Map_Tile_6_11":{"terrain":"road"}, "Triggers":[{"actions":[{"enabled":true, "parameters":["2424", "Nuru's Vengeance", "Fly Sniper", "Destroy the gate with your spearman (Requires Spearman and Knight).", "Defeat all enemy dogs on the map. (Requires Knight).", "", "Win with standard conditions. (Requires Knight)."], "id":"ap_export"}], "isIntro":false, "recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":{}, "id":"Export (Always on Top)"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "id":"$trigger_default_defeat_no_units"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "id":"$trigger_default_defeat_commander"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "id":"$trigger_default_defeat_hq"}, {"actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "isIntro":false, "recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "id":"$trigger_default_victory"}, {"actions":[{"enabled":true, "parameters":["253005"], "id":"ap_location_send"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["P1"], "id":"player_victorious"}], "id":"P1 Wins (Sends Victory Check)"}, {"actions":[{"enabled":true, "parameters":["*non_commander", "P2", "2", "2", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*non_commander", "P1", "0", "0", "1"], "id":"unit_random_teleport"}], "isIntro":false, "recurring":"start_of_match", "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":{}, "id":"Random TP"}, {"actions":[{"enabled":true, "parameters":["knight", "1", "current", "0", "0", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["happy", "generic_faahri", "The cavalry you requested is here!", "1", "Someone"], "id":"dialogue_box_simple"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["252004", "1", "0"], "id":"ap_has_item"}], "id":"Spawn Knights"}, {"actions":[{"enabled":true, "parameters":["happy", "generic_faahri", "The Spearmen have taken care of the gate!", "1", "Some Dude"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["253006"], "id":"ap_location_send"}], "isIntro":false, "recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["current"], "id":"player_turn"}, {"enabled":true, "parameters":["current", "1", "0", "spearman", "4"], "id":"unit_presence"}, {"enabled":true, "parameters":["gate", "any", "3"], "id":"unit_lost"}], "id":"Spearmen Kill Gate"}, {"actions":[{"enabled":true, "parameters":["happy", "generic_faahri", "All enemy dogs defeated!", "1", "Some Guy"], "id":"dialogue_box_simple"}, {"enabled":true, "parameters":["253007"], "id":"ap_location_send"}], "isIntro":false, "recurring":"once", "players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "conditions":[{"enabled":true, "parameters":["current", "0", "0", "dog", "-1"], "id":"unit_presence"}], "id":"Defeated all Enemy Dogs"}], "Locations":{"1":{"interactable":false, "getArea":null, "name":"Knight Reinforcements", "positions":[{"x":14, "y":13}, {"x":15, "y":13}, {"x":15, "y":14}, {"x":14, "y":14}, {"x":16, "y":14}, {"x":17, "y":14}, {"x":17, "y":13}, {"x":18, "y":13}, {"x":18, "y":14}], "centre":{"x":16, "y":14}, "setArea":null, "id":1}, "2":{"interactable":false, "getArea":null, "name":"Shuffle Units", "positions":[{"x":8, "y":2}, {"x":9, "y":1}, {"x":8, "y":1}, {"x":9, "y":2}, {"x":7, "y":2}, {"x":7, "y":1}, {"x":10, "y":2}, {"x":10, "y":1}, {"x":15, "y":3}, {"x":15, "y":2}, {"x":14, "y":2}, {"x":14, "y":3}, {"x":14, "y":6}, {"x":13, "y":6}, {"x":13, "y":7}, {"x":14, "y":7}, {"x":15, "y":7}, {"x":16, "y":6}, {"x":15, "y":6}, {"x":19, "y":3}, {"x":18, "y":4}, {"x":19, "y":4}, {"x":18, "y":3}, {"x":20, "y":4}, {"x":20, "y":3}, {"x":18, "y":5}, {"x":19, "y":5}, {"x":20, "y":5}, {"x":16, "y":3}, {"x":6, "y":1}, {"x":12, "y":4}, {"x":11, "y":4}, {"x":10, "y":4}, {"x":10, "y":3}, {"x":9, "y":3}, {"x":9, "y":4}], "centre":{"x":13, "y":4}, "setArea":null, "id":2}, "3":{"interactable":false, "getArea":null, "name":"Gate", "positions":[{"x":2, "y":9}], "centre":{"x":2, "y":9}, "setArea":null, "id":3}, "4":{"interactable":false, "getArea":null, "name":"Spear Position", "positions":[{"x":2, "y":8}, {"x":2, "y":10}], "centre":{"x":2, "y":9}, "setArea":null, "id":4}, "0":{"interactable":false, "getArea":null, "name":"Shuffle Soldier and Dog Spawn", "positions":[{"x":0, "y":10}, {"x":1, "y":10}, {"x":2, "y":10}, {"x":3, "y":10}, {"x":3, "y":11}, {"x":3, "y":12}, {"x":3, "y":13}, {"x":1, "y":13}, {"x":0, "y":13}, {"x":2, "y":13}, {"x":1, "y":12}, {"x":0, "y":12}, {"x":0, "y":11}, {"x":1, "y":11}, {"x":2, "y":11}, {"x":2, "y":12}], "centre":{"x":2, "y":12}, "setArea":null, "id":0}}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"road"}, "Map_Tile_11_2":{"terrain":"road"}, "Map_Tile_17_2":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_20_14":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_20_13":{"terrain":"road"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_20_10":{"terrain":"plains"}, "Map_Tile_20_9":{"terrain":"plains"}, "Map_Tile_20_8":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"sword", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["sword"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"soldier", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "cost":100}, "hadTurn":false, "startPos":{"x":20, "y":8, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"soldier", "stunned":false, "damageTakenPercent":100, "id":11, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":20, "y":8, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_20_0":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":3, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":false, "id":"trebuchetSling", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":3, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":5}], "isRecruitable":true, "weaponIds":["trebuchetSling"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"wheels", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"trebuchet", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["trebuchet", "type.ground.heavy"], "cost":1100}, "hadTurn":false, "startPos":{"x":20, "y":0, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"trebuchet", "stunned":false, "damageTakenPercent":100, "id":10, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":20, "y":0, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_20_6":{"terrain":"plains"}, "Map_Tile_20_5":{"terrain":"forest"}, "Map_Tile_20_1":{"terrain":"road"}, "Map_Tile_20_7":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"road"}, "Map_Tile_19_12":{"terrain":"road"}, "Map_Tile_19_11":{"terrain":"road"}, "Map_Tile_19_9":{"terrain":"road"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_18_2":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"barracks", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":18, "y":2, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"barracks", "stunned":false, "damageTakenPercent":100, "id":2, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":18, "y":2, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_17_14":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"forest"}, "Map_Tile_16_11":{"terrain":"forest"}, "Map_Tile_19_3":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_19_1":{"terrain":"road"}, "Map_Tile_18_13":{"terrain":"plains"}, "Map_Tile_19_2":{"terrain":"plains"}, "Map_Tile_18_11":{"terrain":"forest"}, "Map_Tile_2_12":{"terrain":"carpet"}, "Map_Tile_18_8":{"terrain":"plains"}, "Map_Tile_10_2":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":10, "y":2, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":14, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":10, "y":2, "facing":3}, "setGroove":null}, "terrain":"forest"}, "Map_Tile_18_7":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_18_4":{"terrain":"plains"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_19_8":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"fortified_garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"fortified_city", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "cost":1000}, "hadTurn":false, "startPos":{"x":19, "y":8, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"fortified_city", "stunned":false, "damageTakenPercent":100, "id":8, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":19, "y":8, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_18_1":{"terrain":"road"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"forest"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"mountain"}, "Map_Tile_17_12":{"terrain":"plains"}, "Map_Tile_1_10":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"sword", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["sword"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"soldier", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "cost":100}, "hadTurn":false, "startPos":{"x":1, "y":10, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"soldier", "stunned":false, "damageTakenPercent":100, "id":22, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":1, "y":10, "facing":0}, "setGroove":null}, "terrain":"cobblestone"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"forest"}, "Map_Tile_0_12":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"sword", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["sword"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"soldier", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "cost":100}, "hadTurn":false, "startPos":{"x":0, "y":12, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"soldier", "stunned":false, "damageTakenPercent":100, "id":23, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":0, "y":12, "facing":0}, "setGroove":null}, "terrain":"carpet"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"forest"}, "Map_Tile_16_13":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":["thief", "rifleman"], "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"hideout", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":16, "y":13, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"hideout", "stunned":false, "damageTakenPercent":100, "id":4, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":16, "y":13, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_15_7":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":15, "y":7, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":18, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":15, "y":7, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_16_1":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":3, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"nuruAttack", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":false, "weaponIds":["nuruAttack"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.0, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":true, "isStructure":false, "id":"commander_nuru", "inWater":false, "canBeCaptured":false, "maxGroove":400, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["commander", "type.ground.light"], "cost":500}, "hadTurn":false, "startPos":{"x":16, "y":1, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"commander_nuru", "stunned":false, "damageTakenPercent":100, "id":9, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"teleport_beam", "canBeAttacked":true, "pos":{"x":16, "y":1, "facing":3}, "setGroove":null}, "terrain":"road"}, "Map_Tile_1_9":{"terrain":"wall"}, "Map_Tile_11_8":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":-1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":true, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"city", "inWater":false, "canBeCaptured":true, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":500}, "hadTurn":false, "startPos":{"x":11, "y":8, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"city", "stunned":false, "damageTakenPercent":100, "id":5, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":11, "y":8, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"mountain"}, "Map_Tile_11_6":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"wall"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"road"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_14_9":{"terrain":"road"}, "Map_Tile_0_7":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"sword", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["sword"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"soldier", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "cost":100}, "hadTurn":false, "startPos":{"x":0, "y":7, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"soldier", "stunned":false, "damageTakenPercent":100, "id":21, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":0, "y":7, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"cobblestone"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_15_3":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":15, "y":3, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":12, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":15, "y":3, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_17_11":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_12_0":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"hq", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":3000}, "hadTurn":false, "startPos":{"x":12, "y":0, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"hq", "stunned":false, "damageTakenPercent":100, "id":34, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":12, "y":0, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_0_10":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"sword", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["sword"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":4, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"soldier", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "cost":100}, "hadTurn":false, "startPos":{"x":0, "y":10, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"soldier", "stunned":false, "damageTakenPercent":100, "id":24, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":0, "y":10, "facing":0}, "setGroove":null}, "terrain":"cobblestone"}, "Map_Tile_14_7":{"terrain":"forest"}, "Map_Tile_7_11":{"terrain":"road"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"forest"}, "Map_Tile_14_1":{"terrain":"road"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"cobblestone"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"mountain"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"road"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"road"}, "Map_Tile_1_14":{"terrain":"cobblestone"}, "Map_Tile_16_9":{"terrain":"road"}, "Map_Tile_12_11":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"garrison", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":70, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":0, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":{}, "isRecruitable":true, "weaponIds":{}, "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"land_building", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.0, "aliasId":"", "moveRange":0, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":true, "id":"hq", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["structure"], "cost":3000}, "hadTurn":false, "startPos":{"x":12, "y":11, "facing":0}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"hq", "stunned":false, "damageTakenPercent":100, "id":27, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":12, "y":11, "facing":0}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_16_6":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":16, "y":6, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":30, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":16, "y":6, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_12_7":{"terrain":"forest"}, "Map_Tile_12_4":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":12, "y":4, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":31, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":12, "y":4, "facing":3}, "setGroove":null}, "terrain":"mountain"}, "Map_Tile_7_13":{"terrain":"forest"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"road"}, "Map_Tile_10_4":{"unit":{"attachedFlagId":-1, "hasBeenKilled":false, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "itemDropNumber":0, "loadedUnits":{}, "tentacled":false, "garrisonClassId":"", "transportedBy":-1, "attackerId":-1, "merchantDiscounts":{}, "attackerPlayerId":-1, "health":100, "rangedDamageTakenPercent":100, "recruitDiscountMultiplier":0.0, "itemId":"", "factionOverride":"", "killedByLosing":false, "recruits":{}, "inTransport":false, "canChargeGroove":true, "miniGrooveId":"", "items":{}, "playerId":1, "underwater":false, "unitClass":{"inAir":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "weapons":[{"canAttackAir":false, "canAttackSubmerged":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "id":"bite", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "directionality":"omni", "minRange":1, "blockedByEnemies":false, "unitIdWhenAttacking":"", "terrainExclusion":{}, "maxRange":1}], "isRecruitable":true, "weaponIds":["bite"], "verbCostMultiplier":1.0, "isAttackable":true, "movementType":"walking", "critConditionId":"", "canReinforce":false, "passiveMultiplier":1.5, "aliasId":"", "moveRange":5, "reinforceMultiplier":1.0, "canBeActivated":false, "maxHealth":100, "loadCapacity":0, "isCommander":false, "isStructure":false, "id":"dog", "inWater":false, "canBeCaptured":false, "maxGroove":0, "canAttack":true, "transportTags":{}, "isDamagingParentUnit":false, "tags":["dog", "type.ground.light", "animal"], "cost":150}, "hadTurn":false, "startPos":{"x":10, "y":4, "facing":3}, "attackerUnitClass":"", "canBeAttackedFromDistance":true, "unitClassId":"dog", "stunned":false, "damageTakenPercent":100, "id":33, "blessings":{}, "setHealth":null, "grooveCharge":0, "state":{}, "grooveId":"", "canBeAttacked":true, "pos":{"x":10, "y":4, "facing":3}, "setGroove":null}, "terrain":"plains"}, "Map_Tile_1_1":{"terrain":"forest"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"cobblestone"}, "Map_Tile_8_12":{"terrain":"forest"}, "Map_Tile_2_0":{"terrain":"plains"}, "Player_1":{"recruit_dragon":true, "recruit_merman":true, "recruit_giant":true, "recruit_dog":true, "recruit_turtle":true, "recruit_ballista":true, "recruit_harpoonship":true, "recruit_thief":true, "recruit_caravel":true, "recruit_mage":true, "recruit_frog":true, "recruit_balloon":true, "recruit_griffin_walking":true, "recruit_warship":true, "gold":300, "recruit_spearman":true, "recruit_rifleman":true, "recruit_witch":true, "recruit_trebuchet":true, "recruit_knight":true, "recruit_soldier":true, "team":0, "recruit_travelboat":true, "recruit_harpy":true, "recruit_wagon":true, "recruit_archer":true, "recruit_kraken":true}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Operation_Seagull.json b/worlds/wargroove2/levels/Operation_Seagull.json new file mode 100644 index 000000000000..0d35365b18e9 --- /dev/null +++ b/worlds/wargroove2/levels/Operation_Seagull.json @@ -0,0 +1 @@ +{"Map_Tile_3_7":{"terrain":"sea"}, "Map_Tile_10_8":{"terrain":"ocean"}, "Map_Tile_26_8":{"terrain":"ocean"}, "Map_Tile_18_2":{"terrain":"ocean"}, "Map_Tile_4_4":{"terrain":"sea"}, "Map_Tile_16_2":{"terrain":"ocean"}, "Map_Tile_22_1":{"terrain":"ocean"}, "Map_Tile_0_6":{"terrain":"ocean"}, "Map_Tile_4_0":{"terrain":"ocean"}, "Map_Tile_17_2":{"terrain":"ocean"}, "Map_Tile_15_6":{"terrain":"sea"}, "Map_Tile_24_6":{"terrain":"road"}, "Map_Tile_8_9":{"terrain":"ocean"}, "Map_Tile_22_3":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"port", "health":100, "miniGrooveId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":7, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"port", "isCommander":false, "aliasId":"", "movementType":"river_sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":22}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":22}, "inTransport":false}}, "Map_Tile_22_0":{"terrain":"ocean"}, "Map_Tile_6_6":{"terrain":"ocean"}, "Map_Tile_17_0":{"terrain":"ocean"}, "Map_Tile_2_4":{"terrain":"sea"}, "Map_Tile_16_8":{"terrain":"sea"}, "Map_Tile_18_7":{"terrain":"sea"}, "Map_Tile_15_7":{"terrain":"sea"}, "Map_Tile_16_10":{"terrain":"sea"}, "Map_Tile_10_3":{"terrain":"ocean"}, "Map_Tile_17_9":{"terrain":"ocean"}, "Map_Tile_10_10":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":12, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":10, "x":10}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":10, "x":10}, "inTransport":false}}, "Map_Tile_21_4":{"terrain":"ocean"}, "Map_Tile_9_4":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":11, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":4, "x":9}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":4, "x":9}, "inTransport":false}}, "Map_Tile_14_1":{"terrain":"reef"}, "Player_Count":2, "Map_Tile_0_2":{"terrain":"ocean"}, "Map_Tile_18_9":{"terrain":"ocean"}, "Map_Tile_26_6":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":21, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":6, "x":26}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":6, "x":26}, "inTransport":false}}, "Map_Tile_19_9":{"terrain":"ocean"}, "Map_Tile_21_7":{"terrain":"sea"}, "Map_Tile_6_9":{"terrain":"ocean"}, "Map_Name":"Operation Seagull", "Map_Tile_2_1":{"terrain":"ocean"}, "Counters":{}, "Map_Tile_13_7":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"portal_neutral", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":29, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"portal_neutral", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":false, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":13}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":13}, "inTransport":false}}, "Map_Tile_13_1":{"terrain":"ocean"}, "Map_Tile_13_4":{"terrain":"wall"}, "Map_Tile_4_8":{"terrain":"ocean"}, "Map_Tile_22_5":{"terrain":"sea"}, "Map_Tile_11_9":{"terrain":"ocean"}, "Map_Tile_8_8":{"terrain":"ocean"}, "Map_Tile_22_8":{"terrain":"ocean"}, "Map_Tile_20_7":{"terrain":"reef"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"wall"}, "Map_Tile_17_10":{"terrain":"ocean"}, "Map_Tile_9_8":{"terrain":"ocean"}, "Map_Tile_19_4":{"terrain":"ocean"}, "Map_Tile_9_7":{"terrain":"ocean"}, "Map_Tile_9_3":{"terrain":"sea"}, "Map_Tile_5_8":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"caravel", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":35, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"caravelWeapon", "maxRange":1, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"caravel", "isCommander":false, "aliasId":"", "movementType":"river_sailing", "passiveMultiplier":1.5, "loadCapacity":0, "inAir":false, "inWater":true, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":250, "canReinforce":false, "moveRange":5, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["caravel", "type.sea.light"], "weaponIds":["caravelWeapon"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":8, "x":5}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":8, "x":5}, "inTransport":false}}, "Map_Tile_1_10":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":41, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":10, "x":1}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":10, "x":1}, "inTransport":false}}, "Map_Tile_2_2":{"terrain":"reef"}, "Map_Tile_27_3":{"terrain":"ocean"}, "Map_Tile_15_9":{"terrain":"ocean"}, "Map_Tile_19_10":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":14, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":10, "x":19}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":10, "x":19}, "inTransport":false}}, "Map_Tile_6_2":{"terrain":"sea"}, "Map_Tile_0_4":{"terrain":"ocean"}, "Map_Tile_24_3":{"terrain":"ocean"}, "Map_Tile_17_5":{"item":{"type":"wind_potion", "unitTypeRestriction":{}, "isConsumable":true, "itemId":48, "pos":{"y":5, "x":17}}, "terrain":"sea"}, "Map_Tile_16_1":{"terrain":"sea"}, "Map_Tile_4_9":{"terrain":"ocean"}, "Map_Tile_24_9":{"terrain":"ocean"}, "Map_Tile_20_9":{"terrain":"ocean"}, "Map_Tile_21_6":{"terrain":"sea"}, "Map_Tile_20_0":{"terrain":"ocean"}, "Map_Tile_8_0":{"terrain":"ocean"}, "Map_Tile_22_2":{"terrain":"ocean"}, "Map_Tile_23_3":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":24, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":23}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":23}, "inTransport":false}}, "Map_Tile_11_8":{"terrain":"ocean"}, "Map_Tile_14_6":{"terrain":"wall"}, "Map_Tile_3_3":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":32, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":10, "x":5}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":10, "x":5}, "inTransport":false}}, "Map_Tile_19_2":{"terrain":"ocean"}, "Map_Tile_14_10":{"terrain":"ocean"}, "Map_Tile_24_1":{"terrain":"ocean"}, "Map_Tile_5_7":{"terrain":"ocean"}, "Map_Tile_27_7":{"terrain":"ocean"}, "Map_Tile_7_7":{"terrain":"ocean"}, "Map_Tile_13_0":{"terrain":"ocean"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_0_3":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":39, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":0}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":0}, "inTransport":false}}, "Map_Tile_0_0":{"terrain":"ocean"}, "Map_Tile_10_7":{"terrain":"ocean"}, "Map_Tile_6_0":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":31, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":0, "x":6}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":0, "x":6}, "inTransport":false}}, "Map_Tile_26_0":{"terrain":"ocean"}, "Map_Tile_26_3":{"terrain":"plains", "unit":{"grooveId":"", "unitClassId":"tower", "health":100, "miniGrooveId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":8, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"tower", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":26}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":26}, "inTransport":false}}, "Map_Tile_27_8":{"terrain":"ocean"}, "Map_Tile_3_5":{"terrain":"sea"}, "Map_Tile_6_4":{"terrain":"sea"}, "Map_Tile_2_8":{"terrain":"ocean"}, "Map_Tile_9_6":{"terrain":"ocean"}, "Map_Tile_6_7":{"terrain":"ocean"}, "Map_Tile_8_5":{"terrain":"sea"}, "Map_Tile_15_0":{"terrain":"ocean"}, "Map_Tile_17_1":{"terrain":"sea"}, "Map_Tile_20_1":{"terrain":"ocean"}, "Map_Tile_12_4":{"terrain":"wall"}, "Map_Tile_26_5":{"terrain":"ocean"}, "Map_Tile_1_9":{"terrain":"ocean"}, "Map_Tile_14_9":{"terrain":"ocean"}, "Map_Tile_1_1":{"terrain":"ocean"}, "Map_Tile_7_0":{"terrain":"ocean"}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_11_4":{"terrain":"sea"}, "Map_Tile_11_3":{"terrain":"ocean"}, "Map_Tile_20_5":{"terrain":"sea"}, "Map_Tile_12_9":{"terrain":"ocean"}, "Map_Tile_1_6":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"harpy", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":4, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"harpyClaws", "maxRange":1, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"harpy", "isCommander":false, "aliasId":"", "movementType":"flying", "passiveMultiplier":1.25, "loadCapacity":0, "inAir":true, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":600, "canReinforce":false, "moveRange":6, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["harpy", "type.air"], "weaponIds":["harpyClaws"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":6, "x":1}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":6, "x":1}, "inTransport":false}}, "Map_Tile_10_0":{"terrain":"ocean"}, "Map_Tile_3_9":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"port", "health":100, "miniGrooveId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":37, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"port", "isCommander":false, "aliasId":"", "movementType":"river_sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":9, "x":3}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":9, "x":3}, "inTransport":false}}, "Player_1":{"gold":100, "recruit_archer":true, "recruit_dragon":true, "recruit_merman":true, "team":0, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_soldier":true, "recruit_rifleman":true, "recruit_trebuchet":true, "recruit_travelboat":true, "recruit_witch":true, "recruit_warship":true, "recruit_spearman":true, "recruit_kraken":true, "recruit_caravel":true, "recruit_harpy":true, "recruit_mage":true, "recruit_wagon":true, "recruit_giant":true, "recruit_turtle":true, "recruit_dog":true, "recruit_knight":true, "recruit_thief":true, "recruit_frog":true, "recruit_griffin_walking":true, "recruit_ballista":true}, "Map_Tile_9_5":{"terrain":"sea"}, "Map_Tile_25_7":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":20, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":25}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":25}, "inTransport":false}}, "Map_Tile_5_0":{"terrain":"ocean"}, "Map_Tile_2_3":{"terrain":"sea"}, "Map_Tile_18_0":{"terrain":"ocean"}, "Map_Tile_1_3":{"terrain":"ocean"}, "Map_Tile_1_5":{"terrain":"ocean"}, "Map_Tile_17_3":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":15, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":17}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":17}, "inTransport":false}}, "Map_Tile_11_2":{"terrain":"ocean"}, "Map_Tile_11_7":{"terrain":"sea"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_10_2":{"terrain":"ocean"}, "Map_Tile_4_5":{"terrain":"sea"}, "Map_Tile_23_4":{"terrain":"road"}, "Map_Tile_7_6":{"terrain":"ocean"}, "Map_Tile_27_6":{"terrain":"ocean"}, "Map_Tile_3_0":{"terrain":"ocean"}, "Map_Tile_13_10":{"terrain":"reef"}, "Map_Tile_19_5":{"terrain":"reef"}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_17_8":{"terrain":"sea"}, "Map_Tile_25_4":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"fortified_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":43, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"fortified_city", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":1000, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["fortified_city"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"fortified_garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":4, "x":25}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":4, "x":25}, "inTransport":false}}, "Map_Tile_6_10":{"terrain":"sea"}, "Map_Tile_22_6":{"terrain":"ocean"}, "Map_Tile_3_8":{"terrain":"sea"}, "Map_Tile_10_9":{"terrain":"ocean"}, "Map_Tile_2_0":{"terrain":"ocean"}, "Map_Tile_12_8":{"terrain":"sea"}, "Map_Tile_17_7":{"terrain":"sea"}, "Map_Tile_13_2":{"terrain":"sea"}, "Map_Tile_25_0":{"terrain":"ocean"}, "Map_Tile_3_1":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"port", "health":100, "miniGrooveId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":36, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"port", "isCommander":false, "aliasId":"", "movementType":"river_sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":1, "x":3}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":3}, "inTransport":false}}, "Map_Tile_18_8":{"terrain":"ocean"}, "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_6_3":{"terrain":"sea"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_13_3":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"portal_neutral", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":27, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"portal_neutral", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":false, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":13}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":13}, "inTransport":false}}, "Map_Tile_9_2":{"terrain":"sea"}, "Map_Tile_15_5":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"portal_neutral", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":28, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"portal_neutral", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":false, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":5, "x":15}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":5, "x":15}, "inTransport":false}}, "Map_Tile_26_2":{"terrain":"ocean"}, "Map_Tile_23_10":{"terrain":"ocean"}, "Map_Tile_19_1":{"terrain":"ocean"}, "Map_Tile_2_6":{"terrain":"sea"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Tile_14_0":{"terrain":"ocean"}, "Map_Tile_10_4":{"terrain":"sea"}, "Map_Tile_25_3":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":23, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":25}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":25}, "inTransport":false}}, "Map_Tile_27_0":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":46, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":0, "x":27}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":0, "x":27}, "inTransport":false}}, "Map_Tile_15_8":{"terrain":"ocean"}, "Map_Tile_21_1":{"terrain":"sea"}, "Map_Tile_18_1":{"terrain":"ocean"}, "Map_Tile_24_4":{"terrain":"road"}, "Map_Tile_25_9":{"terrain":"ocean"}, "Map_Tile_7_8":{"terrain":"ocean"}, "Flags":{}, "Map_Tile_8_7":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":13, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":8}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":8}, "inTransport":false}}, "Map_Tile_12_2":{"terrain":"ocean"}, "Map_Tile_15_1":{"terrain":"sea"}, "Map_Tile_22_10":{"terrain":"ocean"}, "Triggers":[{"players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Export (Always on Top)", "recurring":"start_of_match", "actions":[{"id":"ap_export", "parameters":["1", "Operation Seagull", "Magnemania", "Destroy the enemy Crystal. (Requires Warship or Kraken)", "Defeat a Harpoon Ship with a Dragon. (Requires Anti-Air and Dragon)", "", "Destroy the enemy Organ. (Requires Merfolk, Anti-Air, and Anti-Sea)"], "enabled":true}], "conditions":{}}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Set AI", "recurring":"start_of_match", "actions":[{"id":"modify_gold", "parameters":["P1", "1", "500"], "enabled":true}, {"id":"ap_spawn_unit", "parameters":["organ", "8", "P2", "1", "1", "1", "1", "undefined", "left"], "enabled":true}, {"id":"set_damage_taken", "parameters":["*unit_structure", "5", "any", "0"], "enabled":true}, {"id":"set_damage_taken", "parameters":["*unit_structure", "8", "any", "150"], "enabled":true}, {"id":"location_set_properties", "parameters":["5", "1", "1", "0"], "enabled":true}, {"id":"ai_set_restriction", "parameters":["*unit_structure", "8", "current", "scripted", "1"], "enabled":true}], "conditions":{}}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Shuffle Units", "recurring":"start_of_match", "actions":[{"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true}], "conditions":{}}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Spawn Crystal", "recurring":"once", "actions":[{"id":"spawn_unit", "parameters":["crystal", "6", "current", "1", "1", "1", "0", "undefined", "centre"], "enabled":true}], "conditions":{}}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Sustain Crystal", "recurring":"repeat", "actions":[{"id":"modify_health", "parameters":["crystal", "6", "current", "1", "20"], "enabled":true}], "conditions":[{"id":"start_of_turn", "parameters":{}, "enabled":true}, {"id":"unit_presence", "parameters":["current", "0", "1", "crystal", "6"], "enabled":true}, {"id":"player_turn", "parameters":["current"], "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Crystal Destroyed (Check 253356)", "recurring":"once", "actions":[{"id":"play_sound_effect", "parameters":["crystalCharge3", "6"], "enabled":true}, {"id":"ap_location_send", "parameters":["253356"], "enabled":true}, {"id":"screenshake", "parameters":["5000", "1", "1", "4"], "enabled":true}, {"id":"wait", "parameters":["1000"], "enabled":true}, {"id":"play_sound_effect", "parameters":["thunder3", "6"], "enabled":true}, {"id":"modify_health", "parameters":["*unit", "-1", "P2", "2", "20"], "enabled":true}, {"id":"play_sound_effect", "parameters":["thunder3", "6"], "enabled":true}, {"id":"modify_health", "parameters":["*unit", "-1", "current", "1", "20"], "enabled":true}, {"id":"wait", "parameters":["500"], "enabled":true}], "conditions":[{"id":"unit_killed", "parameters":["*unit", "current", "crystal", "P2", "-1"], "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Harpoon Ship Destroyed by Dragon (Check 253357)", "recurring":"once", "actions":[{"id":"ap_location_send", "parameters":["253357"], "enabled":true}], "conditions":[{"id":"unit_killed", "parameters":["dragon", "current", "harpoonship", "P2", "-1"], "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_no_units", "recurring":"oncePerPlayer", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_commander", "recurring":"oncePerPlayer", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_hq", "recurring":"oncePerPlayer", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"Victory (Organ Destroyed)", "recurring":"repeat", "actions":[{"id":"wait", "parameters":["1000"], "enabled":true}, {"id":"victory", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["P2", "0", "0", "*unit_structure", "8"], "enabled":true}]}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"$trigger_default_victory", "recurring":"oncePerPlayer", "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "enabled":true, "id":"P1 Wins (Check 253355)", "recurring":"once", "actions":[{"id":"ap_location_send", "parameters":["253355"], "enabled":true}], "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}]}], "Map_Tile_4_6":{"terrain":"ocean"}, "Map_Tile_16_0":{"terrain":"ocean"}, "Map_Tile_11_6":{"terrain":"reef"}, "Locations":{"1":{"name":"Enemy Army Shuffle", "setArea":null, "interactable":false, "id":1, "centre":{"y":0, "x":0}, "getArea":null, "positions":{}}, "2":{"name":"Allied Army Shuffle", "setArea":null, "interactable":false, "id":2, "centre":{"y":0, "x":0}, "getArea":null, "positions":{}}, "3":{"name":"Shuffle2", "setArea":null, "interactable":false, "id":3, "centre":{"y":5, "x":18}, "getArea":null, "positions":[{"y":0, "x":19}, {"y":3, "x":17}, {"y":10, "x":19}, {"y":7, "x":19}, {"y":8, "x":17}, {"y":1, "x":15}, {"y":3, "x":20}]}, "4":{"name":"Shuffle3", "setArea":null, "interactable":false, "id":4, "centre":{"y":0, "x":0}, "getArea":null, "positions":{}}, "5":{"name":"Fortress", "setArea":null, "interactable":false, "id":5, "centre":{"y":5, "x":24}, "getArea":null, "positions":[{"y":4, "x":24}, {"y":4, "x":25}, {"y":5, "x":25}, {"y":6, "x":25}, {"y":5, "x":24}, {"y":6, "x":24}, {"y":4, "x":23}, {"y":6, "x":23}]}, "6":{"name":"Power Crystal", "setArea":null, "interactable":false, "id":6, "centre":{"y":5, "x":13}, "getArea":null, "positions":[{"y":5, "x":13}]}, "7":{"name":"Production Structures", "setArea":null, "interactable":false, "id":7, "centre":{"y":5, "x":24}, "getArea":null, "positions":[{"y":3, "x":22}, {"y":3, "x":26}, {"y":7, "x":26}, {"y":7, "x":22}]}, "8":{"name":"Organ", "setArea":null, "interactable":false, "id":8, "centre":{"y":5, "x":23}, "getArea":null, "positions":[{"y":5, "x":23}]}, "0":{"name":"Shuffle1", "setArea":null, "interactable":false, "id":0, "centre":{"y":6, "x":9}, "getArea":null, "positions":[{"y":1, "x":9}, {"y":1, "x":11}, {"y":4, "x":9}, {"y":7, "x":8}, {"y":7, "x":10}, {"y":7, "x":6}, {"y":10, "x":10}, {"y":9, "x":11}, {"y":10, "x":8}, {"y":4, "x":7}]}}, "Map_Tile_27_10":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":47, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":10, "x":27}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":10, "x":27}, "inTransport":false}}, "Map_Tile_27_9":{"terrain":"ocean"}, "Map_Tile_20_4":{"terrain":"ocean"}, "Map_Tile_18_3":{"terrain":"ocean"}, "Map_Tile_11_0":{"terrain":"ocean"}, "Map_Tile_10_5":{"terrain":"sea"}, "Map_Tile_16_4":{"terrain":"sea"}, "Map_Tile_0_7":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":38, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":0}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":0}, "inTransport":false}}, "Map_Tile_18_6":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"harpy", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":3, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"harpyClaws", "maxRange":1, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"harpy", "isCommander":false, "aliasId":"", "movementType":"flying", "passiveMultiplier":1.25, "loadCapacity":0, "inAir":true, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":600, "canReinforce":false, "moveRange":6, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["harpy", "type.air"], "weaponIds":["harpyClaws"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":3, "y":6, "x":18}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":3, "y":6, "x":18}, "inTransport":false}}, "Map_Tile_11_1":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":10, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":1, "x":11}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":1, "x":11}, "inTransport":false}}, "Map_Tile_0_5":{"terrain":"ocean"}, "Map_Tile_2_9":{"terrain":"ocean"}, "Map_Tile_19_0":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":16, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":0, "x":19}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":0, "x":19}, "inTransport":false}}, "Map_Tile_5_3":{"terrain":"sea"}, "Map_Tile_2_10":{"terrain":"ocean"}, "Map_Tile_27_1":{"terrain":"ocean"}, "Map_Tile_23_9":{"terrain":"ocean"}, "Map_Tile_26_10":{"terrain":"ocean"}, "Map_Tile_26_9":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"merman", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":18, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"bident", "maxRange":2, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"merman", "isCommander":false, "aliasId":"", "movementType":"amphibious", "passiveMultiplier":2.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":350, "canReinforce":false, "moveRange":5, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["merman", "type.amphibious.light"], "weaponIds":["bident"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":3, "y":9, "x":26}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":3, "y":9, "x":26}, "inTransport":false}}, "Map_Tile_5_4":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"ocean"}, "Map_Tile_10_6":{"terrain":"sea"}, "Map_Tile_0_9":{"terrain":"ocean"}, "Map_Tile_26_7":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"port", "health":100, "miniGrooveId":"", "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":25, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"port", "isCommander":false, "aliasId":"", "movementType":"river_sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":26}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":26}, "inTransport":false}}, "Map_Tile_26_4":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":22, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":4, "x":26}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":4, "x":26}, "inTransport":false}}, "Map_Tile_19_3":{"terrain":"ocean"}, "Map_Tile_26_1":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"merman", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":17, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"bident", "maxRange":2, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"merman", "isCommander":false, "aliasId":"", "movementType":"amphibious", "passiveMultiplier":2.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":350, "canReinforce":false, "moveRange":5, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["merman", "type.amphibious.light"], "weaponIds":["bident"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":3, "y":1, "x":26}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":3, "y":1, "x":26}, "inTransport":false}}, "Map_Tile_17_6":{"terrain":"sea"}, "Map_Tile_25_10":{"terrain":"ocean"}, "Map_Tile_12_10":{"terrain":"ocean"}, "Map_Tile_25_8":{"terrain":"ocean"}, "Map_Tile_25_6":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"fortified_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":45, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"fortified_city", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":1000, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["fortified_city"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"fortified_garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":6, "x":25}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":6, "x":25}, "inTransport":false}}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_16_9":{"terrain":"sea"}, "Map_Tile_25_2":{"terrain":"ocean"}, "Map_Tile_25_1":{"terrain":"ocean"}, "Map_Tile_24_10":{"terrain":"ocean"}, "Map_Tile_24_8":{"terrain":"ocean"}, "Map_Tile_21_5":{"terrain":"sea"}, "Map_Tile_24_2":{"terrain":"ocean"}, "Map_Tile_24_5":{"terrain":"road"}, "Map_Tile_7_5":{"terrain":"sea"}, "Map_Tile_14_5":{"terrain":"wall"}, "Map_Tile_15_2":{"terrain":"ocean"}, "Map_Tile_11_5":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"portal_neutral", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":30, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"portal_neutral", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":false, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":5, "x":11}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":5, "x":11}, "inTransport":false}}, "Map_Tile_14_3":{"terrain":"sea"}, "Map_Tile_16_3":{"terrain":"ocean"}, "Map_Tile_23_6":{"terrain":"road"}, "Map_Tile_14_7":{"terrain":"sea"}, "Map_Tile_2_5":{"terrain":"plains", "unit":{"grooveId":"", "unitClassId":"portal", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":42, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"portal", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":false, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":5, "x":2}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":5, "x":2}, "inTransport":false}}, "Map_Tile_24_7":{"terrain":"ocean"}, "Map_Tile_0_1":{"terrain":"ocean"}, "Map_Tile_23_2":{"terrain":"ocean"}, "Map_Tile_19_6":{"terrain":"sea"}, "Map_Tile_6_5":{"item":{"type":"mages_staff", "unitTypeRestriction":{}, "isConsumable":false, "itemId":49, "pos":{"y":5, "x":6}}, "terrain":"reef"}, "Map_Tile_23_0":{"terrain":"ocean"}, "Map_Tile_22_9":{"terrain":"ocean"}, "Map_Tile_22_7":{"terrain":"plains", "unit":{"grooveId":"", "unitClassId":"tower", "health":100, "miniGrooveId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":9, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"tower", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":22}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":22}, "inTransport":false}}, "Map_Tile_12_5":{"terrain":"wall"}, "Map_Tile_6_1":{"terrain":"ocean"}, "Map_Tile_22_4":{"terrain":"ocean"}, "Map_Tile_5_5":{"terrain":"sea"}, "Map_Tile_21_3":{"terrain":"ocean"}, "Map_Tile_7_1":{"terrain":"ocean"}, "Map_Tile_21_9":{"terrain":"ocean"}, "Objectives":["Destroy the enemy Crystal. (Requires Warship or Kraken)", "Defeat a Harpoon Ship with a Dragon. (Requires Anti-Air and Dragon)", "Destroy the enemy Organ. (Requires Merfolk, Anti-Air, and Anti-Sea)"], "Map_Tile_21_2":{"terrain":"ocean"}, "Map_Tile_5_9":{"terrain":"ocean"}, "Map_Tile_23_1":{"terrain":"ocean"}, "Player_2":{"gold":100, "recruit_archer":true, "recruit_dragon":true, "recruit_merman":true, "team":1, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_soldier":true, "recruit_rifleman":true, "recruit_trebuchet":true, "recruit_travelboat":true, "recruit_witch":true, "recruit_warship":false, "recruit_spearman":true, "recruit_kraken":true, "recruit_caravel":true, "recruit_harpy":true, "recruit_mage":true, "recruit_wagon":true, "recruit_giant":true, "recruit_turtle":true, "recruit_dog":true, "recruit_knight":true, "recruit_thief":true, "recruit_frog":true, "recruit_griffin_walking":true, "recruit_ballista":true}, "Map_Tile_20_8":{"terrain":"ocean"}, "Map_Tile_21_10":{"terrain":"ocean"}, "Map_Tile_1_8":{"terrain":"ocean"}, "Map_Tile_1_4":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"harpy", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":2, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"harpyClaws", "maxRange":1, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"harpy", "isCommander":false, "aliasId":"", "movementType":"flying", "passiveMultiplier":1.25, "loadCapacity":0, "inAir":true, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":600, "canReinforce":false, "moveRange":6, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["harpy", "type.air"], "weaponIds":["harpyClaws"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":4, "x":1}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":4, "x":1}, "inTransport":false}}, "Map_Tile_7_10":{"terrain":"reef"}, "Map_Tile_21_8":{"terrain":"ocean"}, "Map_Tile_6_8":{"terrain":"ocean"}, "Map_Tile_25_5":{"terrain":"road", "unit":{"grooveId":"", "unitClassId":"fortified_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":44, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"fortified_city", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":1000, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["fortified_city"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"fortified_garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":5, "x":25}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":5, "x":25}, "inTransport":false}}, "Map_Tile_9_9":{"terrain":"ocean"}, "Map_Tile_20_2":{"terrain":"ocean"}, "Map_Tile_23_7":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":19, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":23}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":23}, "inTransport":false}}, "Author":"Magnemania", "Map_Tile_23_5":{"terrain":"road"}, "Map_Tile_9_0":{"terrain":"ocean"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_21_0":{"terrain":"ocean"}, "Map_Tile_20_10":{"terrain":"ocean"}, "Map_Tile_27_2":{"terrain":"ocean"}, "Map_Tile_4_10":{"terrain":"sea"}, "Map_Tile_10_1":{"terrain":"ocean"}, "Map_Tile_4_1":{"terrain":"ocean"}, "Map_Tile_0_8":{"terrain":"ocean"}, "Map_Tile_2_7":{"terrain":"ocean"}, "Map_Tile_20_6":{"terrain":"sea"}, "Map_Tile_27_5":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"harpoonship", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":26, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"harpoonshipCannon", "maxRange":4, "minRange":2, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"harpoonship", "isCommander":false, "aliasId":"", "movementType":"sailing", "passiveMultiplier":2.0, "loadCapacity":0, "inAir":false, "inWater":true, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":500, "canReinforce":false, "moveRange":8, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["harpoonship", "type.sea.medium"], "weaponIds":["harpoonshipCannon"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":3, "y":5, "x":27}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":3, "y":5, "x":27}, "inTransport":false}}, "Map_Tile_20_3":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":-1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":33, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":20}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":20}, "inTransport":false}}, "Map_Size":{"y":11, "x":28}, "Map_Tile_19_8":{"terrain":"ocean"}, "Map_Tile_9_1":{"terrain":"ocean"}, "Map_Tile_11_10":{"terrain":"ocean"}, "Map_Tile_24_0":{"terrain":"ocean"}, "Map_Tile_9_10":{"terrain":"ocean"}, "Map_Tile_19_7":{"terrain":"sea"}, "Map_Tile_15_10":{"terrain":"ocean"}, "Map_Tile_4_7":{"terrain":"plains", "unit":{"grooveId":"", "unitClassId":"tower", "health":100, "miniGrooveId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":5, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"tower", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":7, "x":4}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":7, "x":4}, "inTransport":false}}, "Map_Tile_16_5":{"terrain":"sea"}, "Map_Tile_1_7":{"terrain":"ocean"}, "Map_Tile_18_10":{"terrain":"ocean"}, "Map_Tile_3_6":{"terrain":"sea"}, "Map_Tile_18_4":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"harpy", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":1, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":6, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"harpyClaws", "maxRange":1, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":2, "id":"harpy", "isCommander":false, "aliasId":"", "movementType":"flying", "passiveMultiplier":1.25, "loadCapacity":0, "inAir":true, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":600, "canReinforce":false, "moveRange":6, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["harpy", "type.air"], "weaponIds":["harpyClaws"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":3, "y":4, "x":18}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":3, "y":4, "x":18}, "inTransport":false}}, "Map_Tile_1_2":{"terrain":"sea"}, "Map_Tile_13_5":{"terrain":"cobblestone"}, "Map_Tile_1_0":{"terrain":"ocean", "unit":{"grooveId":"", "unitClassId":"water_city", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":40, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"water_city", "isCommander":false, "aliasId":"", "movementType":"sea_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":0, "x":1}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":0, "x":1}, "inTransport":false}}, "Map_Tile_0_10":{"terrain":"ocean"}, "Map_Tile_5_2":{"terrain":"sea", "unit":{"grooveId":"", "unitClassId":"caravel", "health":100, "miniGrooveId":"", "recruits":{}, "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":34, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":[{"terrainExclusion":{}, "canAttackSubmerged":false, "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canMoveAndAttack":true, "unitIdWhenAttacking":"", "directionality":"omni", "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"caravelWeapon", "maxRange":1, "minRange":1, "blockedByEnemies":false}], "isStructure":false, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"caravel", "isCommander":false, "aliasId":"", "movementType":"river_sailing", "passiveMultiplier":1.5, "loadCapacity":0, "inAir":false, "inWater":true, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":false, "cost":250, "canReinforce":false, "moveRange":5, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["caravel", "type.sea.light"], "weaponIds":["caravelWeapon"]}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":2, "x":5}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":2, "x":5}, "inTransport":false}}, "Map_Tile_3_10":{"terrain":"sea"}, "Map_Tile_5_1":{"terrain":"ocean"}, "Map_Tile_17_4":{"terrain":"sea"}, "Map_Tile_8_1":{"terrain":"ocean"}, "Map_Tile_18_5":{"terrain":"sea"}, "Map_Tile_3_4":{"terrain":"sea"}, "Map_Tile_15_4":{"terrain":"reef"}, "Map_Tile_15_3":{"terrain":"sea"}, "Map_Tile_14_2":{"terrain":"sea"}, "Map_Tile_14_8":{"terrain":"sea"}, "Map_Tile_13_9":{"terrain":"ocean"}, "Map_Tile_13_8":{"terrain":"sea"}, "Map_Tile_23_8":{"terrain":"ocean"}, "Map_Tile_4_3":{"terrain":"plains", "unit":{"grooveId":"", "unitClassId":"tower", "health":100, "miniGrooveId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "factionOverride":"", "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "merchantDiscounts":{}, "transportedBy":-1, "blessings":{}, "items":{}, "playerId":0, "attackerId":-1, "hadTurn":false, "attackerPlayerId":-1, "underwater":false, "state":{}, "grooveCharge":0, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "id":1, "unitClass":{"recruitingCostMultiplier":1.0, "weapons":{}, "isStructure":true, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "maxHealth":100, "isAttackable":true, "resourceCost":1, "id":"tower", "isCommander":false, "aliasId":"", "movementType":"land_building", "passiveMultiplier":1.0, "loadCapacity":0, "inAir":false, "inWater":false, "critConditionId":"", "isRecruitable":true, "canBeActivated":false, "canBeCaptured":true, "cost":500, "canReinforce":true, "moveRange":0, "reinforceMultiplier":1.0, "canAttack":true, "maxGroove":0, "tags":["structure"], "weaponIds":{}}, "recruitDiscountMultiplier":0.0, "garrisonClassId":"garrison", "canBeAttackedFromDistance":true, "canBeAttacked":true, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "tentacled":false, "damageTakenPercent":100, "itemId":"", "pos":{"facing":0, "y":3, "x":4}, "itemDropNumber":0, "stunned":false, "loadedUnits":{}, "canChargeGroove":true, "attackerUnitClass":"", "killedByLosing":false, "startPos":{"facing":0, "y":3, "x":4}, "inTransport":false}}, "Map_Tile_14_4":{"terrain":"wall"}, "Map_Tile_12_7":{"terrain":"sea"}, "Map_Tile_12_6":{"terrain":"wall"}, "Map_Tile_12_3":{"terrain":"sea"}, "Map_Tile_3_2":{"terrain":"sea"}, "Map_Tile_5_6":{"terrain":"ocean"}, "Map_Tile_12_1":{"terrain":"ocean"}, "Map_Tile_4_2":{"terrain":"sea"}, "Map_Tile_12_0":{"terrain":"ocean"}, "Map_Tile_27_4":{"terrain":"ocean"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Portal_Peril.json b/worlds/wargroove2/levels/Portal_Peril.json new file mode 100644 index 000000000000..f7883a029e48 --- /dev/null +++ b/worlds/wargroove2/levels/Portal_Peril.json @@ -0,0 +1 @@ +{"Map_Tile_7_4":{"terrain":"sea"}, "Map_Name":"Portal Peril", "Map_Tile_6_4":{"terrain":"sea"}, "Map_Tile_14_9":{"terrain":"road"}, "Map_Tile_5_11":{"terrain":"forest"}, "Map_Tile_0_12":{"terrain":"forest"}, "Map_Tile_8_5":{"terrain":"sea"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_4_5":{"terrain":"sea"}, "Counters":{"0":0}, "Map_Tile_2_9":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"portal", "canReinforce":false}, "pos":{"y":9, "x":2, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":19, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"portal", "factionOverride":"", "transportedBy":-1, "startPos":{"y":9, "x":2, "facing":0}}, "terrain":"plains"}, "Map_Tile_14_2":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":2, "x":14, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":9, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":2, "x":14, "facing":0}}, "terrain":"plains"}, "Map_Tile_6_12":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"forest"}, "Map_Tile_6_5":{"terrain":"reef"}, "Map_Tile_2_6":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"barracks", "canReinforce":true}, "pos":{"y":6, "x":2, "facing":0}, "grooveCharge":0, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":17, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"barracks", "factionOverride":"", "transportedBy":-1, "startPos":{"y":6, "x":2, "facing":0}}, "terrain":"plains"}, "Map_Tile_5_4":{"terrain":"sea"}, "Map_Tile_4_0":{"terrain":"mountain"}, "Map_Tile_11_10":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"hideout", "canReinforce":true}, "pos":{"y":10, "x":11, "facing":0}, "grooveCharge":0, "recruits":["thief", "rifleman"], "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":13, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"hideout", "factionOverride":"", "transportedBy":-1, "startPos":{"y":10, "x":11, "facing":0}}, "terrain":"plains"}, "Map_Tile_7_2":{"terrain":"road"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Size":{"y":14, "x":17}, "Map_Tile_3_6":{"terrain":"sea"}, "Map_Tile_4_2":{"terrain":"mountain"}, "Map_Tile_3_3":{"terrain":"mountain"}, "Map_Tile_6_9":{"terrain":"sea"}, "Map_Tile_7_8":{"terrain":"sea"}, "Map_Tile_7_11":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"forest"}, "Map_Tile_2_8":{"terrain":"forest"}, "Map_Tile_9_5":{"terrain":"sea"}, "Map_Tile_14_8":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":3000, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"hq", "canReinforce":false}, "pos":{"y":8, "x":14, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":3, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"hq", "factionOverride":"", "transportedBy":-1, "startPos":{"y":8, "x":14, "facing":0}}, "terrain":"road"}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_5_5":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"beach"}, "Map_Tile_3_10":{"terrain":"road"}, "Map_Tile_3_11":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":3, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":[{"directionality":"omni", "id":"merciaSword", "canAttackSubmerged":false, "maxRange":1, "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canCounterAttack":true, "canAttackAir":false, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "minRange":1}], "isStructure":false, "reinforceMultiplier":1.0, "movementType":"walking", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":4, "weaponIds":["merciaSword"], "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":false, "canBeCaptured":false, "cost":500, "inAir":false, "inWater":false, "tags":["commander", "type.ground.light"], "maxGroove":250, "isCommander":true, "id":"commander_mercia", "canReinforce":false}, "pos":{"y":11, "x":3, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"", "id":18, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"heal_aura", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"commander_mercia", "factionOverride":"", "transportedBy":-1, "startPos":{"y":11, "x":3, "facing":0}}, "terrain":"plains"}, "Map_Tile_7_5":{"terrain":"sea"}, "Triggers":[{"recurring":"start_of_match", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Export (Always on Top)", "conditions":{}, "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["0", "Portal Peril", "Magnemania", "Defeat 4 units with Dogs. (Requires Wagon)", "Charge your Groove to 200%. (Requires Wagon)", "", "Win with standard conditions. (Requires Wagon)"], "id":"ap_export"}]}, {"recurring":"once", "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"Set AI", "conditions":{}, "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["current", "balanced"], "id":"ai_set_profile"}]}, {"recurring":"repeat", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Dog Victory", "conditions":[{"enabled":true, "parameters":["dog", "P1", "*unit", "P2", "-1"], "id":"unit_killed"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["0", "1", "1"], "id":"modify_counter"}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Equip Archersbane", "conditions":{}, "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["7", "fortified_shield"], "id":"set_item"}]}, {"recurring":"once", "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"AI Commander Warps In", "conditions":[{"enabled":true, "parameters":["0", "3"], "id":"current_turn_number"}, {"enabled":true, "parameters":["current"], "id":"player_turn"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["6", "0"], "id":"centre_camera"}, {"enabled":true, "parameters":["*commander", "6", "P2", "0", "0", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit"}, {"enabled":true, "parameters":["*commander", "-1", "current", "1"], "id":"set_unit_spent"}, {"enabled":true, "parameters":["*commander", "-1", "current", "0", "100"], "id":"modify_groove"}, {"enabled":true, "parameters":["500", "1", "1", "5"], "id":"screenshake"}]}, {"recurring":"start_of_match", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"Shuffle Units", "conditions":{}, "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["*unit_structure", "any", "0", "0", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "1", "1", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "2", "2", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "3", "3", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "4", "4", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "5", "5", "1"], "id":"unit_random_teleport"}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_no_units", "conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_commander", "conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_defeat_hq", "conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}]}, {"recurring":"oncePerPlayer", "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_victory", "conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["current"], "id":"victory"}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Wins (Check 253325)", "conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["253325"], "id":"ap_location_send"}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"4 Dog Victories (Check 253326)", "conditions":[{"enabled":true, "parameters":["0", "0", "4"], "id":"check_map_counter"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["253326"], "id":"ap_location_send"}]}, {"recurring":"once", "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Overcharged Groove (Check 253327)", "conditions":[{"enabled":true, "parameters":["current", "4", "1", "*commander", "-1", "4", "200"], "id":"unit_groove"}], "isIntro":false, "enabled":true, "actions":[{"enabled":true, "parameters":["253327"], "id":"ap_location_send"}]}], "Map_Tile_1_5":{"terrain":"road"}, "Map_Tile_16_13":{"terrain":"mountain"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"forest"}, "Map_Tile_2_13":{"terrain":"road"}, "Map_Tile_11_6":{"terrain":"beach"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_9_11":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"lumbermill", "canReinforce":true}, "pos":{"y":11, "x":9, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":21, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"lumbermill", "factionOverride":"", "transportedBy":-1, "startPos":{"y":11, "x":9, "facing":0}}, "terrain":"plains"}, "Map_Tile_8_13":{"terrain":"reef"}, "Map_Tile_9_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":[{"directionality":"omni", "id":"sword", "canAttackSubmerged":false, "maxRange":1, "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canCounterAttack":true, "canAttackAir":false, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "minRange":1}], "isStructure":false, "reinforceMultiplier":1.0, "movementType":"walking", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.5, "isAttackable":true, "moveRange":4, "weaponIds":["sword"], "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":100, "inAir":false, "inWater":false, "tags":["soldier", "type.ground.light"], "maxGroove":0, "isCommander":false, "id":"soldier", "canReinforce":false}, "pos":{"y":12, "x":9, "facing":3}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"", "id":14, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"soldier", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":9, "facing":3}}, "terrain":"forest"}, "Map_Tile_10_11":{"terrain":"forest_cut"}, "Map_Tile_13_11":{"terrain":"forest"}, "Map_Tile_16_7":{"terrain":"forest"}, "Map_Tile_16_6":{"terrain":"beach"}, "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"sea"}, "Map_Tile_10_1":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"portal_neutral", "canReinforce":false}, "pos":{"y":1, "x":10, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":-1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":22, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"portal_neutral", "factionOverride":"", "transportedBy":-1, "startPos":{"y":1, "x":10, "facing":0}}, "terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_6_7":{"terrain":"sea"}, "Map_Tile_6_6":{"terrain":"sea"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"sea"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"road"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"sea"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"road"}, "Map_Tile_5_12":{"terrain":"sea"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"sea"}, "Map_Tile_15_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":12, "x":15, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":12, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":15, "facing":0}}, "terrain":"plains"}, "Player_2":{"recruit_ballista":false, "recruit_spearman":true, "recruit_mage":true, "recruit_archer":true, "recruit_giant":false, "recruit_balloon":true, "recruit_griffin_walking":true, "recruit_merman":true, "recruit_harpoonship":true, "recruit_frog":true, "recruit_dog":true, "recruit_soldier":true, "recruit_turtle":true, "recruit_wagon":true, "recruit_witch":true, "recruit_travelboat":true, "recruit_warship":true, "recruit_rifleman":true, "recruit_kraken":true, "recruit_knight":true, "team":1, "recruit_harpy":true, "recruit_trebuchet":true, "recruit_dragon":true, "gold":100, "recruit_caravel":true, "recruit_thief":true}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"mountain"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"forest"}, "Map_Tile_4_8":{"terrain":"road"}, "Map_Tile_2_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":[{"directionality":"omni", "id":"spear", "canAttackSubmerged":false, "maxRange":1, "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canCounterAttack":true, "canAttackAir":false, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "minRange":1}], "isStructure":false, "reinforceMultiplier":1.0, "movementType":"walking", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.5, "isAttackable":true, "moveRange":3, "weaponIds":["spear"], "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":250, "inAir":false, "inWater":false, "tags":["spearman", "type.ground.light"], "maxGroove":0, "isCommander":false, "id":"spearman", "canReinforce":false}, "pos":{"y":12, "x":2, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"", "id":23, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"spearman", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":2, "facing":0}}, "terrain":"road"}, "Map_Tile_12_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"barracks", "canReinforce":true}, "pos":{"y":12, "x":12, "facing":0}, "grooveCharge":0, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":1, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"barracks", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":12, "facing":0}}, "terrain":"road"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"road"}, "Map_Tile_15_7":{"terrain":"forest_cut"}, "Flags":{}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"mountain"}, "Map_Tile_12_10":{"terrain":"road"}, "Map_Tile_10_5":{"terrain":"forest"}, "Map_Tile_2_2":{"terrain":"road"}, "Map_Tile_0_5":{"terrain":"road"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"road"}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"mountain"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"mountain"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"road"}, "Map_Tile_3_0":{"terrain":"mountain"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_0_7":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":7, "x":0, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":-1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":7, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":7, "x":0, "facing":0}}, "terrain":"plains"}, "Map_Tile_14_6":{"terrain":"bridge"}, "Map_Tile_14_10":{"terrain":"road"}, "Map_Tile_3_4":{"terrain":"mountain"}, "Map_Tile_14_7":{"terrain":"road"}, "Map_Tile_1_6":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"mountain"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"road"}, "Map_Tile_14_5":{"terrain":"road"}, "Map_Tile_14_4":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"portal", "canReinforce":false}, "pos":{"y":4, "x":14, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":4, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"portal", "factionOverride":"", "transportedBy":-1, "startPos":{"y":4, "x":14, "facing":0}}, "terrain":"plains"}, "Map_Tile_15_3":{"terrain":"road"}, "Map_Tile_1_0":{"terrain":"mountain"}, "Map_Tile_9_10":{"terrain":"forest"}, "Map_Tile_1_13":{"terrain":"forest"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_4_4":{"terrain":"mountain"}, "Map_Tile_10_2":{"terrain":"road"}, "Map_Tile_4_6":{"terrain":"sea"}, "Map_Tile_8_1":{"terrain":"mountain"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"mountain"}, "Objectives":["Defeat 4 units with Dogs. (Requires Wagon)", "Charge your Groove to 200%. (Requires Wagon)", "Win with standard conditions. (Requires Wagon)"], "Map_Tile_5_1":{"terrain":"road"}, "Map_Tile_9_6":{"terrain":"sea"}, "Map_Tile_13_2":{"terrain":"road"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"forest_cut"}, "Map_Tile_7_12":{"terrain":"sea"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"forest"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"bridge"}, "Map_Tile_13_5":{"terrain":"road"}, "Map_Tile_5_2":{"terrain":"mountain"}, "Map_Tile_12_2":{"terrain":"road"}, "Map_Tile_10_13":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"portal", "canReinforce":false}, "pos":{"y":13, "x":10, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":15, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"portal", "factionOverride":"", "transportedBy":-1, "startPos":{"y":13, "x":10, "facing":0}}, "terrain":"plains"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_5_3":{"terrain":"mountain"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"mountain"}, "Map_Tile_13_0":{"terrain":"forest"}, "Map_Tile_4_7":{"terrain":"road"}, "Map_Tile_12_13":{"terrain":"road"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"road"}, "Map_Tile_6_3":{"terrain":"mountain"}, "Map_Tile_5_8":{"terrain":"forest"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Player_1":{"recruit_ballista":true, "recruit_spearman":true, "recruit_mage":true, "recruit_archer":true, "recruit_giant":true, "recruit_balloon":true, "recruit_griffin_walking":true, "recruit_merman":true, "recruit_harpoonship":true, "recruit_frog":true, "recruit_dog":true, "recruit_soldier":true, "recruit_turtle":true, "recruit_wagon":true, "recruit_witch":true, "recruit_travelboat":true, "recruit_warship":true, "recruit_rifleman":true, "recruit_kraken":true, "recruit_knight":true, "team":0, "recruit_harpy":true, "recruit_trebuchet":true, "recruit_dragon":true, "gold":100, "recruit_caravel":true, "recruit_thief":true}, "Map_Tile_12_7":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":7, "x":12, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":-1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":8, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":7, "x":12, "facing":0}}, "terrain":"plains"}, "Map_Tile_16_0":{"terrain":"mountain"}, "Map_Tile_12_1":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":3000, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"hq", "canReinforce":false}, "pos":{"y":1, "x":12, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":2, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"hq", "factionOverride":"", "transportedBy":-1, "startPos":{"y":1, "x":12, "facing":0}}, "terrain":"plains"}, "Map_Tile_12_0":{"terrain":"forest"}, "Map_Tile_11_13":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":false, "reinforceMultiplier":1.0, "movementType":"walking", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":6, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":400, "inAir":false, "inWater":false, "tags":["thief", "type.ground.hideout"], "maxGroove":0, "isCommander":false, "id":"thief", "canReinforce":false}, "pos":{"y":13, "x":11, "facing":3}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":[{"key":"gold", "value":"0"}], "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"", "id":11, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"thief", "factionOverride":"", "transportedBy":-1, "startPos":{"y":13, "x":11, "facing":3}}, "terrain":"plains"}, "Map_Tile_7_0":{"terrain":"mountain"}, "Map_Tile_1_8":{"terrain":"mountain"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_3_1":{"terrain":"road"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"beach"}, "Map_Tile_11_4":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"road"}, "Map_Tile_5_9":{"item":{"type":"fortified_shield", "itemId":27, "unitTypeRestriction":{}, "pos":{"y":9, "x":5}, "isConsumable":false}, "terrain":"plains"}, "Map_Tile_11_3":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"road"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"road"}, "Player_Count":2, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_4_9":{"terrain":"road"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"forest"}, "Map_Tile_1_1":{"item":{"type":"groove_boost", "itemId":25, "unitTypeRestriction":{}, "pos":{"y":1, "x":1}, "isConsumable":true}, "terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"mountain"}, "Map_Tile_10_7":{"terrain":"forest"}, "Author":"Magnemania", "Map_Tile_10_6":{"terrain":"beach"}, "Map_Tile_14_3":{"terrain":"road"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"mountain"}, "Map_Tile_9_7":{"terrain":"sea"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"sea"}, "Map_Tile_4_1":{"terrain":"road"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"reef"}, "Map_Tile_7_13":{"terrain":"reef"}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"road"}, "Map_Tile_12_6":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"road"}, "Map_Tile_0_3":{"terrain":"road"}, "Map_Tile_4_13":{"terrain":"sea"}, "Map_Tile_11_0":{"terrain":"forest"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"mountain"}, "Map_Tile_3_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":[{"directionality":"omni", "id":"bow", "canAttackSubmerged":false, "maxRange":3, "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canCounterAttack":true, "canAttackAir":true, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "minRange":1}], "isStructure":false, "reinforceMultiplier":1.0, "movementType":"walking", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.3500000238419, "isAttackable":true, "moveRange":3, "weaponIds":["bow"], "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":500, "inAir":false, "inWater":false, "tags":["archer", "type.ground.light"], "maxGroove":0, "isCommander":false, "id":"archer", "canReinforce":false}, "pos":{"y":12, "x":3, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"", "id":24, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"archer", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":3, "facing":0}}, "terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"mountain"}, "Map_Tile_8_11":{"terrain":"sea"}, "Map_Tile_4_3":{"terrain":"mountain"}, "Map_Tile_3_8":{"terrain":"forest"}, "Map_Tile_7_10":{"terrain":"sea"}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_0_10":{"item":{"type":"fortified_shield", "itemId":26, "unitTypeRestriction":{}, "pos":{"y":10, "x":0}, "isConsumable":false}, "terrain":"plains"}, "Map_Tile_1_7":{"terrain":"road"}, "Map_Tile_0_6":{"terrain":"forest"}, "Map_Tile_3_2":{"terrain":"mountain"}, "Map_Tile_0_2":{"terrain":"road"}, "Map_Tile_8_7":{"terrain":"sea"}, "Locations":{"1":{"getArea":null, "setArea":null, "id":1, "interactable":false, "name":"Enemy Army Shuffle", "centre":{"y":12, "x":11}, "positions":[{"y":11, "x":10}, {"y":12, "x":11}, {"y":13, "x":11}, {"y":12, "x":10}, {"y":12, "x":9}, {"y":13, "x":12}]}, "2":{"getArea":null, "setArea":null, "id":2, "interactable":false, "name":"Allied Army Shuffle", "centre":{"y":12, "x":3}, "positions":[{"y":12, "x":4}, {"y":12, "x":3}, {"y":12, "x":2}, {"y":11, "x":2}, {"y":11, "x":4}]}, "3":{"getArea":null, "setArea":null, "id":3, "interactable":false, "name":"Shuffle2", "centre":{"y":3, "x":12}, "positions":[{"y":3, "x":12}, {"y":3, "x":10}, {"y":5, "x":15}, {"y":1, "x":15}, {"y":4, "x":8}]}, "4":{"getArea":null, "setArea":null, "id":4, "interactable":false, "name":"Shuffle3", "centre":{"y":9, "x":13}, "positions":[{"y":7, "x":12}, {"y":7, "x":10}, {"y":10, "x":15}, {"y":9, "x":15}, {"y":9, "x":12}, {"y":10, "x":16}]}, "5":{"getArea":null, "setArea":null, "id":5, "interactable":false, "name":"Enemy Structure Shuffle", "centre":{"y":11, "x":13}, "positions":[{"y":12, "x":15}, {"y":13, "x":13}, {"y":11, "x":15}, {"y":12, "x":13}, {"y":11, "x":14}, {"y":9, "x":10}]}, "6":{"getArea":null, "setArea":null, "id":6, "interactable":false, "name":"Enemy Commander Spawn", "centre":{"y":13, "x":10}, "positions":[{"y":12, "x":10}, {"y":13, "x":9}, {"y":13, "x":11}]}, "0":{"getArea":null, "setArea":null, "id":0, "interactable":false, "name":"Shuffle1", "centre":{"y":3, "x":3}, "positions":[{"y":1, "x":0}, {"y":1, "x":7}, {"y":3, "x":7}, {"y":3, "x":1}, {"y":7, "x":0}]}}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"sea"}, "Map_Tile_0_4":{"terrain":"road"}, "Map_Tile_13_3":{"terrain":"road"}, "Map_Tile_9_3":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":3, "x":9, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":10, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":3, "x":9, "facing":0}}, "terrain":"plains"}, "Map_Tile_1_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"barracks", "canReinforce":true}, "pos":{"y":12, "x":1, "facing":0}, "grooveCharge":0, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "playerId":-1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":16, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"barracks", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":1, "facing":0}}, "terrain":"plains"}, "Map_Tile_2_7":{"terrain":"road"}, "Map_Tile_0_13":{"terrain":"forest"}, "Map_Tile_16_3":{"terrain":"road"}, "Map_Tile_8_8":{"terrain":"sea"}, "Map_Tile_3_5":{"terrain":"sea"}, "Map_Tile_5_0":{"terrain":"mountain"}, "Map_Tile_0_8":{"terrain":"forest"}, "Map_Tile_15_13":{"terrain":"mountain"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_4_12":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":[{"directionality":"omni", "id":"spear", "canAttackSubmerged":false, "maxRange":1, "unitIdWhenAttacking":"", "canMoveAndAttack":true, "terrainExclusion":{}, "canCounterAttack":true, "canAttackAir":false, "horizontalAndVerticalOnly":false, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "minRange":1}], "isStructure":false, "reinforceMultiplier":1.0, "movementType":"walking", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.5, "isAttackable":true, "moveRange":3, "weaponIds":["spear"], "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":false, "cost":250, "inAir":false, "inWater":false, "tags":["spearman", "type.ground.light"], "maxGroove":0, "isCommander":false, "id":"spearman", "canReinforce":false}, "pos":{"y":12, "x":4, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":0, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"", "id":20, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"spearman", "factionOverride":"", "transportedBy":-1, "startPos":{"y":12, "x":4, "facing":0}}, "terrain":"forest"}, "Map_Tile_13_13":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":13, "x":13, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":5, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":13, "x":13, "facing":0}}, "terrain":"plains"}, "Map_Tile_0_1":{"unit":{"unitClass":{"transportTags":{}, "critConditionId":"", "aliasId":"", "resourceCost":1, "canBeActivated":false, "isDamagingParentUnit":false, "canAttack":true, "weapons":{}, "isStructure":true, "reinforceMultiplier":1.0, "movementType":"land_building", "loadCapacity":0, "verbCostMultiplier":1.0, "passiveMultiplier":1.0, "isAttackable":true, "moveRange":0, "weaponIds":{}, "maxHealth":100, "recruitingCostMultiplier":1.0, "isRecruitable":true, "canBeCaptured":true, "cost":500, "inAir":false, "inWater":false, "tags":["structure"], "maxGroove":0, "isCommander":false, "id":"city", "canReinforce":true}, "pos":{"y":1, "x":0, "facing":0}, "grooveCharge":0, "recruits":{}, "playerId":-1, "merchantDiscountMultiplier":0.0, "blessings":{}, "attachedFlagId":-1, "state":{}, "inTransport":false, "attackerId":-1, "killedByLosing":false, "garrisonClassId":"garrison", "id":6, "canChargeGroove":true, "itemDropNumber":0, "items":{}, "underwater":false, "recruitDiscounts":{}, "damageTakenPercent":100, "attackerPlayerId":-1, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "canBeAttacked":true, "health":100, "hasBeenKilled":false, "setGroove":null, "setHealth":null, "canBeAttackedFromDistance":true, "loadedUnits":{}, "grooveId":"", "tentacled":false, "hadTurn":false, "stunned":false, "itemId":"", "miniGrooveId":"", "attackerUnitClass":"", "rangedDamageTakenPercent":100, "unitClassId":"city", "factionOverride":"", "transportedBy":-1, "startPos":{"y":1, "x":0, "facing":0}}, "terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Precarious_Cliffs.json b/worlds/wargroove2/levels/Precarious_Cliffs.json new file mode 100644 index 000000000000..c96072db5b3e --- /dev/null +++ b/worlds/wargroove2/levels/Precarious_Cliffs.json @@ -0,0 +1 @@ +{"Map_Tile_8_5":{"terrain":"abyss"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"abyss"}, "Map_Tile_8_8":{"terrain":"cobblestone"}, "Map_Tile_3_2":{"terrain":"abyss"}, "Map_Tile_11_9":{"terrain":"abyss"}, "Map_Tile_10_2":{"terrain":"abyss"}, "Map_Tile_12_1":{"terrain":"abyss"}, "Map_Tile_7_8":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":6, "isStructure":false, "cost":800, "weapons":[{"canMoveAndAttack":false, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":6, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"ballistaBolt", "canAttackSubmerged":false, "minRange":2}], "verbCostMultiplier":1.0, "resourceCost":1, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["ballista", "type.ground.heavy"], "critConditionId":"", "movementType":"wheels", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"ballista", "canAttack":true, "weaponIds":["ballistaBolt"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":7, "y":8}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":7, "y":8}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"ballista", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":15}, "terrain":"plains"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_9_5":{"terrain":"abyss"}, "Map_Tile_22_9":{"terrain":"abyss"}, "Map_Tile_22_12":{"terrain":"abyss"}, "Map_Tile_11_2":{"terrain":"abyss"}, "Map_Tile_20_11":{"terrain":"plains"}, "Map_Tile_22_2":{"terrain":"mountain"}, "Map_Tile_16_12":{"terrain":"abyss"}, "Map_Tile_4_12":{"terrain":"abyss"}, "Map_Tile_3_3":{"terrain":"mountain"}, "Map_Tile_11_1":{"terrain":"abyss"}, "Map_Tile_15_3":{"terrain":"forest"}, "Map_Tile_2_6":{"terrain":"abyss"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"abyss"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_0_7":{"terrain":"abyss"}, "Map_Tile_2_10":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":2, "y":10}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":2, "y":10}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":9}, "terrain":"road"}, "Map_Tile_9_8":{"terrain":"forest"}, "Map_Tile_1_9":{"terrain":"abyss"}, "Map_Tile_20_3":{"terrain":"mountain"}, "Triggers":[{"recurring":"start_of_match", "id":"Export", "actions":[{"parameters":["643", "Precarious Cliffs", "Fly Sniper", "Have an enemy Witch attack without a crit (Requires Airstrike Event).", "Defeat a ballista with an archer (Requires Airstrike Event and Archer).", "", "Defend your stronghold for 10 turns (Requires Airstike Event and Archer)."], "id":"ap_export", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "enabled":true}, {"recurring":"start_of_match", "id":"Init", "actions":[{"parameters":["*unit_structure", "P2", "1", "1", "1"], "id":"unit_random_teleport", "enabled":true}, {"parameters":["*unit_structure", "P2", "2", "2", "1"], "id":"unit_random_teleport", "enabled":true}, {"parameters":["*unit_structure", "P2", "3", "3", "1"], "id":"unit_random_teleport", "enabled":true}, {"parameters":["*commander", "P1", "0", "0", "1"], "id":"unit_random_teleport", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units", "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence", "enabled":true}], "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander", "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["*commander", "current", "-1"], "id":"unit_lost", "enabled":true}], "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq", "actions":[{"parameters":["current"], "id":"eliminate", "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["hq", "current", "-1"], "id":"unit_lost", "enabled":true}], "enabled":true}, {"recurring":"oncePerPlayer", "id":"$trigger_default_victory", "actions":[{"parameters":["current"], "id":"victory", "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0"], "id":"number_of_opponents", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"P1 Victory (253033)", "actions":[{"parameters":["253033"], "id":"ap_location_send", "enabled":true}, {"parameters":["P1"], "id":"victory", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["0", "10"], "id":"current_turn_number", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Enemy Witch No Crit (253034)", "actions":[{"parameters":["253034"], "id":"ap_location_send", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["P2", "0", "1", "witch", "-2"], "id":"unit_presence", "enabled":true}, {"parameters":["P2", "0", "0", "witch", "-10"], "id":"unit_presence", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Destroy Ballista with Archer (253035)", "actions":[{"parameters":["253035"], "id":"ap_location_send", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["archer", "P1", "ballista", "P2", "-1"], "id":"unit_killed", "enabled":true}], "enabled":true}, {"recurring":"start_of_match", "id":"Spawn Archers", "actions":[{"parameters":["archer", "0", "P1", "1", "1", "4", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["252003", "0", "1"], "id":"ap_has_item", "enabled":true}], "enabled":true}, {"recurring":"start_of_match", "id":"Spawn Air Units", "actions":[{"parameters":["harpy", "0", "P1", "1", "1", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["witch", "0", "P1", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["balloon", "0", "P1", "1", "1", "3", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["dragon", "0", "P1", "1", "1", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["252026", "0", "1"], "id":"ap_has_item", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Enemy Reinforcements Warning", "actions":[{"parameters":["sad", "generic_archer", "The enemy will receive ground and air reinforcements next turn.", "1", "Weather Reporter"], "id":"dialogue_box_simple", "enabled":true}], "isIntro":false, "players":[0, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["0", "4"], "id":"current_turn_number", "enabled":true}, {"parameters":["P2"], "id":"player_turn", "enabled":true}], "enabled":true}, {"recurring":"once", "id":"Enemy Reinforcements", "actions":[{"parameters":["witch", "1", "current", "0", "0", "1", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["harpy", "1", "current", "0", "0", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["mage", "2", "current", "0", "0", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}, {"parameters":["mage", "3", "current", "0", "0", "2", "1", "undefined", "centre"], "id":"ap_spawn_unit", "enabled":true}], "isIntro":false, "players":[0, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["0", "5"], "id":"current_turn_number", "enabled":true}, {"parameters":["P2"], "id":"player_turn", "enabled":true}], "enabled":true}], "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_0_9":{"terrain":"abyss"}, "Map_Tile_0_0":{"terrain":"abyss"}, "Map_Tile_8_1":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":6, "isStructure":false, "cost":800, "weapons":[{"canMoveAndAttack":false, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":6, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"ballistaBolt", "canAttackSubmerged":false, "minRange":2}], "verbCostMultiplier":1.0, "resourceCost":1, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["ballista", "type.ground.heavy"], "critConditionId":"", "movementType":"wheels", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"ballista", "canAttack":true, "weaponIds":["ballistaBolt"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":8, "y":1}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":8, "y":1}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"ballista", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":14}, "terrain":"plains"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_2_8":{"terrain":"abyss"}, "Map_Tile_12_10":{"terrain":"abyss"}, "Map_Tile_3_4":{"terrain":"mountain"}, "Map_Tile_22_1":{"terrain":"abyss"}, "Map_Tile_7_6":{"terrain":"abyss"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"abyss"}, "Map_Tile_11_6":{"terrain":"abyss"}, "Map_Tile_5_8":{"terrain":"abyss"}, "Map_Tile_15_4":{"terrain":"abyss"}, "Map_Tile_5_0":{"terrain":"abyss"}, "Map_Tile_14_3":{"terrain":"road"}, "Map_Tile_4_2":{"terrain":"abyss"}, "Map_Tile_12_3":{"terrain":"abyss"}, "Map_Tile_19_9":{"terrain":"plains"}, "Map_Tile_13_8":{"terrain":"abyss"}, "Map_Tile_23_4":{"terrain":"abyss"}, "Map_Tile_16_8":{"terrain":"abyss"}, "Map_Tile_14_12":{"terrain":"abyss"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_10_11":{"terrain":"abyss"}, "Map_Tile_17_2":{"terrain":"abyss"}, "Map_Tile_16_1":{"terrain":"abyss"}, "Map_Tile_13_0":{"terrain":"abyss"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_17_1":{"terrain":"abyss"}, "Map_Tile_14_11":{"terrain":"abyss"}, "Map_Tile_19_1":{"terrain":"plains"}, "Player_1":{"recruit_harpy":true, "recruit_warship":true, "recruit_archer":true, "recruit_giant":true, "recruit_wagon":true, "recruit_soldier":true, "recruit_witch":true, "recruit_knight":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_turtle":true, "recruit_rifleman":true, "recruit_frog":true, "gold":100, "recruit_dragon":true, "recruit_harpoonship":true, "recruit_thief":true, "recruit_balloon":true, "recruit_merman":true, "recruit_spearman":true, "recruit_mage":true, "team":0, "recruit_caravel":true, "recruit_kraken":true, "recruit_trebuchet":true, "recruit_griffin_walking":true, "recruit_ballista":true}, "Map_Tile_20_7":{"terrain":"abyss"}, "Map_Tile_3_11":{"terrain":"abyss"}, "Map_Tile_21_11":{"terrain":"plains"}, "Map_Tile_19_11":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"abyss"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"abyss"}, "Map_Tile_4_5":{"terrain":"mountain"}, "Map_Tile_10_5":{"terrain":"abyss"}, "Map_Tile_6_8":{"terrain":"abyss"}, "Map_Tile_3_1":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"abyss_bridge"}, "Map_Tile_14_1":{"terrain":"abyss"}, "Map_Tile_22_0":{"terrain":"abyss"}, "Map_Tile_8_0":{"terrain":"abyss"}, "Map_Tile_9_6":{"terrain":"abyss"}, "Map_Tile_19_0":{"terrain":"abyss"}, "Map_Tile_12_6":{"terrain":"mountain"}, "Map_Tile_17_3":{"terrain":"abyss_bridge"}, "Map_Tile_1_12":{"terrain":"forest"}, "Map_Tile_2_7":{"unit":{"unitClass":{"passiveMultiplier":2.0, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":7, "isStructure":false, "cost":750, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"witchSpell", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":3, "inAir":true, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["witch", "type.air", "spellcaster"], "critConditionId":"", "movementType":"flying", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"witch", "canAttack":true, "weaponIds":["witchSpell"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":2, "y":7}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":2, "y":7}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"witch", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":8}, "terrain":"abyss"}, "Map_Tile_15_8":{"terrain":"forest"}, "Map_Tile_19_12":{"terrain":"abyss"}, "Map_Tile_9_12":{"terrain":"abyss"}, "Map_Size":{"y":13, "x":24}, "Map_Tile_0_1":{"terrain":"abyss"}, "Map_Tile_4_6":{"terrain":"abyss"}, "Map_Tile_4_9":{"terrain":"abyss"}, "Map_Tile_6_2":{"terrain":"abyss"}, "Map_Tile_15_11":{"terrain":"abyss"}, "Map_Tile_16_10":{"terrain":"abyss"}, "Map_Tile_14_6":{"unit":{"unitClass":{"passiveMultiplier":1.0, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":true, "moveRange":0, "isStructure":true, "cost":500, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["structure"], "critConditionId":"", "movementType":"land_building", "canBeCaptured":true, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"city", "canAttack":true, "weaponIds":{}, "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":14, "y":6}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"garrison", "recruits":{}, "startPos":{"facing":0, "x":14, "y":6}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":-1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"city", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":16}, "terrain":"plains"}, "Map_Tile_6_4":{"terrain":"abyss_bridge"}, "Map_Tile_17_12":{"terrain":"abyss"}, "Map_Tile_5_9":{"terrain":"abyss"}, "Map_Tile_10_12":{"terrain":"abyss"}, "Map_Tile_6_7":{"terrain":"abyss"}, "Map_Tile_3_12":{"terrain":"abyss"}, "Map_Name":"Precarious Cliffs", "Map_Tile_23_7":{"terrain":"abyss"}, "Map_Tile_1_1":{"unit":{"unitClass":{"passiveMultiplier":2.0, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":7, "isStructure":false, "cost":750, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"witchSpell", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":3, "inAir":true, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["witch", "type.air", "spellcaster"], "critConditionId":"", "movementType":"flying", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"witch", "canAttack":true, "weaponIds":["witchSpell"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":1, "y":1}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":1, "y":1}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"witch", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":3}, "terrain":"abyss"}, "Map_Tile_8_12":{"terrain":"abyss"}, "Map_Tile_2_11":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":2, "y":11}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":2, "y":11}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":10}, "terrain":"road"}, "Map_Tile_6_6":{"terrain":"abyss"}, "Map_Tile_0_6":{"terrain":"abyss"}, "Map_Tile_3_5":{"terrain":"road"}, "Player_2":{"recruit_harpy":true, "recruit_warship":true, "recruit_archer":true, "recruit_giant":true, "recruit_wagon":true, "recruit_soldier":true, "recruit_witch":true, "recruit_knight":true, "recruit_travelboat":true, "recruit_dog":true, "recruit_turtle":true, "recruit_rifleman":true, "recruit_frog":true, "gold":300, "recruit_dragon":true, "recruit_harpoonship":true, "recruit_thief":true, "recruit_balloon":true, "recruit_merman":true, "recruit_spearman":true, "recruit_mage":true, "team":1, "recruit_caravel":true, "recruit_kraken":true, "recruit_trebuchet":true, "recruit_griffin_walking":true, "recruit_ballista":true}, "Map_Tile_9_2":{"terrain":"abyss"}, "Map_Tile_6_12":{"terrain":"abyss"}, "Map_Tile_8_7":{"terrain":"forest"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_9_4":{"terrain":"abyss_bridge"}, "Map_Tile_8_6":{"terrain":"abyss"}, "Map_Tile_17_8":{"terrain":"abyss"}, "Map_Tile_17_0":{"terrain":"abyss"}, "Map_Tile_13_6":{"terrain":"forest"}, "Map_Tile_1_5":{"terrain":"abyss"}, "Map_Tile_10_3":{"terrain":"abyss"}, "Map_Tile_0_10":{"terrain":"abyss"}, "Map_Tile_12_7":{"terrain":"abyss"}, "Map_Tile_21_0":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":3, "x":21, "y":0}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":3, "x":21, "y":0}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":5}, "terrain":"abyss_bridge"}, "Map_Tile_17_10":{"terrain":"abyss"}, "Map_Tile_1_2":{"terrain":"abyss"}, "Map_Tile_9_3":{"terrain":"abyss"}, "Map_Tile_15_5":{"terrain":"abyss"}, "Map_Tile_14_5":{"terrain":"forest"}, "Player_Count":2, "Map_Tile_23_11":{"terrain":"abyss"}, "Map_Tile_11_0":{"terrain":"abyss"}, "Map_Tile_15_0":{"terrain":"abyss"}, "Map_Tile_18_0":{"terrain":"abyss"}, "Map_Tile_17_5":{"terrain":"abyss"}, "Map_Tile_11_5":{"terrain":"abyss"}, "Map_Tile_22_10":{"terrain":"abyss"}, "Map_Tile_12_0":{"terrain":"abyss"}, "Map_Tile_10_4":{"terrain":"abyss_bridge"}, "Map_Tile_8_9":{"terrain":"forest"}, "Map_Tile_18_6":{"terrain":"abyss"}, "Map_Tile_12_12":{"terrain":"abyss"}, "Map_Tile_16_6":{"terrain":"abyss"}, "Map_Tile_7_11":{"terrain":"abyss"}, "Map_Tile_19_7":{"terrain":"abyss"}, "Map_Tile_6_3":{"terrain":"abyss"}, "Map_Tile_2_3":{"unit":{"unitClass":{"passiveMultiplier":1.25, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":6, "isStructure":false, "cost":600, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"harpyClaws", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":1.0, "resourceCost":2, "inAir":true, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["harpy", "type.air"], "critConditionId":"", "movementType":"flying", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"harpy", "canAttack":true, "weaponIds":["harpyClaws"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":2, "y":3}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":2, "y":3}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"harpy", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":7}, "terrain":"abyss"}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_18_1":{"terrain":"abyss"}, "Map_Tile_21_2":{"terrain":"road"}, "Map_Tile_18_2":{"terrain":"plains"}, "Map_Tile_20_4":{"terrain":"forest"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"abyss"}, "Map_Tile_1_3":{"terrain":"abyss"}, "Map_Tile_7_2":{"terrain":"carpet"}, "Map_Tile_20_2":{"terrain":"mountain"}, "Map_Tile_12_11":{"terrain":"abyss"}, "Map_Tile_17_9":{"terrain":"abyss"}, "Map_Tile_21_6":{"terrain":"abyss"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_3_6":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"abyss_bridge"}, "Map_Tile_18_12":{"terrain":"abyss"}, "Map_Tile_14_4":{"terrain":"mountain"}, "Map_Tile_20_0":{"terrain":"abyss"}, "Map_Tile_2_2":{"terrain":"abyss"}, "Map_Tile_1_7":{"terrain":"abyss"}, "Map_Tile_5_4":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"abyss"}, "Map_Tile_0_2":{"terrain":"abyss"}, "Map_Tile_20_1":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":3, "x":20, "y":1}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":3, "x":20, "y":1}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":13}, "terrain":"forest"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_2_5":{"terrain":"forest"}, "Map_Tile_2_9":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"abyss"}, "Locations":{"1":{"name":"P2 Shuffle 1", "getArea":null, "interactable":false, "setArea":null, "centre":{"y":4, "x":1}, "positions":[{"y":7, "x":1}, {"y":7, "x":2}, {"y":7, "x":0}, {"y":8, "x":0}, {"y":6, "x":1}, {"y":5, "x":1}, {"y":4, "x":1}, {"y":3, "x":1}, {"y":3, "x":2}, {"y":2, "x":2}, {"y":1, "x":2}, {"y":0, "x":2}, {"y":1, "x":1}, {"y":2, "x":1}, {"y":0, "x":1}, {"y":1, "x":0}, {"y":0, "x":0}, {"y":2, "x":0}, {"y":3, "x":0}, {"y":4, "x":0}, {"y":5, "x":0}, {"y":6, "x":0}, {"y":9, "x":0}, {"y":10, "x":0}, {"y":8, "x":1}, {"y":9, "x":1}, {"y":6, "x":2}], "id":1}, "2":{"name":"P2 Shuffle 2", "getArea":null, "interactable":false, "setArea":null, "centre":{"y":11, "x":1}, "positions":[{"y":10, "x":3}, {"y":10, "x":2}, {"y":10, "x":1}, {"y":11, "x":1}, {"y":11, "x":2}, {"y":12, "x":1}, {"y":12, "x":2}, {"y":12, "x":0}, {"y":11, "x":0}], "id":2}, "3":{"name":"P2 Shuffle 3", "getArea":null, "interactable":false, "setArea":null, "centre":{"y":2, "x":21}, "positions":[{"y":3, "x":21}, {"y":2, "x":21}, {"y":1, "x":21}, {"y":1, "x":20}, {"y":0, "x":21}, {"y":2, "x":22}, {"y":2, "x":20}, {"y":1, "x":19}], "id":3}, "0":{"name":"P1 Spawn", "getArea":null, "interactable":false, "setArea":null, "centre":{"y":9, "x":20}, "positions":[{"y":8, "x":21}, {"y":8, "x":20}, {"y":9, "x":20}, {"y":9, "x":19}, {"y":10, "x":19}, {"y":10, "x":20}, {"y":10, "x":21}, {"y":9, "x":21}, {"y":11, "x":20}, {"y":11, "x":19}, {"y":12, "x":19}, {"y":8, "x":22}, {"y":8, "x":19}, {"y":9, "x":22}, {"y":9, "x":18}, {"y":10, "x":18}, {"y":11, "x":18}, {"y":7, "x":20}, {"y":7, "x":21}, {"y":8, "x":18}, {"y":7, "x":19}, {"y":7, "x":22}, {"y":11, "x":21}, {"y":10, "x":22}, {"y":11, "x":22}, {"y":12, "x":21}, {"y":12, "x":20}], "id":0}}, "Map_Tile_9_0":{"terrain":"abyss"}, "Map_Tile_6_0":{"terrain":"abyss"}, "Map_Tile_14_0":{"terrain":"abyss"}, "Flags":{}, "Map_Tile_3_0":{"terrain":"abyss"}, "Map_Tile_16_9":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"abyss"}, "Map_Tile_1_6":{"unit":{"unitClass":{"passiveMultiplier":1.25, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":6, "isStructure":false, "cost":600, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"harpyClaws", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":1.0, "resourceCost":2, "inAir":true, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["harpy", "type.air"], "critConditionId":"", "movementType":"flying", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"harpy", "canAttack":true, "weaponIds":["harpyClaws"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":1, "y":6}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":1, "y":6}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"harpy", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":2}, "terrain":"abyss"}, "Map_Tile_10_7":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"abyss"}, "Map_Tile_4_0":{"terrain":"abyss"}, "Map_Tile_23_12":{"terrain":"abyss"}, "Map_Tile_23_10":{"terrain":"abyss"}, "Map_Tile_1_10":{"terrain":"forest"}, "Map_Tile_5_5":{"terrain":"abyss"}, "Map_Tile_23_9":{"terrain":"abyss"}, "Map_Tile_23_8":{"terrain":"abyss"}, "Map_Tile_5_11":{"terrain":"abyss"}, "Map_Tile_23_3":{"terrain":"abyss"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"abyss"}, "Map_Tile_23_6":{"terrain":"abyss"}, "Map_Tile_2_0":{"terrain":"abyss"}, "Map_Tile_23_5":{"terrain":"abyss"}, "Map_Tile_23_2":{"terrain":"abyss"}, "Map_Tile_23_1":{"terrain":"abyss"}, "Map_Tile_17_6":{"terrain":"abyss"}, "Map_Tile_3_8":{"terrain":"abyss_bridge"}, "Map_Tile_6_5":{"terrain":"abyss"}, "Map_Tile_22_11":{"terrain":"abyss"}, "Map_Tile_20_12":{"terrain":"abyss"}, "Map_Tile_15_1":{"terrain":"abyss"}, "Map_Tile_22_7":{"terrain":"abyss"}, "Author":"Fly Sniper", "Map_Tile_22_5":{"terrain":"abyss"}, "Map_Tile_22_4":{"terrain":"abyss"}, "Map_Tile_12_5":{"terrain":"mountain"}, "Map_Tile_22_3":{"terrain":"abyss"}, "Map_Tile_21_12":{"terrain":"abyss"}, "Map_Tile_21_10":{"terrain":"plains"}, "Map_Tile_21_9":{"terrain":"plains"}, "Map_Tile_21_8":{"terrain":"plains"}, "Map_Tile_21_7":{"terrain":"abyss"}, "Map_Tile_21_5":{"terrain":"abyss"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_18_5":{"terrain":"abyss"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_21_3":{"terrain":"forest"}, "Map_Tile_13_10":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"abyss"}, "Map_Tile_20_10":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"abyss"}, "Map_Tile_20_9":{"unit":{"unitClass":{"passiveMultiplier":1.0, "canBeActivated":false, "isCommander":true, "loadCapacity":0, "canReinforce":false, "moveRange":4, "isStructure":false, "cost":500, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"merciaSword", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":1.0, "resourceCost":3, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["commander", "type.ground.light"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":false, "transportTags":{}, "maxGroove":250, "id":"commander_mercia", "canAttack":true, "weaponIds":["merciaSword"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":3, "x":20, "y":9}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":3, "x":20, "y":9}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":0, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"heal_aura", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"commander_mercia", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":17}, "terrain":"forest"}, "Map_Tile_4_1":{"terrain":"abyss"}, "Map_Tile_20_8":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"road"}, "Map_Tile_20_5":{"terrain":"abyss"}, "Map_Tile_19_10":{"terrain":"forest"}, "Map_Tile_18_10":{"terrain":"abyss"}, "Map_Tile_19_6":{"terrain":"abyss"}, "Map_Tile_19_5":{"terrain":"abyss"}, "Map_Tile_19_3":{"terrain":"forest"}, "Map_Tile_1_0":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"abyss"}, "Map_Tile_7_4":{"terrain":"abyss_bridge"}, "Map_Tile_19_2":{"terrain":"forest"}, "Map_Tile_13_11":{"terrain":"abyss"}, "Map_Tile_18_11":{"terrain":"abyss"}, "Map_Tile_2_1":{"terrain":"abyss"}, "Map_Tile_4_7":{"terrain":"abyss"}, "Map_Tile_19_8":{"terrain":"abyss"}, "Map_Tile_18_9":{"terrain":"abyss"}, "Map_Tile_16_5":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"forest"}, "Map_Tile_18_7":{"terrain":"abyss"}, "Map_Tile_10_6":{"terrain":"abyss"}, "Map_Tile_5_1":{"terrain":"abyss"}, "Map_Tile_7_0":{"terrain":"abyss"}, "Map_Tile_21_4":{"terrain":"abyss"}, "Map_Tile_18_4":{"terrain":"abyss"}, "Map_Tile_6_9":{"terrain":"abyss"}, "Map_Tile_1_4":{"terrain":"abyss"}, "Map_Tile_18_3":{"terrain":"abyss_bridge"}, "Objectives":["Have an enemy Witch attack without a crit (Requires Airstrike Event).", "Defeat a ballista with an archer (Requires Airstrike Event and Archer).", "Defend your stronghold for 10 turns (Requires Airstike Event and Archer)."], "Map_Tile_12_2":{"terrain":"abyss"}, "Map_Tile_10_10":{"terrain":"abyss"}, "Map_Tile_17_11":{"terrain":"abyss"}, "Map_Tile_0_8":{"unit":{"unitClass":{"passiveMultiplier":2.0, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":7, "isStructure":false, "cost":750, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"witchSpell", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":3, "inAir":true, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["witch", "type.air", "spellcaster"], "critConditionId":"", "movementType":"flying", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"witch", "canAttack":true, "weaponIds":["witchSpell"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":0, "y":8}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":0, "y":8}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"witch", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":4}, "terrain":"abyss"}, "Map_Tile_14_10":{"terrain":"abyss"}, "Map_Tile_7_7":{"terrain":"mountain"}, "Map_Tile_9_1":{"terrain":"carpet"}, "Map_Tile_17_7":{"terrain":"abyss"}, "Map_Tile_23_0":{"terrain":"abyss"}, "Map_Tile_17_4":{"terrain":"abyss"}, "Map_Tile_3_7":{"terrain":"abyss_bridge"}, "Map_Tile_13_12":{"terrain":"abyss"}, "Map_Tile_21_1":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":3, "x":21, "y":1}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":3, "x":21, "y":1}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":12}, "terrain":"road"}, "Map_Tile_15_10":{"terrain":"abyss"}, "Map_Tile_8_2":{"terrain":"forest"}, "Map_Tile_11_7":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"abyss"}, "Map_Tile_16_11":{"terrain":"abyss"}, "Map_Tile_15_12":{"terrain":"abyss"}, "Map_Tile_5_7":{"terrain":"abyss"}, "Map_Tile_16_3":{"terrain":"abyss_bridge"}, "Map_Tile_16_2":{"terrain":"abyss"}, "Map_Tile_12_9":{"terrain":"abyss"}, "Map_Tile_5_6":{"terrain":"abyss"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_1_11":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":1, "y":11}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":1, "y":11}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":11}, "terrain":"road"}, "Map_Tile_3_10":{"unit":{"unitClass":{"passiveMultiplier":1.5, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":5, "isStructure":false, "cost":400, "weapons":[{"canMoveAndAttack":true, "unitIdWhenAttacking":"", "blockedByEnemies":false, "maxRange":1, "directionality":"omni", "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "horizontalAndVerticalOnly":false, "id":"lightning", "canAttackSubmerged":false, "minRange":1}], "verbCostMultiplier":0.5, "resourceCost":2, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["mage", "type.ground.light", "spellcaster"], "critConditionId":"", "movementType":"walking", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"mage", "canAttack":true, "weaponIds":["lightning"], "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":3, "y":10}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"", "recruits":{}, "startPos":{"facing":0, "x":3, "y":10}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":1, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"mage", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":6}, "terrain":"road"}, "Map_Tile_15_6":{"terrain":"abyss"}, "Map_Tile_0_5":{"terrain":"abyss"}, "Map_Tile_0_4":{"terrain":"abyss"}, "Map_Tile_15_2":{"terrain":"abyss"}, "Map_Tile_0_12":{"terrain":"forest"}, "Map_Tile_4_8":{"terrain":"abyss"}, "Map_Tile_5_12":{"terrain":"abyss"}, "Map_Tile_14_9":{"terrain":"abyss"}, "Map_Tile_5_3":{"terrain":"forest"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"carpet"}, "Map_Tile_4_11":{"terrain":"abyss"}, "Map_Tile_14_2":{"terrain":"abyss"}, "Map_Tile_6_1":{"terrain":"abyss"}, "Map_Tile_8_3":{"terrain":"abyss"}, "Map_Tile_10_0":{"terrain":"abyss"}, "Map_Tile_13_7":{"terrain":"forest"}, "Map_Tile_7_5":{"terrain":"abyss"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_13_5":{"unit":{"unitClass":{"passiveMultiplier":1.0, "canBeActivated":false, "isCommander":false, "loadCapacity":0, "canReinforce":false, "moveRange":0, "isStructure":true, "cost":3000, "weapons":{}, "verbCostMultiplier":1.0, "resourceCost":1, "inAir":false, "isDamagingParentUnit":false, "aliasId":"", "isAttackable":true, "tags":["structure"], "critConditionId":"", "movementType":"land_building", "canBeCaptured":false, "isRecruitable":true, "transportTags":{}, "maxGroove":0, "id":"hq", "canAttack":true, "weaponIds":{}, "inWater":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "maxHealth":100}, "rangedDamageTakenPercent":100, "attackerPlayerId":-1, "pos":{"facing":0, "x":13, "y":5}, "damageTakenPercent":100, "health":100, "items":{}, "hadTurn":false, "stunned":false, "attackerId":-1, "state":{}, "garrisonClassId":"garrison", "recruits":{}, "startPos":{"facing":0, "x":13, "y":5}, "setGroove":null, "loadedUnits":{}, "merchantDiscountMultiplier":0.0, "underwater":false, "itemId":"", "hasBeenKilled":false, "setHealth":null, "playerId":0, "blessings":{}, "canBeAttackedFromDistance":true, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "grooveId":"", "canChargeGroove":true, "factionOverride":"", "inTransport":false, "tentacled":false, "attackerUnitClass":"", "itemDropNumber":0, "unitClassId":"hq", "transportedBy":-1, "killedByLosing":false, "attachedFlagId":-1, "miniGrooveId":"", "canBeAttacked":true, "merchantDiscounts":{}, "recruitDiscounts":{}, "id":1}, "terrain":"plains"}, "Map_Tile_13_3":{"terrain":"road"}, "Counters":{}, "Map_Tile_16_4":{"terrain":"abyss"}, "Map_Tile_0_3":{"terrain":"abyss"}, "Map_Tile_5_2":{"terrain":"abyss"}, "Map_Tile_4_4":{"terrain":"road"}, "Map_Tile_9_11":{"terrain":"abyss"}, "Map_Tile_10_1":{"terrain":"abyss"}, "Map_Tile_22_6":{"terrain":"abyss"}, "Map_Tile_20_6":{"terrain":"abyss"}, "Map_Tile_8_11":{"terrain":"abyss"}, "Map_Tile_2_4":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Riflemen_Blockade.json b/worlds/wargroove2/levels/Riflemen_Blockade.json new file mode 100644 index 000000000000..6049f02de339 --- /dev/null +++ b/worlds/wargroove2/levels/Riflemen_Blockade.json @@ -0,0 +1 @@ +{"Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"wall"}, "Map_Tile_9_9":{"terrain":"mountain"}, "Map_Tile_9_10":{"terrain":"mountain"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"road"}, "Map_Tile_4_19":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_13_17":{"terrain":"forest"}, "Map_Tile_9_15":{"terrain":"road"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"forest"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_7_18":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"forest"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Name":"Riflemen Blockade", "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_11_12":{"terrain":"road"}, "Map_Size":{"y":20, "x":16}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_8_19":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":1, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":19, "x":8, "facing":3}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":500, "moveRange":4, "inWater":false, "critConditionId":"", "tags":["commander", "type.ground.light"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":[{"canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"merciaSword", "terrainExclusion":{}, "minRange":1, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "canAttackSubmerged":false, "canAttackAir":false}], "isStructure":false, "resourceCost":3, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":false, "id":"commander_mercia", "isCommander":true, "movementType":"walking", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":["merciaSword"], "maxGroove":250}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"commander_mercia", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":14, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"heal_aura", "pos":{"y":19, "x":8, "facing":3}, "playerId":0, "garrisonClassId":""}, "terrain":"plains"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"road"}, "Map_Tile_10_17":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"mountain"}, "Map_Tile_12_8":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":8, "x":12, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":8, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":8, "x":12, "facing":0}, "playerId":1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"forest"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"road"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_3_15":{"terrain":"wall"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"forest"}, "Map_Tile_0_10":{"terrain":"road"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_1_18":{"terrain":"plains"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_14_17":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"forest"}, "Map_Tile_11_18":{"terrain":"plains"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_14_18":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"road"}, "Map_Tile_2_14":{"terrain":"wall"}, "Map_Tile_12_2":{"terrain":"plains"}, "Author":"Fly Sniper", "Map_Tile_6_8":{"terrain":"mountain"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"plains"}, "Map_Tile_2_18":{"terrain":"forest"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_2_15":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":200, "canBeAttackedFromDistance":true, "startPos":{"y":15, "x":2, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":650, "moveRange":4, "inWater":false, "critConditionId":"", "tags":["rifleman", "type.ground.hideout"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":[{"canMoveAndAttack":false, "blockedByEnemies":true, "horizontalAndVerticalExtraWidth":1, "canCounterAttack":true, "horizontalAndVerticalOnly":true, "id":"musket", "terrainExclusion":["forest"], "minRange":1, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":9, "canAttackSubmerged":false, "canAttackAir":false}], "isStructure":false, "resourceCost":2, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"rifleman", "isCommander":false, "movementType":"walking", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":["musket"], "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"rifleman", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":1, "recruits":{}, "state":[{"key":"ammo", "value":"3"}], "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":15, "x":2, "facing":0}, "playerId":1, "garrisonClassId":""}, "terrain":"road"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_12_14":{"terrain":"wall"}, "Map_Tile_11_9":{"terrain":"road"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"forest"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"plains"}, "Objectives":["Get a critical hit with a Harpy.", "Get a critical hit with a Dragon.", "Win with standard conditions with all thieves alive (Requires Rifleman)."], "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_0_17":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_8_14":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":1, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":14, "x":8, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":3000, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"hq", "isCommander":false, "movementType":"land_building", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"hq", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":6, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":14, "x":8, "facing":0}, "playerId":0, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_4_12":{"terrain":"road"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"road"}, "Map_Tile_6_4":{"terrain":"plains"}, "Map_Tile_0_19":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"forest"}, "Map_Tile_12_17":{"terrain":"plains"}, "Triggers":[{"id":"Export", "actions":[{"id":"ap_export", "enabled":true, "parameters":["13331", "Riflemen Blockade", "Fly Sniper", "Get a critical hit with a Harpy.", "Get a critical hit with a Dragon.", "", "Win with standard conditions with all thieves alive (Requires Rifleman)."]}], "isIntro":false, "conditions":{}, "recurring":"start_of_match", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_defeat_no_units", "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "isIntro":false, "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "recurring":"oncePerPlayer", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_defeat_commander", "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "isIntro":false, "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["*commander", "current", "-1"]}], "recurring":"oncePerPlayer", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"Defeat (Lost Thief)", "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "isIntro":false, "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["thief", "current", "-1"]}], "recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_defeat_hq", "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "isIntro":false, "conditions":[{"id":"unit_lost", "enabled":true, "parameters":["hq", "current", "-1"]}], "recurring":"oncePerPlayer", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_victory", "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "isIntro":false, "conditions":[{"id":"number_of_opponents", "enabled":true, "parameters":["current", "0", "0"]}], "recurring":"oncePerPlayer", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"AI Aggressive", "actions":[{"id":"ai_set_profile", "enabled":true, "parameters":["P2", "aggressive"]}], "isIntro":false, "conditions":{}, "recurring":"start_of_match", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"TP", "actions":[{"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "P1", "5", "5", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "P2", "6", "6", "1"]}], "isIntro":false, "conditions":{}, "recurring":"start_of_match", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"P1 Victory (253021)", "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253021"]}], "isIntro":false, "conditions":[{"id":"player_victorious", "enabled":true, "parameters":["P1"]}], "recurring":"once", "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"Harpy Crit (253022)", "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253022"]}], "isIntro":false, "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["P1", "0", "1", "harpy", "-10"]}], "recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"Dragon Crit (253023)", "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253023"]}], "isIntro":false, "conditions":[{"id":"unit_presence", "enabled":true, "parameters":["P1", "0", "1", "dragon", "-10"]}], "recurring":"once", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"Story", "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["neutral", "janjak", "I'm almost to my contact, but there's riflemen behind those walls.", "1", "Code Names Thief"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["rifleman", "7", "P1", "0", "0", "1", "1", "undefined", "centre"]}, {"id":"dialogue_box_simple", "enabled":true, "parameters":["happy", "hans", "Sounds like a challenge to me.", "1", "A Sniper"]}, {"id":"dialogue_box_simple", "enabled":true, "parameters":["neutral", "janjak", "Heh, I could use all the help I can get. Say, aren't you the self insert character?", "1", "Code Names Thief"]}, {"id":"dialogue_box_simple", "enabled":true, "parameters":["happy", "hans", "Nonsense! Fly Sniper doesn't even play as snipers in those video games! I have much better aim than him!", "1", "Flea Sniper"]}], "isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252022", "0", "1"]}], "recurring":"start_of_match", "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}], "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_7_2":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":2, "x":7, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":400, "moveRange":5, "inWater":false, "critConditionId":"", "tags":["mage", "type.ground.light", "spellcaster"], "canBeActivated":false, "verbCostMultiplier":0.5, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":[{"canMoveAndAttack":true, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "id":"lightning", "terrainExclusion":{}, "minRange":1, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":1, "canAttackSubmerged":false, "canAttackAir":true}], "isStructure":false, "resourceCost":2, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"mage", "isCommander":false, "movementType":"walking", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":["lightning"], "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"mage", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":16, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":2, "x":7, "facing":0}, "playerId":1, "garrisonClassId":""}, "terrain":"plains"}, "Map_Tile_4_10":{"terrain":"road"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"mountain"}, "Flags":{}, "Map_Tile_3_14":{"terrain":"wall"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"road"}, "Map_Tile_6_17":{"terrain":"road"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Player_1":{"recruit_harpy":true, "recruit_witch":true, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_warship":true, "recruit_spearman":true, "recruit_dog":true, "recruit_rifleman":true, "recruit_mage":true, "recruit_archer":true, "recruit_dragon":true, "recruit_thief":true, "recruit_griffin_walking":true, "recruit_kraken":true, "recruit_ballista":true, "recruit_trebuchet":true, "recruit_turtle":true, "recruit_caravel":true, "recruit_travelboat":true, "team":0, "recruit_soldier":true, "recruit_frog":true, "recruit_merman":true, "recruit_knight":true, "recruit_wagon":true, "gold":650, "recruit_giant":true}, "Map_Tile_11_6":{"terrain":"road"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"plains"}, "Map_Tile_9_12":{"terrain":"road"}, "Map_Tile_2_19":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":19, "x":2, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":18, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":19, "x":2, "facing":0}, "playerId":0, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_13_11":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":11, "x":13, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":12, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":11, "x":13, "facing":0}, "playerId":-1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_5_17":{"terrain":"plains"}, "Player_2":{"recruit_harpy":true, "recruit_witch":true, "recruit_harpoonship":true, "recruit_balloon":true, "recruit_warship":true, "recruit_spearman":true, "recruit_dog":true, "recruit_rifleman":true, "recruit_mage":false, "recruit_archer":true, "recruit_dragon":true, "recruit_thief":true, "recruit_griffin_walking":true, "recruit_kraken":true, "recruit_ballista":false, "recruit_trebuchet":false, "recruit_turtle":true, "recruit_caravel":true, "recruit_travelboat":true, "team":1, "recruit_soldier":true, "recruit_frog":true, "recruit_merman":true, "recruit_knight":false, "recruit_wagon":false, "gold":0, "recruit_giant":false}, "Map_Tile_10_12":{"terrain":"road"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"forest"}, "Map_Tile_9_4":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"mountain"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"road"}, "Map_Tile_2_16":{"terrain":"wall"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_3_19":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"forest"}, "Map_Tile_0_12":{"terrain":"plains"}, "Map_Tile_3_18":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_8_18":{"terrain":"plains"}, "Map_Tile_1_15":{"terrain":"wall"}, "Map_Tile_2_9":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":9, "x":2, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":7, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":9, "x":2, "facing":0}, "playerId":1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_9_17":{"terrain":"road"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"forest"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_5_12":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":12, "x":5, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":11, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":12, "x":5, "facing":0}, "playerId":-1, "garrisonClassId":"garrison"}, "terrain":"road"}, "Map_Tile_5_8":{"terrain":"forest"}, "Map_Tile_7_13":{"terrain":"forest"}, "Map_Tile_4_18":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"plains"}, "Counters":{}, "Locations":{"0":{"id":0, "centre":{"y":15, "x":5}, "name":"Rando Forest 1", "positions":[{"y":14, "x":4}, {"y":15, "x":4}, {"y":16, "x":4}, {"y":16, "x":5}, {"y":15, "x":5}, {"y":14, "x":5}], "getArea":null, "setArea":null, "interactable":false}, "1":{"id":1, "centre":{"y":15, "x":11}, "name":"Rando Forest 2", "positions":[{"y":14, "x":11}, {"y":14, "x":10}, {"y":15, "x":10}, {"y":16, "x":10}, {"y":16, "x":11}, {"y":15, "x":11}], "getArea":null, "setArea":null, "interactable":false}, "2":{"id":2, "centre":{"y":12, "x":8}, "name":"Rando Forest 3", "positions":[{"y":11, "x":7}, {"y":11, "x":8}, {"y":12, "x":8}, {"y":12, "x":7}], "getArea":null, "setArea":null, "interactable":false}, "3":{"id":3, "centre":{"y":8, "x":8}, "name":"Rando Forest 4", "positions":[{"y":11, "x":3}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":11, "x":0}, {"y":12, "x":1}, {"y":12, "x":2}, {"y":12, "x":3}, {"y":12, "x":0}, {"y":9, "x":3}, {"y":8, "x":3}, {"y":8, "x":2}, {"y":8, "x":1}, {"y":9, "x":1}, {"y":9, "x":0}, {"y":8, "x":0}, {"y":7, "x":1}, {"y":6, "x":1}, {"y":6, "x":0}, {"y":6, "x":2}, {"y":7, "x":3}, {"y":6, "x":3}, {"y":7, "x":2}, {"y":7, "x":0}, {"y":11, "x":15}, {"y":12, "x":15}, {"y":12, "x":14}, {"y":12, "x":13}, {"y":12, "x":12}, {"y":11, "x":12}, {"y":11, "x":14}, {"y":11, "x":13}, {"y":9, "x":2}, {"y":9, "x":12}, {"y":9, "x":13}, {"y":9, "x":14}, {"y":9, "x":15}, {"y":8, "x":15}, {"y":8, "x":14}, {"y":8, "x":13}, {"y":8, "x":12}, {"y":7, "x":12}, {"y":7, "x":13}, {"y":7, "x":14}, {"y":7, "x":15}, {"y":6, "x":15}, {"y":6, "x":14}, {"y":6, "x":13}, {"y":6, "x":12}, {"y":6, "x":10}, {"y":7, "x":10}, {"y":7, "x":9}, {"y":7, "x":8}, {"y":7, "x":7}, {"y":7, "x":6}, {"y":7, "x":5}, {"y":6, "x":5}, {"y":6, "x":6}, {"y":6, "x":7}, {"y":6, "x":8}, {"y":6, "x":9}], "getArea":null, "setArea":null, "interactable":false}, "4":{"id":4, "centre":{"y":7, "x":8}, "name":"Single Tile", "positions":[{"y":7, "x":8}], "getArea":null, "setArea":null, "interactable":false}, "5":{"id":5, "centre":{"y":18, "x":8}, "name":"P1 TP", "positions":[{"y":19, "x":7}, {"y":19, "x":6}, {"y":18, "x":6}, {"y":18, "x":5}, {"y":19, "x":5}, {"y":19, "x":8}, {"y":19, "x":9}, {"y":18, "x":9}, {"y":18, "x":10}, {"y":17, "x":10}, {"y":19, "x":10}, {"y":17, "x":5}, {"y":17, "x":4}, {"y":18, "x":4}, {"y":19, "x":4}, {"y":17, "x":11}, {"y":18, "x":11}, {"y":19, "x":11}], "getArea":null, "setArea":null, "interactable":false}, "6":{"id":6, "centre":{"y":2, "x":8}, "name":"P2 TP", "positions":[{"y":2, "x":11}, {"y":2, "x":10}, {"y":2, "x":9}, {"y":2, "x":8}, {"y":2, "x":7}, {"y":2, "x":6}, {"y":2, "x":5}, {"y":2, "x":4}], "getArea":null, "setArea":null, "interactable":false}, "7":{"id":7, "centre":{"y":16, "x":7}, "name":"Rifleman Spawn", "positions":[{"y":16, "x":7}], "getArea":null, "setArea":null, "interactable":false}}, "Map_Tile_12_16":{"terrain":"wall"}, "Map_Tile_2_17":{"terrain":"plains"}, "Map_Tile_6_16":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_3_17":{"terrain":"plains"}, "Map_Tile_15_19":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"road"}, "Map_Tile_15_18":{"terrain":"plains"}, "Map_Tile_7_5":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":5, "x":7, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":3000, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"hq", "isCommander":false, "movementType":"land_building", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"hq", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":13, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":5, "x":7, "facing":0}, "playerId":1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_15_17":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"road"}, "Map_Tile_12_19":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"mountain"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_5_19":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"forest"}, "Map_Tile_14_19":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_1_16":{"terrain":"wall"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_14_14":{"terrain":"wall"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_9_16":{"terrain":"road"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_13_15":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":200, "canBeAttackedFromDistance":true, "startPos":{"y":15, "x":13, "facing":3}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":650, "moveRange":4, "inWater":false, "critConditionId":"", "tags":["rifleman", "type.ground.hideout"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":[{"canMoveAndAttack":false, "blockedByEnemies":true, "horizontalAndVerticalExtraWidth":1, "canCounterAttack":true, "horizontalAndVerticalOnly":true, "id":"musket", "terrainExclusion":["forest"], "minRange":1, "unitIdWhenAttacking":"", "directionality":"omni", "maxRange":9, "canAttackSubmerged":false, "canAttackAir":false}], "isStructure":false, "resourceCost":2, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"rifleman", "isCommander":false, "movementType":"walking", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":["musket"], "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"rifleman", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":2, "recruits":{}, "state":[{"key":"ammo", "value":"3"}], "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":15, "x":13, "facing":3}, "playerId":1, "garrisonClassId":""}, "terrain":"road"}, "Map_Tile_5_16":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"forest"}, "Map_Tile_7_15":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":15, "x":7, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"tower_ap", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"tower_ap", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":5, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":15, "x":7, "facing":0}, "playerId":-1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_13_18":{"terrain":"plains"}, "Map_Tile_13_16":{"terrain":"wall"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"mountain"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_7_9":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":9, "x":7, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":10, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":9, "x":7, "facing":0}, "playerId":1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_13_10":{"terrain":"forest"}, "Map_Tile_10_15":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_1_17":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"road"}, "Map_Tile_15_15":{"terrain":"mountain"}, "Map_Tile_1_14":{"terrain":"wall"}, "Map_Tile_3_16":{"terrain":"wall"}, "Map_Tile_6_19":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":19, "x":6, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"hideout", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"hideout", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":4, "recruits":["thief", "rifleman"], "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":19, "x":6, "facing":0}, "playerId":0, "garrisonClassId":"garrison"}, "terrain":"road"}, "Map_Tile_12_10":{"terrain":"road"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_13_19":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":19, "x":13, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":17, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":19, "x":13, "facing":0}, "playerId":0, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_9_18":{"terrain":"road"}, "Map_Tile_7_17":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"forest"}, "Map_Tile_12_18":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_4_15":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_5_18":{"terrain":"plains"}, "Map_Tile_14_16":{"terrain":"wall"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"road"}, "Map_Tile_11_17":{"terrain":"plains"}, "Map_Tile_14_15":{"terrain":"wall"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_4_5":{"terrain":"road"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_15_14":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_5_13":{"terrain":"forest"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"mountain"}, "Map_Tile_0_18":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"road"}, "Map_Tile_3_10":{"terrain":"road"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_9_19":{"terrain":"road"}, "Map_Tile_4_13":{"terrain":"mountain"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"road"}, "Map_Tile_11_19":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"road"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"road"}, "Map_Tile_4_9":{"terrain":"road"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_10_19":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":19, "x":10, "facing":3}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":false, "cost":400, "moveRange":6, "inWater":false, "critConditionId":"", "tags":["thief", "type.ground.hideout"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":false, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"thief", "isCommander":false, "movementType":"walking", "canBeCaptured":false, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"thief", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":3, "recruits":{}, "state":[{"key":"gold", "value":"0"}], "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":19, "x":10, "facing":3}, "playerId":0, "garrisonClassId":""}, "terrain":"plains"}, "Map_Tile_10_18":{"terrain":"plains"}, "Map_Tile_8_17":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"plains"}, "Map_Tile_0_15":{"terrain":"mountain"}, "Map_Tile_6_15":{"terrain":"road"}, "Map_Tile_6_9":{"terrain":"mountain"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"road"}, "Map_Tile_9_8":{"terrain":"mountain"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"forest"}, "Map_Tile_9_0":{"terrain":"forest"}, "Map_Tile_4_17":{"terrain":"plains"}, "Map_Tile_11_2":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":2, "x":11, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"barracks_ap", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"barracks_ap", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":15, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":2, "x":11, "facing":0}, "playerId":1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_6_7":{"terrain":"forest"}, "Map_Tile_15_16":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"road"}, "Map_Tile_8_11":{"terrain":"forest"}, "Map_Tile_4_3":{"terrain":"road"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_8_9":{"unit":{"hasBeenKilled":false, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "canChargeGroove":true, "factionOverride":"", "stunned":false, "inTransport":false, "killedByLosing":false, "blessings":{}, "merchantDiscounts":{}, "itemDropNumber":0, "rangedDamageTakenPercent":100, "health":100, "damageTakenPercent":100, "canBeAttackedFromDistance":true, "startPos":{"y":9, "x":8, "facing":0}, "transportedBy":-1, "hadTurn":false, "setGroove":null, "itemId":"", "items":{}, "unitClass":{"reinforceMultiplier":1.0, "canReinforce":true, "cost":500, "moveRange":0, "inWater":false, "critConditionId":"", "tags":["structure"], "canBeActivated":false, "verbCostMultiplier":1.0, "isDamagingParentUnit":false, "isAttackable":true, "transportTags":{}, "weapons":{}, "isStructure":true, "resourceCost":1, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "loadCapacity":0, "canAttack":true, "inAir":false, "isRecruitable":true, "id":"city", "isCommander":false, "movementType":"land_building", "canBeCaptured":true, "maxHealth":100, "aliasId":"", "weaponIds":{}, "maxGroove":0}, "attackerUnitClass":"", "canBeAttacked":true, "attachedFlagId":-1, "tentacled":false, "loadedUnits":{}, "unitClassId":"city", "attackerId":-1, "underwater":false, "setHealth":null, "attackerPlayerId":-1, "grooveCharge":0, "recruitDiscounts":{}, "id":9, "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "grooveId":"", "pos":{"y":9, "x":8, "facing":0}, "playerId":1, "garrisonClassId":"garrison"}, "terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_7_19":{"terrain":"plains"}, "Map_Tile_1_9":{"terrain":"mountain"}, "Map_Tile_4_16":{"terrain":"plains"}, "Map_Tile_1_19":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"wall"}, "Map_Tile_10_13":{"terrain":"plains"}, "Map_Tile_6_18":{"terrain":"road"}, "Map_Tile_6_14":{"terrain":"road"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"forest"}, "Map_Tile_8_8":{"terrain":"mountain"}, "Map_Tile_4_4":{"terrain":"road"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"forest"}, "Map_Tile_3_8":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Skydiving.json b/worlds/wargroove2/levels/Skydiving.json new file mode 100644 index 000000000000..6507e33120a6 --- /dev/null +++ b/worlds/wargroove2/levels/Skydiving.json @@ -0,0 +1 @@ +{"Map_Tile_0_12":{"terrain":"beach"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"forest"}, "Map_Tile_3_0":{"terrain":"forest"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_15_17":{"terrain":"plains"}, "Map_Tile_6_16":{"terrain":"road"}, "Map_Tile_9_9":{"terrain":"forest"}, "Map_Tile_11_19":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_19_18":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"plains"}, "Map_Tile_1_17":{"terrain":"road"}, "Map_Tile_0_16":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"road"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"road"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_14_19":{"terrain":"plains"}, "Map_Tile_16_16":{"terrain":"road"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"road"}, "Map_Tile_15_12":{"terrain":"plains"}, "Map_Tile_16_19":{"terrain":"plains"}, "Map_Tile_9_17":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_18_12":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"forest"}, "Map_Tile_4_13":{"terrain":"plains"}, "Map_Tile_11_14":{"terrain":"plains"}, "Map_Tile_8_18":{"terrain":"mountain"}, "Map_Tile_10_18":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"cobblestone"}, "Map_Tile_1_14":{"terrain":"forest"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_18_19":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"forest"}, "Map_Tile_3_19":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"forest"}, "Map_Tile_4_7":{"terrain":"cobblestone"}, "Map_Tile_13_17":{"terrain":"plains"}, "Map_Tile_2_15":{"terrain":"forest"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"cobblestone"}, "Map_Tile_17_4":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_0_17":{"terrain":"plains"}, "Map_Tile_8_19":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"wall"}, "Map_Tile_15_14":{"terrain":"forest"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_17_16":{"terrain":"road"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_12_13":{"terrain":"cobblestone"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"wall"}, "Map_Tile_17_1":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"forest"}, "Map_Tile_10_0":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"forest"}, "Map_Tile_15_15":{"terrain":"bridge"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"road"}, "Map_Tile_11_12":{"terrain":"cobblestone"}, "Map_Tile_8_10":{"terrain":"forest"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"forest"}, "Map_Tile_11_8":{"terrain":"forest"}, "Map_Tile_8_4":{"terrain":"road"}, "Map_Tile_11_0":{"terrain":"forest"}, "Map_Tile_10_11":{"terrain":"road"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_17_0":{"terrain":"plains"}, "Map_Tile_5_17":{"terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_4_15":{"terrain":"plains"}, "Map_Tile_5_15":{"terrain":"plains"}, "Map_Tile_17_12":{"terrain":"road"}, "Map_Tile_10_9":{"terrain":"road"}, "Map_Tile_13_19":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"road"}, "Map_Tile_5_16":{"terrain":"road"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_1_19":{"terrain":"mountain"}, "Map_Tile_7_17":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"forest"}, "Map_Tile_14_9":{"terrain":"forest"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"forest"}, "Map_Tile_3_15":{"terrain":"plains"}, "Map_Tile_1_15":{"terrain":"plains"}, "Map_Tile_18_2":{"terrain":"plains"}, "Player_1":{"team":0, "recruit_travelboat":true, "recruit_dog":true, "recruit_harpy":true, "recruit_warship":true, "recruit_spearman":true, "recruit_wagon":true, "recruit_rifleman":true, "recruit_trebuchet":true, "recruit_dragon":true, "recruit_harpoonship":true, "recruit_giant":true, "recruit_turtle":true, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":true, "recruit_witch":true, "recruit_knight":true, "recruit_thief":true, "gold":0, "recruit_merman":true, "recruit_kraken":true, "recruit_soldier":true, "recruit_frog":true, "recruit_ballista":true, "recruit_caravel":true, "recruit_mage":true}, "Map_Tile_4_18":{"terrain":"forest"}, "Map_Tile_2_19":{"terrain":"plains"}, "Flags":{}, "Map_Tile_8_17":{"terrain":"plains"}, "Triggers":[{"isIntro":false, "conditions":{}, "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"Export", "enabled":true, "actions":[{"id":"ap_export", "enabled":true, "parameters":["643", "Skydiving", "Fly Sniper", "Kill a Stronghold with your Dragon (Requires Dragon, Balloon and Airstrike Event)", "", "", "First player to kill 5 Strongholds Wins (Requires Balloon and Airstrike Event)!"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"unit_killed", "enabled":true, "parameters":["*unit_structure", "any", "hq", "current", "-1"]}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"An AI HQ Dies (Witch Text)", "enabled":true, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["happy", "oaracle", "Ha! Ha! Ha! The air is foul with my corrosive spell, no balloon will survive for long!", "1", "Unknown Mage"]}], "recurring":"once"}, {"isIntro":false, "conditions":[{"id":"unit_killed", "enabled":true, "parameters":["*unit_structure", "any", "hq", "current", "-1"]}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"An AI HQ Dies", "enabled":true, "actions":[{"id":"modify_counter", "enabled":true, "parameters":["1", "1", "1"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["griffin_walking", "0", "current", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["mage", "0", "current", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"modify_health", "enabled":true, "parameters":["balloon", "-1", "P1", "2", "50"]}], "recurring":"repeat"}, {"isIntro":false, "conditions":[{"id":"unit_killed", "enabled":true, "parameters":["*unit_structure", "any", "hq", "current", "-1"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"A Player HQ Dies", "enabled":true, "actions":[{"id":"modify_counter", "enabled":true, "parameters":["2", "1", "1"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["dog", "0", "current", "0", "1", "1", "1", "undefined", "centre"]}], "recurring":"repeat"}, {"isIntro":false, "conditions":[{"id":"check_map_counter", "enabled":true, "parameters":["1", "0", "4"]}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"Player, 1 Stronghold left", "enabled":true, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["extra", "generic_archer", "Player 1 has 1 stronghold kill remaining.", "1", "Unknown Guy"]}], "recurring":"once"}, {"isIntro":false, "conditions":[{"id":"check_map_counter", "enabled":true, "parameters":["2", "0", "4"]}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"AI, 1 Stronghold left", "enabled":true, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["extra", "generic_archer", "The enemy has 1 stronghold kill remaining.", "1", "Unknown Guy"]}], "recurring":"once"}, {"isIntro":false, "conditions":[{"id":"check_map_counter", "enabled":true, "parameters":["2", "4", "5"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Enemy defeated 5 Strongholds (Defeat)", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"once"}, {"isIntro":false, "conditions":[{"id":"check_map_counter", "enabled":true, "parameters":["1", "4", "5"]}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"Player defeated 5 Strongholds (Victory)", "enabled":true, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"once"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252026", "0", "0"]}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"Check for Air Strike Event", "enabled":true, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["neutral", "rhomb", "Pistil! We don't have any reason to fight the Florans! Leave immediately! (Requires Airstrike Event to sneak past Rhomb)", "1", "Rhomb"]}, {"id":"eliminate", "enabled":true, "parameters":["P1"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "0", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Player doesn't have balloons", "enabled":true, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["neutral", "pistil", "Where are those balloons?! Is Lytra using them?! This mission can't be done without them!", "1", "Pistil"]}, {"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"number_of_opponents", "enabled":true, "parameters":["current", "0", "0"]}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"$trigger_default_victory", "enabled":true, "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer"}, {"isIntro":false, "conditions":[{"id":"player_victorious", "enabled":true, "parameters":["P1"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"P1 Victory (253013)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253013"]}], "recurring":"once"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Knights", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["knight", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["knight", "-9", "P1"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Mages", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["mage", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["mage", "-9", "P1"]}, {"id":"wait", "enabled":true, "parameters":["0"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Archers", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["archer", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["archer", "-9", "P1"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Dogs", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["dog", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["dog", "-9", "P1"]}, {"id":"wait", "enabled":true, "parameters":["0"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Soldiers", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"wait", "enabled":true, "parameters":["0"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Commander", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["*commander", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"wait", "enabled":true, "parameters":["0"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252012", "1", "0"]}, {"id":"player_turn", "enabled":true, "parameters":["current"]}, {"id":"end_of_turn", "enabled":true, "parameters":{}}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Balloon Soldiers Every Turn", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["balloon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"spawn_unit_inside", "enabled":true, "parameters":["soldier", "-9", "P1"]}, {"id":"wait", "enabled":true, "parameters":["0"]}], "recurring":"repeat"}, {"isIntro":false, "conditions":[{"id":"player_turn", "enabled":true, "parameters":["current"]}, {"id":"end_of_turn", "enabled":true, "parameters":{}}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"AI Spawn Soldiers Every Turn", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["soldier", "0", "P2", "0", "1", "4", "1", "undefined", "centre"]}], "recurring":"repeat"}, {"isIntro":false, "conditions":[{"id":"player_turn", "enabled":true, "parameters":["current"]}, {"id":"end_of_turn", "enabled":true, "parameters":{}}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "id":"AI Initial Spawn", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["mage", "0", "current", "0", "1", "3", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["spearman", "0", "current", "0", "1", "2", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["soldier", "0", "current", "0", "1", "4", "1", "undefined", "centre"]}, {"id":"ap_spawn_unit", "enabled":true, "parameters":["knight", "0", "current", "0", "1", "1", "1", "undefined", "centre"]}], "recurring":"once"}, {"isIntro":false, "conditions":{}, "players":[1, 1, 0, 0, 0, 0, 0, 0], "id":"Spawn HQs", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["hq", "0", "current", "0", "1", "10", "1", "undefined", "centre"]}], "recurring":"oncePerPlayer"}, {"isIntro":false, "conditions":[{"id":"ap_has_item", "enabled":true, "parameters":["252011", "0", "2"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Spawn Dragon", "enabled":true, "actions":[{"id":"ap_spawn_unit", "enabled":true, "parameters":["dragon", "0", "P1", "0", "1", "1", "1", "undefined", "centre"]}], "recurring":"start_of_match"}, {"isIntro":false, "conditions":[{"id":"unit_killed", "enabled":true, "parameters":["dragon", "P1", "hq", "P2", "-1"]}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "id":"Defeat a Stronghold with your Dragon (253014)", "enabled":true, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253014"]}], "recurring":"once"}], "Locations":{"1":{"interactable":false, "positions":{}, "getArea":null, "id":1, "name":"Player Spawn", "centre":{"x":0, "y":0}, "setArea":null}, "0":{"interactable":false, "positions":[{"x":2, "y":2}, {"x":3, "y":2}, {"x":4, "y":2}, {"x":5, "y":2}, {"x":6, "y":2}, {"x":7, "y":2}, {"x":8, "y":2}, {"x":9, "y":2}, {"x":10, "y":2}, {"x":11, "y":2}, {"x":12, "y":2}, {"x":13, "y":2}, {"x":14, "y":2}, {"x":15, "y":2}, {"x":16, "y":2}, {"x":17, "y":2}, {"x":17, "y":3}, {"x":17, "y":4}, {"x":17, "y":5}, {"x":17, "y":6}, {"x":17, "y":7}, {"x":17, "y":8}, {"x":17, "y":9}, {"x":17, "y":10}, {"x":17, "y":11}, {"x":17, "y":12}, {"x":17, "y":13}, {"x":17, "y":14}, {"x":17, "y":15}, {"x":17, "y":16}, {"x":17, "y":17}, {"x":16, "y":17}, {"x":15, "y":17}, {"x":14, "y":17}, {"x":13, "y":17}, {"x":12, "y":17}, {"x":11, "y":17}, {"x":10, "y":17}, {"x":9, "y":17}, {"x":8, "y":17}, {"x":7, "y":17}, {"x":6, "y":17}, {"x":5, "y":17}, {"x":4, "y":17}, {"x":3, "y":17}, {"x":2, "y":17}, {"x":2, "y":16}, {"x":2, "y":15}, {"x":2, "y":14}, {"x":2, "y":13}, {"x":2, "y":12}, {"x":2, "y":11}, {"x":2, "y":10}, {"x":2, "y":9}, {"x":2, "y":8}, {"x":2, "y":7}, {"x":2, "y":6}, {"x":2, "y":5}, {"x":2, "y":4}, {"x":2, "y":3}, {"x":3, "y":3}, {"x":4, "y":3}, {"x":5, "y":3}, {"x":6, "y":3}, {"x":7, "y":3}, {"x":8, "y":3}, {"x":9, "y":3}, {"x":10, "y":3}, {"x":11, "y":3}, {"x":12, "y":3}, {"x":13, "y":3}, {"x":14, "y":3}, {"x":15, "y":3}, {"x":16, "y":3}, {"x":16, "y":4}, {"x":16, "y":5}, {"x":16, "y":6}, {"x":16, "y":7}, {"x":16, "y":8}, {"x":16, "y":9}, {"x":16, "y":10}, {"x":16, "y":11}, {"x":16, "y":12}, {"x":16, "y":13}, {"x":16, "y":14}, {"x":16, "y":15}, {"x":16, "y":16}, {"x":15, "y":16}, {"x":14, "y":16}, {"x":13, "y":16}, {"x":12, "y":16}, {"x":11, "y":16}, {"x":10, "y":16}, {"x":9, "y":16}, {"x":8, "y":16}, {"x":7, "y":16}, {"x":6, "y":16}, {"x":5, "y":16}, {"x":4, "y":16}, {"x":3, "y":16}, {"x":3, "y":15}, {"x":3, "y":14}, {"x":3, "y":13}, {"x":3, "y":12}, {"x":3, "y":11}, {"x":3, "y":10}, {"x":3, "y":9}, {"x":3, "y":8}, {"x":3, "y":7}, {"x":3, "y":6}, {"x":3, "y":5}, {"x":3, "y":4}, {"x":4, "y":4}, {"x":5, "y":4}, {"x":6, "y":4}, {"x":7, "y":4}, {"x":8, "y":4}, {"x":9, "y":4}, {"x":10, "y":4}, {"x":11, "y":4}, {"x":12, "y":4}, {"x":13, "y":4}, {"x":14, "y":4}, {"x":15, "y":4}, {"x":15, "y":5}, {"x":15, "y":6}, {"x":15, "y":7}, {"x":15, "y":8}, {"x":15, "y":9}, {"x":15, "y":10}, {"x":15, "y":11}, {"x":15, "y":12}, {"x":15, "y":13}, {"x":15, "y":14}, {"x":15, "y":15}, {"x":14, "y":15}, {"x":13, "y":15}, {"x":12, "y":15}, {"x":11, "y":15}, {"x":10, "y":15}, {"x":9, "y":15}, {"x":8, "y":15}, {"x":7, "y":15}, {"x":6, "y":15}, {"x":5, "y":15}, {"x":4, "y":15}, {"x":4, "y":14}, {"x":4, "y":13}, {"x":4, "y":12}, {"x":4, "y":11}, {"x":4, "y":10}, {"x":4, "y":9}, {"x":4, "y":8}, {"x":4, "y":7}, {"x":4, "y":6}, {"x":4, "y":5}, {"x":5, "y":5}, {"x":6, "y":5}, {"x":7, "y":5}, {"x":8, "y":5}, {"x":9, "y":5}, {"x":10, "y":5}, {"x":11, "y":5}, {"x":12, "y":5}, {"x":13, "y":5}, {"x":14, "y":5}, {"x":14, "y":6}, {"x":14, "y":7}, {"x":14, "y":8}, {"x":14, "y":9}, {"x":14, "y":10}, {"x":14, "y":11}, {"x":14, "y":12}, {"x":14, "y":13}, {"x":14, "y":14}, {"x":13, "y":14}, {"x":12, "y":14}, {"x":11, "y":14}, {"x":10, "y":14}, {"x":9, "y":14}, {"x":8, "y":14}, {"x":7, "y":14}, {"x":6, "y":14}, {"x":5, "y":14}, {"x":5, "y":13}, {"x":5, "y":12}, {"x":5, "y":11}, {"x":5, "y":10}, {"x":5, "y":9}, {"x":5, "y":8}, {"x":5, "y":7}, {"x":5, "y":6}, {"x":6, "y":6}, {"x":7, "y":6}, {"x":8, "y":6}, {"x":9, "y":6}, {"x":10, "y":6}, {"x":11, "y":6}, {"x":12, "y":6}, {"x":13, "y":6}, {"x":13, "y":7}, {"x":13, "y":8}, {"x":13, "y":9}, {"x":13, "y":10}, {"x":13, "y":11}, {"x":13, "y":12}, {"x":13, "y":13}, {"x":12, "y":13}, {"x":11, "y":13}, {"x":10, "y":13}, {"x":9, "y":13}, {"x":8, "y":13}, {"x":7, "y":13}, {"x":6, "y":13}, {"x":6, "y":12}, {"x":6, "y":11}, {"x":6, "y":10}, {"x":6, "y":9}, {"x":6, "y":8}, {"x":6, "y":7}, {"x":9, "y":7}, {"x":10, "y":7}, {"x":11, "y":7}, {"x":12, "y":7}, {"x":12, "y":8}, {"x":12, "y":9}, {"x":12, "y":10}, {"x":12, "y":11}, {"x":12, "y":12}, {"x":11, "y":12}, {"x":10, "y":12}, {"x":9, "y":12}, {"x":8, "y":12}, {"x":7, "y":12}, {"x":10, "y":11}, {"x":11, "y":11}, {"x":9, "y":11}, {"x":8, "y":11}, {"x":7, "y":11}, {"x":7, "y":10}, {"x":8, "y":10}, {"x":9, "y":10}, {"x":10, "y":9}, {"x":11, "y":9}, {"x":11, "y":10}, {"x":10, "y":10}, {"x":10, "y":8}, {"x":11, "y":8}, {"x":9, "y":9}, {"x":8, "y":9}, {"x":8, "y":8}, {"x":8, "y":7}, {"x":7, "y":7}, {"x":7, "y":8}, {"x":7, "y":9}, {"x":9, "y":8}, {"x":1, "y":2}, {"x":1, "y":3}, {"x":1, "y":4}, {"x":1, "y":5}, {"x":1, "y":6}, {"x":1, "y":7}, {"x":1, "y":8}, {"x":1, "y":9}, {"x":1, "y":10}, {"x":1, "y":11}, {"x":1, "y":12}, {"x":1, "y":13}, {"x":1, "y":14}, {"x":1, "y":15}, {"x":1, "y":16}, {"x":1, "y":17}, {"x":2, "y":18}, {"x":3, "y":18}, {"x":4, "y":18}, {"x":5, "y":18}, {"x":6, "y":18}, {"x":7, "y":18}, {"x":8, "y":18}, {"x":9, "y":18}, {"x":10, "y":18}, {"x":11, "y":18}, {"x":12, "y":18}, {"x":13, "y":18}, {"x":14, "y":18}, {"x":15, "y":18}, {"x":16, "y":18}, {"x":18, "y":17}, {"x":18, "y":16}, {"x":18, "y":15}, {"x":18, "y":14}, {"x":18, "y":13}, {"x":18, "y":12}, {"x":18, "y":11}, {"x":18, "y":10}, {"x":18, "y":9}, {"x":18, "y":8}, {"x":18, "y":7}, {"x":18, "y":6}, {"x":18, "y":5}, {"x":18, "y":4}, {"x":18, "y":3}, {"x":17, "y":1}, {"x":16, "y":1}, {"x":15, "y":1}, {"x":14, "y":1}, {"x":13, "y":1}, {"x":12, "y":1}, {"x":11, "y":1}, {"x":10, "y":1}, {"x":9, "y":1}, {"x":8, "y":1}, {"x":7, "y":1}, {"x":6, "y":1}, {"x":5, "y":1}, {"x":4, "y":1}, {"x":3, "y":1}, {"x":2, "y":1}, {"x":18, "y":2}, {"x":17, "y":18}], "getArea":null, "id":0, "name":"HQ Spawn", "centre":{"x":9, "y":9}, "setArea":null}}, "Map_Tile_19_19":{"terrain":"mountain"}, "Map_Tile_0_11":{"terrain":"beach"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_14_18":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_19_14":{"terrain":"plains"}, "Map_Tile_12_12":{"terrain":"cobblestone"}, "Map_Tile_8_14":{"terrain":"plains"}, "Map_Tile_9_13":{"terrain":"plains"}, "Map_Tile_7_12":{"terrain":"abyss"}, "Map_Tile_10_17":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"road"}, "Map_Tile_19_13":{"terrain":"river"}, "Map_Tile_19_12":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_11_16":{"terrain":"plains"}, "Map_Tile_7_15":{"terrain":"plains"}, "Map_Tile_19_11":{"terrain":"plains"}, "Map_Tile_18_15":{"terrain":"plains"}, "Map_Tile_18_4":{"terrain":"plains"}, "Map_Tile_1_12":{"terrain":"beach"}, "Map_Tile_19_7":{"terrain":"plains"}, "Map_Tile_17_15":{"terrain":"road"}, "Map_Name":"Skydiving", "Map_Tile_19_6":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_6_14":{"terrain":"road"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_19_2":{"terrain":"plains"}, "Map_Tile_19_1":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"forest"}, "Map_Tile_5_12":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_18_18":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"mountain"}, "Map_Tile_17_18":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_18_17":{"terrain":"plains"}, "Map_Tile_18_16":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"beach"}, "Map_Tile_18_14":{"terrain":"plains"}, "Map_Tile_18_13":{"terrain":"river"}, "Map_Tile_18_11":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"forest"}, "Map_Tile_18_7":{"terrain":"plains"}, "Map_Tile_9_18":{"terrain":"plains"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_18_5":{"terrain":"plains"}, "Map_Tile_4_2":{"terrain":"mountain"}, "Map_Tile_15_2":{"terrain":"plains"}, "Map_Tile_19_9":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"plains"}, "Map_Tile_18_1":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"road"}, "Map_Tile_18_0":{"terrain":"plains"}, "Map_Tile_17_19":{"terrain":"plains"}, "Map_Tile_17_17":{"terrain":"plains"}, "Map_Tile_17_14":{"terrain":"road"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"bridge"}, "Map_Tile_0_15":{"terrain":"plains"}, "Map_Tile_17_11":{"terrain":"road"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_17_10":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"forest"}, "Map_Tile_17_9":{"terrain":"plains"}, "Map_Tile_11_17":{"terrain":"forest"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Tile_17_6":{"terrain":"mountain"}, "Map_Tile_17_5":{"terrain":"mountain"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_7_18":{"terrain":"plains"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_17_3":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"forest"}, "Map_Tile_3_17":{"terrain":"plains"}, "Map_Tile_2_17":{"terrain":"forest"}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"cobblestone"}, "Map_Tile_16_18":{"terrain":"plains"}, "Map_Tile_1_18":{"terrain":"mountain"}, "Map_Tile_16_17":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"road"}, "Map_Tile_16_15":{"terrain":"river"}, "Map_Tile_1_7":{"terrain":"forest"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_16_14":{"terrain":"river"}, "Map_Tile_16_13":{"terrain":"river"}, "Map_Tile_16_6":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Size":{"x":20, "y":20}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_10_16":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"road"}, "Map_Tile_2_10":{"terrain":"forest"}, "Map_Tile_10_5":{"terrain":"road"}, "Map_Tile_8_12":{"terrain":"abyss"}, "Map_Tile_10_12":{"terrain":"road"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_10_14":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"forest"}, "Map_Tile_0_19":{"terrain":"mountain"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_0_18":{"terrain":"mountain"}, "Map_Tile_5_18":{"terrain":"forest"}, "Map_Tile_15_19":{"terrain":"forest"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_15_18":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"road"}, "Map_Tile_15_16":{"terrain":"road"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_15_11":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains"}, "Map_Tile_19_16":{"terrain":"plains"}, "Map_Tile_5_13":{"terrain":"abyss"}, "Map_Tile_12_11":{"terrain":"cobblestone"}, "Map_Tile_13_16":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"road"}, "Map_Tile_15_4":{"terrain":"road"}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"road"}, "Objectives":["Kill a Stronghold with your Dragon (Requires Dragon, Balloon and Airstrike Event)", "First player to kill 5 Strongholds Wins (Requires Balloon and Airstrike Event)!"], "Map_Tile_6_12":{"terrain":"abyss_bridge"}, "Map_Tile_9_2":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"forest"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_14_13":{"terrain":"plains"}, "Map_Tile_14_16":{"terrain":"river"}, "Map_Tile_14_15":{"terrain":"river"}, "Map_Tile_14_14":{"terrain":"plains"}, "Map_Tile_14_17":{"terrain":"mountain"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_19_5":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_4_19":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"abyss"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_2_18":{"terrain":"plains"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_5_19":{"terrain":"plains"}, "Map_Tile_12_7":{"terrain":"wall"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_4_16":{"terrain":"forest_cut"}, "Map_Tile_19_0":{"terrain":"forest"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"forest"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_5_14":{"terrain":"forest"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_19_15":{"terrain":"plains"}, "Map_Tile_2_14":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"plains"}, "Map_Tile_17_2":{"terrain":"forest"}, "Map_Tile_13_0":{"terrain":"plains"}, "Map_Tile_12_19":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"wall"}, "Map_Tile_12_18":{"terrain":"plains"}, "Map_Tile_12_17":{"terrain":"plains"}, "Map_Tile_6_18":{"terrain":"plains"}, "Map_Tile_7_19":{"terrain":"plains"}, "Map_Tile_12_16":{"terrain":"plains"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_6_17":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"forest"}, "Map_Tile_12_14":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"abyss"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_2_13":{"terrain":"plains"}, "Map_Tile_1_8":{"terrain":"forest"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_9_0":{"terrain":"forest"}, "Map_Tile_11_15":{"terrain":"plains"}, "Counters":{"1":0, "2":0, "0":0}, "Map_Tile_6_19":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Player_2":{"team":1, "recruit_travelboat":true, "recruit_dog":true, "recruit_harpy":true, "recruit_warship":true, "recruit_spearman":true, "recruit_wagon":true, "recruit_rifleman":true, "recruit_trebuchet":true, "recruit_dragon":true, "recruit_harpoonship":true, "recruit_giant":true, "recruit_turtle":true, "recruit_archer":true, "recruit_griffin_walking":true, "recruit_balloon":true, "recruit_witch":true, "recruit_knight":true, "recruit_thief":true, "gold":0, "recruit_merman":true, "recruit_kraken":true, "recruit_soldier":true, "recruit_frog":false, "recruit_ballista":false, "recruit_caravel":true, "recruit_mage":true}, "Map_Tile_5_8":{"terrain":"cobblestone"}, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_11_18":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"plains"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_19_17":{"terrain":"plains"}, "Map_Tile_11_13":{"terrain":"cobblestone"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_6_13":{"terrain":"abyss_bridge"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_7_16":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"beach"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_2_11":{"terrain":"beach"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_3_18":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"road"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_3_16":{"terrain":"road"}, "Author":"Fly Sniper", "Player_Count":2, "Map_Tile_10_15":{"terrain":"forest"}, "Map_Tile_10_13":{"terrain":"road"}, "Map_Tile_10_8":{"terrain":"road"}, "Map_Tile_2_16":{"terrain":"road"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_10_19":{"terrain":"plains"}, "Map_Tile_9_14":{"terrain":"plains"}, "Map_Tile_9_19":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"road"}, "Map_Tile_7_14":{"terrain":"plains"}, "Map_Tile_9_16":{"terrain":"plains"}, "Map_Tile_13_18":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_1_3":{"terrain":"forest"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_8_15":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"beach"}, "Map_Tile_2_12":{"terrain":"beach"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_1_16":{"terrain":"road"}, "Map_Tile_19_8":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_4_17":{"terrain":"plains"}, "Map_Tile_9_15":{"terrain":"plains"}, "Map_Tile_8_16":{"terrain":"plains"}, "Map_Tile_19_3":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Slippery_Bridge.json b/worlds/wargroove2/levels/Slippery_Bridge.json new file mode 100644 index 000000000000..daaebd8f09e2 --- /dev/null +++ b/worlds/wargroove2/levels/Slippery_Bridge.json @@ -0,0 +1 @@ +{"Map_Tile_1_3":{"terrain":"sea"}, "Map_Tile_2_1":{"terrain":"bridge"}, "Map_Tile_8_3":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"spear", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["spearman", "type.ground.light"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":3, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":250, "canReinforce":false, "maxGroove":0, "weaponIds":["spear"], "loadCapacity":0, "id":"spearman"}, "garrisonClassId":"", "pos":{"facing":3, "x":8, "y":3}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":3, "x":8, "y":3}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":14, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"spearman", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_0_5":{"terrain":"road"}, "Map_Tile_1_1":{"terrain":"road"}, "Map_Tile_3_11":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":2, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"lance", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["knight", "type.ground.heavy"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"riding", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":6, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":600, "canReinforce":false, "maxGroove":0, "weaponIds":["lance"], "loadCapacity":0, "id":"knight"}, "garrisonClassId":"", "pos":{"facing":0, "x":3, "y":11}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":3, "y":11}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":20, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"knight", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_1_6":{"terrain":"beach"}, "Locations":{"1":{"name":"Enemy Army Shuffle", "getArea":null, "id":1, "centre":{"y":2, "x":7}, "interactable":false, "setArea":null, "positions":[{"y":1, "x":6}, {"y":1, "x":7}, {"y":1, "x":8}, {"y":2, "x":8}, {"y":3, "x":8}]}, "2":{"name":"Allied Army Shuffle", "getArea":null, "id":2, "centre":{"y":9, "x":2}, "interactable":false, "setArea":null, "positions":[{"y":8, "x":1}, {"y":9, "x":1}, {"y":9, "x":2}, {"y":10, "x":2}, {"y":10, "x":3}, {"y":11, "x":3}, {"y":10, "x":1}, {"y":8, "x":2}, {"y":9, "x":3}, {"y":8, "x":0}]}, "3":{"name":"Enemy Structure Shuffle", "getArea":null, "id":3, "centre":{"y":1, "x":8}, "interactable":false, "setArea":null, "positions":[{"y":0, "x":7}, {"y":0, "x":9}, {"y":2, "x":7}]}, "4":{"name":"Sea Village Shuffle", "getArea":null, "id":4, "centre":{"y":4, "x":5}, "interactable":false, "setArea":null, "positions":[{"y":3, "x":3}, {"y":6, "x":7}, {"y":8, "x":6}, {"y":5, "x":7}, {"y":2, "x":3}, {"y":3, "x":5}, {"y":3, "x":2}]}, "0":{"name":"Random Giant Start", "getArea":null, "id":0, "centre":{"y":2, "x":8}, "interactable":false, "setArea":null, "positions":[{"y":0, "x":6}, {"y":3, "x":9}]}}, "Map_Tile_4_1":{"terrain":"bridge"}, "Map_Tile_0_0":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":0, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":0, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":8, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_6_9":{"terrain":"reef"}, "Map_Name":"Slippery Bridge", "Map_Tile_4_5":{"terrain":"sea", "unit":{"recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"river_sea_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"port"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":4, "y":5}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":4, "y":5}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":2, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"port", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_3_6":{"terrain":"sea"}, "Map_Tile_8_6":{"terrain":"bridge"}, "Objectives":["Rout the Enemy (Victory) (Requires Frog).", "Control all Sea Villages (Requires Merfolk)."], "Map_Tile_8_7":{"terrain":"bridge"}, "Map_Tile_1_5":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":1, "y":5}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":1, "y":5}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":7, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_2_4":{"terrain":"sea"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_5_2":{"terrain":"sea"}, "Map_Tile_2_5":{"terrain":"sea"}, "Map_Tile_0_3":{"terrain":"road"}, "Map_Tile_9_5":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":9, "y":5}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":-1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":9, "y":5}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":22, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_8_9":{"terrain":"forest_cut"}, "Map_Tile_3_7":{"terrain":"sea"}, "Map_Tile_6_10":{"terrain":"bridge"}, "Map_Tile_4_0":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":4, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":-1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":4, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":17, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_7_11":{"terrain":"sea"}, "Map_Tile_9_2":{"terrain":"plains", "unit":{"recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"barracks"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":9, "y":2}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":9, "y":2}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":4, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"barracks", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_7_8":{"terrain":"sea"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"sea"}, "Map_Tile_2_7":{"terrain":"sea"}, "Map_Tile_7_6":{"terrain":"sea", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"sea_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"water_city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":7, "y":6}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":-1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":7, "y":6}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":6, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"water_city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_5_7":{"terrain":"sea"}, "Map_Tile_6_3":{"terrain":"reef"}, "Player_2":{"recruit_knight":true, "recruit_griffin_walking":false, "team":1, "recruit_trebuchet":false, "recruit_warship":false, "recruit_dog":false, "recruit_giant":true, "recruit_rifleman":false, "recruit_frog":false, "recruit_dragon":false, "recruit_harpoonship":false, "recruit_turtle":false, "recruit_travelboat":false, "recruit_witch":false, "recruit_merman":false, "recruit_balloon":false, "gold":100, "recruit_thief":false, "recruit_spearman":true, "recruit_soldier":false, "recruit_wagon":false, "recruit_mage":false, "recruit_kraken":false, "recruit_archer":true, "recruit_caravel":false, "recruit_harpy":false, "recruit_ballista":false}, "Map_Tile_1_11":{"terrain":"road"}, "Map_Tile_0_11":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":0, "y":11}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":0, "y":11}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":23, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_0_9":{"terrain":"road"}, "Map_Tile_1_2":{"terrain":"beach"}, "Map_Tile_4_9":{"terrain":"sea"}, "Map_Tile_8_5":{"terrain":"bridge"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"road"}, "Map_Tile_5_6":{"terrain":"beach"}, "Map_Tile_7_0":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":7, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":7, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":5, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_2_2":{"terrain":"sea"}, "Author":"Magnemania", "Map_Tile_5_4":{"terrain":"sea"}, "Map_Tile_2_0":{"terrain":"sea"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"road", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":3, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"trebuchetSling", "canMoveAndAttack":false, "minRange":3, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["trebuchet", "type.ground.heavy"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"wheels", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":5, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":1100, "canReinforce":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "loadCapacity":0, "id":"trebuchet"}, "garrisonClassId":"", "pos":{"facing":3, "x":8, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":3, "x":8, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":18, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"trebuchet", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_3_1":{"terrain":"bridge"}, "Map_Tile_5_9":{"terrain":"reef"}, "Map_Tile_6_0":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":3, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"giantSlam", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":2.5, "tags":["giant", "type.ground.heavy", "tall"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"riding", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":5, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":1200, "canReinforce":false, "maxGroove":0, "weaponIds":["giantSlam"], "loadCapacity":0, "id":"giant"}, "garrisonClassId":"", "pos":{"facing":3, "x":6, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":3, "x":6, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":21, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"giant", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Player_Count":2, "Map_Tile_6_4":{"terrain":"sea"}, "Map_Tile_6_6":{"terrain":"sea"}, "Map_Tile_3_0":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":3, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":3, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":16, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"beach"}, "Map_Tile_4_11":{"terrain":"forest"}, "Map_Tile_5_10":{"terrain":"bridge"}, "Map_Tile_9_0":{"terrain":"road", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":9, "y":0}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":9, "y":0}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":24, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_5_8":{"terrain":"sea"}, "Map_Tile_5_1":{"terrain":"bridge"}, "Map_Tile_0_6":{"terrain":"road"}, "Map_Tile_1_8":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"sword", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["soldier", "type.ground.light"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":4, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":100, "canReinforce":false, "maxGroove":0, "weaponIds":["sword"], "loadCapacity":0, "id":"soldier"}, "garrisonClassId":"", "pos":{"facing":0, "x":1, "y":8}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":1, "y":8}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":11, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"soldier", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_0_4":{"terrain":"road"}, "Map_Tile_6_2":{"terrain":"sea"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"sea", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"sea_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"water_city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":3, "y":3}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":-1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":3, "y":3}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":10, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"water_city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_6_5":{"terrain":"sea"}, "Map_Tile_4_8":{"terrain":"sea"}, "Map_Tile_2_11":{"terrain":"forest"}, "Map_Tile_6_7":{"terrain":"sea"}, "Map_Tile_0_2":{"terrain":"road"}, "Flags":{}, "Counters":{}, "Triggers":[{"conditions":{}, "isIntro":false, "id":"Export (Always on Top)", "actions":[{"parameters":["1"], "enabled":true, "id":"ap_export"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true}, {"conditions":{}, "isIntro":false, "id":"Set AI", "actions":[{"parameters":["current", "aggressive"], "enabled":true, "id":"ai_set_profile"}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true}, {"conditions":{}, "isIntro":false, "id":"Shuffle Units", "actions":[{"parameters":["*unit_structure", "current", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "current", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "current", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true}, {"conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "isIntro":false, "id":"$trigger_default_defeat_no_units", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"conditions":[{"parameters":["current", "0", "0", "*unit", "-1"], "enabled":true, "id":"unit_presence"}], "isIntro":false, "id":"Defeat (No Units)", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[0, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "isIntro":false, "id":"$trigger_default_defeat_commander", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "isIntro":false, "id":"$trigger_default_defeat_commander", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "isIntro":false, "id":"$trigger_default_defeat_hq", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "isIntro":false, "id":"$trigger_default_victory", "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"conditions":[{"parameters":["current", "4", "3", "water_city", "-1"], "enabled":true, "id":"unit_presence"}], "isIntro":false, "id":"Capture the Water Villages (Check 253301)", "actions":[{"parameters":["253301"], "enabled":true, "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true}, {"conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "isIntro":false, "id":"P1 Wins (Check 253300)", "actions":[{"parameters":["253300"], "enabled":true, "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true}], "Map_Tile_9_7":{"terrain":"sea"}, "Map_Tile_8_2":{"terrain":"forest"}, "Map_Tile_4_3":{"terrain":"sea"}, "Map_Tile_9_11":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":9, "y":11}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":9, "y":11}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":9, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Player_1":{"recruit_knight":true, "recruit_griffin_walking":true, "team":0, "recruit_trebuchet":true, "recruit_warship":true, "recruit_dog":true, "recruit_giant":true, "recruit_rifleman":true, "recruit_frog":true, "recruit_dragon":true, "recruit_harpoonship":true, "recruit_turtle":true, "recruit_travelboat":true, "recruit_witch":true, "recruit_merman":true, "recruit_balloon":true, "gold":100, "recruit_thief":true, "recruit_spearman":true, "recruit_soldier":true, "recruit_wagon":true, "recruit_mage":true, "recruit_kraken":true, "recruit_archer":true, "recruit_caravel":true, "recruit_harpy":true, "recruit_ballista":true}, "Map_Tile_6_1":{"terrain":"road", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"spear", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["spearman", "type.ground.light"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":3, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":250, "canReinforce":false, "maxGroove":0, "weaponIds":["spear"], "loadCapacity":0, "id":"spearman"}, "garrisonClassId":"", "pos":{"facing":3, "x":6, "y":1}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":3, "x":6, "y":1}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":15, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"spearman", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_9_9":{"terrain":"forest"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"sea"}, "Map_Tile_7_10":{"terrain":"bridge"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"road"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_3_8":{"terrain":"beach"}, "Map_Tile_0_1":{"terrain":"road"}, "Map_Tile_4_7":{"terrain":"sea"}, "Map_Tile_1_4":{"terrain":"sea"}, "Map_Tile_9_8":{"terrain":"beach"}, "Map_Tile_2_3":{"terrain":"sea"}, "Map_Tile_3_4":{"terrain":"sea"}, "Map_Tile_1_9":{"terrain":"road", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":2, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"airtrooperBeak", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["airtrooper", "type.air"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"airphibious", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":5, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":400, "canReinforce":false, "maxGroove":0, "weaponIds":["airtrooperBeak"], "loadCapacity":0, "id":"griffin_walking"}, "garrisonClassId":"", "pos":{"facing":0, "x":1, "y":9}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":1, "y":9}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":19, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"griffin_walking", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_8_10":{"terrain":"forest_cut"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"sea"}, "Map_Tile_0_8":{"terrain":"road"}, "Map_Tile_6_11":{"terrain":"sea"}, "Map_Tile_7_1":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"bridge"}, "Map_Tile_2_6":{"terrain":"sea"}, "Map_Tile_2_9":{"terrain":"plains", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":3, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"merciaSword", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":false, "aliasId":"", "passiveMultiplier":1.0, "tags":["commander", "type.ground.light"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":true, "moveRange":4, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":false, "maxGroove":250, "weaponIds":["merciaSword"], "loadCapacity":0, "id":"commander_mercia"}, "garrisonClassId":"", "pos":{"facing":0, "x":2, "y":9}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"heal_aura", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":2, "y":9}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":1, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"commander_mercia", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_4_2":{"terrain":"ocean"}, "Map_Tile_7_5":{"terrain":"sea"}, "Map_Tile_9_1":{"terrain":"reef"}, "Map_Tile_3_10":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"beach"}, "Map_Tile_8_1":{"terrain":"road", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":2, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":true, "id":"lightning", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["mage", "type.ground.light", "spellcaster"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "isStructure":false, "verbCostMultiplier":0.5, "isCommander":false, "moveRange":5, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":400, "canReinforce":false, "maxGroove":0, "weaponIds":["lightning"], "loadCapacity":0, "id":"mage"}, "garrisonClassId":"", "pos":{"facing":3, "x":8, "y":1}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":3, "x":8, "y":1}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":13, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"mage", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"sea", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":true, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "weapons":{}, "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.0, "tags":["structure"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"sea_building", "isStructure":true, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":0, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":500, "canReinforce":true, "maxGroove":0, "weaponIds":{}, "loadCapacity":0, "id":"water_city"}, "garrisonClassId":"garrison", "pos":{"facing":0, "x":6, "y":8}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":-1, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":6, "y":8}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":3, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"water_city", "health":100, "stunned":false, "merchantDiscounts":{}}}, "Map_Tile_3_2":{"terrain":"ocean"}, "Map_Size":{"y":12, "x":10}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"forest", "unit":{"recruits":{}, "recruitDiscounts":{}, "grooveCharge":0, "unitClass":{"canBeCaptured":false, "recruitingCostMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":2, "weapons":[{"horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "blockedByEnemies":false, "terrainExclusion":{}, "unitIdWhenAttacking":"", "canAttackAir":false, "id":"airtrooperBeak", "canMoveAndAttack":true, "minRange":1, "canCounterAttack":true, "horizontalAndVerticalOnly":false, "canAttackSubmerged":false}], "isRecruitable":true, "aliasId":"", "passiveMultiplier":1.5, "tags":["airtrooper", "type.air"], "canAttack":true, "maxHealth":100, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"airphibious", "isStructure":false, "verbCostMultiplier":1.0, "isCommander":false, "moveRange":5, "inWater":false, "transportTags":{}, "inAir":false, "canBeActivated":false, "cost":400, "canReinforce":false, "maxGroove":0, "weaponIds":["airtrooperBeak"], "loadCapacity":0, "id":"griffin_walking"}, "garrisonClassId":"", "pos":{"facing":0, "x":2, "y":10}, "canBeAttacked":true, "recruitDiscountMultiplier":0.0, "state":{}, "playerId":0, "rangedDamageTakenPercent":100, "grooveId":"", "blessings":{}, "attackerPlayerId":-1, "itemId":"", "killedByLosing":false, "startPos":{"facing":0, "x":2, "y":10}, "underwater":false, "damageTakenPercent":100, "miniGrooveId":"", "attackerUnitClass":"", "id":12, "hasBeenKilled":false, "attachedFlagId":-1, "canChargeGroove":true, "setHealth":null, "itemDropNumber":0, "setGroove":null, "tentacled":false, "items":{}, "loadedUnits":{}, "factionOverride":"", "attackerId":-1, "canBeAttackedFromDistance":true, "transportedBy":-1, "hadTurn":false, "merchantDiscountMultiplier":0.0, "inTransport":false, "unitClassId":"griffin_walking", "health":100, "stunned":false, "merchantDiscounts":{}}}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Spire_Fire.json b/worlds/wargroove2/levels/Spire_Fire.json new file mode 100644 index 000000000000..466a35f51f0d --- /dev/null +++ b/worlds/wargroove2/levels/Spire_Fire.json @@ -0,0 +1 @@ +{"Map_Tile_0_5":{"terrain":"river"}, "Map_Tile_15_11":{"terrain":"abyss"}, "Map_Tile_14_10":{"terrain":"mountain"}, "Map_Tile_5_0":{"terrain":"river"}, "Author":"Magnemania", "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"sea"}, "Map_Tile_5_1":{"terrain":"river"}, "Map_Tile_10_2":{"terrain":"mountain"}, "Map_Tile_0_4":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":4, "x":0}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":4, "x":0}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":21}, "terrain":"plains"}, "Flags":{"0":0}, "Map_Tile_18_3":{"unit":{"inTransport":false, "startPos":{"facing":3, "y":3, "x":18}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":3, "y":3, "x":18}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"soldier", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["soldier", "type.ground.light"], "moveRange":4, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":[{"minRange":1, "id":"sword", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":100, "inWater":false, "id":"soldier", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["sword"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":40}, "terrain":"plains"}, "Map_Tile_19_5":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":5, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":5, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"water_city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"sea_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"water_city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":4}, "terrain":"sea"}, "Triggers":[{"conditions":{}, "isIntro":false, "enabled":true, "id":"Export (Always on Top)", "actions":[{"enabled":true, "parameters":["1"], "id":"ap_export"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match"}, {"conditions":[{"enabled":true, "parameters":["*unit_structure", "P1", "tower", "current", "-1"], "id":"unit_killed"}], "isIntro":false, "enabled":true, "id":"Tower Destruction", "actions":[{"enabled":true, "parameters":["tower", "-3", "any", "1", "0"], "id":"remove_units"}], "players":[0, 0, 1, 0, 0, 0, 0, 0], "recurring":"repeat"}, {"conditions":{}, "isIntro":false, "enabled":true, "id":"Set AI", "actions":[{"enabled":true, "parameters":["P2", "aggressive"], "id":"ai_set_profile"}, {"enabled":true, "parameters":["P3", "aggressive"], "id":"ai_set_profile"}, {"enabled":true, "parameters":["P3", "1"], "id":"change_team"}, {"enabled":true, "parameters":["P4", "1"], "id":"change_team"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once"}, {"conditions":[{"enabled":true, "parameters":["dragon", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "enabled":true, "id":"Player Loses Dragon", "actions":[{"enabled":true, "parameters":["0", "1"], "id":"set_map_flag"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once"}, {"conditions":{}, "isIntro":false, "enabled":true, "id":"Shuffle Units", "actions":[{"enabled":true, "parameters":["*unit_structure", "any", "4", "4", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "2", "2", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "1", "1", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "3", "3", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "0", "0", "1"], "id":"unit_random_teleport"}, {"enabled":true, "parameters":["*unit_structure", "any", "5", "5", "1"], "id":"unit_random_teleport"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"], "id":"unit_presence"}], "isIntro":false, "enabled":true, "id":"Victory (Destroyed All Towers)", "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 1, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer"}, {"conditions":[{"enabled":true, "parameters":["witch", "current", "witch", "any", "-1"], "id":"unit_killed"}], "isIntro":false, "enabled":true, "id":"Destroy Sky Rider with Sky Rider (Check 25306)", "actions":[{"enabled":true, "parameters":["253306"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0", "tower", "-1"], "id":"unit_presence"}], "isIntro":false, "enabled":true, "id":"Defeat (Lost All Towers)", "actions":[{"enabled":true, "parameters":["P1"], "id":"victory"}], "players":[0, 0, 1, 0, 0, 0, 0, 0], "recurring":"once"}, {"conditions":[{"enabled":true, "parameters":["*commander", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_commander", "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer"}, {"conditions":[{"enabled":true, "parameters":["hq", "current", "-1"], "id":"unit_lost"}], "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_hq", "actions":[{"enabled":true, "parameters":["current"], "id":"eliminate"}], "players":[1, 1, 1, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer"}, {"conditions":[{"enabled":true, "parameters":["current", "0", "0"], "id":"number_of_opponents"}], "isIntro":false, "enabled":true, "id":"$trigger_default_victory", "actions":[{"enabled":true, "parameters":["current"], "id":"victory"}], "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer"}, {"conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}, {"enabled":true, "parameters":["0", "0"], "id":"check_map_flag"}], "isIntro":false, "enabled":true, "id":"Dragon Survives (Check 253307)", "actions":[{"enabled":true, "parameters":["253307"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once"}, {"conditions":[{"enabled":true, "parameters":["current"], "id":"player_victorious"}], "isIntro":false, "enabled":true, "id":"P1 Wins (Check 253305)", "actions":[{"enabled":true, "parameters":["253305"], "id":"ap_location_send"}], "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once"}], "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"river"}, "Map_Tile_17_6":{"terrain":"beach"}, "Map_Tile_9_0":{"terrain":"forest"}, "Map_Tile_18_2":{"terrain":"road"}, "Map_Tile_7_10":{"terrain":"forest_cut"}, "Map_Tile_2_5":{"terrain":"bridge"}, "Map_Tile_19_11":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":11, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":11, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"water_city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"sea_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"water_city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":2}, "terrain":"sea"}, "Map_Tile_19_10":{"terrain":"sea"}, "Map_Tile_14_5":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":5, "x":14}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":5, "x":14}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":-1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":11}, "terrain":"plains"}, "Map_Tile_18_9":{"terrain":"sea"}, "Map_Tile_14_2":{"terrain":"road"}, "Map_Tile_9_10":{"terrain":"road"}, "Map_Tile_19_8":{"terrain":"ocean"}, "Map_Tile_7_2":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":2, "x":7}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":2, "x":7}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":-1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":25}, "terrain":"plains"}, "Map_Tile_14_4":{"terrain":"road"}, "Map_Tile_19_7":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":7, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":7, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"water_city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"sea_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"water_city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":3}, "terrain":"sea"}, "Map_Tile_2_2":{"terrain":"road"}, "Map_Tile_5_6":{"terrain":"river"}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_19_6":{"terrain":"sea"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_15_9":{"terrain":"mountain"}, "Map_Tile_17_1":{"terrain":"sea"}, "Counters":{}, "Map_Tile_19_4":{"terrain":"sea"}, "Map_Tile_14_3":{"terrain":"road"}, "Map_Tile_2_1":{"terrain":"road"}, "Map_Tile_19_3":{"unit":{"inTransport":false, "startPos":{"facing":3, "y":3, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":3, "y":3, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"soldier", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["soldier", "type.ground.light"], "moveRange":4, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":[{"minRange":1, "id":"sword", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":100, "inWater":false, "id":"soldier", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["sword"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":38}, "terrain":"plains"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_0_2":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":2, "x":0}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":2, "x":0}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"mage", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["mage", "type.ground.light", "spellcaster"], "moveRange":5, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":0.5, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":2, "weapons":[{"minRange":1, "id":"lightning", "canAttackAir":true, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":400, "inWater":false, "id":"mage", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["lightning"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":35}, "terrain":"plains"}, "Map_Tile_12_5":{"terrain":"plains"}, "Map_Tile_18_8":{"unit":{"inTransport":false, "startPos":{"facing":3, "y":8, "x":18}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":3, "y":8, "x":18}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"witch", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["witch", "type.air", "spellcaster"], "moveRange":7, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":0.5, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":true, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"flying", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":3, "weapons":[{"minRange":1, "id":"witchSpell", "canAttackAir":true, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":750, "inWater":false, "id":"witch", "passiveMultiplier":2.0, "recruitingCostMultiplier":1.0, "weaponIds":["witchSpell"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":14}, "terrain":"sea"}, "Map_Tile_19_2":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":2, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":2, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"tower", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"tower", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":33}, "terrain":"road"}, "Map_Tile_1_5":{"terrain":"river"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"forest_cut"}, "Map_Tile_19_1":{"unit":{"inTransport":false, "startPos":{"facing":3, "y":1, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":3, "y":1, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"knight", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["knight", "type.ground.heavy"], "moveRange":6, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"riding", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":2, "weapons":[{"minRange":1, "id":"lance", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":600, "inWater":false, "id":"knight", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["lance"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":24}, "terrain":"plains"}, "Map_Tile_8_9":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":9, "x":8}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":9, "x":8}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"barracks", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"barracks", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":6}, "terrain":"plains"}, "Map_Tile_7_8":{"terrain":"forest"}, "Map_Tile_13_9":{"terrain":"mountain"}, "Map_Tile_19_0":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":0, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":0, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":23}, "terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_18_11":{"terrain":"sea"}, "Map_Tile_7_11":{"terrain":"plains"}, "Player_3":{"recruit_turtle":false, "recruit_rifleman":false, "recruit_thief":false, "recruit_dragon":false, "recruit_harpy":true, "recruit_dog":false, "recruit_wagon":false, "recruit_merman":false, "recruit_ballista":false, "recruit_balloon":false, "gold":100, "recruit_giant":false, "recruit_warship":false, "recruit_soldier":false, "recruit_spearman":false, "team":1, "recruit_frog":false, "recruit_kraken":false, "recruit_trebuchet":false, "recruit_griffin_walking":false, "recruit_knight":false, "recruit_mage":false, "recruit_harpoonship":false, "recruit_witch":true, "recruit_caravel":false, "recruit_travelboat":false, "recruit_archer":false}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"sea"}, "Map_Tile_6_4":{"terrain":"road"}, "Map_Tile_8_7":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"river"}, "Map_Tile_4_5":{"terrain":"river"}, "Map_Tile_18_7":{"terrain":"sea"}, "Map_Tile_18_6":{"terrain":"sea"}, "Map_Tile_6_8":{"terrain":"forest"}, "Map_Tile_2_11":{"terrain":"forest"}, "Map_Tile_18_5":{"terrain":"sea"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_5_5":{"terrain":"river"}, "Map_Tile_13_3":{"terrain":"forest"}, "Locations":{"1":{"getArea":null, "positions":[{"x":3, "y":2}, {"x":3, "y":4}, {"x":18, "y":3}, {"x":16, "y":2}, {"x":4, "y":1}, {"x":17, "y":2}, {"x":2, "y":2}, {"x":0, "y":2}], "centre":{"x":8, "y":2}, "id":1, "interactable":false, "setArea":null, "name":"Enemy Army Shuffle"}, "2":{"getArea":null, "positions":[{"x":9, "y":9}, {"x":9, "y":8}, {"x":10, "y":8}, {"x":8, "y":8}], "centre":{"x":9, "y":8}, "id":2, "interactable":false, "setArea":null, "name":"Allied Army Shuffle"}, "3":{"getArea":null, "positions":{}, "centre":{"x":0, "y":0}, "id":3, "interactable":false, "setArea":null, "name":"Tower Shuffle"}, "4":{"getArea":null, "positions":[{"x":10, "y":5}, {"x":8, "y":5}, {"x":8, "y":3}, {"x":12, "y":5}, {"x":6, "y":5}, {"x":12, "y":3}, {"x":7, "y":2}, {"x":6, "y":2}, {"x":14, "y":5}], "centre":{"x":9, "y":4}, "id":4, "interactable":false, "setArea":null, "name":"Structure Shuffle"}, "5":{"getArea":null, "positions":[{"x":19, "y":1}, {"x":1, "y":1}], "centre":{"x":10, "y":1}, "id":5, "interactable":false, "setArea":null, "name":"Knight Shuffle"}, "0":{"getArea":null, "positions":[{"x":18, "y":1}, {"x":1, "y":7}, {"x":18, "y":8}], "centre":{"x":12, "y":5}, "id":0, "interactable":false, "setArea":null, "name":"Random Witch Start"}}, "Map_Tile_14_6":{"terrain":"mountain"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_17_11":{"terrain":"forest"}, "Map_Tile_17_10":{"terrain":"forest"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_7_5":{"terrain":"plains"}, "Map_Tile_17_2":{"terrain":"bridge"}, "Player_1":{"recruit_turtle":true, "recruit_rifleman":true, "recruit_thief":true, "recruit_dragon":true, "recruit_harpy":true, "recruit_dog":true, "recruit_wagon":true, "recruit_merman":true, "recruit_ballista":true, "recruit_balloon":true, "gold":100, "recruit_giant":true, "recruit_warship":true, "recruit_soldier":true, "recruit_spearman":true, "team":0, "recruit_frog":true, "recruit_kraken":true, "recruit_trebuchet":true, "recruit_griffin_walking":true, "recruit_knight":true, "recruit_mage":true, "recruit_harpoonship":true, "recruit_witch":true, "recruit_caravel":true, "recruit_travelboat":true, "recruit_archer":true}, "Map_Tile_13_11":{"terrain":"abyss"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Tile_0_0":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":0, "x":0}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":0, "x":0}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":19}, "terrain":"plains"}, "Map_Tile_15_6":{"terrain":"mountain"}, "Map_Tile_0_6":{"terrain":"road"}, "Map_Tile_17_4":{"terrain":"sea"}, "Map_Tile_17_3":{"terrain":"sea"}, "Map_Tile_10_1":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":1, "x":10}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":1, "x":10}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"tower", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"tower", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":31}, "terrain":"road"}, "Map_Tile_17_9":{"terrain":"forest"}, "Map_Tile_17_0":{"terrain":"sea"}, "Map_Tile_4_10":{"terrain":"river"}, "Map_Tile_16_10":{"terrain":"forest"}, "Map_Tile_16_9":{"terrain":"mountain"}, "Map_Tile_16_8":{"terrain":"mountain"}, "Map_Tile_3_10":{"terrain":"river"}, "Map_Tile_14_7":{"terrain":"mountain"}, "Map_Tile_15_5":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"mountain"}, "Map_Tile_12_8":{"terrain":"mountain"}, "Map_Tile_16_7":{"terrain":"mountain"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_13_1":{"terrain":"plains"}, "Objectives":["Destroy all Towers (Victory) (Requires Mage or Sky Rider).", "Destroy an enemy Sky Rider with your own (Requires Sky Rider).", "Win without losing your Dragon (Requires Mage or Sky Rider)."], "Map_Tile_11_0":{"terrain":"mountain"}, "Map_Tile_2_9":{"terrain":"road"}, "Map_Tile_2_3":{"terrain":"road"}, "Map_Tile_6_3":{"terrain":"road"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"road"}, "Map_Tile_5_4":{"terrain":"river"}, "Map_Tile_14_8":{"terrain":"mountain"}, "Map_Tile_15_10":{"terrain":"mountain"}, "Map_Tile_15_8":{"terrain":"mountain"}, "Map_Tile_1_6":{"terrain":"road"}, "Map_Tile_15_7":{"terrain":"mountain"}, "Map_Tile_5_7":{"terrain":"river"}, "Map_Tile_15_3":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":3, "x":15}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":3, "x":15}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":-1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":27}, "terrain":"plains"}, "Map_Tile_15_2":{"terrain":"road"}, "Map_Tile_2_8":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":8, "x":2}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":8, "x":2}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"tower", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"tower", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":32}, "terrain":"road"}, "Player_Count":3, "Map_Tile_1_1":{"terrain":"plains"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_15_0":{"terrain":"sea"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_11_3":{"terrain":"mountain"}, "Map_Tile_15_1":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":1, "x":15}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":1, "x":15}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"barracks", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"barracks", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":37}, "terrain":"plains"}, "Map_Tile_14_9":{"terrain":"mountain"}, "Map_Tile_16_1":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"road"}, "Map_Tile_13_6":{"terrain":"mountain"}, "Map_Tile_18_1":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"road"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_9_11":{"terrain":"road"}, "Map_Tile_19_9":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":9, "x":19}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":9, "x":19}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"water_city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"sea_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"water_city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":5}, "terrain":"sea"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_0_7":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":7, "x":0}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":7, "x":0}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":28}, "terrain":"plains"}, "Map_Tile_4_3":{"terrain":"road"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"mountain"}, "Map_Tile_6_6":{"terrain":"forest"}, "Map_Tile_1_3":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":3, "x":1}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"raise_dead", "pos":{"facing":0, "y":3, "x":1}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"commander_valder", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["commander", "type.ground.light"], "moveRange":4, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":250, "isRecruitable":false, "maxHealth":100, "resourceCost":3, "weapons":[{"minRange":1, "id":"valderSpell", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":true, "cost":500, "inWater":false, "id":"commander_valder", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":["valderSpell"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":15}, "terrain":"road"}, "Map_Tile_18_0":{"terrain":"forest"}, "Map_Tile_3_3":{"terrain":"road"}, "Map_Tile_10_3":{"terrain":"mountain"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"mountain"}, "Map_Tile_5_2":{"terrain":"river"}, "Map_Tile_14_11":{"terrain":"abyss"}, "Map_Tile_18_4":{"terrain":"sea"}, "Map_Tile_9_7":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":7, "x":9}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":7, "x":9}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"dragon", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["dragon", "type.air"], "moveRange":8, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":true, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"flying", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":3, "weapons":[{"minRange":1, "id":"fireBreath", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":1250, "inWater":false, "id":"dragon", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["fireBreath"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":13}, "terrain":"road"}, "Map_Tile_10_11":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":11, "x":10}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":11, "x":10}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"hq", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["structure"], "moveRange":0, "canBeCaptured":false, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":3000, "inWater":false, "id":"hq", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":1}, "terrain":"plains"}, "Map_Tile_12_10":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":10, "x":12}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":10, "x":12}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":8}, "terrain":"plains"}, "Map_Tile_2_0":{"terrain":"road"}, "Map_Tile_1_4":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":4, "x":1}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":4, "x":1}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"soldier", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["soldier", "type.ground.light"], "moveRange":4, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":[{"minRange":1, "id":"sword", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":100, "inWater":false, "id":"soldier", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["sword"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":39}, "terrain":"plains"}, "Player_2":{"recruit_turtle":true, "recruit_rifleman":true, "recruit_thief":true, "recruit_dragon":true, "recruit_harpy":true, "recruit_dog":true, "recruit_wagon":true, "recruit_merman":true, "recruit_ballista":true, "recruit_balloon":true, "gold":100, "recruit_giant":true, "recruit_warship":true, "recruit_soldier":true, "recruit_spearman":true, "team":1, "recruit_frog":true, "recruit_kraken":true, "recruit_trebuchet":true, "recruit_griffin_walking":true, "recruit_knight":true, "recruit_mage":true, "recruit_harpoonship":true, "recruit_witch":true, "recruit_caravel":true, "recruit_travelboat":true, "recruit_archer":true}, "Map_Tile_12_7":{"terrain":"mountain"}, "Map_Tile_12_6":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":6, "x":12}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":6, "x":12}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":9}, "terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"plains"}, "Map_Tile_8_2":{"terrain":"mountain"}, "Map_Tile_9_2":{"terrain":"mountain"}, "Map_Tile_3_4":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":4, "x":3}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":4, "x":3}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"soldier", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["soldier", "type.ground.light"], "moveRange":4, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":[{"minRange":1, "id":"sword", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":100, "inWater":false, "id":"soldier", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["sword"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":16}, "terrain":"plains"}, "Map_Tile_11_2":{"terrain":"mountain"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_5_8":{"terrain":"river"}, "Map_Tile_13_7":{"terrain":"mountain"}, "Map_Tile_8_11":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":11, "x":8}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":11, "x":8}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":7}, "terrain":"plains"}, "Map_Tile_5_3":{"terrain":"bridge"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"forest"}, "Map_Tile_3_11":{"terrain":"river"}, "Map_Tile_4_9":{"terrain":"river"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"road"}, "Map_Tile_8_8":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":8, "x":8}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":8, "x":8}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"spearman", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["spearman", "type.ground.light"], "moveRange":3, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":[{"minRange":1, "id":"spear", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":250, "inWater":false, "id":"spearman", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["spear"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":17}, "terrain":"plains"}, "Map_Tile_13_8":{"terrain":"mountain"}, "Map_Tile_0_9":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":9, "x":0}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":9, "x":0}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":2, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":29}, "terrain":"plains"}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"river"}, "Map_Tile_3_0":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":0, "x":3}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":0, "x":3}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":20}, "terrain":"plains"}, "Map_Tile_0_10":{"terrain":"road"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"road"}, "Map_Tile_16_6":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":6, "x":16}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":6, "x":16}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":-1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":26}, "terrain":"plains"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_9_4":{"terrain":"road"}, "Map_Tile_1_8":{"terrain":"road"}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_10_9":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":9, "x":10}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":9, "x":10}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"tower", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"tower", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":34}, "terrain":"plains"}, "Map_Size":{"x":20, "y":12}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_0_1":{"terrain":"forest"}, "Map_Tile_2_7":{"terrain":"road"}, "Map_Tile_3_5":{"terrain":"river"}, "Map_Tile_8_4":{"terrain":"road"}, "Map_Tile_7_0":{"terrain":"mountain"}, "Map_Tile_9_8":{"terrain":"road"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_0_8":{"terrain":"road"}, "Map_Tile_10_4":{"terrain":"road"}, "Map_Tile_6_0":{"terrain":"plains"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"forest"}, "Map_Tile_9_3":{"terrain":"mountain"}, "Map_Tile_4_8":{"terrain":"river"}, "Map_Tile_4_6":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":6, "x":4}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":6, "x":4}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":22}, "terrain":"plains"}, "Map_Tile_9_1":{"terrain":"mountain"}, "Map_Tile_9_6":{"terrain":"road"}, "Map_Tile_11_7":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"mountain"}, "Map_Tile_12_9":{"terrain":"mountain"}, "Map_Tile_11_1":{"terrain":"mountain"}, "Map_Tile_2_6":{"terrain":"road"}, "Map_Tile_0_3":{"terrain":"road"}, "Map_Tile_6_7":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":7, "x":6}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":7, "x":6}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"lumbermill", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":-1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"lumbermill", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":12}, "terrain":"plains"}, "Map_Tile_8_3":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":3, "x":8}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":3, "x":8}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"city", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":-1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"city", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":10}, "terrain":"plains"}, "Map_Tile_8_0":{"terrain":"mountain"}, "Map_Tile_17_5":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"forest"}, "Map_Tile_9_9":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":9, "x":9}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"heal_aura", "pos":{"facing":0, "y":9, "x":9}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"commander_mercia", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["commander", "type.ground.light"], "moveRange":4, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":250, "isRecruitable":false, "maxHealth":100, "resourceCost":3, "weapons":[{"minRange":1, "id":"merciaSword", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":true, "cost":500, "inWater":false, "id":"commander_mercia", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":["merciaSword"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":30}, "terrain":"road"}, "Map_Tile_1_2":{"unit":{"inTransport":false, "startPos":{"facing":0, "y":2, "x":1}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":0, "y":2, "x":1}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"garrison", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"barracks", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":1, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":true, "tags":["structure"], "moveRange":0, "canBeCaptured":true, "isStructure":true, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"land_building", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":{}, "canAttack":true, "isCommander":false, "cost":500, "inWater":false, "id":"barracks", "passiveMultiplier":1.0, "recruitingCostMultiplier":1.0, "weaponIds":{}, "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":36}, "terrain":"plains"}, "Map_Tile_7_4":{"terrain":"road"}, "Map_Tile_7_7":{"terrain":"forest"}, "Map_Tile_4_0":{"terrain":"mountain"}, "Map_Tile_5_10":{"terrain":"river"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"river"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Name":"Spire Fire", "Map_Tile_10_8":{"unit":{"inTransport":false, "startPos":{"facing":3, "y":8, "x":10}, "recruitDiscountMultiplier":0.0, "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "stunned":false, "tentacled":false, "grooveId":"", "pos":{"facing":3, "y":8, "x":10}, "attachedFlagId":-1, "rangedDamageTakenPercent":100, "killedByLosing":false, "health":100, "grooveCharge":0, "attackerPlayerId":-1, "factionOverride":"", "garrisonClassId":"", "hasBeenKilled":false, "setHealth":null, "attackerId":-1, "recruits":{}, "setGroove":null, "state":{}, "blessings":{}, "recruitDiscounts":{}, "canBeAttackedFromDistance":true, "unitClassId":"spearman", "canChargeGroove":true, "damageTakenPercent":100, "canBeAttacked":true, "playerId":0, "attackerUnitClass":"", "loadedUnits":{}, "itemDropNumber":0, "itemId":"", "unitClass":{"canReinforce":false, "tags":["spearman", "type.ground.light"], "moveRange":3, "canBeCaptured":false, "isStructure":false, "verbCostMultiplier":1.0, "canBeActivated":false, "loadCapacity":0, "aliasId":"", "isDamagingParentUnit":false, "inAir":false, "transportTags":{}, "reinforceMultiplier":1.0, "critConditionId":"", "movementType":"walking", "maxGroove":0, "isRecruitable":true, "maxHealth":100, "resourceCost":1, "weapons":[{"minRange":1, "id":"spear", "canAttackAir":false, "directionality":"omni", "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "horizontalAndVerticalExtraWidth":0, "blockedByEnemies":false, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "maxRange":1, "canCounterAttack":true}], "canAttack":true, "isCommander":false, "cost":250, "inWater":false, "id":"spearman", "passiveMultiplier":1.5, "recruitingCostMultiplier":1.0, "weaponIds":["spear"], "isAttackable":true}, "miniGrooveId":"", "underwater":false, "transportedBy":-1, "items":{}, "hadTurn":false, "id":18}, "terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Split_Valley.json b/worlds/wargroove2/levels/Split_Valley.json new file mode 100644 index 000000000000..8f7bef9c9031 --- /dev/null +++ b/worlds/wargroove2/levels/Split_Valley.json @@ -0,0 +1 @@ +{"Locations":{"1":{"getArea":null, "centre":{"x":9, "y":14}, "interactable":false, "setArea":null, "positions":[{"x":10, "y":15}, {"x":9, "y":15}, {"x":8, "y":15}, {"x":7, "y":14}, {"x":7, "y":13}, {"x":8, "y":13}, {"x":9, "y":13}, {"x":10, "y":13}, {"x":10, "y":14}, {"x":11, "y":14}, {"x":6, "y":14}, {"x":11, "y":13}, {"x":12, "y":15}, {"x":12, "y":14}, {"x":6, "y":15}, {"x":8, "y":14}, {"x":13, "y":15}, {"x":5, "y":15}], "id":1, "name":"Enemy Army Shuffle"}, "2":{"getArea":null, "centre":{"x":0, "y":6}, "interactable":false, "setArea":null, "positions":[{"x":0, "y":5}, {"x":0, "y":6}], "id":2, "name":"River Village Shuffle Left"}, "3":{"getArea":null, "centre":{"x":9, "y":12}, "interactable":false, "setArea":null, "positions":[{"x":11, "y":11}, {"x":5, "y":13}, {"x":8, "y":11}, {"x":13, "y":14}], "id":3, "name":"Shuffle2"}, "4":{"getArea":null, "centre":{"x":0, "y":0}, "interactable":false, "setArea":null, "positions":{}, "id":4, "name":"Shuffle3"}, "5":{"getArea":null, "centre":{"x":9, "y":9}, "interactable":false, "setArea":null, "positions":[{"x":4, "y":9}, {"x":5, "y":9}, {"x":5, "y":10}, {"x":4, "y":10}, {"x":14, "y":7}, {"x":14, "y":8}, {"x":14, "y":9}, {"x":14, "y":10}, {"x":14, "y":11}, {"x":4, "y":6}], "id":5, "name":"Bridges"}, "6":{"getArea":null, "centre":{"x":15, "y":1}, "interactable":false, "setArea":null, "positions":[{"x":14, "y":1}, {"x":15, "y":1}, {"x":15, "y":0}, {"x":14, "y":0}], "id":6, "name":"River Village Right Shuffle"}, "0":{"getArea":null, "centre":{"x":8, "y":1}, "interactable":false, "setArea":null, "positions":[{"x":4, "y":0}, {"x":8, "y":1}, {"x":3, "y":0}, {"x":5, "y":0}, {"x":10, "y":0}, {"x":8, "y":0}, {"x":17, "y":0}, {"x":18, "y":1}, {"x":13, "y":1}, {"x":14, "y":1}, {"x":0, "y":2}, {"x":0, "y":1}], "id":0, "name":"Shuffle1"}}, "Map_Tile_7_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":7, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":27, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":7, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"knight", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lance", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":600, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["knight", "type.ground.heavy"], "canBeActivated":false, "id":"knight", "aliasId":"", "moveRange":6, "weaponIds":["lance"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_15_1":{"terrain":"river"}, "Map_Tile_13_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":13, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":36, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":13, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"giant", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"giantSlam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":1200, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["giant", "type.ground.heavy", "tall"], "canBeActivated":false, "id":"giant", "aliasId":"", "moveRange":5, "weaponIds":["giantSlam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_4_0":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":4, "y":0}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":21, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":4, "y":0}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_16_4":{"terrain":"road"}, "Map_Tile_13_3":{"terrain":"road"}, "Map_Tile_2_14":{"terrain":"river"}, "Map_Tile_18_5":{"terrain":"road"}, "Map_Tile_0_15":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"road"}, "Map_Tile_6_4":{"terrain":"mountain"}, "Map_Tile_10_5":{"terrain":"road"}, "Map_Tile_0_8":{"terrain":"road"}, "Map_Tile_8_2":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"road"}, "Map_Tile_4_8":{"terrain":"road"}, "Map_Tile_11_10":{"terrain":"mountain"}, "Map_Tile_8_7":{"terrain":"mountain"}, "Map_Tile_3_2":{"terrain":"mountain"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"mountain"}, "Map_Tile_1_11":{"terrain":"river"}, "Map_Tile_10_14":{"terrain":"forest", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":10, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":43, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":10, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"giant", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"giantSlam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":1200, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["giant", "type.ground.heavy", "tall"], "canBeActivated":false, "id":"giant", "aliasId":"", "moveRange":5, "weaponIds":["giantSlam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_6_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":29, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":6, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"soldier", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"sword", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"walking", "isDamagingParentUnit":false, "cost":100, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["soldier", "type.ground.light"], "canBeActivated":false, "id":"soldier", "aliasId":"", "moveRange":4, "weaponIds":["sword"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_8_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":42, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":8, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"giant", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"giantSlam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":1200, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["giant", "type.ground.heavy", "tall"], "canBeActivated":false, "id":"giant", "aliasId":"", "moveRange":5, "weaponIds":["giantSlam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_9_6":{"terrain":"road"}, "Map_Tile_11_12":{"terrain":"plains"}, "Map_Tile_2_8":{"terrain":"road"}, "Map_Tile_14_8":{"terrain":"bridge"}, "Map_Tile_10_3":{"terrain":"road"}, "Map_Tile_7_4":{"terrain":"forest_cut"}, "Map_Tile_1_12":{"terrain":"river"}, "Map_Tile_11_9":{"terrain":"mountain"}, "Map_Tile_8_3":{"terrain":"road"}, "Map_Tile_7_7":{"terrain":"mountain"}, "Map_Tile_13_8":{"terrain":"plains"}, "Map_Tile_11_1":{"terrain":"mountain"}, "Map_Tile_1_8":{"terrain":"road"}, "Map_Tile_9_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":40, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":9, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"knight", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lance", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":600, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["knight", "type.ground.heavy"], "canBeActivated":false, "id":"knight", "aliasId":"", "moveRange":6, "weaponIds":["lance"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_9_4":{"terrain":"road"}, "Map_Tile_9_2":{"terrain":"road"}, "Counters":{}, "Map_Tile_16_1":{"terrain":"plains"}, "Flags":{"0":0}, "Map_Tile_10_10":{"terrain":"plains"}, "Map_Tile_2_9":{"terrain":"wall"}, "Map_Tile_9_7":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":7}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":20, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":9, "y":7}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":-1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"bridge"}, "Map_Tile_1_5":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"road"}, "Map_Tile_9_12":{"terrain":"plains"}, "Map_Tile_17_11":{"terrain":"river"}, "Map_Tile_15_7":{"terrain":"wall"}, "Map_Tile_14_2":{"terrain":"road"}, "Map_Tile_8_1":{"terrain":"plains"}, "Map_Tile_18_14":{"terrain":"river"}, "Map_Tile_6_13":{"terrain":"forest"}, "Map_Tile_18_4":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":18, "y":4}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":12, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":18, "y":4}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_3_8":{"terrain":"wall"}, "Map_Tile_19_1":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":5, "y":8}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":17, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":5, "y":8}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":-1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_8_12":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"mountain"}, "Map_Tile_6_7":{"terrain":"forest"}, "Map_Tile_1_10":{"terrain":"wall"}, "Map_Tile_18_2":{"terrain":"mountain"}, "Map_Tile_3_5":{"terrain":"wall"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"mountain"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"mountain"}, "Map_Tile_5_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":5, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":35, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":5, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"giant", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"giantSlam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":1200, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["giant", "type.ground.heavy", "tall"], "canBeActivated":false, "id":"giant", "aliasId":"", "moveRange":5, "weaponIds":["giantSlam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_13_6":{"terrain":"forest_cut"}, "Map_Tile_4_6":{"terrain":"bridge"}, "Map_Tile_7_6":{"terrain":"mountain"}, "Map_Tile_9_8":{"terrain":"mountain"}, "Map_Tile_17_12":{"terrain":"river"}, "Map_Tile_1_7":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":1, "y":7}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":32, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":1, "y":7}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"barracks", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"barracks", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveCharge":0}}, "Map_Tile_6_3":{"terrain":"forest_cut"}, "Map_Tile_1_14":{"terrain":"forest"}, "Map_Tile_3_1":{"terrain":"plains"}, "Map_Tile_17_13":{"terrain":"river"}, "Map_Tile_11_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":11, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":15, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":11, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"golem_unit", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"golem_slam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":false, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"wheels", "isDamagingParentUnit":false, "cost":2000, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["golem", "tall"], "canBeActivated":false, "id":"golem_unit", "aliasId":"", "moveRange":7, "weaponIds":["golem_slam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_4_13":{"terrain":"river"}, "Map_Tile_14_7":{"terrain":"bridge"}, "Map_Tile_12_8":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":12, "y":8}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":18, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":12, "y":8}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":-1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_16_0":{"terrain":"mountain"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_9_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":39, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":9, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"archer", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"bow", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":3, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"walking", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.3500000238419, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["archer", "type.ground.light"], "canBeActivated":false, "id":"archer", "aliasId":"", "moveRange":3, "weaponIds":["bow"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_0_5":{"terrain":"river"}, "Map_Tile_16_7":{"terrain":"plains"}, "Map_Tile_17_6":{"terrain":"road"}, "Map_Tile_2_11":{"terrain":"river"}, "Map_Tile_6_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":31, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":6, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"mage", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lightning", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"walking", "isDamagingParentUnit":false, "cost":400, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":0.5, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["mage", "type.ground.light", "spellcaster"], "canBeActivated":false, "id":"mage", "aliasId":"", "moveRange":5, "weaponIds":["lightning"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_8_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":37, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":8, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"ballista", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":6, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":false, "directionality":"omni", "minRange":2}], "inAir":false, "isRecruitable":true, "movementType":"wheels", "isDamagingParentUnit":false, "cost":800, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["ballista", "type.ground.heavy"], "canBeActivated":false, "id":"ballista", "aliasId":"", "moveRange":6, "weaponIds":["ballistaBolt"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_19_14":{"terrain":"forest"}, "Map_Tile_6_6":{"terrain":"forest_cut"}, "Map_Tile_17_3":{"terrain":"mountain"}, "Map_Tile_19_2":{"terrain":"mountain"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_17_1":{"terrain":"plains", "item":{"itemId":44, "pos":{"x":17, "y":1}, "isConsumable":true, "type":"immunity_potion", "unitTypeRestriction":{}}}, "Map_Tile_3_11":{"terrain":"river"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"mountain"}, "Map_Tile_12_1":{"terrain":"mountain"}, "Map_Tile_7_13":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"river"}, "Map_Tile_12_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":12, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":30, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":12, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"mage", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lightning", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"walking", "isDamagingParentUnit":false, "cost":400, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":0.5, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["mage", "type.ground.light", "spellcaster"], "canBeActivated":false, "id":"mage", "aliasId":"", "moveRange":5, "weaponIds":["lightning"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_0_11":{"terrain":"river"}, "Map_Tile_7_10":{"terrain":"plains"}, "Objectives":["Defeat a unit with a Trebuchet crit. (Requires Trebuchet)", "Have all three ranged ground units at once. (Requires Archer, Ballista, and Trebuchet)", "Survive for 7 turns. (Requires Trebuchet and either Air Trooper or Bridges)"], "Map_Tile_18_6":{"terrain":"road"}, "Map_Tile_12_6":{"terrain":"mountain"}, "Map_Tile_18_13":{"terrain":"forest"}, "Map_Tile_19_9":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":19, "y":9}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":23, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":19, "y":9}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_2_12":{"terrain":"river"}, "Map_Tile_5_9":{"terrain":"bridge"}, "Map_Tile_1_4":{"terrain":"mountain"}, "Map_Tile_3_6":{"terrain":"wall"}, "Map_Tile_17_4":{"terrain":"mountain"}, "Player_Count":2, "Map_Tile_10_7":{"terrain":"forest"}, "Map_Tile_2_15":{"terrain":"river"}, "Map_Tile_4_12":{"terrain":"river"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_1_13":{"terrain":"plains"}, "Map_Tile_19_5":{"terrain":"road"}, "Map_Tile_18_1":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":18, "y":1}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":5, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":18, "y":1}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_10_12":{"terrain":"plains"}, "Map_Tile_4_14":{"terrain":"river"}, "Map_Tile_8_10":{"terrain":"mountain"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"wall"}, "Map_Tile_0_14":{"terrain":"forest"}, "Map_Tile_15_12":{"terrain":"plains"}, "Author":"Magnemania", "Map_Tile_5_3":{"terrain":"forest_cut"}, "Map_Tile_4_2":{"terrain":"mountain"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"mountain"}, "Map_Tile_3_4":{"terrain":"river"}, "Map_Tile_1_3":{"terrain":"mountain"}, "Map_Tile_5_4":{"terrain":"forest_cut"}, "Map_Tile_15_0":{"terrain":"river"}, "Map_Tile_13_7":{"terrain":"forest_cut"}, "Map_Name":"Split Valley", "Map_Tile_3_10":{"terrain":"river"}, "Map_Tile_1_15":{"terrain":"river"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_3_14":{"terrain":"river"}, "Map_Tile_5_14":{"terrain":"forest"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_6_12":{"terrain":"forest"}, "Map_Tile_9_5":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":5}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":4, "transportedBy":-1, "grooveId":"heal_aura", "startPos":{"facing":0, "x":9, "y":5}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"commander_mercia", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"merciaSword", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":false, "movementType":"walking", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":true, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":250, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["commander", "type.ground.light"], "canBeActivated":false, "id":"commander_mercia", "aliasId":"", "moveRange":4, "weaponIds":["merciaSword"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_10_13":{"terrain":"forest", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":10, "y":13}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":8, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":10, "y":13}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"knight", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lance", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":600, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["knight", "type.ground.heavy"], "canBeActivated":false, "id":"knight", "aliasId":"", "moveRange":6, "weaponIds":["lance"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_14_14":{"terrain":"forest"}, "Map_Tile_19_0":{"terrain":"mountain"}, "Map_Size":{"x":20, "y":16}, "Map_Tile_7_3":{"terrain":"road"}, "Map_Tile_16_14":{"terrain":"river"}, "Map_Tile_14_12":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"road"}, "Map_Tile_8_8":{"terrain":"mountain"}, "Triggers":[{"id":"Export (Always on Top)", "isIntro":false, "actions":[{"id":"ap_export", "parameters":["1", "Split Valley", "Magnemania", "Defeat a unit with a Trebuchet crit. (Requires Trebuchet)", "Have all three ranged ground units at once. (Requires Archer, Ballista, and Trebuchet)", "", "Survive for 7 turns. (Requires Trebuchet and either Air Trooper or Bridges)"], "enabled":true}], "recurring":"start_of_match", "conditions":{}, "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"Set AI", "isIntro":false, "actions":[{"id":"ai_set_profile", "parameters":["current", "aggressive"], "enabled":true}, {"id":"modify_gold", "parameters":["P1", "1", "1000"], "enabled":true}], "recurring":"once", "conditions":{}, "enabled":true, "players":[0, 1, 0, 0, 0, 0, 0, 0]}, {"id":"Shuffle Units", "isIntro":false, "actions":[{"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "6", "6", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true}], "recurring":"start_of_match", "conditions":{}, "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"Bridge Controls Active", "isIntro":false, "actions":[{"id":"dialogue_box_simple", "parameters":["neutral", "felheim_villager4", "Bridge controls are active.", "0", ""], "enabled":true}, {"id":"dialogue_box_simple", "parameters":["neutral", "felheim_villager4", "Retract the bridges. We must slow their advance.", "0", ""], "enabled":true}, {"id":"activate_flood", "parameters":["5", "river", "default", "", "0", "0", "0"], "enabled":true}], "recurring":"once", "conditions":[{"id":"player_turn", "parameters":["current"], "enabled":true}, {"id":"ap_has_item", "parameters":["252023", "1", "4"], "enabled":true}, {"id":"start_of_turn", "parameters":{}, "enabled":true}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"Trebuchet Crit Kill (Check 253371)", "isIntro":false, "actions":[{"id":"ap_location_send", "parameters":["253371"], "enabled":true}], "recurring":"once", "conditions":[{"id":"unit_killed", "parameters":["trebuchet", "current", "*unit", "P2", "-1"], "enabled":true}, {"id":"unit_presence", "parameters":["current", "0", "1", "trebuchet", "-10"], "enabled":true}, {"id":"location_compare", "parameters":["-2", "overlaps", "-10"], "enabled":true}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"Have Archer, Trebuchet, and Ballista (Check 253372)", "isIntro":false, "actions":[{"id":"ap_location_send", "parameters":["253372"], "enabled":true}], "recurring":"once", "conditions":[{"id":"unit_presence", "parameters":["current", "4", "1", "trebuchet", "-1"], "enabled":true}, {"id":"unit_presence", "parameters":["current", "4", "1", "archer", "-1"], "enabled":true}, {"id":"unit_presence", "parameters":["current", "4", "1", "ballista", "-1"], "enabled":true}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_defeat_no_units", "isIntro":false, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_defeat_commander", "isIntro":false, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_defeat_hq", "isIntro":false, "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"$trigger_default_victory", "isIntro":false, "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "recurring":"oncePerPlayer", "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0]}, {"id":"Victory (Survive 7 Turns)", "isIntro":false, "actions":[{"id":"victory", "parameters":["P1"], "enabled":true}], "recurring":"once", "conditions":[{"id":"player_turn", "parameters":["P2"], "enabled":true}, {"id":"current_turn_number", "parameters":["0", "7"], "enabled":true}, {"id":"end_of_turn", "parameters":{}, "enabled":true}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}, {"id":"P1 Wins (Check 253370)", "isIntro":false, "actions":[{"id":"ap_location_send", "parameters":["253370"], "enabled":true}], "recurring":"once", "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0]}], "Map_Tile_19_15":{"terrain":"river"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"road"}, "Map_Tile_12_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":12, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":25, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":12, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"soldier", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"sword", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"walking", "isDamagingParentUnit":false, "cost":100, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["soldier", "type.ground.light"], "canBeActivated":false, "id":"soldier", "aliasId":"", "moveRange":4, "weaponIds":["sword"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_12_13":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"road"}, "Map_Tile_19_13":{"terrain":"forest"}, "Map_Tile_9_13":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":13}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":41, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":9, "y":13}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"giant", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"giantSlam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":1200, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["giant", "type.ground.heavy", "tall"], "canBeActivated":false, "id":"giant", "aliasId":"", "moveRange":5, "weaponIds":["giantSlam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_17_14":{"terrain":"river"}, "Map_Tile_19_12":{"terrain":"river"}, "Map_Tile_17_10":{"terrain":"wall"}, "Map_Tile_19_11":{"terrain":"river"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_19_10":{"terrain":"wall"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"road"}, "Map_Tile_14_1":{"terrain":"river", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":14, "y":1}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":34, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":14, "y":1}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"river_city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"river_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"river_city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_19_7":{"terrain":"road"}, "Map_Tile_19_6":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":19, "y":6}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":22, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":19, "y":6}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"wall"}, "Map_Tile_10_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":10, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":38, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":10, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"ballista", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":6, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":false, "directionality":"omni", "minRange":2}], "inAir":false, "isRecruitable":true, "movementType":"wheels", "isDamagingParentUnit":false, "cost":800, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["ballista", "type.ground.heavy"], "canBeActivated":false, "id":"ballista", "aliasId":"", "moveRange":6, "weaponIds":["ballistaBolt"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_18_15":{"terrain":"river"}, "Map_Tile_18_12":{"terrain":"river"}, "Map_Tile_18_11":{"terrain":"river"}, "Map_Tile_18_10":{"terrain":"wall"}, "Map_Tile_11_7":{"terrain":"mountain"}, "Map_Tile_18_9":{"terrain":"road"}, "Map_Tile_18_8":{"terrain":"road"}, "Map_Tile_4_3":{"terrain":"forest_cut"}, "Map_Tile_11_13":{"terrain":"plains"}, "Map_Tile_18_0":{"terrain":"mountain"}, "Map_Tile_10_4":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":10, "y":4}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":10, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":10, "y":4}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"griffin_walking", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"airtrooperBeak", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"airphibious", "isDamagingParentUnit":false, "cost":400, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["airtrooper", "type.air"], "canBeActivated":false, "id":"griffin_walking", "aliasId":"", "moveRange":5, "weaponIds":["airtrooperBeak"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_15_15":{"terrain":"plains"}, "Map_Tile_17_15":{"terrain":"forest"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":17, "y":8}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":1, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":17, "y":8}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"barracks", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"barracks", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "grooveCharge":0}}, "Map_Tile_11_5":{"terrain":"road"}, "Map_Tile_17_7":{"terrain":"road"}, "Player_2":{"team":1, "recruit_ballista":true, "recruit_harpy":true, "recruit_frog":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_knight":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_archer":true, "recruit_dragon":true, "gold":100, "recruit_turtle":true, "recruit_kraken":true, "recruit_soldier":true, "recruit_giant":true, "recruit_travelboat":true, "recruit_warship":true, "recruit_caravel":true, "recruit_dog":true, "recruit_mage":true, "recruit_spearman":true, "recruit_witch":true, "recruit_trebuchet":true, "recruit_wagon":true, "recruit_griffin_walking":true, "recruit_balloon":true}, "Map_Tile_11_0":{"terrain":"mountain"}, "Map_Tile_2_13":{"terrain":"river"}, "Map_Tile_0_6":{"terrain":"river", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":0, "y":6}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":33, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":0, "y":6}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"river_city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"river_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"river_city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_17_2":{"terrain":"mountain"}, "Map_Tile_17_0":{"terrain":"plains"}, "Map_Tile_13_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":13, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":7, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":13, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_16_13":{"terrain":"river"}, "Map_Tile_15_13":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"river"}, "Map_Tile_16_11":{"terrain":"river"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"mountain"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_3_13":{"terrain":"river"}, "Map_Tile_16_5":{"terrain":"road"}, "Map_Tile_13_0":{"terrain":"mountain"}, "Map_Tile_16_3":{"terrain":"road"}, "Map_Tile_12_3":{"terrain":"road"}, "Map_Tile_14_3":{"terrain":"road"}, "Map_Tile_3_15":{"terrain":"river"}, "Map_Tile_7_15":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":7, "y":15}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":26, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":7, "y":15}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"golem_unit", "setHealth":null, "unitClass":{"resourceCost":3, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"golem_slam", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":true, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":false, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"wheels", "isDamagingParentUnit":false, "cost":2000, "critConditionId":"", "isCommander":false, "passiveMultiplier":2.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["golem", "tall"], "canBeActivated":false, "id":"golem_unit", "aliasId":"", "moveRange":7, "weaponIds":["golem_slam"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_0_4":{"terrain":"mountain"}, "Map_Tile_15_11":{"terrain":"river"}, "Map_Tile_15_10":{"terrain":"wall"}, "Map_Tile_15_9":{"terrain":"wall"}, "Map_Tile_15_8":{"terrain":"wall"}, "Map_Tile_9_1":{"terrain":"road", "item":{"itemId":46, "pos":{"x":9, "y":1}, "isConsumable":false, "type":"power_gauntlet", "unitTypeRestriction":{}}}, "Map_Tile_11_3":{"terrain":"road"}, "Map_Tile_13_4":{"terrain":"forest"}, "Map_Tile_19_3":{"terrain":"mountain"}, "Map_Tile_15_6":{"terrain":"wall"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_8_13":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":13}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":24, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":8, "y":13}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"knight", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lance", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":600, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["knight", "type.ground.heavy"], "canBeActivated":false, "id":"knight", "aliasId":"", "moveRange":6, "weaponIds":["lance"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_5_11":{"terrain":"road"}, "Map_Tile_17_9":{"terrain":"road"}, "Map_Tile_15_5":{"terrain":"wall"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_15_4":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":15, "y":4}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":19, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":15, "y":4}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":-1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_9_3":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":9, "y":3}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":2, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":9, "y":3}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"hq", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":3000, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"hq", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_15_3":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":15, "y":3}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":13, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":15, "y":3}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_15_2":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":8, "y":4}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":9, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":8, "y":4}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"griffin_walking", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"airtrooperBeak", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"airphibious", "isDamagingParentUnit":false, "cost":400, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["airtrooper", "type.air"], "canBeActivated":false, "id":"griffin_walking", "aliasId":"", "moveRange":5, "weaponIds":["airtrooperBeak"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_15_14":{"terrain":"forest"}, "Map_Tile_14_15":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"river"}, "Map_Tile_14_13":{"terrain":"forest"}, "Map_Tile_14_11":{"terrain":"bridge"}, "Map_Tile_18_7":{"terrain":"road"}, "Map_Tile_14_10":{"terrain":"bridge"}, "Map_Tile_14_9":{"terrain":"bridge"}, "Map_Tile_8_9":{"terrain":"mountain"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_0_0":{"terrain":"mountain"}, "Map_Tile_12_7":{"terrain":"mountain"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_2_4":{"terrain":"road"}, "Map_Tile_7_5":{"terrain":"mountain"}, "Map_Tile_7_12":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_12_11":{"terrain":"forest"}, "Map_Tile_5_12":{"terrain":"forest"}, "Map_Tile_3_7":{"terrain":"wall"}, "Map_Tile_4_10":{"terrain":"bridge"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"road"}, "Map_Tile_11_11":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":11, "y":11}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":14, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":11, "y":11}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_13_13":{"terrain":"plains"}, "Map_Tile_13_12":{"terrain":"plains"}, "Map_Tile_3_12":{"terrain":"river"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"forest"}, "Map_Tile_2_0":{"terrain":"road"}, "Map_Tile_10_0":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":10, "y":0}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":11, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":10, "y":0}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_2_7":{"terrain":"road"}, "Map_Tile_12_0":{"terrain":"mountain"}, "Map_Tile_10_2":{"terrain":"plains"}, "Map_Tile_9_9":{"terrain":"mountain"}, "Map_Tile_4_4":{"terrain":"forest_cut"}, "Player_1":{"team":0, "recruit_ballista":true, "recruit_harpy":true, "recruit_frog":true, "recruit_harpoonship":true, "recruit_merman":true, "recruit_knight":true, "recruit_thief":true, "recruit_rifleman":true, "recruit_archer":true, "recruit_dragon":true, "gold":100, "recruit_turtle":true, "recruit_kraken":true, "recruit_soldier":true, "recruit_giant":true, "recruit_travelboat":true, "recruit_warship":true, "recruit_caravel":true, "recruit_dog":true, "recruit_mage":true, "recruit_spearman":true, "recruit_witch":true, "recruit_trebuchet":true, "recruit_wagon":true, "recruit_griffin_walking":true, "recruit_balloon":true}, "Map_Tile_3_3":{"terrain":"road"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"wall"}, "Map_Tile_1_0":{"terrain":"mountain"}, "Map_Tile_12_4":{"terrain":"mountain"}, "Map_Tile_16_2":{"terrain":"road"}, "Map_Tile_6_11":{"terrain":"road"}, "Map_Tile_6_9":{"terrain":"forest_cut"}, "Map_Tile_11_14":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":3, "x":11, "y":14}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":28, "transportedBy":-1, "grooveId":"", "startPos":{"facing":3, "x":11, "y":14}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"knight", "setHealth":null, "unitClass":{"resourceCost":2, "isAttackable":true, "isStructure":false, "reinforceMultiplier":1.0, "weapons":[{"id":"lance", "unitIdWhenAttacking":"", "canAttackSubmerged":false, "canAttackAir":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "terrainExclusion":{}, "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "canCounterAttack":true, "canMoveAndAttack":true, "directionality":"omni", "minRange":1}], "inAir":false, "isRecruitable":true, "movementType":"riding", "isDamagingParentUnit":false, "cost":600, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.5, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":false, "canReinforce":false, "recruitingCostMultiplier":1.0, "tags":["knight", "type.ground.heavy"], "canBeActivated":false, "id":"knight", "aliasId":"", "moveRange":6, "weaponIds":["lance"], "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":1, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_4_15":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"mountain"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_1_1":{"terrain":"plains", "item":{"itemId":45, "pos":{"x":1, "y":1}, "isConsumable":true, "type":"groove_boost", "unitTypeRestriction":{}}}, "Map_Tile_5_0":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"forest_cut"}, "Map_Tile_0_10":{"terrain":"wall"}, "Map_Tile_10_6":{"terrain":"road", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":10, "y":6}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":3, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":10, "y":6}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"tower", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"tower", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "grooveCharge":0}}, "Map_Tile_0_3":{"terrain":"mountain"}, "Map_Tile_5_13":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":5, "y":13}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":6, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":5, "y":13}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"city", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"city", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains", "unit":{"rangedDamageTakenPercent":100, "pos":{"facing":0, "x":6, "y":5}, "merchantDiscountMultiplier":0.0, "recruitDiscounts":{}, "id":16, "transportedBy":-1, "grooveId":"", "startPos":{"facing":0, "x":6, "y":5}, "itemDropNumber":0, "hadTurn":false, "merchantDiscounts":{}, "attackerId":-1, "canChargeGroove":true, "underwater":false, "hasBeenKilled":false, "factionOverride":"", "items":{}, "state":{}, "miniGrooveId":"", "unitClassId":"lumbermill", "setHealth":null, "unitClass":{"resourceCost":1, "isAttackable":true, "isStructure":true, "reinforceMultiplier":1.0, "weapons":{}, "inAir":false, "isRecruitable":true, "movementType":"land_building", "isDamagingParentUnit":false, "cost":500, "critConditionId":"", "isCommander":false, "passiveMultiplier":1.0, "transportTags":{}, "maxGroove":0, "loadCapacity":0, "verbCostMultiplier":1.0, "maxHealth":100, "canBeCaptured":true, "canReinforce":true, "recruitingCostMultiplier":1.0, "tags":["structure"], "canBeActivated":false, "id":"lumbermill", "aliasId":"", "moveRange":0, "weaponIds":{}, "inWater":false, "canAttack":true}, "canBeAttackedFromDistance":true, "attackerPlayerId":-1, "canBeAttacked":true, "tentacled":false, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "playerId":0, "itemId":"", "damageTakenPercent":100, "garrisonClassId":"garrison", "killedByLosing":false, "recruitDiscountMultiplier":0.0, "blessings":{}, "inTransport":false, "setGroove":null, "health":100, "attackerUnitClass":"", "recruits":{}, "grooveCharge":0}}, "Map_Tile_7_0":{"terrain":"mountain"}, "Map_Tile_9_0":{"terrain":"road"}, "Map_Tile_8_0":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_8_5":{"terrain":"mountain"}, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_0_13":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"river"}, "Map_Tile_4_9":{"terrain":"bridge"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Sunken_Forest.json b/worlds/wargroove2/levels/Sunken_Forest.json new file mode 100644 index 000000000000..244fe121216b --- /dev/null +++ b/worlds/wargroove2/levels/Sunken_Forest.json @@ -0,0 +1 @@ +{"Map_Tile_12_7":{"terrain":"bridge"}, "Map_Tile_8_8":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"sea"}, "Map_Tile_7_2":{"terrain":"forest_cut"}, "Map_Tile_5_7":{"terrain":"reef"}, "Map_Tile_5_8":{"terrain":"sea"}, "Map_Tile_1_8":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":1, "y":8, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"barracks", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"barracks", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":5, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":1, "y":8, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_6_9":{"terrain":"bridge"}, "Map_Tile_11_10":{"terrain":"beach"}, "Map_Tile_4_8":{"terrain":"sea"}, "Map_Tile_10_3":{"terrain":"sea"}, "Objectives":["Station an Archer on the mountain. (Requires Archer).", "Destroy the enemy Fortress with a Warship (Requires Warship and anti-air).", "Win with standard conditions (Requires Mage or Harpoon Ship)."], "Map_Tile_4_3":{"terrain":"forest"}, "Map_Tile_0_3":{"terrain":"forest"}, "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"sea"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_15_0":{"terrain":"forest"}, "Map_Tile_11_9":{"terrain":"reef"}, "Map_Tile_15_3":{"terrain":"forest", "unit":{"playerId":1, "startPos":{"x":15, "y":3, "facing":3}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"harpy", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.25, "movementType":"flying", "maxHealth":100, "isAttackable":true, "resourceCost":2, "verbCostMultiplier":1.0, "weaponIds":["harpyClaws"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"harpyClaws", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":6, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":600, "canBeActivated":false, "tags":["harpy", "type.air"], "inAir":true, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"harpy", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":18, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":15, "y":3, "facing":3}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_12_1":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"sea"}, "Map_Name":"Sunken Forest", "Map_Tile_9_7":{"terrain":"forest"}, "Map_Tile_13_2":{"terrain":"forest", "unit":{"playerId":1, "startPos":{"x":13, "y":2, "facing":3}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"commander_wulfar_pirate", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":175, "isCommander":true, "passiveMultiplier":1.0, "movementType":"walking", "maxHealth":100, "isAttackable":true, "resourceCost":3, "verbCostMultiplier":1.0, "weaponIds":["wulfarHammer"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"wulfarHammer", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":4, "recruitingCostMultiplier":1.0, "isRecruitable":false, "cost":500, "canBeActivated":false, "tags":["commander", "type.ground.light", "tall"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"commander_wulfar_pirate", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":15, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":13, "y":2, "facing":3}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":"golf"}}, "Map_Tile_8_6":{"terrain":"forest"}, "Counters":{}, "Map_Tile_6_1":{"terrain":"sea"}, "Map_Tile_9_2":{"terrain":"forest"}, "Map_Tile_12_6":{"terrain":"forest"}, "Map_Tile_3_7":{"terrain":"sea"}, "Map_Tile_5_10":{"terrain":"sea"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"bridge"}, "Map_Tile_12_9":{"terrain":"beach"}, "Map_Tile_9_1":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"sea"}, "Map_Tile_5_4":{"terrain":"forest"}, "Author":"Magnemania", "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_3_8":{"terrain":"beach", "unit":{"playerId":0, "startPos":{"x":3, "y":8, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"caravel", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.5, "movementType":"river_sailing", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":["caravelWeapon"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"caravelWeapon", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":5, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":250, "canBeActivated":false, "tags":["caravel", "type.sea.light"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"caravel", "transportTags":{}, "inWater":true, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":21, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":3, "y":8, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_0_9":{"terrain":"forest"}, "Map_Tile_14_5":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"forest"}, "Map_Tile_11_5":{"terrain":"sea"}, "Map_Tile_2_0":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":2, "y":0, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":22, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":2, "y":0, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"reef"}, "Map_Tile_15_8":{"terrain":"forest"}, "Map_Tile_7_9":{"terrain":"bridge"}, "Map_Tile_13_3":{"terrain":"sea"}, "Map_Tile_5_2":{"terrain":"plains", "unit":{"playerId":-1, "startPos":{"x":5, "y":2, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":14, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":5, "y":2, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_8_2":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":8, "y":2, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"hq", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":3000, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":true, "loadCapacity":0, "id":"hq", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":3, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":8, "y":2, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"reef"}, "Map_Tile_7_7":{"terrain":"forest"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"sea"}, "Map_Tile_3_10":{"terrain":"forest_cut"}, "Map_Tile_11_8":{"terrain":"sea"}, "Map_Tile_0_5":{"terrain":"sea", "unit":{"playerId":-1, "startPos":{"x":0, "y":5, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"water_city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"sea_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"water_city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":25, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":0, "y":5, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_3_3":{"terrain":"sea"}, "Map_Tile_10_4":{"terrain":"sea"}, "Map_Tile_7_10":{"terrain":"sea"}, "Map_Tile_0_6":{"terrain":"reef"}, "Map_Tile_7_8":{"terrain":"bridge"}, "Map_Tile_4_5":{"terrain":"forest"}, "Map_Tile_7_5":{"terrain":"reef"}, "Map_Tile_0_10":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":0, "y":10, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":6, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":0, "y":10, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":8, "y":7, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"hq", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":3000, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":true, "loadCapacity":0, "id":"hq", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":2, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":8, "y":7, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_11_4":{"terrain":"sea", "unit":{"playerId":1, "startPos":{"x":11, "y":4, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"water_city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"sea_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"water_city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":12, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":11, "y":4, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"sea"}, "Map_Tile_3_2":{"terrain":"bridge"}, "Map_Tile_8_5":{"terrain":"bridge"}, "Map_Tile_10_10":{"terrain":"sea"}, "Map_Tile_7_1":{"terrain":"forest_cut"}, "Map_Tile_13_9":{"terrain":"forest"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"sea"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"sea"}, "Map_Tile_6_4":{"terrain":"beach"}, "Map_Tile_11_0":{"terrain":"sea", "unit":{"playerId":1, "startPos":{"x":11, "y":0, "facing":3}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"harpy", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.25, "movementType":"flying", "maxHealth":100, "isAttackable":true, "resourceCost":2, "verbCostMultiplier":1.0, "weaponIds":["harpyClaws"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"harpyClaws", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":6, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":600, "canBeActivated":false, "tags":["harpy", "type.air"], "inAir":true, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"harpy", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":16, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":11, "y":0, "facing":3}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_15_10":{"terrain":"forest"}, "Map_Tile_4_1":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"bridge"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"sea"}, "Map_Tile_13_7":{"terrain":"bridge"}, "Map_Tile_15_4":{"terrain":"plains"}, "Map_Size":{"y":11, "x":16}, "Map_Tile_1_10":{"terrain":"forest_cut"}, "Map_Tile_15_2":{"terrain":"forest"}, "Map_Tile_15_1":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains"}, "Map_Tile_14_7":{"terrain":"bridge"}, "Triggers":[{"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "id":"Export (Always on Top)", "isIntro":false, "enabled":true, "recurring":"start_of_match", "actions":[{"parameters":["1"], "enabled":true, "id":"ap_export"}]}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "conditions":{}, "id":"Set AI", "isIntro":false, "enabled":true, "recurring":"once", "actions":[{"parameters":["current", "aggressive"], "enabled":true, "id":"ai_set_profile"}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}, "id":"Shuffle Units", "isIntro":false, "enabled":true, "recurring":"start_of_match", "actions":[{"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "id":"$trigger_default_defeat_no_units", "isIntro":false, "enabled":true, "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "id":"$trigger_default_defeat_no_units", "isIntro":false, "enabled":true, "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "id":"$trigger_default_defeat_commander", "isIntro":false, "enabled":true, "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "id":"$trigger_default_defeat_hq", "isIntro":false, "enabled":true, "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}]}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "id":"$trigger_default_victory", "isIntro":false, "enabled":true, "recurring":"oncePerPlayer", "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current", "0", "1", "archer", "5"], "enabled":true, "id":"unit_presence"}], "id":"Station Archer on Mountain (Check 253311)", "isIntro":false, "enabled":true, "recurring":"once", "actions":[{"parameters":["253311"], "enabled":true, "id":"ap_location_send"}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["warship", "current", "hq", "P2", "-1"], "enabled":true, "id":"unit_killed"}], "id":"Warship Destroys Stronghold (Check 253312)", "isIntro":false, "enabled":true, "recurring":"once", "actions":[{"parameters":["253312"], "enabled":true, "id":"ap_location_send"}]}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "id":"P1 Wins (Check 253310)", "isIntro":false, "enabled":true, "recurring":"once", "actions":[{"parameters":["253310"], "enabled":true, "id":"ap_location_send"}]}], "Map_Tile_14_4":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":14, "y":4, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":9, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":14, "y":4, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_6_3":{"terrain":"plains"}, "Map_Tile_14_3":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":14, "y":2, "facing":3}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"archer", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.3500000238419, "movementType":"walking", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":["bow"], "weapons":[{"maxRange":3, "unitIdWhenAttacking":"", "id":"bow", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":3, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["archer", "type.ground.light"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"archer", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":17, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":14, "y":2, "facing":3}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_14_1":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":14, "y":1, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"barracks", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"barracks", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":10, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":14, "y":1, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_13_10":{"terrain":"forest"}, "Map_Tile_9_3":{"terrain":"beach"}, "Player_Count":2, "Flags":{}, "Map_Tile_13_8":{"terrain":"sea"}, "Map_Tile_15_5":{"terrain":"bridge"}, "Player_2":{"recruit_travelboat":true, "recruit_griffin_walking":false, "recruit_thief":true, "recruit_knight":true, "recruit_spearman":true, "recruit_turtle":true, "recruit_frog":true, "recruit_ballista":false, "recruit_archer":true, "team":1, "recruit_witch":false, "recruit_dragon":false, "recruit_soldier":true, "recruit_merman":true, "recruit_rifleman":true, "recruit_mage":true, "recruit_giant":false, "recruit_harpoonship":true, "recruit_caravel":true, "recruit_warship":true, "recruit_trebuchet":false, "recruit_dog":true, "recruit_kraken":true, "gold":100, "recruit_wagon":true, "recruit_balloon":true, "recruit_harpy":true}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_9_6":{"terrain":"mountain"}, "Map_Tile_13_5":{"terrain":"reef"}, "Map_Tile_13_4":{"terrain":"sea"}, "Map_Tile_1_4":{"terrain":"sea"}, "Map_Tile_13_0":{"terrain":"forest"}, "Locations":{"1":{"centre":{"y":2, "x":14}, "getArea":null, "id":1, "positions":[{"y":2, "x":13}, {"y":2, "x":14}, {"y":3, "x":14}, {"y":3, "x":15}, {"y":1, "x":13}], "setArea":null, "name":"Enemy Army Shuffle", "interactable":false}, "2":{"centre":{"y":9, "x":2}, "getArea":null, "id":2, "positions":[{"y":8, "x":2}, {"y":9, "x":2}, {"y":9, "x":3}, {"y":8, "x":1}, {"y":10, "x":3}], "setArea":null, "name":"Allied Army Shuffle", "interactable":false}, "3":{"centre":{"y":4, "x":10}, "getArea":null, "id":3, "positions":[{"y":1, "x":1}, {"y":1, "x":14}, {"y":9, "x":15}], "setArea":null, "name":"Enemy Structure Shuffle", "interactable":false}, "4":{"centre":{"y":8, "x":4}, "getArea":null, "id":4, "positions":[{"y":10, "x":8}, {"y":10, "x":7}, {"y":5, "x":0}, {"y":5, "x":1}], "setArea":null, "name":"Sea Village Shuffle", "interactable":false}, "0":{"centre":{"y":2, "x":12}, "getArea":null, "id":0, "positions":[{"y":0, "x":11}, {"y":5, "x":15}, {"y":2, "x":11}, {"y":4, "x":13}, {"y":0, "x":9}], "setArea":null, "name":"Harpy Shuffle", "interactable":false}, "5":{"centre":{"y":6, "x":9}, "getArea":null, "id":5, "positions":[{"y":6, "x":9}], "setArea":null, "name":"Mountain", "interactable":false}}, "Map_Tile_0_0":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":0, "y":0, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":11, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":0, "y":0, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_12_4":{"terrain":"sea"}, "Map_Tile_2_8":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":2, "y":8, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"dog", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.5, "movementType":"walking", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":["bite"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"bite", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":5, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":150, "canBeActivated":false, "tags":["dog", "type.ground.light", "animal"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"dog", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":20, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":2, "y":8, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_12_8":{"terrain":"reef"}, "Map_Tile_12_3":{"terrain":"sea"}, "Map_Tile_9_0":{"terrain":"sea", "unit":{"playerId":1, "startPos":{"x":9, "y":0, "facing":3}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"harpy", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.25, "movementType":"flying", "maxHealth":100, "isAttackable":true, "resourceCost":2, "verbCostMultiplier":1.0, "weaponIds":["harpyClaws"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"harpyClaws", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":true, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":6, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":600, "canBeActivated":false, "tags":["harpy", "type.air"], "inAir":true, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"harpy", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":27, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":9, "y":0, "facing":3}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_5_9":{"terrain":"bridge"}, "Map_Tile_1_6":{"terrain":"sea"}, "Map_Tile_12_0":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":12, "y":0, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":8, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":12, "y":0, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_11_7":{"terrain":"forest"}, "Map_Tile_2_3":{"terrain":"sea"}, "Map_Tile_6_0":{"terrain":"sea"}, "Map_Tile_11_3":{"terrain":"sea"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"forest"}, "Map_Tile_11_2":{"terrain":"reef"}, "Map_Tile_11_1":{"terrain":"bridge"}, "Map_Tile_4_2":{"terrain":"bridge"}, "Map_Tile_12_10":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":12, "y":10, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":24, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":12, "y":10, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_10_9":{"terrain":"sea"}, "Map_Tile_10_8":{"terrain":"sea"}, "Map_Tile_15_7":{"terrain":"plains", "unit":{"playerId":-1, "startPos":{"x":15, "y":7, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":13, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":15, "y":7, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_1_1":{"terrain":"plains", "unit":{"playerId":1, "startPos":{"x":1, "y":1, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"tower", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"tower", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":23, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":1, "y":1, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_10_7":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":9, "y":8, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"barracks", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"barracks", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":4, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":9, "y":8, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_5_5":{"terrain":"forest"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_8_1":{"terrain":"forest"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":3, "y":9, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"commander_mercia", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":false, "reinforceMultiplier":1.0, "maxGroove":250, "isCommander":true, "passiveMultiplier":1.0, "movementType":"walking", "maxHealth":100, "isAttackable":true, "resourceCost":3, "verbCostMultiplier":1.0, "weaponIds":["merciaSword"], "weapons":[{"maxRange":1, "unitIdWhenAttacking":"", "id":"merciaSword", "blockedByEnemies":false, "canCounterAttack":true, "terrainExclusion":{}, "minRange":1, "directionality":"omni", "canAttackAir":false, "horizontalAndVerticalExtraWidth":0, "canAttackSubmerged":false, "canMoveAndAttack":true, "horizontalAndVerticalOnly":false}], "critConditionId":"", "moveRange":4, "recruitingCostMultiplier":1.0, "isRecruitable":false, "cost":500, "canBeActivated":false, "tags":["commander", "type.ground.light"], "inAir":false, "aliasId":"", "canReinforce":false, "isStructure":false, "loadCapacity":0, "id":"commander_mercia", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":19, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":3, "y":9, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":"heal_aura"}}, "Map_Tile_4_10":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":4, "y":10, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":28, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":4, "y":10, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_0_4":{"terrain":"sea"}, "Map_Tile_0_8":{"terrain":"forest"}, "Map_Tile_10_2":{"terrain":"sea"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains", "unit":{"playerId":0, "startPos":{"x":2, "y":10, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"land_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":7, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":2, "y":10, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_10_1":{"terrain":"bridge"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_2_2":{"terrain":"bridge"}, "Map_Tile_10_5":{"terrain":"sea"}, "Map_Tile_10_6":{"terrain":"forest"}, "Map_Tile_2_5":{"terrain":"bridge"}, "Player_1":{"recruit_travelboat":true, "recruit_griffin_walking":true, "recruit_thief":true, "recruit_knight":true, "recruit_spearman":true, "recruit_turtle":true, "recruit_frog":true, "recruit_ballista":true, "recruit_archer":true, "team":0, "recruit_witch":true, "recruit_dragon":true, "recruit_soldier":true, "recruit_merman":true, "recruit_rifleman":true, "recruit_mage":true, "recruit_giant":true, "recruit_harpoonship":true, "recruit_caravel":true, "recruit_warship":true, "recruit_trebuchet":true, "recruit_dog":true, "recruit_kraken":true, "gold":100, "recruit_wagon":true, "recruit_balloon":true, "recruit_harpy":true}, "Map_Tile_6_6":{"terrain":"sea"}, "Map_Tile_12_2":{"terrain":"forest"}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_1_3":{"terrain":"sea"}, "Map_Tile_6_5":{"terrain":"sea"}, "Map_Tile_12_5":{"terrain":"sea"}, "Map_Tile_11_6":{"terrain":"forest"}, "Map_Tile_8_10":{"terrain":"sea", "unit":{"playerId":-1, "startPos":{"x":8, "y":10, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"water_city", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"sea_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"water_city", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":26, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":{}, "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":8, "y":10, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}, "Map_Tile_6_8":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"sea"}, "Map_Tile_5_1":{"terrain":"sea"}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_0_2":{"terrain":"forest"}, "Map_Tile_7_6":{"terrain":"forest"}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"forest"}, "Map_Tile_0_7":{"terrain":"sea"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"sea"}, "Map_Tile_8_9":{"terrain":"sea"}, "Map_Tile_3_5":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"bridge"}, "Map_Tile_3_6":{"terrain":"sea", "unit":{"playerId":0, "startPos":{"x":3, "y":6, "facing":0}, "loadedUnits":{}, "recruitDiscounts":{}, "unitClassId":"port", "items":{}, "hadTurn":false, "grooveCharge":0, "itemDropNumber":0, "hasBeenKilled":false, "factionOverride":"", "underwater":false, "setHealth":null, "tentacled":false, "attachedFlagId":-1, "merchantDiscounts":{}, "rangedDamageTakenPercent":100, "canBeAttackedFromDistance":true, "setGroove":null, "state":{}, "unitClass":{"isDamagingParentUnit":false, "canBeCaptured":true, "reinforceMultiplier":1.0, "maxGroove":0, "isCommander":false, "passiveMultiplier":1.0, "movementType":"river_sea_building", "maxHealth":100, "isAttackable":true, "resourceCost":1, "verbCostMultiplier":1.0, "weaponIds":{}, "weapons":{}, "critConditionId":"", "moveRange":0, "recruitingCostMultiplier":1.0, "isRecruitable":true, "cost":500, "canBeActivated":false, "tags":["structure"], "inAir":false, "aliasId":"", "canReinforce":true, "isStructure":true, "loadCapacity":0, "id":"port", "transportTags":{}, "inWater":false, "canAttack":true}, "stunned":false, "miniGrooveId":"", "recruitDiscountMultiplier":0.0, "blessings":{}, "canBeAttacked":true, "id":1, "itemId":"", "merchantDiscountMultiplier":0.0, "garrisonClassId":"garrison", "attackerId":-1, "canChargeGroove":true, "transportedBy":-1, "health":100, "recruits":["travelboat", "caravel", "merman", "turtle", "harpoonship", "frog", "kraken", "warship"], "attackerUnitClass":"", "inTransport":false, "attackerPlayerId":-1, "pos":{"x":3, "y":6, "facing":0}, "killedByLosing":false, "damageTakenPercent":100, "grooveId":""}}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Tenris_Mistake.json b/worlds/wargroove2/levels/Tenris_Mistake.json new file mode 100644 index 000000000000..f99f1df6bae6 --- /dev/null +++ b/worlds/wargroove2/levels/Tenris_Mistake.json @@ -0,0 +1 @@ +{"Map_Tile_7_7":{"terrain":"sea"}, "Map_Tile_2_4":{"terrain":"sea"}, "Map_Tile_13_6":{"terrain":"beach"}, "Map_Tile_2_2":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":2, "x":2}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":2, "x":2}, "killedByLosing":false, "items":{}, "id":32, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_9_8":{"terrain":"sea"}, "Map_Tile_18_6":{"terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"sea"}, "Map_Tile_6_10":{"terrain":"forest"}, "Map_Tile_17_9":{"terrain":"plains"}, "Map_Tile_4_4":{"terrain":"sea"}, "Map_Tile_5_0":{"terrain":"sea"}, "Map_Tile_10_2":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":2, "x":10}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":2, "x":10}, "killedByLosing":false, "items":{}, "id":33, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_8_5":{"terrain":"sea"}, "Map_Tile_0_10":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":10, "x":0}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":10, "x":0}, "killedByLosing":false, "items":{}, "id":31, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_14_2":{"terrain":"sea", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":3, "y":2, "x":14}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["witchSpell"], "canAttack":true, "maxGroove":0, "moveRange":7, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"witchSpell", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":true}], "canBeCaptured":false, "inAir":true, "movementType":"flying", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":2.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":750, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["witch", "type.air", "spellcaster"], "isRecruitable":true, "id":"witch", "resourceCost":3, "isCommander":false, "verbCostMultiplier":0.5, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":3, "y":2, "x":14}, "killedByLosing":false, "items":{}, "id":27, "unitClassId":"witch", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_12_6":{"terrain":"plains"}, "Map_Tile_8_10":{"terrain":"sea"}, "Map_Tile_5_7":{"terrain":"sea"}, "Map_Tile_11_7":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":3, "y":7, "x":11}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["lance"], "canAttack":true, "maxGroove":0, "moveRange":6, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"lance", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false}], "canBeCaptured":false, "inAir":false, "movementType":"riding", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":600, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["knight", "type.ground.heavy"], "isRecruitable":true, "id":"knight", "resourceCost":2, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":3, "y":7, "x":11}, "killedByLosing":false, "items":{}, "id":28, "unitClassId":"knight", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"plains"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_7_6":{"terrain":"sea"}, "Map_Tile_10_0":{"terrain":"beach"}, "Map_Tile_4_0":{"terrain":"forest"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_9_2":{"terrain":"plains"}, "Flags":{}, "Map_Tile_15_3":{"terrain":"sea"}, "Counters":{}, "Map_Tile_1_7":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"beach"}, "Map_Tile_10_8":{"terrain":"sea"}, "Triggers":[{"conditions":{}, "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"ap_export", "enabled":true, "parameters":["1"]}], "recurring":"start_of_match", "id":"Export (Always on Top)"}, {"conditions":{}, "enabled":true, "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"ai_set_profile", "enabled":true, "parameters":["current", "balanced"]}], "recurring":"once", "id":"Set AI"}, {"conditions":{}, "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "current", "2", "2", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "current", "1", "1", "1"]}, {"id":"unit_random_teleport", "enabled":true, "parameters":["*unit_structure", "current", "3", "3", "1"]}], "recurring":"start_of_match", "id":"Shuffle Units"}, {"conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "10", "*structure", "-1"]}], "enabled":true, "players":[0, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["neutral", "tenri", "The enemy has captured 10\/15 structures!", "0", ""]}], "recurring":"once", "id":"Structure Warning (Enemy)"}, {"conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "10", "*structure", "-1"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"dialogue_box_simple", "enabled":true, "parameters":["happy", "koji", "You have captured 10\/15 structures!", "0", ""]}], "recurring":"once", "id":"Structure Warning (Player)"}, {"conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "4", "15", "*structure", "-1"]}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "recurring":"once", "id":"Victory (15 Buildings Captured)"}, {"conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "0", "*unit_structure", "-1"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units"}, {"conditions":[{"id":"unit_lost", "enabled":true, "parameters":["*commander", "current", "-1"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander"}, {"conditions":[{"id":"unit_lost", "enabled":true, "parameters":["hq", "current", "-1"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"eliminate", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq"}, {"conditions":[{"id":"number_of_opponents", "enabled":true, "parameters":["current", "0", "0"]}], "enabled":true, "players":[1, 1, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"victory", "enabled":true, "parameters":["current"]}], "recurring":"oncePerPlayer", "id":"$trigger_default_victory"}, {"conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "0", "1", "*structure", "0"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253316"]}], "recurring":"once", "id":"Capture Enemy Barracks (Check 253316)"}, {"conditions":[{"id":"unit_presence", "enabled":true, "parameters":["current", "4", "1", "*commander", "4"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253317"]}], "recurring":"once", "id":"Move Commander to Enemy Landmass (Check 253317)"}, {"conditions":[{"id":"player_victorious", "enabled":true, "parameters":["current"]}], "enabled":true, "players":[1, 0, 0, 0, 0, 0, 0, 0], "isIntro":false, "actions":[{"id":"ap_location_send", "enabled":true, "parameters":["253315"]}], "recurring":"once", "id":"P1 Wins (Check 253315)"}], "Map_Tile_14_3":{"terrain":"sea"}, "Locations":{"1":{"name":"Harpy Shuffle", "positions":[{"x":10, "y":4}, {"x":9, "y":4}, {"x":10, "y":5}], "getArea":null, "setArea":null, "interactable":false, "centre":{"x":10, "y":4}, "id":1}, "2":{"name":"Allied Army Shuffle", "positions":[{"x":2, "y":8}, {"x":1, "y":9}, {"x":3, "y":9}, {"x":2, "y":10}], "getArea":null, "setArea":null, "interactable":false, "centre":{"x":2, "y":9}, "id":2}, "3":{"name":"Witch Shuffle", "positions":[{"x":14, "y":2}, {"x":15, "y":2}, {"x":14, "y":1}], "getArea":null, "setArea":null, "interactable":false, "centre":{"x":14, "y":2}, "id":3}, "4":{"name":"Enemy Landmass", "positions":[{"x":11, "y":5}, {"x":11, "y":6}, {"x":10, "y":6}, {"x":10, "y":7}, {"x":11, "y":7}, {"x":12, "y":5}, {"x":12, "y":6}, {"x":12, "y":7}, {"x":13, "y":7}, {"x":13, "y":6}, {"x":12, "y":8}, {"x":13, "y":8}, {"x":13, "y":9}, {"x":12, "y":9}, {"x":13, "y":10}, {"x":14, "y":10}, {"x":15, "y":10}, {"x":16, "y":10}, {"x":17, "y":10}, {"x":18, "y":10}, {"x":19, "y":10}, {"x":20, "y":10}, {"x":19, "y":9}, {"x":18, "y":9}, {"x":17, "y":9}, {"x":16, "y":9}, {"x":15, "y":9}, {"x":14, "y":9}, {"x":15, "y":8}, {"x":16, "y":8}, {"x":17, "y":8}, {"x":18, "y":8}, {"x":19, "y":8}, {"x":19, "y":7}, {"x":18, "y":7}, {"x":17, "y":7}, {"x":17, "y":6}, {"x":18, "y":6}, {"x":19, "y":6}, {"x":18, "y":5}, {"x":17, "y":5}, {"x":16, "y":5}, {"x":19, "y":5}, {"x":16, "y":0}, {"x":17, "y":0}, {"x":18, "y":0}, {"x":19, "y":0}, {"x":20, "y":0}, {"x":20, "y":1}, {"x":19, "y":1}, {"x":18, "y":1}, {"x":17, "y":1}, {"x":16, "y":1}, {"x":16, "y":2}, {"x":17, "y":2}, {"x":18, "y":2}, {"x":19, "y":2}, {"x":20, "y":2}, {"x":20, "y":3}, {"x":20, "y":4}, {"x":19, "y":4}, {"x":18, "y":4}, {"x":17, "y":4}, {"x":17, "y":3}, {"x":18, "y":3}, {"x":19, "y":3}, {"x":16, "y":4}, {"x":16, "y":3}, {"x":15, "y":0}, {"x":15, "y":1}, {"x":15, "y":2}], "getArea":null, "setArea":null, "interactable":false, "centre":{"x":16, "y":5}, "id":4}, "0":{"name":"Enemy Barracks", "positions":[{"x":7, "y":9}], "getArea":null, "setArea":null, "interactable":false, "centre":{"x":7, "y":9}, "id":0}}, "Map_Tile_20_10":{"terrain":"plains"}, "Map_Tile_9_5":{"terrain":"sea"}, "Map_Tile_16_3":{"terrain":"beach"}, "Map_Tile_0_4":{"terrain":"sea"}, "Map_Tile_12_9":{"terrain":"plains"}, "Map_Tile_20_8":{"terrain":"sea"}, "Map_Tile_4_2":{"terrain":"sea"}, "Map_Tile_4_5":{"terrain":"sea"}, "Map_Tile_20_7":{"terrain":"sea"}, "Map_Tile_20_6":{"terrain":"sea"}, "Map_Tile_12_3":{"terrain":"sea"}, "Map_Tile_17_0":{"terrain":"forest"}, "Map_Tile_20_5":{"terrain":"sea"}, "Map_Tile_20_4":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"plains"}, "Map_Tile_20_2":{"terrain":"forest"}, "Map_Tile_20_1":{"terrain":"plains"}, "Map_Tile_20_0":{"terrain":"plains"}, "Author":"Magnemania", "Map_Tile_12_1":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":1, "x":12}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":1, "x":12}, "killedByLosing":false, "items":{}, "id":5, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_6_6":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"sea"}, "Map_Tile_2_9":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":9, "x":2}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":false, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":3000, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"hq", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":9, "x":2}, "killedByLosing":false, "items":{}, "id":15, "unitClassId":"hq", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_3_1":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":1, "x":3}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":1, "x":3}, "killedByLosing":false, "items":{}, "id":2, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_8_9":{"terrain":"forest"}, "Map_Tile_0_0":{"terrain":"forest"}, "Map_Tile_0_5":{"terrain":"sea"}, "Map_Tile_1_5":{"terrain":"sea"}, "Map_Tile_19_8":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"beach"}, "Map_Tile_19_6":{"terrain":"plains"}, "Map_Tile_12_5":{"terrain":"beach"}, "Map_Tile_15_4":{"terrain":"sea"}, "Map_Tile_19_5":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":5, "x":19}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":5, "x":19}, "killedByLosing":false, "items":{}, "id":12, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_3_6":{"terrain":"sea"}, "Map_Tile_19_10":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"sea"}, "Map_Tile_19_3":{"terrain":"sea"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_19_2":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":2, "x":19}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":2, "x":19}, "killedByLosing":false, "items":{}, "id":19, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_19_1":{"terrain":"sea"}, "Map_Tile_5_2":{"terrain":"sea"}, "Map_Tile_14_1":{"terrain":"sea"}, "Player_2":{"recruit_witch":false, "recruit_archer":false, "recruit_knight":false, "recruit_trebuchet":false, "recruit_mage":true, "recruit_dog":false, "recruit_griffin_walking":true, "gold":100, "recruit_soldier":true, "recruit_rifleman":false, "team":1, "recruit_dragon":false, "recruit_balloon":false, "recruit_turtle":false, "recruit_travelboat":false, "recruit_harpoonship":false, "recruit_kraken":false, "recruit_harpy":true, "recruit_thief":false, "recruit_warship":false, "recruit_caravel":false, "recruit_ballista":false, "recruit_spearman":true, "recruit_merman":false, "recruit_frog":false, "recruit_giant":false, "recruit_wagon":false}, "Map_Tile_18_10":{"terrain":"plains"}, "Map_Tile_18_9":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"sea"}, "Map_Tile_4_10":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":10, "x":4}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":10, "x":4}, "killedByLosing":false, "items":{}, "id":13, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_18_8":{"terrain":"plains"}, "Map_Tile_15_5":{"terrain":"sea"}, "Map_Tile_5_8":{"terrain":"sea"}, "Map_Tile_3_3":{"terrain":"sea", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":3, "x":3}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["fireBreath"], "canAttack":true, "maxGroove":0, "moveRange":8, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"fireBreath", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false}], "canBeCaptured":false, "inAir":true, "movementType":"flying", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":1250, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["dragon", "type.air"], "isRecruitable":true, "id":"dragon", "resourceCost":3, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":3, "x":3}, "killedByLosing":false, "items":{}, "id":24, "unitClassId":"dragon", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_18_5":{"terrain":"plains"}, "Map_Tile_16_7":{"terrain":"sea"}, "Map_Tile_8_2":{"terrain":"sea"}, "Map_Tile_18_4":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":3, "x":18}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"tower", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":3, "x":18}, "killedByLosing":false, "items":{}, "id":23, "unitClassId":"tower", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_9_0":{"terrain":"sea"}, "Map_Tile_18_2":{"terrain":"plains"}, "Map_Tile_17_3":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"plains"}, "Map_Name":"Tenri's Mistake", "Map_Tile_3_8":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":8, "x":3}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"barracks", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":8, "x":3}, "killedByLosing":false, "items":{}, "id":16, "unitClassId":"barracks", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_18_0":{"terrain":"plains"}, "Map_Tile_16_0":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":3, "y":0, "x":16}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["lightning"], "canAttack":true, "maxGroove":0, "moveRange":5, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"lightning", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":true}], "canBeCaptured":false, "inAir":false, "movementType":"walking", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":400, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["mage", "type.ground.light", "spellcaster"], "isRecruitable":true, "id":"mage", "resourceCost":2, "isCommander":false, "verbCostMultiplier":0.5, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":3, "y":0, "x":16}, "killedByLosing":false, "items":{}, "id":34, "unitClassId":"mage", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_1_9":{"terrain":"plains"}, "Map_Tile_19_9":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":9, "x":19}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":9, "x":19}, "killedByLosing":false, "items":{}, "id":10, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_17_10":{"terrain":"plains"}, "Map_Tile_17_6":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":6, "x":17}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":6, "x":17}, "killedByLosing":false, "items":{}, "id":20, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_17_5":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"sea"}, "Map_Tile_2_0":{"terrain":"plains", "item":{"type":"swift_potion", "unitTypeRestriction":{}, "isConsumable":true, "pos":{"x":2, "y":0}, "itemId":37}}, "Map_Tile_7_4":{"terrain":"sea"}, "Map_Tile_17_4":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":3, "y":4, "x":17}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["spear"], "canAttack":true, "maxGroove":0, "moveRange":3, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"spear", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false}], "canBeCaptured":false, "inAir":false, "movementType":"walking", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":250, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["spearman", "type.ground.light"], "isRecruitable":true, "id":"spearman", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":3, "y":4, "x":17}, "killedByLosing":false, "items":{}, "id":35, "unitClassId":"spearman", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_18_1":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":1, "x":18}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":false, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":3000, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"hq", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":1, "x":18}, "killedByLosing":false, "items":{}, "id":18, "unitClassId":"hq", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_17_2":{"terrain":"plains"}, "Map_Tile_15_2":{"terrain":"beach"}, "Map_Tile_15_9":{"terrain":"plains"}, "Map_Tile_17_1":{"terrain":"forest"}, "Map_Tile_16_10":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":10, "x":16}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":10, "x":16}, "killedByLosing":false, "items":{}, "id":9, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_11_3":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":3, "x":11}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":3, "x":11}, "killedByLosing":false, "items":{}, "id":4, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_13_7":{"terrain":"plains"}, "Map_Tile_4_3":{"terrain":"sea"}, "Map_Tile_4_7":{"terrain":"beach"}, "Map_Tile_10_1":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":10, "x":15}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":10, "x":15}, "killedByLosing":false, "items":{}, "id":8, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_11_10":{"terrain":"sea"}, "Map_Tile_7_5":{"terrain":"sea"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_9_7":{"terrain":"sea"}, "Map_Tile_9_6":{"terrain":"sea"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_16_2":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"sea"}, "Map_Size":{"x":21, "y":11}, "Map_Tile_6_0":{"terrain":"sea"}, "Map_Tile_16_5":{"terrain":"plains"}, "Map_Tile_13_2":{"terrain":"beach"}, "Map_Tile_12_10":{"terrain":"sea"}, "Map_Tile_6_4":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":4, "x":6}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"tower", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":4, "x":6}, "killedByLosing":false, "items":{}, "id":14, "unitClassId":"tower", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_13_0":{"terrain":"plains", "item":{"type":"immunity_potion", "unitTypeRestriction":{}, "isConsumable":true, "pos":{"x":13, "y":0}, "itemId":38}}, "Map_Tile_4_6":{"terrain":"sea"}, "Map_Tile_14_4":{"terrain":"sea"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"beach"}, "Map_Tile_5_9":{"terrain":"sea"}, "Map_Tile_15_6":{"terrain":"sea"}, "Map_Tile_2_7":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":7, "x":2}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"tower", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":7, "x":2}, "killedByLosing":false, "items":{}, "id":30, "unitClassId":"tower", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_1_1":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":1, "x":1}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":1, "x":1}, "killedByLosing":false, "items":{}, "id":1, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_20_9":{"terrain":"plains"}, "Map_Tile_9_10":{"terrain":"sea"}, "Map_Tile_7_1":{"terrain":"reef"}, "Map_Tile_2_6":{"terrain":"sea", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":6, "x":2}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["witchSpell"], "canAttack":true, "maxGroove":0, "moveRange":7, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"witchSpell", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":true}], "canBeCaptured":false, "inAir":true, "movementType":"flying", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":2.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":750, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["witch", "type.air", "spellcaster"], "isRecruitable":true, "id":"witch", "resourceCost":3, "isCommander":false, "verbCostMultiplier":0.5, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":6, "x":2}, "killedByLosing":false, "items":{}, "id":21, "unitClassId":"witch", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_5_6":{"terrain":"sea"}, "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":7, "x":3}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["harpyClaws"], "canAttack":true, "maxGroove":0, "moveRange":6, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"harpyClaws", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":true}], "canBeCaptured":false, "inAir":true, "movementType":"flying", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.25, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":600, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["harpy", "type.air"], "isRecruitable":true, "id":"harpy", "resourceCost":2, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":7, "x":3}, "killedByLosing":false, "items":{}, "id":22, "unitClassId":"harpy", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_7_2":{"terrain":"sea"}, "Map_Tile_18_7":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"beach"}, "Map_Tile_8_6":{"terrain":"sea"}, "Map_Tile_1_6":{"terrain":"sea"}, "Map_Tile_19_0":{"terrain":"forest"}, "Map_Tile_11_6":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":6, "x":11}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":6, "x":11}, "killedByLosing":false, "items":{}, "id":6, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_14_7":{"terrain":"sea"}, "Map_Tile_14_8":{"terrain":"sea"}, "Map_Tile_14_9":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"sea"}, "Map_Tile_15_0":{"terrain":"beach"}, "Map_Tile_3_4":{"terrain":"sea"}, "Map_Tile_6_3":{"terrain":"beach"}, "Map_Tile_13_9":{"terrain":"plains"}, "Map_Tile_8_4":{"terrain":"sea"}, "Map_Tile_13_8":{"terrain":"beach"}, "Map_Tile_13_5":{"terrain":"sea"}, "Map_Tile_7_9":{"terrain":"plains", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":9, "x":7}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"barracks", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":9, "x":7}, "killedByLosing":false, "items":{}, "id":17, "unitClassId":"barracks", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"sea"}, "Map_Tile_13_1":{"terrain":"beach"}, "Map_Tile_12_8":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"beach"}, "Map_Tile_5_5":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":5, "x":5}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":5, "x":5}, "killedByLosing":false, "items":{}, "id":3, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_12_4":{"terrain":"sea"}, "Map_Tile_12_2":{"terrain":"plains"}, "Map_Tile_11_9":{"terrain":"sea"}, "Map_Tile_0_8":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":8, "x":0}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":8, "x":0}, "killedByLosing":false, "items":{}, "id":36, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_11_5":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"beach"}, "Map_Tile_7_0":{"terrain":"sea"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_6_7":{"terrain":"sea"}, "Map_Tile_15_7":{"terrain":"sea"}, "Map_Tile_10_10":{"terrain":"ocean"}, "Map_Tile_17_8":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":8, "x":17}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":8, "x":17}, "killedByLosing":false, "items":{}, "id":11, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"sea"}, "Map_Tile_9_4":{"terrain":"sea"}, "Map_Tile_9_1":{"terrain":"beach"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":9, "x":3}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["lightning"], "canAttack":true, "maxGroove":0, "moveRange":5, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"lightning", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":true}], "canBeCaptured":false, "inAir":false, "movementType":"walking", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":400, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["mage", "type.ground.light", "spellcaster"], "isRecruitable":true, "id":"mage", "resourceCost":2, "isCommander":false, "verbCostMultiplier":0.5, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":9, "x":3}, "killedByLosing":false, "items":{}, "id":26, "unitClassId":"mage", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_0_7":{"terrain":"sea"}, "Map_Tile_8_7":{"terrain":"sea"}, "Map_Tile_3_2":{"terrain":"beach"}, "Map_Tile_10_4":{"terrain":"sea", "unit":{"playerId":1, "merchantDiscountMultiplier":0.0, "pos":{"facing":3, "y":4, "x":10}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["harpyClaws"], "canAttack":true, "maxGroove":0, "moveRange":6, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"harpyClaws", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":true}], "canBeCaptured":false, "inAir":true, "movementType":"flying", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.25, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":600, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["harpy", "type.air"], "isRecruitable":true, "id":"harpy", "resourceCost":2, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":3, "y":4, "x":10}, "killedByLosing":false, "items":{}, "id":25, "unitClassId":"harpy", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_13_4":{"terrain":"sea"}, "Map_Tile_8_3":{"terrain":"sea"}, "Map_Tile_10_3":{"terrain":"plains"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_0_6":{"terrain":"sea"}, "Map_Tile_10_6":{"terrain":"plains"}, "Map_Tile_16_1":{"terrain":"plains"}, "Player_Count":2, "Map_Tile_5_1":{"terrain":"sea"}, "Map_Tile_9_9":{"terrain":"sea"}, "Map_Tile_12_7":{"terrain":"plains", "unit":{"playerId":-1, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":7, "x":12}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":{}, "canAttack":true, "maxGroove":0, "moveRange":0, "weapons":{}, "canBeCaptured":true, "inAir":false, "movementType":"land_building", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":true, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["structure"], "isRecruitable":true, "id":"city", "resourceCost":1, "isCommander":false, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":true}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"garrison", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"", "startPos":{"facing":0, "y":7, "x":12}, "killedByLosing":false, "items":{}, "id":7, "unitClassId":"city", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"sea"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_4_1":{"terrain":"beach"}, "Map_Tile_10_5":{"terrain":"sea"}, "Objectives":["Capture the enemy's Barracks (Requires Balloon or Air Trooper).", "Land on the enemy's main landmass with your Commander (Requires Balloon).", "Win by capturing 15 structures before the enemy (Requires Balloon or Air Trooper)."], "Map_Tile_4_9":{"terrain":"plains"}, "Map_Tile_3_5":{"terrain":"sea"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_2_10":{"terrain":"plains", "unit":{"playerId":0, "merchantDiscountMultiplier":0.0, "pos":{"facing":0, "y":10, "x":2}, "attackerId":-1, "tentacled":false, "attackerUnitClass":"", "factionOverride":"", "attackerPlayerId":-1, "unitClass":{"aliasId":"", "weaponIds":["merciaSword"], "canAttack":true, "maxGroove":250, "moveRange":4, "weapons":[{"maxRange":1, "directionality":"omni", "blockedByEnemies":false, "horizontalAndVerticalOnly":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "id":"merciaSword", "minRange":1, "canMoveAndAttack":true, "canAttackSubmerged":false, "terrainExclusion":{}, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false}], "canBeCaptured":false, "inAir":false, "movementType":"walking", "isAttackable":true, "recruitingCostMultiplier":1.0, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "inWater":false, "maxHealth":100, "canReinforce":false, "cost":500, "isDamagingParentUnit":false, "transportTags":{}, "canBeActivated":false, "tags":["commander", "type.ground.light"], "isRecruitable":false, "id":"commander_mercia", "resourceCost":3, "isCommander":true, "verbCostMultiplier":1.0, "critConditionId":"", "loadCapacity":0, "isStructure":false}, "damageTakenPercent":100, "canChargeGroove":true, "transportedBy":-1, "rangedDamageTakenPercent":100, "itemDropNumber":0, "merchantDiscounts":{}, "canBeAttackedFromDistance":true, "hadTurn":false, "hasBeenKilled":false, "health":100, "state":{}, "setGroove":null, "garrisonClassId":"", "recruitDiscountMultiplier":0.0, "grooveCharge":0, "setHealth":null, "recruits":{}, "canBeAttacked":true, "attachedFlagId":-1, "stunned":false, "loadedUnits":{}, "blessings":{}, "grooveId":"heal_aura", "startPos":{"facing":0, "y":10, "x":2}, "killedByLosing":false, "items":{}, "id":29, "unitClassId":"commander_mercia", "recruitDiscounts":{}, "inTransport":false, "underwater":false, "miniGrooveId":"", "itemId":""}}, "Map_Tile_0_2":{"terrain":"forest"}, "Map_Tile_14_0":{"terrain":"sea"}, "Map_Tile_1_4":{"terrain":"sea"}, "Map_Tile_8_1":{"terrain":"sea"}, "Player_1":{"recruit_witch":true, "recruit_archer":true, "recruit_knight":true, "recruit_trebuchet":true, "recruit_mage":true, "recruit_dog":true, "recruit_griffin_walking":true, "gold":100, "recruit_soldier":true, "recruit_rifleman":true, "team":0, "recruit_dragon":true, "recruit_balloon":true, "recruit_turtle":true, "recruit_travelboat":true, "recruit_harpoonship":true, "recruit_kraken":true, "recruit_harpy":true, "recruit_thief":true, "recruit_warship":true, "recruit_caravel":true, "recruit_ballista":true, "recruit_spearman":true, "recruit_merman":true, "recruit_frog":true, "recruit_giant":true, "recruit_wagon":true}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_7_8":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"sea"}, "Map_Tile_19_7":{"terrain":"plains"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_6_2":{"terrain":"sea"}, "Map_Tile_5_3":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Terrible_Tributaries.json b/worlds/wargroove2/levels/Terrible_Tributaries.json new file mode 100644 index 000000000000..437ed565f24d --- /dev/null +++ b/worlds/wargroove2/levels/Terrible_Tributaries.json @@ -0,0 +1 @@ +{"Map_Tile_17_1":{"terrain":"river"}, "Map_Tile_12_2":{"terrain":"river"}, "Map_Tile_11_1":{"terrain":"forest"}, "Map_Tile_16_6":{"terrain":"river"}, "Map_Tile_9_2":{"terrain":"forest"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_15_12":{"terrain":"river"}, "Map_Tile_15_0":{"terrain":"river"}, "Map_Tile_24_1":{"terrain":"river"}, "Map_Tile_12_9":{"terrain":"river"}, "Map_Tile_24_8":{"terrain":"river"}, "Map_Tile_13_4":{"terrain":"river"}, "Map_Tile_9_10":{"terrain":"river"}, "Map_Tile_9_7":{"terrain":"river"}, "Map_Tile_13_9":{"terrain":"river"}, "Map_Tile_5_5":{"terrain":"road"}, "Map_Tile_19_9":{"terrain":"river"}, "Map_Tile_17_6":{"terrain":"river"}, "Map_Tile_14_4":{"terrain":"river"}, "Map_Tile_0_6":{"terrain":"river"}, "Objectives":["Defeat the Knight with a Merfolk (Requires Merfolk and Riverboat).", "Steal from the Stronghold (Requires Thief and Riverboat).", "Win with standard conditions (Requires Riverboat)."], "Map_Tile_10_4":{"terrain":"river"}, "Map_Tile_11_10":{"terrain":"river"}, "Map_Tile_3_12":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"forest"}, "Map_Tile_4_10":{"terrain":"river"}, "Map_Tile_16_2":{"terrain":"river"}, "Map_Tile_22_12":{"terrain":"river"}, "Map_Tile_2_8":{"terrain":"forest"}, "Map_Tile_9_4":{"terrain":"river"}, "Map_Tile_8_9":{"terrain":"plains"}, "Map_Size":{"x":26, "y":13}, "Map_Tile_5_0":{"terrain":"road"}, "Map_Tile_7_11":{"terrain":"river"}, "Map_Tile_18_6":{"terrain":"river"}, "Map_Tile_0_4":{"terrain":"forest_cut"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_23_9":{"terrain":"river"}, "Map_Tile_20_6":{"terrain":"river"}, "Map_Tile_8_10":{"terrain":"river"}, "Map_Tile_13_5":{"terrain":"plains"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_19_8":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":15, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":19, "y":8}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":19, "y":8}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_5_9":{"terrain":"river"}, "Map_Tile_25_1":{"terrain":"river"}, "Map_Tile_1_3":{"terrain":"forest_cut"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_12_0":{"terrain":"plains"}, "Map_Tile_19_6":{"terrain":"river"}, "Map_Tile_23_6":{"terrain":"river"}, "Map_Tile_2_0":{"terrain":"forest_cut"}, "Map_Tile_5_10":{"terrain":"river"}, "Map_Tile_14_3":{"terrain":"forest"}, "Map_Tile_22_10":{"terrain":"river"}, "Map_Tile_15_2":{"terrain":"river"}, "Map_Tile_12_10":{"terrain":"river"}, "Map_Tile_2_5":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":7, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"dog", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["bite"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":5, "cost":150, "id":"dog", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["dog", "type.ground.light", "animal"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"bite", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":2, "y":5}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":2, "y":5}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_6_0":{"terrain":"mountain"}, "Map_Tile_19_10":{"terrain":"river"}, "Map_Tile_24_5":{"terrain":"river"}, "Map_Tile_16_0":{"terrain":"river"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_1_11":{"terrain":"river"}, "Map_Tile_3_3":{"terrain":"river"}, "Map_Tile_1_1":{"terrain":"forest"}, "Map_Tile_21_8":{"terrain":"river"}, "Map_Tile_7_9":{"terrain":"river"}, "Map_Tile_3_7":{"terrain":"river"}, "Map_Tile_7_8":{"terrain":"forest"}, "Map_Tile_24_9":{"terrain":"river"}, "Map_Tile_9_9":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":3, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":9, "y":9}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":9, "y":9}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_17_12":{"terrain":"river"}, "Map_Tile_11_2":{"terrain":"plains"}, "Map_Tile_25_7":{"terrain":"river"}, "Map_Tile_2_1":{"terrain":"forest_cut"}, "Map_Tile_22_1":{"terrain":"river"}, "Map_Tile_6_3":{"terrain":"river"}, "Map_Tile_1_6":{"terrain":"river"}, "Author":"Fly Sniper", "Map_Tile_6_9":{"terrain":"bridge"}, "Map_Tile_24_11":{"terrain":"river"}, "Map_Tile_13_8":{"terrain":"river"}, "Map_Tile_20_2":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"river"}, "Map_Tile_25_11":{"terrain":"river"}, "Map_Tile_1_5":{"terrain":"river"}, "Map_Tile_4_12":{"terrain":"plains"}, "Map_Tile_23_5":{"terrain":"river"}, "Map_Tile_3_0":{"terrain":"river"}, "Map_Tile_25_3":{"terrain":"river"}, "Map_Tile_7_2":{"terrain":"river"}, "Map_Tile_22_3":{"terrain":"river"}, "Map_Tile_15_5":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":11, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":15, "y":5}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":15, "y":5}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":21, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":13, "y":0}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":13, "y":0}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_19_5":{"terrain":"river"}, "Map_Tile_7_12":{"terrain":"river"}, "Map_Tile_3_2":{"terrain":"river"}, "Map_Tile_3_11":{"terrain":"river"}, "Map_Tile_19_7":{"terrain":"river"}, "Map_Tile_13_6":{"terrain":"river"}, "Map_Tile_4_6":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":2, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":4, "y":6}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":4, "y":6}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_6_12":{"terrain":"forest"}, "Map_Tile_24_12":{"terrain":"river"}, "Map_Tile_19_2":{"terrain":"plains"}, "Map_Tile_23_7":{"terrain":"river"}, "Map_Tile_15_11":{"terrain":"river"}, "Map_Tile_17_5":{"terrain":"river"}, "Player_1":{"recruit_dog":true, "recruit_thief":true, "gold":700, "recruit_frog":true, "recruit_harpoonship":true, "team":0, "recruit_merman":true, "recruit_caravel":true, "recruit_dragon":true, "recruit_giant":true, "recruit_soldier":true, "recruit_turtle":true, "recruit_warship":true, "recruit_spearman":true, "recruit_balloon":true, "recruit_knight":true, "recruit_ballista":true, "recruit_witch":true, "recruit_harpy":true, "recruit_archer":true, "recruit_trebuchet":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_kraken":true, "recruit_travelboat":true, "recruit_mage":true, "recruit_wagon":true}, "Map_Tile_18_1":{"terrain":"river"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"river"}, "Map_Tile_5_3":{"terrain":"road"}, "Map_Tile_5_1":{"terrain":"road"}, "Map_Tile_14_5":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":20, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":14, "y":5}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":14, "y":5}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_15_7":{"terrain":"river"}, "Map_Tile_15_3":{"terrain":"plains"}, "Map_Tile_9_8":{"terrain":"river"}, "Map_Tile_23_3":{"terrain":"river"}, "Map_Tile_1_0":{"terrain":"forest"}, "Map_Tile_21_10":{"terrain":"river"}, "Map_Tile_4_4":{"terrain":"river"}, "Map_Tile_15_4":{"terrain":"river"}, "Map_Tile_4_9":{"terrain":"forest"}, "Map_Tile_24_4":{"terrain":"river"}, "Map_Tile_9_0":{"terrain":"river"}, "Map_Tile_18_8":{"terrain":"plains"}, "Map_Tile_9_3":{"terrain":"plains"}, "Map_Tile_2_6":{"terrain":"plains"}, "Map_Tile_10_10":{"terrain":"river"}, "Map_Tile_9_1":{"terrain":"river"}, "Map_Tile_5_11":{"terrain":"road"}, "Map_Tile_3_6":{"terrain":"river"}, "Map_Tile_13_1":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":12, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":13, "y":1}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":13, "y":1}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_0_1":{"terrain":"forest"}, "Map_Tile_19_1":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"river"}, "Map_Tile_1_10":{"terrain":"river"}, "Map_Tile_24_10":{"terrain":"river"}, "Map_Tile_13_2":{"terrain":"river"}, "Map_Tile_12_4":{"terrain":"river"}, "Map_Tile_4_5":{"terrain":"river"}, "Map_Tile_23_2":{"terrain":"river"}, "Map_Tile_22_6":{"terrain":"river"}, "Map_Tile_12_12":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"plains"}, "Map_Tile_11_7":{"terrain":"river"}, "Map_Tile_22_11":{"terrain":"forest"}, "Counters":{}, "Map_Tile_8_5":{"terrain":"river"}, "Map_Tile_6_1":{"terrain":"plains"}, "Map_Tile_18_4":{"terrain":"river"}, "Map_Tile_10_3":{"terrain":"river"}, "Map_Tile_10_2":{"terrain":"river"}, "Map_Tile_10_12":{"terrain":"river"}, "Map_Tile_24_6":{"terrain":"river"}, "Map_Tile_13_12":{"terrain":"river"}, "Map_Tile_14_2":{"terrain":"river"}, "Map_Tile_20_5":{"terrain":"river"}, "Flags":{}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_14_8":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":10, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":14, "y":8}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":14, "y":8}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_16_4":{"terrain":"river"}, "Map_Tile_10_1":{"terrain":"river"}, "Map_Tile_0_2":{"terrain":"forest_cut"}, "Map_Tile_3_9":{"terrain":"river"}, "Map_Tile_23_1":{"terrain":"river"}, "Map_Tile_18_2":{"terrain":"river"}, "Map_Tile_17_11":{"terrain":"river"}, "Map_Tile_2_3":{"terrain":"forest_cut"}, "Map_Tile_10_5":{"terrain":"river"}, "Map_Tile_10_11":{"terrain":"river"}, "Map_Tile_4_2":{"terrain":"forest"}, "Map_Tile_16_7":{"terrain":"river"}, "Map_Tile_11_5":{"terrain":"river"}, "Map_Tile_17_9":{"terrain":"river"}, "Map_Tile_16_12":{"terrain":"river"}, "Map_Tile_6_5":{"terrain":"road"}, "Map_Tile_22_9":{"terrain":"river"}, "Triggers":[{"actions":[{"enabled":true, "id":"ap_export", "parameters":["4215", "Terrible Tributaries", "Fly Sniper", "", "Defeat the Knight with a Merfolk (Requires Merfolk and Riverboat).", "Steal from the Stronghold (Requires Thief and Riverboat).", "Win with standard conditions (Requires Riverboat)."]}], "recurring":"start_of_match", "isIntro":false, "enabled":true, "id":"Export", "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":{}}, {"actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_no_units", "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"]}]}, {"actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_commander", "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["*commander", "current", "-1"]}]}, {"actions":[{"enabled":true, "id":"eliminate", "parameters":["current"]}], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true, "id":"$trigger_default_defeat_hq", "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_lost", "parameters":["hq", "current", "-1"]}]}, {"actions":[{"enabled":true, "id":"victory", "parameters":["current"]}], "recurring":"oncePerPlayer", "isIntro":false, "enabled":true, "id":"$trigger_default_victory", "players":[1, 1, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"number_of_opponents", "parameters":["current", "0", "0"]}]}, {"actions":[{"enabled":true, "id":"unit_random_teleport", "parameters":["*unit_structure", "P2", "0", "0", "1"]}, {"enabled":true, "id":"unit_random_teleport", "parameters":["*unit_structure", "P2", "1", "1", "1"]}, {"enabled":true, "id":"unit_random_teleport", "parameters":["*commander", "P1", "2", "2", "1"]}], "recurring":"start_of_match", "isIntro":false, "enabled":true, "id":"TP", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":{}}, {"actions":[{"enabled":true, "id":"ap_spawn_unit", "parameters":["caravel", "2", "current", "0", "1", "7", "1", "undefined", "centre"]}, {"enabled":true, "id":"dialogue_box_simple", "parameters":["happy", "sweetcheeks", "Riverboats at your service!", "1", "\"Fish\""]}], "recurring":"start_of_match", "isIntro":false, "enabled":true, "id":"Spawn Riverboats", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"ap_has_item", "parameters":["252014", "0", "1"]}]}, {"actions":[{"enabled":true, "id":"ap_spawn_unit", "parameters":["merman", "2", "current", "0", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"unit_random_teleport", "parameters":["*commander", "P1", "2", "2", "1"]}, {"enabled":true, "id":"dialogue_box_simple", "parameters":["happy", "sourcheeks", "Is this where we're meeting to play Code Names?", "1", "Fish"]}], "recurring":"start_of_match", "isIntro":false, "enabled":true, "id":"Spawn 1 Merfolk", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"ap_has_item", "parameters":["252015", "0", "1"]}]}, {"actions":[{"enabled":true, "id":"ap_spawn_unit", "parameters":["thief", "2", "current", "0", "1", "1", "1", "undefined", "centre"]}, {"enabled":true, "id":"dialogue_box_simple", "parameters":["sad", "janjak", "Steal from the riverfolk they said, it would be easy they said.", "1", "\"Good\" Thief"]}, {"enabled":true, "id":"dialogue_box_simple", "parameters":["sad", "janjak", "Don't they know I'm alergic to Code Names?", "1", "\"Good\" Thief"]}], "recurring":"start_of_match", "isIntro":false, "enabled":true, "id":"Spawn 1 Thief", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"ap_has_item", "parameters":["252021", "0", "1"]}]}, {"actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253015"]}], "recurring":"once", "isIntro":false, "enabled":true, "id":"Player Victory (253015)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"player_victorious", "parameters":["P1"]}]}, {"actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253016"]}, {"enabled":true, "id":"dialogue_box_simple", "parameters":["extra3", "sourcheeks", "I said travel 3! Travel 3! The fish was the assassin!", "1", "Fish"]}], "recurring":"once", "isIntro":false, "enabled":true, "id":"Defeat Knight with Merfolk (253016)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_killed", "parameters":["merman", "P1", "knight", "P2", "-1"]}]}, {"actions":[{"enabled":true, "id":"ap_location_send", "parameters":["253017"]}, {"enabled":true, "id":"dialogue_box_simple", "parameters":["neutral", "janjak", "Swim around all you want, the box of Code Names is mine now.", "1", "\"Good\" Thief"]}], "recurring":"once", "isIntro":false, "enabled":true, "id":"Thief Steals (253017)", "players":[1, 0, 0, 0, 0, 0, 0, 0], "conditions":[{"enabled":true, "id":"unit_presence", "parameters":["current", "0", "1", "thief_with_gold", "3"]}]}], "Map_Tile_9_12":{"terrain":"river"}, "Map_Tile_25_12":{"terrain":"river"}, "Map_Tile_25_10":{"terrain":"river"}, "Map_Tile_13_11":{"terrain":"river"}, "Map_Tile_15_10":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":22, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":15, "y":10}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":15, "y":10}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_1_7":{"terrain":"river"}, "Map_Tile_11_3":{"terrain":"river"}, "Map_Tile_25_8":{"terrain":"river"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_16_8":{"terrain":"river"}, "Locations":{"1":{"interactable":false, "name":"Other Units TP", "getArea":null, "centre":{"x":5, "y":7}, "positions":[{"x":0, "y":10}, {"x":3, "y":8}, {"x":2, "y":5}, {"x":3, "y":5}, {"x":4, "y":5}, {"x":5, "y":5}, {"x":6, "y":5}, {"x":6, "y":6}, {"x":6, "y":7}, {"x":5, "y":7}, {"x":5, "y":8}, {"x":6, "y":8}, {"x":5, "y":6}, {"x":4, "y":6}, {"x":7, "y":5}, {"x":7, "y":6}, {"x":7, "y":7}, {"x":7, "y":8}, {"x":8, "y":8}, {"x":8, "y":9}, {"x":9, "y":9}, {"x":9, "y":8}, {"x":9, "y":11}, {"x":8, "y":11}, {"x":8, "y":10}, {"x":9, "y":10}, {"x":7, "y":10}, {"x":7, "y":9}, {"x":6, "y":9}, {"x":5, "y":9}, {"x":5, "y":10}, {"x":6, "y":10}, {"x":4, "y":10}, {"x":3, "y":10}, {"x":3, "y":11}, {"x":2, "y":11}, {"x":1, "y":11}, {"x":1, "y":10}, {"x":1, "y":5}, {"x":2, "y":6}, {"x":2, "y":7}, {"x":2, "y":8}, {"x":2, "y":9}, {"x":2, "y":10}, {"x":3, "y":9}, {"x":3, "y":7}, {"x":4, "y":7}, {"x":4, "y":8}, {"x":4, "y":9}, {"x":3, "y":6}, {"x":0, "y":4}, {"x":1, "y":4}, {"x":2, "y":4}, {"x":3, "y":4}, {"x":4, "y":4}, {"x":5, "y":4}, {"x":6, "y":4}, {"x":7, "y":4}, {"x":8, "y":4}, {"x":8, "y":5}, {"x":8, "y":6}, {"x":8, "y":7}, {"x":9, "y":7}], "setArea":null, "id":1}, "2":{"interactable":false, "name":"Riverboat spawn", "getArea":null, "centre":{"x":24, "y":6}, "positions":[{"x":24, "y":12}, {"x":24, "y":11}, {"x":24, "y":10}, {"x":24, "y":9}, {"x":24, "y":8}, {"x":24, "y":7}, {"x":24, "y":6}, {"x":24, "y":5}, {"x":24, "y":4}, {"x":24, "y":3}, {"x":24, "y":2}, {"x":24, "y":1}, {"x":24, "y":0}, {"x":25, "y":0}, {"x":25, "y":12}, {"x":25, "y":11}, {"x":25, "y":10}, {"x":25, "y":9}, {"x":25, "y":8}, {"x":25, "y":7}, {"x":25, "y":6}, {"x":25, "y":5}, {"x":25, "y":4}, {"x":25, "y":3}, {"x":25, "y":2}, {"x":25, "y":1}], "setArea":null, "id":2}, "3":{"interactable":false, "name":"Thief Steal Location", "getArea":null, "centre":{"x":9, "y":6}, "positions":[{"x":9, "y":7}, {"x":8, "y":6}, {"x":9, "y":5}, {"x":10, "y":6}], "setArea":null, "id":3}, "0":{"interactable":false, "name":"Soldier TP", "getArea":null, "centre":{"x":17, "y":5}, "positions":[{"x":19, "y":8}, {"x":18, "y":8}, {"x":17, "y":8}, {"x":16, "y":8}, {"x":14, "y":8}, {"x":14, "y":7}, {"x":15, "y":7}, {"x":16, "y":7}, {"x":17, "y":7}, {"x":18, "y":7}, {"x":15, "y":8}, {"x":19, "y":7}, {"x":20, "y":7}, {"x":21, "y":7}, {"x":22, "y":7}, {"x":23, "y":7}, {"x":23, "y":8}, {"x":22, "y":8}, {"x":21, "y":8}, {"x":20, "y":8}, {"x":20, "y":11}, {"x":19, "y":11}, {"x":19, "y":10}, {"x":20, "y":10}, {"x":21, "y":10}, {"x":22, "y":10}, {"x":23, "y":10}, {"x":17, "y":10}, {"x":16, "y":10}, {"x":15, "y":10}, {"x":14, "y":10}, {"x":13, "y":8}, {"x":12, "y":8}, {"x":11, "y":8}, {"x":11, "y":9}, {"x":12, "y":9}, {"x":12, "y":6}, {"x":11, "y":6}, {"x":11, "y":5}, {"x":12, "y":5}, {"x":13, "y":5}, {"x":14, "y":5}, {"x":15, "y":5}, {"x":21, "y":4}, {"x":20, "y":4}, {"x":19, "y":4}, {"x":18, "y":4}, {"x":17, "y":4}, {"x":17, "y":5}, {"x":16, "y":5}, {"x":18, "y":5}, {"x":19, "y":5}, {"x":20, "y":5}, {"x":21, "y":5}, {"x":22, "y":5}, {"x":23, "y":5}, {"x":23, "y":4}, {"x":22, "y":4}, {"x":23, "y":2}, {"x":23, "y":1}, {"x":22, "y":1}, {"x":22, "y":0}, {"x":21, "y":0}, {"x":20, "y":0}, {"x":19, "y":0}, {"x":18, "y":0}, {"x":17, "y":0}, {"x":16, "y":0}, {"x":15, "y":0}, {"x":15, "y":1}, {"x":16, "y":1}, {"x":17, "y":1}, {"x":18, "y":1}, {"x":19, "y":1}, {"x":20, "y":1}, {"x":21, "y":1}, {"x":23, "y":0}, {"x":22, "y":2}, {"x":21, "y":2}, {"x":20, "y":2}, {"x":19, "y":2}, {"x":18, "y":2}, {"x":17, "y":2}, {"x":10, "y":6}, {"x":10, "y":5}, {"x":9, "y":5}, {"x":15, "y":3}, {"x":14, "y":3}, {"x":13, "y":3}, {"x":13, "y":1}, {"x":12, "y":1}, {"x":11, "y":2}, {"x":10, "y":2}, {"x":9, "y":2}, {"x":9, "y":3}, {"x":13, "y":0}, {"x":11, "y":1}], "setArea":null, "id":0}}, "Map_Tile_25_4":{"terrain":"river"}, "Map_Tile_25_2":{"terrain":"river"}, "Map_Tile_22_2":{"terrain":"river"}, "Map_Tile_18_0":{"terrain":"river"}, "Map_Tile_14_7":{"terrain":"river"}, "Map_Tile_9_6":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":18, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"hq", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":false, "moveRange":0, "cost":3000, "id":"hq", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":9, "y":6}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":9, "y":6}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_24_7":{"terrain":"river"}, "Map_Tile_24_3":{"terrain":"river"}, "Map_Tile_8_2":{"terrain":"river"}, "Map_Tile_14_12":{"terrain":"river"}, "Map_Tile_18_3":{"terrain":"river"}, "Map_Tile_23_12":{"terrain":"river"}, "Map_Tile_23_11":{"terrain":"river"}, "Map_Tile_15_9":{"terrain":"river"}, "Map_Tile_21_6":{"terrain":"river"}, "Map_Tile_23_10":{"terrain":"river"}, "Map_Tile_2_7":{"terrain":"river"}, "Map_Tile_1_2":{"terrain":"forest_cut"}, "Map_Tile_18_7":{"terrain":"river"}, "Map_Tile_23_4":{"terrain":"river"}, "Map_Tile_9_5":{"terrain":"river"}, "Map_Tile_10_7":{"terrain":"river"}, "Map_Tile_2_4":{"terrain":"river"}, "Player_Count":2, "Map_Tile_17_8":{"terrain":"river"}, "Map_Tile_22_8":{"terrain":"river"}, "Map_Tile_22_7":{"terrain":"plains"}, "Map_Tile_22_5":{"terrain":"river"}, "Map_Name":"Terrible Tributaries", "Map_Tile_12_8":{"terrain":"river"}, "Map_Tile_5_7":{"terrain":"river", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":19, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"merman", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":2.0, "reinforceMultiplier":1.0, "resourceCost":2, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["bident"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"amphibious", "canReinforce":false, "moveRange":5, "cost":350, "id":"merman", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["merman", "type.amphibious.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"bident", "canAttackSubmerged":false, "directionality":"omni", "maxRange":2, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":5, "y":7}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":5, "y":7}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_22_0":{"terrain":"river"}, "Map_Tile_13_7":{"terrain":"river"}, "Map_Tile_1_4":{"terrain":"river"}, "Map_Tile_1_9":{"terrain":"river"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_21_11":{"terrain":"plains"}, "Map_Tile_21_9":{"terrain":"river"}, "Map_Tile_21_7":{"terrain":"plains"}, "Map_Tile_6_8":{"terrain":"road", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":8, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"spearman", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["spear"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":3, "cost":250, "id":"spearman", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["spearman", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"spear", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":6, "y":8}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":6, "y":8}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_16_5":{"terrain":"river"}, "Map_Tile_21_5":{"terrain":"river"}, "Map_Tile_21_4":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":16, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":21, "y":4}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":21, "y":4}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_21_3":{"terrain":"river"}, "Map_Tile_21_2":{"terrain":"river"}, "Map_Tile_3_4":{"terrain":"river"}, "Player_2":{"recruit_dog":true, "recruit_thief":true, "gold":100, "recruit_frog":true, "recruit_harpoonship":true, "team":1, "recruit_merman":true, "recruit_caravel":true, "recruit_dragon":true, "recruit_giant":true, "recruit_soldier":true, "recruit_turtle":true, "recruit_warship":true, "recruit_spearman":true, "recruit_balloon":true, "recruit_knight":true, "recruit_ballista":true, "recruit_witch":true, "recruit_harpy":true, "recruit_archer":true, "recruit_trebuchet":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "recruit_kraken":true, "recruit_travelboat":true, "recruit_mage":true, "recruit_wagon":true}, "Map_Tile_6_6":{"terrain":"road"}, "Map_Tile_10_9":{"terrain":"river"}, "Map_Tile_21_1":{"terrain":"river"}, "Map_Tile_7_5":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":1, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":7, "y":5}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":7, "y":5}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_20_12":{"terrain":"river"}, "Map_Tile_20_11":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":14, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":20, "y":11}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":20, "y":11}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_18_9":{"terrain":"river"}, "Map_Tile_20_9":{"terrain":"river"}, "Map_Tile_20_8":{"terrain":"river"}, "Map_Tile_12_5":{"terrain":"forest"}, "Map_Tile_20_7":{"terrain":"river"}, "Map_Tile_20_4":{"terrain":"forest"}, "Map_Tile_8_3":{"terrain":"river"}, "Map_Tile_20_3":{"terrain":"river"}, "Map_Tile_16_3":{"terrain":"river"}, "Map_Tile_19_11":{"terrain":"plains"}, "Map_Tile_20_0":{"terrain":"river"}, "Map_Tile_19_12":{"terrain":"river"}, "Map_Tile_3_8":{"terrain":"plains", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":6, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"dog", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["bite"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":5, "cost":150, "id":"dog", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["dog", "type.ground.light", "animal"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"bite", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":3, "y":8}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":3, "y":8}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_20_1":{"terrain":"river"}, "Map_Tile_7_10":{"terrain":"river"}, "Map_Tile_19_4":{"terrain":"plains"}, "Map_Tile_19_3":{"terrain":"river"}, "Map_Tile_8_1":{"terrain":"river"}, "Map_Tile_14_1":{"terrain":"river"}, "Map_Tile_19_0":{"terrain":"river", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":13, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"soldier", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["sword"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":100, "id":"soldier", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["soldier", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"sword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":19, "y":0}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":19, "y":0}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_6_2":{"terrain":"river"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_18_12":{"terrain":"river"}, "Map_Tile_18_10":{"terrain":"river"}, "Map_Tile_20_10":{"terrain":"river"}, "Map_Tile_0_3":{"terrain":"forest_cut"}, "Map_Tile_16_1":{"terrain":"plains"}, "Map_Tile_5_8":{"terrain":"river"}, "Map_Tile_1_8":{"terrain":"river"}, "Map_Tile_17_4":{"terrain":"river"}, "Map_Tile_14_9":{"terrain":"river"}, "Map_Tile_10_6":{"terrain":"plains", "unit":{"playerId":-1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":4, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"city", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":{}, "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"land_building", "canReinforce":true, "moveRange":0, "cost":500, "id":"city", "isStructure":true, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":true, "inAir":false, "canBeActivated":false, "tags":["structure"], "aliasId":"", "weapons":{}}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":10, "y":6}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"garrison", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":10, "y":6}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_18_5":{"terrain":"river"}, "Map_Tile_2_12":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"river"}, "Map_Tile_24_0":{"terrain":"river"}, "Map_Tile_17_7":{"terrain":"river"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_16_10":{"terrain":"forest"}, "Map_Tile_11_8":{"terrain":"plains"}, "Map_Tile_14_6":{"terrain":"river"}, "Map_Tile_5_2":{"terrain":"road"}, "Map_Tile_5_12":{"terrain":"road"}, "Map_Tile_23_0":{"terrain":"river"}, "Map_Tile_5_4":{"terrain":"bridge"}, "Map_Tile_25_0":{"terrain":"river"}, "Map_Tile_7_0":{"terrain":"mountain"}, "Map_Tile_17_10":{"terrain":"river"}, "Map_Tile_10_8":{"terrain":"river"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_4_7":{"terrain":"river"}, "Map_Tile_10_0":{"terrain":"river"}, "Map_Tile_8_8":{"terrain":"river"}, "Map_Tile_17_0":{"terrain":"river"}, "Map_Tile_21_12":{"terrain":"river"}, "Map_Tile_6_11":{"terrain":"road"}, "Map_Tile_8_12":{"terrain":"river"}, "Map_Tile_12_1":{"terrain":"forest"}, "Map_Tile_11_0":{"terrain":"plains"}, "Map_Tile_3_1":{"terrain":"river"}, "Map_Tile_8_4":{"terrain":"river"}, "Map_Tile_3_5":{"terrain":"river"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_0_7":{"terrain":"river"}, "Map_Tile_15_6":{"terrain":"river"}, "Map_Tile_8_7":{"terrain":"river"}, "Map_Tile_21_0":{"terrain":"river"}, "Map_Tile_1_12":{"terrain":"plains"}, "Map_Tile_16_9":{"terrain":"river"}, "Map_Tile_2_11":{"terrain":"river"}, "Map_Tile_25_6":{"terrain":"river", "unit":{"playerId":0, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":17, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"commander_mercia", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.0, "reinforceMultiplier":1.0, "resourceCost":3, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":true, "weaponIds":["merciaSword"], "isRecruitable":false, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":4, "cost":500, "id":"commander_mercia", "isStructure":false, "transportTags":{}, "maxGroove":250, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["commander", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"merciaSword", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":3, "x":25, "y":6}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"heal_aura", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":3, "x":25, "y":6}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_11_4":{"terrain":"river"}, "Map_Tile_23_8":{"terrain":"river"}, "Map_Tile_25_9":{"terrain":"river"}, "Map_Tile_2_2":{"terrain":"forest"}, "Map_Tile_12_6":{"terrain":"river"}, "Map_Tile_17_2":{"terrain":"river"}, "Map_Tile_17_3":{"terrain":"river"}, "Map_Tile_25_5":{"terrain":"river"}, "Map_Tile_6_7":{"terrain":"bridge", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":9, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"spearman", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":1, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["spear"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"walking", "canReinforce":false, "moveRange":3, "cost":250, "id":"spearman", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["spearman", "type.ground.light"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"spear", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":6, "y":7}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":6, "y":7}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_0_0":{"terrain":"forest", "unit":{"playerId":1, "hasBeenKilled":false, "itemDropNumber":0, "attackerPlayerId":-1, "recruits":{}, "canBeAttacked":true, "blessings":{}, "canChargeGroove":true, "rangedDamageTakenPercent":100, "items":{}, "attackerUnitClass":"", "id":5, "stunned":false, "health":100, "hadTurn":false, "miniGrooveId":"", "factionOverride":"", "killedByLosing":false, "setHealth":null, "loadedUnits":{}, "tentacled":false, "recruitDiscounts":{}, "state":{}, "damageTakenPercent":100, "unitClassId":"knight", "attachedFlagId":-1, "unitClass":{"inWater":false, "passiveMultiplier":1.5, "reinforceMultiplier":1.0, "resourceCost":2, "loadCapacity":0, "canAttack":true, "maxHealth":100, "verbCostMultiplier":1.0, "isCommander":false, "weaponIds":["lance"], "isRecruitable":true, "isDamagingParentUnit":false, "isAttackable":true, "critConditionId":"", "movementType":"riding", "canReinforce":false, "moveRange":6, "cost":600, "id":"knight", "isStructure":false, "transportTags":{}, "maxGroove":0, "recruitingCostMultiplier":1.0, "canBeCaptured":false, "inAir":false, "canBeActivated":false, "tags":["knight", "type.ground.heavy"], "aliasId":"", "weapons":[{"horizontalAndVerticalOnly":false, "canCounterAttack":true, "minRange":1, "terrainExclusion":{}, "id":"lance", "canAttackSubmerged":false, "directionality":"omni", "maxRange":1, "blockedByEnemies":false, "horizontalAndVerticalExtraWidth":0, "canAttackAir":false, "unitIdWhenAttacking":"", "canMoveAndAttack":true}]}, "underwater":false, "attackerId":-1, "transportedBy":-1, "itemId":"", "pos":{"facing":0, "x":0, "y":0}, "recruitDiscountMultiplier":0.0, "inTransport":false, "garrisonClassId":"", "grooveId":"", "merchantDiscounts":{}, "merchantDiscountMultiplier":0.0, "startPos":{"facing":0, "x":0, "y":0}, "grooveCharge":0, "canBeAttackedFromDistance":true, "setGroove":null}}, "Map_Tile_15_1":{"terrain":"river"}, "Map_Tile_18_11":{"terrain":"river"}, "Map_Tile_14_11":{"terrain":"river"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"river"}, "Map_Tile_7_7":{"terrain":"river"}, "Map_Tile_24_2":{"terrain":"river"}, "Map_Tile_14_0":{"terrain":"river"}, "Map_Tile_0_8":{"terrain":"river"}, "Map_Tile_13_10":{"terrain":"river"}, "Map_Tile_12_7":{"terrain":"river"}, "Map_Tile_12_3":{"terrain":"river"}, "Map_Tile_2_9":{"terrain":"river"}, "Map_Tile_11_12":{"terrain":"forest"}, "Map_Tile_13_3":{"terrain":"plains"}, "Map_Tile_6_10":{"terrain":"road"}, "Map_Tile_8_0":{"terrain":"mountain"}, "Map_Tile_11_9":{"terrain":"forest"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_3_10":{"terrain":"river"}, "Map_Tile_22_4":{"terrain":"plains"}, "Map_Tile_4_8":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Towers_of_the_Abyss.json b/worlds/wargroove2/levels/Towers_of_the_Abyss.json new file mode 100644 index 000000000000..cb2c39b97c7e --- /dev/null +++ b/worlds/wargroove2/levels/Towers_of_the_Abyss.json @@ -0,0 +1 @@ +{"Map_Tile_19_3":{"terrain":"abyss_bridge"}, "Map_Tile_7_2":{"terrain":"abyss_bridge"}, "Map_Tile_9_1":{"terrain":"abyss"}, "Map_Tile_20_8":{"terrain":"road"}, "Map_Tile_19_1":{"terrain":"abyss"}, "Map_Tile_21_0":{"terrain":"abyss"}, "Map_Tile_9_2":{"terrain":"abyss"}, "Map_Tile_10_7":{"terrain":"abyss_bridge"}, "Map_Tile_26_8":{"terrain":"abyss"}, "Map_Tile_18_11":{"terrain":"abyss"}, "Map_Tile_23_5":{"terrain":"forest_cut"}, "Map_Tile_3_4":{"terrain":"abyss"}, "Map_Tile_9_4":{"terrain":"abyss"}, "Map_Tile_17_1":{"terrain":"abyss"}, "Map_Tile_10_3":{"terrain":"abyss"}, "Map_Tile_4_0":{"terrain":"abyss"}, "Map_Tile_13_13":{"terrain":"abyss"}, "Map_Tile_9_14":{"terrain":"abyss"}, "Map_Tile_19_8":{"unit":{"attackerPlayerId":-1, "id":32, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"fortified_garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":19, "y":8, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"fortified_city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":1000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":19, "y":8, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"fortified_city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_17_4":{"terrain":"abyss"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_14_11":{"unit":{"attackerPlayerId":-1, "id":35, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":14, "y":11, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "aliasId":"", "movementType":"walking", "id":"soldier", "weaponIds":["sword"], "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":100, "isAttackable":true, "critConditionId":"", "moveRange":4, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"sword", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":14, "y":11, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"soldier", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss_bridge"}, "Map_Tile_2_15":{"terrain":"abyss"}, "Map_Tile_24_5":{"terrain":"abyss"}, "Map_Tile_10_17":{"terrain":"plains"}, "Map_Tile_19_9":{"terrain":"abyss"}, "Map_Tile_26_0":{"unit":{"attackerPlayerId":-1, "id":14, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":26, "y":0, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.25, "aliasId":"", "movementType":"flying", "id":"harpy", "weaponIds":["harpyClaws"], "isDamagingParentUnit":false, "tags":["harpy", "type.air"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":600, "isAttackable":true, "critConditionId":"", "moveRange":6, "isCommander":false, "inAir":true, "resourceCost":2, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "directionality":"omni", "id":"harpyClaws", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":26, "y":0, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"harpy", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss"}, "Map_Tile_25_5":{"terrain":"abyss"}, "Map_Tile_18_13":{"terrain":"abyss"}, "Map_Tile_5_8":{"terrain":"river"}, "Map_Tile_18_15":{"terrain":"abyss"}, "Map_Tile_22_15":{"unit":{"attackerPlayerId":-1, "id":41, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":22, "y":15, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"barracks", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":22, "y":15, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"barracks", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_0_6":{"terrain":"abyss"}, "Map_Tile_20_15":{"unit":{"attackerPlayerId":-1, "id":43, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":20, "y":15, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":20, "y":15, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_13_19":{"terrain":"abyss"}, "Map_Tile_6_8":{"terrain":"river"}, "Map_Tile_14_19":{"terrain":"abyss"}, "Map_Tile_20_16":{"terrain":"plains"}, "Map_Tile_19_6":{"unit":{"attackerPlayerId":-1, "id":8, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":19, "y":6, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":19, "y":6, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_21_9":{"terrain":"road"}, "Map_Tile_17_10":{"terrain":"abyss"}, "Map_Tile_0_8":{"terrain":"abyss"}, "Map_Tile_3_18":{"terrain":"abyss"}, "Map_Tile_6_16":{"terrain":"road"}, "Map_Tile_9_7":{"terrain":"road"}, "Map_Tile_16_18":{"terrain":"forest"}, "Map_Tile_9_5":{"terrain":"abyss"}, "Map_Tile_24_8":{"terrain":"mountain"}, "Map_Tile_21_1":{"terrain":"plains"}, "Map_Tile_18_10":{"terrain":"abyss"}, "Map_Tile_22_6":{"terrain":"mountain"}, "Map_Tile_14_10":{"terrain":"road"}, "Map_Tile_14_0":{"unit":{"attackerPlayerId":-1, "id":13, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":14, "y":0, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.25, "aliasId":"", "movementType":"flying", "id":"harpy", "weaponIds":["harpyClaws"], "isDamagingParentUnit":false, "tags":["harpy", "type.air"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":600, "isAttackable":true, "critConditionId":"", "moveRange":6, "isCommander":false, "inAir":true, "resourceCost":2, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "directionality":"omni", "id":"harpyClaws", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":14, "y":0, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"harpy", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss"}, "Map_Tile_12_2":{"terrain":"abyss"}, "Map_Tile_1_8":{"terrain":"abyss"}, "Map_Tile_27_10":{"terrain":"abyss"}, "Map_Tile_17_7":{"terrain":"road"}, "Map_Tile_21_14":{"terrain":"road"}, "Map_Tile_2_13":{"terrain":"abyss"}, "Map_Tile_29_2":{"terrain":"abyss"}, "Map_Tile_9_0":{"terrain":"abyss"}, "Map_Tile_20_12":{"terrain":"abyss"}, "Map_Tile_23_2":{"unit":{"attackerPlayerId":-1, "id":2, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":23, "y":2, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"tower", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":23, "y":2, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"tower", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_22_0":{"terrain":"abyss"}, "Map_Tile_7_8":{"terrain":"river"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_24_13":{"unit":{"attackerPlayerId":-1, "id":42, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":24, "y":13, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":24, "y":13, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_19_18":{"terrain":"abyss"}, "Map_Tile_24_0":{"terrain":"abyss"}, "Map_Tile_8_8":{"terrain":"road"}, "Map_Tile_14_5":{"terrain":"abyss"}, "Map_Tile_5_14":{"terrain":"road"}, "Map_Tile_29_11":{"terrain":"abyss"}, "Map_Tile_11_11":{"terrain":"abyss"}, "Map_Tile_28_5":{"terrain":"abyss"}, "Map_Tile_15_6":{"terrain":"abyss"}, "Map_Tile_29_16":{"terrain":"abyss"}, "Map_Tile_19_2":{"terrain":"abyss"}, "Map_Tile_25_7":{"terrain":"abyss"}, "Map_Tile_22_1":{"unit":{"attackerPlayerId":-1, "id":20, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"fortified_garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":22, "y":1, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"fortified_city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":1000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":22, "y":1, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"fortified_city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_6":{"terrain":"abyss"}, "Map_Tile_11_12":{"terrain":"abyss"}, "Map_Tile_23_11":{"terrain":"abyss"}, "Map_Tile_23_7":{"terrain":"mountain"}, "Map_Tile_26_17":{"terrain":"forest"}, "Map_Tile_16_16":{"terrain":"plains"}, "Map_Tile_29_14":{"terrain":"abyss"}, "Map_Tile_6_19":{"terrain":"abyss"}, "Map_Tile_29_8":{"terrain":"abyss"}, "Map_Tile_19_14":{"terrain":"abyss"}, "Map_Tile_17_16":{"unit":{"attackerPlayerId":-1, "id":48, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":17, "y":16, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":17, "y":16, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_2_11":{"terrain":"abyss"}, "Map_Tile_13_10":{"terrain":"forest"}, "Map_Tile_0_1":{"terrain":"abyss"}, "Map_Tile_22_7":{"terrain":"mountain"}, "Map_Tile_1_6":{"terrain":"abyss"}, "Map_Tile_28_9":{"terrain":"abyss"}, "Map_Tile_15_16":{"terrain":"road"}, "Map_Tile_10_14":{"terrain":"abyss"}, "Map_Tile_4_12":{"unit":{"attackerPlayerId":-1, "id":45, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":4, "y":12, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":4, "y":12, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_1_2":{"terrain":"abyss"}, "Map_Tile_27_12":{"terrain":"abyss"}, "Map_Tile_13_17":{"terrain":"plains"}, "Map_Tile_12_14":{"unit":{"attackerPlayerId":-1, "id":4, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":12, "y":14, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":12, "y":14, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_16_9":{"terrain":"plains"}, "Map_Tile_7_13":{"terrain":"abyss"}, "Map_Tile_21_12":{"terrain":"abyss_bridge"}, "Map_Tile_25_6":{"terrain":"abyss"}, "Map_Tile_4_8":{"terrain":"abyss"}, "Map_Tile_15_4":{"terrain":"abyss"}, "Map_Tile_10_13":{"terrain":"abyss"}, "Map_Tile_16_1":{"terrain":"abyss"}, "Map_Tile_24_14":{"terrain":"road"}, "Map_Tile_17_15":{"terrain":"abyss"}, "Map_Tile_12_13":{"terrain":"abyss"}, "Map_Tile_5_10":{"terrain":"abyss"}, "Map_Tile_28_16":{"terrain":"abyss"}, "Map_Tile_5_18":{"unit":{"attackerPlayerId":-1, "id":18, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":5, "y":18, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":5, "y":18, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_4_2":{"terrain":"forest"}, "Map_Tile_10_1":{"terrain":"abyss"}, "Map_Tile_9_11":{"terrain":"abyss"}, "Map_Tile_26_14":{"terrain":"abyss"}, "Map_Tile_21_8":{"terrain":"road"}, "Map_Tile_16_17":{"terrain":"plains"}, "Author":"Magnemania", "Map_Tile_2_9":{"terrain":"abyss_bridge"}, "Map_Tile_26_12":{"terrain":"abyss"}, "Map_Tile_4_1":{"unit":{"attackerPlayerId":-1, "id":1, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":4, "y":1, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"tower", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":4, "y":1, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"tower", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_21_16":{"terrain":"road"}, "Map_Tile_6_5":{"terrain":"river"}, "Map_Tile_2_19":{"terrain":"abyss"}, "Map_Tile_3_5":{"terrain":"abyss"}, "Map_Tile_5_7":{"terrain":"river"}, "Map_Tile_8_16":{"terrain":"abyss_bridge"}, "Map_Tile_15_7":{"terrain":"road"}, "Map_Tile_23_0":{"terrain":"abyss"}, "Map_Tile_16_15":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"abyss"}, "Map_Tile_7_4":{"terrain":"abyss"}, "Map_Tile_24_1":{"terrain":"abyss"}, "Map_Tile_29_19":{"terrain":"abyss"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_8_0":{"terrain":"abyss"}, "Map_Tile_4_18":{"terrain":"abyss"}, "Map_Tile_18_16":{"terrain":"abyss"}, "Map_Tile_13_1":{"terrain":"plains"}, "Map_Tile_0_19":{"terrain":"abyss"}, "Map_Tile_10_0":{"unit":{"attackerPlayerId":-1, "id":27, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":10, "y":0, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.25, "aliasId":"", "movementType":"flying", "id":"harpy", "weaponIds":["harpyClaws"], "isDamagingParentUnit":false, "tags":["harpy", "type.air"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":600, "isAttackable":true, "critConditionId":"", "moveRange":6, "isCommander":false, "inAir":true, "resourceCost":2, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "directionality":"omni", "id":"harpyClaws", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":10, "y":0, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"harpy", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss"}, "Map_Tile_29_13":{"terrain":"abyss"}, "Map_Tile_7_17":{"terrain":"abyss"}, "Map_Tile_14_6":{"terrain":"plains"}, "Map_Tile_16_12":{"terrain":"abyss"}, "Map_Tile_13_14":{"terrain":"plains"}, "Map_Tile_16_11":{"terrain":"abyss"}, "Map_Tile_8_1":{"terrain":"abyss"}, "Map_Tile_1_5":{"terrain":"abyss"}, "Map_Tile_5_9":{"terrain":"abyss_bridge"}, "Map_Tile_16_10":{"unit":{"attackerPlayerId":-1, "id":6, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":16, "y":10, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":16, "y":10, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_18_5":{"terrain":"abyss"}, "Map_Tile_11_17":{"terrain":"mountain"}, "Map_Tile_13_11":{"terrain":"abyss"}, "Map_Tile_10_15":{"unit":{"attackerPlayerId":-1, "id":3, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":10, "y":15, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":10, "y":15, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_17_19":{"terrain":"abyss"}, "Map_Tile_21_17":{"unit":{"attackerPlayerId":-1, "id":44, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":21, "y":17, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":21, "y":17, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_10_11":{"unit":{"attackerPlayerId":-1, "id":28, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":10, "y":11, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"cursed_structure", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":false, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":10, "y":11, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"cursed_structure", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_0_18":{"terrain":"abyss"}, "Map_Tile_14_9":{"terrain":"road"}, "Map_Tile_6_11":{"terrain":"abyss"}, "Map_Tile_20_11":{"terrain":"abyss"}, "Map_Tile_4_4":{"terrain":"abyss"}, "Map_Tile_21_18":{"terrain":"abyss"}, "Map_Tile_18_6":{"terrain":"abyss"}, "Map_Tile_15_10":{"terrain":"road"}, "Map_Tile_9_17":{"terrain":"abyss"}, "Map_Tile_9_15":{"terrain":"abyss"}, "Map_Tile_20_6":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"abyss"}, "Map_Tile_8_6":{"terrain":"abyss"}, "Map_Tile_26_2":{"terrain":"abyss"}, "Map_Tile_1_9":{"terrain":"abyss"}, "Map_Tile_4_3":{"terrain":"abyss"}, "Map_Tile_22_2":{"terrain":"plains"}, "Map_Tile_8_11":{"terrain":"abyss"}, "Map_Tile_13_12":{"terrain":"abyss"}, "Map_Tile_29_1":{"terrain":"abyss"}, "Map_Tile_20_19":{"terrain":"abyss"}, "Map_Tile_13_8":{"unit":{"attackerPlayerId":-1, "id":29, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":13, "y":8, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"barracks", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":13, "y":8, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"barracks", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_29_4":{"terrain":"abyss"}, "Counters":{}, "Map_Tile_18_3":{"terrain":"abyss_bridge"}, "Map_Tile_18_18":{"terrain":"abyss"}, "Map_Tile_7_10":{"terrain":"abyss"}, "Map_Tile_2_18":{"terrain":"abyss"}, "Map_Tile_2_7":{"terrain":"abyss"}, "Map_Tile_17_9":{"terrain":"abyss"}, "Map_Tile_7_1":{"terrain":"abyss"}, "Map_Tile_20_13":{"terrain":"abyss"}, "Map_Tile_29_15":{"terrain":"abyss"}, "Map_Tile_1_11":{"terrain":"abyss"}, "Map_Tile_0_0":{"terrain":"abyss"}, "Map_Tile_27_2":{"terrain":"abyss"}, "Map_Tile_10_12":{"terrain":"abyss"}, "Map_Tile_18_7":{"terrain":"road"}, "Map_Tile_9_13":{"terrain":"abyss"}, "Map_Tile_20_4":{"terrain":"abyss"}, "Map_Tile_27_0":{"terrain":"abyss"}, "Map_Tile_10_19":{"terrain":"abyss"}, "Map_Tile_5_12":{"unit":{"attackerPlayerId":-1, "id":39, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":5, "y":12, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "aliasId":"", "movementType":"walking", "id":"spearman", "weaponIds":["spear"], "isDamagingParentUnit":false, "tags":["spearman", "type.ground.light"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":250, "isAttackable":true, "critConditionId":"", "moveRange":3, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"spear", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":5, "y":12, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"spearman", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_20_14":{"terrain":"plains"}, "Map_Tile_0_3":{"terrain":"abyss"}, "Map_Tile_9_19":{"terrain":"abyss"}, "Map_Tile_1_17":{"terrain":"abyss"}, "Map_Tile_20_1":{"terrain":"plains"}, "Map_Tile_24_18":{"terrain":"plains"}, "Map_Tile_13_5":{"terrain":"abyss"}, "Map_Tile_24_19":{"terrain":"abyss"}, "Map_Tile_23_16":{"unit":{"attackerPlayerId":-1, "id":37, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":23, "y":16, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "aliasId":"", "movementType":"walking", "id":"soldier", "weaponIds":["sword"], "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":100, "isAttackable":true, "critConditionId":"", "moveRange":4, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"sword", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":23, "y":16, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"soldier", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_18_12":{"terrain":"abyss"}, "Map_Name":"Towers of the Abyss", "Map_Tile_11_0":{"terrain":"abyss"}, "Map_Tile_12_16":{"terrain":"road"}, "Map_Tile_22_17":{"unit":{"attackerPlayerId":-1, "id":36, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":22, "y":17, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":225, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"walking", "id":"commander_pistil", "weaponIds":["pistilAttack"], "isDamagingParentUnit":false, "tags":["commander", "type.ground.light"], "isRecruitable":false, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":4, "isCommander":true, "inAir":false, "resourceCost":3, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"pistilAttack", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":22, "y":17, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"commander_pistil", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"chain_reaction", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_6_14":{"unit":{"attackerPlayerId":-1, "id":9, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":6, "y":14, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":6, "y":14, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_3_19":{"terrain":"abyss"}, "Map_Tile_7_16":{"terrain":"road"}, "Map_Tile_20_10":{"unit":{"attackerPlayerId":-1, "id":11, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":20, "y":10, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":20, "y":10, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_17_11":{"terrain":"abyss"}, "Map_Tile_26_4":{"terrain":"abyss"}, "Map_Tile_5_19":{"terrain":"abyss"}, "Map_Tile_3_1":{"unit":{"attackerPlayerId":-1, "id":21, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"fortified_garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":3, "y":1, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"fortified_city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":1000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":3, "y":1, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"fortified_city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_10_18":{"terrain":"abyss"}, "Map_Tile_23_1":{"terrain":"abyss"}, "Map_Tile_7_9":{"terrain":"road"}, "Map_Tile_3_7":{"terrain":"abyss"}, "Map_Tile_16_8":{"terrain":"abyss"}, "Map_Tile_22_8":{"terrain":"plains"}, "Map_Tile_3_15":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"abyss"}, "Map_Tile_2_0":{"unit":{"attackerPlayerId":-1, "id":12, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":2, "y":0, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.25, "aliasId":"", "movementType":"flying", "id":"harpy", "weaponIds":["harpyClaws"], "isDamagingParentUnit":false, "tags":["harpy", "type.air"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":600, "isAttackable":true, "critConditionId":"", "moveRange":6, "isCommander":false, "inAir":true, "resourceCost":2, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "directionality":"omni", "id":"harpyClaws", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":2, "y":0, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"harpy", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss"}, "Map_Tile_5_2":{"terrain":"forest"}, "Map_Tile_18_0":{"terrain":"abyss"}, "Map_Tile_26_11":{"terrain":"abyss"}, "Map_Tile_6_12":{"unit":{"attackerPlayerId":-1, "id":46, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":6, "y":12, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":6, "y":12, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_16_19":{"terrain":"abyss"}, "Map_Tile_0_2":{"terrain":"abyss"}, "Map_Tile_3_9":{"terrain":"abyss_bridge"}, "Map_Tile_11_14":{"terrain":"abyss"}, "Map_Tile_1_12":{"terrain":"abyss"}, "Map_Tile_7_3":{"terrain":"abyss"}, "Map_Tile_1_14":{"terrain":"abyss"}, "Map_Tile_24_3":{"terrain":"abyss"}, "Player_Count":2, "Map_Tile_20_3":{"terrain":"plains"}, "Map_Tile_4_15":{"terrain":"forest"}, "Map_Tile_15_14":{"terrain":"road"}, "Map_Tile_24_4":{"terrain":"abyss"}, "Player_1":{"recruit_dragon":true, "recruit_giant":true, "recruit_thief":true, "recruit_caravel":true, "recruit_soldier":true, "recruit_spearman":true, "recruit_knight":true, "recruit_dog":true, "recruit_harpy":true, "recruit_ballista":true, "recruit_travelboat":true, "recruit_balloon":true, "team":0, "recruit_warship":true, "recruit_merman":true, "recruit_turtle":true, "recruit_frog":true, "recruit_witch":true, "recruit_mage":true, "recruit_kraken":true, "recruit_wagon":true, "recruit_archer":true, "recruit_rifleman":true, "recruit_griffin_walking":true, "gold":100, "recruit_trebuchet":true, "recruit_harpoonship":true}, "Map_Tile_27_9":{"terrain":"abyss"}, "Map_Tile_20_18":{"terrain":"abyss"}, "Map_Tile_20_2":{"terrain":"plains"}, "Map_Tile_13_15":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"abyss"}, "Map_Tile_6_18":{"unit":{"attackerPlayerId":-1, "id":17, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":6, "y":18, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":6, "y":18, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_28_15":{"terrain":"abyss"}, "Map_Tile_16_4":{"terrain":"abyss"}, "Map_Tile_4_14":{"terrain":"plains"}, "Map_Tile_16_6":{"terrain":"abyss"}, "Map_Tile_3_11":{"terrain":"abyss"}, "Flags":{"0":0}, "Map_Tile_15_2":{"terrain":"abyss_bridge"}, "Map_Tile_14_1":{"terrain":"abyss"}, "Map_Tile_15_5":{"terrain":"abyss"}, "Map_Tile_27_16":{"terrain":"abyss"}, "Map_Tile_24_11":{"terrain":"abyss"}, "Map_Tile_6_10":{"terrain":"abyss"}, "Map_Tile_7_12":{"terrain":"abyss"}, "Map_Tile_27_13":{"terrain":"abyss"}, "Map_Tile_13_2":{"terrain":"abyss"}, "Map_Tile_25_15":{"unit":{"attackerPlayerId":-1, "id":16, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":25, "y":15, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":25, "y":15, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_14_16":{"terrain":"road"}, "Map_Tile_12_12":{"terrain":"abyss"}, "Map_Tile_14_8":{"terrain":"road"}, "Map_Tile_1_3":{"terrain":"abyss"}, "Map_Tile_2_2":{"terrain":"abyss"}, "Map_Tile_26_6":{"terrain":"abyss"}, "Map_Tile_0_11":{"terrain":"abyss"}, "Map_Tile_3_14":{"terrain":"abyss"}, "Map_Tile_12_15":{"terrain":"plains"}, "Map_Tile_11_10":{"terrain":"abyss"}, "Map_Tile_21_19":{"terrain":"abyss"}, "Map_Tile_10_5":{"terrain":"abyss"}, "Map_Tile_19_12":{"unit":{"attackerPlayerId":-1, "id":30, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":19, "y":12, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"cursed_structure", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":false, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":19, "y":12, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"cursed_structure", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_16_0":{"terrain":"abyss"}, "Map_Tile_10_8":{"terrain":"abyss"}, "Map_Tile_24_9":{"terrain":"abyss"}, "Map_Tile_24_10":{"terrain":"abyss"}, "Map_Tile_6_9":{"terrain":"bridge"}, "Map_Tile_12_11":{"terrain":"abyss"}, "Map_Tile_26_16":{"terrain":"abyss"}, "Map_Tile_0_10":{"terrain":"abyss"}, "Map_Tile_25_14":{"terrain":"abyss_bridge"}, "Map_Tile_5_6":{"terrain":"river"}, "Map_Tile_11_19":{"terrain":"abyss"}, "Map_Tile_22_14":{"terrain":"mountain"}, "Map_Tile_19_16":{"terrain":"abyss"}, "Map_Tile_29_0":{"terrain":"abyss"}, "Map_Tile_13_4":{"terrain":"abyss"}, "Map_Tile_27_14":{"terrain":"abyss"}, "Map_Tile_17_6":{"terrain":"abyss"}, "Map_Tile_8_14":{"terrain":"abyss"}, "Map_Tile_23_13":{"terrain":"forest"}, "Map_Tile_28_17":{"terrain":"abyss"}, "Map_Tile_8_7":{"terrain":"road"}, "Map_Tile_19_19":{"terrain":"abyss"}, "Map_Tile_11_8":{"terrain":"road"}, "Map_Tile_14_17":{"terrain":"plains"}, "Map_Tile_12_7":{"unit":{"attackerPlayerId":-1, "id":24, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":12, "y":7, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"hq", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":false, "cost":3000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":12, "y":7, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"hq", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_2_16":{"terrain":"abyss"}, "Map_Tile_2_12":{"terrain":"abyss"}, "Map_Tile_4_11":{"terrain":"abyss"}, "Map_Tile_12_18":{"terrain":"plains"}, "Map_Tile_17_14":{"terrain":"abyss"}, "Map_Tile_4_16":{"terrain":"plains"}, "Map_Tile_2_17":{"terrain":"abyss"}, "Map_Tile_19_17":{"terrain":"abyss"}, "Map_Tile_24_16":{"unit":{"attackerPlayerId":-1, "id":38, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":24, "y":16, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "aliasId":"", "movementType":"riding", "id":"knight", "weaponIds":["lance"], "isDamagingParentUnit":false, "tags":["knight", "type.ground.heavy"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":600, "isAttackable":true, "critConditionId":"", "moveRange":6, "isCommander":false, "inAir":false, "resourceCost":2, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"lance", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":24, "y":16, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"knight", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_5_13":{"terrain":"abyss_bridge"}, "Map_Tile_11_18":{"terrain":"abyss"}, "Map_Tile_4_13":{"terrain":"abyss"}, "Map_Tile_17_0":{"terrain":"abyss"}, "Map_Tile_17_5":{"terrain":"abyss"}, "Map_Tile_21_6":{"terrain":"plains"}, "Map_Tile_22_12":{"terrain":"abyss_bridge"}, "Map_Tile_27_7":{"terrain":"abyss"}, "Map_Tile_0_13":{"terrain":"abyss"}, "Objectives":["Destroy a Cursed Structure with a Trebuchet. (Requires Ballista and Trebuchet)", "Complete the map without taking Stronghold damage. (Requires Ballista and Walls)", "Destroy all of the Cursed Structures. (Requires Ballista)"], "Map_Tile_5_1":{"terrain":"plains"}, "Map_Tile_1_7":{"terrain":"abyss"}, "Map_Tile_10_10":{"terrain":"abyss"}, "Map_Tile_3_16":{"terrain":"plains"}, "Map_Tile_8_17":{"terrain":"abyss"}, "Map_Tile_1_18":{"terrain":"abyss"}, "Map_Tile_11_16":{"terrain":"road"}, "Map_Tile_15_1":{"terrain":"abyss"}, "Triggers":[{"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["1", "Towers of the Abyss", "Magnemania", "Destroy a Cursed Structure with a Trebuchet. (Requires Ballista and Trebuchet)", "Complete the map without taking Stronghold damage. (Requires Ballista and Walls)", "", "Destroy all of the Cursed Structures. (Requires Ballista)"], "enabled":true, "id":"ap_export"}], "conditions":{}, "recurring":"start_of_match", "id":"Export (Always on Top)"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["P2", "aggressive"], "enabled":true, "id":"ai_set_profile"}], "conditions":{}, "recurring":"once", "id":"Set AI"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["cursed_structure", "-3", "any", "0", "0"], "enabled":true, "id":"remove_units"}], "conditions":[{"parameters":["cursed_structure", "current", "-1"], "enabled":true, "id":"unit_lost"}], "recurring":"repeat", "id":"Obelisk Destroyed"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["extra", "elodie", "YOUR CALL IS ANSWERED. THE FLYING INVADERS SHALL NOT PASS.", "0", ""], "enabled":true, "id":"dialogue_box_simple"}, {"parameters":["7", "wall", "default", "", "100", "0", "0"], "enabled":true, "id":"activate_flood"}], "conditions":[{"parameters":["252024", "1", "0"], "enabled":true, "id":"ap_has_item"}], "recurring":"once", "id":"Walls Rise"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["253331"], "enabled":true, "id":"ap_location_send"}], "conditions":[{"parameters":["trebuchet", "current", "cursed_structure", "any", "-1"], "enabled":true, "id":"unit_killed"}], "recurring":"once", "id":"Obelisk Destroyed by Trebuchet (Check 253332)"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["*unit_structure", "any", "0", "0", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "1", "1", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "5", "5", "1"], "enabled":true, "id":"unit_random_teleport"}, {"parameters":["*unit_structure", "any", "6", "6", "1"], "enabled":true, "id":"unit_random_teleport"}], "conditions":{}, "recurring":"start_of_match", "id":"Shuffle Units"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["0", "1"], "enabled":true, "id":"set_map_flag"}], "conditions":[{"parameters":["current", "0", "1", "hq", "-1", "3", "100"], "enabled":true, "id":"unit_health"}], "recurring":"once", "id":"Fortress Attacked"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["P1"], "enabled":true, "id":"victory"}], "conditions":[{"parameters":["current", "0", "0", "cursed_structure", "-1"], "enabled":true, "id":"unit_presence"}], "recurring":"once", "id":"All Obelisks Destroyed"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "conditions":[{"parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true, "id":"unit_presence"}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_no_units"}, {"players":[1, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "conditions":[{"parameters":["hq", "current", "-1"], "enabled":true, "id":"unit_lost"}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_hq"}, {"players":[0, 1, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"victory"}], "conditions":[{"parameters":["current", "0", "0"], "enabled":true, "id":"number_of_opponents"}], "recurring":"oncePerPlayer", "id":"$trigger_default_victory"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["current"], "enabled":true, "id":"eliminate"}], "conditions":[{"parameters":["*commander", "current", "-1"], "enabled":true, "id":"unit_lost"}], "recurring":"oncePerPlayer", "id":"$trigger_default_defeat_commander"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["253332"], "enabled":true, "id":"ap_location_send"}], "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}, {"parameters":["0", "0"], "enabled":true, "id":"check_map_flag"}], "recurring":"once", "id":"Won without Fortress Damage (Check 253331)"}, {"players":[1, 0, 0, 0, 0, 0, 0, 0], "enabled":true, "isIntro":false, "actions":[{"parameters":["253330"], "enabled":true, "id":"ap_location_send"}], "conditions":[{"parameters":["current"], "enabled":true, "id":"player_victorious"}], "recurring":"once", "id":"P1 Wins (Check 253330)"}], "Locations":{"0":{"interactable":false, "setArea":null, "centre":{"x":14, "y":16}, "name":"Shuffle1", "positions":[{"x":16, "y":14}, {"x":12, "y":14}, {"x":10, "y":15}, {"x":12, "y":18}, {"x":17, "y":16}, {"x":17, "y":18}, {"x":16, "y":15}, {"x":10, "y":17}, {"x":13, "y":18}, {"x":13, "y":14}, {"x":11, "y":15}, {"x":26, "y":13}], "getArea":null, "id":0}, "1":{"interactable":false, "setArea":null, "centre":{"x":17, "y":15}, "name":"Enemy Army Shuffle", "positions":[{"x":23, "y":16}, {"x":24, "y":16}, {"x":22, "y":17}, {"x":5, "y":12}, {"x":5, "y":13}, {"x":23, "y":17}], "getArea":null, "id":1}, "2":{"interactable":false, "setArea":null, "centre":{"x":15, "y":12}, "name":"Allied Army Shuffle", "positions":[{"x":14, "y":11}, {"x":14, "y":12}, {"x":15, "y":12}, {"x":15, "y":13}, {"x":14, "y":13}, {"x":15, "y":11}], "getArea":null, "id":2}, "3":{"interactable":false, "setArea":null, "centre":{"x":5, "y":16}, "name":"Shuffle2", "positions":[{"x":4, "y":17}, {"x":5, "y":18}, {"x":6, "y":18}, {"x":3, "y":15}, {"x":3, "y":16}, {"x":4, "y":14}, {"x":6, "y":14}, {"x":6, "y":15}, {"x":7, "y":15}], "getArea":null, "id":3}, "4":{"interactable":false, "setArea":null, "centre":{"x":17, "y":8}, "name":"Shuffle3", "positions":[{"x":15, "y":8}, {"x":16, "y":10}, {"x":9, "y":8}, {"x":11, "y":6}, {"x":13, "y":6}, {"x":19, "y":6}, {"x":23, "y":9}, {"x":20, "y":9}, {"x":20, "y":10}, {"x":21, "y":6}], "getArea":null, "id":4}, "5":{"interactable":false, "setArea":null, "centre":{"x":22, "y":15}, "name":"Enemy Structure Shuffle", "positions":[{"x":24, "y":13}, {"x":23, "y":13}, {"x":20, "y":15}, {"x":21, "y":17}, {"x":20, "y":16}, {"x":22, "y":17}, {"x":23, "y":17}], "getArea":null, "id":5}, "6":{"interactable":false, "setArea":null, "centre":{"x":13, "y":6}, "name":"Obelisk Shuffle", "positions":[{"x":10, "y":11}, {"x":19, "y":12}, {"x":12, "y":3}, {"x":8, "y":3}, {"x":16, "y":3}], "getArea":null, "id":6}, "7":{"interactable":false, "setArea":null, "centre":{"x":12, "y":7}, "name":"Abyss Walls", "positions":[{"x":10, "y":6}, {"x":10, "y":5}, {"x":11, "y":5}, {"x":12, "y":5}, {"x":13, "y":5}, {"x":14, "y":5}, {"x":15, "y":5}, {"x":15, "y":6}, {"x":10, "y":8}, {"x":10, "y":9}, {"x":11, "y":9}, {"x":12, "y":9}, {"x":12, "y":10}, {"x":16, "y":8}, {"x":17, "y":8}, {"x":17, "y":9}, {"x":17, "y":10}, {"x":9, "y":6}, {"x":8, "y":6}, {"x":9, "y":9}, {"x":9, "y":10}, {"x":8, "y":10}, {"x":7, "y":10}, {"x":6, "y":10}, {"x":16, "y":6}, {"x":17, "y":6}, {"x":18, "y":6}, {"x":12, "y":6}], "getArea":null, "id":7}}, "Map_Tile_5_0":{"terrain":"abyss"}, "Map_Tile_29_18":{"terrain":"abyss"}, "Map_Tile_8_5":{"terrain":"abyss"}, "Map_Tile_29_17":{"terrain":"abyss"}, "Map_Tile_7_14":{"terrain":"abyss"}, "Map_Tile_17_8":{"terrain":"abyss"}, "Map_Tile_29_12":{"terrain":"abyss"}, "Map_Tile_17_17":{"terrain":"mountain"}, "Map_Tile_28_19":{"terrain":"abyss"}, "Map_Tile_2_4":{"terrain":"abyss"}, "Map_Tile_11_1":{"terrain":"plains"}, "Map_Tile_29_7":{"terrain":"abyss"}, "Map_Tile_13_9":{"terrain":"forest"}, "Map_Tile_23_14":{"terrain":"road"}, "Map_Tile_4_9":{"terrain":"abyss_bridge"}, "Map_Tile_6_6":{"terrain":"river"}, "Map_Tile_6_2":{"terrain":"abyss_bridge"}, "Map_Tile_29_5":{"terrain":"abyss"}, "Map_Tile_22_13":{"terrain":"plains"}, "Map_Tile_29_3":{"terrain":"abyss"}, "Map_Tile_29_10":{"terrain":"abyss"}, "Map_Tile_22_11":{"terrain":"abyss_bridge"}, "Map_Tile_28_18":{"terrain":"abyss"}, "Map_Tile_28_14":{"terrain":"abyss"}, "Map_Tile_2_3":{"terrain":"abyss"}, "Map_Tile_12_5":{"terrain":"abyss"}, "Map_Tile_28_13":{"terrain":"abyss"}, "Map_Tile_8_3":{"terrain":"plains"}, "Map_Tile_28_12":{"terrain":"abyss"}, "Map_Tile_19_0":{"terrain":"abyss"}, "Map_Tile_28_10":{"terrain":"abyss"}, "Map_Tile_28_8":{"terrain":"abyss"}, "Map_Tile_28_7":{"terrain":"abyss"}, "Map_Tile_7_18":{"terrain":"abyss"}, "Map_Tile_28_4":{"terrain":"abyss"}, "Map_Tile_28_3":{"terrain":"abyss"}, "Map_Tile_28_2":{"terrain":"abyss"}, "Map_Tile_10_2":{"unit":{"attackerPlayerId":-1, "id":22, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"fortified_garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":10, "y":2, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"fortified_city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":1000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":10, "y":2, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"fortified_city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_28_1":{"terrain":"abyss"}, "Player_2":{"recruit_dragon":true, "recruit_giant":true, "recruit_thief":true, "recruit_caravel":true, "recruit_soldier":true, "recruit_spearman":true, "recruit_knight":true, "recruit_dog":true, "recruit_harpy":true, "recruit_ballista":false, "recruit_travelboat":false, "recruit_balloon":false, "team":1, "recruit_warship":true, "recruit_merman":true, "recruit_turtle":true, "recruit_frog":true, "recruit_witch":true, "recruit_mage":true, "recruit_kraken":true, "recruit_wagon":false, "recruit_archer":true, "recruit_rifleman":true, "recruit_griffin_walking":false, "gold":100, "recruit_trebuchet":true, "recruit_harpoonship":true}, "Map_Tile_28_0":{"terrain":"abyss"}, "Map_Tile_23_15":{"terrain":"road"}, "Map_Tile_25_16":{"terrain":"plains"}, "Map_Tile_27_19":{"terrain":"abyss"}, "Map_Tile_5_11":{"unit":{"attackerPlayerId":-1, "id":47, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":5, "y":11, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"barracks", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":5, "y":11, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"barracks", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"road"}, "Map_Tile_27_18":{"terrain":"abyss"}, "Map_Tile_27_17":{"terrain":"abyss"}, "Map_Tile_0_15":{"terrain":"abyss"}, "Map_Tile_27_15":{"terrain":"abyss"}, "Map_Tile_27_11":{"terrain":"abyss"}, "Map_Tile_27_8":{"terrain":"abyss"}, "Map_Tile_16_7":{"terrain":"abyss_bridge"}, "Map_Tile_27_5":{"terrain":"abyss"}, "Map_Tile_27_4":{"terrain":"abyss"}, "Map_Tile_27_3":{"terrain":"abyss"}, "Map_Tile_23_3":{"terrain":"abyss"}, "Map_Tile_15_3":{"terrain":"abyss"}, "Map_Tile_27_1":{"terrain":"abyss"}, "Map_Tile_26_19":{"terrain":"abyss"}, "Map_Tile_13_6":{"terrain":"plains"}, "Map_Tile_26_18":{"terrain":"abyss"}, "Map_Tile_1_4":{"terrain":"abyss"}, "Map_Tile_26_15":{"terrain":"abyss"}, "Map_Tile_7_0":{"terrain":"abyss"}, "Map_Tile_16_5":{"terrain":"abyss"}, "Map_Tile_2_10":{"terrain":"abyss_bridge"}, "Map_Tile_26_13":{"terrain":"abyss"}, "Map_Tile_4_17":{"unit":{"attackerPlayerId":-1, "id":19, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":4, "y":17, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":4, "y":17, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_4_10":{"terrain":"abyss"}, "Map_Tile_26_10":{"terrain":"abyss"}, "Map_Tile_26_9":{"terrain":"abyss"}, "Map_Tile_26_7":{"terrain":"abyss"}, "Map_Tile_26_5":{"terrain":"abyss"}, "Map_Tile_9_9":{"terrain":"abyss"}, "Map_Tile_3_0":{"terrain":"abyss"}, "Map_Tile_29_6":{"terrain":"abyss"}, "Map_Tile_6_17":{"terrain":"plains"}, "Map_Tile_26_1":{"terrain":"abyss"}, "Map_Size":{"x":30, "y":20}, "Map_Tile_12_19":{"terrain":"abyss"}, "Map_Tile_17_3":{"terrain":"abyss"}, "Map_Tile_22_5":{"terrain":"mountain"}, "Map_Tile_25_17":{"terrain":"abyss"}, "Map_Tile_18_9":{"terrain":"abyss"}, "Map_Tile_11_2":{"terrain":"abyss"}, "Map_Tile_23_9":{"terrain":"plains"}, "Map_Tile_6_1":{"terrain":"forest"}, "Map_Tile_16_2":{"terrain":"abyss_bridge"}, "Map_Tile_2_5":{"terrain":"abyss"}, "Map_Tile_25_12":{"terrain":"abyss_bridge"}, "Map_Tile_14_12":{"unit":{"attackerPlayerId":-1, "id":33, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":14, "y":12, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":250, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"walking", "id":"commander_mercia", "weaponIds":["merciaSword"], "isDamagingParentUnit":false, "tags":["commander", "type.ground.light"], "isRecruitable":false, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":4, "isCommander":true, "inAir":false, "resourceCost":3, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"merciaSword", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":14, "y":12, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"commander_mercia", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"heal_aura", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss_bridge"}, "Map_Tile_4_7":{"terrain":"river"}, "Map_Tile_25_11":{"terrain":"abyss_bridge"}, "Map_Tile_6_13":{"terrain":"abyss"}, "Map_Tile_23_17":{"unit":{"attackerPlayerId":-1, "id":40, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":23, "y":17, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "aliasId":"", "movementType":"walking", "id":"mage", "weaponIds":["lightning"], "isDamagingParentUnit":false, "tags":["mage", "type.ground.light", "spellcaster"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":0.5, "canReinforce":false, "cost":400, "isAttackable":true, "critConditionId":"", "moveRange":5, "isCommander":false, "inAir":false, "resourceCost":2, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":true, "terrainExclusion":{}, "directionality":"omni", "id":"lightning", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":23, "y":17, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"mage", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_25_10":{"terrain":"abyss"}, "Map_Tile_25_9":{"terrain":"abyss"}, "Map_Tile_8_12":{"terrain":"abyss"}, "Map_Tile_25_1":{"terrain":"abyss"}, "Map_Tile_8_2":{"terrain":"abyss"}, "Map_Tile_25_8":{"terrain":"abyss"}, "Map_Tile_25_4":{"terrain":"abyss"}, "Map_Tile_14_18":{"terrain":"plains"}, "Map_Tile_21_11":{"terrain":"abyss_bridge"}, "Map_Tile_25_2":{"terrain":"abyss"}, "Map_Tile_23_4":{"terrain":"abyss"}, "Map_Tile_21_13":{"terrain":"road"}, "Map_Tile_19_11":{"terrain":"abyss"}, "Map_Tile_25_0":{"terrain":"abyss"}, "Map_Tile_1_1":{"terrain":"abyss"}, "Map_Tile_24_17":{"terrain":"plains"}, "Map_Tile_2_1":{"terrain":"abyss"}, "Map_Tile_20_9":{"terrain":"plains"}, "Map_Tile_24_15":{"terrain":"plains"}, "Map_Tile_24_12":{"terrain":"abyss_bridge"}, "Map_Tile_6_0":{"terrain":"abyss"}, "Map_Tile_3_12":{"terrain":"abyss"}, "Map_Tile_22_3":{"terrain":"abyss"}, "Map_Tile_22_4":{"terrain":"abyss"}, "Map_Tile_2_14":{"terrain":"abyss"}, "Map_Tile_6_7":{"unit":{"attackerPlayerId":-1, "id":31, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"fortified_garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":6, "y":7, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"fortified_city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":1000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":6, "y":7, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"fortified_city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_14_3":{"unit":{"attackerPlayerId":-1, "id":23, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"fortified_garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":14, "y":3, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"fortified_city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["fortified_city"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":1000, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":14, "y":3, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"fortified_city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_25_3":{"terrain":"abyss"}, "Map_Tile_15_17":{"terrain":"road"}, "Map_Tile_24_2":{"terrain":"abyss"}, "Map_Tile_21_5":{"terrain":"abyss"}, "Map_Tile_23_19":{"terrain":"abyss"}, "Map_Tile_7_11":{"terrain":"abyss"}, "Map_Tile_24_6":{"terrain":"mountain"}, "Map_Tile_29_9":{"terrain":"abyss"}, "Map_Tile_23_18":{"terrain":"mountain"}, "Map_Tile_6_15":{"terrain":"plains"}, "Map_Tile_0_12":{"terrain":"abyss"}, "Map_Tile_27_6":{"terrain":"abyss"}, "Map_Tile_20_7":{"terrain":"road"}, "Map_Tile_15_8":{"terrain":"plains"}, "Map_Tile_23_12":{"terrain":"abyss_bridge"}, "Map_Tile_26_3":{"terrain":"abyss"}, "Map_Tile_25_13":{"terrain":"abyss"}, "Map_Tile_20_17":{"terrain":"abyss"}, "Map_Tile_23_8":{"terrain":"plains"}, "Map_Tile_21_2":{"terrain":"plains"}, "Map_Tile_20_0":{"terrain":"abyss"}, "Map_Tile_10_9":{"terrain":"abyss"}, "Map_Tile_23_6":{"terrain":"mountain"}, "Map_Tile_9_10":{"terrain":"abyss"}, "Map_Tile_19_5":{"terrain":"abyss"}, "Map_Tile_21_3":{"terrain":"mountain"}, "Map_Tile_8_13":{"terrain":"abyss"}, "Map_Tile_10_16":{"terrain":"road"}, "Map_Tile_12_0":{"terrain":"abyss"}, "Map_Tile_12_3":{"unit":{"attackerPlayerId":-1, "id":25, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":12, "y":3, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"cursed_structure", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":false, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":12, "y":3, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"cursed_structure", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_25_18":{"terrain":"abyss"}, "Map_Tile_16_14":{"unit":{"attackerPlayerId":-1, "id":5, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":16, "y":14, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":16, "y":14, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_11_5":{"terrain":"abyss"}, "Map_Tile_3_13":{"terrain":"abyss"}, "Map_Tile_18_1":{"terrain":"abyss"}, "Map_Tile_14_14":{"terrain":"road"}, "Map_Tile_11_9":{"terrain":"abyss"}, "Map_Tile_5_17":{"terrain":"plains"}, "Map_Tile_22_19":{"terrain":"abyss"}, "Map_Tile_22_18":{"terrain":"abyss"}, "Map_Tile_8_19":{"terrain":"abyss"}, "Map_Tile_22_16":{"terrain":"road"}, "Map_Tile_18_17":{"terrain":"abyss"}, "Map_Tile_1_19":{"terrain":"abyss"}, "Map_Tile_22_10":{"terrain":"road"}, "Map_Tile_17_2":{"terrain":"abyss"}, "Map_Tile_1_15":{"terrain":"abyss"}, "Map_Tile_22_9":{"terrain":"plains"}, "Map_Tile_8_18":{"terrain":"abyss"}, "Map_Tile_28_11":{"terrain":"abyss"}, "Map_Tile_1_16":{"terrain":"abyss"}, "Map_Tile_7_19":{"terrain":"abyss"}, "Map_Tile_2_6":{"terrain":"abyss"}, "Map_Tile_0_9":{"terrain":"abyss"}, "Map_Tile_14_15":{"unit":{"attackerPlayerId":-1, "id":10, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":14, "y":15, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"barracks", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":14, "y":15, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"barracks", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_21_15":{"terrain":"road"}, "Map_Tile_11_3":{"terrain":"abyss"}, "Map_Tile_2_8":{"terrain":"abyss"}, "Map_Tile_11_13":{"terrain":"abyss"}, "Map_Tile_3_17":{"terrain":"abyss"}, "Map_Tile_3_3":{"terrain":"abyss"}, "Map_Tile_0_17":{"terrain":"abyss"}, "Map_Tile_16_13":{"terrain":"abyss"}, "Map_Tile_11_7":{"terrain":"road"}, "Map_Tile_21_7":{"terrain":"mountain"}, "Map_Tile_1_10":{"terrain":"abyss"}, "Map_Tile_7_15":{"terrain":"plains"}, "Map_Tile_21_4":{"terrain":"abyss"}, "Map_Tile_1_13":{"terrain":"abyss"}, "Map_Tile_15_0":{"terrain":"abyss"}, "Map_Tile_15_9":{"terrain":"road"}, "Map_Tile_15_18":{"terrain":"plains"}, "Map_Tile_25_19":{"terrain":"abyss"}, "Map_Tile_14_4":{"terrain":"abyss"}, "Map_Tile_4_19":{"terrain":"abyss"}, "Map_Tile_11_15":{"terrain":"plains"}, "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_20_5":{"terrain":"abyss"}, "Map_Tile_28_6":{"terrain":"abyss"}, "Map_Tile_14_7":{"terrain":"road"}, "Map_Tile_1_0":{"terrain":"abyss"}, "Map_Tile_19_13":{"terrain":"abyss"}, "Map_Tile_10_6":{"terrain":"abyss"}, "Map_Tile_15_15":{"terrain":"road"}, "Map_Tile_5_15":{"terrain":"road"}, "Map_Tile_4_5":{"terrain":"abyss"}, "Map_Tile_19_7":{"terrain":"road"}, "Map_Tile_13_3":{"terrain":"abyss"}, "Map_Tile_19_4":{"terrain":"abyss"}, "Map_Tile_0_7":{"terrain":"abyss"}, "Map_Tile_8_10":{"terrain":"abyss"}, "Map_Tile_24_7":{"terrain":"mountain"}, "Map_Tile_18_19":{"terrain":"abyss"}, "Map_Tile_17_12":{"terrain":"abyss"}, "Map_Tile_7_7":{"terrain":"river"}, "Map_Tile_0_16":{"terrain":"abyss"}, "Map_Tile_18_14":{"terrain":"abyss"}, "Map_Tile_6_3":{"terrain":"abyss"}, "Map_Tile_12_1":{"unit":{"attackerPlayerId":-1, "id":26, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":12, "y":1, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"tower", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":12, "y":1, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":1, "unitClassId":"tower", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_9_18":{"terrain":"abyss"}, "Map_Tile_19_10":{"terrain":"abyss"}, "Map_Tile_18_8":{"terrain":"abyss"}, "Map_Tile_9_3":{"terrain":"abyss"}, "Map_Tile_16_3":{"terrain":"plains"}, "Map_Tile_18_4":{"terrain":"abyss"}, "Map_Tile_12_10":{"terrain":"abyss"}, "Map_Tile_17_13":{"terrain":"abyss"}, "Map_Tile_18_2":{"terrain":"abyss"}, "Map_Tile_15_19":{"terrain":"abyss"}, "Map_Tile_7_6":{"terrain":"river"}, "Map_Tile_0_4":{"terrain":"abyss"}, "Map_Tile_13_18":{"unit":{"attackerPlayerId":-1, "id":15, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":13, "y":18, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":13, "y":18, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_17_18":{"terrain":"plains"}, "Map_Tile_0_14":{"terrain":"abyss"}, "Map_Tile_9_6":{"terrain":"abyss"}, "Map_Tile_11_6":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"abyss"}, "Map_Tile_7_5":{"terrain":"abyss"}, "Map_Tile_8_9":{"terrain":"road"}, "Map_Tile_9_12":{"terrain":"abyss"}, "Map_Tile_15_12":{"unit":{"attackerPlayerId":-1, "id":34, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":15, "y":12, "facing":3}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.5, "aliasId":"", "movementType":"walking", "id":"soldier", "weaponIds":["sword"], "isDamagingParentUnit":false, "tags":["soldier", "type.ground.light"], "isRecruitable":true, "isStructure":false, "verbCostMultiplier":1.0, "canReinforce":false, "cost":100, "isAttackable":true, "critConditionId":"", "moveRange":4, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":false, "weapons":[{"unitIdWhenAttacking":"", "canCounterAttack":true, "canAttackAir":false, "terrainExclusion":{}, "directionality":"omni", "id":"sword", "blockedByEnemies":false, "minRange":1, "canAttackSubmerged":false, "maxRange":1, "horizontalAndVerticalExtraWidth":0, "horizontalAndVerticalOnly":false, "canMoveAndAttack":true}], "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":15, "y":12, "facing":3}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":0, "unitClassId":"soldier", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"abyss_bridge"}, "Map_Tile_5_16":{"terrain":"road"}, "Map_Tile_19_15":{"terrain":"abyss"}, "Map_Tile_15_13":{"terrain":"abyss_bridge"}, "Map_Tile_8_4":{"terrain":"abyss"}, "Map_Tile_23_10":{"terrain":"abyss"}, "Map_Tile_15_11":{"terrain":"abyss_bridge"}, "Map_Tile_5_3":{"terrain":"mountain"}, "Map_Tile_14_13":{"terrain":"abyss_bridge"}, "Map_Tile_9_8":{"unit":{"attackerPlayerId":-1, "id":7, "rangedDamageTakenPercent":100, "miniGrooveId":"", "merchantDiscountMultiplier":0.0, "setGroove":null, "factionOverride":"", "attackerUnitClass":"", "garrisonClassId":"garrison", "attackerId":-1, "hasBeenKilled":false, "items":{}, "pos":{"x":9, "y":8, "facing":0}, "merchantDiscounts":{}, "loadedUnits":{}, "inTransport":false, "stunned":false, "unitClass":{"maxGroove":0, "canBeActivated":false, "canAttack":true, "loadCapacity":0, "passiveMultiplier":1.0, "aliasId":"", "movementType":"land_building", "id":"city", "weaponIds":{}, "isDamagingParentUnit":false, "tags":["structure"], "isRecruitable":true, "isStructure":true, "verbCostMultiplier":1.0, "canReinforce":true, "cost":500, "isAttackable":true, "critConditionId":"", "moveRange":0, "isCommander":false, "inAir":false, "resourceCost":1, "inWater":false, "canBeCaptured":true, "weapons":{}, "maxHealth":100, "reinforceMultiplier":1.0, "transportTags":{}, "recruitingCostMultiplier":1.0}, "canBeAttackedFromDistance":true, "canBeAttacked":true, "state":{}, "startPos":{"x":9, "y":8, "facing":0}, "itemDropNumber":0, "killedByLosing":false, "blessings":{}, "recruitDiscountMultiplier":0.0, "grooveCharge":0, "tentacled":false, "recruitDiscounts":{}, "itemId":"", "playerId":-1, "unitClassId":"city", "setHealth":null, "transportedBy":-1, "health":100, "canChargeGroove":true, "grooveId":"", "underwater":false, "attachedFlagId":-1, "recruits":{}, "hadTurn":false, "damageTakenPercent":100}, "terrain":"plains"}, "Map_Tile_5_5":{"terrain":"river"}, "Map_Tile_21_10":{"terrain":"road"}, "Map_Tile_9_16":{"terrain":"road"}, "Map_Tile_5_4":{"terrain":"abyss"}, "Map_Tile_13_16":{"terrain":"road"}, "Map_Tile_12_17":{"terrain":"plains"}, "Map_Tile_11_4":{"terrain":"abyss"}, "Map_Tile_0_5":{"terrain":"abyss"}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_12_4":{"terrain":"abyss"}, "Map_Tile_3_8":{"terrain":"abyss"}, "Map_Tile_8_15":{"terrain":"abyss"}, "Map_Tile_3_10":{"terrain":"abyss"}} \ No newline at end of file diff --git a/worlds/wargroove2/levels/Wagon_Freeway.json b/worlds/wargroove2/levels/Wagon_Freeway.json new file mode 100644 index 000000000000..314cee663706 --- /dev/null +++ b/worlds/wargroove2/levels/Wagon_Freeway.json @@ -0,0 +1 @@ +{"Map_Tile_25_0":{"terrain":"plains"}, "Map_Tile_10_11":{"terrain":"plains"}, "Map_Tile_5_10":{"terrain":"plains"}, "Map_Tile_12_4":{"terrain":"road"}, "Map_Tile_16_10":{"unit":{"id":20, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":10, "x":16}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":10, "x":16}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_17_1":{"unit":{"id":4, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":1, "x":17}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":1, "x":17}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_19_8":{"terrain":"road"}, "Map_Tile_1_10":{"terrain":"plains"}, "Map_Tile_16_11":{"unit":{"id":15, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":11, "x":16}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":11, "x":16}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_17_11":{"unit":{"id":14, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":11, "x":17}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":11, "x":17}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_13_4":{"terrain":"road"}, "Map_Tile_13_11":{"terrain":"plains"}, "Map_Tile_18_8":{"terrain":"road"}, "Map_Tile_1_6":{"terrain":"plains"}, "Map_Tile_15_6":{"terrain":"road"}, "Map_Tile_10_10":{"unit":{"id":37, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":10, "x":10}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":10, "x":10}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_7_11":{"terrain":"plains"}, "Map_Tile_12_3":{"terrain":"road"}, "Map_Tile_12_1":{"terrain":"road"}, "Map_Tile_18_1":{"unit":{"id":3, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":1, "x":18}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":1, "x":18}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_11_3":{"terrain":"road"}, "Map_Tile_27_1":{"terrain":"plains"}, "Map_Tile_11_5":{"terrain":"road"}, "Map_Tile_25_6":{"terrain":"road"}, "Map_Tile_0_1":{"terrain":"plains"}, "Map_Tile_12_6":{"terrain":"road"}, "Map_Tile_13_7":{"terrain":"road"}, "Map_Tile_4_9":{"terrain":"mountain"}, "Map_Tile_21_3":{"terrain":"road"}, "Map_Tile_4_4":{"terrain":"plains"}, "Map_Tile_25_3":{"terrain":"road"}, "Map_Tile_2_1":{"terrain":"plains"}, "Map_Tile_1_0":{"terrain":"plains"}, "Map_Tile_19_0":{"unit":{"id":7, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":0, "x":19}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":0, "x":19}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_25_2":{"terrain":"plains"}, "Map_Tile_10_8":{"terrain":"road"}, "Map_Tile_18_9":{"terrain":"wall"}, "Map_Tile_11_7":{"terrain":"road"}, "Map_Tile_19_11":{"unit":{"id":11, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":11, "x":19}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":11, "x":19}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_18_5":{"terrain":"road"}, "Map_Tile_10_2":{"terrain":"road"}, "Map_Tile_14_7":{"terrain":"road"}, "Map_Tile_4_11":{"terrain":"plains"}, "Map_Tile_28_1":{"terrain":"plains"}, "Map_Tile_13_9":{"terrain":"forest"}, "Map_Tile_3_4":{"terrain":"plains"}, "Map_Tile_28_2":{"terrain":"plains"}, "Map_Tile_20_5":{"terrain":"road"}, "Map_Tile_2_8":{"terrain":"plains"}, "Map_Tile_27_5":{"terrain":"road"}, "Map_Tile_20_7":{"terrain":"road"}, "Map_Tile_8_0":{"terrain":"forest"}, "Map_Tile_17_9":{"terrain":"wall"}, "Map_Tile_6_7":{"terrain":"plains"}, "Map_Tile_8_9":{"terrain":"forest"}, "Map_Tile_8_2":{"terrain":"forest"}, "Map_Tile_1_3":{"terrain":"plains"}, "Map_Tile_10_7":{"terrain":"road"}, "Map_Tile_16_1":{"unit":{"id":5, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":1, "x":16}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":1, "x":16}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_8_10":{"terrain":"plains"}, "Map_Tile_3_3":{"terrain":"plains"}, "Map_Tile_4_6":{"terrain":"plains"}, "Map_Tile_11_6":{"terrain":"road"}, "Map_Tile_22_10":{"terrain":"wall"}, "Map_Tile_6_10":{"terrain":"plains"}, "Map_Tile_18_2":{"terrain":"wall"}, "Map_Tile_21_6":{"terrain":"road"}, "Map_Tile_9_11":{"terrain":"plains"}, "Map_Tile_2_7":{"terrain":"plains"}, "Map_Tile_3_0":{"terrain":"plains"}, "Map_Tile_0_10":{"terrain":"plains"}, "Map_Tile_26_11":{"terrain":"plains"}, "Map_Tile_27_8":{"terrain":"road"}, "Map_Tile_6_6":{"terrain":"forest"}, "Map_Tile_12_0":{"terrain":"road"}, "Map_Tile_24_1":{"terrain":"plains"}, "Map_Tile_20_0":{"unit":{"id":6, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":0, "x":20}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":0, "x":20}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_11_10":{"terrain":"plains"}, "Map_Tile_7_0":{"terrain":"plains"}, "Map_Tile_12_9":{"terrain":"forest"}, "Map_Tile_4_10":{"terrain":"plains"}, "Map_Tile_22_7":{"terrain":"road"}, "Map_Tile_16_0":{"unit":{"id":10, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":0, "x":16}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":0, "x":16}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_11_2":{"terrain":"road"}, "Map_Tile_4_5":{"terrain":"plains"}, "Map_Tile_10_5":{"terrain":"road"}, "Map_Tile_24_5":{"terrain":"road"}, "Map_Tile_15_4":{"terrain":"road"}, "Map_Tile_16_7":{"terrain":"road"}, "Map_Tile_15_9":{"terrain":"wall"}, "Map_Tile_28_8":{"terrain":"road"}, "Map_Tile_9_10":{"terrain":"plains"}, "Map_Tile_5_11":{"terrain":"plains"}, "Map_Tile_13_0":{"terrain":"road"}, "Map_Tile_8_5":{"terrain":"forest"}, "Map_Tile_17_2":{"terrain":"wall"}, "Map_Tile_9_8":{"terrain":"road"}, "Map_Tile_16_8":{"terrain":"road"}, "Map_Tile_10_1":{"terrain":"road"}, "Map_Tile_26_1":{"terrain":"plains"}, "Map_Tile_14_1":{"terrain":"plains"}, "Map_Tile_22_2":{"terrain":"wall"}, "Map_Tile_2_11":{"terrain":"plains"}, "Map_Tile_10_0":{"terrain":"road"}, "Map_Tile_24_6":{"terrain":"road"}, "Map_Tile_2_2":{"terrain":"plains"}, "Map_Tile_7_7":{"terrain":"plains"}, "Map_Tile_1_5":{"terrain":"mountain"}, "Map_Tile_22_8":{"terrain":"road"}, "Map_Tile_15_2":{"terrain":"wall"}, "Map_Tile_21_2":{"terrain":"wall"}, "Player_2":{"recruit_turtle":true, "recruit_caravel":true, "recruit_balloon":true, "recruit_wagon":true, "recruit_merman":true, "recruit_harpoonship":true, "recruit_ballista":false, "recruit_thief":true, "recruit_dragon":true, "recruit_spearman":true, "recruit_griffin_walking":true, "recruit_dog":true, "recruit_kraken":true, "recruit_trebuchet":false, "recruit_soldier":true, "recruit_warship":true, "recruit_travelboat":true, "recruit_harpy":true, "recruit_giant":true, "recruit_frog":true, "recruit_knight":true, "recruit_archer":true, "recruit_witch":true, "gold":800, "team":1, "recruit_rifleman":true, "recruit_mage":true}, "Map_Tile_1_9":{"unit":{"id":27, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"barracks", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":9, "x":1}, "attackerUnitClass":"", "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"barracks", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":9, "x":1}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_6_8":{"terrain":"plains"}, "Map_Tile_23_3":{"terrain":"road"}, "Map_Tile_9_3":{"terrain":"road"}, "Map_Tile_12_7":{"terrain":"road"}, "Map_Tile_23_1":{"terrain":"plains"}, "Map_Tile_18_7":{"terrain":"road"}, "Map_Tile_3_8":{"terrain":"plains"}, "Map_Tile_7_3":{"terrain":"plains"}, "Map_Tile_27_9":{"terrain":"plains"}, "Map_Tile_7_4":{"terrain":"plains"}, "Map_Tile_15_10":{"terrain":"wall"}, "Map_Tile_15_11":{"terrain":"wall"}, "Map_Tile_24_10":{"terrain":"plains"}, "Map_Tile_10_9":{"terrain":"forest"}, "Map_Tile_6_3":{"terrain":"carpet"}, "Map_Tile_18_6":{"terrain":"road"}, "Map_Tile_9_1":{"terrain":"road"}, "Map_Tile_6_2":{"terrain":"plains"}, "Map_Tile_27_3":{"terrain":"road"}, "Map_Tile_4_8":{"terrain":"plains"}, "Map_Tile_0_9":{"terrain":"plains"}, "Map_Tile_3_9":{"terrain":"plains"}, "Map_Tile_7_1":{"terrain":"plains"}, "Map_Tile_14_0":{"terrain":"plains"}, "Map_Tile_23_0":{"terrain":"plains"}, "Map_Tile_2_0":{"terrain":"plains"}, "Map_Tile_0_4":{"terrain":"plains"}, "Map_Tile_5_4":{"terrain":"carpet"}, "Map_Tile_29_7":{"terrain":"road"}, "Map_Tile_17_0":{"unit":{"id":9, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":0, "x":17}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":0, "x":17}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_12_5":{"terrain":"road"}, "Map_Tile_18_4":{"terrain":"road"}, "Map_Tile_24_0":{"terrain":"plains"}, "Map_Tile_8_6":{"terrain":"forest"}, "Map_Tile_20_4":{"terrain":"road"}, "Map_Tile_29_8":{"terrain":"road"}, "Map_Tile_3_2":{"terrain":"plains"}, "Map_Tile_10_4":{"terrain":"road"}, "Map_Tile_1_8":{"terrain":"plains"}, "Map_Tile_15_1":{"terrain":"wall"}, "Map_Tile_19_2":{"terrain":"wall"}, "Map_Tile_7_9":{"terrain":"plains"}, "Map_Tile_12_10":{"terrain":"plains"}, "Map_Tile_27_4":{"terrain":"road"}, "Map_Tile_9_4":{"terrain":"road"}, "Map_Tile_25_10":{"terrain":"plains"}, "Map_Tile_13_10":{"terrain":"plains"}, "Map_Tile_11_0":{"terrain":"road"}, "Objectives":["Capture the Guardian (No Requirements).", "Build an Air Trooper.", "Win with standard conditions (Requires Wagon and Spearman)."], "Map_Tile_19_1":{"unit":{"id":2, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":1, "x":19}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":1, "x":19}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_13_5":{"terrain":"road"}, "Map_Tile_6_5":{"terrain":"plains"}, "Map_Tile_1_2":{"terrain":"plains"}, "Map_Tile_9_6":{"terrain":"road"}, "Triggers":[{"id":"Export", "actions":[{"id":"ap_export", "parameters":["241", "Wagon Freeway", "Fly Sniper", "Capture the Guardian (No Requirements).", "Build an Air Trooper.", "", "Win with standard conditions (Requires Wagon and Spearman)."], "enabled":true}], "conditions":{}, "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true}, {"id":"$trigger_default_defeat_no_units", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["current", "0", "0", "*unit_structure", "-1"], "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"id":"$trigger_default_defeat_commander", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["*commander", "current", "-1"], "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"id":"$trigger_default_defeat_hq", "actions":[{"id":"eliminate", "parameters":["current"], "enabled":true}], "conditions":[{"id":"unit_lost", "parameters":["hq", "current", "-1"], "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"id":"$trigger_default_victory", "actions":[{"id":"victory", "parameters":["current"], "enabled":true}], "conditions":[{"id":"number_of_opponents", "parameters":["current", "0", "0"], "enabled":true}], "isIntro":false, "players":[1, 1, 0, 0, 0, 0, 0, 0], "recurring":"oncePerPlayer", "enabled":true}, {"id":"Guardian Captured (253025)", "actions":[{"id":"ap_location_send", "parameters":["253025"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["current", "1", "0", "golem_unit", "-1"], "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true}, {"id":"Air Trooper Built (253026)", "actions":[{"id":"ap_location_send", "parameters":["253026"], "enabled":true}], "conditions":[{"id":"unit_presence", "parameters":["current", "1", "0", "griffin_walking", "-1"], "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true}, {"id":"P1 Victory (253024)", "actions":[{"id":"ap_location_send", "parameters":["253024"], "enabled":true}], "conditions":[{"id":"player_victorious", "parameters":["current"], "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"once", "enabled":true}, {"id":"TP", "actions":[{"id":"unit_random_teleport", "parameters":["*unit_structure", "P1", "0", "0", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "P1", "1", "1", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "2", "2", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "3", "3", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "4", "4", "1"], "enabled":true}, {"id":"unit_random_teleport", "parameters":["*unit_structure", "any", "5", "5", "1"], "enabled":true}], "conditions":{}, "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true}, {"id":"Spawn Wagons", "actions":[{"id":"ap_spawn_unit", "parameters":["wagon", "0", "P1", "1", "1", "3", "1", "undefined", "centre"], "enabled":true}], "conditions":[{"id":"ap_has_item", "parameters":["252001", "0", "1"], "enabled":true}], "isIntro":false, "players":[1, 0, 0, 0, 0, 0, 0, 0], "recurring":"start_of_match", "enabled":true}], "Map_Tile_14_2":{"terrain":"plains"}, "Map_Tile_8_8":{"terrain":"forest"}, "Locations":{"1":{"id":1, "setArea":null, "getArea":null, "interactable":false, "centre":{"y":5, "x":26}, "positions":[{"y":5, "x":25}, {"y":5, "x":26}, {"y":4, "x":26}, {"y":6, "x":26}, {"y":6, "x":25}, {"y":4, "x":25}], "name":"P1 TP2"}, "2":{"id":2, "setArea":null, "getArea":null, "interactable":false, "centre":{"y":6, "x":15}, "positions":[{"y":4, "x":16}, {"y":5, "x":16}, {"y":6, "x":16}, {"y":7, "x":16}, {"y":8, "x":16}, {"y":3, "x":16}, {"y":3, "x":15}, {"y":4, "x":15}, {"y":5, "x":15}, {"y":6, "x":15}, {"y":7, "x":15}, {"y":8, "x":15}, {"y":3, "x":14}, {"y":4, "x":14}, {"y":5, "x":14}, {"y":6, "x":14}, {"y":7, "x":14}, {"y":8, "x":14}], "name":"P2 TP1"}, "3":{"id":3, "setArea":null, "getArea":null, "interactable":false, "centre":{"y":8, "x":7}, "positions":[{"y":10, "x":10}, {"y":11, "x":8}, {"y":11, "x":9}, {"y":10, "x":9}, {"y":10, "x":8}, {"y":10, "x":7}, {"y":11, "x":7}, {"y":10, "x":6}, {"y":11, "x":6}, {"y":9, "x":7}, {"y":9, "x":6}, {"y":8, "x":6}, {"y":8, "x":7}, {"y":7, "x":6}, {"y":7, "x":7}, {"y":6, "x":7}, {"y":5, "x":7}, {"y":5, "x":6}, {"y":6, "x":6}, {"y":4, "x":7}, {"y":3, "x":7}, {"y":2, "x":7}, {"y":3, "x":6}, {"y":4, "x":6}], "name":"P2 TP2"}, "4":{"id":4, "setArea":null, "getArea":null, "interactable":false, "centre":{"y":5, "x":1}, "positions":[{"y":9, "x":1}, {"y":6, "x":2}, {"y":1, "x":3}, {"y":0, "x":0}, {"y":2, "x":3}, {"y":3, "x":3}, {"y":4, "x":3}, {"y":4, "x":2}, {"y":5, "x":2}, {"y":10, "x":1}, {"y":10, "x":2}, {"y":11, "x":2}, {"y":11, "x":1}, {"y":10, "x":0}, {"y":9, "x":0}, {"y":8, "x":0}, {"y":7, "x":0}, {"y":6, "x":0}, {"y":5, "x":0}, {"y":4, "x":0}, {"y":3, "x":0}, {"y":2, "x":0}, {"y":1, "x":0}, {"y":11, "x":0}, {"y":8, "x":1}, {"y":7, "x":1}, {"y":6, "x":1}, {"y":5, "x":1}, {"y":4, "x":1}, {"y":3, "x":1}, {"y":2, "x":1}, {"y":1, "x":1}, {"y":0, "x":1}, {"y":0, "x":2}, {"y":0, "x":3}, {"y":1, "x":2}, {"y":2, "x":2}, {"y":3, "x":2}, {"y":9, "x":2}], "name":"P2 TP3"}, "5":{"id":5, "setArea":null, "getArea":null, "interactable":false, "centre":{"y":7, "x":5}, "positions":[{"y":7, "x":4}, {"y":8, "x":4}, {"y":7, "x":5}, {"y":6, "x":5}, {"y":6, "x":4}, {"y":5, "x":5}, {"y":5, "x":4}, {"y":8, "x":5}], "name":"P2 TP4"}, "0":{"id":0, "setArea":null, "getArea":null, "interactable":false, "centre":{"y":6, "x":28}, "positions":[{"y":3, "x":28}, {"y":4, "x":28}, {"y":5, "x":28}, {"y":6, "x":28}, {"y":7, "x":28}, {"y":8, "x":28}, {"y":9, "x":28}, {"y":10, "x":28}, {"y":9, "x":29}, {"y":8, "x":29}, {"y":7, "x":29}, {"y":6, "x":29}, {"y":5, "x":29}, {"y":4, "x":29}, {"y":3, "x":29}, {"y":2, "x":29}, {"y":1, "x":28}, {"y":2, "x":28}], "name":"P1 TP"}}, "Map_Tile_13_8":{"terrain":"road"}, "Map_Tile_29_11":{"terrain":"plains"}, "Map_Tile_6_4":{"terrain":"carpet"}, "Map_Tile_23_11":{"terrain":"plains"}, "Map_Tile_17_7":{"terrain":"road"}, "Map_Tile_15_5":{"terrain":"road"}, "Map_Tile_13_2":{"terrain":"road"}, "Map_Tile_29_9":{"terrain":"plains"}, "Map_Tile_15_7":{"terrain":"road"}, "Map_Tile_29_5":{"unit":{"id":21, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"commander_mercia", "items":{}, "blessings":{}, "health":100, "grooveId":"heal_aura", "pos":{"facing":3, "y":5, "x":29}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"commander_mercia", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"merciaSword", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":true, "minRange":1, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"walking", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":4, "inWater":false, "isRecruitable":false, "cost":500, "passiveMultiplier":1.0, "canReinforce":false, "canBeActivated":false, "isCommander":true, "maxGroove":250, "weaponIds":["merciaSword"], "resourceCost":3, "tags":["commander", "type.ground.light"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":0, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":5, "x":29}, "recruitDiscounts":{}}, "terrain":"road"}, "Map_Tile_28_9":{"terrain":"plains"}, "Map_Tile_5_1":{"terrain":"mountain"}, "Map_Tile_19_9":{"terrain":"wall"}, "Map_Tile_1_4":{"terrain":"plains"}, "Map_Tile_8_7":{"terrain":"forest"}, "Map_Tile_21_9":{"terrain":"wall"}, "Map_Tile_29_4":{"terrain":"road"}, "Map_Tile_29_3":{"terrain":"road"}, "Map_Tile_11_9":{"terrain":"forest"}, "Map_Tile_18_0":{"unit":{"id":8, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":0, "x":18}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":0, "x":18}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_14_3":{"terrain":"road"}, "Map_Tile_26_6":{"terrain":"road"}, "Map_Tile_29_0":{"terrain":"plains"}, "Map_Tile_14_5":{"terrain":"road"}, "Map_Tile_28_3":{"unit":{"id":24, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"barracks", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":3, "x":28}, "attackerUnitClass":"", "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"barracks", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":0, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":3, "x":28}, "recruitDiscounts":{}}, "terrain":"road"}, "Map_Tile_28_10":{"unit":{"id":23, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"barracks", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":10, "x":28}, "attackerUnitClass":"", "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"barracks", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":0, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":10, "x":28}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_28_7":{"terrain":"road"}, "Map_Tile_17_6":{"terrain":"road"}, "Map_Tile_25_4":{"terrain":"road"}, "Map_Tile_19_4":{"terrain":"road"}, "Map_Tile_20_2":{"terrain":"wall"}, "Map_Tile_2_4":{"terrain":"plains"}, "Map_Tile_28_5":{"terrain":"road"}, "Map_Tile_4_2":{"terrain":"plains"}, "Map_Tile_28_4":{"terrain":"road"}, "Map_Tile_28_0":{"terrain":"plains"}, "Map_Tile_27_11":{"terrain":"plains"}, "Map_Tile_25_7":{"terrain":"road"}, "Map_Tile_11_1":{"terrain":"road"}, "Map_Tile_27_10":{"terrain":"plains"}, "Map_Tile_23_6":{"terrain":"road"}, "Map_Tile_27_7":{"terrain":"road"}, "Map_Tile_23_7":{"terrain":"road"}, "Map_Tile_27_6":{"terrain":"road"}, "Map_Tile_27_2":{"terrain":"plains"}, "Map_Tile_11_8":{"terrain":"road"}, "Map_Tile_27_0":{"terrain":"plains"}, "Map_Tile_26_10":{"terrain":"plains"}, "Map_Tile_26_9":{"unit":{"id":38, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"fortified_city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":9, "x":26}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"fortified_garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"fortified_city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":1000, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["fortified_city"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":9, "x":26}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_0_6":{"terrain":"plains"}, "Map_Tile_22_9":{"terrain":"wall"}, "Map_Tile_25_5":{"unit":{"id":22, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"hq", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":5, "x":25}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"hq", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":3000, "passiveMultiplier":1.0, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":0, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":5, "x":25}, "recruitDiscounts":{}}, "terrain":"road"}, "Map_Tile_26_3":{"terrain":"road"}, "Map_Tile_26_5":{"terrain":"road"}, "Map_Tile_26_4":{"terrain":"road"}, "Map_Tile_26_7":{"terrain":"road"}, "Map_Tile_29_10":{"terrain":"plains"}, "Map_Tile_26_0":{"terrain":"plains"}, "Map_Tile_1_1":{"terrain":"forest"}, "Map_Tile_14_9":{"terrain":"forest"}, "Map_Tile_25_11":{"terrain":"plains"}, "Flags":{}, "Map_Tile_25_9":{"terrain":"plains"}, "Map_Tile_10_6":{"terrain":"road"}, "Map_Tile_20_9":{"terrain":"wall"}, "Map_Tile_25_8":{"terrain":"road"}, "Map_Tile_28_6":{"unit":{"id":25, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"tower", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":6, "x":28}, "attackerUnitClass":"", "itemId":"", "recruits":["griffin_walking", "balloon", "harpy", "witch", "dragon"], "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"tower", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":0, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":6, "x":28}, "recruitDiscounts":{}}, "terrain":"road"}, "Map_Name":"Wagon Freeway", "Map_Tile_25_1":{"terrain":"plains"}, "Map_Tile_6_11":{"terrain":"forest"}, "Map_Tile_24_4":{"terrain":"road"}, "Map_Tile_24_9":{"terrain":"plains"}, "Map_Tile_24_8":{"terrain":"road"}, "Map_Tile_24_7":{"terrain":"road"}, "Map_Tile_6_1":{"terrain":"mountain"}, "Map_Tile_24_3":{"terrain":"road"}, "Map_Tile_22_5":{"terrain":"road"}, "Map_Tile_8_1":{"terrain":"forest"}, "Map_Tile_1_7":{"terrain":"forest"}, "Map_Tile_23_10":{"terrain":"plains"}, "Map_Tile_5_9":{"terrain":"plains"}, "Map_Tile_23_8":{"terrain":"road"}, "Map_Tile_21_8":{"terrain":"road"}, "Map_Tile_12_11":{"terrain":"plains"}, "Map_Tile_23_2":{"terrain":"plains"}, "Map_Tile_20_1":{"unit":{"id":1, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":1, "x":20}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":1, "x":20}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_22_11":{"terrain":"wall"}, "Map_Tile_26_8":{"terrain":"road"}, "Map_Tile_16_5":{"unit":{"id":40, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"golem_unit", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":5, "x":16}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"golem_unit", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"golem_slam", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":false, "terrainExclusion":{}, "canMoveAndAttack":true, "minRange":1, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":1, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":7, "inWater":false, "isRecruitable":true, "cost":2000, "passiveMultiplier":2.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["golem_slam"], "resourceCost":3, "tags":["golem", "tall"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":5, "x":16}, "recruitDiscounts":{}}, "terrain":"road"}, "Map_Tile_7_6":{"terrain":"plains"}, "Map_Tile_24_2":{"terrain":"plains"}, "Map_Tile_2_5":{"terrain":"plains"}, "Map_Tile_7_10":{"terrain":"plains"}, "Map_Tile_12_8":{"terrain":"road"}, "Map_Tile_11_11":{"terrain":"plains"}, "Map_Tile_22_4":{"terrain":"road"}, "Map_Tile_22_3":{"terrain":"road"}, "Map_Tile_9_0":{"terrain":"road"}, "Map_Tile_22_1":{"terrain":"wall"}, "Map_Tile_22_0":{"terrain":"wall"}, "Map_Tile_21_11":{"unit":{"id":31, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":11, "x":21}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":11, "x":21}, "recruitDiscounts":{}}, "terrain":"plains"}, "Player_Count":2, "Map_Tile_5_2":{"terrain":"plains"}, "Map_Tile_21_10":{"unit":{"id":29, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":10, "x":21}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":10, "x":21}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_23_5":{"terrain":"road"}, "Map_Tile_16_9":{"terrain":"wall"}, "Map_Tile_17_5":{"terrain":"road"}, "Map_Tile_17_3":{"terrain":"road"}, "Map_Tile_21_5":{"terrain":"road"}, "Map_Tile_21_4":{"terrain":"road"}, "Map_Tile_21_1":{"unit":{"id":30, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":1, "x":21}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":1, "x":21}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_3_6":{"terrain":"plains"}, "Map_Tile_21_0":{"unit":{"id":32, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":0, "x":21}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":0, "x":21}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_20_11":{"unit":{"id":12, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":11, "x":20}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":11, "x":20}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_5_8":{"terrain":"mountain"}, "Map_Tile_0_5":{"terrain":"plains"}, "Map_Tile_14_11":{"terrain":"plains"}, "Map_Tile_20_6":{"terrain":"road"}, "Map_Tile_2_10":{"terrain":"plains"}, "Map_Tile_4_7":{"unit":{"id":26, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"hq", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":7, "x":4}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"hq", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":3000, "passiveMultiplier":1.0, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":7, "x":4}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_10_3":{"terrain":"road"}, "Map_Tile_2_3":{"terrain":"plains"}, "Map_Tile_29_1":{"terrain":"plains"}, "Map_Tile_3_7":{"terrain":"plains"}, "Map_Tile_20_3":{"terrain":"road"}, "Map_Tile_9_9":{"terrain":"forest"}, "Map_Tile_9_7":{"terrain":"road"}, "Map_Tile_0_11":{"terrain":"plains"}, "Map_Tile_17_8":{"terrain":"road"}, "Map_Tile_0_0":{"unit":{"id":35, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":0, "x":0}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":0, "x":0}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_1_11":{"terrain":"plains"}, "Map_Tile_3_11":{"terrain":"plains"}, "Map_Tile_17_10":{"unit":{"id":19, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":10, "x":17}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":10, "x":17}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_19_10":{"unit":{"id":17, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":10, "x":19}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":10, "x":19}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_19_7":{"terrain":"road"}, "Map_Tile_8_11":{"unit":{"id":36, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":11, "x":8}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":11, "x":8}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_26_2":{"unit":{"id":39, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"fortified_city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":2, "x":26}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"fortified_garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"fortified_city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":1000, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["fortified_city"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":2, "x":26}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_23_9":{"terrain":"plains"}, "Map_Tile_15_3":{"terrain":"road"}, "Map_Tile_3_5":{"terrain":"forest"}, "Map_Tile_14_8":{"terrain":"road"}, "Map_Tile_5_5":{"terrain":"plains"}, "Map_Tile_5_7":{"terrain":"plains"}, "Map_Tile_2_6":{"unit":{"id":34, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":6, "x":2}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":6, "x":2}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_23_4":{"terrain":"road"}, "Map_Tile_2_9":{"terrain":"plains"}, "Map_Tile_8_3":{"terrain":"forest"}, "Map_Tile_4_0":{"terrain":"plains"}, "Map_Tile_5_6":{"terrain":"plains"}, "Map_Tile_19_3":{"terrain":"road"}, "Map_Tile_0_8":{"terrain":"plains"}, "Map_Tile_6_0":{"terrain":"mountain"}, "Map_Tile_3_1":{"unit":{"id":28, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"barracks", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":1, "x":3}, "attackerUnitClass":"", "itemId":"", "recruits":["soldier", "dog", "spearman", "wagon", "mage", "archer", "knight", "ballista", "trebuchet", "giant"], "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"barracks", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":1, "x":3}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_19_6":{"terrain":"road"}, "Map_Tile_24_11":{"terrain":"plains"}, "Map_Tile_20_10":{"unit":{"id":16, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":10, "x":20}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":10, "x":20}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_6_9":{"terrain":"plains"}, "Map_Tile_18_3":{"terrain":"road"}, "Map_Size":{"y":12, "x":30}, "Map_Tile_18_11":{"unit":{"id":13, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"ballista", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":11, "x":18}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"ballista", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"ballistaBolt", "canAttackAir":true, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":2, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":6, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":6, "inWater":false, "isRecruitable":true, "cost":800, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["ballistaBolt"], "resourceCost":1, "tags":["ballista", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":11, "x":18}, "recruitDiscounts":{}}, "terrain":"plains"}, "Player_1":{"recruit_turtle":true, "recruit_caravel":true, "recruit_balloon":true, "recruit_wagon":true, "recruit_merman":true, "recruit_harpoonship":true, "recruit_ballista":true, "recruit_thief":true, "recruit_dragon":true, "recruit_spearman":true, "recruit_griffin_walking":true, "recruit_dog":true, "recruit_kraken":true, "recruit_trebuchet":true, "recruit_soldier":true, "recruit_warship":true, "recruit_travelboat":true, "recruit_harpy":true, "recruit_giant":true, "recruit_frog":true, "recruit_knight":true, "recruit_archer":true, "recruit_witch":true, "gold":500, "team":0, "recruit_rifleman":true, "recruit_mage":true}, "Map_Tile_0_3":{"terrain":"plains"}, "Map_Tile_14_4":{"terrain":"road"}, "Map_Tile_13_6":{"terrain":"road"}, "Map_Tile_8_4":{"terrain":"forest"}, "Map_Tile_17_4":{"terrain":"road"}, "Map_Tile_21_7":{"terrain":"road"}, "Author":"Fly Sniper", "Map_Tile_9_5":{"terrain":"road"}, "Map_Tile_20_8":{"terrain":"road"}, "Map_Tile_22_6":{"terrain":"road"}, "Map_Tile_16_6":{"terrain":"road"}, "Map_Tile_4_3":{"terrain":"plains"}, "Map_Tile_12_2":{"terrain":"road"}, "Map_Tile_14_10":{"terrain":"plains"}, "Map_Tile_16_4":{"terrain":"road"}, "Map_Tile_16_3":{"terrain":"road"}, "Map_Tile_16_2":{"terrain":"wall"}, "Map_Tile_3_10":{"terrain":"forest"}, "Map_Tile_13_1":{"terrain":"road"}, "Map_Tile_28_11":{"terrain":"plains"}, "Map_Tile_7_2":{"terrain":"plains"}, "Map_Tile_13_3":{"terrain":"road"}, "Map_Tile_11_4":{"terrain":"road"}, "Map_Tile_15_8":{"terrain":"road"}, "Map_Tile_29_6":{"terrain":"road"}, "Map_Tile_19_5":{"terrain":"road"}, "Map_Tile_15_0":{"terrain":"wall"}, "Map_Tile_0_7":{"terrain":"plains"}, "Map_Tile_5_3":{"terrain":"carpet"}, "Map_Tile_14_6":{"terrain":"road"}, "Map_Tile_7_5":{"unit":{"id":33, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"city", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":0, "y":5, "x":7}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"garrison", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"city", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":{}, "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":true, "movementType":"land_building", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":0, "inWater":false, "isRecruitable":true, "cost":500, "passiveMultiplier":1.0, "canReinforce":true, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":{}, "resourceCost":1, "tags":["structure"], "canBeCaptured":true}, "attachedFlagId":-1, "playerId":-1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":0, "y":5, "x":7}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_4_1":{"terrain":"plains"}, "Map_Tile_5_0":{"terrain":"mountain"}, "Map_Tile_29_2":{"terrain":"plains"}, "Map_Tile_0_2":{"terrain":"plains"}, "Counters":{}, "Map_Tile_18_10":{"unit":{"id":18, "merchantDiscountMultiplier":0.0, "rangedDamageTakenPercent":100, "hadTurn":false, "attackerId":-1, "miniGrooveId":"", "stunned":false, "underwater":false, "canChargeGroove":true, "unitClassId":"trebuchet", "items":{}, "blessings":{}, "health":100, "grooveId":"", "pos":{"facing":3, "y":10, "x":18}, "attackerUnitClass":"", "itemId":"", "recruits":{}, "state":{}, "recruitDiscountMultiplier":0.0, "canBeAttackedFromDistance":true, "canBeAttacked":true, "itemDropNumber":0, "merchantDiscounts":{}, "transportedBy":-1, "setGroove":null, "hasBeenKilled":false, "garrisonClassId":"", "inTransport":false, "damageTakenPercent":100, "setHealth":null, "unitClass":{"id":"trebuchet", "reinforceMultiplier":1.0, "canAttack":true, "recruitingCostMultiplier":1.0, "weapons":[{"id":"trebuchetSling", "canAttackAir":false, "blockedByEnemies":false, "unitIdWhenAttacking":"", "canCounterAttack":true, "terrainExclusion":{}, "canMoveAndAttack":false, "minRange":3, "horizontalAndVerticalOnly":false, "horizontalAndVerticalExtraWidth":0, "maxRange":5, "directionality":"omni", "canAttackSubmerged":false}], "verbCostMultiplier":1.0, "transportTags":{}, "isDamagingParentUnit":false, "isAttackable":true, "aliasId":"", "loadCapacity":0, "isStructure":false, "movementType":"wheels", "inAir":false, "critConditionId":"", "maxHealth":100, "moveRange":5, "inWater":false, "isRecruitable":true, "cost":1100, "passiveMultiplier":1.5, "canReinforce":false, "canBeActivated":false, "isCommander":false, "maxGroove":0, "weaponIds":["trebuchetSling"], "resourceCost":3, "tags":["trebuchet", "type.ground.heavy"], "canBeCaptured":false}, "attachedFlagId":-1, "playerId":1, "tentacled":false, "loadedUnits":{}, "killedByLosing":false, "attackerPlayerId":-1, "factionOverride":"", "grooveCharge":0, "startPos":{"facing":3, "y":10, "x":18}, "recruitDiscounts":{}}, "terrain":"plains"}, "Map_Tile_9_2":{"terrain":"road"}, "Map_Tile_7_8":{"terrain":"plains"}} \ No newline at end of file diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 635a56796b2f..254064098db9 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -2,24 +2,27 @@ Archipelago init file for The Witness """ import dataclasses - -from typing import Dict, Optional, cast -from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState -from Options import PerGameCommonOptions, Toggle -from .presets import witness_option_presets -from worlds.AutoWorld import World, WebWorld +from logging import error, warning +from typing import Any, Dict, List, Optional, cast + +from BaseClasses import CollectionState, Entrance, Location, Region, Tutorial + +from Options import OptionError, PerGameCommonOptions, Toggle +from worlds.AutoWorld import WebWorld, World + +from .data import static_items as static_witness_items +from .data import static_locations as static_witness_locations +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import DoorItemDefinition, ItemData +from .data.utils import get_audio_logs +from .hints import CompactItemData, create_all_hints, make_compact_hint_data, make_laser_hints +from .locations import WitnessPlayerLocations +from .options import TheWitnessOptions, witness_option_groups +from .player_items import WitnessItem, WitnessPlayerItems from .player_logic import WitnessPlayerLogic -from .static_logic import StaticWitnessLogic, ItemCategory, DoorItemDefinition -from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ - get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \ - make_extra_location_hints, create_all_hints, make_laser_hints, make_compact_hint_data, CompactItemData -from .locations import WitnessPlayerLocations, StaticWitnessLocations -from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData -from .regions import WitnessRegions +from .presets import witness_option_presets +from .regions import WitnessPlayerRegions from .rules import set_rules -from .options import TheWitnessOptions -from .utils import get_audio_logs, get_laser_shuffle -from logging import warning, error class WitnessWebWorld(WebWorld): @@ -34,6 +37,7 @@ class WitnessWebWorld(WebWorld): )] options_presets = witness_option_presets + option_groups = witness_option_groups class WitnessWorld(World): @@ -50,98 +54,124 @@ class WitnessWorld(World): options: TheWitnessOptions item_name_to_id = { - name: data.ap_code for name, data in StaticWitnessItems.item_data.items() + # ITEM_DATA doesn't have any event items in it + name: cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items() } - location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID - item_name_groups = StaticWitnessItems.item_groups - location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS + location_name_to_id = static_witness_locations.ALL_LOCATIONS_TO_ID + item_name_groups = static_witness_items.ITEM_GROUPS + location_name_groups = static_witness_locations.AREA_LOCATION_GROUPS required_client_version = (0, 4, 5) - def __init__(self, multiworld: "MultiWorld", player: int): - super().__init__(multiworld, player) - - self.player_logic = None - self.locat = None - self.items = None - self.regio = None + player_logic: WitnessPlayerLogic + player_locations: WitnessPlayerLocations + player_items: WitnessPlayerItems + player_regions: WitnessPlayerRegions - self.log_ids_to_hints: Dict[int, CompactItemData] = dict() - self.laser_ids_to_hints: Dict[int, CompactItemData] = dict() + log_ids_to_hints: Dict[int, CompactItemData] + laser_ids_to_hints: Dict[int, CompactItemData] - self.items_placed_early = [] - self.own_itempool = [] + items_placed_early: List[str] + own_itempool: List[WitnessItem] - def _get_slot_data(self): + def _get_slot_data(self) -> Dict[str, Any]: return { - 'seed': self.random.randrange(0, 1000000), - 'victory_location': int(self.player_logic.VICTORY_LOCATION, 16), - 'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID, - 'item_id_to_door_hexes': StaticWitnessItems.get_item_to_door_mappings(), - 'door_hexes_in_the_pool': self.items.get_door_ids_in_pool(), - 'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(), - 'disabled_entities': [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES], - 'log_ids_to_hints': self.log_ids_to_hints, - 'laser_ids_to_hints': self.laser_ids_to_hints, - 'progressive_item_lists': self.items.get_progressive_item_ids_in_pool(), - 'obelisk_side_id_to_EPs': StaticWitnessLogic.OBELISK_SIDE_ID_TO_EP_HEXES, - 'precompleted_puzzles': [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], - 'entity_to_name': StaticWitnessLogic.ENTITY_ID_TO_NAME, + "seed": self.random.randrange(0, 1000000), + "victory_location": int(self.player_logic.VICTORY_LOCATION, 16), + "panelhex_to_id": self.player_locations.CHECK_PANELHEX_TO_ID, + "item_id_to_door_hexes": static_witness_items.get_item_to_door_mappings(), + "door_hexes_in_the_pool": self.player_items.get_door_ids_in_pool(), + "symbols_not_in_the_game": self.player_items.get_symbol_ids_not_in_pool(), + "disabled_entities": [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES], + "log_ids_to_hints": self.log_ids_to_hints, + "laser_ids_to_hints": self.laser_ids_to_hints, + "progressive_item_lists": self.player_items.get_progressive_item_ids_in_pool(), + "obelisk_side_id_to_EPs": static_witness_logic.OBELISK_SIDE_ID_TO_EP_HEXES, + "precompleted_puzzles": [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], + "entity_to_name": static_witness_logic.ENTITY_ID_TO_NAME, } - def generate_early(self): + def determine_sufficient_progression(self) -> None: + """ + Determine whether there are enough progression items in this world to consider it "interactive". + In the case of singleplayer, this just outputs a warning. + In the case of multiplayer, the requirements are a bit stricter and an Exception is raised. + """ + + # A note on Obelisk Keys: + # Obelisk Keys are never relevant in singleplayer, because the locations they lock are irrelevant to in-game + # progress and irrelevant to all victory conditions. Thus, I consider them "fake progression" for singleplayer. + # However, those locations could obviously contain big items needed for other players, so I consider + # "Obelisk Keys only" valid for multiworld. + + # A note on Laser Shuffle: + # In singleplayer, I don't mind "Ice Rod Hunt" type gameplay, so "laser shuffle only" is valid. + # However, I do not want to allow "Ice Rod Hunt" style gameplay in multiworld, so "laser shuffle only" is + # not considered interactive enough for multiworld. + + interacts_sufficiently_with_multiworld = ( + self.options.shuffle_symbols + or self.options.shuffle_doors + or self.options.obelisk_keys and self.options.shuffle_EPs + ) + + has_locally_relevant_progression = ( + self.options.shuffle_symbols + or self.options.shuffle_doors + or self.options.shuffle_lasers + or self.options.shuffle_boat + or self.options.early_caves == "add_to_pool" and self.options.victory_condition == "challenge" + ) + + if not has_locally_relevant_progression and self.multiworld.players == 1: + warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression" + f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.") + elif not interacts_sufficiently_with_multiworld and self.multiworld.players > 1: + raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough" + f" progression items that can be placed in other players' worlds. Please turn on Symbol" + f" Shuffle, Door Shuffle, or Obelisk Keys.") + + def generate_early(self) -> None: disabled_locations = self.options.exclude_locations.value self.player_logic = WitnessPlayerLogic( self, disabled_locations, self.options.start_inventory.value ) - self.locat: WitnessPlayerLocations = WitnessPlayerLocations(self, self.player_logic) - self.items: WitnessPlayerItems = WitnessPlayerItems( - self, self.player_logic, self.locat + self.player_locations: WitnessPlayerLocations = WitnessPlayerLocations(self, self.player_logic) + self.player_items: WitnessPlayerItems = WitnessPlayerItems( + self, self.player_logic, self.player_locations ) - self.regio: WitnessRegions = WitnessRegions(self.locat, self) + self.player_regions: WitnessPlayerRegions = WitnessPlayerRegions(self.player_locations, self) - interacts_with_multiworld = ( - self.options.shuffle_symbols or - self.options.shuffle_doors or - self.options.shuffle_lasers == "anywhere" - ) + self.log_ids_to_hints = {} - has_progression = ( - interacts_with_multiworld - or self.options.shuffle_lasers == "local" - or self.options.shuffle_boat - or self.options.early_caves == "add_to_pool" - ) - - if not has_progression and self.multiworld.players == 1: - warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression" - f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.") - elif not interacts_with_multiworld and self.multiworld.players > 1: - raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough" - f" progression items that can be placed in other players' worlds. Please turn on Symbol" - f" Shuffle, Door Shuffle or non-local Laser Shuffle.") + self.determine_sufficient_progression() if self.options.shuffle_lasers == "local": self.options.local_items.value |= self.item_name_groups["Lasers"] - def create_regions(self): - self.regio.create_regions(self, self.player_logic) + def create_regions(self) -> None: + self.player_regions.create_regions(self, self.player_logic) # Set rules early so extra locations can be created based on the results of exploring collection states set_rules(self) + # Start creating items + + self.items_placed_early = [] + self.own_itempool = [] + # Add event items and tie them to event locations (e.g. laser activations). event_locations = [] - for event_location in self.locat.EVENT_LOCATION_TABLE: + for event_location in self.player_locations.EVENT_LOCATION_TABLE: item_obj = self.create_item( self.player_logic.EVENT_ITEM_PAIRS[event_location] ) - location_obj = self.multiworld.get_location(event_location, self.player) + location_obj = self.get_location(event_location) location_obj.place_locked_item(item_obj) self.own_itempool.append(item_obj) @@ -149,25 +179,28 @@ def create_regions(self): # Place other locked items dog_puzzle_skip = self.create_item("Puzzle Skip") - self.multiworld.get_location("Town Pet the Dog", self.player).place_locked_item(dog_puzzle_skip) + self.get_location("Town Pet the Dog").place_locked_item(dog_puzzle_skip) self.own_itempool.append(dog_puzzle_skip) self.items_placed_early.append("Puzzle Skip") - # Pick an early item to place on the tutorial gate. - early_items = [item for item in self.items.get_early_items() if item in self.items.get_mandatory_items()] - if early_items: - random_early_item = self.random.choice(early_items) - if self.options.puzzle_randomization == "sigma_expert": - # In Expert, only tag the item as early, rather than forcing it onto the gate. - self.multiworld.local_early_items[self.player][random_early_item] = 1 - else: - # Force the item onto the tutorial gate check and remove it from our random pool. - gate_item = self.create_item(random_early_item) - self.multiworld.get_location("Tutorial Gate Open", self.player).place_locked_item(gate_item) - self.own_itempool.append(gate_item) - self.items_placed_early.append(random_early_item) + if self.options.early_symbol_item: + # Pick an early item to place on the tutorial gate. + early_items = [ + item for item in self.player_items.get_early_items() if item in self.player_items.get_mandatory_items() + ] + if early_items: + random_early_item = self.random.choice(early_items) + if self.options.puzzle_randomization == "sigma_expert": + # In Expert, only tag the item as early, rather than forcing it onto the gate. + self.multiworld.local_early_items[self.player][random_early_item] = 1 + else: + # Force the item onto the tutorial gate check and remove it from our random pool. + gate_item = self.create_item(random_early_item) + self.get_location("Tutorial Gate Open").place_locked_item(gate_item) + self.own_itempool.append(gate_item) + self.items_placed_early.append(random_early_item) # There are some really restrictive settings in The Witness. # They are rarely played, but when they are, we add some extra sphere 1 locations. @@ -200,19 +233,19 @@ def create_regions(self): break region, loc = extra_checks.pop(0) - self.locat.add_location_late(loc) - self.multiworld.get_region(region, self.player).add_locations({loc: self.location_name_to_id[loc]}) + self.player_locations.add_location_late(loc) + self.get_region(region).add_locations({loc: self.location_name_to_id[loc]}) player = self.multiworld.get_player_name(self.player) - + warning(f"""Location "{loc}" had to be added to {player}'s world due to insufficient sphere 1 size.""") - def create_items(self): + def create_items(self) -> None: # Determine pool size. - pool_size: int = len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) + pool_size = len(self.player_locations.CHECK_LOCATION_TABLE) - len(self.player_locations.EVENT_LOCATION_TABLE) # Fill mandatory items and remove precollected and/or starting items from the pool. - item_pool: Dict[str, int] = self.items.get_mandatory_items() + item_pool = self.player_items.get_mandatory_items() # Remove one copy of each item that was placed early for already_placed in self.items_placed_early: @@ -249,7 +282,7 @@ def create_items(self): remaining_item_slots = pool_size - sum(item_pool.values()) # Add puzzle skips. - num_puzzle_skips = self.options.puzzle_skip_amount + num_puzzle_skips = self.options.puzzle_skip_amount.value if num_puzzle_skips > remaining_item_slots: warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world has insufficient locations" @@ -260,7 +293,7 @@ def create_items(self): # Add junk items. if remaining_item_slots > 0: - item_pool.update(self.items.get_filler_items(remaining_item_slots)) + item_pool.update(self.player_items.get_filler_items(remaining_item_slots)) # Generate the actual items. for item_name, quantity in sorted(item_pool.items()): @@ -268,32 +301,28 @@ def create_items(self): self.own_itempool += new_items self.multiworld.itempool += new_items - if self.items.item_data[item_name].local_only: + if self.player_items.item_data[item_name].local_only: self.options.local_items.value.add(item_name) - def fill_slot_data(self) -> dict: + def fill_slot_data(self) -> Dict[str, Any]: + self.log_ids_to_hints: Dict[int, CompactItemData] = {} + self.laser_ids_to_hints: Dict[int, CompactItemData] = {} + already_hinted_locations = set() # Laser hints if self.options.laser_hints: - laser_hints = make_laser_hints(self, StaticWitnessItems.item_groups["Lasers"]) + laser_hints = make_laser_hints(self, sorted(static_witness_items.ITEM_GROUPS["Lasers"])) for item_name, hint in laser_hints.items(): - item_def = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]) + item_def = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]) self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player) - already_hinted_locations.add(hint.location) + already_hinted_locations.add(cast(Location, hint.location)) # Audio Log Hints hint_amount = self.options.hint_amount.value - - credits_hint = ( - "This Randomizer is brought to you by\n" - "NewSoupVi, Jarno, blastron,\n" - "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1 - ) - audio_logs = get_audio_logs().copy() if hint_amount: @@ -312,15 +341,8 @@ def fill_slot_data(self) -> dict: audio_log = audio_logs.pop() self.log_ids_to_hints[int(audio_log, 16)] = compact_hint_data - if audio_logs: - audio_log = audio_logs.pop() - self.log_ids_to_hints[int(audio_log, 16)] = credits_hint - - joke_hints = generate_joke_hints(self, len(audio_logs)) - - while audio_logs: - audio_log = audio_logs.pop() - self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop() + # Client will generate joke hints for these. + self.log_ids_to_hints.update({int(audio_log, 16): ("", -1, -1) for audio_log in audio_logs}) # Options for the client & auto-tracker @@ -333,18 +355,18 @@ def fill_slot_data(self) -> dict: return slot_data - def create_item(self, item_name: str) -> Item: + def create_item(self, item_name: str) -> WitnessItem: # If the player's plando options are malformed, the item_name parameter could be a dictionary containing the # name of the item, rather than the item itself. This is a workaround to prevent a crash. - if type(item_name) is dict: - item_name = list(item_name.keys())[0] + if isinstance(item_name, dict): + item_name = next(iter(item_name)) # this conditional is purely for unit tests, which need to be able to create an item before generate_early item_data: ItemData - if hasattr(self, 'items') and self.items and item_name in self.items.item_data: - item_data = self.items.item_data[item_name] + if hasattr(self, "player_items") and self.player_items and item_name in self.player_items.item_data: + item_data = self.player_items.item_data[item_name] else: - item_data = StaticWitnessItems.item_data[item_name] + item_data = static_witness_items.ITEM_DATA[item_name] return WitnessItem(item_name, item_data.classification, item_data.ap_code, player=self.player) @@ -359,12 +381,13 @@ class WitnessLocation(Location): game: str = "The Witness" entity_hex: int = -1 - def __init__(self, player: int, name: str, address: Optional[int], parent, ch_hex: int = -1): + def __init__(self, player: int, name: str, address: Optional[int], parent: Region, ch_hex: int = -1) -> None: super().__init__(player, name, address, parent) self.entity_hex = ch_hex -def create_region(world: WitnessWorld, name: str, locat: WitnessPlayerLocations, region_locations=None, exits=None): +def create_region(world: WitnessWorld, name: str, player_locations: WitnessPlayerLocations, + region_locations: Optional[List[str]] = None, exits: Optional[List[str]] = None) -> Region: """ Create an Archipelago Region for The Witness """ @@ -372,18 +395,18 @@ def create_region(world: WitnessWorld, name: str, locat: WitnessPlayerLocations, ret = Region(name, world.player, world.multiworld) if region_locations: for location in region_locations: - loc_id = locat.CHECK_LOCATION_TABLE[location] + loc_id = player_locations.CHECK_LOCATION_TABLE[location] entity_hex = -1 - if location in StaticWitnessLogic.ENTITIES_BY_NAME: + if location in static_witness_logic.ENTITIES_BY_NAME: entity_hex = int( - StaticWitnessLogic.ENTITIES_BY_NAME[location]["entity_hex"], 0 + static_witness_logic.ENTITIES_BY_NAME[location]["entity_hex"], 0 ) - location = WitnessLocation( + location_obj = WitnessLocation( world.player, location, loc_id, ret, entity_hex ) - ret.locations.append(location) + ret.locations.append(location_obj) if exits: for single_exit in exits: ret.exits.append(Entrance(world.player, single_exit, ret)) diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/data/WitnessItems.txt similarity index 89% rename from worlds/witness/WitnessItems.txt rename to worlds/witness/data/WitnessItems.txt index 6f63eccc9521..782fa9c3d226 100644 --- a/worlds/witness/WitnessItems.txt +++ b/worlds/witness/data/WitnessItems.txt @@ -72,7 +72,7 @@ Doors: 1164 - Town RGB Control (Panel) - 0x334D8 1166 - Town Maze Stairs (Panel) - 0x28A79 1167 - Town Maze Rooftop Bridge (Panel) - 0x2896A -1169 - Town Windmill Entry (Panel) - 0x17F5F +1169 - Windmill Entry (Panel) - 0x17F5F 1172 - Town Cargo Box Entry (Panel) - 0x0A0C8 1173 - Town Desert Laser Redirect Control (Panel) - 0x09F98 1182 - Windmill Turn Control (Panel) - 0x17D02 @@ -159,7 +159,7 @@ Doors: 1723 - Town RGB House Entry (Door) - 0x28A61 1726 - Town Church Entry (Door) - 0x03BB0 1729 - Town Maze Stairs (Door) - 0x28AA2 -1732 - Town Windmill Entry (Door) - 0x1845B +1732 - Windmill Entry (Door) - 0x1845B 1735 - Town RGB House Stairs (Door) - 0x2897B 1738 - Town Tower Second (Door) - 0x27798 1741 - Town Tower First (Door) - 0x27799 @@ -177,7 +177,7 @@ Doors: 1774 - Bunker Elevator Room Entry (Door) - 0x0A08D 1777 - Swamp Entry (Door) - 0x00C1C 1780 - Swamp Between Bridges First Door - 0x184B7 -1783 - Swamp Platform Shortcut Door - 0x38AE6 +1783 - Swamp Platform Shortcut (Door) - 0x38AE6 1786 - Swamp Cyan Water Pump (Door) - 0x04B7F 1789 - Swamp Between Bridges Second Door - 0x18507 1792 - Swamp Red Water Pump (Door) - 0x183F2 @@ -201,7 +201,7 @@ Doors: 1849 - Caves Pillar Door - 0x019A5 1855 - Caves Swamp Shortcut (Door) - 0x2D859 1858 - Challenge Entry (Door) - 0x0A19A -1861 - Challenge Tunnels Entry (Door) - 0x0348A +1861 - Tunnels Entry (Door) - 0x0348A 1864 - Tunnels Theater Shortcut (Door) - 0x27739 1867 - Tunnels Desert Shortcut (Door) - 0x27263 1870 - Tunnels Town Shortcut (Door) - 0x09E87 @@ -265,6 +265,13 @@ Doors: 2165 - Caves Panels - 0x3369D,0x00FF8,0x0A16E,0x335AB,0x335AC 2170 - Tunnels Panels - 0x09E85,0x039B4 +2200 - Desert Obelisk Key - 0x0332B,0x03367,0x28B8A,0x037B6,0x037B2,0x000F7,0x3351D,0x0053C,0x00771,0x335C8,0x335C9,0x337F8,0x037BB,0x220E4,0x220E5,0x334B9,0x334BC,0x22106,0x0A14C,0x0A14D,0x00359 +2201 - Monastery Obelisk Key - 0x03ABC,0x03ABE,0x03AC0,0x03AC4,0x03AC5,0x03BE2,0x03BE3,0x0A409,0x006E5,0x006E6,0x006E7,0x034A7,0x034AD,0x034AF,0x03DAB,0x03DAC,0x03DAD,0x03E01,0x289F4,0x289F5,0x00263 +2202 - Treehouse Obelisk Key - 0x0053D,0x0053E,0x00769,0x33721,0x220A7,0x220BD,0x03B22,0x03B23,0x03B24,0x03B25,0x03A79,0x28ABD,0x28ABE,0x3388F,0x28B29,0x28B2A,0x018B6,0x033BE,0x033BF,0x033DD,0x033E5,0x28AE9,0x3348F,0x00097 +2203 - Mountainside Obelisk Key - 0x001A3,0x335AE,0x000D3,0x035F5,0x09D5D,0x09D5E,0x09D63,0x3370E,0x035DE,0x03601,0x03603,0x03D0D,0x3369A,0x336C8,0x33505,0x03A9E,0x016B2,0x3365F,0x03731,0x036CE,0x03C07,0x03A93,0x03AA6,0x3397C,0x0105D,0x0A304,0x035CB,0x035CF,0x00367 +2204 - Quarry Obelisk Key - 0x28A7B,0x005F6,0x00859,0x17CB9,0x28A4A,0x334B6,0x00614,0x0069D,0x28A4C,0x289CF,0x289D1,0x33692,0x03E77,0x03E7C,0x22073 +2205 - Town Obelisk Key - 0x035C7,0x01848,0x03D06,0x33530,0x33600,0x28A2F,0x28A37,0x334A3,0x3352F,0x33857,0x33879,0x03C19,0x28B30,0x035C9,0x03335,0x03412,0x038A6,0x038AA,0x03E3F,0x03E40,0x28B8E,0x28B91,0x03BCE,0x03BCF,0x03BD1,0x339B6,0x33A20,0x33A29,0x33A2A,0x33B06,0x0A16C + Lasers: 1500 - Symmetry Laser - 0x00509 1501 - Desert Laser - 0x012FB diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/data/WitnessLogic.txt similarity index 99% rename from worlds/witness/WitnessLogic.txt rename to worlds/witness/data/WitnessLogic.txt index e3bacfb4b0e4..b7814626ada0 100644 --- a/worlds/witness/WitnessLogic.txt +++ b/worlds/witness/data/WitnessLogic.txt @@ -482,7 +482,7 @@ Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 158207 - 0x03713 (Laser Shortcut Panel) - True - True Door - 0x0364E (Laser Shortcut) - 0x03713 158208 - 0x00B10 (Entry Left) - True - True -158209 - 0x00C92 (Entry Right) - True - True +158209 - 0x00C92 (Entry Right) - 0x00B10 - True Door - 0x0C128 (Entry Inner) - 0x00B10 Door - 0x0C153 (Entry Outer) - 0x00C92 158210 - 0x00290 (Outside 1) - 0x09D9B - True @@ -766,7 +766,7 @@ Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat Door - 0x184B7 (Between Bridges First Door) - 0x00990 158317 - 0x17C0D (Platform Shortcut Left Panel) - True - Shapers 158318 - 0x17C0E (Platform Shortcut Right Panel) - 0x17C0D - Shapers -Door - 0x38AE6 (Platform Shortcut Door) - 0x17C0E +Door - 0x38AE6 (Platform Shortcut) - 0x17C0E Door - 0x04B7F (Cyan Water Pump) - 0x00006 Swamp Cyan Underwater (Swamp): @@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: -158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True +159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers @@ -1028,15 +1028,15 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Colored Squares & Stars + Same Colored Symbol 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol 158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers -158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares & Symmetry & Colored Dots +158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Symmetry & Colored Dots Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: +Mountain Floor 2 Above The Abyss (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD & 0x09ED8 & 0x09E86: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2): @@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry Elevator (Mountain Bottom Floor): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158530 - 0x3D9A6 (Elevator Door Close Left) - True - True 158531 - 0x3D9A7 (Elevator Door Close Right) - True - True 158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True 158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/data/WitnessLogicExpert.txt similarity index 99% rename from worlds/witness/WitnessLogicExpert.txt rename to worlds/witness/data/WitnessLogicExpert.txt index b01d5551ec55..1d1d010fde88 100644 --- a/worlds/witness/WitnessLogicExpert.txt +++ b/worlds/witness/data/WitnessLogicExpert.txt @@ -482,7 +482,7 @@ Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 158207 - 0x03713 (Laser Shortcut Panel) - True - True Door - 0x0364E (Laser Shortcut) - 0x03713 158208 - 0x00B10 (Entry Left) - True - True -158209 - 0x00C92 (Entry Right) - True - True +158209 - 0x00C92 (Entry Right) - 0x00B10 - True Door - 0x0C128 (Entry Inner) - 0x00B10 Door - 0x0C153 (Entry Outer) - 0x00C92 158210 - 0x00290 (Outside 1) - 0x09D9B - True @@ -766,7 +766,7 @@ Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat Door - 0x184B7 (Between Bridges First Door) - 0x00990 158317 - 0x17C0D (Platform Shortcut Left Panel) - True - Rotated Shapers 158318 - 0x17C0E (Platform Shortcut Right Panel) - 0x17C0D - Rotated Shapers -Door - 0x38AE6 (Platform Shortcut Door) - 0x17C0E +Door - 0x38AE6 (Platform Shortcut) - 0x17C0E Door - 0x04B7F (Cyan Water Pump) - 0x00006 Swamp Cyan Underwater (Swamp): @@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: -158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True +159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Shapers & Dots & Full Dots 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers & Shapers & Dots & Full Dots @@ -1028,7 +1028,7 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Triangles & Stars + Same Colored Symbol 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol @@ -1036,7 +1036,7 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: +Mountain Floor 2 Above The Abyss (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD & 0x09ED8 & 0x09E86: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2): @@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots Elevator (Mountain Bottom Floor): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158530 - 0x3D9A6 (Elevator Door Close Left) - True - True 158531 - 0x3D9A7 (Elevator Door Close Right) - True - True 158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True 158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/data/WitnessLogicVanilla.txt similarity index 99% rename from worlds/witness/WitnessLogicVanilla.txt rename to worlds/witness/data/WitnessLogicVanilla.txt index 62c38d412427..851031ab72f0 100644 --- a/worlds/witness/WitnessLogicVanilla.txt +++ b/worlds/witness/data/WitnessLogicVanilla.txt @@ -482,7 +482,7 @@ Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 158207 - 0x03713 (Laser Shortcut Panel) - True - True Door - 0x0364E (Laser Shortcut) - 0x03713 158208 - 0x00B10 (Entry Left) - True - True -158209 - 0x00C92 (Entry Right) - True - True +158209 - 0x00C92 (Entry Right) - 0x00B10 - True Door - 0x0C128 (Entry Inner) - 0x00B10 Door - 0x0C153 (Entry Outer) - 0x00C92 158210 - 0x00290 (Outside 1) - 0x09D9B - True @@ -766,7 +766,7 @@ Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat Door - 0x184B7 (Between Bridges First Door) - 0x00990 158317 - 0x17C0D (Platform Shortcut Left Panel) - True - Shapers 158318 - 0x17C0E (Platform Shortcut Right Panel) - 0x17C0D - Shapers -Door - 0x38AE6 (Platform Shortcut Door) - 0x17C0E +Door - 0x38AE6 (Platform Shortcut) - 0x17C0E Door - 0x04B7F (Cyan Water Pump) - 0x00006 Swamp Cyan Underwater (Swamp): @@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: -158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True +159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers @@ -1028,7 +1028,7 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Colored Squares 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Dots 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol @@ -1036,7 +1036,7 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: +Mountain Floor 2 Above The Abyss (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD & 0x09ED8 & 0x09E86: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2): @@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry Elevator (Mountain Bottom Floor): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158530 - 0x3D9A6 (Elevator Door Close Left) - True - True 158531 - 0x3D9A7 (Elevator Door Close Right) - True - True 158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True 158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True diff --git a/worlds/witness/data/__init__.py b/worlds/witness/data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/witness/data/item_definition_classes.py b/worlds/witness/data/item_definition_classes.py new file mode 100644 index 000000000000..b095a83abe63 --- /dev/null +++ b/worlds/witness/data/item_definition_classes.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Optional + +from BaseClasses import ItemClassification + + +class ItemCategory(Enum): + SYMBOL = 0 + DOOR = 1 + LASER = 2 + USEFUL = 3 + FILLER = 4 + TRAP = 5 + JOKE = 6 + EVENT = 7 + + +CATEGORY_NAME_MAPPINGS: Dict[str, ItemCategory] = { + "Symbols:": ItemCategory.SYMBOL, + "Doors:": ItemCategory.DOOR, + "Lasers:": ItemCategory.LASER, + "Useful:": ItemCategory.USEFUL, + "Filler:": ItemCategory.FILLER, + "Traps:": ItemCategory.TRAP, + "Jokes:": ItemCategory.JOKE +} + + +@dataclass(frozen=True) +class ItemDefinition: + local_code: int + category: ItemCategory + + +@dataclass(frozen=True) +class ProgressiveItemDefinition(ItemDefinition): + child_item_names: List[str] + + +@dataclass(frozen=True) +class DoorItemDefinition(ItemDefinition): + panel_id_hexes: List[str] + + +@dataclass(frozen=True) +class WeightedItemDefinition(ItemDefinition): + weight: int + + +@dataclass() +class ItemData: + """ + ItemData for an item in The Witness + """ + ap_code: Optional[int] + definition: ItemDefinition + classification: ItemClassification + local_only: bool = False diff --git a/worlds/witness/settings/Audio_Logs.txt b/worlds/witness/data/settings/Audio_Logs.txt similarity index 100% rename from worlds/witness/settings/Audio_Logs.txt rename to worlds/witness/data/settings/Audio_Logs.txt diff --git a/worlds/witness/settings/Door_Shuffle/Boat.txt b/worlds/witness/data/settings/Door_Shuffle/Boat.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Boat.txt rename to worlds/witness/data/settings/Door_Shuffle/Boat.txt diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Additional_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Complex_Additional_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Complex_Additional_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Complex_Additional_Panels.txt diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Complex_Door_Panels.txt similarity index 97% rename from worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Complex_Door_Panels.txt index 70223bd74924..63d8a58d2676 100644 --- a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt +++ b/worlds/witness/data/settings/Door_Shuffle/Complex_Door_Panels.txt @@ -19,7 +19,7 @@ Monastery Entry Right (Panel) Town RGB House Entry (Panel) Town Church Entry (Panel) Town Maze Stairs (Panel) -Town Windmill Entry (Panel) +Windmill Entry (Panel) Town Cargo Box Entry (Panel) Theater Entry (Panel) Theater Exit (Panel) diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt b/worlds/witness/data/settings/Door_Shuffle/Complex_Doors.txt similarity index 98% rename from worlds/witness/settings/Door_Shuffle/Complex_Doors.txt rename to worlds/witness/data/settings/Door_Shuffle/Complex_Doors.txt index 87ec69f59c81..513f1d9a71fb 100644 --- a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt +++ b/worlds/witness/data/settings/Door_Shuffle/Complex_Doors.txt @@ -49,7 +49,7 @@ Town Wooden Roof Stairs (Door) Town RGB House Entry (Door) Town Church Entry (Door) Town Maze Stairs (Door) -Town Windmill Entry (Door) +Windmill Entry (Door) Town RGB House Stairs (Door) Town Tower Second (Door) Town Tower First (Door) @@ -67,7 +67,7 @@ Bunker UV Room Entry (Door) Bunker Elevator Room Entry (Door) Swamp Entry (Door) Swamp Between Bridges First Door -Swamp Platform Shortcut Door +Swamp Platform Shortcut (Door) Swamp Cyan Water Pump (Door) Swamp Between Bridges Second Door Swamp Red Water Pump (Door) @@ -92,7 +92,7 @@ Caves Pillar Door Caves Mountain Shortcut (Door) Caves Swamp Shortcut (Door) Challenge Entry (Door) -Challenge Tunnels Entry (Door) +Tunnels Entry (Door) Tunnels Theater Shortcut (Door) Tunnels Desert Shortcut (Door) Tunnels Town Shortcut (Door) diff --git a/worlds/witness/settings/Door_Shuffle/Elevators_Come_To_You.txt b/worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Elevators_Come_To_You.txt rename to worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt diff --git a/worlds/witness/data/settings/Door_Shuffle/Obelisk_Keys.txt b/worlds/witness/data/settings/Door_Shuffle/Obelisk_Keys.txt new file mode 100644 index 000000000000..9ebcc9fa2ffa --- /dev/null +++ b/worlds/witness/data/settings/Door_Shuffle/Obelisk_Keys.txt @@ -0,0 +1,7 @@ +Items: +Desert Obelisk Key +Monastery Obelisk Key +Treehouse Obelisk Key +Mountainside Obelisk Key +Quarry Obelisk Key +Town Obelisk Key \ No newline at end of file diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Additional_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Simple_Additional_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Simple_Additional_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Simple_Additional_Panels.txt diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt b/worlds/witness/data/settings/Door_Shuffle/Simple_Doors.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Simple_Doors.txt rename to worlds/witness/data/settings/Door_Shuffle/Simple_Doors.txt diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Simple_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Simple_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Simple_Panels.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_All.txt b/worlds/witness/data/settings/EP_Shuffle/EP_All.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_All.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_All.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_Easy.txt b/worlds/witness/data/settings/EP_Shuffle/EP_Easy.txt similarity index 93% rename from worlds/witness/settings/EP_Shuffle/EP_Easy.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_Easy.txt index 6f9c80fc0a94..95c1fc39fb7a 100644 --- a/worlds/witness/settings/EP_Shuffle/EP_Easy.txt +++ b/worlds/witness/data/settings/EP_Shuffle/EP_Easy.txt @@ -15,3 +15,4 @@ Disabled Locations: 0x09D63 (Mountain Pink Bridge EP) 0x09D5E (Mountain Blue Bridge EP) 0x09D5D (Mountain Yellow Bridge EP) +0x220BD (Both Orange Bridges EP) diff --git a/worlds/witness/settings/EP_Shuffle/EP_NoEclipse.txt b/worlds/witness/data/settings/EP_Shuffle/EP_NoEclipse.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_NoEclipse.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_NoEclipse.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt b/worlds/witness/data/settings/EP_Shuffle/EP_Sides.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_Sides.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_Sides.txt diff --git a/worlds/witness/settings/Early_Caves.txt b/worlds/witness/data/settings/Early_Caves.txt similarity index 100% rename from worlds/witness/settings/Early_Caves.txt rename to worlds/witness/data/settings/Early_Caves.txt diff --git a/worlds/witness/settings/Early_Caves_Start.txt b/worlds/witness/data/settings/Early_Caves_Start.txt similarity index 100% rename from worlds/witness/settings/Early_Caves_Start.txt rename to worlds/witness/data/settings/Early_Caves_Start.txt diff --git a/worlds/witness/settings/Postgame/Caves.txt b/worlds/witness/data/settings/Exclusions/Caves_Except_Path_To_Challenge.txt similarity index 100% rename from worlds/witness/settings/Postgame/Caves.txt rename to worlds/witness/data/settings/Exclusions/Caves_Except_Path_To_Challenge.txt diff --git a/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt b/worlds/witness/data/settings/Exclusions/Disable_Unrandomized.txt similarity index 90% rename from worlds/witness/settings/Exclusions/Disable_Unrandomized.txt rename to worlds/witness/data/settings/Exclusions/Disable_Unrandomized.txt index 09c366cfaabd..3dfc34e8ad0a 100644 --- a/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt +++ b/worlds/witness/data/settings/Exclusions/Disable_Unrandomized.txt @@ -134,17 +134,3 @@ Disabled Locations: 0x17E67 (Bunker UV Room 2) 0x09DE0 (Bunker Laser) 0x0A079 (Bunker Elevator Control) - -0x034A7 (Monastery Left Shutter EP) -0x034AD (Monastery Middle Shutter EP) -0x034AF (Monastery Right Shutter EP) -0x339B6 (Theater Eclipse EP) -0x33A29 (Theater Window EP) -0x33A2A (Theater Door EP) -0x33B06 (Theater Church EP) -0x3352F (Tutorial Gate EP) -0x33600 (Tutorial Patio Flowers EP) -0x035F5 (Bunker Tinted Door EP) -0x000D3 (Bunker Green Room Flowers EP) -0x33A20 (Theater Flowers EP) -0x03BE2 (Monastery Garden Left EP) diff --git a/worlds/witness/settings/Exclusions/Discards.txt b/worlds/witness/data/settings/Exclusions/Discards.txt similarity index 100% rename from worlds/witness/settings/Exclusions/Discards.txt rename to worlds/witness/data/settings/Exclusions/Discards.txt diff --git a/worlds/witness/data/settings/Exclusions/Vaults.txt b/worlds/witness/data/settings/Exclusions/Vaults.txt new file mode 100644 index 000000000000..9eade5e52855 --- /dev/null +++ b/worlds/witness/data/settings/Exclusions/Vaults.txt @@ -0,0 +1,8 @@ +Disabled Locations: +0x033D4 (Outside Tutorial Vault) +0x0CC7B (Desert Vault) +0x00AFB (Shipwreck Vault) +0x15ADD (Jungle Vault) +0x002A6 (Mountainside Vault) +0x2FAF6 (Tunnels Vault Box) +0x00815 (Theater Video Input) diff --git a/worlds/witness/settings/Laser_Shuffle.txt b/worlds/witness/data/settings/Laser_Shuffle.txt similarity index 100% rename from worlds/witness/settings/Laser_Shuffle.txt rename to worlds/witness/data/settings/Laser_Shuffle.txt diff --git a/worlds/witness/settings/Symbol_Shuffle.txt b/worlds/witness/data/settings/Symbol_Shuffle.txt similarity index 100% rename from worlds/witness/settings/Symbol_Shuffle.txt rename to worlds/witness/data/settings/Symbol_Shuffle.txt diff --git a/worlds/witness/data/static_items.py b/worlds/witness/data/static_items.py new file mode 100644 index 000000000000..b0d8fc3c4f6e --- /dev/null +++ b/worlds/witness/data/static_items.py @@ -0,0 +1,56 @@ +from typing import Dict, List, Set + +from BaseClasses import ItemClassification + +from . import static_logic as static_witness_logic +from .item_definition_classes import DoorItemDefinition, ItemCategory, ItemData +from .static_locations import ID_START + +ITEM_DATA: Dict[str, ItemData] = {} +ITEM_GROUPS: Dict[str, Set[str]] = {} + +# Useful items that are treated specially at generation time and should not be automatically added to the player's +# item list during get_progression_items. +_special_usefuls: List[str] = ["Puzzle Skip"] + + +def populate_items() -> None: + for item_name, definition in static_witness_logic.ALL_ITEMS.items(): + ap_item_code = definition.local_code + ID_START + classification: ItemClassification = ItemClassification.filler + local_only: bool = False + + if definition.category is ItemCategory.SYMBOL: + classification = ItemClassification.progression + ITEM_GROUPS.setdefault("Symbols", set()).add(item_name) + elif definition.category is ItemCategory.DOOR: + classification = ItemClassification.progression + ITEM_GROUPS.setdefault("Doors", set()).add(item_name) + elif definition.category is ItemCategory.LASER: + classification = ItemClassification.progression_skip_balancing + ITEM_GROUPS.setdefault("Lasers", set()).add(item_name) + elif definition.category is ItemCategory.USEFUL: + classification = ItemClassification.useful + elif definition.category is ItemCategory.FILLER: + if item_name in ["Energy Fill (Small)"]: + local_only = True + classification = ItemClassification.filler + elif definition.category is ItemCategory.TRAP: + classification = ItemClassification.trap + elif definition.category is ItemCategory.JOKE: + classification = ItemClassification.filler + + ITEM_DATA[item_name] = ItemData(ap_item_code, definition, + classification, local_only) + + +def get_item_to_door_mappings() -> Dict[int, List[int]]: + output: Dict[int, List[int]] = {} + for item_name, item_data in ITEM_DATA.items(): + if not isinstance(item_data.definition, DoorItemDefinition) or item_data.ap_code is None: + continue + output[item_data.ap_code] = [int(hex_string, 16) for hex_string in item_data.definition.panel_id_hexes] + return output + + +populate_items() diff --git a/worlds/witness/data/static_locations.py b/worlds/witness/data/static_locations.py new file mode 100644 index 000000000000..de321d20c0f9 --- /dev/null +++ b/worlds/witness/data/static_locations.py @@ -0,0 +1,484 @@ +from typing import Dict, Set, cast + +from . import static_logic as static_witness_logic + +ID_START = 158000 + +GENERAL_LOCATIONS = { + "Tutorial Front Left", + "Tutorial Back Left", + "Tutorial Back Right", + "Tutorial Patio Floor", + "Tutorial Gate Open", + + "Outside Tutorial Vault Box", + "Outside Tutorial Discard", + "Outside Tutorial Shed Row 5", + "Outside Tutorial Tree Row 9", + "Outside Tutorial Outpost Entry Panel", + "Outside Tutorial Outpost Exit Panel", + + "Glass Factory Discard", + "Glass Factory Back Wall 5", + "Glass Factory Front 3", + "Glass Factory Melting 3", + + "Symmetry Island Lower Panel", + "Symmetry Island Right 5", + "Symmetry Island Back 6", + "Symmetry Island Left 7", + "Symmetry Island Upper Panel", + "Symmetry Island Scenery Outlines 5", + "Symmetry Island Laser Yellow 3", + "Symmetry Island Laser Blue 3", + "Symmetry Island Laser Panel", + + "Orchard Apple Tree 5", + + "Desert Vault Box", + "Desert Discard", + "Desert Surface 8", + "Desert Light Room 3", + "Desert Pond Room 5", + "Desert Flood Room 6", + "Desert Elevator Room Hexagonal", + "Desert Elevator Room Bent 3", + "Desert Laser Panel", + + "Quarry Entry 1 Panel", + "Quarry Entry 2 Panel", + "Quarry Stoneworks Entry Left Panel", + "Quarry Stoneworks Entry Right Panel", + "Quarry Stoneworks Lower Row 6", + "Quarry Stoneworks Upper Row 8", + "Quarry Stoneworks Control Room Left", + "Quarry Stoneworks Control Room Right", + "Quarry Stoneworks Stairs Panel", + "Quarry Boathouse Intro Right", + "Quarry Boathouse Intro Left", + "Quarry Boathouse Front Row 5", + "Quarry Boathouse Back First Row 9", + "Quarry Boathouse Back Second Row 3", + "Quarry Discard", + "Quarry Laser Panel", + + "Shadows Intro 8", + "Shadows Far 8", + "Shadows Near 5", + "Shadows Laser Panel", + + "Keep Hedge Maze 1", + "Keep Hedge Maze 2", + "Keep Hedge Maze 3", + "Keep Hedge Maze 4", + "Keep Pressure Plates 1", + "Keep Pressure Plates 2", + "Keep Pressure Plates 3", + "Keep Pressure Plates 4", + "Keep Discard", + "Keep Laser Panel Hedges", + "Keep Laser Panel Pressure Plates", + + "Shipwreck Vault Box", + "Shipwreck Discard", + + "Monastery Outside 3", + "Monastery Inside 4", + "Monastery Laser Panel", + + "Town Cargo Box Entry Panel", + "Town Cargo Box Discard", + "Town Tall Hexagonal", + "Town Church Entry Panel", + "Town Church Lattice", + "Town Maze Panel", + "Town Rooftop Discard", + "Town Red Rooftop 5", + "Town Wooden Roof Lower Row 5", + "Town Wooden Rooftop", + "Windmill Entry Panel", + "Town RGB House Entry Panel", + "Town Laser Panel", + + "Town RGB House Upstairs Left", + "Town RGB House Upstairs Right", + "Town RGB House Sound Room Right", + + "Windmill Theater Entry Panel", + "Theater Exit Left Panel", + "Theater Exit Right Panel", + "Theater Tutorial Video", + "Theater Desert Video", + "Theater Jungle Video", + "Theater Shipwreck Video", + "Theater Mountain Video", + "Theater Discard", + + "Jungle Discard", + "Jungle First Row 3", + "Jungle Second Row 4", + "Jungle Popup Wall 6", + "Jungle Laser Panel", + + "Jungle Vault Box", + "Jungle Monastery Garden Shortcut Panel", + + "Bunker Entry Panel", + "Bunker Intro Left 5", + "Bunker Intro Back 4", + "Bunker Glass Room 3", + "Bunker UV Room 2", + "Bunker Laser Panel", + + "Swamp Entry Panel", + "Swamp Intro Front 6", + "Swamp Intro Back 8", + "Swamp Between Bridges Near Row 4", + "Swamp Cyan Underwater 5", + "Swamp Platform Row 4", + "Swamp Platform Shortcut Right Panel", + "Swamp Between Bridges Far Row 4", + "Swamp Red Underwater 4", + "Swamp Purple Underwater", + "Swamp Beyond Rotating Bridge 4", + "Swamp Blue Underwater 5", + "Swamp Laser Panel", + "Swamp Laser Shortcut Right Panel", + + "Treehouse First Door Panel", + "Treehouse Second Door Panel", + "Treehouse Third Door Panel", + "Treehouse Yellow Bridge 9", + "Treehouse First Purple Bridge 5", + "Treehouse Second Purple Bridge 7", + "Treehouse Green Bridge 7", + "Treehouse Green Bridge Discard", + "Treehouse Left Orange Bridge 15", + "Treehouse Laser Discard", + "Treehouse Right Orange Bridge 12", + "Treehouse Laser Panel", + "Treehouse Drawbridge Panel", + + "Mountainside Discard", + "Mountainside Vault Box", + "Mountaintop River Shape", + + "Tutorial First Hallway EP", + "Tutorial Cloud EP", + "Tutorial Patio Flowers EP", + "Tutorial Gate EP", + "Outside Tutorial Garden EP", + "Outside Tutorial Town Sewer EP", + "Outside Tutorial Path EP", + "Outside Tutorial Tractor EP", + "Mountainside Thundercloud EP", + "Glass Factory Vase EP", + "Symmetry Island Glass Factory Black Line Reflection EP", + "Symmetry Island Glass Factory Black Line EP", + "Desert Sand Snake EP", + "Desert Facade Right EP", + "Desert Facade Left EP", + "Desert Stairs Left EP", + "Desert Stairs Right EP", + "Desert Broken Wall Straight EP", + "Desert Broken Wall Bend EP", + "Desert Shore EP", + "Desert Island EP", + "Desert Pond Room Near Reflection EP", + "Desert Pond Room Far Reflection EP", + "Desert Flood Room EP", + "Desert Elevator EP", + "Quarry Shore EP", + "Quarry Entrance Pipe EP", + "Quarry Sand Pile EP", + "Quarry Rock Line EP", + "Quarry Rock Line Reflection EP", + "Quarry Railroad EP", + "Quarry Stoneworks Ramp EP", + "Quarry Stoneworks Lift EP", + "Quarry Boathouse Moving Ramp EP", + "Quarry Boathouse Hook EP", + "Shadows Quarry Stoneworks Rooftop Vent EP", + "Treehouse Beach Rock Shadow EP", + "Treehouse Beach Sand Shadow EP", + "Treehouse Beach Both Orange Bridges EP", + "Keep Red Flowers EP", + "Keep Purple Flowers EP", + "Shipwreck Circle Near EP", + "Shipwreck Circle Left EP", + "Shipwreck Circle Far EP", + "Shipwreck Stern EP", + "Shipwreck Rope Inner EP", + "Shipwreck Rope Outer EP", + "Shipwreck Couch EP", + "Keep Pressure Plates 1 EP", + "Keep Pressure Plates 2 EP", + "Keep Pressure Plates 3 EP", + "Keep Pressure Plates 4 Left Exit EP", + "Keep Pressure Plates 4 Right Exit EP", + "Keep Path EP", + "Keep Hedges EP", + "Monastery Facade Left Near EP", + "Monastery Facade Left Far Short EP", + "Monastery Facade Left Far Long EP", + "Monastery Facade Right Near EP", + "Monastery Facade Left Stairs EP", + "Monastery Facade Right Stairs EP", + "Monastery Grass Stairs EP", + "Monastery Left Shutter EP", + "Monastery Middle Shutter EP", + "Monastery Right Shutter EP", + "Windmill First Blade EP", + "Windmill Second Blade EP", + "Windmill Third Blade EP", + "Town Tower Underside Third EP", + "Town Tower Underside Fourth EP", + "Town Tower Underside First EP", + "Town Tower Underside Second EP", + "Town RGB House Red EP", + "Town RGB House Green EP", + "Town Maze Bridge Underside EP", + "Town Black Line Redirect EP", + "Town Black Line Church EP", + "Town Brown Bridge EP", + "Town Black Line Tower EP", + "Theater Eclipse EP", + "Theater Window EP", + "Theater Door EP", + "Theater Church EP", + "Jungle Long Arch Moss EP", + "Jungle Straight Left Moss EP", + "Jungle Pop-up Wall Moss EP", + "Jungle Short Arch Moss EP", + "Jungle Entrance EP", + "Jungle Tree Halo EP", + "Jungle Bamboo CCW EP", + "Jungle Bamboo CW EP", + "Jungle Green Leaf Moss EP", + "Monastery Garden Left EP", + "Monastery Garden Right EP", + "Monastery Wall EP", + "Bunker Tinted Door EP", + "Bunker Green Room Flowers EP", + "Swamp Purple Sand Middle EP", + "Swamp Purple Sand Top EP", + "Swamp Purple Sand Bottom EP", + "Swamp Sliding Bridge Left EP", + "Swamp Sliding Bridge Right EP", + "Swamp Cyan Underwater Sliding Bridge EP", + "Swamp Rotating Bridge CCW EP", + "Swamp Rotating Bridge CW EP", + "Swamp Boat EP", + "Swamp Long Bridge Side EP", + "Swamp Purple Underwater Right EP", + "Swamp Purple Underwater Left EP", + "Treehouse Buoy EP", + "Treehouse Right Orange Bridge EP", + "Treehouse Burned House Beach EP", + "Mountainside Cloud Cycle EP", + "Mountainside Bush EP", + "Mountainside Apparent River EP", + "Mountaintop River Shape EP", + "Mountaintop Arch Black EP", + "Mountaintop Arch White Right EP", + "Mountaintop Arch White Left EP", + "Mountain Bottom Floor Yellow Bridge EP", + "Mountain Bottom Floor Blue Bridge EP", + "Mountain Floor 2 Pink Bridge EP", + "Caves Skylight EP", + "Challenge Water EP", + "Tunnels Theater Flowers EP", + "Boat Desert EP", + "Boat Shipwreck CCW Underside EP", + "Boat Shipwreck Green EP", + "Boat Shipwreck CW Underside EP", + "Boat Bunker Yellow Line EP", + "Boat Town Long Sewer EP", + "Boat Tutorial EP", + "Boat Tutorial Reflection EP", + "Boat Tutorial Moss EP", + "Boat Cargo Box EP", + + "Desert Obelisk Side 1", + "Desert Obelisk Side 2", + "Desert Obelisk Side 3", + "Desert Obelisk Side 4", + "Desert Obelisk Side 5", + "Monastery Obelisk Side 1", + "Monastery Obelisk Side 2", + "Monastery Obelisk Side 3", + "Monastery Obelisk Side 4", + "Monastery Obelisk Side 5", + "Monastery Obelisk Side 6", + "Treehouse Obelisk Side 1", + "Treehouse Obelisk Side 2", + "Treehouse Obelisk Side 3", + "Treehouse Obelisk Side 4", + "Treehouse Obelisk Side 5", + "Treehouse Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", + "Quarry Obelisk Side 1", + "Quarry Obelisk Side 2", + "Quarry Obelisk Side 3", + "Quarry Obelisk Side 4", + "Quarry Obelisk Side 5", + "Town Obelisk Side 1", + "Town Obelisk Side 2", + "Town Obelisk Side 3", + "Town Obelisk Side 4", + "Town Obelisk Side 5", + "Town Obelisk Side 6", + + "Caves Mountain Shortcut Panel", + "Caves Swamp Shortcut Panel", + + "Caves Blue Tunnel Right First 4", + "Caves Blue Tunnel Left First 1", + "Caves Blue Tunnel Left Second 5", + "Caves Blue Tunnel Right Second 5", + "Caves Blue Tunnel Right Third 1", + "Caves Blue Tunnel Left Fourth 1", + "Caves Blue Tunnel Left Third 1", + + "Caves First Floor Middle", + "Caves First Floor Right", + "Caves First Floor Left", + "Caves First Floor Grounded", + "Caves Lone Pillar", + "Caves First Wooden Beam", + "Caves Second Wooden Beam", + "Caves Third Wooden Beam", + "Caves Fourth Wooden Beam", + "Caves Right Upstairs Left Row 8", + "Caves Right Upstairs Right Row 3", + "Caves Left Upstairs Single", + "Caves Left Upstairs Left Row 5", + + "Caves Challenge Entry Panel", + "Challenge Tunnels Entry Panel", + + "Tunnels Vault Box", + "Theater Challenge Video", + + "Tunnels Town Shortcut Panel", + + "Caves Skylight EP", + "Challenge Water EP", + "Tunnels Theater Flowers EP", + "Tutorial Gate EP", + + "Mountaintop Mountain Entry Panel", + + "Mountain Floor 1 Light Bridge Controller", + + "Mountain Floor 1 Right Row 5", + "Mountain Floor 1 Left Row 7", + "Mountain Floor 1 Back Row 3", + "Mountain Floor 1 Trash Pillar 2", + "Mountain Floor 2 Near Row 5", + "Mountain Floor 2 Far Row 6", + + "Mountain Floor 2 Light Bridge Controller Near", + "Mountain Floor 2 Light Bridge Controller Far", + + "Mountain Bottom Floor Yellow Bridge EP", + "Mountain Bottom Floor Blue Bridge EP", + "Mountain Floor 2 Pink Bridge EP", + + "Mountain Floor 2 Elevator Discard", + "Mountain Bottom Floor Giant Puzzle", + + "Mountain Bottom Floor Pillars Room Entry Left", + "Mountain Bottom Floor Pillars Room Entry Right", + + "Mountain Bottom Floor Caves Entry Panel", + + "Mountain Bottom Floor Left Pillar 4", + "Mountain Bottom Floor Right Pillar 4", + + "Challenge Vault Box", + "Theater Challenge Video", + "Mountain Bottom Floor Discard", +} + +OBELISK_SIDES = { + "Desert Obelisk Side 1", + "Desert Obelisk Side 2", + "Desert Obelisk Side 3", + "Desert Obelisk Side 4", + "Desert Obelisk Side 5", + "Monastery Obelisk Side 1", + "Monastery Obelisk Side 2", + "Monastery Obelisk Side 3", + "Monastery Obelisk Side 4", + "Monastery Obelisk Side 5", + "Monastery Obelisk Side 6", + "Treehouse Obelisk Side 1", + "Treehouse Obelisk Side 2", + "Treehouse Obelisk Side 3", + "Treehouse Obelisk Side 4", + "Treehouse Obelisk Side 5", + "Treehouse Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", + "Quarry Obelisk Side 1", + "Quarry Obelisk Side 2", + "Quarry Obelisk Side 3", + "Quarry Obelisk Side 4", + "Quarry Obelisk Side 5", + "Town Obelisk Side 1", + "Town Obelisk Side 2", + "Town Obelisk Side 3", + "Town Obelisk Side 4", + "Town Obelisk Side 5", + "Town Obelisk Side 6", +} + +ALL_LOCATIONS_TO_ID: Dict[str, int] = {} + +AREA_LOCATION_GROUPS: Dict[str, Set[str]] = {} + + +def get_id(entity_hex: str) -> int: + """ + Calculates the location ID for any given location + """ + + return cast(int, static_witness_logic.ENTITIES_BY_HEX[entity_hex]["id"]) + + +def get_event_name(entity_hex: str) -> str: + """ + Returns the event name of any given panel. + """ + + action = " Opened" if static_witness_logic.ENTITIES_BY_HEX[entity_hex]["entityType"] == "Door" else " Solved" + + return cast(str, static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"]) + action + + +ALL_LOCATIONS_TO_IDS = { + panel_obj["checkName"]: get_id(chex) + for chex, panel_obj in static_witness_logic.ENTITIES_BY_HEX.items() + if panel_obj["id"] +} + +ALL_LOCATIONS_TO_IDS = dict( + sorted(ALL_LOCATIONS_TO_IDS.items(), key=lambda loc: loc[1]) +) + +for key, item in ALL_LOCATIONS_TO_IDS.items(): + ALL_LOCATIONS_TO_ID[key] = item + +for loc in ALL_LOCATIONS_TO_IDS: + area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"]["name"] + AREA_LOCATION_GROUPS.setdefault(area, set()).add(loc) diff --git a/worlds/witness/data/static_logic.py b/worlds/witness/data/static_logic.py new file mode 100644 index 000000000000..a9175c0c30b3 --- /dev/null +++ b/worlds/witness/data/static_logic.py @@ -0,0 +1,303 @@ +from collections import defaultdict +from typing import Any, Dict, List, Optional, Set, Tuple + +from Utils import cache_argsless + +from .item_definition_classes import ( + CATEGORY_NAME_MAPPINGS, + DoorItemDefinition, + ItemCategory, + ItemDefinition, + ProgressiveItemDefinition, + WeightedItemDefinition, +) +from .utils import ( + WitnessRule, + define_new_region, + get_items, + get_sigma_expert_logic, + get_sigma_normal_logic, + get_vanilla_logic, + logical_or_witness_rules, + parse_lambda, +) + + +class StaticWitnessLogicObj: + def __init__(self, lines: Optional[List[str]] = None) -> None: + if lines is None: + lines = get_sigma_normal_logic() + + # All regions with a list of panels in them and the connections to other regions, before logic adjustments + self.ALL_REGIONS_BY_NAME: Dict[str, Dict[str, Any]] = {} + self.ALL_AREAS_BY_NAME: Dict[str, Dict[str, Any]] = {} + self.CONNECTIONS_WITH_DUPLICATES: Dict[str, Dict[str, Set[WitnessRule]]] = defaultdict(lambda: defaultdict(set)) + self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = {} + + self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {} + self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {} + self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = {} + + self.OBELISK_SIDE_ID_TO_EP_HEXES: Dict[int, Set[int]] = {} + + self.EP_TO_OBELISK_SIDE: Dict[str, str] = {} + + self.ENTITY_ID_TO_NAME: Dict[str, str] = {} + + self.read_logic_file(lines) + self.reverse_connections() + self.combine_connections() + + def read_logic_file(self, lines: List[str]) -> None: + """ + Reads the logic file and does the initial population of data structures + """ + + current_region = {} + current_area: Dict[str, Any] = { + "name": "Misc", + "regions": [], + } + self.ALL_AREAS_BY_NAME["Misc"] = current_area + + for line in lines: + if line == "" or line[0] == "#": + continue + + if line[-1] == ":": + new_region_and_connections = define_new_region(line) + current_region = new_region_and_connections[0] + region_name = current_region["name"] + self.ALL_REGIONS_BY_NAME[region_name] = current_region + for connection in new_region_and_connections[1]: + self.CONNECTIONS_WITH_DUPLICATES[region_name][connection[0]].add(connection[1]) + current_area["regions"].append(region_name) + continue + + if line[0] == "=": + area_name = line[2:-2] + current_area = { + "name": area_name, + "regions": [], + } + self.ALL_AREAS_BY_NAME[area_name] = current_area + continue + + line_split = line.split(" - ") + + location_id = line_split.pop(0) + + entity_name_full = line_split.pop(0) + + entity_hex = entity_name_full[0:7] + entity_name = entity_name_full[9:-1] + + required_panel_lambda = line_split.pop(0) + + full_entity_name = current_region["shortName"] + " " + entity_name + + if location_id == "Door" or location_id == "Laser": + self.ENTITIES_BY_HEX[entity_hex] = { + "checkName": full_entity_name, + "entity_hex": entity_hex, + "region": None, + "id": None, + "entityType": location_id, + "area": current_area, + } + + self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] + + self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = { + "entities": parse_lambda(required_panel_lambda) + } + + # Lasers and Doors exist in a region, but don't have a regional *requirement* + # If a laser is activated, you don't need to physically walk up to it for it to count + # As such, logically, they behave more as if they were part of the "Entry" region + self.ALL_REGIONS_BY_NAME["Entry"]["entities"].append(entity_hex) + # However, it will also be important to keep track of their physical location for postgame purposes. + current_region["physical_entities"].append(entity_hex) + continue + + required_item_lambda = line_split.pop(0) + + laser_names = { + "Laser", + "Laser Hedges", + "Laser Pressure Plates", + } + is_vault_or_video = "Vault" in entity_name or "Video" in entity_name + + if "Discard" in entity_name: + location_type = "Discard" + elif is_vault_or_video or entity_name == "Tutorial Gate Close": + location_type = "Vault" + elif entity_name in laser_names: + location_type = "Laser" + elif "Obelisk Side" in entity_name: + location_type = "Obelisk Side" + elif "EP" in entity_name: + location_type = "EP" + else: + location_type = "General" + + required_items = parse_lambda(required_item_lambda) + required_panels = parse_lambda(required_panel_lambda) + + required_items = frozenset(required_items) + + requirement = { + "entities": required_panels, + "items": required_items + } + + if location_type == "Obelisk Side": + eps = set(next(iter(required_panels))) + eps -= {"Theater to Tunnels"} + + eps_ints = {int(h, 16) for h in eps} + + self.OBELISK_SIDE_ID_TO_EP_HEXES[int(entity_hex, 16)] = eps_ints + for ep_hex in eps: + self.EP_TO_OBELISK_SIDE[ep_hex] = entity_hex + + self.ENTITIES_BY_HEX[entity_hex] = { + "checkName": full_entity_name, + "entity_hex": entity_hex, + "region": current_region, + "id": int(location_id), + "entityType": location_type, + "area": current_area, + } + + self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name + + self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] + self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement + + current_region["entities"].append(entity_hex) + current_region["physical_entities"].append(entity_hex) + + def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None: + target = connection[0] + traversal_options = connection[1] + + # Reverse this connection with all its possibilities, except the ones marked as "OneWay". + for requirement in traversal_options: + remaining_options = set() + for option in requirement: + if not any(req == "TrueOneWay" for req in option): + remaining_options.add(option) + + if remaining_options: + self.CONNECTIONS_WITH_DUPLICATES[target][source_region].add(frozenset(remaining_options)) + + def reverse_connections(self) -> None: + # Iterate all connections + for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()): + for connection in connections.items(): + self.reverse_connection(region_name, connection) + + def combine_connections(self) -> None: + # All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice. + self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: set() for region_name in self.ALL_REGIONS_BY_NAME} + + for source, connections in self.CONNECTIONS_WITH_DUPLICATES.items(): + for target, requirement in connections.items(): + combined_req = logical_or_witness_rules(requirement) + self.STATIC_CONNECTIONS_BY_REGION_NAME[source].add((target, combined_req)) + + +# Item data parsed from WitnessItems.txt +ALL_ITEMS: Dict[str, ItemDefinition] = {} +_progressive_lookup: Dict[str, str] = {} + + +def parse_items() -> None: + """ + Parses currently defined items from WitnessItems.txt + """ + + lines: List[str] = get_items() + current_category: ItemCategory = ItemCategory.SYMBOL + + for line in lines: + # Skip empty lines and comments. + if line == "" or line[0] == "#": + continue + + # If this line is a category header, update our cached category. + if line in CATEGORY_NAME_MAPPINGS.keys(): + current_category = CATEGORY_NAME_MAPPINGS[line] + continue + + line_split = line.split(" - ") + + item_code = int(line_split[0]) + item_name = line_split[1] + arguments: List[str] = line_split[2].split(",") if len(line_split) >= 3 else [] + + if current_category in [ItemCategory.DOOR, ItemCategory.LASER]: + # Map doors to IDs. + ALL_ITEMS[item_name] = DoorItemDefinition(item_code, current_category, arguments) + elif current_category == ItemCategory.TRAP or current_category == ItemCategory.FILLER: + # Read filler weights. + weight = int(arguments[0]) if len(arguments) >= 1 else 1 + ALL_ITEMS[item_name] = WeightedItemDefinition(item_code, current_category, weight) + elif arguments: + # Progressive items. + ALL_ITEMS[item_name] = ProgressiveItemDefinition(item_code, current_category, arguments) + for child_item in arguments: + _progressive_lookup[child_item] = item_name + else: + ALL_ITEMS[item_name] = ItemDefinition(item_code, current_category) + + +def get_parent_progressive_item(item_name: str) -> str: + """ + Returns the name of the item's progressive parent, if there is one, or the item's name if not. + """ + return _progressive_lookup.get(item_name, item_name) + + +@cache_argsless +def get_vanilla() -> StaticWitnessLogicObj: + return StaticWitnessLogicObj(get_vanilla_logic()) + + +@cache_argsless +def get_sigma_normal() -> StaticWitnessLogicObj: + return StaticWitnessLogicObj(get_sigma_normal_logic()) + + +@cache_argsless +def get_sigma_expert() -> StaticWitnessLogicObj: + return StaticWitnessLogicObj(get_sigma_expert_logic()) + + +def __getattr__(name: str) -> StaticWitnessLogicObj: + if name == "vanilla": + return get_vanilla() + if name == "sigma_normal": + return get_sigma_normal() + if name == "sigma_expert": + return get_sigma_expert() + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +parse_items() + +ALL_REGIONS_BY_NAME = get_sigma_normal().ALL_REGIONS_BY_NAME +ALL_AREAS_BY_NAME = get_sigma_normal().ALL_AREAS_BY_NAME +STATIC_CONNECTIONS_BY_REGION_NAME = get_sigma_normal().STATIC_CONNECTIONS_BY_REGION_NAME + +ENTITIES_BY_HEX = get_sigma_normal().ENTITIES_BY_HEX +ENTITIES_BY_NAME = get_sigma_normal().ENTITIES_BY_NAME +STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = get_sigma_normal().STATIC_DEPENDENT_REQUIREMENTS_BY_HEX + +OBELISK_SIDE_ID_TO_EP_HEXES = get_sigma_normal().OBELISK_SIDE_ID_TO_EP_HEXES + +EP_TO_OBELISK_SIDE = get_sigma_normal().EP_TO_OBELISK_SIDE + +ENTITY_ID_TO_NAME = get_sigma_normal().ENTITY_ID_TO_NAME diff --git a/worlds/witness/utils.py b/worlds/witness/data/utils.py similarity index 70% rename from worlds/witness/utils.py rename to worlds/witness/data/utils.py index b1f1b6d83100..f89aaf7d3e18 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/data/utils.py @@ -1,13 +1,21 @@ -from functools import lru_cache from math import floor -from typing import List, Collection, FrozenSet, Tuple, Dict, Any, Set from pkgutil import get_data -from random import random +from random import Random +from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Set, Tuple, TypeVar +T = TypeVar("T") -def weighted_sample(world_random: random, population: List, weights: List[float], k: int): +# A WitnessRule is just an or-chain of and-conditions. +# It represents the set of all options that could fulfill this requirement. +# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}} +# {} is an unusable requirement. +# {{}} is an always usable requirement. +WitnessRule = FrozenSet[FrozenSet[str]] + + +def weighted_sample(world_random: Random, population: List[T], weights: List[float], k: int) -> List[T]: positions = range(len(population)) - indices = [] + indices: List[int] = [] while True: needed = k - len(indices) if not needed: @@ -48,7 +56,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]: return rounded_output -def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str, FrozenSet[FrozenSet[str]]]]]: +def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]: """ Returns a region object by parsing a line in the logic file """ @@ -76,12 +84,13 @@ def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str region_obj = { "name": region_name, "shortName": region_name_simple, - "panels": list() + "entities": [], + "physical_entities": [], } return region_obj, options -def parse_lambda(lambda_string) -> FrozenSet[FrozenSet[str]]: +def parse_lambda(lambda_string: str) -> WitnessRule: """ Turns a lambda String literal like this: a | b & c into a set of sets like this: {{a}, {b, c}} @@ -90,31 +99,20 @@ def parse_lambda(lambda_string) -> FrozenSet[FrozenSet[str]]: if lambda_string == "True": return frozenset([frozenset()]) split_ands = set(lambda_string.split(" | ")) - lambda_set = frozenset({frozenset(a.split(" & ")) for a in split_ands}) - - return lambda_set + return frozenset({frozenset(a.split(" & ")) for a in split_ands}) -class lazy(object): - def __init__(self, func, name=None): - self.func = func - self.name = name if name is not None else func.__name__ - self.__doc__ = func.__doc__ +_adjustment_file_cache = {} - def __get__(self, instance, class_): - if instance is None: - res = self.func(class_) - setattr(class_, self.name, res) - return res - res = self.func(instance) - setattr(instance, self.name, res) - return res - -@lru_cache(maxsize=None) def get_adjustment_file(adjustment_file: str) -> List[str]: - data = get_data(__name__, adjustment_file).decode('utf-8') - return [line.strip() for line in data.split("\n")] + if adjustment_file not in _adjustment_file_cache: + data = get_data(__name__, adjustment_file) + if data is None: + raise FileNotFoundError(f"Could not find {adjustment_file}") + _adjustment_file_cache[adjustment_file] = [line.strip() for line in data.decode("utf-8").split("\n")] + + return _adjustment_file_cache[adjustment_file] def get_disable_unrandomized_list() -> List[str]: @@ -177,6 +175,10 @@ def get_ep_obelisks() -> List[str]: return get_adjustment_file("settings/EP_Shuffle/EP_Sides.txt") +def get_obelisk_keys() -> List[str]: + return get_adjustment_file("settings/Door_Shuffle/Obelisk_Keys.txt") + + def get_ep_easy() -> List[str]: return get_adjustment_file("settings/EP_Shuffle/EP_Easy.txt") @@ -193,36 +195,8 @@ def get_discard_exclusion_list() -> List[str]: return get_adjustment_file("settings/Exclusions/Discards.txt") -def get_caves_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Caves.txt") - - -def get_beyond_challenge_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Beyond_Challenge.txt") - - -def get_bottom_floor_discard_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Bottom_Floor_Discard.txt") - - -def get_bottom_floor_discard_nondoors_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Bottom_Floor_Discard_NonDoors.txt") - - -def get_mountain_upper_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Mountain_Upper.txt") - - -def get_challenge_vault_box_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Challenge_Vault_Box.txt") - - -def get_path_to_challenge_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Path_To_Challenge.txt") - - -def get_mountain_lower_exclusion_list() -> List[str]: - return get_adjustment_file("settings/Postgame/Mountain_Lower.txt") +def get_caves_except_path_to_challenge_exclusion_list() -> List[str]: + return get_adjustment_file("settings/Exclusions/Caves_Except_Path_To_Challenge.txt") def get_elevators_come_to_you() -> List[str]: @@ -245,29 +219,29 @@ def get_items() -> List[str]: return get_adjustment_file("WitnessItems.txt") -def dnf_remove_redundancies(dnf_requirement: FrozenSet[FrozenSet[str]]) -> FrozenSet[FrozenSet[str]]: +def optimize_witness_rule(witness_rule: WitnessRule) -> WitnessRule: """Removes any redundant terms from a logical formula in disjunctive normal form. This means removing any terms that are a superset of any other term get removed. This is possible because of the boolean absorption law: a | (a & b) = a""" to_remove = set() - for option1 in dnf_requirement: - for option2 in dnf_requirement: + for option1 in witness_rule: + for option2 in witness_rule: if option2 < option1: to_remove.add(option1) - return dnf_requirement - to_remove + return witness_rule - to_remove -def dnf_and(dnf_requirements: List[FrozenSet[FrozenSet[str]]]) -> FrozenSet[FrozenSet[str]]: +def logical_and_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRule: """ performs the "and" operator on a list of logical formula in disjunctive normal form, represented as a set of sets. A logical formula might look like this: {{a, b}, {c, d}}, which would mean "a & b | c & d". These can be easily and-ed by just using the boolean distributive law: (a | b) & c = a & c | a & b. """ - current_overall_requirement = frozenset({frozenset()}) + current_overall_requirement: FrozenSet[FrozenSet[str]] = frozenset({frozenset()}) - for next_dnf_requirement in dnf_requirements: + for next_dnf_requirement in witness_rules: new_requirement: Set[FrozenSet[str]] = set() for option1 in current_overall_requirement: @@ -276,4 +250,8 @@ def dnf_and(dnf_requirements: List[FrozenSet[FrozenSet[str]]]) -> FrozenSet[Froz current_overall_requirement = frozenset(new_requirement) - return dnf_remove_redundancies(current_overall_requirement) + return optimize_witness_rule(current_overall_requirement) + + +def logical_or_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRule: + return optimize_witness_rule(frozenset.union(*witness_rules)) diff --git a/worlds/witness/docs/en_The Witness.md b/worlds/witness/docs/en_The Witness.md index 4d00ecaae451..6882ed3fdedf 100644 --- a/worlds/witness/docs/en_The Witness.md +++ b/worlds/witness/docs/en_The Witness.md @@ -1,8 +1,8 @@ # The Witness -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -16,7 +16,7 @@ Panels with puzzle symbols on them are now locked initially. ## What is a "check" in The Witness? Solving the last panel in a row of panels or an important standalone panel will count as a check, and send out an item. -It is also possible to add Environmental Puzzles into the location pool via the "Shuffle Environmental Puzzles" setting. +It is also possible to add Environmental Puzzles into the location pool via the "Shuffle Environmental Puzzles" option. ## What "items" can you unlock in The Witness? @@ -25,7 +25,7 @@ This includes symbols such as "Dots", "Black/White Squares", "Colored Squares", Alternatively (or additionally), you can play "Door shuffle", where some doors won't open until you receive their "key". -Receiving lasers as items is also a possible setting. +You can also set lasers to be items you can receive. ## What else can I find in the world? diff --git a/worlds/witness/docs/setup_en.md b/worlds/witness/docs/setup_en.md index daa9b8b9b5dd..7b6d631198f9 100644 --- a/worlds/witness/docs/setup_en.md +++ b/worlds/witness/docs/setup_en.md @@ -43,4 +43,4 @@ The Witness has a fully functional map tracker that supports auto-tracking. 3. Click on the "AP" symbol at the top. 4. Enter the AP address, slot name and password. -The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It will hide checks & adjust logic accordingly. +The rest should take care of itself! Items and checks will be marked automatically, and it even knows your options - It will hide checks & adjust logic accordingly. diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 4b40ba32dfda..a1ca1b081d3c 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -1,173 +1,18 @@ import logging from dataclasses import dataclass -from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional, Union -from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState -from . import StaticWitnessLogic -from .utils import weighted_sample +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union + +from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld + +from .data import static_logic as static_witness_logic +from .data.utils import weighted_sample +from .player_items import WitnessItem if TYPE_CHECKING: from . import WitnessWorld CompactItemData = Tuple[str, Union[str, int], int] -joke_hints = [ - "Quaternions break my brain", - "Eclipse has nothing, but you should do it anyway.", - "Beep", - "Putting in custom subtitles shouldn't have been as hard as it was...", - "BK mode is right around the corner.", - "You can do it!", - "I believe in you!", - "The person playing is cute. <3", - "dash dot, dash dash dash,\ndash, dot dot dot dot, dot dot,\ndash dot, dash dash dot", - "When you think about it, there are actually a lot of bubbles in a stream.", - "Never gonna give you up\nNever gonna let you down\nNever gonna run around and desert you", - "Thanks to the Archipelago developers for making this possible.", - "Have you tried ChecksFinder?\nIf you like puzzles, you might enjoy it!", - "Have you tried Dark Souls III?\nA tough game like this feels better when friends are helping you!", - "Have you tried Donkey Kong Country 3?\nA legendary game from a golden age of platformers!", - "Have you tried Factorio?\nAlone in an unknown multiworld. Sound familiar?", - "Have you tried Final Fantasy?\nExperience a classic game improved to fit modern standards!", - "Have you tried Hollow Knight?\nAnother independent hit revolutionising a genre!", - "Have you tried A Link to the Past?\nThe Archipelago game that started it all!", - "Have you tried Meritous?\nYou should know that obscure games are often groundbreaking!", - "Have you tried Ocarina of Time?\nOne of the biggest randomizers, big inspiration for this one's features!", - "Have you tried Raft?\nHaven't you always wanted to explore the ocean surrounding this island?", - "Have you tried Risk of Rain 2?\nI haven't either. But I hear it's incredible!", - "Have you tried Rogue Legacy?\nAfter solving so many puzzles it's the perfect way to rest your \"thinking\" brain.", - "Have you tried Secret of Evermore?\nI haven't either. But I hear it's great!", - "Have you tried Slay the Spire?\nExperience the thrill of combat without needing fast fingers!", - "Have you tried SMZ3?\nWhy play one incredible game when you can play 2 at once?", - "Have you tried Starcraft 2?\nUse strategy and management to crush your enemies!", - "Have you tried Super Mario 64?\n3-dimensional games like this owe everything to that game.", - "Have you tried Super Metroid?\nA classic game, yet still one of the best in the genre.", - "Have you tried Timespinner?\nEveryone who plays it ends up loving it!", - "Have you tried VVVVVV?\nExperience the essence of gaming distilled into its purest form!", - "Have you tried The Witness?\nOh. I guess you already have. Thanks for playing!", - "Have you tried Super Mario World?\nI don't think I need to tell you that it is beloved by many.", - "Have you tried Overcooked 2?\nWhen you're done relaxing with puzzles, use your energy to yell at your friends.", - "Have you tried Zillion?\nMe neither. But it looks fun. So, let's try something new together?", - "Have you tried Hylics 2?\nStop motion might just be the epitome of unique art styles.", - "Have you tried Pokemon Red&Blue?\nA cute pet collecting game that fascinated an entire generation.", - "Have you tried Lufia II?\nRoguelites are not just a 2010s phenomenon, turns out.", - "Have you tried Minecraft?\nI have recently learned this is a question that needs to be asked.", - "Have you tried Subnautica?\nIf you like this game's lonely atmosphere, I would suggest you try it.", - - "Have you tried Sonic Adventure 2?\nIf the silence on this island is getting to you, " - "there aren't many games more energetic.", - - "Waiting to get your items?\nTry BK Sudoku! Make progress even while stuck.", - - "Have you tried Adventure?\n...Holy crud, that game is 17 years older than me.", - "Have you tried Muse Dash?\nRhythm game with cute girls!\n(Maybe skip if you don't like the Jungle panels)", - "Have you tried Clique?\nIt's certainly a lot less complicated than this game!", - "Have you tried Bumper Stickers?\nDecades after its inception, people are still inventing unique twists on the match-3 genre.", - "Have you tried DLC Quest?\nI know you all like parody games.\nI got way too many requests to make a randomizer for \"The Looker\".", - "Have you tried Doom?\nI wonder if a smart fridge can connect to Archipelago.", - "Have you tried Kingdom Hearts II?\nI'll wait for you to name a more epic crossover.", - "Have you tried Link's Awakening DX?\nHopefully, Link won't be obsessed with circles when he wakes up.", - "Have you tried The Messenger?\nOld ideas made new again. It's how all art is made.", - "Have you tried Mega Man Battle Network 3?\nIt's a Mega Man RPG. How could you not want to try that?", - "Have you tried Noita?\nIf you like punishing yourself, you will like it.", - "Have you tried Stardew Valley?\nThe Farming game that gave a damn. It's so easy to lose hours and days to it...", - "Have you tried The Legend of Zelda?\nIn some sense, it was the starting point of \"adventure\" in video games.", - "Have you tried Undertale?\nI hope I'm not the 10th person to ask you that. But it's, like, really good.", - "Have you tried Wargroove?\nI'm glad that for every abandoned series, enough people are yearning for its return that one of them will know how to code.", - "Have you tried Blasphemous?\nYou haven't? Blasphemy!\n...Sorry. You should try it, though!", - "Have you tried Doom II?\nGot a good game on your hands? Just make it bigger and better.", - "Have you tried Lingo?\nIt's an open world puzzle game. It features panels with non-verbally explained mechanics.\nIf you like this game, you'll like Lingo too.", - "(Middle Yellow)\nYOU AILED OVERNIGHT\nH--- --- ----- -----?", - "Have you tried Bumper Stickers?\nMaybe after spending so much time on this island, you are longing for a simpler puzzle game.", - "Have you tried Pokemon Emerald?\nI'm going to say it: 10/10, just the right amount of water.", - "Have you tried Terraria?\nA prime example of a survival sandbox game that beats the \"Wide as an ocean, deep as a puddle\" allegations.", - "Have you tried Final Fantasy Mystic Quest?\nApparently, it was made in an attempt to simplify Final Fantasy for the western market.\nThey were right, I suck at RPGs.", - "Have you tried Shivers?\nWitness 2 should totally feature a haunted Museum.", - "Have you tried Heretic?\nWait, there is a Doom Engine game where you can look UP AND DOWN???", - - "One day I was fascinated by the subject of generation of waves by wind.", - "I don't like sandwiches. Why would you think I like sandwiches? Have you ever seen me with a sandwich?", - "Where are you right now?\nI'm at soup!\nWhat do you mean you're at soup?", - "Remember to ask in the Archipelago Discord what the Functioning Brain does.", - "Don't use your puzzle skips, you might need them later.", - "For an extra challenge, try playing blindfolded.", - "Go to the top of the mountain and see if you can see your house.", - "Yellow = Red + Green\nCyan = Green + Blue\nMagenta = Red + Blue", - "Maybe that panel really is unsolvable.", - "Did you make sure it was plugged in?", - "Do not look into laser with remaining eye.", - "Try pressing Space to jump.", - "The Witness is a Doom clone.\nJust replace the demons with puzzles", - "Test Hint please ignore", - "Shapers can never be placed outside the panel boundaries, even if subtracted.", - "The Keep laser panels use the same trick on both sides!", - "Can't get past a door? Try going around. Can't go around? Try building a nether portal.", - "We've been trying to reach you about your car's extended warranty.", - "I hate this game. I hate this game. I hate this game.\n- Chess player Bobby Fischer", - "Dear Mario,\nPlease come to the castle. I've baked a cake for you!", - "Have you tried waking up?\nYeah, me neither.", - "Why do they call it The Witness, when wit game the player view play of with the game.", - "THE WIND FISH IN NAME ONLY, FOR IT IS NEITHER", - "Like this game?\nTry The Wit.nes, Understand, INSIGHT, Taiji What the Witness?, and Tametsi.", - "In a race, It's survival of the Witnesst.", - "This hint has been removed. We apologize for your inconvenience.", - "O-----------", - "Circle is draw\nSquare is separate\nLine is win", - "Circle is draw\nStar is pair\nLine is win", - "Circle is draw\nCircle is copy\nLine is win", - "Circle is draw\nDot is eat\nLine is win", - "Circle is start\nWalk is draw\nLine is win", - "Circle is start\nLine is win\nWitness is you", - "Can't find any items?\nConsider a relaxing boat trip around the island!", - "Don't forget to like, comment, and subscribe.", - "Ah crap, gimme a second.\n[papers rustling]\nSorry, nothing.", - "Trying to get a hint? Too bad.", - "Here's a hint: Get good at the game.", - "I'm still not entirely sure what we're witnessing here.", - "Have you found a red page yet? No? Then have you found a blue page?", - "And here we see the Witness player, seeking answers where there are none-\nDid someone turn on the loudspeaker?", - - "Be quiet. I can't hear the elevator.", - "Witness me.\n- The famous last words of John Witness.", - "It's okay, I always have to skip the Rotated Shaper puzzles too.", - "Alan please add hint.", - "Rumor has it there's an audio log with a hint nearby.", - "In the future, war will break out between obelisk_sides and individual EP players.\nWhich side are you on?", - "Droplets: Low, High, Mid.\nAmbience: Mid, Low, Mid, High.", - "Name a better game involving lines. I'll wait.", - "\"You have to draw a line in the sand.\"\n- Arin \"Egoraptor\" Hanson", - "Have you tried?\nThe puzzles tend to get easier if you do.", - "Sorry, I accidentally left my phone in the Jungle.\nAnd also all my fragile dishes.", - "Winner of the \"Most Irrelevant PR in AP History\" award!", - "I bet you wish this was a real hint :)", - "\"This hint is an impostor.\"- Junk hint submitted by T1mshady.\n...wait, I'm not supposed to say that part?", - "Wouldn't you like to know, weather buoy?", - "Give me a few minutes, I should have better material by then.", - "Just pet the doggy! You know you want to!!!", - "ceci n'est pas une metroidvania", - "HINT is MELT\nYOU is HOT", - "Who's that behind you?", - ":3", - "^v ^^v> >>^>v\n^^v>v ^v>> v>^> v>v^", - "Statement #0162601, regarding a strange island that--\nOh, wait, sorry. I'm not supposed to be here.", - "Hollow Bastion has 6 progression items.\nOr maybe it doesn't.\nI wouldn't know.", - "Set your hint count lower so I can tell you more jokes next time.", - "A non-edge start point is similar to a cat.\nIt must be either inside or outside, it can't be both.", - "What if we kissed on the Bunker Laser Platform?\nJk... unless?", - "You don't have Boat? Invisible boat time!\nYou do have boat? Boat clipping time!", - "Cet indice est en français. Nous nous excusons de tout inconvénients engendrés par cela.", - "How many of you have personally witnessed a total solar eclipse?", - "In the Treehouse area, you will find 69 progression items.\nNice.\n(Source: Just trust me)", - "Lingo\nLingoing\nLingone", - "The name of the captain was Albert Einstein.", - "Panel impossible Sigma plz fix", - "Welcome Back! (:", - "R R R U L L U L U R U R D R D R U U", - "Have you tried checking your tracker?", - - "Hints suggested by:\nIHNN, Beaker, MrPokemon11, Ember, TheM8, NewSoupVi, Jasper Bird, T1mshady," - "KF, Yoshi348, Berserker, BowlinJim, oddGarrett, Pink Switch, Rever, Ishigh, snolid.", -] - @dataclass class WitnessLocationHint: @@ -175,10 +20,12 @@ class WitnessLocationHint: hint_came_from_location: bool # If a hint gets added to a set twice, but once as an item hint and once as a location hint, those are the same - def __hash__(self): + def __hash__(self) -> int: return hash(self.location) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: + if not isinstance(other, WitnessLocationHint): + return False return self.location == other.location @@ -307,7 +154,7 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: "Boat Shipwreck Green EP", "Quarry Stoneworks Control Room Left", ] - + # Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side if "0x33A20" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: priority.append("Town Obelisk Side 6") # Theater Flowers EP @@ -321,15 +168,19 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: return priority -def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint): +def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint) -> WitnessWordedHint: location_name = hint.location.name if hint.location.player != world.player: location_name += " (" + world.multiworld.get_player_name(hint.location.player) + ")" item = hint.location.item - item_name = item.name - if item.player != world.player: - item_name += " (" + world.multiworld.get_player_name(item.player) + ")" + + item_name = "Nothing" + if item is not None: + item_name = item.name + + if item.player != world.player: + item_name += " (" + world.multiworld.get_player_name(item.player) + ")" if hint.hint_came_from_location: hint_text = f"{location_name} contains {item_name}." @@ -339,33 +190,40 @@ def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint): return WitnessWordedHint(hint_text, hint.location) -def hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]) -> Optional[WitnessLocationHint]: - - locations = [item.location for item in own_itempool if item.name == item_name and item.location] +def hint_from_item(world: "WitnessWorld", item_name: str, + own_itempool: List["WitnessItem"]) -> Optional[WitnessLocationHint]: + def get_real_location(multiworld: MultiWorld, location: Location) -> Location: + """If this location is from an item_link pseudo-world, get the location that the item_link item is on. + Return the original location otherwise / as a fallback.""" + if location.player not in world.multiworld.groups: + return location + + try: + if not location.item: + return location + return multiworld.find_item(location.item.name, location.player) + except StopIteration: + return location + + locations = [ + get_real_location(world.multiworld, item.location) + for item in own_itempool if item.name == item_name and item.location + ] if not locations: return None location_obj = world.random.choice(locations) - location_name = location_obj.name - - if location_obj.player != world.player: - location_name += " (" + world.multiworld.get_player_name(location_obj.player) + ")" return WitnessLocationHint(location_obj, False) def hint_from_location(world: "WitnessWorld", location: str) -> Optional[WitnessLocationHint]: - location_obj = world.multiworld.get_location(location, world.player) - item_obj = world.multiworld.get_location(location, world.player).item - item_name = item_obj.name - if item_obj.player != world.player: - item_name += " (" + world.multiworld.get_player_name(item_obj.player) + ")" - - return WitnessLocationHint(location_obj, True) + return WitnessLocationHint(world.get_location(location), True) -def get_items_and_locations_in_random_order(world: "WitnessWorld", own_itempool: List[Item]): +def get_items_and_locations_in_random_order(world: "WitnessWorld", + own_itempool: List["WitnessItem"]) -> Tuple[List[str], List[str]]: prog_items_in_this_world = sorted( item.name for item in own_itempool if item.advancement and item.code and item.location @@ -381,7 +239,7 @@ def get_items_and_locations_in_random_order(world: "WitnessWorld", own_itempool: return prog_items_in_this_world, locations_in_this_world -def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List[Item], +def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List["WitnessItem"], already_hinted_locations: Set[Location] ) -> Tuple[List[WitnessLocationHint], List[WitnessLocationHint]]: prog_items_in_this_world, loc_in_this_world = get_items_and_locations_in_random_order(world, own_itempool) @@ -428,17 +286,21 @@ def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List[Ite return always_hints, priority_hints -def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item], +def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List["WitnessItem"], already_hinted_locations: Set[Location], hints_to_use_first: List[WitnessLocationHint], unhinted_locations_for_hinted_areas: Dict[str, Set[Location]]) -> List[WitnessWordedHint]: prog_items_in_this_world, locations_in_this_world = get_items_and_locations_in_random_order(world, own_itempool) next_random_hint_is_location = world.random.randrange(0, 2) - hints = [] + hints: List[WitnessWordedHint] = [] # This is a way to reverse a Dict[a,List[b]] to a Dict[b,a] - area_reverse_lookup = {v: k for k, l in unhinted_locations_for_hinted_areas.items() for v in l} + area_reverse_lookup = { + unhinted_location: hinted_area + for hinted_area, unhinted_locations in unhinted_locations_for_hinted_areas.items() + for unhinted_location in unhinted_locations + } while len(hints) < hint_amount: if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first: @@ -446,6 +308,7 @@ def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itemp logging.warning(f"Ran out of items/locations to hint for player {player_name}.") break + location_hint: Optional[WitnessLocationHint] if hints_to_use_first: location_hint = hints_to_use_first.pop() elif next_random_hint_is_location and locations_in_this_world: @@ -459,7 +322,7 @@ def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itemp next_random_hint_is_location = not next_random_hint_is_location continue - if not location_hint or location_hint.location in already_hinted_locations: + if location_hint is None or location_hint.location in already_hinted_locations: continue # Don't hint locations in areas that are almost fully hinted out already @@ -478,10 +341,6 @@ def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itemp return hints -def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int, int]]: - return [(x, -1, -1) for x in world.random.sample(joke_hints, amount)] - - def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[str, List[Location]], already_hinted_locations: Set[Location]) -> Tuple[List[str], Dict[str, Set[Location]]]: """ @@ -490,8 +349,8 @@ def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[st When this happens, they are made less likely to receive an area hint. """ - unhinted_locations_per_area = dict() - unhinted_location_percentage_per_area = dict() + unhinted_locations_per_area = {} + unhinted_location_percentage_per_area = {} for area_name, locations in locations_per_area.items(): not_yet_hinted_locations = sum(location not in already_hinted_locations for location in locations) @@ -512,16 +371,16 @@ def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[st def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]: - potential_areas = list(StaticWitnessLogic.ALL_AREAS_BY_NAME.keys()) + potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.keys()) - locations_per_area = dict() - items_per_area = dict() + locations_per_area = {} + items_per_area = {} for area in potential_areas: regions = [ - world.regio.created_regions[region] - for region in StaticWitnessLogic.ALL_AREAS_BY_NAME[area]["regions"] - if region in world.regio.created_regions + world.get_region(region) + for region in static_witness_logic.ALL_AREAS_BY_NAME[area]["regions"] + if region in world.player_regions.created_region_names ] locations = [location for region in regions for location in region.get_locations() if location.address] @@ -579,7 +438,7 @@ def word_area_hint(world: "WitnessWorld", hinted_area: str, corresponding_items: if local_lasers == total_progression: sentence_end = (" for this world." if player_count > 1 else ".") - hint_string += f"\nAll of them are lasers" + sentence_end + hint_string += "\nAll of them are lasers" + sentence_end elif player_count > 1: if local_progression and non_local_progression: @@ -646,7 +505,7 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int, already_hinted_locations |= { loc for loc in world.multiworld.get_reachable_locations(state, world.player) - if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" + if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" } intended_location_hints = hint_amount - area_hints @@ -679,7 +538,7 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int, location_hints_created_in_round_1 = len(generated_hints) - unhinted_locations_per_area: Dict[str, Set[Location]] = dict() + unhinted_locations_per_area: Dict[str, Set[Location]] = {} # Then, make area hints. if area_hints: @@ -730,17 +589,29 @@ def make_compact_hint_data(hint: WitnessWordedHint, local_player_number: int) -> location = hint.location area_amount = hint.area_amount - # None if junk hint, address if location hint, area string if area hint - arg_1 = location.address if location else (hint.area if hint.area else None) + # -1 if junk hint, address if location hint, area string if area hint + arg_1: Union[str, int] + if location and location.address is not None: + arg_1 = location.address + elif hint.area is not None: + arg_1 = hint.area + else: + arg_1 = -1 # self.player if junk hint, player if location hint, progression amount if area hint - arg_2 = area_amount if area_amount is not None else (location.player if location else local_player_number) + arg_2: int + if area_amount is not None: + arg_2 = area_amount + elif location is not None: + arg_2 = location.player + else: + arg_2 = local_player_number return hint.wording, arg_1, arg_2 def make_laser_hints(world: "WitnessWorld", laser_names: List[str]) -> Dict[str, WitnessWordedHint]: - laser_hints_by_name = dict() + laser_hints_by_name = {} for item_name in laser_names: location_hint = hint_from_item(world, item_name, world.own_itempool) diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index cd6d71f46911..1796f051b896 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -3,511 +3,24 @@ """ from typing import TYPE_CHECKING +from .data import static_locations as static_witness_locations +from .data import static_logic as static_witness_logic from .player_logic import WitnessPlayerLogic -from .static_logic import StaticWitnessLogic if TYPE_CHECKING: from . import WitnessWorld -ID_START = 158000 - - -class StaticWitnessLocations: - """ - Witness Location Constants that stay consistent across worlds - """ - - GENERAL_LOCATIONS = { - "Tutorial Front Left", - "Tutorial Back Left", - "Tutorial Back Right", - "Tutorial Patio Floor", - "Tutorial Gate Open", - - "Outside Tutorial Vault Box", - "Outside Tutorial Discard", - "Outside Tutorial Shed Row 5", - "Outside Tutorial Tree Row 9", - "Outside Tutorial Outpost Entry Panel", - "Outside Tutorial Outpost Exit Panel", - - "Glass Factory Discard", - "Glass Factory Back Wall 5", - "Glass Factory Front 3", - "Glass Factory Melting 3", - - "Symmetry Island Lower Panel", - "Symmetry Island Right 5", - "Symmetry Island Back 6", - "Symmetry Island Left 7", - "Symmetry Island Upper Panel", - "Symmetry Island Scenery Outlines 5", - "Symmetry Island Laser Yellow 3", - "Symmetry Island Laser Blue 3", - "Symmetry Island Laser Panel", - - "Orchard Apple Tree 5", - - "Desert Vault Box", - "Desert Discard", - "Desert Surface 8", - "Desert Light Room 3", - "Desert Pond Room 5", - "Desert Flood Room 6", - "Desert Elevator Room Hexagonal", - "Desert Elevator Room Bent 3", - "Desert Laser Panel", - - "Quarry Entry 1 Panel", - "Quarry Entry 2 Panel", - "Quarry Stoneworks Entry Left Panel", - "Quarry Stoneworks Entry Right Panel", - "Quarry Stoneworks Lower Row 6", - "Quarry Stoneworks Upper Row 8", - "Quarry Stoneworks Control Room Left", - "Quarry Stoneworks Control Room Right", - "Quarry Stoneworks Stairs Panel", - "Quarry Boathouse Intro Right", - "Quarry Boathouse Intro Left", - "Quarry Boathouse Front Row 5", - "Quarry Boathouse Back First Row 9", - "Quarry Boathouse Back Second Row 3", - "Quarry Discard", - "Quarry Laser Panel", - - "Shadows Intro 8", - "Shadows Far 8", - "Shadows Near 5", - "Shadows Laser Panel", - - "Keep Hedge Maze 1", - "Keep Hedge Maze 2", - "Keep Hedge Maze 3", - "Keep Hedge Maze 4", - "Keep Pressure Plates 1", - "Keep Pressure Plates 2", - "Keep Pressure Plates 3", - "Keep Pressure Plates 4", - "Keep Discard", - "Keep Laser Panel Hedges", - "Keep Laser Panel Pressure Plates", - - "Shipwreck Vault Box", - "Shipwreck Discard", - - "Monastery Outside 3", - "Monastery Inside 4", - "Monastery Laser Panel", - - "Town Cargo Box Entry Panel", - "Town Cargo Box Discard", - "Town Tall Hexagonal", - "Town Church Entry Panel", - "Town Church Lattice", - "Town Maze Panel", - "Town Rooftop Discard", - "Town Red Rooftop 5", - "Town Wooden Roof Lower Row 5", - "Town Wooden Rooftop", - "Windmill Entry Panel", - "Town RGB House Entry Panel", - "Town Laser Panel", - - "Town RGB House Upstairs Left", - "Town RGB House Upstairs Right", - "Town RGB House Sound Room Right", - - "Windmill Theater Entry Panel", - "Theater Exit Left Panel", - "Theater Exit Right Panel", - "Theater Tutorial Video", - "Theater Desert Video", - "Theater Jungle Video", - "Theater Shipwreck Video", - "Theater Mountain Video", - "Theater Discard", - - "Jungle Discard", - "Jungle First Row 3", - "Jungle Second Row 4", - "Jungle Popup Wall 6", - "Jungle Laser Panel", - - "Jungle Vault Box", - "Jungle Monastery Garden Shortcut Panel", - - "Bunker Entry Panel", - "Bunker Intro Left 5", - "Bunker Intro Back 4", - "Bunker Glass Room 3", - "Bunker UV Room 2", - "Bunker Laser Panel", - - "Swamp Entry Panel", - "Swamp Intro Front 6", - "Swamp Intro Back 8", - "Swamp Between Bridges Near Row 4", - "Swamp Cyan Underwater 5", - "Swamp Platform Row 4", - "Swamp Platform Shortcut Right Panel", - "Swamp Between Bridges Far Row 4", - "Swamp Red Underwater 4", - "Swamp Purple Underwater", - "Swamp Beyond Rotating Bridge 4", - "Swamp Blue Underwater 5", - "Swamp Laser Panel", - "Swamp Laser Shortcut Right Panel", - - "Treehouse First Door Panel", - "Treehouse Second Door Panel", - "Treehouse Third Door Panel", - "Treehouse Yellow Bridge 9", - "Treehouse First Purple Bridge 5", - "Treehouse Second Purple Bridge 7", - "Treehouse Green Bridge 7", - "Treehouse Green Bridge Discard", - "Treehouse Left Orange Bridge 15", - "Treehouse Laser Discard", - "Treehouse Right Orange Bridge 12", - "Treehouse Laser Panel", - "Treehouse Drawbridge Panel", - - "Mountainside Discard", - "Mountainside Vault Box", - "Mountaintop River Shape", - - "Tutorial First Hallway EP", - "Tutorial Cloud EP", - "Tutorial Patio Flowers EP", - "Tutorial Gate EP", - "Outside Tutorial Garden EP", - "Outside Tutorial Town Sewer EP", - "Outside Tutorial Path EP", - "Outside Tutorial Tractor EP", - "Mountainside Thundercloud EP", - "Glass Factory Vase EP", - "Symmetry Island Glass Factory Black Line Reflection EP", - "Symmetry Island Glass Factory Black Line EP", - "Desert Sand Snake EP", - "Desert Facade Right EP", - "Desert Facade Left EP", - "Desert Stairs Left EP", - "Desert Stairs Right EP", - "Desert Broken Wall Straight EP", - "Desert Broken Wall Bend EP", - "Desert Shore EP", - "Desert Island EP", - "Desert Pond Room Near Reflection EP", - "Desert Pond Room Far Reflection EP", - "Desert Flood Room EP", - "Desert Elevator EP", - "Quarry Shore EP", - "Quarry Entrance Pipe EP", - "Quarry Sand Pile EP", - "Quarry Rock Line EP", - "Quarry Rock Line Reflection EP", - "Quarry Railroad EP", - "Quarry Stoneworks Ramp EP", - "Quarry Stoneworks Lift EP", - "Quarry Boathouse Moving Ramp EP", - "Quarry Boathouse Hook EP", - "Shadows Quarry Stoneworks Rooftop Vent EP", - "Treehouse Beach Rock Shadow EP", - "Treehouse Beach Sand Shadow EP", - "Treehouse Beach Both Orange Bridges EP", - "Keep Red Flowers EP", - "Keep Purple Flowers EP", - "Shipwreck Circle Near EP", - "Shipwreck Circle Left EP", - "Shipwreck Circle Far EP", - "Shipwreck Stern EP", - "Shipwreck Rope Inner EP", - "Shipwreck Rope Outer EP", - "Shipwreck Couch EP", - "Keep Pressure Plates 1 EP", - "Keep Pressure Plates 2 EP", - "Keep Pressure Plates 3 EP", - "Keep Pressure Plates 4 Left Exit EP", - "Keep Pressure Plates 4 Right Exit EP", - "Keep Path EP", - "Keep Hedges EP", - "Monastery Facade Left Near EP", - "Monastery Facade Left Far Short EP", - "Monastery Facade Left Far Long EP", - "Monastery Facade Right Near EP", - "Monastery Facade Left Stairs EP", - "Monastery Facade Right Stairs EP", - "Monastery Grass Stairs EP", - "Monastery Left Shutter EP", - "Monastery Middle Shutter EP", - "Monastery Right Shutter EP", - "Windmill First Blade EP", - "Windmill Second Blade EP", - "Windmill Third Blade EP", - "Town Tower Underside Third EP", - "Town Tower Underside Fourth EP", - "Town Tower Underside First EP", - "Town Tower Underside Second EP", - "Town RGB House Red EP", - "Town RGB House Green EP", - "Town Maze Bridge Underside EP", - "Town Black Line Redirect EP", - "Town Black Line Church EP", - "Town Brown Bridge EP", - "Town Black Line Tower EP", - "Theater Eclipse EP", - "Theater Window EP", - "Theater Door EP", - "Theater Church EP", - "Jungle Long Arch Moss EP", - "Jungle Straight Left Moss EP", - "Jungle Pop-up Wall Moss EP", - "Jungle Short Arch Moss EP", - "Jungle Entrance EP", - "Jungle Tree Halo EP", - "Jungle Bamboo CCW EP", - "Jungle Bamboo CW EP", - "Jungle Green Leaf Moss EP", - "Monastery Garden Left EP", - "Monastery Garden Right EP", - "Monastery Wall EP", - "Bunker Tinted Door EP", - "Bunker Green Room Flowers EP", - "Swamp Purple Sand Middle EP", - "Swamp Purple Sand Top EP", - "Swamp Purple Sand Bottom EP", - "Swamp Sliding Bridge Left EP", - "Swamp Sliding Bridge Right EP", - "Swamp Cyan Underwater Sliding Bridge EP", - "Swamp Rotating Bridge CCW EP", - "Swamp Rotating Bridge CW EP", - "Swamp Boat EP", - "Swamp Long Bridge Side EP", - "Swamp Purple Underwater Right EP", - "Swamp Purple Underwater Left EP", - "Treehouse Buoy EP", - "Treehouse Right Orange Bridge EP", - "Treehouse Burned House Beach EP", - "Mountainside Cloud Cycle EP", - "Mountainside Bush EP", - "Mountainside Apparent River EP", - "Mountaintop River Shape EP", - "Mountaintop Arch Black EP", - "Mountaintop Arch White Right EP", - "Mountaintop Arch White Left EP", - "Mountain Bottom Floor Yellow Bridge EP", - "Mountain Bottom Floor Blue Bridge EP", - "Mountain Floor 2 Pink Bridge EP", - "Caves Skylight EP", - "Challenge Water EP", - "Tunnels Theater Flowers EP", - "Boat Desert EP", - "Boat Shipwreck CCW Underside EP", - "Boat Shipwreck Green EP", - "Boat Shipwreck CW Underside EP", - "Boat Bunker Yellow Line EP", - "Boat Town Long Sewer EP", - "Boat Tutorial EP", - "Boat Tutorial Reflection EP", - "Boat Tutorial Moss EP", - "Boat Cargo Box EP", - - "Desert Obelisk Side 1", - "Desert Obelisk Side 2", - "Desert Obelisk Side 3", - "Desert Obelisk Side 4", - "Desert Obelisk Side 5", - "Monastery Obelisk Side 1", - "Monastery Obelisk Side 2", - "Monastery Obelisk Side 3", - "Monastery Obelisk Side 4", - "Monastery Obelisk Side 5", - "Monastery Obelisk Side 6", - "Treehouse Obelisk Side 1", - "Treehouse Obelisk Side 2", - "Treehouse Obelisk Side 3", - "Treehouse Obelisk Side 4", - "Treehouse Obelisk Side 5", - "Treehouse Obelisk Side 6", - "Mountainside Obelisk Side 1", - "Mountainside Obelisk Side 2", - "Mountainside Obelisk Side 3", - "Mountainside Obelisk Side 4", - "Mountainside Obelisk Side 5", - "Mountainside Obelisk Side 6", - "Quarry Obelisk Side 1", - "Quarry Obelisk Side 2", - "Quarry Obelisk Side 3", - "Quarry Obelisk Side 4", - "Quarry Obelisk Side 5", - "Town Obelisk Side 1", - "Town Obelisk Side 2", - "Town Obelisk Side 3", - "Town Obelisk Side 4", - "Town Obelisk Side 5", - "Town Obelisk Side 6", - - "Caves Mountain Shortcut Panel", - "Caves Swamp Shortcut Panel", - - "Caves Blue Tunnel Right First 4", - "Caves Blue Tunnel Left First 1", - "Caves Blue Tunnel Left Second 5", - "Caves Blue Tunnel Right Second 5", - "Caves Blue Tunnel Right Third 1", - "Caves Blue Tunnel Left Fourth 1", - "Caves Blue Tunnel Left Third 1", - - "Caves First Floor Middle", - "Caves First Floor Right", - "Caves First Floor Left", - "Caves First Floor Grounded", - "Caves Lone Pillar", - "Caves First Wooden Beam", - "Caves Second Wooden Beam", - "Caves Third Wooden Beam", - "Caves Fourth Wooden Beam", - "Caves Right Upstairs Left Row 8", - "Caves Right Upstairs Right Row 3", - "Caves Left Upstairs Single", - "Caves Left Upstairs Left Row 5", - - "Caves Challenge Entry Panel", - "Challenge Tunnels Entry Panel", - - "Tunnels Vault Box", - "Theater Challenge Video", - - "Tunnels Town Shortcut Panel", - - "Caves Skylight EP", - "Challenge Water EP", - "Tunnels Theater Flowers EP", - "Tutorial Gate EP", - - "Mountaintop Mountain Entry Panel", - - "Mountain Floor 1 Light Bridge Controller", - - "Mountain Floor 1 Right Row 5", - "Mountain Floor 1 Left Row 7", - "Mountain Floor 1 Back Row 3", - "Mountain Floor 1 Trash Pillar 2", - "Mountain Floor 2 Near Row 5", - "Mountain Floor 2 Far Row 6", - - "Mountain Floor 2 Light Bridge Controller Near", - "Mountain Floor 2 Light Bridge Controller Far", - - "Mountain Bottom Floor Yellow Bridge EP", - "Mountain Bottom Floor Blue Bridge EP", - "Mountain Floor 2 Pink Bridge EP", - - "Mountain Floor 2 Elevator Discard", - "Mountain Bottom Floor Giant Puzzle", - - "Mountain Bottom Floor Pillars Room Entry Left", - "Mountain Bottom Floor Pillars Room Entry Right", - - "Mountain Bottom Floor Caves Entry Panel", - - "Mountain Bottom Floor Left Pillar 4", - "Mountain Bottom Floor Right Pillar 4", - - "Challenge Vault Box", - "Theater Challenge Video", - "Mountain Bottom Floor Discard", - } - - OBELISK_SIDES = { - "Desert Obelisk Side 1", - "Desert Obelisk Side 2", - "Desert Obelisk Side 3", - "Desert Obelisk Side 4", - "Desert Obelisk Side 5", - "Monastery Obelisk Side 1", - "Monastery Obelisk Side 2", - "Monastery Obelisk Side 3", - "Monastery Obelisk Side 4", - "Monastery Obelisk Side 5", - "Monastery Obelisk Side 6", - "Treehouse Obelisk Side 1", - "Treehouse Obelisk Side 2", - "Treehouse Obelisk Side 3", - "Treehouse Obelisk Side 4", - "Treehouse Obelisk Side 5", - "Treehouse Obelisk Side 6", - "Mountainside Obelisk Side 1", - "Mountainside Obelisk Side 2", - "Mountainside Obelisk Side 3", - "Mountainside Obelisk Side 4", - "Mountainside Obelisk Side 5", - "Mountainside Obelisk Side 6", - "Quarry Obelisk Side 1", - "Quarry Obelisk Side 2", - "Quarry Obelisk Side 3", - "Quarry Obelisk Side 4", - "Quarry Obelisk Side 5", - "Town Obelisk Side 1", - "Town Obelisk Side 2", - "Town Obelisk Side 3", - "Town Obelisk Side 4", - "Town Obelisk Side 5", - "Town Obelisk Side 6", - } - - ALL_LOCATIONS_TO_ID = dict() - - AREA_LOCATION_GROUPS = dict() - - @staticmethod - def get_id(chex: str): - """ - Calculates the location ID for any given location - """ - - return StaticWitnessLogic.ENTITIES_BY_HEX[chex]["id"] - - @staticmethod - def get_event_name(panel_hex: str): - """ - Returns the event name of any given panel. - """ - - action = " Opened" if StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["entityType"] == "Door" else " Solved" - - return StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["checkName"] + action - - def __init__(self): - all_loc_to_id = { - panel_obj["checkName"]: self.get_id(chex) - for chex, panel_obj in StaticWitnessLogic.ENTITIES_BY_HEX.items() - if panel_obj["id"] - } - - all_loc_to_id = dict( - sorted(all_loc_to_id.items(), key=lambda loc: loc[1]) - ) - - for key, item in all_loc_to_id.items(): - self.ALL_LOCATIONS_TO_ID[key] = item - - for loc in all_loc_to_id: - area = StaticWitnessLogic.ENTITIES_BY_NAME[loc]["area"]["name"] - self.AREA_LOCATION_GROUPS.setdefault(area, []).append(loc) - - class WitnessPlayerLocations: """ Class that defines locations for a single player """ - def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic): + def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic) -> None: """Defines locations AFTER logic changes due to options""" self.PANEL_TYPES_TO_SHUFFLE = {"General", "Laser"} - self.CHECK_LOCATIONS = StaticWitnessLocations.GENERAL_LOCATIONS.copy() + self.CHECK_LOCATIONS = static_witness_locations.GENERAL_LOCATIONS.copy() if world.options.shuffle_discarded_panels: self.PANEL_TYPES_TO_SHUFFLE.add("Discard") @@ -520,55 +33,50 @@ def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic): elif world.options.shuffle_EPs == "obelisk_sides": self.PANEL_TYPES_TO_SHUFFLE.add("Obelisk Side") - for obelisk_loc in StaticWitnessLocations.OBELISK_SIDES: - obelisk_loc_hex = StaticWitnessLogic.ENTITIES_BY_NAME[obelisk_loc]["entity_hex"] + for obelisk_loc in static_witness_locations.OBELISK_SIDES: + obelisk_loc_hex = static_witness_logic.ENTITIES_BY_NAME[obelisk_loc]["entity_hex"] if player_logic.REQUIREMENTS_BY_HEX[obelisk_loc_hex] == frozenset({frozenset()}): self.CHECK_LOCATIONS.discard(obelisk_loc) self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | player_logic.ADDED_CHECKS - self.CHECK_LOCATIONS.discard(StaticWitnessLogic.ENTITIES_BY_HEX[player_logic.VICTORY_LOCATION]["checkName"]) + self.CHECK_LOCATIONS.discard(static_witness_logic.ENTITIES_BY_HEX[player_logic.VICTORY_LOCATION]["checkName"]) self.CHECK_LOCATIONS = self.CHECK_LOCATIONS - { - StaticWitnessLogic.ENTITIES_BY_HEX[entity_hex]["checkName"] + static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"] for entity_hex in player_logic.COMPLETELY_DISABLED_ENTITIES | player_logic.PRECOMPLETED_LOCATIONS } self.CHECK_PANELHEX_TO_ID = { - StaticWitnessLogic.ENTITIES_BY_NAME[ch]["entity_hex"]: StaticWitnessLocations.ALL_LOCATIONS_TO_ID[ch] + static_witness_logic.ENTITIES_BY_NAME[ch]["entity_hex"]: static_witness_locations.ALL_LOCATIONS_TO_ID[ch] for ch in self.CHECK_LOCATIONS - if StaticWitnessLogic.ENTITIES_BY_NAME[ch]["entityType"] in self.PANEL_TYPES_TO_SHUFFLE + if static_witness_logic.ENTITIES_BY_NAME[ch]["entityType"] in self.PANEL_TYPES_TO_SHUFFLE } - dog_hex = StaticWitnessLogic.ENTITIES_BY_NAME["Town Pet the Dog"]["entity_hex"] - dog_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID["Town Pet the Dog"] + dog_hex = static_witness_logic.ENTITIES_BY_NAME["Town Pet the Dog"]["entity_hex"] + dog_id = static_witness_locations.ALL_LOCATIONS_TO_ID["Town Pet the Dog"] self.CHECK_PANELHEX_TO_ID[dog_hex] = dog_id self.CHECK_PANELHEX_TO_ID = dict( sorted(self.CHECK_PANELHEX_TO_ID.items(), key=lambda item: item[1]) ) - event_locations = { - p for p in player_logic.USED_EVENT_NAMES_BY_HEX - } + event_locations = set(player_logic.USED_EVENT_NAMES_BY_HEX) self.EVENT_LOCATION_TABLE = { - StaticWitnessLocations.get_event_name(panel_hex): None - for panel_hex in event_locations + static_witness_locations.get_event_name(entity_hex): None + for entity_hex in event_locations } check_dict = { - StaticWitnessLogic.ENTITIES_BY_HEX[location]["checkName"]: - StaticWitnessLocations.get_id(StaticWitnessLogic.ENTITIES_BY_HEX[location]["entity_hex"]) + static_witness_logic.ENTITIES_BY_HEX[location]["checkName"]: + static_witness_locations.get_id(static_witness_logic.ENTITIES_BY_HEX[location]["entity_hex"]) for location in self.CHECK_PANELHEX_TO_ID } self.CHECK_LOCATION_TABLE = {**self.EVENT_LOCATION_TABLE, **check_dict} - def add_location_late(self, entity_name: str): - entity_hex = StaticWitnessLogic.ENTITIES_BY_NAME[entity_name]["entity_hex"] - self.CHECK_LOCATION_TABLE[entity_hex] = entity_name - self.CHECK_PANELHEX_TO_ID[entity_hex] = StaticWitnessLocations.get_id(entity_hex) - - -StaticWitnessLocations() + def add_location_late(self, entity_name: str) -> None: + entity_hex = static_witness_logic.ENTITIES_BY_NAME[entity_name]["entity_hex"] + self.CHECK_LOCATION_TABLE[entity_hex] = static_witness_locations.get_id(entity_hex) + self.CHECK_PANELHEX_TO_ID[entity_hex] = static_witness_locations.get_id(entity_hex) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index a24896e1d057..4855fc715933 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,53 +1,78 @@ from dataclasses import dataclass -from schema import Schema, And, Optional +from schema import And, Schema -from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict +from Options import Choice, DefaultOnToggle, OptionDict, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility -from .static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import ItemCategory, WeightedItemDefinition class DisableNonRandomizedPuzzles(Toggle): - """Disables puzzles that cannot be randomized. + """ + Disables puzzles that cannot be randomized. This includes many puzzles that heavily involve the environment, such as Shadows, Monastery or Orchard. + The lasers for those areas will activate as you solve optional puzzles, such as Discarded Panels. - Additionally, the panels activating Monastery Laser and Jungle Popup Wall will be on from the start.""" + Additionally, the panel activating the Jungle Popup Wall will be on from the start. + """ display_name = "Disable non randomized puzzles" class EarlyCaves(Choice): - """Adds an item that opens the Caves Shortcuts to Swamp and Mountain, - allowing early access to the Caves even if you are not playing a remote Door Shuffle mode. - You can either add this item to the pool to be found on one of your randomized checks, - or you can outright start with it and have immediate access to the Caves. - If you choose "add_to_pool" and you are already playing a remote Door Shuffle mode, this setting will do nothing.""" + """ + Adds an item that opens the Caves Shortcuts to Swamp and Mountain, allowing early access to the Caves even if you are not playing a remote Door Shuffle mode. + You can either add this item to the pool to be found in the multiworld, or you can outright start with it and have immediate access to the Caves. + + If you choose "Add To Pool" and you are already playing a remote Door Shuffle mode, this option will do nothing. + """ display_name = "Early Caves" option_off = 0 + alias_false = 0 option_add_to_pool = 1 option_starting_inventory = 2 + alias_true = 2 + alias_on = 2 + + +class EarlySymbolItem(DefaultOnToggle): + """ + Put a random helpful symbol item on an early check, specifically Tutorial Gate Open if it is available early. + """ + + visibility = Visibility.none class ShuffleSymbols(DefaultOnToggle): - """You will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols. - If you turn this off, there will be no progression items in the game unless you turn on door shuffle.""" + """ + If on, you will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols. + + Please note that there is no minimum set of progression items in this randomizer. + If you turn this option off and don't turn on door shuffle or obelisk keys, there will be no progression items, which will disallow you from adding your yaml to a multiworld generation. + """ display_name = "Shuffle Symbols" class ShuffleLasers(Choice): - """If on, the 11 lasers are turned into items and will activate on their own upon receiving them. - Note: There is a visual bug that can occur with the Desert Laser. It does not affect gameplay - The Laser can still - be redirected as normal, for both applications of redirection.""" + """ + If on, the 11 lasers are turned into items and will activate on their own upon receiving them. + """ display_name = "Shuffle Lasers" option_off = 0 + alias_false = 0 option_local = 1 option_anywhere = 2 + alias_true = 2 + alias_on = 2 class ShuffleDoors(Choice): - """If on, opening doors, moving bridges etc. will require a "key". - If set to "panels", the panel on the door will be locked until receiving its corresponding key. - If set to "doors", the door will open immediately upon receiving its key. Door panels are added as location checks. - "Mixed" includes all doors from "doors", and all control panels (bridges, elevators etc.) from "panels".""" + """ + If on, opening doors, moving bridges etc. will require a "key". + - Panels: The panel on the door will be locked until receiving its corresponding key. + - Doors: The door will open immediately upon receiving its key. Door panels are added as location checks. + - Mixed: Includes all doors from "doors", and all control panels (bridges, elevators etc.) from "panels". + """ display_name = "Shuffle Doors" option_off = 0 option_panels = 1 @@ -56,38 +81,47 @@ class ShuffleDoors(Choice): class DoorGroupings(Choice): - """If set to "none", there will be one key for every door, resulting in up to 120 keys being added to the item pool. - If set to "regional", all doors in the same general region will open at once with a single key, - reducing the amount of door items and complexity.""" + """ + Controls how door items are grouped. + + - Off: There will be one key for each door, potentially resulting in upwards of 120 keys being added to the item pool. + - Regional: All doors in the same general region will open at once with a single key, reducing the amount of door items and complexity. + """ display_name = "Door Groupings" option_off = 0 option_regional = 1 class ShuffleBoat(DefaultOnToggle): - """If set, adds a "Boat" item to the item pool. Before receiving this item, you will not be able to use the boat.""" + """ + If on, adds a "Boat" item to the item pool. Before receiving this item, you will not be able to use the boat. + """ display_name = "Shuffle Boat" class ShuffleDiscardedPanels(Toggle): - """Add Discarded Panels into the location pool. - Solving certain Discarded Panels may still be necessary to beat the game, even if this is off - The main example - of this being the alternate activation triggers in disable_non_randomized.""" + """ + Adds Discarded Panels into the location pool. + Even if this is off, solving certain Discarded Panels may still be necessary to beat the game - The main example of this being the alternate activation triggers in "Disable non randomized puzzles". + """ display_name = "Shuffle Discarded Panels" class ShuffleVaultBoxes(Toggle): - """Add Vault Boxes to the location pool.""" + """ + Adds Vault Boxes to the location pool. + """ display_name = "Shuffle Vault Boxes" class ShuffleEnvironmentalPuzzles(Choice): """ - Add Environmental/Obelisk Puzzles into the location pool. - In "individual", every Environmental Puzzle sends an item. - In "obelisk_sides", completing every puzzle on one side of an Obelisk sends an item. - Note: In Obelisk Sides, any EPs excluded through another setting will be counted as pre-completed on their Obelisk. + Adds Environmental/Obelisk Puzzles into the location pool. + - Individual: Every Environmental Puzzle sends an item. + - Obelisk Sides: Completing every puzzle on one side of an Obelisk sends an item. + + Note: In Obelisk Sides, any EPs excluded through another option will be pre-completed on their Obelisk. """ display_name = "Shuffle Environmental Puzzles" option_off = 0 @@ -96,17 +130,18 @@ class ShuffleEnvironmentalPuzzles(Choice): class ShuffleDog(Toggle): - """Add petting the Town dog into the location pool.""" - + """ + Adds petting the Town dog into the location pool. + """ display_name = "Pet the Dog" class EnvironmentalPuzzlesDifficulty(Choice): """ When "Shuffle Environmental Puzzles" is on, this setting governs which EPs are eligible for the location pool. - On "eclipse", every EP in the game is eligible, including the 1-hour-long "Theater Eclipse EP". - On "tedious", Theater Eclipse EP is excluded from the location pool. - On "normal", several other difficult or long EPs are excluded as well. + - Eclipse: Every EP in the game is eligible, including the 1-hour-long "Theater Eclipse EP". + - Tedious Theater Eclipse EP is excluded from the location pool. + - Normal: several other difficult or long EPs are excluded as well. """ display_name = "Environmental Puzzles Difficulty" option_normal = 0 @@ -114,21 +149,34 @@ class EnvironmentalPuzzlesDifficulty(Choice): option_eclipse = 2 +class ObeliskKeys(DefaultOnToggle): + """ + Add one Obelisk Key item per Obelisk, locking you out of solving any of the associated Environmental Puzzles. + + Does nothing if "Shuffle Environmental Puzzles" is set to "off". + """ + display_name = "Obelisk Keys" + + class ShufflePostgame(Toggle): - """Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal. - Use this if you don't play with release on victory. IMPORTANT NOTE: The possibility of your second - "Progressive Dots" showing up in the Caves is ignored, they will still be considered "postgame" in base settings.""" + """ + Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal. + Use this if you don't play with release on victory. + """ display_name = "Shuffle Postgame" class VictoryCondition(Choice): - """Set the victory condition for this world. - Elevator: Start the elevator at the bottom of the mountain (requires Mountain Lasers). - Challenge: Beat the secret Challenge (requires Challenge Lasers). - Mountain Box Short: Input the short solution to the Mountaintop Box (requires Mountain Lasers). - Mountain Box Long: Input the long solution to the Mountaintop Box (requires Challenge Lasers). + """ + Set the victory condition for this world. + - Elevator: Start the elevator at the bottom of the mountain (requires Mountain Lasers). + - Challenge: Beat the secret Challenge (requires Challenge Lasers). + - Mountain Box Short: Input the short solution to the Mountaintop Box (requires Mountain Lasers). + - Mountain Box Long: Input the long solution to the Mountaintop Box (requires Challenge Lasers). + It is important to note that while the Mountain Box requires Desert Laser to be redirected in Town for that laser - to count, the laser locks on the Elevator and Challenge Timer panels do not.""" + to count, the laser locks on the Elevator and Challenge Timer panels do not. + """ display_name = "Victory Condition" option_elevator = 0 option_challenge = 1 @@ -137,7 +185,9 @@ class VictoryCondition(Choice): class PuzzleRandomization(Choice): - """Puzzles in this randomizer are randomly generated. This setting changes the difficulty/types of puzzles.""" + """ + Puzzles in this randomizer are randomly generated. This option changes the difficulty/types of puzzles. + """ display_name = "Puzzle Randomization" option_sigma_normal = 0 option_sigma_expert = 1 @@ -145,10 +195,11 @@ class PuzzleRandomization(Choice): class MountainLasers(Range): - """Sets the amount of lasers required to enter the Mountain. - If set to a higher amount than 7, the mountaintop box will be slightly rotated to make it possible to solve without - the hatch being opened. - This change will also be applied logically to the long solution ("Challenge Lasers" setting).""" + """ + Sets the number of lasers required to enter the Mountain. + If set to a higher number than 7, the mountaintop box will be slightly rotated to make it possible to solve without the hatch being opened. + This change will also be applied logically to the long solution ("Challenge Lasers" option). + """ display_name = "Required Lasers for Mountain Entry" range_start = 1 range_end = 11 @@ -156,7 +207,9 @@ class MountainLasers(Range): class ChallengeLasers(Range): - """Sets the amount of beams required to enter the Caves through the Mountain Bottom Floor Discard.""" + """ + Sets the number of lasers required to enter the Caves through the Mountain Bottom Floor Discard and to unlock the Challenge Timer Panel. + """ display_name = "Required Lasers for Challenge" range_start = 1 range_end = 11 @@ -164,13 +217,17 @@ class ChallengeLasers(Range): class ElevatorsComeToYou(Toggle): - """If true, the Quarry Elevator, Bunker Elevator and Swamp Long Bridge will "come to you" if you approach them. - This does actually affect logic as it allows unintended backwards / early access into these areas.""" + """ + If on, the Quarry Elevator, Bunker Elevator and Swamp Long Bridge will "come to you" if you approach them. + This does actually affect logic as it allows unintended backwards / early access into these areas. + """ display_name = "All Bridges & Elevators come to you" class TrapPercentage(Range): - """Replaces junk items with traps, at the specified rate.""" + """ + Replaces junk items with traps, at the specified rate. + """ display_name = "Trap Percentage" range_start = 0 range_end = 100 @@ -178,26 +235,28 @@ class TrapPercentage(Range): class TrapWeights(OptionDict): - """Specify the weights determining how many copies of each trap item will be in your itempool. + """ + Specify the weights determining how many copies of each trap item will be in your itempool. If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). - If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option.""" - + If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option. + """ display_name = "Trap Weights" schema = Schema({ trap_name: And(int, lambda n: n >= 0) - for trap_name, item_definition in StaticWitnessLogic.all_items.items() + for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP }) default = { trap_name: item_definition.weight - for trap_name, item_definition in StaticWitnessLogic.all_items.items() + for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP } class PuzzleSkipAmount(Range): - """Adds this number of Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. - Works on most panels in the game - The only big exception is The Challenge.""" + """ + Adds this many Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. + """ display_name = "Puzzle Skips" range_start = 0 range_end = 30 @@ -205,8 +264,10 @@ class PuzzleSkipAmount(Range): class HintAmount(Range): - """Adds hints to Audio Logs. If set to a low amount, up to 2 additional duplicates of each hint will be added. - Remaining Audio Logs will have junk hints.""" + """ + Adds hints to Audio Logs. If set to a low amount, up to 2 additional duplicates of each hint will be added. + Remaining Audio Logs will have junk hints. + """ display_name = "Hints on Audio Logs" range_start = 0 range_end = 49 @@ -214,11 +275,12 @@ class HintAmount(Range): class AreaHintPercentage(Range): - """There are two types of hints for The Witness. - "Location hints" hint one location in your world / containing an item for your world. - "Area hints" will tell you some general info about the items you can find in one of the - main geographic areas on the island. - Use this option to specify how many of your hints you want to be area hints. The rest will be location hints.""" + """ + There are two types of hints for The Witness. + "Location hints" hint one location in your world or one location containing an item for your world. + "Area hints" tell you some general info about the items you can find in one of the main geographic areas on the island. + Use this option to specify how many of your hints you want to be area hints. The rest will be location hints. + """ display_name = "Area Hint Percentage" range_start = 0 range_end = 100 @@ -226,20 +288,26 @@ class AreaHintPercentage(Range): class LaserHints(Toggle): - """If on, lasers will tell you where their items are if you walk close to them in-game. - Only applies if laser shuffle is enabled.""" + """ + If on, lasers will tell you where their items are if you walk close to them in-game. + Only applies if Laser Shuffle is enabled. + """ display_name = "Laser Hints" class DeathLink(Toggle): - """If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies. - The effect of a "death" in The Witness is a Bonk Trap.""" + """ + If on, whenever you fail a puzzle (with some exceptions), you and everyone who is also on Death Link dies. + The effect of a "death" in The Witness is a Bonk Trap. + """ display_name = "Death Link" class DeathLinkAmnesty(Range): - """Number of panel fails to allow before sending a death through Death Link. - 0 means every panel fail will send a death, 1 means every other panel fail will send a death, etc.""" + """ + The number of panel fails to allow before sending a death through Death Link. + 0 means every panel fail will send a death, 1 means every other panel fail will send a death, etc. + """ display_name = "Death Link Amnesty" range_start = 0 range_end = 5 @@ -257,13 +325,15 @@ class TheWitnessOptions(PerGameCommonOptions): disable_non_randomized_puzzles: DisableNonRandomizedPuzzles shuffle_discarded_panels: ShuffleDiscardedPanels shuffle_vault_boxes: ShuffleVaultBoxes - shuffle_EPs: ShuffleEnvironmentalPuzzles + obelisk_keys: ObeliskKeys + shuffle_EPs: ShuffleEnvironmentalPuzzles # noqa: N815 EP_difficulty: EnvironmentalPuzzlesDifficulty shuffle_postgame: ShufflePostgame victory_condition: VictoryCondition mountain_lasers: MountainLasers challenge_lasers: ChallengeLasers early_caves: EarlyCaves + early_symbol_item: EarlySymbolItem elevators_come_to_you: ElevatorsComeToYou trap_percentage: TrapPercentage trap_weights: TrapWeights @@ -273,3 +343,45 @@ class TheWitnessOptions(PerGameCommonOptions): laser_hints: LaserHints death_link: DeathLink death_link_amnesty: DeathLinkAmnesty + + +witness_option_groups = [ + OptionGroup("Puzzles & Goal", [ + PuzzleRandomization, + VictoryCondition, + MountainLasers, + ChallengeLasers, + ]), + OptionGroup("Locations", [ + ShuffleDiscardedPanels, + ShuffleVaultBoxes, + ShuffleEnvironmentalPuzzles, + EnvironmentalPuzzlesDifficulty, + ShufflePostgame, + DisableNonRandomizedPuzzles, + ]), + OptionGroup("Progression Items", [ + ShuffleSymbols, + ShuffleDoors, + DoorGroupings, + ShuffleLasers, + ShuffleBoat, + ObeliskKeys, + ]), + OptionGroup("Filler Items", [ + PuzzleSkipAmount, + TrapPercentage, + TrapWeights + ]), + OptionGroup("Hints", [ + HintAmount, + AreaHintPercentage, + LaserHints + ]), + OptionGroup("Misc", [ + EarlyCaves, + ElevatorsComeToYou, + DeathLink, + DeathLinkAmnesty, + ]) +] diff --git a/worlds/witness/items.py b/worlds/witness/player_items.py similarity index 59% rename from worlds/witness/items.py rename to worlds/witness/player_items.py index 6802fd2a21b5..718fd7d172ba 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/player_items.py @@ -2,16 +2,23 @@ Defines progression, junk and event items for The Witness """ import copy - -from dataclasses import dataclass -from typing import Optional, Dict, List, Set, TYPE_CHECKING - -from BaseClasses import Item, MultiWorld, ItemClassification -from .locations import ID_START, WitnessPlayerLocations +from typing import TYPE_CHECKING, Dict, List, Set, cast + +from BaseClasses import Item, ItemClassification, MultiWorld + +from .data import static_items as static_witness_items +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import ( + DoorItemDefinition, + ItemCategory, + ItemData, + ItemDefinition, + ProgressiveItemDefinition, + WeightedItemDefinition, +) +from .data.utils import build_weighted_int_list +from .locations import WitnessPlayerLocations from .player_logic import WitnessPlayerLogic -from .static_logic import ItemDefinition, DoorItemDefinition, ProgressiveItemDefinition, ItemCategory, \ - StaticWitnessLogic, WeightedItemDefinition -from .utils import build_weighted_int_list if TYPE_CHECKING: from . import WitnessWorld @@ -19,17 +26,6 @@ NUM_ENERGY_UPGRADES = 4 -@dataclass() -class ItemData: - """ - ItemData for an item in The Witness - """ - ap_code: Optional[int] - definition: ItemDefinition - classification: ItemClassification - local_only: bool = False - - class WitnessItem(Item): """ Item from the game The Witness @@ -37,79 +33,30 @@ class WitnessItem(Item): game: str = "The Witness" -class StaticWitnessItems: - """ - Class that handles Witness items independent of world settings - """ - item_data: Dict[str, ItemData] = {} - item_groups: Dict[str, List[str]] = {} - - # Useful items that are treated specially at generation time and should not be automatically added to the player's - # item list during get_progression_items. - special_usefuls: List[str] = ["Puzzle Skip"] - - def __init__(self): - for item_name, definition in StaticWitnessLogic.all_items.items(): - ap_item_code = definition.local_code + ID_START - classification: ItemClassification = ItemClassification.filler - local_only: bool = False - - if definition.category is ItemCategory.SYMBOL: - classification = ItemClassification.progression - StaticWitnessItems.item_groups.setdefault("Symbols", []).append(item_name) - elif definition.category is ItemCategory.DOOR: - classification = ItemClassification.progression - StaticWitnessItems.item_groups.setdefault("Doors", []).append(item_name) - elif definition.category is ItemCategory.LASER: - classification = ItemClassification.progression_skip_balancing - StaticWitnessItems.item_groups.setdefault("Lasers", []).append(item_name) - elif definition.category is ItemCategory.USEFUL: - classification = ItemClassification.useful - elif definition.category is ItemCategory.FILLER: - if item_name in ["Energy Fill (Small)"]: - local_only = True - classification = ItemClassification.filler - elif definition.category is ItemCategory.TRAP: - classification = ItemClassification.trap - elif definition.category is ItemCategory.JOKE: - classification = ItemClassification.filler - - StaticWitnessItems.item_data[item_name] = ItemData(ap_item_code, definition, - classification, local_only) - - @staticmethod - def get_item_to_door_mappings() -> Dict[int, List[int]]: - output: Dict[int, List[int]] = {} - for item_name, item_data in {name: data for name, data in StaticWitnessItems.item_data.items() - if isinstance(data.definition, DoorItemDefinition)}.items(): - item = StaticWitnessItems.item_data[item_name] - output[item.ap_code] = [int(hex_string, 16) for hex_string in item_data.definition.panel_id_hexes] - return output - - class WitnessPlayerItems: """ Class that defines Items for a single world """ - def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: WitnessPlayerLocations): + def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic, + player_locations: WitnessPlayerLocations) -> None: """Adds event items after logic changes due to options""" self._world: "WitnessWorld" = world self._multiworld: MultiWorld = world.multiworld self._player_id: int = world.player - self._logic: WitnessPlayerLogic = logic - self._locations: WitnessPlayerLocations = locat + self._logic: WitnessPlayerLogic = player_logic + self._locations: WitnessPlayerLocations = player_locations # Duplicate the static item data, then make any player-specific adjustments to classification. - self.item_data: Dict[str, ItemData] = copy.deepcopy(StaticWitnessItems.item_data) + self.item_data: Dict[str, ItemData] = copy.deepcopy(static_witness_items.ITEM_DATA) # Remove all progression items that aren't actually in the game. self.item_data = { name: data for (name, data) in self.item_data.items() if data.classification not in - {ItemClassification.progression, ItemClassification.progression_skip_balancing} - or name in logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME + {ItemClassification.progression, ItemClassification.progression_skip_balancing} + or name in player_logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME } # Downgrade door items @@ -138,9 +85,10 @@ def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: Witn # Add setting-specific useful items to the mandatory item list. for item_name, item_data in {name: data for (name, data) in self.item_data.items() if data.classification == ItemClassification.useful}.items(): - if item_name in StaticWitnessItems.special_usefuls: + if item_name in static_witness_items._special_usefuls: continue - elif item_name == "Energy Capacity": + + if item_name == "Energy Capacity": self._mandatory_items[item_name] = NUM_ENERGY_UPGRADES elif isinstance(item_data.classification, ProgressiveItemDefinition): self._mandatory_items[item_name] = len(item_data.mappings) @@ -149,7 +97,7 @@ def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: Witn # Add event items to the item definition list for later lookup. for event_location in self._locations.EVENT_LOCATION_TABLE: - location_name = logic.EVENT_ITEM_PAIRS[event_location] + location_name = player_logic.EVENT_ITEM_PAIRS[event_location] self.item_data[location_name] = ItemData(None, ItemDefinition(0, ItemCategory.EVENT), ItemClassification.progression, False) @@ -207,10 +155,7 @@ def get_early_items(self) -> List[str]: """ output: Set[str] = set() if self._world.options.shuffle_symbols: - if self._world.options.shuffle_doors: - output = {"Dots", "Black/White Squares", "Symmetry"} - else: - output = {"Dots", "Black/White Squares", "Symmetry", "Shapers", "Stars"} + output = {"Dots", "Black/White Squares", "Symmetry", "Shapers", "Stars"} if self._world.options.shuffle_discarded_panels: if self._world.options.puzzle_randomization == "sigma_expert": @@ -219,7 +164,7 @@ def get_early_items(self) -> List[str]: output.add("Triangles") # Replace progressive items with their parents. - output = {StaticWitnessLogic.get_parent_progressive_item(item) for item in output} + output = {static_witness_logic.get_parent_progressive_item(item) for item in output} # Remove items that are mentioned in any plando options. (Hopefully, in the future, plando will get resolved # before create_items so that we'll be able to check placed items instead of just removing all items mentioned @@ -227,28 +172,29 @@ def get_early_items(self) -> List[str]: for plando_setting in self._multiworld.plando_items[self._player_id]: if plando_setting.get("from_pool", True): for item_setting_key in [key for key in ["item", "items"] if key in plando_setting]: - if type(plando_setting[item_setting_key]) is str: + if isinstance(plando_setting[item_setting_key], str): output -= {plando_setting[item_setting_key]} - elif type(plando_setting[item_setting_key]) is dict: + elif isinstance(plando_setting[item_setting_key], dict): output -= {item for item, weight in plando_setting[item_setting_key].items() if weight} else: # Assume this is some other kind of iterable. for inner_item in plando_setting[item_setting_key]: - if type(inner_item) is str: + if isinstance(inner_item, str): output -= {inner_item} - elif type(inner_item) is dict: + elif isinstance(inner_item, dict): output -= {item for item, weight in inner_item.items() if weight} # Sort the output for consistency across versions if the implementation changes but the logic does not. - return sorted(list(output)) + return sorted(output) def get_door_ids_in_pool(self) -> List[int]: """ Returns the total set of all door IDs that are controlled by items in the pool. """ output: List[int] = [] - for item_name, item_data in {name: data for name, data in self.item_data.items() - if isinstance(data.definition, DoorItemDefinition)}.items(): + for item_name, item_data in dict(self.item_data.items()).items(): + if not isinstance(item_data.definition, DoorItemDefinition): + continue output += [int(hex_string, 16) for hex_string in item_data.definition.panel_id_hexes] return output @@ -257,19 +203,21 @@ def get_symbol_ids_not_in_pool(self) -> List[int]: """ Returns the item IDs of symbol items that were defined in the configuration file but are not in the pool. """ - return [data.ap_code for name, data in StaticWitnessItems.item_data.items() - if name not in self.item_data.keys() and data.definition.category is ItemCategory.SYMBOL] + return [ + # data.ap_code is guaranteed for a symbol definition + cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items() + if name not in self.item_data.keys() and data.definition.category is ItemCategory.SYMBOL + ] def get_progressive_item_ids_in_pool(self) -> Dict[int, List[int]]: output: Dict[int, List[int]] = {} - for item_name, quantity in {name: quantity for name, quantity in self._mandatory_items.items()}.items(): + for item_name, quantity in dict(self._mandatory_items.items()).items(): item = self.item_data[item_name] if isinstance(item.definition, ProgressiveItemDefinition): # Note: we need to reference the static table here rather than the player-specific one because the child # items were removed from the pool when we pruned out all progression items not in the settings. - output[item.ap_code] = [StaticWitnessItems.item_data[child_item].ap_code - for child_item in item.definition.child_item_names] + output[cast(int, item.ap_code)] = [cast(int, static_witness_items.ITEM_DATA[child_item].ap_code) + for child_item in item.definition.child_item_names] return output -StaticWitnessItems() diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 229da0a2879a..e8d11f43f51c 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -17,11 +17,40 @@ import copy from collections import defaultdict -from typing import cast, TYPE_CHECKING from logging import warning - -from .static_logic import StaticWitnessLogic, DoorItemDefinition, ItemCategory, ProgressiveItemDefinition -from .utils import * +from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast + +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition +from .data.static_logic import StaticWitnessLogicObj +from .data.utils import ( + WitnessRule, + define_new_region, + get_boat, + get_caves_except_path_to_challenge_exclusion_list, + get_complex_additional_panels, + get_complex_door_panels, + get_complex_doors, + get_disable_unrandomized_list, + get_discard_exclusion_list, + get_early_caves_list, + get_early_caves_start_list, + get_elevators_come_to_you, + get_ep_all_individual, + get_ep_easy, + get_ep_no_eclipse, + get_ep_obelisks, + get_laser_shuffle, + get_obelisk_keys, + get_simple_additional_panels, + get_simple_doors, + get_simple_panels, + get_symbol_shuffle_list, + get_vault_exclusion_list, + logical_and_witness_rules, + logical_or_witness_rules, + parse_lambda, +) if TYPE_CHECKING: from . import WitnessWorld @@ -30,8 +59,96 @@ class WitnessPlayerLogic: """WITNESS LOGIC CLASS""" - @lru_cache(maxsize=None) - def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]: + VICTORY_LOCATION: str + + def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_inv: Dict[str, int]) -> None: + self.YAML_DISABLED_LOCATIONS: Set[str] = disabled_locations + self.YAML_ADDED_ITEMS: Dict[str, int] = start_inv + + self.EVENT_PANELS_FROM_PANELS: Set[str] = set() + self.EVENT_PANELS_FROM_REGIONS: Set[str] = set() + + self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES: Set[str] = set() + + self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY: Set[str] = set() + + self.UNREACHABLE_REGIONS: Set[str] = set() + + self.THEORETICAL_ITEMS: Set[str] = set() + self.THEORETICAL_ITEMS_NO_MULTI: Set[str] = set() + self.MULTI_AMOUNTS: Dict[str, int] = defaultdict(lambda: 1) + self.MULTI_LISTS: Dict[str, List[str]] = {} + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI: Set[str] = set() + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME: Set[str] = set() + self.DOOR_ITEMS_BY_ID: Dict[str, List[str]] = {} + self.STARTING_INVENTORY: Set[str] = set() + + self.DIFFICULTY = world.options.puzzle_randomization + + self.REFERENCE_LOGIC: StaticWitnessLogicObj + if self.DIFFICULTY == "sigma_expert": + self.REFERENCE_LOGIC = static_witness_logic.sigma_expert + elif self.DIFFICULTY == "none": + self.REFERENCE_LOGIC = static_witness_logic.vanilla + else: + self.REFERENCE_LOGIC = static_witness_logic.sigma_normal + + self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy( + self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME + ) + self.CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy( + self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME + ) + self.DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = copy.deepcopy( + self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX + ) + self.REQUIREMENTS_BY_HEX: Dict[str, WitnessRule] = {} + + self.EVENT_ITEM_PAIRS: Dict[str, str] = {} + self.COMPLETELY_DISABLED_ENTITIES: Set[str] = set() + self.DISABLE_EVERYTHING_BEHIND: Set[str] = set() + self.PRECOMPLETED_LOCATIONS: Set[str] = set() + self.EXCLUDED_LOCATIONS: Set[str] = set() + self.ADDED_CHECKS: Set[str] = set() + self.VICTORY_LOCATION = "0x0356B" + + self.ALWAYS_EVENT_NAMES_BY_HEX = { + "0x00509": "+1 Laser (Symmetry Laser)", + "0x012FB": "+1 Laser (Desert Laser)", + "0x09F98": "Desert Laser Redirection", + "0x01539": "+1 Laser (Quarry Laser)", + "0x181B3": "+1 Laser (Shadows Laser)", + "0x014BB": "+1 Laser (Keep Laser)", + "0x17C65": "+1 Laser (Monastery Laser)", + "0x032F9": "+1 Laser (Town Laser)", + "0x00274": "+1 Laser (Jungle Laser)", + "0x0C2B2": "+1 Laser (Bunker Laser)", + "0x00BF6": "+1 Laser (Swamp Laser)", + "0x028A4": "+1 Laser (Treehouse Laser)", + "0x17C34": "Mountain Entry", + "0xFFF00": "Bottom Floor Discard Turns On", + } + + self.USED_EVENT_NAMES_BY_HEX: Dict[str, str] = {} + self.CONDITIONAL_EVENTS: Dict[Tuple[str, str], str] = {} + + # The basic requirements to solve each entity come from StaticWitnessLogic. + # However, for any given world, the options (e.g. which item shuffles are enabled) affect the requirements. + self.make_options_adjustments(world) + self.determine_unrequired_entities(world) + self.find_unsolvable_entities(world) + + # After we have adjusted the raw requirements, we perform a dependency reduction for the entity requirements. + # This will make the access conditions way faster, instead of recursively checking dependent entities each time. + self.make_dependency_reduced_checklist() + + # Finalize which items actually exist in the MultiWorld and which get grouped into progressive items. + self.finalize_items() + + # Create event-item pairs for specific panels in the game. + self.make_event_panel_lists() + + def reduce_req_within_region(self, entity_hex: str) -> WitnessRule: """ Panels in this game often only turn on when other panels are solved. Those other panels may have different item requirements. @@ -40,113 +157,147 @@ def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]: Panels outside of the same region will still be checked manually. """ - if panel_hex in self.COMPLETELY_DISABLED_ENTITIES or panel_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES: + if self.is_disabled(entity_hex): return frozenset() - entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel_hex] + entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex] - these_items = frozenset({frozenset()}) + if entity_obj["region"] is not None and entity_obj["region"]["name"] in self.UNREACHABLE_REGIONS: + return frozenset() - if entity_obj["id"]: - these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"] + # For the requirement of an entity, we consider two things: + # 1. Any items this entity needs (e.g. Symbols or Door Items) + these_items: WitnessRule = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex].get("items", frozenset({frozenset()})) + # 2. Any entities that this entity depends on (e.g. one panel powering on the next panel in a set) + these_panels: WitnessRule = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex]["entities"] + # Remove any items that don't actually exist in the settings (e.g. Symbol Shuffle turned off) these_items = frozenset({ subset.intersection(self.THEORETICAL_ITEMS_NO_MULTI) for subset in these_items }) + # Update the list of "items that are actually being used by any entity" for subset in these_items: self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(subset) - these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"] - - if panel_hex in self.DOOR_ITEMS_BY_ID: - door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[panel_hex]}) - - all_options = set() - - for dependentItem in door_items: - self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependentItem) - for items_option in these_items: - all_options.add(items_option.union(dependentItem)) - - # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved, - # except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. - # In the future, it would be wise to make a distinction between "power dependencies" and other dependencies. - if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels): - these_items = all_options - - # Another dependency that is not power-based: The Symmetry Island Upper Panel latches - elif panel_hex == "0x1C349": - these_items = all_options - - # For any other door entity, we just return a set with the item that opens it & disregard power dependencies - else: - return frozenset(all_options) - - disabled_eps = {eHex for eHex in self.COMPLETELY_DISABLED_ENTITIES - if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[eHex]["entityType"] == "EP"} - - these_panels = frozenset({panels - disabled_eps - for panels in these_panels}) - - if these_panels == frozenset({frozenset()}): - return these_items - - all_options = set() + # Handle door entities (door shuffle) + if entity_hex in self.DOOR_ITEMS_BY_ID: + # If this entity is opened by a door item that exists in the itempool, add that item to its requirements. + door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[entity_hex]}) + + for dependent_item in door_items: + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependent_item) + + these_items = logical_and_witness_rules([door_items, these_items]) + + # A door entity is opened by its door item instead of previous entities powering it. + # That means we need to ignore any dependent requirements. + # However, there are some entities that depend on other entities because of an environmental reason. + # Those requirements need to be preserved even in door shuffle. + entity_dependencies_need_to_be_preserved = ( + # EPs keep all their entity dependencies + static_witness_logic.ENTITIES_BY_HEX[entity_hex]["entityType"] == "EP" + # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved, + # except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. + # In the future, it'd be wise to make a distinction between "power dependencies" and other dependencies. + or entity_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels) + # Another dependency that is not power-based: The Symmetry Island Upper Panel latches + or entity_hex == "0x1C349" + ) + + # If this is not one of those special cases, solving this door entity only needs its own item requirement. + # Dependent entities from these_panels are ignored, and we just return these_items directly. + if not entity_dependencies_need_to_be_preserved: + return these_items + + # Now that we have item requirements and entity dependencies, it's time for the dependency reduction. + + # For each entity that this entity depends on (e.g. a panel turning on another panel), + # Add that entities requirements to this entity. + # If there are multiple options, consider each, and then or-chain them. + all_options = [] for option in these_panels: - dependent_items_for_option = frozenset({frozenset()}) + dependent_items_for_option: WitnessRule = frozenset({frozenset()}) + # For each entity in this option, resolve it to its actual requirement. for option_entity in option: - dep_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX.get(option_entity) + dep_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX.get(option_entity, {}) - if option_entity in self.ALWAYS_EVENT_NAMES_BY_HEX: - new_items = frozenset({frozenset([option_entity])}) - elif (panel_hex, option_entity) in self.CONDITIONAL_EVENTS: - new_items = frozenset({frozenset([option_entity])}) - self.USED_EVENT_NAMES_BY_HEX[option_entity] = self.CONDITIONAL_EVENTS[(panel_hex, option_entity)] - elif option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect", - "PP2 Weirdness", "Theater to Tunnels"}: + if option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect", + "PP2 Weirdness", "Theater to Tunnels"}: new_items = frozenset({frozenset([option_entity])}) + elif option_entity in self.DISABLE_EVERYTHING_BEHIND: + new_items = frozenset() else: - new_items = self.reduce_req_within_region(option_entity) - if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]: - new_items = frozenset( - frozenset(possibility | {dep_obj["region"]["name"]}) - for possibility in new_items - ) + theoretical_new_items = self.get_entity_requirement(option_entity) + + if not theoretical_new_items: + # If the dependent entity is unsolvable & it is an EP, the current entity is an Obelisk Side. + # In this case, we actually have to skip it because it will just become pre-solved instead. + if dep_obj["entityType"] == "EP": + continue + # If the dependent entity is unsolvable and is NOT an EP, this requirement option is invalid. + new_items = frozenset() + elif option_entity in self.ALWAYS_EVENT_NAMES_BY_HEX: + new_items = frozenset({frozenset([option_entity])}) + elif (entity_hex, option_entity) in self.CONDITIONAL_EVENTS: + new_items = frozenset({frozenset([option_entity])}) + self.USED_EVENT_NAMES_BY_HEX[option_entity] = self.CONDITIONAL_EVENTS[ + (entity_hex, option_entity) + ] + else: + new_items = theoretical_new_items + if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]: + new_items = frozenset( + frozenset(possibility | {dep_obj["region"]["name"]}) + for possibility in new_items + ) + + dependent_items_for_option = logical_and_witness_rules([dependent_items_for_option, new_items]) + + # Combine the resolved dependent entity requirements with the item requirements of this entity. + all_options.append(logical_and_witness_rules([these_items, dependent_items_for_option])) - dependent_items_for_option = dnf_and([dependent_items_for_option, new_items]) + # or-chain all separate dependent entity options. + return logical_or_witness_rules(all_options) + + def get_entity_requirement(self, entity_hex: str) -> WitnessRule: + """ + Get requirement of entity by its hex code. + These requirements are cached, with the actual function calculating them being reduce_req_within_region. + """ + requirement = self.REQUIREMENTS_BY_HEX.get(entity_hex) - for items_option in these_items: - for dependentItem in dependent_items_for_option: - all_options.add(items_option.union(dependentItem)) + if requirement is None: + requirement = self.reduce_req_within_region(entity_hex) + self.REQUIREMENTS_BY_HEX[entity_hex] = requirement - return dnf_remove_redundancies(frozenset(all_options)) + return requirement - def make_single_adjustment(self, adj_type: str, line: str): - from . import StaticWitnessItems + def make_single_adjustment(self, adj_type: str, line: str) -> None: + from .data import static_items as static_witness_items """Makes a single logic adjustment based on additional logic file""" if adj_type == "Items": line_split = line.split(" - ") item_name = line_split[0] - if item_name not in StaticWitnessItems.item_data: - raise RuntimeError("Item \"" + item_name + "\" does not exist.") + if item_name not in static_witness_items.ITEM_DATA: + raise RuntimeError(f'Item "{item_name}" does not exist.') self.THEORETICAL_ITEMS.add(item_name) - if isinstance(StaticWitnessLogic.all_items[item_name], ProgressiveItemDefinition): + if isinstance(static_witness_logic.ALL_ITEMS[item_name], ProgressiveItemDefinition): self.THEORETICAL_ITEMS_NO_MULTI.update(cast(ProgressiveItemDefinition, - StaticWitnessLogic.all_items[item_name]).child_item_names) + static_witness_logic.ALL_ITEMS[item_name]).child_item_names) else: self.THEORETICAL_ITEMS_NO_MULTI.add(item_name) - if StaticWitnessLogic.all_items[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: - panel_hexes = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]).panel_id_hexes - for panel_hex in panel_hexes: - self.DOOR_ITEMS_BY_ID.setdefault(panel_hex, []).append(item_name) + if static_witness_logic.ALL_ITEMS[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: + entity_hexes = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).panel_id_hexes + for entity_hex in entity_hexes: + self.DOOR_ITEMS_BY_ID.setdefault(entity_hex, []).append(item_name) return @@ -154,18 +305,18 @@ def make_single_adjustment(self, adj_type: str, line: str): item_name = line self.THEORETICAL_ITEMS.discard(item_name) - if isinstance(StaticWitnessLogic.all_items[item_name], ProgressiveItemDefinition): + if isinstance(static_witness_logic.ALL_ITEMS[item_name], ProgressiveItemDefinition): self.THEORETICAL_ITEMS_NO_MULTI.difference_update( - cast(ProgressiveItemDefinition, StaticWitnessLogic.all_items[item_name]).child_item_names + cast(ProgressiveItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).child_item_names ) else: self.THEORETICAL_ITEMS_NO_MULTI.discard(item_name) - if StaticWitnessLogic.all_items[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: - panel_hexes = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]).panel_id_hexes - for panel_hex in panel_hexes: - if panel_hex in self.DOOR_ITEMS_BY_ID and item_name in self.DOOR_ITEMS_BY_ID[panel_hex]: - self.DOOR_ITEMS_BY_ID[panel_hex].remove(item_name) + if static_witness_logic.ALL_ITEMS[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: + entity_hexes = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).panel_id_hexes + for entity_hex in entity_hexes: + if entity_hex in self.DOOR_ITEMS_BY_ID and item_name in self.DOOR_ITEMS_BY_ID[entity_hex]: + self.DOOR_ITEMS_BY_ID[entity_hex].remove(item_name) if adj_type == "Starting Inventory": self.STARTING_INVENTORY.add(line) @@ -185,13 +336,13 @@ def make_single_adjustment(self, adj_type: str, line: str): line_split = line.split(" - ") requirement = { - "panels": parse_lambda(line_split[1]), + "entities": parse_lambda(line_split[1]), } if len(line_split) > 2: required_items = parse_lambda(line_split[2]) items_actually_in_the_game = [ - item_name for item_name, item_definition in StaticWitnessLogic.all_items.items() + item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items() if item_definition.category is ItemCategory.SYMBOL ] required_items = frozenset( @@ -206,23 +357,23 @@ def make_single_adjustment(self, adj_type: str, line: str): return if adj_type == "Disabled Locations": - panel_hex = line[:7] + entity_hex = line[:7] - self.COMPLETELY_DISABLED_ENTITIES.add(panel_hex) + self.COMPLETELY_DISABLED_ENTITIES.add(entity_hex) return if adj_type == "Irrelevant Locations": - panel_hex = line[:7] + entity_hex = line[:7] - self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES.add(panel_hex) + self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES.add(entity_hex) return if adj_type == "Region Changes": new_region_and_options = define_new_region(line + ":") - self.CONNECTIONS_BY_REGION_NAME[new_region_and_options[0]["name"]] = new_region_and_options[1] + self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[new_region_and_options[0]["name"]] = new_region_and_options[1] return @@ -232,117 +383,124 @@ def make_single_adjustment(self, adj_type: str, line: str): target_region = line_split[1] panel_set_string = line_split[2] - for connection in self.CONNECTIONS_BY_REGION_NAME[source_region]: + for connection in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region]: if connection[0] == target_region: - self.CONNECTIONS_BY_REGION_NAME[source_region].remove(connection) + self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].remove(connection) if panel_set_string == "TrueOneWay": - self.CONNECTIONS_BY_REGION_NAME[source_region].add( + self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add( (target_region, frozenset({frozenset(["TrueOneWay"])})) ) else: - new_lambda = connection[1] | parse_lambda(panel_set_string) - self.CONNECTIONS_BY_REGION_NAME[source_region].add((target_region, new_lambda)) + new_lambda = logical_or_witness_rules([connection[1], parse_lambda(panel_set_string)]) + self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add((target_region, new_lambda)) break - else: # Execute if loop did not break. TIL this is a thing you can do! + else: new_conn = (target_region, parse_lambda(panel_set_string)) - self.CONNECTIONS_BY_REGION_NAME[source_region].add(new_conn) + self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(new_conn) if adj_type == "Added Locations": if "0x" in line: line = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[line]["checkName"] self.ADDED_CHECKS.add(line) - @staticmethod - def handle_postgame(world: "WitnessWorld"): - # In shuffle_postgame, panels that become accessible "after or at the same time as the goal" are disabled. - # This has a lot of complicated considerations, which I'll try my best to explain. + def handle_postgame(self, world: "WitnessWorld") -> List[List[str]]: + """ + In shuffle_postgame, panels that become accessible "after or at the same time as the goal" are disabled. + This mostly involves the disabling of key panels (e.g. long box when the goal is short box). + These will then hava a cascading effect on other entities that are locked "behind" them. + """ + postgame_adjustments = [] # Make some quick references to some options - doors = world.options.shuffle_doors >= 2 # "Panels" mode has no overarching region accessibility implications. + remote_doors = world.options.shuffle_doors >= 2 # "Panels" mode has no region accessibility implications. early_caves = world.options.early_caves victory = world.options.victory_condition mnt_lasers = world.options.mountain_lasers chal_lasers = world.options.challenge_lasers - # Goal is "short box" but short box requires more lasers than long box - reverse_shortbox_goal = victory == "mountain_box_short" and mnt_lasers > chal_lasers - # Goal is "short box", and long box requires at least as many lasers as short box (as god intended) proper_shortbox_goal = victory == "mountain_box_short" and chal_lasers >= mnt_lasers # Goal is "long box", but short box requires at least as many lasers than long box. reverse_longbox_goal = victory == "mountain_box_long" and mnt_lasers >= chal_lasers - # If goal is shortbox or "reverse longbox", you will never enter the mountain from the top before winning. - mountain_enterable_from_top = not (victory == "mountain_box_short" or reverse_longbox_goal) + # ||| Section 1: Proper postgame cases ||| + # When something only comes into logic after the goal, e.g. "longbox is postgame if the goal is shortbox". - # Caves & Challenge should never have anything if doors are vanilla - definitionally "post-game" - # This is technically imprecise, but it matches player expectations better. - if not (early_caves or doors): - postgame_adjustments.append(get_caves_exclusion_list()) - postgame_adjustments.append(get_beyond_challenge_exclusion_list()) + # Disable anything directly locked by the victory panel + self.DISABLE_EVERYTHING_BEHIND.add(self.VICTORY_LOCATION) - # If Challenge is the goal, some panels on the way need to be left on, as well as Challenge Vault box itself - if not victory == "challenge": - postgame_adjustments.append(get_path_to_challenge_exclusion_list()) - postgame_adjustments.append(get_challenge_vault_box_exclusion_list()) - - # Challenge can only have something if the goal is not challenge or longbox itself. - # In case of shortbox, it'd have to be a "reverse shortbox" situation where shortbox requires *more* lasers. - # In that case, it'd also have to be a doors mode, but that's already covered by the previous block. - if not (victory == "elevator" or reverse_shortbox_goal): - postgame_adjustments.append(get_beyond_challenge_exclusion_list()) - if not victory == "challenge": - postgame_adjustments.append(get_challenge_vault_box_exclusion_list()) - - # Mountain can't be reached if the goal is shortbox (or "reverse long box") - if not mountain_enterable_from_top: - postgame_adjustments.append(get_mountain_upper_exclusion_list()) - - # Same goes for lower mountain, but that one *can* be reached in remote doors modes. - if not doors: - postgame_adjustments.append(get_mountain_lower_exclusion_list()) - - # The Mountain Bottom Floor Discard is a bit complicated, so we handle it separately. ("it" == the Discard) - # In Elevator Goal, it is definitionally in the post-game, unless remote doors is played. - # In Challenge Goal, it is before the Challenge, so it is not post-game. - # In Short Box Goal, you can win before turning it on, UNLESS Short Box requires MORE lasers than long box. - # In Long Box Goal, it is always in the post-game because solving long box is what turns it on. - if not ((victory == "elevator" and doors) or victory == "challenge" or (reverse_shortbox_goal and doors)): - # We now know Bottom Floor Discard is in the post-game. - # This has different consequences depending on whether remote doors is being played. - # If doors are vanilla, Bottom Floor Discard locks a door to an area, which has to be disabled as well. - if doors: - postgame_adjustments.append(get_bottom_floor_discard_exclusion_list()) - else: - postgame_adjustments.append(get_bottom_floor_discard_nondoors_exclusion_list()) - - # In Challenge goal + early_caves + vanilla doors, you could find something important on Bottom Floor Discard, - # including the Caves Shortcuts themselves if playing "early_caves: start_inventory". - # This is another thing that was deemed "unfun" more than fitting the actual definition of post-game. - if victory == "challenge" and early_caves and not doors: - postgame_adjustments.append(get_bottom_floor_discard_nondoors_exclusion_list()) + # If we have a long box goal, Challenge is behind the amount of lasers required to just win. + # This is technically slightly incorrect as the Challenge Vault Box could contain a *symbol* that is required + # to open Mountain Entry (Stars 2). However, since there is a very easy sphere 1 snipe, this is not considered. + if victory == "mountain_box_long": + postgame_adjustments.append(["Disabled Locations:", "0x0A332 (Challenge Timer Start)"]) - # If we have a proper short box goal, long box will never be activated first. + # If we have a proper short box goal, anything based on challenge lasers will never have something required. if proper_shortbox_goal: postgame_adjustments.append(["Disabled Locations:", "0xFFF00 (Mountain Box Long)"]) + postgame_adjustments.append(["Disabled Locations:", "0x0A332 (Challenge Timer Start)"]) + + # In a case where long box can be activated before short box, short box is postgame. + if reverse_longbox_goal: + postgame_adjustments.append(["Disabled Locations:", "0x09F7F (Mountain Box Short)"]) + + # ||| Section 2: "Fun" considerations ||| + # These are cases in which it was deemed "unfun" to have an "oops, all lasers" situation, especially when + # it's for a single possible item. + + mbfd_extra_exclusions = ( + # Progressive Dots 2 behind 11 lasers in an Elevator seed with vanilla doors = :( + victory == "elevator" and not remote_doors + + # Caves Shortcuts / Challenge Entry (Panel) on MBFD in a Challenge seed with vanilla doors = :( + or victory == "challenge" and early_caves and not remote_doors + ) + + if mbfd_extra_exclusions: + postgame_adjustments.append(["Disabled Locations:", "0xFFF00 (Mountain Box Long)"]) + + # Another big postgame case that is missed is "Desert Laser Redirect (Panel)". + # An 11 lasers longbox seed could technically have this item on Challenge Vault Box. + # This case is not considered and we will act like Desert Laser Redirect (Panel) is always accessible. + # (Which means we do no additional work, this comment just exists to document that case) + + # ||| Section 3: "Post-or-equal-game" cases ||| + # These are cases in which something comes into logic *at the same time* as your goal and thus also can't + # possibly have a required item. These can be a bit awkward. + + # When your victory is Challenge, but you have to get to it the vanilla way, there are no required items + # that can show up in the Caves that aren't also needed on the descent through Mountain. + # So, we should disable all entities in the Caves and Tunnels *except* for those that are required to enter. + if not (early_caves or remote_doors) and victory == "challenge": + postgame_adjustments.append(get_caves_except_path_to_challenge_exclusion_list()) return postgame_adjustments - def make_options_adjustments(self, world: "WitnessWorld"): + def make_options_adjustments(self, world: "WitnessWorld") -> None: """Makes logic adjustments based on options""" adjustment_linesets_in_order = [] # Make condensed references to some options - doors = world.options.shuffle_doors >= 2 # "Panels" mode has no overarching region accessibility implications. + remote_doors = world.options.shuffle_doors >= 2 # "Panels" mode has no overarching region access implications. lasers = world.options.shuffle_lasers victory = world.options.victory_condition mnt_lasers = world.options.mountain_lasers chal_lasers = world.options.challenge_lasers + # Victory Condition + if victory == "elevator": + self.VICTORY_LOCATION = "0x3D9A9" + elif victory == "challenge": + self.VICTORY_LOCATION = "0x0356B" + elif victory == "mountain_box_short": + self.VICTORY_LOCATION = "0x09F7F" + elif victory == "mountain_box_long": + self.VICTORY_LOCATION = "0xFFF00" + # Exclude panels from the post-game if shuffle_postgame is false. if not world.options.shuffle_postgame: adjustment_linesets_in_order += self.handle_postgame(world) @@ -351,27 +509,16 @@ def make_options_adjustments(self, world: "WitnessWorld"): if not world.options.shuffle_discarded_panels: # In disable_non_randomized, the discards are needed for alternate activation triggers, UNLESS both # (remote) doors and lasers are shuffled. - if not world.options.disable_non_randomized_puzzles or (doors and lasers): + if not world.options.disable_non_randomized_puzzles or (remote_doors and lasers): adjustment_linesets_in_order.append(get_discard_exclusion_list()) - if doors: - adjustment_linesets_in_order.append(get_bottom_floor_discard_exclusion_list()) + if remote_doors: + adjustment_linesets_in_order.append(["Disabled Locations:", "0x17FA2"]) if not world.options.shuffle_vault_boxes: adjustment_linesets_in_order.append(get_vault_exclusion_list()) if not victory == "challenge": - adjustment_linesets_in_order.append(get_challenge_vault_box_exclusion_list()) - - # Victory Condition - - if victory == "elevator": - self.VICTORY_LOCATION = "0x3D9A9" - elif victory == "challenge": - self.VICTORY_LOCATION = "0x0356B" - elif victory == "mountain_box_short": - self.VICTORY_LOCATION = "0x09F7F" - elif victory == "mountain_box_long": - self.VICTORY_LOCATION = "0xFFF00" + adjustment_linesets_in_order.append(["Disabled Locations:", "0x0A332"]) # Long box can usually only be solved by opening Mountain Entry. However, if it requires 7 lasers or less # (challenge_lasers <= 7), you can now solve it without opening Mountain Entry first. @@ -417,7 +564,7 @@ def make_options_adjustments(self, world: "WitnessWorld"): if world.options.early_caves == "starting_inventory": adjustment_linesets_in_order.append(get_early_caves_start_list()) - if world.options.early_caves == "add_to_pool" and not doors: + if world.options.early_caves == "add_to_pool" and not remote_doors: adjustment_linesets_in_order.append(get_early_caves_list()) if world.options.elevators_come_to_you: @@ -429,6 +576,9 @@ def make_options_adjustments(self, world: "WitnessWorld"): if lasers: adjustment_linesets_in_order.append(get_laser_shuffle()) + if world.options.shuffle_EPs and world.options.obelisk_keys: + adjustment_linesets_in_order.append(get_obelisk_keys()) + if world.options.shuffle_EPs == "obelisk_sides": ep_gen = ((ep_hex, ep_obj) for (ep_hex, ep_obj) in self.REFERENCE_LOGIC.ENTITIES_BY_HEX.items() if ep_obj["entityType"] == "EP") @@ -442,7 +592,7 @@ def make_options_adjustments(self, world: "WitnessWorld"): adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:]) if not world.options.shuffle_EPs: - adjustment_linesets_in_order.append(["Irrelevant Locations:"] + get_ep_all_individual()[1:]) + adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_all_individual()[1:]) for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS: if yaml_disabled_location not in self.REFERENCE_LOGIC.ENTITIES_BY_NAME: @@ -467,28 +617,200 @@ def make_options_adjustments(self, world: "WitnessWorld"): current_adjustment_type = line[:-1] continue + if current_adjustment_type is None: + raise ValueError(f"Adjustment lineset {adjustment_lineset} is malformed") + self.make_single_adjustment(current_adjustment_type, line) for entity_id in self.COMPLETELY_DISABLED_ENTITIES: if entity_id in self.DOOR_ITEMS_BY_ID: del self.DOOR_ITEMS_BY_ID[entity_id] - def make_dependency_reduced_checklist(self): + def discover_reachable_regions(self) -> Set[str]: + """ + Some options disable panels or remove specific items. + This can make entire regions completely unreachable, because all their incoming connections are invalid. + This function starts from the Entry region and performs a graph search to discover all reachable regions. + """ + reachable_regions = {"Entry"} + new_regions_found = True + + # This for loop "floods" the region graph until no more new regions are discovered. + # Note that connections that rely on disabled entities are considered invalid. + # This fact may lead to unreachable regions being discovered. + while new_regions_found: + new_regions_found = False + regions_to_check = reachable_regions.copy() + + # Find new regions through connections from currently reachable regions + while regions_to_check: + next_region = regions_to_check.pop() + + for region_exit in self.CONNECTIONS_BY_REGION_NAME[next_region]: + target = region_exit[0] + + if target in reachable_regions: + continue + + # There may be multiple conncetions between two regions. We should check all of them to see if + # any of them are valid. + for option in region_exit[1]: + # If a connection requires having access to a not-yet-reached region, do not consider it. + # Otherwise, this connection is valid, and the target region is reachable -> break for loop + if not any(req in self.CONNECTIONS_BY_REGION_NAME and req not in reachable_regions + for req in option): + break + # If none of the connections were valid, this region is not reachable this way, for now. + else: + continue + + new_regions_found = True + regions_to_check.add(target) + reachable_regions.add(target) + + return reachable_regions + + def find_unsolvable_entities(self, world: "WitnessWorld") -> None: """ - Turns dependent check set into semi-independent check set + Settings like "shuffle_postgame: False" may disable certain panels. + This may make panels or regions logically locked by those panels unreachable. + We will determine these automatically and disable them as well. """ + all_regions = set(self.CONNECTIONS_BY_REGION_NAME_THEORETICAL) + + while True: + # Re-make the dependency reduced entity requirements dict, which depends on currently + self.make_dependency_reduced_checklist() + + # Check if any regions have become unreachable. + reachable_regions = self.discover_reachable_regions() + new_unreachable_regions = all_regions - reachable_regions - self.UNREACHABLE_REGIONS + if new_unreachable_regions: + self.UNREACHABLE_REGIONS.update(new_unreachable_regions) + + # Then, discover unreachable entities. + newly_discovered_disabled_entities = set() + + # First, entities in unreachable regions are obviously themselves unreachable. + for region in new_unreachable_regions: + for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region]["physical_entities"]: + # Never disable the Victory Location. + if entity == self.VICTORY_LOCATION: + continue + + # Never disable a laser (They should still function even if you can't walk up to them). + if static_witness_logic.ENTITIES_BY_HEX[entity]["entityType"] == "Laser": + continue + + newly_discovered_disabled_entities.add(entity) + + # Secondly, any entities that depend on disabled entities are unreachable as well. + for entity, req in self.REQUIREMENTS_BY_HEX.items(): + # If the requirement is empty (unsolvable) and it isn't disabled already, add it to "newly disabled" + if not req and not self.is_disabled(entity): + # Never disable the Victory Location. + if entity == self.VICTORY_LOCATION: + continue + + # If we are disabling a laser, something has gone wrong. + if static_witness_logic.ENTITIES_BY_HEX[entity]["entityType"] == "Laser": + laser_name = static_witness_logic.ENTITIES_BY_HEX[entity]["checkName"] + player_name = world.multiworld.get_player_name(world.player) + raise RuntimeError(f"Somehow, {laser_name} was disabled for player {player_name}." + f" This is not allowed to happen, please report to Violet.") + + newly_discovered_disabled_entities.add(entity) + + # Disable the newly determined unreachable entities. + self.COMPLETELY_DISABLED_ENTITIES.update(newly_discovered_disabled_entities) + + # If we didn't find any new unreachable regions or entities this cycle, we are done. + # If we did, we need to do another cycle to see if even more regions or entities became unreachable. + if not new_unreachable_regions and not newly_discovered_disabled_entities: + return + + def reduce_connection_requirement(self, connection: Tuple[str, WitnessRule]) -> WitnessRule: + all_possibilities = [] + + # Check each traversal option individually + for option in connection[1]: + individual_entity_requirements: List[WitnessRule] = [] + for entity in option: + # If a connection requires solving a disabled entity, it is not valid. + if not self.solvability_guaranteed(entity) or entity in self.DISABLE_EVERYTHING_BEHIND: + individual_entity_requirements.append(frozenset()) + # If a connection requires acquiring an event, add that event to its requirements. + elif (entity in self.ALWAYS_EVENT_NAMES_BY_HEX + or entity not in self.REFERENCE_LOGIC.ENTITIES_BY_HEX): + individual_entity_requirements.append(frozenset({frozenset({entity})})) + # If a connection requires entities, use their newly calculated independent requirements. + else: + entity_req = self.get_entity_requirement(entity) + + if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]: + region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]["name"] + entity_req = logical_and_witness_rules([entity_req, frozenset({frozenset({region_name})})]) + + individual_entity_requirements.append(entity_req) + + # Merge all possible requirements into one DNF condition. + all_possibilities.append(logical_and_witness_rules(individual_entity_requirements)) + + return logical_or_witness_rules(all_possibilities) + + def make_dependency_reduced_checklist(self) -> None: + """ + Every entity has a requirement. This requirement may involve other entities. + Example: Solving a panel powers a cable, and that cable turns on the next panel. + These dependencies are specified in the logic files (e.g. "WitnessLogic.txt") and may be modified by options. + + Recursively having to check the requirements of every dependent entity would be very slow, so we go through this + recursion once and make a single, independent requirement for each entity. + + This requirement may include symbol items, door items, regions, or events. + A requirement is saved as a two-dimensional set that represents a disjuntive normal form. + """ + + # Requirements are cached per entity. However, we might redo the whole reduction process multiple times. + # So, we first clear this cache. + self.REQUIREMENTS_BY_HEX = {} + + # We also clear any data structures that we might have filled in a previous dependency reduction + self.REQUIREMENTS_BY_HEX = {} + self.USED_EVENT_NAMES_BY_HEX = {} + self.CONNECTIONS_BY_REGION_NAME = {} + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI = set() + + # Make independent requirements for entities for entity_hex in self.DEPENDENT_REQUIREMENTS_BY_HEX.keys(): - indep_requirement = self.reduce_req_within_region(entity_hex) + indep_requirement = self.get_entity_requirement(entity_hex) self.REQUIREMENTS_BY_HEX[entity_hex] = indep_requirement + # Make independent region connection requirements based on the entities they require + for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items(): + new_connections = set() + + for connection in connections: + overall_requirement = self.reduce_connection_requirement(connection) + + # If there is a way to use this connection, add it. + if overall_requirement: + new_connections.add((connection[0], overall_requirement)) + + self.CONNECTIONS_BY_REGION_NAME[region] = new_connections + + def finalize_items(self) -> None: + """ + Finalise which items are used in the world, and handle their progressive versions. + """ for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI: if item not in self.THEORETICAL_ITEMS: - progressive_item_name = StaticWitnessLogic.get_parent_progressive_item(item) + progressive_item_name = static_witness_logic.get_parent_progressive_item(item) self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(progressive_item_name) child_items = cast(ProgressiveItemDefinition, - StaticWitnessLogic.all_items[progressive_item_name]).child_item_names + static_witness_logic.ALL_ITEMS[progressive_item_name]).child_item_names multi_list = [child_item for child_item in child_items if child_item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI] self.MULTI_AMOUNTS[item] = multi_list.index(item) + 1 @@ -496,41 +818,20 @@ def make_dependency_reduced_checklist(self): else: self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(item) - for region, connections in self.CONNECTIONS_BY_REGION_NAME.items(): - new_connections = [] - - for connection in connections: - overall_requirement = frozenset() - - for option in connection[1]: - individual_entity_requirements = [] - for entity in option: - if (entity in self.ALWAYS_EVENT_NAMES_BY_HEX - or entity not in self.REFERENCE_LOGIC.ENTITIES_BY_HEX): - individual_entity_requirements.append(frozenset({frozenset({entity})})) - else: - entity_req = self.reduce_req_within_region(entity) - - if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]: - region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]["name"] - entity_req = dnf_and([entity_req, frozenset({frozenset({region_name})})]) - - individual_entity_requirements.append(entity_req) - - overall_requirement |= dnf_and(individual_entity_requirements) - - new_connections.append((connection[0], overall_requirement)) - - self.CONNECTIONS_BY_REGION_NAME[region] = new_connections - - def solvability_guaranteed(self, entity_hex: str): + def solvability_guaranteed(self, entity_hex: str) -> bool: return not ( entity_hex in self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY or entity_hex in self.COMPLETELY_DISABLED_ENTITIES or entity_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES ) - def determine_unrequired_entities(self, world: "WitnessWorld"): + def is_disabled(self, entity_hex: str) -> bool: + return ( + entity_hex in self.COMPLETELY_DISABLED_ENTITIES + or entity_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES + ) + + def determine_unrequired_entities(self, world: "WitnessWorld") -> None: """Figure out which major items are actually useless in this world's settings""" # Gather quick references to relevant options @@ -579,7 +880,6 @@ def determine_unrequired_entities(self, world: "WitnessWorld"): "0x01BEA": difficulty == "none" and eps_shuffled, # Keep PP2 "0x0A0C9": eps_shuffled or discards_shuffled or disable_non_randomized, # Cargo Box Entry Door "0x09EEB": discards_shuffled or mountain_upper_included, # Mountain Floor 2 Elevator Control Panel - "0x09EDD": mountain_upper_included, # Mountain Floor 2 Exit Door "0x17CAB": symbols_shuffled or not disable_non_randomized or "0x17CAB" not in self.DOOR_ITEMS_BY_ID, # Jungle Popup Wall Panel } @@ -589,20 +889,23 @@ def determine_unrequired_entities(self, world: "WitnessWorld"): item_name for item_name, is_required in is_item_required_dict.items() if not is_required } - def make_event_item_pair(self, panel: str): + def make_event_item_pair(self, entity_hex: str) -> Tuple[str, str]: """ Makes a pair of an event panel and its event item """ - action = " Opened" if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["entityType"] == "Door" else " Solved" + action = " Opened" if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex]["entityType"] == "Door" else " Solved" + + name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex]["checkName"] + action + if entity_hex not in self.USED_EVENT_NAMES_BY_HEX: + warning(f'Entity "{name}" does not have an associated event name.') + self.USED_EVENT_NAMES_BY_HEX[entity_hex] = name + " Event" + return (name, self.USED_EVENT_NAMES_BY_HEX[entity_hex]) - name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["checkName"] + action - if panel not in self.USED_EVENT_NAMES_BY_HEX: - warning("Panel \"" + name + "\" does not have an associated event name.") - self.USED_EVENT_NAMES_BY_HEX[panel] = name + " Event" - pair = (name, self.USED_EVENT_NAMES_BY_HEX[panel]) - return pair + def make_event_panel_lists(self) -> None: + """ + Makes event-item pairs for entities with associated events, unless these entities are disabled. + """ - def make_event_panel_lists(self): self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory" self.USED_EVENT_NAMES_BY_HEX.update(self.ALWAYS_EVENT_NAMES_BY_HEX) @@ -615,70 +918,3 @@ def make_event_panel_lists(self): for panel in self.USED_EVENT_NAMES_BY_HEX: pair = self.make_event_item_pair(panel) self.EVENT_ITEM_PAIRS[pair[0]] = pair[1] - - def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_inv: Dict[str, int]): - self.YAML_DISABLED_LOCATIONS = disabled_locations - self.YAML_ADDED_ITEMS = start_inv - - self.EVENT_PANELS_FROM_PANELS = set() - self.EVENT_PANELS_FROM_REGIONS = set() - - self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES = set() - - self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY = set() - - self.THEORETICAL_ITEMS = set() - self.THEORETICAL_ITEMS_NO_MULTI = set() - self.MULTI_AMOUNTS = defaultdict(lambda: 1) - self.MULTI_LISTS = dict() - self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI = set() - self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = set() - self.DOOR_ITEMS_BY_ID: Dict[str, List[str]] = {} - self.STARTING_INVENTORY = set() - - self.DIFFICULTY = world.options.puzzle_randomization - - if self.DIFFICULTY == "sigma_normal": - self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_normal - elif self.DIFFICULTY == "sigma_expert": - self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_expert - elif self.DIFFICULTY == "none": - self.REFERENCE_LOGIC = StaticWitnessLogic.vanilla - - self.CONNECTIONS_BY_REGION_NAME = copy.copy(self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME) - self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) - self.REQUIREMENTS_BY_HEX = dict() - - # Determining which panels need to be events is a difficult process. - # At the end, we will have EVENT_ITEM_PAIRS for all the necessary ones. - self.EVENT_ITEM_PAIRS = dict() - self.COMPLETELY_DISABLED_ENTITIES = set() - self.PRECOMPLETED_LOCATIONS = set() - self.EXCLUDED_LOCATIONS = set() - self.ADDED_CHECKS = set() - self.VICTORY_LOCATION = "0x0356B" - - self.ALWAYS_EVENT_NAMES_BY_HEX = { - "0x00509": "+1 Laser (Symmetry Laser)", - "0x012FB": "+1 Laser (Desert Laser)", - "0x09F98": "Desert Laser Redirection", - "0x01539": "+1 Laser (Quarry Laser)", - "0x181B3": "+1 Laser (Shadows Laser)", - "0x014BB": "+1 Laser (Keep Laser)", - "0x17C65": "+1 Laser (Monastery Laser)", - "0x032F9": "+1 Laser (Town Laser)", - "0x00274": "+1 Laser (Jungle Laser)", - "0x0C2B2": "+1 Laser (Bunker Laser)", - "0x00BF6": "+1 Laser (Swamp Laser)", - "0x028A4": "+1 Laser (Treehouse Laser)", - "0x17C34": "Mountain Entry", - "0xFFF00": "Bottom Floor Discard Turns On", - } - - self.USED_EVENT_NAMES_BY_HEX = {} - self.CONDITIONAL_EVENTS = {} - - self.make_options_adjustments(world) - self.determine_unrequired_entities(world) - self.make_dependency_reduced_checklist() - self.make_event_panel_lists() diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 0f37fd50a393..2a53484a4c77 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -14,6 +14,7 @@ "door_groupings": DoorGroupings.option_off, "shuffle_boat": True, "shuffle_lasers": ShuffleLasers.option_local, + "obelisk_keys": ObeliskKeys.option_false, "disable_non_randomized_puzzles": True, "shuffle_discarded_panels": False, @@ -35,6 +36,7 @@ "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, "death_link": DeathLink.default, + "death_link_amnesty": DeathLinkAmnesty.default, }, # For relative beginners who want to move to the next step. @@ -48,6 +50,7 @@ "door_groupings": DoorGroupings.option_regional, "shuffle_boat": True, "shuffle_lasers": ShuffleLasers.option_off, + "obelisk_keys": ObeliskKeys.option_false, "disable_non_randomized_puzzles": False, "shuffle_discarded_panels": True, @@ -69,6 +72,7 @@ "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, "death_link": DeathLink.default, + "death_link_amnesty": DeathLinkAmnesty.default, }, # Allsanity but without the BS (no expert, no tedious EPs). @@ -82,6 +86,7 @@ "door_groupings": DoorGroupings.option_off, "shuffle_boat": True, "shuffle_lasers": ShuffleLasers.option_anywhere, + "obelisk_keys": ObeliskKeys.option_true, "disable_non_randomized_puzzles": False, "shuffle_discarded_panels": True, @@ -103,5 +108,6 @@ "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, "death_link": DeathLink.default, + "death_link_amnesty": DeathLinkAmnesty.default, }, } diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py index 350017c6943a..2528c8abe22b 100644 --- a/worlds/witness/regions.py +++ b/worlds/witness/regions.py @@ -2,26 +2,44 @@ Defines Region for The Witness, assigns locations to them, and connects them with the proper requirements """ -from typing import FrozenSet, TYPE_CHECKING, Dict, Tuple, List +from collections import defaultdict +from typing import TYPE_CHECKING, Dict, List, Set, Tuple from BaseClasses import Entrance, Region -from Utils import KeyedDefaultDict -from .static_logic import StaticWitnessLogic -from .locations import WitnessPlayerLocations, StaticWitnessLocations + +from worlds.generic.Rules import CollectionRule + +from .data import static_locations as static_witness_locations +from .data import static_logic as static_witness_logic +from .data.static_logic import StaticWitnessLogicObj +from .data.utils import WitnessRule, optimize_witness_rule +from .locations import WitnessPlayerLocations from .player_logic import WitnessPlayerLogic if TYPE_CHECKING: from . import WitnessWorld -class WitnessRegions: +class WitnessPlayerRegions: """Class that defines Witness Regions""" - locat = None - logic = None + def __init__(self, player_locations: WitnessPlayerLocations, world: "WitnessWorld") -> None: + difficulty = world.options.puzzle_randomization + + self.reference_logic: StaticWitnessLogicObj + if difficulty == "sigma_normal": + self.reference_logic = static_witness_logic.sigma_normal + elif difficulty == "sigma_expert": + self.reference_logic = static_witness_logic.sigma_expert + else: + self.reference_logic = static_witness_logic.vanilla + + self.player_locations = player_locations + self.two_way_entrance_register: Dict[Tuple[str, str], List[Entrance]] = defaultdict(lambda: []) + self.created_region_names: Set[str] = set() @staticmethod - def make_lambda(item_requirement: FrozenSet[FrozenSet[str]], world: "WitnessWorld"): + def make_lambda(item_requirement: WitnessRule, world: "WitnessWorld") -> CollectionRule: from .rules import _meets_item_requirements """ @@ -31,8 +49,8 @@ def make_lambda(item_requirement: FrozenSet[FrozenSet[str]], world: "WitnessWorl return _meets_item_requirements(item_requirement, world) - def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, req: FrozenSet[FrozenSet[str]], - regions_by_name: Dict[str, Region], backwards: bool = False): + def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, req: WitnessRule, + regions_by_name: Dict[str, Region]) -> None: """ connect two regions and set the corresponding requirement """ @@ -40,10 +58,6 @@ def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, r # Remove any possibilities where being in the target region would be required anyway. real_requirement = frozenset({option for option in req if target not in option}) - # There are some connections that should only be done one way. If this is a backwards connection, check for that - if backwards: - real_requirement = frozenset({option for option in real_requirement if "TrueOneWay" not in option}) - # Dissolve any "True" or "TrueOneWay" real_requirement = frozenset({option - {"True", "TrueOneWay"} for option in real_requirement}) @@ -53,12 +67,12 @@ def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, r # We don't need to check for the accessibility of the source region. final_requirement = frozenset({option - frozenset({source}) for option in real_requirement}) + final_requirement = optimize_witness_rule(final_requirement) source_region = regions_by_name[source] target_region = regions_by_name[target] - backwards = " Backwards" if backwards else "" - connection_name = source + " to " + target + backwards + connection_name = source + " to " + target connection = Entrance( world.player, @@ -71,7 +85,8 @@ def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, r source_region.exits.append(connection) connection.connect(target_region) - self.created_entrances[source, target].append(connection) + self.two_way_entrance_register[source, target].append(connection) + self.two_way_entrance_register[target, source].append(connection) # Register any necessary indirect connections mentioned_regions = { @@ -82,67 +97,41 @@ def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, r for dependent_region in mentioned_regions: world.multiworld.register_indirect_condition(regions_by_name[dependent_region], connection) - def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic): + def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic) -> None: """ Creates all the regions for The Witness """ from . import create_region - all_locations = set() - regions_by_name = dict() + all_locations: Set[str] = set() + regions_by_name: Dict[str, Region] = {} + + regions_to_create = { + k: v for k, v in self.reference_logic.ALL_REGIONS_BY_NAME.items() + if k not in player_logic.UNREACHABLE_REGIONS + } - for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items(): + for region_name, region in regions_to_create.items(): locations_for_this_region = [ - self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] for panel in region["panels"] - if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] in self.locat.CHECK_LOCATION_TABLE + self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] for panel in region["entities"] + if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] + in self.player_locations.CHECK_LOCATION_TABLE ] locations_for_this_region += [ - StaticWitnessLocations.get_event_name(panel) for panel in region["panels"] - if StaticWitnessLocations.get_event_name(panel) in self.locat.EVENT_LOCATION_TABLE + static_witness_locations.get_event_name(panel) for panel in region["entities"] + if static_witness_locations.get_event_name(panel) in self.player_locations.EVENT_LOCATION_TABLE ] all_locations = all_locations | set(locations_for_this_region) - new_region = create_region(world, region_name, self.locat, locations_for_this_region) + new_region = create_region(world, region_name, self.player_locations, locations_for_this_region) regions_by_name[region_name] = new_region - for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items(): - for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]: - self.connect_if_possible(world, region_name, connection[0], connection[1], regions_by_name) - self.connect_if_possible(world, connection[0], region_name, connection[1], regions_by_name, True) - - # find regions that are completely disconnected from the start node and remove them - regions_to_check = {"Menu"} - reachable_regions = {"Menu"} + self.created_region_names = set(regions_by_name) - while regions_to_check: - next_region = regions_to_check.pop() - region_obj = regions_by_name[next_region] + world.multiworld.regions += regions_by_name.values() - for exit in region_obj.exits: - target = exit.connected_region - - if target.name in reachable_regions: - continue - - regions_to_check.add(target.name) - reachable_regions.add(target.name) - - self.created_regions = {k: v for k, v in regions_by_name.items() if k in reachable_regions} - - world.multiworld.regions += self.created_regions.values() - - def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"): - difficulty = world.options.puzzle_randomization - - if difficulty == "sigma_normal": - self.reference_logic = StaticWitnessLogic.sigma_normal - elif difficulty == "sigma_expert": - self.reference_logic = StaticWitnessLogic.sigma_expert - elif difficulty == "none": - self.reference_logic = StaticWitnessLogic.vanilla - - self.locat = locat - self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = KeyedDefaultDict(lambda _: []) - self.created_regions: Dict[str, Region] = dict() + for region_name, region in regions_to_create.items(): + for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]: + self.connect_if_possible(world, region_name, connection[0], connection[1], regions_by_name) diff --git a/worlds/witness/ruff.toml b/worlds/witness/ruff.toml new file mode 100644 index 000000000000..a35711cce66d --- /dev/null +++ b/worlds/witness/ruff.toml @@ -0,0 +1,11 @@ +line-length = 120 + +[lint] +select = ["C", "E", "F", "R", "W", "I", "N", "Q", "UP", "RUF", "ISC", "T20"] +ignore = ["C9", "RUF012", "RUF100"] + +[lint.per-file-ignores] +# The way options definitions work right now, I am forced to break line length requirements. +"options.py" = ["E501"] +# The import list would just be so big if I imported every option individually in presets.py +"presets.py" = ["F403", "F405"] diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py index 8636829a4ef1..12a9a1ed4b59 100644 --- a/worlds/witness/rules.py +++ b/worlds/witness/rules.py @@ -2,14 +2,16 @@ Defines the rules by which locations can be accessed, depending on the items received """ - -from typing import TYPE_CHECKING, Callable, FrozenSet +from typing import TYPE_CHECKING from BaseClasses import CollectionState -from .player_logic import WitnessPlayerLogic + +from worlds.generic.Rules import CollectionRule, set_rule + +from .data import static_logic as static_witness_logic +from .data.utils import WitnessRule from .locations import WitnessPlayerLocations -from . import StaticWitnessLogic, WitnessRegions -from worlds.generic.Rules import set_rule +from .player_logic import WitnessPlayerLogic if TYPE_CHECKING: from . import WitnessWorld @@ -29,18 +31,17 @@ ] -def _has_laser(laser_hex: str, world: "WitnessWorld", player: int, - redirect_required: bool) -> Callable[[CollectionState], bool]: +def _has_laser(laser_hex: str, world: "WitnessWorld", player: int, redirect_required: bool) -> CollectionRule: if laser_hex == "0x012FB" and redirect_required: return lambda state: ( - _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)(state) + _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.player_locations)(state) and state.has("Desert Laser Redirection", player) ) - else: - return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat) + + return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.player_locations) -def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> Callable[[CollectionState], bool]: +def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> CollectionRule: laser_lambdas = [] for laser_hex in laser_hexes: @@ -52,7 +53,7 @@ def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logic: WitnessPlayerLogic, - locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]: + player_locations: WitnessPlayerLocations) -> CollectionRule: """ Determines whether a panel can be solved """ @@ -60,100 +61,166 @@ def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logi panel_obj = player_logic.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel] entity_name = panel_obj["checkName"] - if entity_name + " Solved" in locat.EVENT_LOCATION_TABLE: + if entity_name + " Solved" in player_locations.EVENT_LOCATION_TABLE: return lambda state: state.has(player_logic.EVENT_ITEM_PAIRS[entity_name + " Solved"], player) - else: - return make_lambda(panel, world) - -def _can_move_either_direction(state: CollectionState, source: str, target: str, regio: WitnessRegions) -> bool: - entrance_forward = regio.created_entrances[source, target] - entrance_backward = regio.created_entrances[target, source] - - return ( - any(entrance.can_reach(state) for entrance in entrance_forward) - or - any(entrance.can_reach(state) for entrance in entrance_backward) - ) + return make_lambda(panel, world) def _can_do_expert_pp2(state: CollectionState, world: "WitnessWorld") -> bool: + """ + For Expert PP2, you need a way to access PP2 from the front, and a separate way from the back. + This condition is quite complicated. We'll attempt to evaluate it as lazily as possible. + """ + player = world.player + player_regions = world.player_regions - hedge_2_access = ( - _can_move_either_direction(state, "Keep 2nd Maze", "Keep", world.regio) + front_access = ( + any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 2nd Pressure Plate", "Keep"]) + and state.can_reach_region("Keep", player) ) - hedge_3_access = ( - _can_move_either_direction(state, "Keep 3rd Maze", "Keep", world.regio) - or _can_move_either_direction(state, "Keep 3rd Maze", "Keep 2nd Maze", world.regio) - and hedge_2_access + # If we don't have front access, we can't do PP2. + if not front_access: + return False + + # Front access works. Now, we need to check for the many ways to access PP2 from the back. + # All of those ways lead through the PP3 exit door from PP4. So we check this first. + + fourth_to_third = any(e.can_reach(state) for e in player_regions.two_way_entrance_register[ + "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate" + ]) + + # If we can't get from PP4 to PP3, we can't do PP2. + if not fourth_to_third: + return False + + # We can go from PP4 to PP3. We now need to find a way to PP4. + # The shadows shortcut is the simplest way. + + shadows_shortcut = ( + any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Pressure Plate", "Shadows"]) ) - hedge_4_access = ( - _can_move_either_direction(state, "Keep 4th Maze", "Keep", world.regio) - or _can_move_either_direction(state, "Keep 4th Maze", "Keep 3rd Maze", world.regio) - and hedge_3_access + if shadows_shortcut: + return True + + # We don't have the Shadows shortcut. This means we need to come in through the PP4 exit door instead. + + tower_to_pp4 = any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Pressure Plate", "Keep Tower"] ) - hedge_access = ( - _can_move_either_direction(state, "Keep 4th Maze", "Keep Tower", world.regio) - and state.can_reach("Keep", "Region", player) - and hedge_4_access + # If we don't have the PP4 exit door, we've run out of options. + if not tower_to_pp4: + return False + + # We have the PP4 exit door. If we can get to Keep Tower from behind, we can do PP2. + # The simplest way would be the Tower Shortcut. + + tower_shortcut = any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep", "Keep Tower"]) + + if tower_shortcut: + return True + + # We don't have the Tower shortcut. At this point, there is one possibility remaining: + # Getting to Keep Tower through the hedge mazes. This can be done in a multitude of ways. + # No matter what, though, we would need Hedge Maze 4 Exit to Keep Tower. + + tower_access_from_hedges = any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Maze", "Keep Tower"] ) - backwards_to_fourth = ( - state.can_reach("Keep", "Region", player) - and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Keep Tower", world.regio) - and ( - _can_move_either_direction(state, "Keep", "Keep Tower", world.regio) - or hedge_access - ) + if not tower_access_from_hedges: + return False + + # We can reach Keep Tower from Hedge Maze 4. If we now have the Hedge 4 Shortcut, we are immediately good. + + hedge_4_shortcut = any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Maze", "Keep"] ) - shadows_shortcut = ( - state.can_reach("Main Island", "Region", player) - and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Shadows", world.regio) + # If we have the hedge 4 shortcut, that works. + if hedge_4_shortcut: + return True + + # We don't have the hedge 4 shortcut. This means we would now need to come through Hedge Maze 3. + + hedge_3_to_4 = any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Maze", "Keep 3rd Maze"] ) - backwards_access = ( - _can_move_either_direction(state, "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate", world.regio) - and (backwards_to_fourth or shadows_shortcut) + if not hedge_3_to_4: + return False + + # We can get to Hedge 4 from Hedge 3. If we have the Hedge 3 Shortcut, we're good. + + hedge_3_shortcut = any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 3rd Maze", "Keep"] ) - front_access = ( - _can_move_either_direction(state, "Keep 2nd Pressure Plate", "Keep", world.regio) - and state.can_reach("Keep", "Region", player) + if hedge_3_shortcut: + return True + + # We don't have Hedge 3 Shortcut. This means we would now need to come through Hedge Maze 2. + + hedge_2_to_3 = any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 3rd Maze", "Keep 2nd Maze"] ) - return front_access and backwards_access + if not hedge_2_to_3: + return False + + # We can get to Hedge 3 from Hedge 2. If we can get from Keep to Hedge 2, we're good. + # This covers both Hedge 1 Exit and Hedge 2 Shortcut, because Hedge 1 is just part of the Keep region. + + return any( + e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 2nd Maze", "Keep"] + ) def _can_do_theater_to_tunnels(state: CollectionState, world: "WitnessWorld") -> bool: + """ + To do Tunnels Theater Flowers EP, you need to quickly move from Theater to Tunnels. + This condition is a little tricky. We'll attempt to evaluate it as lazily as possible. + """ + + # Checking for access to Theater is not necessary, as solvability of Tutorial Video is checked in the other half + # of the Theater Flowers EP condition. + + player_regions = world.player_regions + direct_access = ( - _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio) - and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio) + any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Tunnels", "Windmill Interior"]) + and any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Theater", "Windmill Interior"]) ) - theater_from_town = ( - _can_move_either_direction(state, "Town", "Windmill Interior", world.regio) - and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio) - or _can_move_either_direction(state, "Town", "Theater", world.regio) - ) + if direct_access: + return True - tunnels_from_town = ( - _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio) - and _can_move_either_direction(state, "Town", "Windmill Interior", world.regio) - or _can_move_either_direction(state, "Tunnels", "Town", world.regio) - ) + # We don't have direct access through the shortest path. + # This means we somehow need to exit Theater to the Main Island, and then enter Tunnels from the Main Island. + # Getting to Tunnels through Mountain -> Caves -> Tunnels is way too slow, so we only expect paths through Town. + + # We need a way from Theater to Town. This is actually guaranteed, otherwise we wouldn't be in Theater. + # The only ways to Theater are through Town and Tunnels. We just checked the Tunnels way. + # This might need to be changed when warps are implemented. - return direct_access or theater_from_town and tunnels_from_town + # We also need a way from Town to Tunnels. + + return ( + any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Tunnels", "Windmill Interior"]) + and any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Town", "Windmill Interior"]) + or any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Tunnels", "Town"]) + ) def _has_item(item: str, world: "WitnessWorld", player: int, - player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]: + player_logic: WitnessPlayerLogic, player_locations: WitnessPlayerLocations) -> CollectionRule: if item in player_logic.REFERENCE_LOGIC.ALL_REGIONS_BY_NAME: - return lambda state: state.can_reach(item, "Region", player) + region = world.get_region(item) + return region.can_reach if item == "7 Lasers": laser_req = world.options.mountain_lasers.value return _has_lasers(laser_req, world, False) @@ -166,26 +233,25 @@ def _has_item(item: str, world: "WitnessWorld", player: int, if item == "11 Lasers + Redirect": laser_req = world.options.challenge_lasers.value return _has_lasers(laser_req, world, True) - elif item == "PP2 Weirdness": + if item == "PP2 Weirdness": return lambda state: _can_do_expert_pp2(state, world) - elif item == "Theater to Tunnels": + if item == "Theater to Tunnels": return lambda state: _can_do_theater_to_tunnels(state, world) if item in player_logic.USED_EVENT_NAMES_BY_HEX: - return _can_solve_panel(item, world, player, player_logic, locat) + return _can_solve_panel(item, world, player, player_logic, player_locations) - prog_item = StaticWitnessLogic.get_parent_progressive_item(item) + prog_item = static_witness_logic.get_parent_progressive_item(item) return lambda state: state.has(prog_item, player, player_logic.MULTI_AMOUNTS[item]) -def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]], - world: "WitnessWorld") -> Callable[[CollectionState], bool]: +def _meets_item_requirements(requirements: WitnessRule, world: "WitnessWorld") -> CollectionRule: """ Checks whether item and panel requirements are met for a panel """ lambda_conversion = [ - [_has_item(item, world, world.player, world.player_logic, world.locat) for item in subset] + [_has_item(item, world, world.player, world.player_logic, world.player_locations) for item in subset] for subset in requirements ] @@ -195,7 +261,7 @@ def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]], ) -def make_lambda(entity_hex: str, world: "WitnessWorld") -> Callable[[CollectionState], bool]: +def make_lambda(entity_hex: str, world: "WitnessWorld") -> CollectionRule: """ Lambdas are created in a for loop so values need to be captured """ @@ -204,15 +270,15 @@ def make_lambda(entity_hex: str, world: "WitnessWorld") -> Callable[[CollectionS return _meets_item_requirements(entity_req, world) -def set_rules(world: "WitnessWorld"): +def set_rules(world: "WitnessWorld") -> None: """ Sets all rules for all locations """ - for location in world.locat.CHECK_LOCATION_TABLE: + for location in world.player_locations.CHECK_LOCATION_TABLE: real_location = location - if location in world.locat.EVENT_LOCATION_TABLE: + if location in world.player_locations.EVENT_LOCATION_TABLE: real_location = location[:-7] associated_entity = world.player_logic.REFERENCE_LOGIC.ENTITIES_BY_NAME[real_location] @@ -220,8 +286,8 @@ def set_rules(world: "WitnessWorld"): rule = make_lambda(entity_hex, world) - location = world.multiworld.get_location(location, world.player) + location = world.get_location(location) set_rule(location, rule) - world.multiworld.completion_condition[world.player] = lambda state: state.has('Victory', world.player) + world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player) diff --git a/worlds/witness/settings/Exclusions/Vaults.txt b/worlds/witness/settings/Exclusions/Vaults.txt deleted file mode 100644 index d9e5d28cd694..000000000000 --- a/worlds/witness/settings/Exclusions/Vaults.txt +++ /dev/null @@ -1,31 +0,0 @@ -Disabled Locations: -0x033D4 (Outside Tutorial Vault) -0x03481 (Outside Tutorial Vault Box) -0x033D0 (Outside Tutorial Vault Door) -0x0CC7B (Desert Vault) -0x0339E (Desert Vault Box) -0x03444 (Desert Vault Door) -0x00AFB (Shipwreck Vault) -0x03535 (Shipwreck Vault Box) -0x17BB4 (Shipwreck Vault Door) -0x15ADD (Jungle Vault) -0x03702 (Jungle Vault Box) -0x15287 (Jungle Vault Door) -0x002A6 (Mountainside Vault) -0x03542 (Mountainside Vault Box) -0x00085 (Mountainside Vault Door) -0x2FAF6 (Tunnels Vault Box) -0x00815 (Theater Video Input) -0x03553 (Theater Tutorial Video) -0x03552 (Theater Desert Video) -0x0354E (Theater Jungle Video) -0x03549 (Theater Challenge Video) -0x0354F (Theater Shipwreck Video) -0x03545 (Theater Mountain Video) -0x03505 (Tutorial Gate Close) -0x339B6 (Theater clipse EP) -0x33A29 (Theater Window EP) -0x33A2A (Theater Door EP) -0x33B06 (Theater Church EP) -0x33A20 (Theater Flowers EP) -0x3352F (Tutorial Gate EP) diff --git a/worlds/witness/settings/Postgame/Beyond_Challenge.txt b/worlds/witness/settings/Postgame/Beyond_Challenge.txt deleted file mode 100644 index 5cd20b6a5e40..000000000000 --- a/worlds/witness/settings/Postgame/Beyond_Challenge.txt +++ /dev/null @@ -1,4 +0,0 @@ -Disabled Locations: -0x03549 (Challenge Video) - -0x339B6 (Eclipse EP) diff --git a/worlds/witness/settings/Postgame/Bottom_Floor_Discard.txt b/worlds/witness/settings/Postgame/Bottom_Floor_Discard.txt deleted file mode 100644 index 8f7d6a257a53..000000000000 --- a/worlds/witness/settings/Postgame/Bottom_Floor_Discard.txt +++ /dev/null @@ -1,2 +0,0 @@ -Disabled Locations: -0x17FA2 (Mountain Bottom Floor Discard) diff --git a/worlds/witness/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt b/worlds/witness/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt deleted file mode 100644 index 5ea7c578d8bf..000000000000 --- a/worlds/witness/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt +++ /dev/null @@ -1,6 +0,0 @@ -Disabled Locations: -0x17FA2 (Mountain Bottom Floor Discard) -0x17F33 (Rock Open Door) -0x00FF8 (Caves Entry Panel) -0x334E1 (Rock Control) -0x2D77D (Caves Entry Door) diff --git a/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt b/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt deleted file mode 100644 index 8b431694b3b4..000000000000 --- a/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt +++ /dev/null @@ -1,22 +0,0 @@ -Disabled Locations: -0x0356B (Challenge Vault Box) -0x04D75 (Vault Door) -0x0A332 (Start Timer) -0x0088E (Small Basic) -0x00BAF (Big Basic) -0x00BF3 (Square) -0x00C09 (Maze Map) -0x00CDB (Stars and Dots) -0x0051F (Symmetry) -0x00524 (Stars and Shapers) -0x00CD4 (Big Basic 2) -0x00CB9 (Choice Squares Right) -0x00CA1 (Choice Squares Middle) -0x00C80 (Choice Squares Left) -0x00C68 (Choice Squares 2 Right) -0x00C59 (Choice Squares 2 Middle) -0x00C22 (Choice Squares 2 Left) -0x034F4 (Maze Hidden 1) -0x034EC (Maze Hidden 2) -0x1C31A (Dots Pillar) -0x1C319 (Squares Pillar) diff --git a/worlds/witness/settings/Postgame/Mountain_Lower.txt b/worlds/witness/settings/Postgame/Mountain_Lower.txt deleted file mode 100644 index aecddec5adde..000000000000 --- a/worlds/witness/settings/Postgame/Mountain_Lower.txt +++ /dev/null @@ -1,27 +0,0 @@ -Disabled Locations: -0x17F93 (Elevator Discard) -0x09EEB (Elevator Control Panel) -0x09FC1 (Giant Puzzle Bottom Left) -0x09F8E (Giant Puzzle Bottom Right) -0x09F01 (Giant Puzzle Top Right) -0x09EFF (Giant Puzzle Top Left) -0x09FDA (Giant Puzzle) -0x09F89 (Exit Door) -0x01983 (Pillars Room Entry Left) -0x01987 (Pillars Room Entry Right) -0x0C141 (Pillars Room Entry Door) -0x0383A (Right Pillar 1) -0x09E56 (Right Pillar 2) -0x09E5A (Right Pillar 3) -0x33961 (Right Pillar 4) -0x0383D (Left Pillar 1) -0x0383F (Left Pillar 2) -0x03859 (Left Pillar 3) -0x339BB (Left Pillar 4) -0x3D9A6 (Elevator Door Closer Left) -0x3D9A7 (Elevator Door Close Right) -0x3C113 (Elevator Entry Left) -0x3C114 (Elevator Entry Right) -0x3D9AA (Back Wall Left) -0x3D9A8 (Back Wall Right) -0x3D9A9 (Elevator Start) diff --git a/worlds/witness/settings/Postgame/Mountain_Upper.txt b/worlds/witness/settings/Postgame/Mountain_Upper.txt deleted file mode 100644 index e2b0765f533c..000000000000 --- a/worlds/witness/settings/Postgame/Mountain_Upper.txt +++ /dev/null @@ -1,41 +0,0 @@ -Disabled Locations: -0x17C34 (Mountain Entry Panel) -0x09E39 (Light Bridge Controller) -0x09E7A (Right Row 1) -0x09E71 (Right Row 2) -0x09E72 (Right Row 3) -0x09E69 (Right Row 4) -0x09E7B (Right Row 5) -0x09E73 (Left Row 1) -0x09E75 (Left Row 2) -0x09E78 (Left Row 3) -0x09E79 (Left Row 4) -0x09E6C (Left Row 5) -0x09E6F (Left Row 6) -0x09E6B (Left Row 7) -0x33AF5 (Back Row 1) -0x33AF7 (Back Row 2) -0x09F6E (Back Row 3) -0x09EAD (Trash Pillar 1) -0x09EAF (Trash Pillar 2) -0x09E54 (Mountain Floor 1 Exit Door) -0x09FD3 (Near Row 1) -0x09FD4 (Near Row 2) -0x09FD6 (Near Row 3) -0x09FD7 (Near Row 4) -0x09FD8 (Near Row 5) -0x09FFB (Staircase Near Door) -0x09EDD (Elevator Room Entry Door) -0x09E86 (Light Bridge Controller Near) -0x09FCC (Far Row 1) -0x09FCE (Far Row 2) -0x09FCF (Far Row 3) -0x09FD0 (Far Row 4) -0x09FD1 (Far Row 5) -0x09FD2 (Far Row 6) -0x09E07 (Staircase Far Door) -0x09ED8 (Light Bridge Controller Far) - -0x09D63 (Pink Bridge EP) -0x09D5D (Yellow Bridge EP) -0x09D5E (Blue Bridge EP) diff --git a/worlds/witness/settings/Postgame/Path_To_Challenge.txt b/worlds/witness/settings/Postgame/Path_To_Challenge.txt deleted file mode 100644 index 3f9239cc4832..000000000000 --- a/worlds/witness/settings/Postgame/Path_To_Challenge.txt +++ /dev/null @@ -1,30 +0,0 @@ -Disabled Locations: -0x0356B (Vault Box) -0x04D75 (Vault Door) -0x17F33 (Rock Open Door) -0x00FF8 (Caves Entry Panel) -0x334E1 (Rock Control) -0x2D77D (Caves Entry Door) -0x09DD5 (Lone Pillar) -0x019A5 (Caves Pillar Door) -0x0A16E (Challenge Entry Panel) -0x0A19A (Challenge Entry Door) -0x0A332 (Start Timer) -0x0088E (Small Basic) -0x00BAF (Big Basic) -0x00BF3 (Square) -0x00C09 (Maze Map) -0x00CDB (Stars and Dots) -0x0051F (Symmetry) -0x00524 (Stars and Shapers) -0x00CD4 (Big Basic 2) -0x00CB9 (Choice Squares Right) -0x00CA1 (Choice Squares Middle) -0x00C80 (Choice Squares Left) -0x00C68 (Choice Squares 2 Right) -0x00C59 (Choice Squares 2 Middle) -0x00C22 (Choice Squares 2 Left) -0x034F4 (Maze Hidden 1) -0x034EC (Maze Hidden 2) -0x1C31A (Dots Pillar) -0x1C319 (Squares Pillar) diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py deleted file mode 100644 index 3efab4915e69..000000000000 --- a/worlds/witness/static_logic.py +++ /dev/null @@ -1,300 +0,0 @@ -from dataclasses import dataclass -from enum import Enum -from typing import Dict, List - -from .utils import define_new_region, parse_lambda, lazy, get_items, get_sigma_normal_logic, get_sigma_expert_logic,\ - get_vanilla_logic - - -class ItemCategory(Enum): - SYMBOL = 0 - DOOR = 1 - LASER = 2 - USEFUL = 3 - FILLER = 4 - TRAP = 5 - JOKE = 6 - EVENT = 7 - - -CATEGORY_NAME_MAPPINGS: Dict[str, ItemCategory] = { - "Symbols:": ItemCategory.SYMBOL, - "Doors:": ItemCategory.DOOR, - "Lasers:": ItemCategory.LASER, - "Useful:": ItemCategory.USEFUL, - "Filler:": ItemCategory.FILLER, - "Traps:": ItemCategory.TRAP, - "Jokes:": ItemCategory.JOKE -} - - -@dataclass(frozen=True) -class ItemDefinition: - local_code: int - category: ItemCategory - - -@dataclass(frozen=True) -class ProgressiveItemDefinition(ItemDefinition): - child_item_names: List[str] - - -@dataclass(frozen=True) -class DoorItemDefinition(ItemDefinition): - panel_id_hexes: List[str] - - -@dataclass(frozen=True) -class WeightedItemDefinition(ItemDefinition): - weight: int - - -class StaticWitnessLogicObj: - def read_logic_file(self, lines): - """ - Reads the logic file and does the initial population of data structures - """ - - current_region = dict() - current_area = { - "name": "Misc", - "regions": [], - } - self.ALL_AREAS_BY_NAME["Misc"] = current_area - - for line in lines: - if line == "" or line[0] == "#": - continue - - if line[-1] == ":": - new_region_and_connections = define_new_region(line) - current_region = new_region_and_connections[0] - region_name = current_region["name"] - self.ALL_REGIONS_BY_NAME[region_name] = current_region - self.STATIC_CONNECTIONS_BY_REGION_NAME[region_name] = new_region_and_connections[1] - current_area["regions"].append(region_name) - continue - - if line[0] == "=": - area_name = line[2:-2] - current_area = { - "name": area_name, - "regions": [], - } - self.ALL_AREAS_BY_NAME[area_name] = current_area - continue - - line_split = line.split(" - ") - - location_id = line_split.pop(0) - - entity_name_full = line_split.pop(0) - - entity_hex = entity_name_full[0:7] - entity_name = entity_name_full[9:-1] - - required_panel_lambda = line_split.pop(0) - - full_entity_name = current_region["shortName"] + " " + entity_name - - if location_id == "Door" or location_id == "Laser": - self.ENTITIES_BY_HEX[entity_hex] = { - "checkName": full_entity_name, - "entity_hex": entity_hex, - "region": None, - "id": None, - "entityType": location_id, - "area": current_area, - } - - self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] - - self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = { - "panels": parse_lambda(required_panel_lambda) - } - - # Lasers and Doors exist in a region, but don't have a regional *requirement* - # If a laser is activated, you don't need to physically walk up to it for it to count - # As such, logically, they behave more as if they were part of the "Entry" region - self.ALL_REGIONS_BY_NAME["Entry"]["panels"].append(entity_hex) - continue - - required_item_lambda = line_split.pop(0) - - laser_names = { - "Laser", - "Laser Hedges", - "Laser Pressure Plates", - } - is_vault_or_video = "Vault" in entity_name or "Video" in entity_name - - if "Discard" in entity_name: - location_type = "Discard" - elif is_vault_or_video or entity_name == "Tutorial Gate Close": - location_type = "Vault" - elif entity_name in laser_names: - location_type = "Laser" - elif "Obelisk Side" in entity_name: - location_type = "Obelisk Side" - elif "EP" in entity_name: - location_type = "EP" - else: - location_type = "General" - - required_items = parse_lambda(required_item_lambda) - required_panels = parse_lambda(required_panel_lambda) - - required_items = frozenset(required_items) - - requirement = { - "panels": required_panels, - "items": required_items - } - - if location_type == "Obelisk Side": - eps = set(list(required_panels)[0]) - eps -= {"Theater to Tunnels"} - - eps_ints = {int(h, 16) for h in eps} - - self.OBELISK_SIDE_ID_TO_EP_HEXES[int(entity_hex, 16)] = eps_ints - for ep_hex in eps: - self.EP_TO_OBELISK_SIDE[ep_hex] = entity_hex - - self.ENTITIES_BY_HEX[entity_hex] = { - "checkName": full_entity_name, - "entity_hex": entity_hex, - "region": current_region, - "id": int(location_id), - "entityType": location_type, - "area": current_area, - } - - self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name - - self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] - self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement - - current_region["panels"].append(entity_hex) - - def __init__(self, lines=None): - if lines is None: - lines = get_sigma_normal_logic() - - # All regions with a list of panels in them and the connections to other regions, before logic adjustments - self.ALL_REGIONS_BY_NAME = dict() - self.ALL_AREAS_BY_NAME = dict() - self.STATIC_CONNECTIONS_BY_REGION_NAME = dict() - - self.ENTITIES_BY_HEX = dict() - self.ENTITIES_BY_NAME = dict() - self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict() - - self.OBELISK_SIDE_ID_TO_EP_HEXES = dict() - - self.EP_TO_OBELISK_SIDE = dict() - - self.ENTITY_ID_TO_NAME = dict() - - self.read_logic_file(lines) - - -class StaticWitnessLogic: - # Item data parsed from WitnessItems.txt - all_items: Dict[str, ItemDefinition] = {} - _progressive_lookup: Dict[str, str] = {} - - ALL_REGIONS_BY_NAME = dict() - ALL_AREAS_BY_NAME = dict() - STATIC_CONNECTIONS_BY_REGION_NAME = dict() - - OBELISK_SIDE_ID_TO_EP_HEXES = dict() - - ENTITIES_BY_HEX = dict() - ENTITIES_BY_NAME = dict() - STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict() - - EP_TO_OBELISK_SIDE = dict() - - ENTITY_ID_TO_NAME = dict() - - @staticmethod - def parse_items(): - """ - Parses currently defined items from WitnessItems.txt - """ - - lines: List[str] = get_items() - current_category: ItemCategory = ItemCategory.SYMBOL - - for line in lines: - # Skip empty lines and comments. - if line == "" or line[0] == "#": - continue - - # If this line is a category header, update our cached category. - if line in CATEGORY_NAME_MAPPINGS.keys(): - current_category = CATEGORY_NAME_MAPPINGS[line] - continue - - line_split = line.split(" - ") - - item_code = int(line_split[0]) - item_name = line_split[1] - arguments: List[str] = line_split[2].split(",") if len(line_split) >= 3 else [] - - if current_category in [ItemCategory.DOOR, ItemCategory.LASER]: - # Map doors to IDs. - StaticWitnessLogic.all_items[item_name] = DoorItemDefinition(item_code, current_category, - arguments) - elif current_category == ItemCategory.TRAP or current_category == ItemCategory.FILLER: - # Read filler weights. - weight = int(arguments[0]) if len(arguments) >= 1 else 1 - StaticWitnessLogic.all_items[item_name] = WeightedItemDefinition(item_code, current_category, weight) - elif arguments: - # Progressive items. - StaticWitnessLogic.all_items[item_name] = ProgressiveItemDefinition(item_code, current_category, - arguments) - for child_item in arguments: - StaticWitnessLogic._progressive_lookup[child_item] = item_name - else: - StaticWitnessLogic.all_items[item_name] = ItemDefinition(item_code, current_category) - - @staticmethod - def get_parent_progressive_item(item_name: str): - """ - Returns the name of the item's progressive parent, if there is one, or the item's name if not. - """ - return StaticWitnessLogic._progressive_lookup.get(item_name, item_name) - - @lazy - def sigma_expert(self) -> StaticWitnessLogicObj: - return StaticWitnessLogicObj(get_sigma_expert_logic()) - - @lazy - def sigma_normal(self) -> StaticWitnessLogicObj: - return StaticWitnessLogicObj(get_sigma_normal_logic()) - - @lazy - def vanilla(self) -> StaticWitnessLogicObj: - return StaticWitnessLogicObj(get_vanilla_logic()) - - def __init__(self): - self.parse_items() - - self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME) - self.ALL_AREAS_BY_NAME.update(self.sigma_normal.ALL_AREAS_BY_NAME) - self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME) - - self.ENTITIES_BY_HEX.update(self.sigma_normal.ENTITIES_BY_HEX) - self.ENTITIES_BY_NAME.update(self.sigma_normal.ENTITIES_BY_NAME) - self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX.update(self.sigma_normal.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) - - self.OBELISK_SIDE_ID_TO_EP_HEXES.update(self.sigma_normal.OBELISK_SIDE_ID_TO_EP_HEXES) - - self.EP_TO_OBELISK_SIDE.update(self.sigma_normal.EP_TO_OBELISK_SIDE) - - self.ENTITY_ID_TO_NAME.update(self.sigma_normal.ENTITY_ID_TO_NAME) - - -StaticWitnessLogic() diff --git a/worlds/witness/test/__init__.py b/worlds/witness/test/__init__.py new file mode 100644 index 000000000000..0a24467feab2 --- /dev/null +++ b/worlds/witness/test/__init__.py @@ -0,0 +1,161 @@ +from test.bases import WorldTestBase +from test.general import gen_steps, setup_multiworld +from test.multiworld.test_multiworlds import MultiworldTestBase +from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union, cast + +from BaseClasses import CollectionState, Entrance, Item, Location, Region + +from .. import WitnessWorld + + +class WitnessTestBase(WorldTestBase): + game = "The Witness" + player: ClassVar[int] = 1 + + world: WitnessWorld + + def can_beat_game_with_items(self, items: Iterable[Item]) -> bool: + """ + Check that the items listed are enough to beat the game. + """ + + state = CollectionState(self.multiworld) + for item in items: + state.collect(item) + return state.multiworld.can_beat_game(state) + + def assert_dependency_on_event_item(self, spot: Union[Location, Region, Entrance], item_name: str) -> None: + """ + WorldTestBase.assertAccessDependency, but modified & simplified to work with event items + """ + event_items = [item for item in self.multiworld.get_items() if item.name == item_name] + self.assertTrue(event_items, f"Event item {item_name} does not exist.") + + event_locations = [cast(Location, event_item.location) for event_item in event_items] + + # Checking for an access dependency on an event item requires a bit of extra work, + # as state.remove forces a sweep, which will pick up the event item again right after we tried to remove it. + # So, we temporarily set the access rules of the event locations to be impossible. + original_rules = {event_location.name: event_location.access_rule for event_location in event_locations} + for event_location in event_locations: + event_location.access_rule = lambda _: False + + # We can't use self.assertAccessDependency here, it doesn't work for event items. (As of 2024-06-30) + test_state = self.multiworld.get_all_state(False) + + self.assertFalse(spot.can_reach(test_state), f"{spot.name} is reachable without {item_name}") + + test_state.collect(event_items[0]) + + self.assertTrue(spot.can_reach(test_state), f"{spot.name} is not reachable despite having {item_name}") + + # Restore original access rules. + for event_location in event_locations: + event_location.access_rule = original_rules[event_location.name] + + def assert_location_exists(self, location_name: str, strict_check: bool = True) -> None: + """ + Assert that a location exists in this world. + If strict_check, also make sure that this (non-event) location COULD exist. + """ + + if strict_check: + self.assertIn(location_name, self.world.location_name_to_id, f"Location {location_name} can never exist") + + try: + self.world.get_location(location_name) + except KeyError: + self.fail(f"Location {location_name} does not exist.") + + def assert_location_does_not_exist(self, location_name: str, strict_check: bool = True) -> None: + """ + Assert that a location exists in this world. + If strict_check, be explicit about whether the location could exist in the first place. + """ + + if strict_check: + self.assertIn(location_name, self.world.location_name_to_id, f"Location {location_name} can never exist") + + self.assertRaises( + KeyError, + lambda _: self.world.get_location(location_name), + f"Location {location_name} exists, but is not supposed to.", + ) + + def assert_can_beat_with_minimally(self, required_item_counts: Mapping[str, int]) -> None: + """ + Assert that the specified mapping of items is enough to beat the game, + and that having one less of any item would result in the game being unbeatable. + """ + # Find the actual items + found_items = [item for item in self.multiworld.get_items() if item.name in required_item_counts] + actual_items: Dict[str, List[Item]] = {item_name: [] for item_name in required_item_counts} + for item in found_items: + if len(actual_items[item.name]) < required_item_counts[item.name]: + actual_items[item.name].append(item) + + # Assert that enough items exist in the item pool to satisfy the specified required counts + for item_name, item_objects in actual_items.items(): + self.assertEqual( + len(item_objects), + required_item_counts[item_name], + f"Couldn't find {required_item_counts[item_name]} copies of item {item_name} available in the pool, " + f"only found {len(item_objects)}", + ) + + # assert that multiworld is beatable with the items specified + self.assertTrue( + self.can_beat_game_with_items(item for items in actual_items.values() for item in items), + f"Could not beat game with items: {required_item_counts}", + ) + + # assert that one less copy of any item would result in the multiworld being unbeatable + for item_name, item_objects in actual_items.items(): + with self.subTest(f"Verify cannot beat game with one less copy of {item_name}"): + removed_item = item_objects.pop() + self.assertFalse( + self.can_beat_game_with_items(item for items in actual_items.values() for item in items), + f"Game was beatable despite having {len(item_objects)} copies of {item_name} " + f"instead of the specified {required_item_counts[item_name]}", + ) + item_objects.append(removed_item) + + +class WitnessMultiworldTestBase(MultiworldTestBase): + options_per_world: List[Dict[str, Any]] + common_options: Dict[str, Any] = {} + + def setUp(self) -> None: + """ + Set up a multiworld with multiple players, each using different options. + """ + + self.multiworld = setup_multiworld([WitnessWorld] * len(self.options_per_world), ()) + + for world, options in zip(self.multiworld.worlds.values(), self.options_per_world): + for option_name, option_value in {**self.common_options, **options}.items(): + option = getattr(world.options, option_name) + self.assertIsNotNone(option) + + option.value = option.from_any(option_value).value + + self.assertSteps(gen_steps) + + def collect_by_name(self, item_names: Union[str, Iterable[str]], player: int) -> List[Item]: + """ + Collect all copies of a specified item name (or list of item names) for a player in the multiworld item pool. + """ + + items = self.get_items_by_name(item_names, player) + for item in items: + self.multiworld.state.collect(item) + return items + + def get_items_by_name(self, item_names: Union[str, Iterable[str]], player: int) -> List[Item]: + """ + Return all copies of a specified item name (or list of item names) for a player in the multiworld item pool. + """ + + if isinstance(item_names, str): + item_names = (item_names,) + return [item for item in self.multiworld.itempool if item.name in item_names and item.player == player] diff --git a/worlds/witness/test/test_auto_elevators.py b/worlds/witness/test/test_auto_elevators.py new file mode 100644 index 000000000000..16b1b5a56d37 --- /dev/null +++ b/worlds/witness/test/test_auto_elevators.py @@ -0,0 +1,66 @@ +from ..test import WitnessMultiworldTestBase, WitnessTestBase + + +class TestElevatorsComeToYou(WitnessTestBase): + options = { + "elevators_come_to_you": True, + "shuffle_doors": "mixed", + "shuffle_symbols": False, + } + + def test_bunker_laser(self) -> None: + """ + In elevators_come_to_you, Bunker can be entered from the back. + This means that you can access the laser with just Bunker Elevator Control (Panel). + It also means that you can, for example, access UV Room with the Control and the Elevator Room Entry Door. + """ + + self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", self.player)) + + self.collect_by_name("Bunker Elevator Control (Panel)") + + self.assertTrue(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Bunker UV Room 2", "Location", self.player)) + + self.collect_by_name("Bunker Elevator Room Entry (Door)") + self.collect_by_name("Bunker Drop-Down Door Controls (Panel)") + + self.assertTrue(self.multiworld.state.can_reach("Bunker UV Room 2", "Location", self.player)) + + +class TestElevatorsComeToYouBleed(WitnessMultiworldTestBase): + options_per_world = [ + { + "elevators_come_to_you": False, + }, + { + "elevators_come_to_you": True, + }, + { + "elevators_come_to_you": False, + }, + ] + + common_options = { + "shuffle_symbols": False, + "shuffle_doors": "panels", + } + + def test_correct_access_per_player(self) -> None: + """ + Test that in a multiworld with players that alternate the elevators_come_to_you option, + the actual behavior alternates as well and doesn't bleed over from slot to slot. + (This is essentially a "does connection info bleed over" test). + """ + + self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 1)) + self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 2)) + self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 3)) + + self.collect_by_name(["Bunker Elevator Control (Panel)"], 1) + self.collect_by_name(["Bunker Elevator Control (Panel)"], 2) + self.collect_by_name(["Bunker Elevator Control (Panel)"], 3) + + self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 1)) + self.assertTrue(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 2)) + self.assertFalse(self.multiworld.state.can_reach("Bunker Laser Panel", "Location", 3)) diff --git a/worlds/witness/test/test_disable_non_randomized.py b/worlds/witness/test/test_disable_non_randomized.py new file mode 100644 index 000000000000..e7cb1597b2ba --- /dev/null +++ b/worlds/witness/test/test_disable_non_randomized.py @@ -0,0 +1,37 @@ +from ..rules import _has_lasers +from ..test import WitnessTestBase + + +class TestDisableNonRandomized(WitnessTestBase): + options = { + "disable_non_randomized_puzzles": True, + "shuffle_doors": "panels", + "early_symbol_item": False, + } + + def test_locations_got_disabled_and_alternate_activation_triggers_work(self) -> None: + """ + Test the different behaviors of the disable_non_randomized mode: + + 1. Unrandomized locations like Orchard Apple Tree 5 are disabled. + 2. Certain doors or lasers that would usually be activated by unrandomized panels depend on event items instead. + 3. These alternate activations are tied to solving Discarded Panels. + """ + + with self.subTest("Test that unrandomized locations are disabled."): + self.assert_location_does_not_exist("Orchard Apple Tree 5") + + with self.subTest("Test that alternate activation trigger events exist."): + self.assert_dependency_on_event_item( + self.world.get_entrance("Town Tower After Third Door to Town Tower Top"), + "Town Tower 4th Door Opens", + ) + + with self.subTest("Test that alternate activation triggers award lasers."): + self.assertFalse(_has_lasers(1, self.world, False)(self.multiworld.state)) + + self.collect_by_name("Triangles") + + # Alternate triggers yield Bunker Laser (Mountainside Discard) and Monastery Laser (Desert Discard) + self.assertTrue(_has_lasers(2, self.world, False)(self.multiworld.state)) + self.assertFalse(_has_lasers(3, self.world, False)(self.multiworld.state)) diff --git a/worlds/witness/test/test_door_shuffle.py b/worlds/witness/test/test_door_shuffle.py new file mode 100644 index 000000000000..0e38c32d69e2 --- /dev/null +++ b/worlds/witness/test/test_door_shuffle.py @@ -0,0 +1,24 @@ +from ..test import WitnessTestBase + + +class TestIndividualDoors(WitnessTestBase): + options = { + "shuffle_doors": "doors", + "door_groupings": "off", + } + + def test_swamp_laser_shortcut(self) -> None: + """ + Test that Door Shuffle grants early access to Swamp Laser from the back shortcut. + """ + + self.assertTrue(self.get_items_by_name("Swamp Laser Shortcut (Door)")) + + self.assertAccessDependency( + ["Swamp Laser Panel"], + [ + ["Swamp Laser Shortcut (Door)"], + ["Swamp Red Underwater Exit (Door)"], + ], + only_check_listed=True, + ) diff --git a/worlds/witness/test/test_ep_shuffle.py b/worlds/witness/test/test_ep_shuffle.py new file mode 100644 index 000000000000..342390916675 --- /dev/null +++ b/worlds/witness/test/test_ep_shuffle.py @@ -0,0 +1,54 @@ +from ..test import WitnessTestBase + + +class TestIndividualEPs(WitnessTestBase): + options = { + "shuffle_EPs": "individual", + "EP_difficulty": "normal", + "obelisk_keys": True, + "disable_non_randomized_puzzles": True, + "shuffle_postgame": False, + "victory_condition": "mountain_box_short", + "early_caves": "off", + } + + def test_correct_eps_exist_and_are_locked(self) -> None: + """ + Test that EP locations exist in shuffle_EPs, but only the ones that actually should (based on options) + """ + + # Test Tutorial First Hallways EP as a proxy for "EPs exist at all" + # Don't wrap in a subtest - If this fails, there is no point. + self.assert_location_exists("Tutorial First Hallway EP") + + with self.subTest("Test that disable_non_randomized disables Monastery Garden Left EP"): + self.assert_location_does_not_exist("Monastery Garden Left EP") + + with self.subTest("Test that shuffle_postgame being off disables postgame EPs."): + self.assert_location_does_not_exist("Caves Skylight EP") + + with self.subTest("Test that ep_difficulty being set to normal excludes tedious EPs."): + self.assert_location_does_not_exist("Shipwreck Couch EP") + + with self.subTest("Test that EPs are being locked by Obelisk Keys."): + self.assertAccessDependency(["Desert Sand Snake EP"], [["Desert Obelisk Key"]], True) + + +class TestObeliskSides(WitnessTestBase): + options = { + "shuffle_EPs": "obelisk_sides", + "EP_difficulty": "eclipse", + "shuffle_vault_boxes": True, + "shuffle_postgame": True, + } + + def test_eclipse_required_for_town_side_6(self) -> None: + """ + Test that Obelisk Sides require the appropriate event items from the individual EPs. + Specifically, assert that Town Obelisk Side 6 needs Theater Eclipse EP. + This doubles as a test for Theater Eclipse EP existing with the right options. + """ + + self.assert_dependency_on_event_item( + self.world.get_location("Town Obelisk Side 6"), "Town Obelisk Side 6 - Theater Eclipse EP" + ) diff --git a/worlds/witness/test/test_lasers.py b/worlds/witness/test/test_lasers.py new file mode 100644 index 000000000000..f09897ce4053 --- /dev/null +++ b/worlds/witness/test/test_lasers.py @@ -0,0 +1,185 @@ +from ..test import WitnessTestBase + + +class TestSymbolsRequiredToWinElevatorNormal(WitnessTestBase): + options = { + "shuffle_lasers": True, + "puzzle_randomization": "sigma_normal", + "mountain_lasers": 1, + "victory_condition": "elevator", + "early_symbol_item": False, + } + + def test_symbols_to_win(self) -> None: + """ + In symbol shuffle, the only way to reach the Elevator is through Mountain Entry by descending the Mountain. + This requires a very specific set of symbol items per puzzle randomization mode. + In this case, we check Sigma Normal Puzzles. + """ + + exact_requirement = { + "Monastery Laser": 1, + "Progressive Dots": 2, + "Progressive Stars": 2, + "Progressive Symmetry": 2, + "Black/White Squares": 1, + "Colored Squares": 1, + "Shapers": 1, + "Rotated Shapers": 1, + "Eraser": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) + + +class TestSymbolsRequiredToWinElevatorExpert(WitnessTestBase): + options = { + "shuffle_lasers": True, + "mountain_lasers": 1, + "victory_condition": "elevator", + "early_symbol_item": False, + "puzzle_randomization": "sigma_expert", + } + + def test_symbols_to_win(self) -> None: + """ + In symbol shuffle, the only way to reach the Elevator is through Mountain Entry by descending the Mountain. + This requires a very specific set of symbol items per puzzle randomization mode. + In this case, we check Sigma Expert Puzzles. + """ + + exact_requirement = { + "Monastery Laser": 1, + "Progressive Dots": 2, + "Progressive Stars": 2, + "Progressive Symmetry": 2, + "Black/White Squares": 1, + "Colored Squares": 1, + "Shapers": 1, + "Rotated Shapers": 1, + "Negative Shapers": 1, + "Eraser": 1, + "Triangles": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) + + +class TestSymbolsRequiredToWinElevatorVanilla(WitnessTestBase): + options = { + "shuffle_lasers": True, + "mountain_lasers": 1, + "victory_condition": "elevator", + "early_symbol_item": False, + "puzzle_randomization": "none", + } + + def test_symbols_to_win(self) -> None: + """ + In symbol shuffle, the only way to reach the Elevator is through Mountain Entry by descending the Mountain. + This requires a very specific set of symbol items per puzzle randomization mode. + In this case, we check Vanilla Puzzles. + """ + + exact_requirement = { + "Monastery Laser": 1, + "Progressive Dots": 2, + "Progressive Stars": 2, + "Progressive Symmetry": 1, + "Black/White Squares": 1, + "Colored Squares": 1, + "Shapers": 1, + "Rotated Shapers": 1, + "Eraser": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) + + +class TestPanelsRequiredToWinElevator(WitnessTestBase): + options = { + "shuffle_lasers": True, + "mountain_lasers": 1, + "victory_condition": "elevator", + "early_symbol_item": False, + "shuffle_symbols": False, + "shuffle_doors": "panels", + "door_groupings": "off", + } + + def test_panels_to_win(self) -> None: + """ + In door panel shuffle , the only way to reach the Elevator is through Mountain Entry by descending the Mountain. + This requires some control panels for each of the Mountain Floors. + """ + + exact_requirement = { + "Desert Laser": 1, + "Town Desert Laser Redirect Control (Panel)": 1, + "Mountain Floor 1 Light Bridge (Panel)": 1, + "Mountain Floor 2 Light Bridge Near (Panel)": 1, + "Mountain Floor 2 Light Bridge Far (Panel)": 1, + "Mountain Floor 2 Elevator Control (Panel)": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) + + +class TestDoorsRequiredToWinElevator(WitnessTestBase): + options = { + "shuffle_lasers": True, + "mountain_lasers": 1, + "victory_condition": "elevator", + "early_symbol_item": False, + "shuffle_symbols": False, + "shuffle_doors": "doors", + "door_groupings": "off", + } + + def test_doors_to_elevator_paths(self) -> None: + """ + In remote door shuffle, there are three ways to win. + + - Through the normal route (Mountain Entry -> Descend through Mountain -> Reach Bottom Floor) + - Through the Caves using the Caves Shortcuts (Caves -> Reach Bottom Floor) + - Through the Caves via Challenge (Tunnels -> Challenge -> Caves -> Reach Bottom Floor) + """ + + with self.subTest("Test Elevator victory in shuffle_doors through Mountain Entry."): + exact_requirement = { + "Monastery Laser": 1, + "Mountain Floor 1 Exit (Door)": 1, + "Mountain Floor 2 Staircase Near (Door)": 1, + "Mountain Floor 2 Staircase Far (Door)": 1, + "Mountain Floor 2 Exit (Door)": 1, + "Mountain Bottom Floor Giant Puzzle Exit (Door)": 1, + "Mountain Bottom Floor Pillars Room Entry (Door)": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) + + with self.subTest("Test Elevator victory in shuffle_doors through Caves Shortcuts."): + exact_requirement = { + "Monastery Laser": 1, # Elevator Panel itself has a laser lock + "Caves Mountain Shortcut (Door)": 1, + "Caves Entry (Door)": 1, + "Mountain Bottom Floor Rock (Door)": 1, + "Mountain Bottom Floor Pillars Room Entry (Door)": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) + + with self.subTest("Test Elevator victory in shuffle_doors through Tunnels->Challenge->Caves."): + exact_requirement = { + "Monastery Laser": 1, # Elevator Panel itself has a laser lock + "Windmill Entry (Door)": 1, + "Tunnels Theater Shortcut (Door)": 1, + "Tunnels Entry (Door)": 1, + "Challenge Entry (Door)": 1, + "Caves Pillar Door": 1, + "Caves Entry (Door)": 1, + "Mountain Bottom Floor Rock (Door)": 1, + "Mountain Bottom Floor Pillars Room Entry (Door)": 1, + } + + self.assert_can_beat_with_minimally(exact_requirement) diff --git a/worlds/witness/test/test_roll_other_options.py b/worlds/witness/test/test_roll_other_options.py new file mode 100644 index 000000000000..71743c326038 --- /dev/null +++ b/worlds/witness/test/test_roll_other_options.py @@ -0,0 +1,58 @@ +from ..test import WitnessTestBase + +# These are just some random options combinations, just to catch whether I broke anything obvious + + +class TestExpertNonRandomizedEPs(WitnessTestBase): + options = { + "disable_non_randomized": True, + "puzzle_randomization": "sigma_expert", + "shuffle_EPs": "individual", + "ep_difficulty": "eclipse", + "victory_condition": "challenge", + "shuffle_discarded_panels": False, + "shuffle_boat": False, + } + + +class TestVanillaAutoElevatorsPanels(WitnessTestBase): + options = { + "puzzle_randomization": "none", + "elevators_come_to_you": True, + "shuffle_doors": "panels", + "victory_condition": "mountain_box_short", + "early_caves": True, + "shuffle_vault_boxes": True, + "mountain_lasers": 11, + } + + +class TestMiscOptions(WitnessTestBase): + options = { + "death_link": True, + "death_link_amnesty": 3, + "laser_hints": True, + "hint_amount": 40, + "area_hint_percentage": 100, + } + + +class TestMaxEntityShuffle(WitnessTestBase): + options = { + "shuffle_symbols": False, + "shuffle_doors": "mixed", + "shuffle_EPs": "individual", + "obelisk_keys": True, + "shuffle_lasers": "anywhere", + "victory_condition": "mountain_box_long", + } + + +class TestPostgameGroupedDoors(WitnessTestBase): + options = { + "shuffle_postgame": True, + "shuffle_discarded_panels": True, + "shuffle_doors": "doors", + "door_groupings": "regional", + "victory_condition": "elevator", + } diff --git a/worlds/witness/test/test_symbol_shuffle.py b/worlds/witness/test/test_symbol_shuffle.py new file mode 100644 index 000000000000..8012480075a7 --- /dev/null +++ b/worlds/witness/test/test_symbol_shuffle.py @@ -0,0 +1,74 @@ +from ..test import WitnessMultiworldTestBase, WitnessTestBase + + +class TestSymbols(WitnessTestBase): + options = { + "early_symbol_item": False, + } + + def test_progressive_symbols(self) -> None: + """ + Test that Dots & Full Dots are correctly replaced by 2x Progressive Dots, + and test that Dots puzzles and Full Dots puzzles require 1 and 2 copies of this item respectively. + """ + + progressive_dots = self.get_items_by_name("Progressive Dots") + self.assertEqual(len(progressive_dots), 2) + + self.assertFalse(self.multiworld.state.can_reach("Outside Tutorial Shed Row 5", "Location", self.player)) + self.assertFalse( + self.multiworld.state.can_reach("Outside Tutorial Outpost Entry Panel", "Location", self.player) + ) + + self.collect(progressive_dots.pop()) + + self.assertTrue(self.multiworld.state.can_reach("Outside Tutorial Shed Row 5", "Location", self.player)) + self.assertFalse( + self.multiworld.state.can_reach("Outside Tutorial Outpost Entry Panel", "Location", self.player) + ) + + self.collect(progressive_dots.pop()) + + self.assertTrue(self.multiworld.state.can_reach("Outside Tutorial Shed Row 5", "Location", self.player)) + self.assertTrue( + self.multiworld.state.can_reach("Outside Tutorial Outpost Entry Panel", "Location", self.player) + ) + + +class TestSymbolRequirementsMultiworld(WitnessMultiworldTestBase): + options_per_world = [ + { + "puzzle_randomization": "sigma_normal", + }, + { + "puzzle_randomization": "sigma_expert", + }, + { + "puzzle_randomization": "none", + }, + ] + + common_options = { + "shuffle_discarded_panels": True, + "early_symbol_item": False, + } + + def test_arrows_exist_and_are_required_in_expert_seeds_only(self) -> None: + """ + In sigma_expert, Discarded Panels require Arrows. + In sigma_normal, Discarded Panels require Triangles, and Arrows shouldn't exist at all as an item. + """ + + with self.subTest("Test that Arrows exist only in the expert seed."): + self.assertFalse(self.get_items_by_name("Arrows", 1)) + self.assertTrue(self.get_items_by_name("Arrows", 2)) + self.assertFalse(self.get_items_by_name("Arrows", 3)) + + with self.subTest("Test that Discards ask for Triangles in normal, but Arrows in expert."): + desert_discard = "0x17CE7" + triangles = frozenset({frozenset({"Triangles"})}) + arrows = frozenset({frozenset({"Arrows"})}) + + self.assertEqual(self.multiworld.worlds[1].player_logic.REQUIREMENTS_BY_HEX[desert_discard], triangles) + self.assertEqual(self.multiworld.worlds[2].player_logic.REQUIREMENTS_BY_HEX[desert_discard], arrows) + self.assertEqual(self.multiworld.worlds[3].player_logic.REQUIREMENTS_BY_HEX[desert_discard], triangles) diff --git a/worlds/yoshisisland/Client.py b/worlds/yoshisisland/Client.py new file mode 100644 index 000000000000..9b9e0ff52b87 --- /dev/null +++ b/worlds/yoshisisland/Client.py @@ -0,0 +1,147 @@ +import logging +import struct +import typing +import time +from struct import pack + +from NetUtils import ClientStatus, color +from worlds.AutoSNIClient import SNIClient + +if typing.TYPE_CHECKING: + from SNIClient import SNIContext + +snes_logger = logging.getLogger("SNES") + +ROM_START = 0x000000 +WRAM_START = 0xF50000 +WRAM_SIZE = 0x20000 +SRAM_START = 0xE00000 + +YOSHISISLAND_ROMHASH_START = 0x007FC0 +ROMHASH_SIZE = 0x15 + +ITEMQUEUE_HIGH = WRAM_START + 0x1465 +ITEM_RECEIVED = WRAM_START + 0x1467 +DEATH_RECEIVED = WRAM_START + 0x7E23B0 +GAME_MODE = WRAM_START + 0x0118 +YOSHI_STATE = SRAM_START + 0x00AC +DEATHLINK_ADDR = ROM_START + 0x06FC8C +DEATHMUSIC_FLAG = WRAM_START + 0x004F +DEATHFLAG = WRAM_START + 0x00DB +DEATHLINKRECV = WRAM_START + 0x00E0 +GOALFLAG = WRAM_START + 0x14B6 + +VALID_GAME_STATES = [0x0F, 0x10, 0x2C] + + +class YoshisIslandSNIClient(SNIClient): + game = "Yoshi's Island" + patch_suffix = ".apyi" + + async def deathlink_kill_player(self, ctx: "SNIContext") -> None: + from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read + game_state = await snes_read(ctx, GAME_MODE, 0x1) + if game_state[0] != 0x0F: + return + + yoshi_state = await snes_read(ctx, YOSHI_STATE, 0x1) + if yoshi_state[0] != 0x00: + return + + snes_buffered_write(ctx, WRAM_START + 0x026A, bytes([0x01])) + snes_buffered_write(ctx, WRAM_START + 0x00E0, bytes([0x01])) + await snes_flush_writes(ctx) + ctx.death_state = DeathState.dead + ctx.last_death_link = time.time() + + async def validate_rom(self, ctx: "SNIContext") -> bool: + from SNIClient import snes_read + + rom_name = await snes_read(ctx, YOSHISISLAND_ROMHASH_START, ROMHASH_SIZE) + if rom_name is None or rom_name[:7] != b"YOSHIAP": + return False + + ctx.game = self.game + ctx.items_handling = 0b111 # remote items + ctx.rom = rom_name + + death_link = await snes_read(ctx, DEATHLINK_ADDR, 1) + if death_link: + await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + async def game_watcher(self, ctx: "SNIContext") -> None: + from SNIClient import snes_buffered_write, snes_flush_writes, snes_read + + game_mode = await snes_read(ctx, GAME_MODE, 0x1) + item_received = await snes_read(ctx, ITEM_RECEIVED, 0x1) + game_music = await snes_read(ctx, DEATHMUSIC_FLAG, 0x1) + goal_flag = await snes_read(ctx, GOALFLAG, 0x1) + + if "DeathLink" in ctx.tags and ctx.last_death_link + 1 < time.time(): + death_flag = await snes_read(ctx, DEATHFLAG, 0x1) + deathlink_death = await snes_read(ctx, DEATHLINKRECV, 0x1) + currently_dead = (game_music[0] == 0x07 or game_mode[0] == 0x12 or + (death_flag[0] == 0x00 and game_mode[0] == 0x11)) and deathlink_death[0] == 0x00 + await ctx.handle_deathlink_state(currently_dead) + + if game_mode is None: + return + + elif game_mode[0] not in VALID_GAME_STATES: + return + elif item_received[0] > 0x00: + return + + from .Rom import item_values + rom = await snes_read(ctx, YOSHISISLAND_ROMHASH_START, ROMHASH_SIZE) + if rom != ctx.rom: + ctx.rom = None + return + + if goal_flag[0] != 0x00: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + + new_checks = [] + from .Rom import location_table + + location_ram_data = await snes_read(ctx, WRAM_START + 0x1440, 0x80) + for loc_id, loc_data in location_table.items(): + if loc_id not in ctx.locations_checked: + data = location_ram_data[loc_data[0] - 0x1440] + masked_data = data & (1 << loc_data[1]) + bit_set = masked_data != 0 + invert_bit = ((len(loc_data) >= 3) and loc_data[2]) + if bit_set != invert_bit: + new_checks.append(loc_id) + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names.lookup_in_game(new_check_id) + total_locations = len(ctx.missing_locations) + len(ctx.checked_locations) + snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})") + await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}]) + + recv_count = await snes_read(ctx, ITEMQUEUE_HIGH, 2) + recv_index = struct.unpack("H", recv_count)[0] + if recv_index < len(ctx.items_received): + item = ctx.items_received[recv_index] + recv_index += 1 + logging.info("Received %s from %s (%s) (%d/%d in list)" % ( + color(ctx.item_names.lookup_in_game(item.item), "red", "bold"), + color(ctx.player_names[item.player], "yellow"), + ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received))) + + snes_buffered_write(ctx, ITEMQUEUE_HIGH, pack("H", recv_index)) + if item.item in item_values: + item_count = await snes_read(ctx, WRAM_START + item_values[item.item][0], 0x1) + increment = item_values[item.item][1] + new_item_count = item_count[0] + if increment > 1: + new_item_count = increment + else: + new_item_count += increment + + snes_buffered_write(ctx, WRAM_START + item_values[item.item][0], bytes([new_item_count])) + await snes_flush_writes(ctx) diff --git a/worlds/yoshisisland/Items.py b/worlds/yoshisisland/Items.py new file mode 100644 index 000000000000..f30c7317798f --- /dev/null +++ b/worlds/yoshisisland/Items.py @@ -0,0 +1,122 @@ +from typing import Dict, Set, Tuple, NamedTuple, Optional +from BaseClasses import ItemClassification + +class ItemData(NamedTuple): + category: str + code: Optional[int] + classification: ItemClassification + amount: Optional[int] = 1 + +item_table: Dict[str, ItemData] = { + "! Switch": ItemData("Items", 0x302050, ItemClassification.progression), + "Dashed Platform": ItemData("Items", 0x302051, ItemClassification.progression), + "Dashed Stairs": ItemData("Items", 0x302052, ItemClassification.progression), + "Beanstalk": ItemData("Items", 0x302053, ItemClassification.progression), + "Helicopter Morph": ItemData("Morphs", 0x302054, ItemClassification.progression), + "Spring Ball": ItemData("Items", 0x302055, ItemClassification.progression), + "Large Spring Ball": ItemData("Items", 0x302056, ItemClassification.progression), + "Arrow Wheel": ItemData("Items", 0x302057, ItemClassification.progression), + "Vanishing Arrow Wheel": ItemData("Items", 0x302058, ItemClassification.progression), + "Mole Tank Morph": ItemData("Morphs", 0x302059, ItemClassification.progression), + "Watermelon": ItemData("Items", 0x30205A, ItemClassification.progression), + "Ice Melon": ItemData("Items", 0x30205B, ItemClassification.progression), + "Fire Melon": ItemData("Items", 0x30205C, ItemClassification.progression), + "Super Star": ItemData("Items", 0x30205D, ItemClassification.progression), + "Car Morph": ItemData("Morphs", 0x30205E, ItemClassification.progression), + "Flashing Eggs": ItemData("Items", 0x30205F, ItemClassification.progression), + "Giant Eggs": ItemData("Items", 0x302060, ItemClassification.progression), + "Egg Launcher": ItemData("Items", 0x302061, ItemClassification.progression), + "Egg Plant": ItemData("Items", 0x302062, ItemClassification.progression), + "Submarine Morph": ItemData("Morphs", 0x302063, ItemClassification.progression), + "Chomp Rock": ItemData("Items", 0x302064, ItemClassification.progression), + "Poochy": ItemData("Items", 0x302065, ItemClassification.progression), + "Platform Ghost": ItemData("Items", 0x302066, ItemClassification.progression), + "Skis": ItemData("Items", 0x302067, ItemClassification.progression), + "Train Morph": ItemData("Morphs", 0x302068, ItemClassification.progression), + "Key": ItemData("Items", 0x302069, ItemClassification.progression), + "Middle Ring": ItemData("Items", 0x30206A, ItemClassification.progression), + "Bucket": ItemData("Items", 0x30206B, ItemClassification.progression), + "Tulip": ItemData("Items", 0x30206C, ItemClassification.progression), + "Egg Capacity Upgrade": ItemData("Items", 0x30206D, ItemClassification.progression, 5), + "Secret Lens": ItemData("Items", 0x302081, ItemClassification.progression), + + "World 1 Gate": ItemData("Gates", 0x30206E, ItemClassification.progression), + "World 2 Gate": ItemData("Gates", 0x30206F, ItemClassification.progression), + "World 3 Gate": ItemData("Gates", 0x302070, ItemClassification.progression), + "World 4 Gate": ItemData("Gates", 0x302071, ItemClassification.progression), + "World 5 Gate": ItemData("Gates", 0x302072, ItemClassification.progression), + "World 6 Gate": ItemData("Gates", 0x302073, ItemClassification.progression), + + "Extra 1": ItemData("Panels", 0x302074, ItemClassification.progression), + "Extra 2": ItemData("Panels", 0x302075, ItemClassification.progression), + "Extra 3": ItemData("Panels", 0x302076, ItemClassification.progression), + "Extra 4": ItemData("Panels", 0x302077, ItemClassification.progression), + "Extra 5": ItemData("Panels", 0x302078, ItemClassification.progression), + "Extra 6": ItemData("Panels", 0x302079, ItemClassification.progression), + "Extra Panels": ItemData("Panels", 0x30207A, ItemClassification.progression), + + "Bonus 1": ItemData("Panels", 0x30207B, ItemClassification.progression), + "Bonus 2": ItemData("Panels", 0x30207C, ItemClassification.progression), + "Bonus 3": ItemData("Panels", 0x30207D, ItemClassification.progression), + "Bonus 4": ItemData("Panels", 0x30207E, ItemClassification.progression), + "Bonus 5": ItemData("Panels", 0x30207F, ItemClassification.progression), + "Bonus 6": ItemData("Panels", 0x302080, ItemClassification.progression), + "Bonus Panels": ItemData("Panels", 0x302082, ItemClassification.progression), + + "Anytime Egg": ItemData("Consumable", 0x302083, ItemClassification.useful, 0), + "Anywhere Pow": ItemData("Consumable", 0x302084, ItemClassification.filler, 0), + "Winged Cloud Maker": ItemData("Consumable", 0x302085, ItemClassification.filler, 0), + "Pocket Melon": ItemData("Consumable", 0x302086, ItemClassification.filler, 0), + "Pocket Fire Melon": ItemData("Consumable", 0x302087, ItemClassification.filler, 0), + "Pocket Ice Melon": ItemData("Consumable", 0x302088, ItemClassification.filler, 0), + "Magnifying Glass": ItemData("Consumable", 0x302089, ItemClassification.filler, 0), + "+10 Stars": ItemData("Consumable", 0x30208A, ItemClassification.useful, 0), + "+20 Stars": ItemData("Consumable", 0x30208B, ItemClassification.useful, 0), + "1-Up": ItemData("Lives", 0x30208C, ItemClassification.filler, 0), + "2-Up": ItemData("Lives", 0x30208D, ItemClassification.filler, 0), + "3-Up": ItemData("Lives", 0x30208E, ItemClassification.filler, 0), + "10-Up": ItemData("Lives", 0x30208F, ItemClassification.useful, 5), + "Bonus Consumables": ItemData("Events", None, ItemClassification.progression, 0), + "Bandit Consumables": ItemData("Events", None, ItemClassification.progression, 0), + "Bandit Watermelons": ItemData("Events", None, ItemClassification.progression, 0), + + "Fuzzy Trap": ItemData("Traps", 0x302090, ItemClassification.trap, 0), + "Reversal Trap": ItemData("Traps", 0x302091, ItemClassification.trap, 0), + "Darkness Trap": ItemData("Traps", 0x302092, ItemClassification.trap, 0), + "Freeze Trap": ItemData("Traps", 0x302093, ItemClassification.trap, 0), + + "Boss Clear": ItemData("Events", None, ItemClassification.progression, 0), + "Piece of Luigi": ItemData("Items", 0x302095, ItemClassification.progression, 0), + "Saved Baby Luigi": ItemData("Events", None, ItemClassification.progression, 0) +} + +filler_items: Tuple[str, ...] = ( + "Anytime Egg", + "Anywhere Pow", + "Winged Cloud Maker", + "Pocket Melon", + "Pocket Fire Melon", + "Pocket Ice Melon", + "Magnifying Glass", + "+10 Stars", + "+20 Stars", + "1-Up", + "2-Up", + "3-Up" +) + +trap_items: Tuple[str, ...] = ( + "Fuzzy Trap", + "Reversal Trap", + "Darkness Trap", + "Freeze Trap" +) + +def get_item_names_per_category() -> Dict[str, Set[str]]: + categories: Dict[str, Set[str]] = {} + + for name, data in item_table.items(): + if data.category != "Events": + categories.setdefault(data.category, set()).add(name) + + return categories diff --git a/worlds/yoshisisland/Locations.py b/worlds/yoshisisland/Locations.py new file mode 100644 index 000000000000..bc0855260eb4 --- /dev/null +++ b/worlds/yoshisisland/Locations.py @@ -0,0 +1,355 @@ +from typing import List, Optional, NamedTuple, TYPE_CHECKING + +from .Options import PlayerGoal, MinigameChecks +from worlds.generic.Rules import CollectionRule + +if TYPE_CHECKING: + from . import YoshisIslandWorld +from .level_logic import YoshiLogic + + +class LocationData(NamedTuple): + region: str + name: str + code: Optional[int] + LevelID: int + rule: CollectionRule = lambda state: True + + +def get_locations(world: Optional["YoshisIslandWorld"]) -> List[LocationData]: + if world: + logic = YoshiLogic(world) + + location_table: List[LocationData] = [ + LocationData("1-1", "Make Eggs, Throw Eggs: Red Coins", 0x305020, 0x00), + LocationData("1-1", "Make Eggs, Throw Eggs: Flowers", 0x305021, 0x00), + LocationData("1-1", "Make Eggs, Throw Eggs: Stars", 0x305022, 0x00), + LocationData("1-1", "Make Eggs, Throw Eggs: Level Clear", 0x305023, 0x00), + + LocationData("1-2", "Watch Out Below!: Red Coins", 0x305024, 0x01), + LocationData("1-2", "Watch Out Below!: Flowers", 0x305025, 0x01), + LocationData("1-2", "Watch Out Below!: Stars", 0x305026, 0x01), + LocationData("1-2", "Watch Out Below!: Level Clear", 0x305027, 0x01), + + LocationData("1-3", "The Cave Of Chomp Rock: Red Coins", 0x305028, 0x02), + LocationData("1-3", "The Cave Of Chomp Rock: Flowers", 0x305029, 0x02), + LocationData("1-3", "The Cave Of Chomp Rock: Stars", 0x30502A, 0x02), + LocationData("1-3", "The Cave Of Chomp Rock: Level Clear", 0x30502B, 0x02), + + LocationData("1-4", "Burt The Bashful's Fort: Red Coins", 0x30502C, 0x03), + LocationData("1-4", "Burt The Bashful's Fort: Flowers", 0x30502D, 0x03), + LocationData("1-4", "Burt The Bashful's Fort: Stars", 0x30502E, 0x03), + LocationData("1-4", "Burt The Bashful's Fort: Level Clear", 0x30502F, 0x03, lambda state: logic._14CanFightBoss(state)), + LocationData("Burt The Bashful's Boss Room", "Burt The Bashful's Boss Room", None, 0x03, lambda state: logic._14Boss(state)), + + LocationData("1-5", "Hop! Hop! Donut Lifts: Red Coins", 0x305031, 0x04), + LocationData("1-5", "Hop! Hop! Donut Lifts: Flowers", 0x305032, 0x04), + LocationData("1-5", "Hop! Hop! Donut Lifts: Stars", 0x305033, 0x04), + LocationData("1-5", "Hop! Hop! Donut Lifts: Level Clear", 0x305034, 0x04), + + LocationData("1-6", "Shy-Guys On Stilts: Red Coins", 0x305035, 0x05), + LocationData("1-6", "Shy-Guys On Stilts: Flowers", 0x305036, 0x05), + LocationData("1-6", "Shy-Guys On Stilts: Stars", 0x305037, 0x05), + LocationData("1-6", "Shy-Guys On Stilts: Level Clear", 0x305038, 0x05), + + LocationData("1-7", "Touch Fuzzy Get Dizzy: Red Coins", 0x305039, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Flowers", 0x30503A, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Stars", 0x30503B, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Level Clear", 0x30503C, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Gather Coins", None, 0x06, lambda state: logic._17Game(state)), + + LocationData("1-8", "Salvo The Slime's Castle: Red Coins", 0x30503D, 0x07), + LocationData("1-8", "Salvo The Slime's Castle: Flowers", 0x30503E, 0x07), + LocationData("1-8", "Salvo The Slime's Castle: Stars", 0x30503F, 0x07), + LocationData("1-8", "Salvo The Slime's Castle: Level Clear", 0x305040, 0x07, lambda state: logic._18CanFightBoss(state)), + LocationData("Salvo The Slime's Boss Room", "Salvo The Slime's Boss Room", None, 0x07, lambda state: logic._18Boss(state)), + + LocationData("1-Bonus", "Flip Cards", None, 0x09), + ############################################################################################ + LocationData("2-1", "Visit Koopa And Para-Koopa: Red Coins", 0x305041, 0x0C), + LocationData("2-1", "Visit Koopa And Para-Koopa: Flowers", 0x305042, 0x0C), + LocationData("2-1", "Visit Koopa And Para-Koopa: Stars", 0x305043, 0x0C), + LocationData("2-1", "Visit Koopa And Para-Koopa: Level Clear", 0x305044, 0x0C), + + LocationData("2-2", "The Baseball Boys: Red Coins", 0x305045, 0x0D), + LocationData("2-2", "The Baseball Boys: Flowers", 0x305046, 0x0D), + LocationData("2-2", "The Baseball Boys: Stars", 0x305047, 0x0D), + LocationData("2-2", "The Baseball Boys: Level Clear", 0x305048, 0x0D), + + LocationData("2-3", "What's Gusty Taste Like?: Red Coins", 0x305049, 0x0E), + LocationData("2-3", "What's Gusty Taste Like?: Flowers", 0x30504A, 0x0E), + LocationData("2-3", "What's Gusty Taste Like?: Stars", 0x30504B, 0x0E), + LocationData("2-3", "What's Gusty Taste Like?: Level Clear", 0x30504C, 0x0E), + + LocationData("2-4", "Bigger Boo's Fort: Red Coins", 0x30504D, 0x0F), + LocationData("2-4", "Bigger Boo's Fort: Flowers", 0x30504E, 0x0F), + LocationData("2-4", "Bigger Boo's Fort: Stars", 0x30504F, 0x0F), + LocationData("2-4", "Bigger Boo's Fort: Level Clear", 0x305050, 0x0F, lambda state: logic._24CanFightBoss(state)), + LocationData("Bigger Boo's Boss Room", "Bigger Boo's Boss Room", None, 0x0F, lambda state: logic._24Boss(state)), + + LocationData("2-5", "Watch Out For Lakitu: Red Coins", 0x305051, 0x10), + LocationData("2-5", "Watch Out For Lakitu: Flowers", 0x305052, 0x10), + LocationData("2-5", "Watch Out For Lakitu: Stars", 0x305053, 0x10), + LocationData("2-5", "Watch Out For Lakitu: Level Clear", 0x305054, 0x10), + + LocationData("2-6", "The Cave Of The Mystery Maze: Red Coins", 0x305055, 0x11), + LocationData("2-6", "The Cave Of The Mystery Maze: Flowers", 0x305056, 0x11), + LocationData("2-6", "The Cave Of The Mystery Maze: Stars", 0x305057, 0x11), + LocationData("2-6", "The Cave Of The Mystery Maze: Level Clear", 0x305058, 0x11), + LocationData("2-6", "The Cave Of the Mystery Maze: Seed Spitting Contest", None, 0x11, lambda state: logic._26Game(state)), + + LocationData("2-7", "Lakitu's Wall: Red Coins", 0x305059, 0x12), + LocationData("2-7", "Lakitu's Wall: Flowers", 0x30505A, 0x12), + LocationData("2-7", "Lakitu's Wall: Stars", 0x30505B, 0x12), + LocationData("2-7", "Lakitu's Wall: Level Clear", 0x30505C, 0x12), + LocationData("2-7", "Lakitu's Wall: Gather Coins", None, 0x12, lambda state: logic._27Game(state)), + + LocationData("2-8", "The Potted Ghost's Castle: Red Coins", 0x30505D, 0x13), + LocationData("2-8", "The Potted Ghost's Castle: Flowers", 0x30505E, 0x13), + LocationData("2-8", "The Potted Ghost's Castle: Stars", 0x30505F, 0x13), + LocationData("2-8", "The Potted Ghost's Castle: Level Clear", 0x305060, 0x13, lambda state: logic._28CanFightBoss(state)), + LocationData("Roger The Ghost's Boss Room", "Roger The Ghost's Boss Room", None, 0x13, lambda state: logic._28Boss(state)), + ############################################################################################### + LocationData("3-1", "Welcome To Monkey World!: Red Coins", 0x305061, 0x18), + LocationData("3-1", "Welcome To Monkey World!: Flowers", 0x305062, 0x18), + LocationData("3-1", "Welcome To Monkey World!: Stars", 0x305063, 0x18), + LocationData("3-1", "Welcome To Monkey World!: Level Clear", 0x305064, 0x18), + + LocationData("3-2", "Jungle Rhythm...: Red Coins", 0x305065, 0x19), + LocationData("3-2", "Jungle Rhythm...: Flowers", 0x305066, 0x19), + LocationData("3-2", "Jungle Rhythm...: Stars", 0x305067, 0x19), + LocationData("3-2", "Jungle Rhythm...: Level Clear", 0x305068, 0x19), + + LocationData("3-3", "Nep-Enuts' Domain: Red Coins", 0x305069, 0x1A), + LocationData("3-3", "Nep-Enuts' Domain: Flowers", 0x30506A, 0x1A), + LocationData("3-3", "Nep-Enuts' Domain: Stars", 0x30506B, 0x1A), + LocationData("3-3", "Nep-Enuts' Domain: Level Clear", 0x30506C, 0x1A), + + LocationData("3-4", "Prince Froggy's Fort: Red Coins", 0x30506D, 0x1B), + LocationData("3-4", "Prince Froggy's Fort: Flowers", 0x30506E, 0x1B), + LocationData("3-4", "Prince Froggy's Fort: Stars", 0x30506F, 0x1B), + LocationData("3-4", "Prince Froggy's Fort: Level Clear", 0x305070, 0x1B, lambda state: logic._34CanFightBoss(state)), + LocationData("Prince Froggy's Boss Room", "Prince Froggy's Boss Room", None, 0x1B, lambda state: logic._34Boss(state)), + + LocationData("3-5", "Jammin' Through The Trees: Red Coins", 0x305071, 0x1C), + LocationData("3-5", "Jammin' Through The Trees: Flowers", 0x305072, 0x1C), + LocationData("3-5", "Jammin' Through The Trees: Stars", 0x305073, 0x1C), + LocationData("3-5", "Jammin' Through The Trees: Level Clear", 0x305074, 0x1C), + + LocationData("3-6", "The Cave Of Harry Hedgehog: Red Coins", 0x305075, 0x1D), + LocationData("3-6", "The Cave Of Harry Hedgehog: Flowers", 0x305076, 0x1D), + LocationData("3-6", "The Cave Of Harry Hedgehog: Stars", 0x305077, 0x1D), + LocationData("3-6", "The Cave Of Harry Hedgehog: Level Clear", 0x305078, 0x1D), + + LocationData("3-7", "Monkeys' Favorite Lake: Red Coins", 0x305079, 0x1E), + LocationData("3-7", "Monkeys' Favorite Lake: Flowers", 0x30507A, 0x1E), + LocationData("3-7", "Monkeys' Favorite Lake: Stars", 0x30507B, 0x1E), + LocationData("3-7", "Monkeys' Favorite Lake: Level Clear", 0x30507C, 0x1E), + + LocationData("3-8", "Naval Piranha's Castle: Red Coins", 0x30507D, 0x1F), + LocationData("3-8", "Naval Piranha's Castle: Flowers", 0x30507E, 0x1F), + LocationData("3-8", "Naval Piranha's Castle: Stars", 0x30507F, 0x1F), + LocationData("3-8", "Naval Piranha's Castle: Level Clear", 0x305080, 0x1F, lambda state: logic._38CanFightBoss(state)), + LocationData("Naval Piranha's Boss Room", "Naval Piranha's Boss Room", None, 0x1F, lambda state: logic._38Boss(state)), + + LocationData("3-Bonus", "Drawing Lots", None, 0x21), + ############################################################################################## + LocationData("4-1", "GO! GO! MARIO!!: Red Coins", 0x305081, 0x24), + LocationData("4-1", "GO! GO! MARIO!!: Flowers", 0x305082, 0x24), + LocationData("4-1", "GO! GO! MARIO!!: Stars", 0x305083, 0x24), + LocationData("4-1", "GO! GO! MARIO!!: Level Clear", 0x305084, 0x24), + + LocationData("4-2", "The Cave Of The Lakitus: Red Coins", 0x305085, 0x25), + LocationData("4-2", "The Cave Of The Lakitus: Flowers", 0x305086, 0x25), + LocationData("4-2", "The Cave Of The Lakitus: Stars", 0x305087, 0x25), + LocationData("4-2", "The Cave Of The Lakitus: Level Clear", 0x305088, 0x25), + + LocationData("4-3", "Don't Look Back!: Red Coins", 0x305089, 0x26), + LocationData("4-3", "Don't Look Back!: Flowers", 0x30508A, 0x26), + LocationData("4-3", "Don't Look Back!: Stars", 0x30508B, 0x26), + LocationData("4-3", "Don't Look Back!: Level Clear", 0x30508C, 0x26), + + LocationData("4-4", "Marching Milde's Fort: Red Coins", 0x30508D, 0x27), + LocationData("4-4", "Marching Milde's Fort: Flowers", 0x30508E, 0x27), + LocationData("4-4", "Marching Milde's Fort: Stars", 0x30508F, 0x27), + LocationData("4-4", "Marching Milde's Fort: Level Clear", 0x305090, 0x27, lambda state: logic._44CanFightBoss(state)), + LocationData("Marching Milde's Boss Room", "Marching Milde's Boss Room", None, 0x27, lambda state: logic._44Boss(state)), + + LocationData("4-5", "Chomp Rock Zone: Red Coins", 0x305091, 0x28), + LocationData("4-5", "Chomp Rock Zone: Flowers", 0x305092, 0x28), + LocationData("4-5", "Chomp Rock Zone: Stars", 0x305093, 0x28), + LocationData("4-5", "Chomp Rock Zone: Level Clear", 0x305094, 0x28), + + LocationData("4-6", "Lake Shore Paradise: Red Coins", 0x305095, 0x29), + LocationData("4-6", "Lake Shore Paradise: Flowers", 0x305096, 0x29), + LocationData("4-6", "Lake Shore Paradise: Stars", 0x305097, 0x29), + LocationData("4-6", "Lake Shore Paradise: Level Clear", 0x305098, 0x29), + + LocationData("4-7", "Ride Like The Wind: Red Coins", 0x305099, 0x2A), + LocationData("4-7", "Ride Like The Wind: Flowers", 0x30509A, 0x2A), + LocationData("4-7", "Ride Like The Wind: Stars", 0x30509B, 0x2A), + LocationData("4-7", "Ride Like The Wind: Level Clear", 0x30509C, 0x2A), + LocationData("4-7", "Ride Like The Wind: Gather Coins", None, 0x2A, lambda state: logic._47Game(state)), + + LocationData("4-8", "Hookbill The Koopa's Castle: Red Coins", 0x30509D, 0x2B), + LocationData("4-8", "Hookbill The Koopa's Castle: Flowers", 0x30509E, 0x2B), + LocationData("4-8", "Hookbill The Koopa's Castle: Stars", 0x30509F, 0x2B), + LocationData("4-8", "Hookbill The Koopa's Castle: Level Clear", 0x3050A0, 0x2B, lambda state: logic._48CanFightBoss(state)), + LocationData("Hookbill The Koopa's Boss Room", "Hookbill The Koopa's Boss Room", None, 0x2B, lambda state: logic._48Boss(state)), + + LocationData("4-Bonus", "Match Cards", None, 0x2D), + ###################################################################################################### + LocationData("5-1", "BLIZZARD!!!: Red Coins", 0x3050A1, 0x30), + LocationData("5-1", "BLIZZARD!!!: Flowers", 0x3050A2, 0x30), + LocationData("5-1", "BLIZZARD!!!: Stars", 0x3050A3, 0x30), + LocationData("5-1", "BLIZZARD!!!: Level Clear", 0x3050A4, 0x30), + + LocationData("5-2", "Ride The Ski Lifts: Red Coins", 0x3050A5, 0x31), + LocationData("5-2", "Ride The Ski Lifts: Flowers", 0x3050A6, 0x31), + LocationData("5-2", "Ride The Ski Lifts: Stars", 0x3050A7, 0x31), + LocationData("5-2", "Ride The Ski Lifts: Level Clear", 0x3050A8, 0x31), + + LocationData("5-3", "Danger - Icy Conditions Ahead: Red Coins", 0x3050A9, 0x32), + LocationData("5-3", "Danger - Icy Conditions Ahead: Flowers", 0x3050AA, 0x32), + LocationData("5-3", "Danger - Icy Conditions Ahead: Stars", 0x3050AB, 0x32), + LocationData("5-3", "Danger - Icy Conditions Ahead: Level Clear", 0x3050AC, 0x32), + + LocationData("5-4", "Sluggy The Unshaven's Fort: Red Coins", 0x3050AD, 0x33), + LocationData("5-4", "Sluggy The Unshaven's Fort: Flowers", 0x3050AE, 0x33), + LocationData("5-4", "Sluggy The Unshaven's Fort: Stars", 0x3050AF, 0x33), + LocationData("5-4", "Sluggy The Unshaven's Fort: Level Clear", 0x3050B0, 0x33, lambda state: logic._54CanFightBoss(state)), + LocationData("Sluggy The Unshaven's Boss Room", "Sluggy The Unshaven's Boss Room", None, 0x33, lambda state: logic._54Boss(state)), + + LocationData("5-5", "Goonie Rides!: Red Coins", 0x3050B1, 0x34), + LocationData("5-5", "Goonie Rides!: Flowers", 0x3050B2, 0x34), + LocationData("5-5", "Goonie Rides!: Stars", 0x3050B3, 0x34), + LocationData("5-5", "Goonie Rides!: Level Clear", 0x3050B4, 0x34), + + LocationData("5-6", "Welcome To Cloud World: Red Coins", 0x3050B5, 0x35), + LocationData("5-6", "Welcome To Cloud World: Flowers", 0x3050B6, 0x35), + LocationData("5-6", "Welcome To Cloud World: Stars", 0x3050B7, 0x35), + LocationData("5-6", "Welcome To Cloud World: Level Clear", 0x3050B8, 0x35), + + LocationData("5-7", "Shifting Platforms Ahead: Red Coins", 0x3050B9, 0x36), + LocationData("5-7", "Shifting Platforms Ahead: Flowers", 0x3050BA, 0x36), + LocationData("5-7", "Shifting Platforms Ahead: Stars", 0x3050BB, 0x36), + LocationData("5-7", "Shifting Platforms Ahead: Level Clear", 0x3050BC, 0x36), + + LocationData("5-8", "Raphael The Raven's Castle: Red Coins", 0x3050BD, 0x37), + LocationData("5-8", "Raphael The Raven's Castle: Flowers", 0x3050BE, 0x37), + LocationData("5-8", "Raphael The Raven's Castle: Stars", 0x3050BF, 0x37), + LocationData("5-8", "Raphael The Raven's Castle: Level Clear", 0x3050C0, 0x37, lambda state: logic._58CanFightBoss(state)), + LocationData("Raphael The Raven's Boss Room", "Raphael The Raven's Boss Room", None, 0x37, lambda state: logic._58Boss(state)), + ###################################################################################################### + + LocationData("6-1", "Scary Skeleton Goonies!: Red Coins", 0x3050C1, 0x3C), + LocationData("6-1", "Scary Skeleton Goonies!: Flowers", 0x3050C2, 0x3C), + LocationData("6-1", "Scary Skeleton Goonies!: Stars", 0x3050C3, 0x3C), + LocationData("6-1", "Scary Skeleton Goonies!: Level Clear", 0x3050C4, 0x3C), + + LocationData("6-2", "The Cave Of The Bandits: Red Coins", 0x3050C5, 0x3D), + LocationData("6-2", "The Cave Of The Bandits: Flowers", 0x3050C6, 0x3D), + LocationData("6-2", "The Cave Of The Bandits: Stars", 0x3050C7, 0x3D), + LocationData("6-2", "The Cave Of The Bandits: Level Clear", 0x3050C8, 0x3D), + + LocationData("6-3", "Beware The Spinning Logs: Red Coins", 0x3050C9, 0x3E), + LocationData("6-3", "Beware The Spinning Logs: Flowers", 0x3050CA, 0x3E), + LocationData("6-3", "Beware The Spinning Logs: Stars", 0x3050CB, 0x3E), + LocationData("6-3", "Beware The Spinning Logs: Level Clear", 0x3050CC, 0x3E), + + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Red Coins", 0x3050CD, 0x3F), + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Flowers", 0x3050CE, 0x3F), + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Stars", 0x3050CF, 0x3F), + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Level Clear", 0x3050D0, 0x3F, lambda state: logic._64CanFightBoss(state)), + LocationData("Tap-Tap The Red Nose's Boss Room", "Tap-Tap The Red Nose's Boss Room", None, 0x3F, lambda state: logic._64Boss(state)), + + LocationData("6-5", "The Very Loooooong Cave: Red Coins", 0x3050D1, 0x40), + LocationData("6-5", "The Very Loooooong Cave: Flowers", 0x3050D2, 0x40), + LocationData("6-5", "The Very Loooooong Cave: Stars", 0x3050D3, 0x40), + LocationData("6-5", "The Very Loooooong Cave: Level Clear", 0x3050D4, 0x40), + + LocationData("6-6", "The Deep, Underground Maze: Red Coins", 0x3050D5, 0x41), + LocationData("6-6", "The Deep, Underground Maze: Flowers", 0x3050D6, 0x41), + LocationData("6-6", "The Deep, Underground Maze: Stars", 0x3050D7, 0x41), + LocationData("6-6", "The Deep, Underground Maze: Level Clear", 0x3050D8, 0x41), + + LocationData("6-7", "KEEP MOVING!!!!: Red Coins", 0x3050D9, 0x42), + LocationData("6-7", "KEEP MOVING!!!!: Flowers", 0x3050DA, 0x42), + LocationData("6-7", "KEEP MOVING!!!!: Stars", 0x3050DB, 0x42), + LocationData("6-7", "KEEP MOVING!!!!: Level Clear", 0x3050DC, 0x42), + + LocationData("6-8", "King Bowser's Castle: Red Coins", 0x3050DD, 0x43), + LocationData("6-8", "King Bowser's Castle: Flowers", 0x3050DE, 0x43), + LocationData("6-8", "King Bowser's Castle: Stars", 0x3050DF, 0x43) + ] + + if not world or world.options.extras_enabled: + location_table += [ + LocationData("1-Extra", "Poochy Ain't Stupid: Red Coins", 0x3050E0, 0x08), + LocationData("1-Extra", "Poochy Ain't Stupid: Flowers", 0x3050E1, 0x08), + LocationData("1-Extra", "Poochy Ain't Stupid: Stars", 0x3050E2, 0x08), + LocationData("1-Extra", "Poochy Ain't Stupid: Level Clear", 0x3050E3, 0x08), + + LocationData("2-Extra", "Hit That Switch!!: Red Coins", 0x3050E4, 0x14), + LocationData("2-Extra", "Hit That Switch!!: Flowers", 0x3050E5, 0x14), + LocationData("2-Extra", "Hit That Switch!!: Stars", 0x3050E6, 0x14), + LocationData("2-Extra", "Hit That Switch!!: Level Clear", 0x3050E7, 0x14), + + LocationData("3-Extra", "More Monkey Madness: Red Coins", 0x3050E8, 0x20), + LocationData("3-Extra", "More Monkey Madness: Flowers", 0x3050E9, 0x20), + LocationData("3-Extra", "More Monkey Madness: Stars", 0x3050EA, 0x20), + LocationData("3-Extra", "More Monkey Madness: Level Clear", 0x3050EB, 0x20), + + LocationData("4-Extra", "The Impossible? Maze: Red Coins", 0x3050EC, 0x2C), + LocationData("4-Extra", "The Impossible? Maze: Flowers", 0x3050ED, 0x2C), + LocationData("4-Extra", "The Impossible? Maze: Stars", 0x3050EE, 0x2C), + LocationData("4-Extra", "The Impossible? Maze: Level Clear", 0x3050EF, 0x2C), + + LocationData("5-Extra", "Kamek's Revenge: Red Coins", 0x3050F0, 0x38), + LocationData("5-Extra", "Kamek's Revenge: Flowers", 0x3050F1, 0x38), + LocationData("5-Extra", "Kamek's Revenge: Stars", 0x3050F2, 0x38), + LocationData("5-Extra", "Kamek's Revenge: Level Clear", 0x3050F3, 0x38), + + LocationData("6-Extra", "Castles - Masterpiece Set: Red Coins", 0x3050F4, 0x44), + LocationData("6-Extra", "Castles - Masterpiece Set: Flowers", 0x3050F5, 0x44), + LocationData("6-Extra", "Castles - Masterpiece Set: Stars", 0x3050F6, 0x44), + LocationData("6-Extra", "Castles - Masterpiece Set: Level Clear", 0x3050F7, 0x44), + ] + + if not world or world.options.minigame_checks in {MinigameChecks.option_bandit_games, MinigameChecks.option_both}: + location_table += [ + LocationData("1-3", "The Cave Of Chomp Rock: Bandit Game", 0x3050F8, 0x02, lambda state: logic._13Game(state)), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Bandit Game", 0x3050F9, 0x06, lambda state: logic._17Game(state)), + LocationData("2-1", "Visit Koopa And Para-Koopa: Bandit Game", 0x3050FA, 0x0C, lambda state: logic._21Game(state)), + LocationData("2-3", "What's Gusty Taste Like?: Bandit Game", 0x3050FB, 0x0E, lambda state: logic._23Game(state)), + LocationData("2-6", "The Cave Of The Mystery Maze: Bandit Game", 0x3050FC, 0x11, lambda state: logic._26Game(state)), + LocationData("2-7", "Lakitu's Wall: Bandit Game", 0x3050FD, 0x12, lambda state: logic._27Game(state)), + LocationData("3-2", "Jungle Rhythm...: Bandit Game", 0x3050FE, 0x19, lambda state: logic._32Game(state)), + LocationData("3-7", "Monkeys' Favorite Lake: Bandit Game", 0x3050FF, 0x1E, lambda state: logic._37Game(state)), + LocationData("4-2", "The Cave Of The Lakitus: Bandit Game", 0x305100, 0x25, lambda state: logic._42Game(state)), + LocationData("4-6", "Lake Shore Paradise: Bandit Game", 0x305101, 0x29, lambda state: logic._46Game(state)), + LocationData("4-7", "Ride Like The Wind: Bandit Game", 0x305102, 0x2A, lambda state: logic._47Game(state)), + LocationData("5-1", "BLIZZARD!!!: Bandit Game", 0x305103, 0x30, lambda state: logic._51Game(state)), + LocationData("6-1", "Scary Skeleton Goonies!: Bandit Game", 0x305104, 0x3C, lambda state: logic._61Game(state)), + LocationData("6-7", "KEEP MOVING!!!!: Bandit Game", 0x305105, 0x42, lambda state: logic._67Game(state)), + ] + + if not world or world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + location_table += [ + LocationData("1-Bonus", "Flip Cards: Victory", 0x305106, 0x09), + LocationData("2-Bonus", "Scratch And Match: Victory", 0x305107, 0x15), + LocationData("3-Bonus", "Drawing Lots: Victory", 0x305108, 0x21), + LocationData("4-Bonus", "Match Cards: Victory", 0x305109, 0x2D), + LocationData("5-Bonus", "Roulette: Victory", 0x30510A, 0x39), + LocationData("6-Bonus", "Slot Machine: Victory", 0x30510B, 0x45), + ] + if not world or world.options.goal == PlayerGoal.option_luigi_hunt: + location_table += [ + LocationData("Overworld", "Reconstituted Luigi", None, 0x00, lambda state: logic.reconstitute_luigi(state)), + ] + if not world or world.options.goal == PlayerGoal.option_bowser: + location_table += [ + LocationData("Bowser's Room", "King Bowser's Castle: Level Clear", None, 0x43, lambda state: logic._68Clear(state)), + ] + + return location_table diff --git a/worlds/yoshisisland/Options.py b/worlds/yoshisisland/Options.py new file mode 100644 index 000000000000..07d0436f6fde --- /dev/null +++ b/worlds/yoshisisland/Options.py @@ -0,0 +1,296 @@ +from dataclasses import dataclass +from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, PerGameCommonOptions + + +class ExtrasEnabled(Toggle): + """If enabled, the more difficult Extra stages will be added into logic. Otherwise, they will be inaccessible.""" + display_name = "Include Extra Stages" + + +class SplitExtras(Toggle): + """If enabled, Extra stages will be unlocked individually. Otherwise, there will be a single 'Extra Panels' item that unlocks all of them.""" + display_name = "Split Extra Stages" + + +class SplitBonus(Toggle): + """If enabled, Bonus Games will be unlocked individually. Otherwise, there will be a single 'Bonus Panels' item that unlocks all of them.""" + display_name = "Split Bonus Games" + + +class ObjectVis(Choice): + """This will determine the default visibility of objects revealed by the Magnifying Glass. + Strict Logic will expect the Secret Lens or a Magnifying Glass to interact with hidden clouds containing stars if they are not set to visible by default.""" + display_name = "Hidden Object Visibility" + option_none = 0 + option_coins_only = 1 + option_clouds_only = 2 + option_full = 3 + default = 1 + + +class SoftlockPrevention(DefaultOnToggle): + """If enabled, hold R + X to warp to the last used Middle Ring, or the start of the level if none have been activated.""" + display_name = "Softlock Prevention Code" + + +class StageLogic(Choice): + """This determines what logic mode the stages will use. + Strict: Best for casual players or those new to playing Yoshi's Island in AP. Level requirements won't expect anything too difficult of the player. + Loose: Recommended for veterans of the original game. Won't expect anything too difficult, but may expect unusual platforming or egg throws. + Expert: Logic may expect advanced knowledge or memorization of level layouts, as well as jumps the player may only have one chance to make without restarting.""" + display_name = "Stage Logic" + option_strict = 0 + option_loose = 1 + option_expert = 2 + # option_glitched = 3 + default = 0 + + +class ShuffleMiddleRings(Toggle): + """If enabled, Middle Rings will be added to the item pool.""" + display_name = "Shuffle Middle Rings" + + +class ShuffleSecretLens(Toggle): + """If enabled, the Secret Lens will be added to the item pool. + The Secret Lens will act as a permanent Magnifying Glass.""" + display_name = "Add Secret Lens" + + +class DisableAutoScrollers(Toggle): + """If enabled, will disable autoscrolling during levels, except during levels which cannot function otherwise.""" + display_name = "Disable Autoscrolling" + + +class ItemLogic(Toggle): + """This will enable logic to expect consumables to be used from the inventory in place of some major items. + Logic will expect you to have access to an Overworld bonus game, or a bandit game to get the necessary items. + Logic will NOT expect grinding end-of-level bonus games, or any inventory consumables received from checks. + Casual logic will only expect consumables from Overworld games; Loose and Expert may expect them from bandit games.""" + display_name = "Consumable Logic" + + +class MinigameChecks(Choice): + """This will set minigame victories to give Archipelago checks. + This will not randomize minigames amongst themselves, and is compatible with item logic. + Bonus games will be expected to be cleared from the Overworld, not the end of levels. + Additionally, 1-Up bonus games will accept any profit as a victory.""" + display_name = "Minigame Reward Checks" + option_none = 0 + option_bandit_games = 1 + option_bonus_games = 2 + option_both = 3 + default = 0 + + +class StartingWorld(Choice): + """This sets which world you start in. Other worlds can be accessed by receiving a Gate respective to that world.""" + display_name = "Starting World" + option_world_1 = 0 + option_world_2 = 1 + option_world_3 = 2 + option_world_4 = 3 + option_world_5 = 4 + option_world_6 = 5 + default = 0 + + +class StartingLives(Range): + """This sets the amount of lives Yoshi will have upon loading the game.""" + display_name = "Starting Life Count" + range_start = 1 + range_end = 999 + default = 3 + + +class PlayerGoal(Choice): + """This sets the goal. Bowser goal requires defeating Bowser at the end of 6-8, while Luigi Hunt requires collecting all required Luigi Pieces.""" + display_name = "Goal" + option_bowser = 0 + option_luigi_hunt = 1 + default = 0 + + +class LuigiPiecesReq(Range): + """This will set how many Luigi Pieces are required to trigger a victory.""" + display_name = "Luigi Pieces Required" + range_start = 1 + range_end = 100 + default = 25 + + +class LuigiPiecesAmt(Range): + """This will set how many Luigi Pieces are in the item pool. + If the number in the pool is lower than the number required, + the amount in the pool will be randomized, with the minimum being the amount required.""" + display_name = "Amount of Luigi Pieces" + range_start = 1 + range_end = 100 + default = 50 + + +class FinalLevelBosses(Range): + """This sets how many bosses need to be defeated to access 6-8. + You can check this in-game by pressing SELECT while in any level.""" + display_name = "Bosses Required for 6-8 Unlock" + range_start = 0 + range_end = 11 + default = 5 + + +class FinalBossBosses(Range): + """This sets how many bosses need to be defeated to access the boss of 6-8. + You can check this in-game by pressing SELECT while in any level.""" + display_name = "Bosses Required for 6-8 Clear" + range_start = 0 + range_end = 11 + default = 0 + + +class BowserDoor(Choice): + """This will set which route you take through 6-8. + Manual: You go through the door that you hit with an egg, as normal. + Doors: Route will be forced to be the door chosen here, regardless of which door you hit. + Gauntlet: You will be forced to go through all 4 routes in order before the final hallway.""" + display_name = "Bowser's Castle Doors" + option_manual = 0 + option_door_1 = 1 + option_door_2 = 2 + option_door_3 = 3 + option_door_4 = 4 + option_gauntlet = 5 + default = 0 + + +class BossShuffle(Toggle): + """This whill shuffle which boss each boss door will lead to. Each boss can only appear once, and Baby Bowser is left alone.""" + display_name = "Boss Shuffle" + + +class LevelShuffle(Choice): + """Disabled: All levels will appear in their normal location. + Bosses Guaranteed: All worlds will have a boss on -4 and -8. + Full: Worlds may have more than 2 or no bosses in them. + Regardless of the setting, 6-8 and Extra stages are not shuffled.""" + display_name = "Level Shuffle" + option_disabled = 0 + option_bosses_guaranteed = 1 + option_full = 2 + default = 0 + + +class YoshiColors(Choice): + """Sets the Yoshi color for each level. + Normal will use the vanilla colors. + Random order will generate a random order of colors that will be used in each level. The stage 1 color will be used for Extra stages, and 6-8. + Random color will generate a random color for each stage. + Singularity will use a single color defined under 'Singularity Yoshi Color' for use in all stages.""" + display_name = "Yoshi Colors" + option_normal = 0 + option_random_order = 1 + option_random_color = 2 + option_singularity = 3 + default = 0 + + +class SinguColor(Choice): + """Sets which color Yoshi will be if Yoshi Colors is set to singularity.""" + display_name = "Singularity Yoshi Color" + option_green = 0 + option_pink = 1 + option_cyan = 3 + option_yellow = 2 + option_purple = 4 + option_brown = 5 + option_red = 6 + option_blue = 7 + default = 0 + + +class BabySound(Choice): + """Change the sound that Baby Mario makes when not on Yoshi.""" + display_name = "Mario Sound Effect" + option_normal = 0 + option_disabled = 1 + option_random_sound_effect = 2 + default = 0 + + +class TrapsEnabled(Toggle): + """Will place traps into the item pool. + Traps have a variety of negative effects, and will only replace filler items.""" + display_name = "Traps Enabled" + + +class TrapPercent(Range): + """Percentage of the item pool that becomes replaced with traps.""" + display_name = "Trap Chance" + range_start = 0 + range_end = 100 + default = 10 + +# class EnableScrets(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class BackgroundColors(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class Foreground Colors(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class Music Shuffle(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class Star Loss Rate(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + + +@dataclass +class YoshisIslandOptions(PerGameCommonOptions): + starting_world: StartingWorld + starting_lives: StartingLives + goal: PlayerGoal + luigi_pieces_required: LuigiPiecesReq + luigi_pieces_in_pool: LuigiPiecesAmt + extras_enabled: ExtrasEnabled + minigame_checks: MinigameChecks + split_extras: SplitExtras + split_bonus: SplitBonus + hidden_object_visibility: ObjectVis + add_secretlens: ShuffleSecretLens + shuffle_midrings: ShuffleMiddleRings + stage_logic: StageLogic + item_logic: ItemLogic + disable_autoscroll: DisableAutoScrollers + softlock_prevention: SoftlockPrevention + castle_open_condition: FinalLevelBosses + castle_clear_condition: FinalBossBosses + bowser_door_mode: BowserDoor + level_shuffle: LevelShuffle + boss_shuffle: BossShuffle + yoshi_colors: YoshiColors + yoshi_singularity_color: SinguColor + baby_mario_sound: BabySound + traps_enabled: TrapsEnabled + trap_percent: TrapPercent + death_link: DeathLink diff --git a/worlds/yoshisisland/Regions.py b/worlds/yoshisisland/Regions.py new file mode 100644 index 000000000000..59e93cfe7979 --- /dev/null +++ b/worlds/yoshisisland/Regions.py @@ -0,0 +1,248 @@ +from typing import List, Dict, TYPE_CHECKING +from BaseClasses import Region, Location +from .Locations import LocationData +from .Options import MinigameChecks +from .level_logic import YoshiLogic +from .setup_bosses import BossReqs +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +class YoshisIslandLocation(Location): + game: str = "Yoshi's Island" + level_id: int + + def __init__(self, player: int, name: str = " ", address: int = None, parent=None, level_id: int = None): + super().__init__(player, name, address, parent) + self.level_id = level_id + + +def init_areas(world: "YoshisIslandWorld", locations: List[LocationData]) -> None: + multiworld = world.multiworld + player = world.player + logic = YoshiLogic(world) + + locations_per_region = get_locations_per_region(locations) + + regions = [ + create_region(world, player, locations_per_region, "Menu"), + create_region(world, player, locations_per_region, "Overworld"), + create_region(world, player, locations_per_region, "World 1"), + create_region(world, player, locations_per_region, "World 2"), + create_region(world, player, locations_per_region, "World 3"), + create_region(world, player, locations_per_region, "World 4"), + create_region(world, player, locations_per_region, "World 5"), + create_region(world, player, locations_per_region, "World 6"), + + create_region(world, player, locations_per_region, "1-1"), + create_region(world, player, locations_per_region, "1-2"), + create_region(world, player, locations_per_region, "1-3"), + create_region(world, player, locations_per_region, "1-4"), + create_region(world, player, locations_per_region, "Burt The Bashful's Boss Room"), + create_region(world, player, locations_per_region, "1-5"), + create_region(world, player, locations_per_region, "1-6"), + create_region(world, player, locations_per_region, "1-7"), + create_region(world, player, locations_per_region, "1-8"), + create_region(world, player, locations_per_region, "Salvo The Slime's Boss Room"), + + create_region(world, player, locations_per_region, "2-1"), + create_region(world, player, locations_per_region, "2-2"), + create_region(world, player, locations_per_region, "2-3"), + create_region(world, player, locations_per_region, "2-4"), + create_region(world, player, locations_per_region, "Bigger Boo's Boss Room"), + create_region(world, player, locations_per_region, "2-5"), + create_region(world, player, locations_per_region, "2-6"), + create_region(world, player, locations_per_region, "2-7"), + create_region(world, player, locations_per_region, "2-8"), + create_region(world, player, locations_per_region, "Roger The Ghost's Boss Room"), + + create_region(world, player, locations_per_region, "3-1"), + create_region(world, player, locations_per_region, "3-2"), + create_region(world, player, locations_per_region, "3-3"), + create_region(world, player, locations_per_region, "3-4"), + create_region(world, player, locations_per_region, "Prince Froggy's Boss Room"), + create_region(world, player, locations_per_region, "3-5"), + create_region(world, player, locations_per_region, "3-6"), + create_region(world, player, locations_per_region, "3-7"), + create_region(world, player, locations_per_region, "3-8"), + create_region(world, player, locations_per_region, "Naval Piranha's Boss Room"), + + create_region(world, player, locations_per_region, "4-1"), + create_region(world, player, locations_per_region, "4-2"), + create_region(world, player, locations_per_region, "4-3"), + create_region(world, player, locations_per_region, "4-4"), + create_region(world, player, locations_per_region, "Marching Milde's Boss Room"), + create_region(world, player, locations_per_region, "4-5"), + create_region(world, player, locations_per_region, "4-6"), + create_region(world, player, locations_per_region, "4-7"), + create_region(world, player, locations_per_region, "4-8"), + create_region(world, player, locations_per_region, "Hookbill The Koopa's Boss Room"), + + create_region(world, player, locations_per_region, "5-1"), + create_region(world, player, locations_per_region, "5-2"), + create_region(world, player, locations_per_region, "5-3"), + create_region(world, player, locations_per_region, "5-4"), + create_region(world, player, locations_per_region, "Sluggy The Unshaven's Boss Room"), + create_region(world, player, locations_per_region, "5-5"), + create_region(world, player, locations_per_region, "5-6"), + create_region(world, player, locations_per_region, "5-7"), + create_region(world, player, locations_per_region, "5-8"), + create_region(world, player, locations_per_region, "Raphael The Raven's Boss Room"), + + create_region(world, player, locations_per_region, "6-1"), + create_region(world, player, locations_per_region, "6-2"), + create_region(world, player, locations_per_region, "6-3"), + create_region(world, player, locations_per_region, "6-4"), + create_region(world, player, locations_per_region, "Tap-Tap The Red Nose's Boss Room"), + create_region(world, player, locations_per_region, "6-5"), + create_region(world, player, locations_per_region, "6-6"), + create_region(world, player, locations_per_region, "6-7"), + create_region(world, player, locations_per_region, "6-8"), + create_region(world, player, locations_per_region, "Bowser's Room"), + ] + + if world.options.extras_enabled: + regions.insert(68, create_region(world, player, locations_per_region, "6-Extra")) + regions.insert(58, create_region(world, player, locations_per_region, "5-Extra")) + regions.insert(48, create_region(world, player, locations_per_region, "4-Extra")) + regions.insert(38, create_region(world, player, locations_per_region, "3-Extra")) + regions.insert(28, create_region(world, player, locations_per_region, "2-Extra")) + regions.insert(18, create_region(world, player, locations_per_region, "1-Extra")) + + if world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + regions.insert(74, create_region(world, player, locations_per_region, "6-Bonus")) + regions.insert(63, create_region(world, player, locations_per_region, "5-Bonus")) + regions.insert(52, create_region(world, player, locations_per_region, "4-Bonus")) + regions.insert(41, create_region(world, player, locations_per_region, "3-Bonus")) + regions.insert(29, create_region(world, player, locations_per_region, "2-Bonus")) + regions.insert(19, create_region(world, player, locations_per_region, "1-Bonus")) + + multiworld.regions += regions + + connect_starting_region(world) + + bosses = BossReqs(world) + + multiworld.get_region("Overworld", player).add_exits( + ["World 1", "World 2", "World 3", "World 4", "World 5", "World 6"], + { + "World 1": lambda state: state.has("World 1 Gate", player), + "World 2": lambda state: state.has("World 2 Gate", player), + "World 3": lambda state: state.has("World 3 Gate", player), + "World 4": lambda state: state.has("World 4 Gate", player), + "World 5": lambda state: state.has("World 5 Gate", player), + "World 6": lambda state: state.has("World 6 Gate", player) + } + ) + + for cur_world in range(1, 7): + for cur_level in range(8): + if cur_world != 6 or cur_level != 7: + multiworld.get_region(f"World {cur_world}", player).add_exits( + [world.level_location_list[(cur_world - 1) * 8 + cur_level]] + ) + + multiworld.get_region("1-4", player).add_exits([world.boss_order[0]],{world.boss_order[0]: lambda state: logic._14Clear(state)}) + multiworld.get_region("1-8", player).add_exits([world.boss_order[1]],{world.boss_order[1]: lambda state: logic._18Clear(state)}) + multiworld.get_region("2-4", player).add_exits([world.boss_order[2]],{world.boss_order[2]: lambda state: logic._24Clear(state)}) + multiworld.get_region("2-8", player).add_exits([world.boss_order[3]],{world.boss_order[3]: lambda state: logic._28Clear(state)}) + multiworld.get_region("3-4", player).add_exits([world.boss_order[4]],{world.boss_order[4]: lambda state: logic._34Clear(state)}) + multiworld.get_region("3-8", player).add_exits([world.boss_order[5]],{world.boss_order[5]: lambda state: logic._38Clear(state)}) + multiworld.get_region("4-4", player).add_exits([world.boss_order[6]],{world.boss_order[6]: lambda state: logic._44Clear(state)}) + multiworld.get_region("4-8", player).add_exits([world.boss_order[7]],{world.boss_order[7]: lambda state: logic._48Clear(state)}) + multiworld.get_region("5-4", player).add_exits([world.boss_order[8]],{world.boss_order[8]: lambda state: logic._54Clear(state)}) + multiworld.get_region("5-8", player).add_exits([world.boss_order[9]],{world.boss_order[9]: lambda state: logic._58Clear(state)}) + multiworld.get_region("World 6", player).add_exits(["6-8"],{"6-8": lambda state: bosses.castle_access(state)}) + multiworld.get_region("6-4", player).add_exits([world.boss_order[10]],{world.boss_order[10]: lambda state: logic._64Clear(state)}) + multiworld.get_region("6-8", player).add_exits(["Bowser's Room"],{"Bowser's Room": lambda state: bosses.castle_clear(state)}) + + if world.options.extras_enabled: + multiworld.get_region("World 1", player).add_exits( + ["1-Extra"], + {"1-Extra": lambda state: state.has_any({"Extra Panels", "Extra 1"}, player)} + ) + multiworld.get_region("World 2", player).add_exits( + ["2-Extra"], + {"2-Extra": lambda state: state.has_any({"Extra Panels", "Extra 2"}, player)} + ) + multiworld.get_region( + "World 3", player).add_exits(["3-Extra"], + {"3-Extra": lambda state: state.has_any({"Extra Panels", "Extra 3"}, player)} + ) + multiworld.get_region("World 4", player).add_exits( + ["4-Extra"], + {"4-Extra": lambda state: state.has_any({"Extra Panels", "Extra 4"}, player)} + ) + multiworld.get_region("World 5", player).add_exits( + ["5-Extra"], + {"5-Extra": lambda state: state.has_any({"Extra Panels", "Extra 5"}, player)} + ) + multiworld.get_region("World 6", player).add_exits( + ["6-Extra"], + {"6-Extra": lambda state: state.has_any({"Extra Panels", "Extra 6"}, player)} + ) + + if world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + multiworld.get_region("World 1", player).add_exits( + ["1-Bonus"], + {"1-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 1"}, player)} + ) + multiworld.get_region("World 2", player).add_exits( + ["2-Bonus"], + {"2-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 2"}, player)} + ) + multiworld.get_region("World 3", player).add_exits( + ["3-Bonus"], + {"3-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 3"}, player)} + ) + multiworld.get_region("World 4", player).add_exits( + ["4-Bonus"], + {"4-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 4"}, player)} + ) + multiworld.get_region("World 5", player).add_exits( + ["5-Bonus"], + {"5-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 5"}, player)} + ) + multiworld.get_region("World 6", player).add_exits( + ["6-Bonus"], + {"6-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 6"}, player)} + ) + + +def create_location(player: int, location_data: LocationData, region: Region) -> Location: + location = YoshisIslandLocation(player, location_data.name, location_data.code, region) + location.access_rule = location_data.rule + location.level_id = location_data.LevelID + + return location + + +def create_region(world: "YoshisIslandWorld", player: int, locations_per_region: Dict[str, List[LocationData]], name: str) -> Region: + region = Region(name, player, world.multiworld) + + if name in locations_per_region: + for location_data in locations_per_region[name]: + location = create_location(player, location_data, region) + region.locations.append(location) + + return region + +def connect_starting_region(world: "YoshisIslandWorld") -> None: + multiworld = world.multiworld + player = world.player + menu = multiworld.get_region("Menu", player) + world_main = multiworld.get_region("Overworld", player) + + starting_region = multiworld.get_region(f"World {world.options.starting_world + 1}", player) + + menu.connect(world_main, "Start Game") + world_main.connect(starting_region, "Overworld") + + +def get_locations_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]: + per_region: Dict[str, List[LocationData]] = {} + + for location in locations: + per_region.setdefault(location.region, []).append(location) + + return per_region diff --git a/worlds/yoshisisland/Rom.py b/worlds/yoshisisland/Rom.py new file mode 100644 index 000000000000..0943ba82514c --- /dev/null +++ b/worlds/yoshisisland/Rom.py @@ -0,0 +1,1230 @@ +import hashlib +import os +import Utils +from worlds.Files import APDeltaPatch +from settings import get_settings +from typing import TYPE_CHECKING, Collection, SupportsIndex + +from .Options import YoshiColors, BowserDoor, PlayerGoal, MinigameChecks + +if TYPE_CHECKING: + from . import YoshisIslandWorld +USHASH = "cb472164c5a71ccd3739963390ec6a50" + +item_values = { + 0x302050: [0x1467, 0x01], # ! Switch + 0x302051: [0x1467, 0x02], # Dashed Platform + 0x302052: [0x1467, 0x03], # Dashed Stairs + 0x302053: [0x1467, 0x04], # Beanstalk + 0x302054: [0x1467, 0x05], # Helicopter + 0x302059: [0x1467, 0x06], # Mole Tank + 0x302068: [0x1467, 0x07], # Train + 0x30205E: [0x1467, 0x08], # Car + 0x302063: [0x1467, 0x09], # Submarine + 0x302055: [0x1467, 0x0A], # Spring Ball + 0x302056: [0x1467, 0x0B], # Large Spring Ball + 0x302057: [0x1467, 0x0C], # Arrow Wheel + 0x302058: [0x1467, 0x0D], # Vanishing Arrow Wheel + 0x30205A: [0x1467, 0x0E], # Watermelon + 0x30205B: [0x1467, 0x0F], # Ice Melon + 0x30205C: [0x1467, 0x10], # Fire Melon + 0x30205D: [0x1467, 0x11], # Super Star + 0x30205F: [0x1467, 0x12], # Flashing Eggs + 0x302060: [0x1467, 0x13], # Giant Eggs + 0x302061: [0x1467, 0x14], # Egg Launcher + 0x302062: [0x1467, 0x15], # Egg Plant + 0x302064: [0x1467, 0x16], # Chomp Rock + 0x302065: [0x1467, 0x17], # Poochy + 0x302066: [0x1467, 0x18], # Platform Ghost + 0x302067: [0x1467, 0x19], # Skis + 0x302069: [0x1467, 0x1A], # Key + 0x30206A: [0x1467, 0x1B], # Middle Ring + 0x30206B: [0x1467, 0x1C], # Bucket + 0x30206C: [0x1467, 0x1D], # Tulip + 0x302081: [0x1467, 0x1E], # Secret Lens + + 0x30206D: [0x1467, 0x1F], # Egg Capacity Upgrade + + 0x30206E: [0x1467, 0x20], # World 1 Gate + 0x30206F: [0x1467, 0x21], # World 2 Gate + 0x302070: [0x1467, 0x22], # World 3 Gate + 0x302071: [0x1467, 0x23], # World 4 Gate + 0x302072: [0x1467, 0x24], # World 5 Gate + 0x302073: [0x1467, 0x25], # World 6 Gate + + 0x302074: [0x1467, 0x26], # Extra 1 + 0x302075: [0x1467, 0x27], # Extra 2 + 0x302076: [0x1467, 0x28], # Extra 3 + 0x302077: [0x1467, 0x29], # Extra 4 + 0x302078: [0x1467, 0x2A], # Extra 5 + 0x302079: [0x1467, 0x2B], # Extra 6 + 0x30207A: [0x1467, 0x2C], # Extra Panels + + 0x30207B: [0x1467, 0x2D], # Bonus 1 + 0x30207C: [0x1467, 0x2E], # Bonus 2 + 0x30207D: [0x1467, 0x2F], # Bonus 3 + 0x30207E: [0x1467, 0x30], # Bonus 4 + 0x30207F: [0x1467, 0x31], # Bonus 5 + 0x302080: [0x1467, 0x32], # Bonus 6 + 0x302082: [0x1467, 0x33], # Bonus Panels + + 0x302083: [0x1467, 0x34], # Anytime Egg + 0x302084: [0x1467, 0x35], # Anywhere Pow + 0x302085: [0x1467, 0x36], # Cloud + 0x302086: [0x1467, 0x37], # Pocket Melon + 0x302088: [0x1467, 0x38], # Ice Melon + 0x302087: [0x1467, 0x39], # Fire Melon + 0x302089: [0x1467, 0x3A], # Magnifying Glass + 0x30208A: [0x1467, 0x3B], # 10 Stars + 0x30208B: [0x1467, 0x3C], # 20 Stars + + 0x30208C: [0x1467, 0x3D], # 1up + 0x30208D: [0x1467, 0x3E], # 2up + 0x30208E: [0x1467, 0x3F], # 3up + 0x30208F: [0x1467, 0x40], # 10up + + 0x302090: [0x1467, 0x41], # Fuzzy Trap + 0x302093: [0x1467, 0x42], # Freeze Trap + 0x302091: [0x1467, 0x43], # Reverse Trap + 0x302092: [0x1467, 0x44], # Dark Trap + 0x302094: [0x1467, 0x00], # Boss clear, local handling + + 0x302095: [0x1467, 0x45] # Luigi Piece + +} + +location_table = { + # 1-1 + 0x305020: [0x146D, 0], # Red Coins + 0x305021: [0x146D, 1], # Flowers + 0x305022: [0x146D, 2], # Stars + 0x305023: [0x146D, 3], # Level Clear + # 1-2 + 0x305024: [0x146E, 0], + 0x305025: [0x146E, 1], + 0x305026: [0x146E, 2], + 0x305027: [0x146E, 3], + # 1-3 + 0x305028: [0x146F, 0], + 0x305029: [0x146F, 1], + 0x30502A: [0x146F, 2], + 0x30502B: [0x146F, 3], + 0x3050F8: [0x146F, 4], + # 1-4 + 0x30502C: [0x1470, 0], + 0x30502D: [0x1470, 1], + 0x30502E: [0x1470, 2], + 0x30502F: [0x1470, 3], + # 1-5 + 0x305031: [0x1471, 0], + 0x305032: [0x1471, 1], + 0x305033: [0x1471, 2], + 0x305034: [0x1471, 3], + # 1-6 + 0x305035: [0x1472, 0], + 0x305036: [0x1472, 1], + 0x305037: [0x1472, 2], + 0x305038: [0x1472, 3], + # 1-7 + 0x305039: [0x1473, 0], + 0x30503A: [0x1473, 1], + 0x30503B: [0x1473, 2], + 0x30503C: [0x1473, 3], + 0x3050F9: [0x1473, 4], + # 1-8 + 0x30503D: [0x1474, 0], + 0x30503E: [0x1474, 1], + 0x30503F: [0x1474, 2], + 0x305040: [0x1474, 3], + # 1-E + 0x3050E0: [0x1475, 0], + 0x3050E1: [0x1475, 1], + 0x3050E2: [0x1475, 2], + 0x3050E3: [0x1475, 3], + # 1-B + 0x305106: [0x1476, 4], + ###################### + # 2-1 + 0x305041: [0x1479, 0], + 0x305042: [0x1479, 1], + 0x305043: [0x1479, 2], + 0x305044: [0x1479, 3], + 0x3050FA: [0x1479, 4], + # 2-2 + 0x305045: [0x147A, 0], + 0x305046: [0x147A, 1], + 0x305047: [0x147A, 2], + 0x305048: [0x147A, 3], + # 2-3 + 0x305049: [0x147B, 0], + 0x30504A: [0x147B, 1], + 0x30504B: [0x147B, 2], + 0x30504C: [0x147B, 3], + 0x3050FB: [0x147B, 4], + # 2-4 + 0x30504D: [0x147C, 0], + 0x30504E: [0x147C, 1], + 0x30504F: [0x147C, 2], + 0x305050: [0x147C, 3], + # 2-5 + 0x305051: [0x147D, 0], + 0x305052: [0x147D, 1], + 0x305053: [0x147D, 2], + 0x305054: [0x147D, 3], + # 2-6 + 0x305055: [0x147E, 0], + 0x305056: [0x147E, 1], + 0x305057: [0x147E, 2], + 0x305058: [0x147E, 3], + 0x3050FC: [0x147E, 4], + # 2-7 + 0x305059: [0x147F, 0], + 0x30505A: [0x147F, 1], + 0x30505B: [0x147F, 2], + 0x30505C: [0x147F, 3], + 0x3050FD: [0x147F, 4], + # 2-8 + 0x30505D: [0x1480, 0], + 0x30505E: [0x1480, 1], + 0x30505F: [0x1480, 2], + 0x305060: [0x1480, 3], + # 2-E + 0x3050E4: [0x1481, 0], + 0x3050E5: [0x1481, 1], + 0x3050E6: [0x1481, 2], + 0x3050E7: [0x1481, 3], + # 2-B + 0x305107: [0x1482, 4], + ###################### + # 3-1 + 0x305061: [0x1485, 0], + 0x305062: [0x1485, 1], + 0x305063: [0x1485, 2], + 0x305064: [0x1485, 3], + # 3-2 + 0x305065: [0x1486, 0], + 0x305066: [0x1486, 1], + 0x305067: [0x1486, 2], + 0x305068: [0x1486, 3], + 0x3050FE: [0x1486, 4], + # 3-3 + 0x305069: [0x1487, 0], + 0x30506A: [0x1487, 1], + 0x30506B: [0x1487, 2], + 0x30506C: [0x1487, 3], + # 3-4 + 0x30506D: [0x1488, 0], + 0x30506E: [0x1488, 1], + 0x30506F: [0x1488, 2], + 0x305070: [0x1488, 3], + # 3-5 + 0x305071: [0x1489, 0], + 0x305072: [0x1489, 1], + 0x305073: [0x1489, 2], + 0x305074: [0x1489, 3], + # 3-6 + 0x305075: [0x148A, 0], + 0x305076: [0x148A, 1], + 0x305077: [0x148A, 2], + 0x305078: [0x148A, 3], + # 3-7 + 0x305079: [0x148B, 0], + 0x30507A: [0x148B, 1], + 0x30507B: [0x148B, 2], + 0x30507C: [0x148B, 3], + 0x3050FF: [0x148B, 4], + # 3-8 + 0x30507D: [0x148C, 0], + 0x30507E: [0x148C, 1], + 0x30507F: [0x148C, 2], + 0x305080: [0x148C, 3], + # 3-E + 0x3050E8: [0x148D, 0], + 0x3050E9: [0x148D, 1], + 0x3050EA: [0x148D, 2], + 0x3050EB: [0x148D, 3], + # 3-B + 0x305108: [0x148E, 4], + ###################### + # 4-1 + 0x305081: [0x1491, 0], + 0x305082: [0x1491, 1], + 0x305083: [0x1491, 2], + 0x305084: [0x1491, 3], + # 4-2 + 0x305085: [0x1492, 0], + 0x305086: [0x1492, 1], + 0x305087: [0x1492, 2], + 0x305088: [0x1492, 3], + 0x305100: [0x1492, 4], + # 4-3 + 0x305089: [0x1493, 0], + 0x30508A: [0x1493, 1], + 0x30508B: [0x1493, 2], + 0x30508C: [0x1493, 3], + # 4-4 + 0x30508D: [0x1494, 0], + 0x30508E: [0x1494, 1], + 0x30508F: [0x1494, 2], + 0x305090: [0x1494, 3], + # 4-5 + 0x305091: [0x1495, 0], + 0x305092: [0x1495, 1], + 0x305093: [0x1495, 2], + 0x305094: [0x1495, 3], + # 4-6 + 0x305095: [0x1496, 0], + 0x305096: [0x1496, 1], + 0x305097: [0x1496, 2], + 0x305098: [0x1496, 3], + 0x305101: [0x1496, 4], + # 4-7 + 0x305099: [0x1497, 0], + 0x30509A: [0x1497, 1], + 0x30509B: [0x1497, 2], + 0x30509C: [0x1497, 3], + 0x305102: [0x1497, 4], + # 4-8 + 0x30509D: [0x1498, 0], + 0x30509E: [0x1498, 1], + 0x30509F: [0x1498, 2], + 0x3050A0: [0x1498, 3], + # 4-E + 0x3050EC: [0x1499, 0], + 0x3050ED: [0x1499, 1], + 0x3050EE: [0x1499, 2], + 0x3050EF: [0x1499, 3], + # 4-B + 0x305109: [0x149A, 4], + ###################### + # 5-1 + 0x3050A1: [0x149D, 0], + 0x3050A2: [0x149D, 1], + 0x3050A3: [0x149D, 2], + 0x3050A4: [0x149D, 3], + 0x305103: [0x149D, 4], + # 5-2 + 0x3050A5: [0x149E, 0], + 0x3050A6: [0x149E, 1], + 0x3050A7: [0x149E, 2], + 0x3050A8: [0x149E, 3], + # 5-3 + 0x3050A9: [0x149F, 0], + 0x3050AA: [0x149F, 1], + 0x3050AB: [0x149F, 2], + 0x3050AC: [0x149F, 3], + # 5-4 + 0x3050AD: [0x14A0, 0], + 0x3050AE: [0x14A0, 1], + 0x3050AF: [0x14A0, 2], + 0x3050B0: [0x14A0, 3], + # 5-5 + 0x3050B1: [0x14A1, 0], + 0x3050B2: [0x14A1, 1], + 0x3050B3: [0x14A1, 2], + 0x3050B4: [0x14A1, 3], + # 5-6 + 0x3050B5: [0x14A2, 0], + 0x3050B6: [0x14A2, 1], + 0x3050B7: [0x14A2, 2], + 0x3050B8: [0x14A2, 3], + # 5-7 + 0x3050B9: [0x14A3, 0], + 0x3050BA: [0x14A3, 1], + 0x3050BB: [0x14A3, 2], + 0x3050BC: [0x14A3, 3], + # 5-8 + 0x3050BD: [0x14A4, 0], + 0x3050BE: [0x14A4, 1], + 0x3050BF: [0x14A4, 2], + 0x3050C0: [0x14A4, 3], + # 5-E + 0x3050F0: [0x14A5, 0], + 0x3050F1: [0x14A5, 1], + 0x3050F2: [0x14A5, 2], + 0x3050F3: [0x14A5, 3], + # 5-B + 0x30510A: [0x14A6, 4], + ####################### + # 6-1 + 0x3050C1: [0x14A9, 0], + 0x3050C2: [0x14A9, 1], + 0x3050C3: [0x14A9, 2], + 0x3050C4: [0x14A9, 3], + 0x305104: [0x14A9, 4], + # 6-2 + 0x3050C5: [0x14AA, 0], + 0x3050C6: [0x14AA, 1], + 0x3050C7: [0x14AA, 2], + 0x3050C8: [0x14AA, 3], + # 6-3 + 0x3050C9: [0x14AB, 0], + 0x3050CA: [0x14AB, 1], + 0x3050CB: [0x14AB, 2], + 0x3050CC: [0x14AB, 3], + # 6-4 + 0x3050CD: [0x14AC, 0], + 0x3050CE: [0x14AC, 1], + 0x3050CF: [0x14AC, 2], + 0x3050D0: [0x14AC, 3], + # 6-5 + 0x3050D1: [0x14AD, 0], + 0x3050D2: [0x14AD, 1], + 0x3050D3: [0x14AD, 2], + 0x3050D4: [0x14AD, 3], + # 6-6 + 0x3050D5: [0x14AE, 0], + 0x3050D6: [0x14AE, 1], + 0x3050D7: [0x14AE, 2], + 0x3050D8: [0x14AE, 3], + # 6-7 + 0x3050D9: [0x14AF, 0], + 0x3050DA: [0x14AF, 1], + 0x3050DB: [0x14AF, 2], + 0x3050DC: [0x14AF, 3], + 0x305105: [0x14AF, 4], + # 6-8 + 0x3050DD: [0x14B0, 0], + 0x3050DE: [0x14B0, 1], + 0x3050DF: [0x14B0, 2], + # 6-E + 0x3050F4: [0x14B1, 0], + 0x3050F5: [0x14B1, 1], + 0x3050F6: [0x14B1, 2], + 0x3050F7: [0x14B1, 3], + # 6-B + 0x30510B: [0x14B2, 4] +} + +class LocalRom: + + def __init__(self, file: str) -> None: + self.name = None + self.hash = hash + self.orig_buffer = None + + with open(file, "rb") as stream: + self.buffer = Utils.read_snes_rom(stream) + + def read_bit(self, address: int, bit_number: int) -> bool: + bitflag = 1 << bit_number + return (self.buffer[address] & bitflag) != 0 + + def read_byte(self, address: int) -> int: + return self.buffer[address] + + def read_bytes(self, startaddress: int, length: int) -> bytearray: + return self.buffer[startaddress:startaddress + length] + + def write_byte(self, address: int, value: int) -> None: + self.buffer[address] = value + + def write_bytes(self, startaddress: int, values: Collection[SupportsIndex]) -> None: + self.buffer[startaddress:startaddress + len(values)] = values + + def write_to_file(self, file: str) -> None: + with open(file, "wb") as outfile: + outfile.write(self.buffer) + + def read_from_file(self, file: str) -> None: + with open(file, "rb") as stream: + self.buffer = bytearray(stream.read()) + +def handle_items(rom: LocalRom) -> None: + rom.write_bytes(0x0077B0, bytearray([0xE2, 0x20, 0xAD, 0x40, 0x14, 0xC2, 0x20, 0xF0, 0x08, 0xBD, 0x82, 0x71, 0x18, 0x5C, 0x3B, 0xB6])) + rom.write_bytes(0x0077C0, bytearray([0x0E, 0x5C, 0x97, 0xB6, 0x0E, 0xA0, 0xFF, 0xAD, 0x74, 0x79, 0x29, 0x01, 0x00, 0xD0, 0x02, 0xA0])) + rom.write_bytes(0x0077D0, bytearray([0x05, 0x98, 0x9D, 0xA2, 0x74, 0x6B, 0xE2, 0x20, 0xBD, 0x60, 0x73, 0xDA, 0xC2, 0x20, 0xA2, 0x00])) + rom.write_bytes(0x0077E0, bytearray([0xDF, 0x70, 0xAF, 0x09, 0xF0, 0x08, 0xE8, 0xE8, 0xE0, 0x08, 0xF0, 0x23, 0x80, 0xF2, 0xE2, 0x20])) + rom.write_bytes(0x0077F0, bytearray([0x8A, 0x4A, 0xAA, 0xBF, 0x78, 0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14, 0xC2, 0x20, 0xF0, 0x08, 0xFA])) + rom.write_bytes(0x007800, bytearray([0xA0, 0x05, 0x98, 0x9D, 0xA2, 0x74, 0x60, 0xFA, 0x22, 0xC5, 0xF7, 0x00, 0xEA, 0xEA, 0x60, 0xFA])) + rom.write_bytes(0x007810, bytearray([0x60, 0x22, 0x23, 0xAF, 0x03, 0x20, 0xD6, 0xF7, 0x6B, 0x20, 0x2F, 0xF8, 0xE2, 0x20, 0xC9, 0x00])) + rom.write_bytes(0x007820, bytearray([0xD0, 0x03, 0xC2, 0x20, 0x6B, 0xC2, 0x20, 0xBD, 0x60, 0x73, 0x38, 0x5C, 0xB1, 0xC9, 0x03, 0xDA])) + rom.write_bytes(0x007830, bytearray([0xBD, 0x60, 0x73, 0xA2, 0x00, 0xDF, 0x7C, 0xAF, 0x09, 0xF0, 0x08, 0xE8, 0xE8, 0xE0, 0x0A, 0xF0])) + rom.write_bytes(0x007840, bytearray([0x13, 0x80, 0xF2, 0xE2, 0x20, 0x8A, 0x4A, 0xAA, 0xBF, 0x86, 0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14])) + rom.write_bytes(0x007850, bytearray([0xFA, 0xC2, 0x20, 0x60, 0xA9, 0x01, 0x00, 0xFA, 0x60, 0x20, 0x2F, 0xF8, 0xE2, 0x20, 0xC9, 0x00])) + rom.write_bytes(0x007860, bytearray([0xC2, 0x20, 0xD0, 0x06, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0xCF, 0xF7, 0x00, 0xA5, 0x14])) + rom.write_bytes(0x007870, bytearray([0x29, 0x0F, 0x00, 0x5C, 0x9A, 0xC9, 0x03, 0x5A, 0xE2, 0x10, 0x20, 0x2F, 0xF8, 0xC2, 0x10, 0x7A])) + rom.write_bytes(0x007880, bytearray([0xE2, 0x20, 0xC9, 0x00, 0xC2, 0x20, 0xD0, 0x08, 0xAD, 0x74, 0x79, 0x29, 0x01, 0x00, 0xF0, 0x04])) + rom.write_bytes(0x007890, bytearray([0x22, 0x3C, 0xAA, 0x03, 0xE2, 0x10, 0x5C, 0x47, 0xC9, 0x03, 0x22, 0x23, 0xAF, 0x03, 0xBD, 0x60])) + rom.write_bytes(0x0078A0, bytearray([0x73, 0xC9, 0x6F, 0x00, 0xF0, 0x07, 0xE2, 0x20, 0xAD, 0x4A, 0x14, 0x80, 0x05, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x0078B0, bytearray([0x49, 0x14, 0xC2, 0x20, 0xD0, 0x06, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0xCF, 0xF7, 0x00])) + rom.write_bytes(0x0078C0, bytearray([0x5C, 0x2D, 0x83, 0x05, 0xBD, 0x60, 0x73, 0xC9, 0x6F, 0x00, 0xF0, 0x07, 0xE2, 0x20, 0xAD, 0x4A])) + rom.write_bytes(0x0078D0, bytearray([0x14, 0x80, 0x05, 0xE2, 0x20, 0xAD, 0x49, 0x14, 0xC2, 0x20, 0xD0, 0x04, 0x5C, 0xA0, 0x83, 0x05])) + rom.write_bytes(0x0078E0, bytearray([0xAD, 0xAC, 0x60, 0x0D, 0xAE, 0x60, 0x5C, 0x84, 0x83, 0x05, 0x22, 0x52, 0xAA, 0x03, 0xBD, 0x60])) + rom.write_bytes(0x0078F0, bytearray([0x73, 0xC9, 0x1E, 0x01, 0xE2, 0x20, 0xF0, 0x05, 0xAD, 0x4C, 0x14, 0x80, 0x03, 0xAD, 0x4B, 0x14])) + rom.write_bytes(0x007900, bytearray([0xEA, 0xC2, 0x20, 0xF0, 0x08, 0x22, 0xCF, 0xF7, 0x00, 0x5C, 0xA6, 0xF0, 0x05, 0x22, 0xC5, 0xF7])) + rom.write_bytes(0x007910, bytearray([0x00, 0x5C, 0xA6, 0xF0, 0x05, 0xE2, 0x20, 0xAD, 0x1C, 0x01, 0xC9, 0x0E, 0xC2, 0x20, 0xF0, 0x18])) + rom.write_bytes(0x007920, bytearray([0x20, 0x59, 0xF9, 0xE2, 0x20, 0xC9, 0x00, 0xF0, 0x04, 0xA9, 0x10, 0x80, 0x2A, 0xA9, 0x02, 0x9D])) + rom.write_bytes(0x007930, bytearray([0x00, 0x6F, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0xA2, 0x0A, 0xA9, 0x2F, 0xCE, 0x5C, 0x22, 0x80])) + rom.write_bytes(0x007940, bytearray([0x04, 0x20, 0x59, 0xF9, 0xE2, 0x20, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x0A, 0xAD, 0x0E, 0x30, 0x29])) + rom.write_bytes(0x007950, bytearray([0x03, 0x00, 0x5C, 0x2E, 0x80, 0x04, 0x6B, 0x80, 0xD6, 0xDA, 0xBD, 0x60, 0x73, 0xA2, 0x00, 0xDF])) + rom.write_bytes(0x007960, bytearray([0x8B, 0xAF, 0x09, 0xF0, 0x04, 0xE8, 0xE8, 0x80, 0xF6, 0xE2, 0x20, 0x8A, 0x4A, 0xAA, 0xBF, 0x91])) + rom.write_bytes(0x007970, bytearray([0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14, 0xFA, 0xC2, 0x20, 0x60, 0x22, 0x2E, 0xAA, 0x03, 0xE2, 0x20])) + rom.write_bytes(0x007980, bytearray([0xAD, 0x50, 0x14, 0xC2, 0x20, 0xD0, 0x06, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0xCF, 0xF7])) + rom.write_bytes(0x007990, bytearray([0x00, 0x5C, 0x05, 0x99, 0x02, 0x69, 0x20, 0x00, 0xC9, 0x20, 0x01, 0xB0, 0x0D, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x0079A0, bytearray([0x50, 0x14, 0xC2, 0x20, 0xF0, 0x04, 0x5C, 0x3E, 0x99, 0x02, 0x5C, 0x8C, 0x99, 0x02, 0x22, 0x23])) + rom.write_bytes(0x0079B0, bytearray([0xAF, 0x03, 0xE2, 0x20, 0xAD, 0x1C, 0x01, 0xC9, 0x02, 0xC2, 0x20, 0xD0, 0x18, 0x20, 0x59, 0xF9])) + rom.write_bytes(0x0079C0, bytearray([0xE2, 0x20, 0xC9, 0x00, 0xD0, 0x13, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0xE2, 0x20, 0xA9, 0x02])) + rom.write_bytes(0x0079D0, bytearray([0x9D, 0x00, 0x6F, 0xC2, 0x20, 0x5C, 0x35, 0x80, 0x04, 0xC2, 0x20, 0x22, 0xCF, 0xF7, 0x00, 0x80])) + rom.write_bytes(0x0079E0, bytearray([0xF2, 0xE2, 0x20, 0xAD, 0x4E, 0x14, 0xC2, 0x20, 0xF0, 0x07, 0xA9, 0x14, 0x00, 0x5C, 0x9E, 0xF1])) + rom.write_bytes(0x0079F0, bytearray([0x07, 0xA9, 0x0E, 0x00, 0x80, 0xF7, 0xBD, 0x60, 0x73, 0xDA, 0xA2, 0x00, 0xDF, 0x94, 0xAF, 0x09])) + rom.write_bytes(0x007A00, bytearray([0xF0, 0x11, 0xE0, 0x08, 0xF0, 0x04, 0xE8, 0xE8, 0x80, 0xF2, 0xFA, 0x22, 0x57, 0xF9, 0x0C, 0x5C])) + rom.write_bytes(0x007A10, bytearray([0xBD, 0xBE, 0x03, 0x8A, 0x4A, 0xE2, 0x20, 0xAA, 0xBF, 0x9E, 0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14])) + rom.write_bytes(0x007A20, bytearray([0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x02, 0x80, 0xE2, 0x4C, 0x61, 0xFF, 0x00, 0x9D, 0x00, 0x6F, 0x74])) + rom.write_bytes(0x007A30, bytearray([0x78, 0x74, 0x18, 0x74, 0x76, 0x9E, 0x36, 0x7A, 0x9E, 0x38, 0x7A, 0x9E, 0x38, 0x7D, 0xBC, 0xC2])) + rom.write_bytes(0x007A40, bytearray([0x77, 0xB9, 0xB5, 0xBE, 0x9D, 0x20, 0x72, 0xA9, 0x00, 0xFC, 0x9D, 0x22, 0x72, 0xA9, 0x40, 0x00])) + rom.write_bytes(0x007A50, bytearray([0x9D, 0x42, 0x75, 0xA9, 0x90, 0x00, 0x22, 0xD2, 0x85, 0x00, 0x6B, 0x5A, 0xE2, 0x20, 0xAD, 0x51])) + rom.write_bytes(0x007A60, bytearray([0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x0D, 0x22, 0xCF, 0xF7, 0x00, 0x7A, 0x9B, 0xAD, 0x30, 0x00])) + rom.write_bytes(0x007A70, bytearray([0x5C, 0x62, 0xB7, 0x03, 0x22, 0xC5, 0xF7, 0x00, 0x7A, 0x9B, 0xA9, 0x03, 0x00, 0x5C, 0x62, 0xB7])) + rom.write_bytes(0x007A80, bytearray([0x03, 0x22, 0x23, 0xAF, 0x03, 0xE2, 0x20, 0xAD, 0x53, 0x14, 0xF0, 0x07, 0xC2, 0x20, 0x22, 0xCF])) + rom.write_bytes(0x007A90, bytearray([0xF7, 0x00, 0x6B, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0x6B, 0xE2, 0x20, 0xAD, 0x53, 0x14, 0xF0])) + rom.write_bytes(0x007AA0, bytearray([0x07, 0xC2, 0x20, 0x22, 0x78, 0xBA, 0x07, 0x6B, 0xC2, 0x20, 0x6B, 0xC9, 0x06, 0x00, 0xB0, 0x0F])) + rom.write_bytes(0x007AB0, bytearray([0xE2, 0x20, 0xAD, 0x54, 0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x04, 0x5C, 0x94, 0x81, 0x07, 0x5C])) + rom.write_bytes(0x007AC0, bytearray([0xFB, 0x81, 0x07, 0x22, 0x23, 0xAF, 0x03, 0xE2, 0x20, 0xAD, 0x54, 0x14, 0xC2, 0x20, 0xF0, 0x08])) + rom.write_bytes(0x007AD0, bytearray([0x22, 0xCF, 0xF7, 0x00, 0x5C, 0xF7, 0x80, 0x07, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0xF7, 0x80, 0x07])) + rom.write_bytes(0x007AE0, bytearray([0x5A, 0xE2, 0x20, 0xAD, 0x55, 0x14, 0xC2, 0x20, 0xF0, 0x06, 0x22, 0xCF, 0xF7, 0x00, 0x80, 0x06])) + rom.write_bytes(0x007AF0, bytearray([0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0x65, 0xC3, 0x0E, 0x7A, 0x5C, 0xFA, 0xBE, 0x0E, 0xE2])) + rom.write_bytes(0x007B00, bytearray([0x20, 0xAD, 0x56, 0x14, 0xC2, 0x20, 0xF0, 0x0A, 0x22, 0xCF, 0xF7, 0x00, 0x22, 0xB7, 0xA5, 0x03])) + rom.write_bytes(0x007B10, bytearray([0x80, 0x04, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0x3D, 0x96, 0x07, 0xBD, 0x02, 0x79, 0x85, 0x0E, 0xE2])) + rom.write_bytes(0x007B20, bytearray([0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xF0, 0x05, 0x22, 0xCF, 0xF7, 0x00, 0x6B, 0x22, 0xC5, 0xF7])) + rom.write_bytes(0x007B30, bytearray([0x00, 0x6B, 0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xD0, 0x0C, 0xAD, 0x74, 0x79, 0x29, 0x01])) + rom.write_bytes(0x007B40, bytearray([0x00, 0xD0, 0x04, 0x5C, 0x4A, 0xF3, 0x06, 0xBD, 0xD6, 0x79, 0x38, 0xFD, 0xE2, 0x70, 0x5C, 0x45])) + rom.write_bytes(0x007B50, bytearray([0xF2, 0x06, 0xAD, 0xAA, 0x60, 0x48, 0x30, 0x0E, 0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xF0])) + rom.write_bytes(0x007B60, bytearray([0x05, 0x68, 0x5C, 0xA0, 0xF3, 0x06, 0x68, 0x5C, 0xE6, 0xF3, 0x06, 0xBD, 0x02, 0x79, 0x85, 0x0E])) + rom.write_bytes(0x007B70, bytearray([0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xD0, 0x08, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0x35, 0xE5])) + rom.write_bytes(0x007B80, bytearray([0x06, 0x22, 0xCF, 0xF7, 0x00, 0x5C, 0x35, 0xE5, 0x06, 0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20])) + rom.write_bytes(0x007B90, bytearray([0xD0, 0x0C, 0xAD, 0x74, 0x79, 0x29, 0x01, 0x00, 0xD0, 0x04, 0x5C, 0x48, 0xF3, 0x06, 0xBD, 0x36])) + rom.write_bytes(0x007BA0, bytearray([0x7A, 0x38, 0xE9, 0x08, 0x00, 0x5C, 0x63, 0xE8, 0x06, 0xAD, 0xAA, 0x60, 0x30, 0x0D, 0xE2, 0x20])) + rom.write_bytes(0x007BB0, bytearray([0xAD, 0x57, 0x14, 0xC2, 0x20, 0xF0, 0x04, 0x5C, 0x99, 0xE8, 0x06, 0x5C, 0xF1, 0xE8, 0x06, 0x9C])) + rom.write_bytes(0x007BC0, bytearray([0xB0, 0x61, 0x9C, 0x8C, 0x0C, 0xE2, 0x20, 0xAD, 0x58, 0x14, 0xC2, 0x20, 0xF0, 0x07, 0x9C, 0x8E])) + rom.write_bytes(0x007BD0, bytearray([0x0C, 0x5C, 0x9D, 0xA4, 0x02, 0xA9, 0x00, 0x00, 0x8F, 0xAE, 0x00, 0x70, 0x8F, 0xAC, 0x00, 0x70])) + rom.write_bytes(0x007BE0, bytearray([0xE2, 0x20, 0xA9, 0xFE, 0x9D, 0x78, 0x79, 0x8F, 0x49, 0x00, 0x7E, 0xC2, 0x20, 0x5C, 0x9D, 0xA4])) + rom.write_bytes(0x007BF0, bytearray([0x02, 0xE2, 0x20, 0xAF, 0x49, 0x00, 0x7E, 0xC2, 0x20, 0xF0, 0x0D, 0xA9, 0x00, 0x00, 0x9D, 0xD8])) + rom.write_bytes(0x007C00, bytearray([0x79, 0x9D, 0x78, 0x79, 0x8F, 0x49, 0x00, 0x7E, 0xBD, 0x16, 0x7C, 0x18, 0x5C, 0x51, 0xA3, 0x02])) + rom.write_bytes(0x007C10, bytearray([0xE2, 0x20, 0xAD, 0x59, 0x14, 0xC2, 0x20, 0xD0, 0x0D, 0x22, 0xC5, 0xF7, 0x00, 0xBD, 0x38, 0x7D])) + rom.write_bytes(0x007C20, bytearray([0xF0, 0x0A, 0x5C, 0x4F, 0xA0, 0x02, 0x22, 0xCF, 0xF7, 0x00, 0x80, 0xF1, 0x5C, 0x59, 0xA0, 0x02])) + rom.write_bytes(0x007C30, bytearray([0xE2, 0x20, 0xAD, 0x59, 0x14, 0xC2, 0x20, 0xF0, 0x09, 0xBB, 0x22, 0x87, 0xBF, 0x03, 0x5C, 0x8D])) + rom.write_bytes(0x007C40, bytearray([0xA3, 0x02, 0x5C, 0x81, 0xA3, 0x02, 0xE2, 0x20, 0xAD, 0x5A, 0x14, 0xC2, 0x20, 0xF0, 0x09, 0xB5])) + rom.write_bytes(0x007C50, bytearray([0x76, 0x29, 0xFF, 0x00, 0x5C, 0x9D, 0x93, 0x02, 0x8D, 0x04, 0x30, 0xA9, 0x00, 0x00, 0x8D, 0x08])) + rom.write_bytes(0x007C60, bytearray([0x30, 0x5C, 0xA5, 0x93, 0x02, 0xE2, 0x20, 0xAD, 0x5A, 0x14, 0xC2, 0x20, 0xD0, 0x01, 0x6B, 0x22])) + rom.write_bytes(0x007C70, bytearray([0x23, 0xAF, 0x03, 0x5C, 0xDA, 0x93, 0x02, 0xE2, 0x20, 0xAD, 0x5B, 0x14, 0xC2, 0x20, 0xF0, 0x09])) + rom.write_bytes(0x007C80, bytearray([0x9B, 0xBD, 0xD6, 0x79, 0x0A, 0x5C, 0xCA, 0xC4, 0x05, 0x6B, 0xE2, 0x20, 0xAD, 0x5B, 0x14, 0xC2])) + rom.write_bytes(0x007C90, bytearray([0x20, 0xF0, 0x09, 0x9B, 0xBD, 0xD6, 0x79, 0x0A, 0x5C, 0xC1, 0xC8, 0x05, 0x6B, 0x22, 0x52, 0xAA])) + rom.write_bytes(0x007CA0, bytearray([0x03, 0xE2, 0x20, 0xAD, 0x5B, 0x14, 0xC2, 0x20, 0xF0, 0x0A, 0xA0, 0x00, 0x22, 0xD1, 0xF7, 0x00])) + rom.write_bytes(0x007CB0, bytearray([0x5C, 0xD9, 0xC4, 0x05, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0x70, 0xC5, 0x05, 0x22, 0x23, 0xAF, 0x03])) + rom.write_bytes(0x007CC0, bytearray([0xE2, 0x20, 0xAD, 0x5C, 0x14, 0xC2, 0x20, 0xF0, 0x0A, 0xA0, 0x00, 0x22, 0xD1, 0xF7, 0x00, 0x5C])) + rom.write_bytes(0x007CD0, bytearray([0x24, 0xC9, 0x0C, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0xF6, 0xE2, 0x20, 0xAD, 0x5C, 0x14, 0xC2, 0x20])) + rom.write_bytes(0x007CE0, bytearray([0xF0, 0x08, 0x8A, 0x8D, 0x02, 0x30, 0x5C, 0x4D, 0xCD, 0x0C, 0xFA, 0x5C, 0x3A, 0xCD, 0x0C, 0x48])) + rom.write_bytes(0x007CF0, bytearray([0xDA, 0xE2, 0x20, 0xAD, 0x5D, 0x14, 0xF0, 0x33, 0xAA, 0x4C, 0x53, 0xFF, 0xFF, 0x18, 0x4C, 0x71])) + rom.write_bytes(0x007D00, bytearray([0xFF, 0x8D, 0x5E, 0x14, 0xC2, 0x20, 0xFA, 0x68, 0x1A, 0x1A, 0xC9, 0x0E, 0x00, 0x90, 0x06, 0x80])) + rom.write_bytes(0x007D10, bytearray([0x16, 0x5C, 0x15, 0xBF, 0x03, 0xE2, 0x20, 0x48, 0xBD, 0x60, 0x73, 0xC9, 0x27, 0xF0, 0x12, 0x68])) + rom.write_bytes(0x007D20, bytearray([0xCD, 0x5E, 0x14, 0xC2, 0x20, 0x90, 0xEA, 0x5C, 0xE5, 0xFA, 0x0B, 0x1A, 0x8D, 0x5D, 0x14, 0x80])) + rom.write_bytes(0x007D30, bytearray([0xC0, 0x68, 0xC2, 0x20, 0xEE, 0xCC, 0x00, 0xEE, 0xCC, 0x00, 0x80, 0xD5, 0xA8, 0x5C, 0x20, 0xBF])) + rom.write_bytes(0x007D40, bytearray([0x03, 0x8B, 0xA9, 0x03, 0x8D, 0x4B, 0x09, 0x8D, 0x01, 0x21, 0x22, 0x39, 0xB4, 0x00, 0x22, 0x79])) + rom.write_bytes(0x007D50, bytearray([0x82, 0x10, 0xDA, 0xAD, 0x0E, 0x03, 0x4A, 0xAA, 0xBF, 0xF3, 0xFE, 0x06, 0xAA, 0xAD, 0x1A, 0x02])) + rom.write_bytes(0x007D60, bytearray([0x9F, 0x00, 0x7C, 0x70, 0x9C, 0x22, 0x02, 0xAF, 0x83, 0xFC, 0x0D, 0xAA, 0xBF, 0xB2, 0xAF, 0x09])) + rom.write_bytes(0x007D70, bytearray([0x0C, 0xCE, 0x00, 0xAD, 0x60, 0x14, 0x0C, 0xCE, 0x00, 0x5A, 0xC2, 0x10, 0xA2, 0xAA, 0xAF, 0xAD])) + rom.write_bytes(0x007D80, bytearray([0xCE, 0x00, 0x89, 0x01, 0xF0, 0x06, 0xA0, 0x22, 0x02, 0x20, 0xCA, 0xFD, 0x89, 0x02, 0xF0, 0x06])) + rom.write_bytes(0x007D90, bytearray([0xA0, 0x2E, 0x02, 0x20, 0xCA, 0xFD, 0x89, 0x04, 0xF0, 0x06, 0xA0, 0x3A, 0x02, 0x20, 0xCA, 0xFD])) + rom.write_bytes(0x007DA0, bytearray([0x89, 0x08, 0xF0, 0x06, 0xA0, 0x46, 0x02, 0x20, 0xCA, 0xFD, 0x89, 0x10, 0xF0, 0x06, 0xA0, 0x52])) + rom.write_bytes(0x007DB0, bytearray([0x02, 0x20, 0xCA, 0xFD, 0x89, 0x20, 0xF0, 0x06, 0xA0, 0x5E, 0x02, 0x20, 0xCA, 0xFD, 0x9C, 0x65])) + rom.write_bytes(0x007DC0, bytearray([0x02, 0xE2, 0x10, 0x7A, 0xFA, 0xAB, 0x5C, 0xB6, 0xA5, 0x17, 0xC2, 0x20, 0x48, 0xA9, 0x07, 0x00])) + rom.write_bytes(0x007DD0, bytearray([0xDA, 0x54, 0x00, 0x09, 0xFA, 0x68, 0xE2, 0x20, 0x60, 0xDA, 0x5A, 0x8B, 0xAD, 0x0E, 0x03, 0xC2])) + rom.write_bytes(0x007DE0, bytearray([0x20, 0xC2, 0x10, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xA8, 0xE2, 0x20, 0xA9, 0x00, 0xEB, 0xA9, 0x7F])) + rom.write_bytes(0x007DF0, bytearray([0xA2, 0xC0, 0x14, 0x54, 0x70, 0x7E, 0xA2, 0xC0, 0x14, 0xA0, 0x40, 0x14, 0xA9, 0x00, 0xEB, 0xA9])) + rom.write_bytes(0x007E00, bytearray([0x7F, 0x54, 0x7E, 0x7E, 0xE2, 0x10, 0xAB, 0x7A, 0xFA, 0xA9, 0x1E, 0x8D, 0x18, 0x01, 0xAF, 0x83])) + rom.write_bytes(0x007E10, bytearray([0xFC, 0x0D, 0xDA, 0xAA, 0xBF, 0xB8, 0xAF, 0x09, 0x8D, 0x18, 0x02, 0xAF, 0x88, 0xFC, 0x0D, 0x49])) + rom.write_bytes(0x007E20, bytearray([0x01, 0x8D, 0x5A, 0x14, 0xFA, 0x5C, 0x58, 0x99, 0x17, 0xAE, 0x15, 0x11, 0xAD, 0x60, 0x14, 0x89])) + rom.write_bytes(0x007E30, bytearray([0x01, 0xD0, 0x0D, 0xAF, 0x83, 0xFC, 0x0D, 0xF0, 0x07, 0x9E, 0x10, 0x00, 0x5C, 0xB1, 0xD8, 0x17])) + rom.write_bytes(0x007E40, bytearray([0xFE, 0x10, 0x00, 0x80, 0xF7, 0xA9, 0xF0, 0x85, 0x4D, 0x8D, 0x63, 0x14, 0xA9, 0x80, 0x8D, 0x20])) + rom.write_bytes(0x007E50, bytearray([0x02, 0x8D, 0x4A, 0x00, 0x5C, 0x59, 0xC1, 0x01, 0xE2, 0x20, 0xAD, 0x61, 0x14, 0x89, 0x01, 0xF0])) + rom.write_bytes(0x007E60, bytearray([0x08, 0x48, 0xA9, 0x09, 0x8F, 0x17, 0x03, 0x17, 0x68, 0x89, 0x02, 0xF0, 0x08, 0x48, 0xA9, 0x09])) + rom.write_bytes(0x007E70, bytearray([0x8F, 0x23, 0x03, 0x17, 0x68, 0x89, 0x04, 0xF0, 0x08, 0x48, 0xA9, 0x09, 0x8F, 0x2F, 0x03, 0x17])) + rom.write_bytes(0x007E80, bytearray([0x68, 0x89, 0x08, 0xF0, 0x08, 0x48, 0xA9, 0x09, 0x8F, 0x3B, 0x03, 0x17, 0x68, 0x89, 0x10, 0xF0])) + rom.write_bytes(0x007E90, bytearray([0x08, 0x48, 0xA9, 0x09, 0x8F, 0x47, 0x03, 0x17, 0x68, 0x89, 0x20, 0xF0, 0x08, 0x48, 0xA9, 0x09])) + rom.write_bytes(0x007EA0, bytearray([0x8F, 0x53, 0x03, 0x17, 0x68, 0xAD, 0x62, 0x14, 0x89, 0x01, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F])) + rom.write_bytes(0x007EB0, bytearray([0x18, 0x03, 0x17, 0x68, 0x89, 0x02, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F, 0x24, 0x03, 0x17, 0x68])) + rom.write_bytes(0x007EC0, bytearray([0x89, 0x04, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F, 0x30, 0x03, 0x17, 0x68, 0x89, 0x08, 0xF0, 0x08])) + rom.write_bytes(0x007ED0, bytearray([0x48, 0xA9, 0x0A, 0x8F, 0x3C, 0x03, 0x17, 0x68, 0x89, 0x10, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F])) + rom.write_bytes(0x007EE0, bytearray([0x48, 0x03, 0x17, 0x68, 0x89, 0x20, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F, 0x54, 0x03, 0x17, 0x68])) + rom.write_bytes(0x007EF0, bytearray([0xC2, 0x20, 0x5C, 0x26, 0xDB, 0x17, 0xAD, 0x63, 0x14, 0xF0, 0x0E, 0xA9, 0x00, 0x8D, 0x63, 0x14])) + rom.write_bytes(0x007F00, bytearray([0xA9, 0x20, 0x8D, 0x18, 0x01, 0x5C, 0x04, 0xA9, 0x17, 0xA9, 0x25, 0x80, 0xF5, 0xAD, 0x06, 0x7E])) + rom.write_bytes(0x007F10, bytearray([0xD0, 0x15, 0xE2, 0x20, 0xAD, 0x64, 0x14, 0xD0, 0x0E, 0xAF, 0x84, 0xFC, 0x0D, 0x89, 0x01, 0xD0])) + rom.write_bytes(0x007F20, bytearray([0x06, 0xC2, 0x20, 0x5C, 0x49, 0xEA, 0x0C, 0xC2, 0x20, 0x5C, 0x47, 0xEA, 0x0C, 0xAD, 0x06, 0x7E])) + rom.write_bytes(0x007F30, bytearray([0xD0, 0x15, 0xE2, 0x20, 0xAD, 0x64, 0x14, 0xD0, 0x0E, 0xAF, 0x84, 0xFC, 0x0D, 0x89, 0x02, 0xD0])) + rom.write_bytes(0x007F40, bytearray([0x06, 0xC2, 0x20, 0x5C, 0x91, 0xC0, 0x03, 0xC2, 0x20, 0x5C, 0xCC, 0xC0, 0x03])) + rom.write_bytes(0x007F53, bytearray([0xBF, 0xA3, 0xAF, 0x09, 0xE0, 0x06, 0xF0, 0x03, 0x4C, 0xFD, 0xFC, 0x4C, 0x01, 0xFD])) + rom.write_bytes(0x007F61, bytearray([0xAF, 0xAE, 0x00, 0x70, 0xD0, 0x07, 0xFA, 0xA9, 0x0E, 0x00, 0x4C, 0x2C, 0xFA, 0x4C, 0x26, 0xFA])) + rom.write_bytes(0x007F71, bytearray([0x6D, 0xCC, 0x00, 0xC9, 0x0E, 0x90, 0x02, 0xA9, 0x0E, 0x4C, 0x01, 0xFD])) + + rom.write_bytes(0x077F82, bytearray([0xE2, 0x20, 0xAD, 0x40, 0x14, 0xD0, 0x08, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0x80])) + rom.write_bytes(0x077F90, bytearray([0x06, 0xA0, 0x00, 0x22, 0xD1, 0xF7, 0x00, 0xC2, 0x20, 0x20, 0x1A, 0xB6, 0x60, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x077FA0, bytearray([0x55, 0x14, 0xC2, 0x20, 0xD0, 0x03, 0x4C, 0xFD, 0xBE, 0x20, 0xBB, 0xBF, 0x4C, 0xFD, 0xBE])) + + rom.write_bytes(0x01FEEE, bytearray([0xB9, 0x00])) + rom.write_bytes(0x01FEF0, bytearray([0x6F, 0x48, 0xDA, 0xBD, 0x60, 0x73, 0xA2, 0x00, 0xDF, 0x70, 0xAF, 0x09, 0xF0, 0x08, 0xE8, 0xE8])) + rom.write_bytes(0x01FF00, bytearray([0xE0, 0x08, 0xF0, 0x1A, 0x80, 0xF2, 0x8A, 0x4A, 0xE2, 0x20, 0xAA, 0xBF, 0x78, 0xAF, 0x09, 0xAA])) + rom.write_bytes(0x01FF10, bytearray([0xBD, 0x40, 0x14, 0xC2, 0x20, 0xD0, 0x07, 0xFA, 0x68, 0xC9, 0x00, 0x00, 0x80, 0x05, 0xFA, 0x68])) + rom.write_bytes(0x01FF20, bytearray([0xC9, 0x10, 0x00, 0x5C, 0x34, 0xC3, 0x03, 0xAE, 0x12, 0x98, 0xE2, 0x20, 0xAD, 0x5E, 0x14, 0xC9])) + rom.write_bytes(0x01FF30, bytearray([0x0E, 0xF0, 0x08, 0x3A, 0x3A, 0xA8, 0xC2, 0x20, 0x4C, 0x15, 0xBF, 0x98, 0x80, 0xF8])) + + rom.write_bytes(0x02FFC0, bytearray([0x0C, 0xA6, 0x12, 0x6B, 0xBD, 0x60, 0x73, 0xC9, 0x1E, 0x01, 0xE2, 0x20, 0xF0, 0x05, 0xAD, 0x4C])) + rom.write_bytes(0x02FFD0, bytearray([0x14, 0x80, 0x03, 0xAD, 0x4B, 0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x03, 0x20, 0xF6, 0xF1, 0x4C])) + rom.write_bytes(0x02FFE0, bytearray([0xB0, 0xF0])) + + rom.write_bytes(0x017FD7, bytearray([0xE2, 0x20, 0xAD, 0x4D, 0x14, 0xC2, 0x20, 0xF0, 0x10])) + rom.write_bytes(0x017FE0, bytearray([0xBD, 0x60, 0x73, 0xC9, 0xA9, 0x01, 0xF0, 0x04, 0xA9, 0x04, 0x00, 0x60, 0xA9, 0x0A, 0x00, 0x60])) + rom.write_bytes(0x017FF0, bytearray([0x68, 0x4C, 0x90, 0xAD])) + + rom.write_bytes(0x03FF48, bytearray([0xE2, 0x20, 0xAD, 0x56, 0x14, 0xC2, 0x20, 0xD0])) + rom.write_bytes(0x03FF50, bytearray([0x03, 0x4C, 0x5B, 0x96, 0x20, 0x3D, 0x9D, 0x4C, 0x4F, 0x96])) + + +def Item_Data(rom: LocalRom) -> None: + rom.write_bytes(0x04AF70, bytearray([0xBB, 0x00, 0xBA, 0x00, 0xC7, 0x00, 0xC8, 0x00, 0x01, 0x02, 0x03, 0x03, 0xB1, 0x00, 0xB0, 0x00])) + rom.write_bytes(0x04AF80, bytearray([0xB2, 0x00, 0xAF, 0x00, 0xB4, 0x00, 0x04, 0x05, 0x06, 0x07, 0x08, 0x07, 0x00, 0x05, 0x00, 0x09])) + rom.write_bytes(0x04AF90, bytearray([0x00, 0x0D, 0x0E, 0x0F, 0x22, 0x00, 0x26, 0x00, 0x29, 0x00, 0x2A, 0x00, 0x2B, 0x00, 0x11, 0x12])) + rom.write_bytes(0x04AFA0, bytearray([0x12, 0x12, 0x12, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01])) + rom.write_bytes(0x04AFB0, bytearray([0x01, 0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A])) + + +def Server_Data(rom: LocalRom) -> None: + rom.write_bytes(0x037EAA, bytearray([0x00, 0x00, 0x01, 0x02, 0x03, 0x04])) + rom.write_bytes(0x037EB0, bytearray([0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14])) + rom.write_bytes(0x037EC0, bytearray([0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x24, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x01])) + rom.write_bytes(0x037ED0, bytearray([0x02, 0x04, 0x08, 0x10, 0x20, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30])) + rom.write_bytes(0x037EE0, bytearray([0x31, 0x32, 0x33, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0xFF, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39])) + rom.write_bytes(0x037EF0, bytearray([0x3A, 0x3B, 0x3C, 0x02, 0x6A, 0xD2, 0x04, 0x03, 0x06, 0x07, 0x08, 0x09, 0x05, 0x01, 0x02, 0x3D])) + rom.write_bytes(0x037F00, bytearray([0x3E, 0x3F, 0x40, 0x01, 0x02, 0x03, 0x0A, 0x80, 0x7E, 0x00, 0x7F, 0x80, 0x7F])) + + +def Menu_Data(rom: LocalRom) -> None: + rom.write_bytes(0x115348, bytearray([0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80])) + rom.write_bytes(0x115350, bytearray([0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E])) + rom.write_bytes(0x115360, bytearray([0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03])) + rom.write_bytes(0x115370, bytearray([0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08])) + rom.write_bytes(0x115380, bytearray([0x08, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x24, 0x2C, 0x00, 0x06, 0x1E, 0x00, 0x06, 0x24, 0x00])) + rom.write_bytes(0x115390, bytearray([0x02, 0x24, 0x00, 0x0E, 0x04, 0x00, 0x18, 0x26, 0x00, 0x26, 0x1A, 0x00, 0x04, 0x22, 0x00, 0x24])) + rom.write_bytes(0x1153A0, bytearray([0x18, 0x00, 0x24, 0x02, 0x00, 0x16, 0x24, 0x00, 0x00, 0x2C, 0x00, 0x2A, 0x2C, 0x00, 0x2C, 0x18])) + rom.write_bytes(0x1153B0, bytearray([0x00, 0x10, 0x18, 0x00, 0x0A, 0x18, 0x00, 0x24, 0x24, 0x00, 0x0A, 0x08, 0x00, 0x0C, 0x08, 0x00])) + rom.write_bytes(0x1153C0, bytearray([0x08, 0x16, 0x00, 0x08, 0x1E, 0x00, 0x04, 0x14, 0x00, 0x1E, 0x0E, 0x00, 0x1E, 0x0C, 0x00, 0x24])) + rom.write_bytes(0x1153D0, bytearray([0x14, 0x00, 0x14, 0x30, 0x00, 0x18, 0x22, 0x00, 0x02, 0x04, 0x00, 0x26, 0x16, 0x00, 0x24, 0x16])) + rom.write_bytes(0x1153E0, bytearray([0x00, 0x5C, 0x38, 0x60, 0x4E, 0x28, 0x1A, 0x16, 0x1C, 0x04, 0x14, 0x36, 0x36, 0x36, 0x80, 0x80])) + rom.write_bytes(0x1153F0, bytearray([0x34, 0x81, 0x81, 0x4E, 0x4E, 0x4E, 0x5C, 0x38, 0x60, 0x4E, 0x04, 0x16, 0x08, 0x00, 0x22, 0x36])) + rom.write_bytes(0x115400, bytearray([0x36, 0x36, 0x36, 0x80, 0x80, 0x34, 0x81, 0x81, 0x4E, 0x4E, 0x4E, 0x50, 0x52, 0x54, 0x56, 0x58])) + rom.write_bytes(0x115410, bytearray([0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x50, 0x52, 0x54, 0x09, 0x15, 0x21, 0x2D, 0x39, 0x45, 0x0C, 0x03])) + rom.write_bytes(0x115420, bytearray([0x07, 0x0F, 0x13, 0x1B, 0x1F, 0x27, 0x2B, 0x33, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04])) + rom.write_bytes(0x115430, bytearray([0x06, 0x06, 0x08, 0x08, 0x0A, 0x41, 0x00, 0x3C, 0x00, 0x33, 0x00, 0x25, 0x00, 0x1B, 0x00, 0x14])) + rom.write_bytes(0x115440, bytearray([0x00, 0x0B, 0x00, 0x02, 0x00, 0xF6, 0x3F, 0xEC, 0x3F, 0xDC, 0x3F])) + + rom.write_bytes(0x082660, bytearray([0x07])) + rom.write_bytes(0x082667, bytearray([0x05])) + rom.write_bytes(0x082677, bytearray([0x0A, 0x03, 0x05])) + rom.write_bytes(0x082688, bytearray([0x00])) + + rom.write_bytes(0x11548E, bytearray([0x60, 0x3d, 0x66, 0x3b, 0x60, 0x3f, 0x60, 0x39, 0x66, 0x39, 0x66, 0x3d, 0x66, 0x3f, 0x60, 0x3b])) + rom.write_bytes(0x11549E, bytearray([0x02, 0x06, 0x04, 0x00, 0x01, 0x03, 0x05, 0x07])) + + +def CodeHandler(rom: LocalRom) -> None: + rom.write_bytes(0x073637, bytearray([0x5C, 0xB0, 0xF7, 0x00])) # Check ! Switch + rom.write_bytes(0x07360B, bytearray([0x20, 0x82, 0xFF])) # Flash ! Switch + + rom.write_bytes(0x01C2F3, bytearray([0x22, 0x11, 0xF8, 0x00])) # Check visibility of winged clouds + rom.write_bytes(0x01C32E, bytearray([0x5C, 0xEE, 0xFE, 0x03])) # Check items in winged clouds + + rom.write_bytes(0x01C9AD, bytearray([0x5C, 0x19, 0xF8, 0x00])) # Check transformations + rom.write_bytes(0x01C995, bytearray([0x5C, 0x59, 0xF8, 0x00])) # Flash transformations + rom.write_bytes(0x01C943, bytearray([0x5C, 0x77, 0xF8, 0x00])) # Fixes a bug where transformation bubbles flashing would displace the sprite + + rom.write_bytes(0x028329, bytearray([0x5C, 0x9A, 0xF8, 0x00])) # Flash Spring Ball + rom.write_bytes(0x02837E, bytearray([0x5C, 0xC4, 0xF8, 0x00])) # Check Spring Ball + + rom.write_bytes(0x02F0A2, bytearray([0x5C, 0xEA, 0xF8, 0x00])) # Flash Arrow Wheel + rom.write_bytes(0x02F0AD, bytearray([0x4C, 0xC4, 0xFF])) # Check Arrow Wheel + + rom.write_bytes(0x02001D, bytearray([0x5C, 0x15, 0xF9, 0x00])) # Check Melon + rom.write_bytes(0x020028, bytearray([0x5C, 0x41, 0xF9, 0x00])) # Secondary check for melon used to overwrite visibility on the ground + rom.write_bytes(0x020031, bytearray([0x5C, 0xAE, 0xF9, 0x00])) # Check for melons that are spawned by objects which skips the initial check + rom.write_bytes(0x012DF7, bytearray([0x20, 0xD7, 0xFF])) # Check for monkeys holding melons + rom.write_bytes(0x012E07, bytearray([0x20, 0xD7, 0xFF])) # Check for monkeys holding melons + rom.write_bytes(0x03F17D, bytearray([0x5C, 0xE1, 0xF9, 0x00])) # Fixes a bug where balloons with ice melons will write to yoshi's mouth before deactivating the melon. + + rom.write_bytes(0x011901, bytearray([0x5C, 0x7A, 0xF9, 0x00])) # Flash Super Star + rom.write_bytes(0x01192A, bytearray([0x5C, 0x95, 0xF9, 0x00])) # Check Super Star + + rom.write_bytes(0x01BEB9, bytearray([0x5C, 0xF6, 0xF9, 0x00])) # Check egg-type items + rom.write_bytes(0x01B75E, bytearray([0x5C, 0x5B, 0xFA, 0x00])) # Flash flashing eggs and force them to purple + + rom.write_bytes(0x03BA31, bytearray([0x22, 0x81, 0xFA, 0x00])) # Flash Arrow Cloud + rom.write_bytes(0x03BA35, bytearray([0x22, 0x9A, 0xFA, 0x00])) # Check Arrow Cloud + rom.write_bytes(0x03BA3D, bytearray([0x22, 0x81, 0xFA, 0x00])) # Flash Arrow Cloud, rotating + rom.write_bytes(0x03BA5A, bytearray([0x22, 0x9A, 0xFA, 0x00])) # Check Arrow Cloud, rotating + + rom.write_bytes(0x03818F, bytearray([0x5C, 0xAB, 0xFA, 0x00])) # Check Egg Plant + rom.write_bytes(0x0380F3, bytearray([0x5C, 0xC3, 0xFA, 0x00])) # Flash Egg Plant + + rom.write_bytes(0x073EF6, bytearray([0x5C, 0xE0, 0xFA, 0x00])) # Flash Chomp Rock + rom.write_bytes(0x073EFA, bytearray([0x4C, 0x9D, 0xFF])) # Check Chomp Rock + + rom.write_bytes(0x039639, bytearray([0x5C, 0xFF, 0xFA, 0x00])) # Flash Poochy + rom.write_bytes(0x03964C, bytearray([0x4C, 0x48, 0xFF])) # Check Poochy + + rom.write_bytes(0x0370C2, bytearray([0x22, 0x1A, 0xFB, 0x00, 0xEA])) # Flash Platform Ghosts + rom.write_bytes(0x03723F, bytearray([0x5C, 0x32, 0xFB, 0x00])) # Fixes a bug where the eyes would assign to a random sprite while flashing + rom.write_bytes(0x03739B, bytearray([0x5C, 0x52, 0xFB, 0x00])) # Check Vertical Platform Ghost + rom.write_bytes(0x036530, bytearray([0x5C, 0x6B, 0xFB, 0x00])) # Flash horizontal ghost + rom.write_bytes(0x03685C, bytearray([0x5C, 0x89, 0xFB, 0x00])) # Fix flashing horizontal ghost + rom.write_bytes(0x036894, bytearray([0x5C, 0xA9, 0xFB, 0x00])) # Check horizontal ghost + + rom.write_bytes(0x012497, bytearray([0x5C, 0xBF, 0xFB, 0x00])) # Check Skis + rom.write_bytes(0x01234D, bytearray([0x5C, 0xF1, 0xFB, 0x00])) # Allow ski doors to be re-entered + + rom.write_bytes(0x01204A, bytearray([0x5C, 0x10, 0xFC, 0x00])) # Flash Key + rom.write_bytes(0x012388, bytearray([0x5C, 0x30, 0xFC, 0x00])) # Check Key + + rom.write_bytes(0x011398, bytearray([0x5C, 0x46, 0xFC, 0x00])) # Flash MidRing + rom.write_bytes(0x0113D6, bytearray([0x5C, 0x65, 0xFC, 0x00])) # Check MidRing + + rom.write_bytes(0x02C4C6, bytearray([0x5C, 0x77, 0xFC, 0x00])) # Check Bucket w/ Item + rom.write_bytes(0x02C8BD, bytearray([0x5C, 0x8A, 0xFC, 0x00])) # Check Bucket, ridable + rom.write_bytes(0x02C4D5, bytearray([0x5C, 0x9D, 0xFC, 0x00])) # Flash Bucket + + rom.write_bytes(0x064920, bytearray([0x5C, 0xBC, 0xFC, 0x00])) # Flash Tulip + rom.write_bytes(0x064D49, bytearray([0x5C, 0xD9, 0xFC, 0x00])) # Check Tulip + + rom.write_bytes(0x01BEC7, bytearray([0x5C, 0xEF, 0xFC, 0x00])) # Check Egg Capacity + rom.write_bytes(0x01BF12, bytearray([0x4C, 0x27, 0xFF])) # Set current egg max + rom.write_bytes(0x01BF1A, bytearray([0x5C, 0x3C, 0xFD, 0x00])) # Cap eggs + + rom.write_bytes(0x0BA5AE, bytearray([0x5C, 0x41, 0xFD, 0x00])) # Unlock Levels + + rom.write_bytes(0x0B9953, bytearray([0x5C, 0xD9, 0xFD, 0x00])) # File initialization + + rom.write_bytes(0x0BD8AB, bytearray([0x5C, 0x29, 0xFE, 0x00])) # Prevent the world 1 tab from being drawn without it being unlocked + + rom.write_bytes(0x00C155, bytearray([0x5C, 0x45, 0xFE, 0x00])) # Save between levels + + rom.write_bytes(0x0BDB20, bytearray([0x5C, 0x58, 0xFE, 0x00])) # Unlock extra and bonus stages + + rom.write_bytes(0x0BA8FF, bytearray([0x5C, 0xF6, 0xFE, 0x00])) # Skip the score animation if coming from start-select, but still save + + rom.write_bytes(0x0BA8A9, bytearray([0x80, 0x46])) # Prevent unlocking new levels + + rom.write_bytes(0x066A42, bytearray([0x5C, 0x0D, 0xFF, 0x00])) # Coin visibility + rom.write_bytes(0x01C08C, bytearray([0x5C, 0x2D, 0xFF, 0x00])) # Cloud visibility + + rom.write_bytes(0x00C0D9, bytearray([0x5C, 0xB8, 0xF3, 0x0B])) # Receive item from server + + rom.write_bytes(0x00C153, bytearray([0xEA, 0xEA])) # Always enable Start/Select + + rom.write_bytes(0x00C18B, bytearray([0x5C, 0x1B, 0xF5, 0x0B])) # Enable traps + + rom.write_bytes(0x01B365, bytearray([0x5C, 0x86, 0xF5, 0x0B])) # Red Coin checks + rom.write_bytes(0x0734C6, bytearray([0x5C, 0xCE, 0xF5, 0x0B])) # Flower checks + rom.write_bytes(0x00C0DE, bytearray([0x5C, 0xF5, 0xF5, 0x0B])) # Star checks + rom.write_bytes(0x00B580, bytearray([0x5C, 0xB1, 0xF5, 0x0B])) # Level Clear checks + + rom.write_bytes(0x0B9937, bytearray([0x5C, 0x23, 0xF6, 0x0B])) # Load AP data + rom.write_bytes(0x0BE14A, bytearray([0x5C, 0x58, 0xF6, 0x0B])) # Save AP data + + rom.write_bytes(0x00D09F, bytearray([0x5C, 0x8C, 0xF6, 0x0B])) # Clear Menu + rom.write_bytes(0x00BCB5, bytearray([0x5C, 0xAD, 0xF6, 0x0B])) # Clear Score for menu + rom.write_bytes(0x00D072, bytearray([0x5C, 0xC3, 0xF6, 0x0B])) # Loads the data for the AP menu + rom.write_bytes(0x00D07A, bytearray([0x5C, 0x5A, 0xF7, 0x0B])) # Draw the AP menu over the pause menu + rom.write_bytes(0x00D17A, bytearray([0x5C, 0xDA, 0xF7, 0x0B])) # Skip the flower counter in the AP menu + rom.write_bytes(0x00D0DE, bytearray([0x5C, 0xF1, 0xF7, 0x0B])) # Skip the coin counter in the AP menu + rom.write_bytes(0x00CFB4, bytearray([0x5C, 0x06, 0xF8, 0x0B])) # Get the number of bosses required to unlock 6-8 + rom.write_bytes(0x00CFD0, bytearray([0x5C, 0x2B, 0xF8, 0x0B])) # Get bosses for 6-8 clear + rom.write_bytes(0x00D203, bytearray([0x5C, 0xF0, 0xF8, 0x0B])) # Wipe total score line + rom.write_bytes(0x00D277, bytearray([0x5C, 0x04, 0xF9, 0x0B])) # Wipe high score line + rom.write_bytes(0x00C104, bytearray([0x5C, 0x18, 0xF9, 0x0B])) # Replace the pause menu with AP menu when SELECT is pressed + rom.write_bytes(0x00C137, bytearray([0x5C, 0x31, 0xF9, 0x0B])) # Prevent accidentally quitting out of a stage while opening the AP menu + rom.write_bytes(0x00CE48, bytearray([0x5C, 0x42, 0xF9, 0x0B])) # When closing the AP menu, reset the AP menu flag so the normal menu can be opened. + + rom.write_bytes(0x0BA5B6, bytearray([0x5C, 0x4E, 0xF9, 0x0B])) # Unlock 6-8 if the current number of defeated bosses is higher than the number of bosses required. If 6-8 is marked 'cleared', skip boss checks + rom.write_bytes(0x01209E, bytearray([0x5C, 0x92, 0xF9, 0x0B])) # Write a flag to check bosses if setting up the final boss door + rom.write_bytes(0x0123AA, bytearray([0x5C, 0xA3, 0xF9, 0x0B])) # If the boss check flag is set, read the number of bosses before opening door + + rom.write_bytes(0x015F7A, bytearray([0x5C, 0xCA, 0xF9, 0x0B])) # Write Boss Clears + + rom.write_bytes(0x0BE16E, bytearray([0x80, 0x12])) # Disable overworld bandit code + + rom.write_bytes(0x083015, bytearray([0x5C, 0x26, 0xFA, 0x0B])) # Flip Cards + rom.write_bytes(0x0839B6, bytearray([0x5C, 0x18, 0xFA, 0x0B])) # Scratch Cards + rom.write_bytes(0x085094, bytearray([0x5C, 0x31, 0xFA, 0x0B])) # Draw Lots + rom.write_bytes(0x0852C5, bytearray([0x5C, 0x3D, 0xFA, 0x0B])) # Match Cards + rom.write_bytes(0x0845EA, bytearray([0x5C, 0x48, 0xFA, 0x0B])) # Roulette + rom.write_bytes(0x083E0A, bytearray([0x5C, 0x53, 0xFA, 0x0B])) # Slots + + rom.write_bytes(0x01D845, bytearray([0x5C, 0x76, 0xF9, 0x0B])) # Check setting for disabled autoscrolls + + rom.write_bytes(0x0BDAC2, bytearray([0x80, 0x0E])) # Prevent extra and bonus stages from auto-unlocking at 100 points + rom.write_bytes(0x0BA720, bytearray([0xA9, 0x00, 0x00])) # Always read level scores as 0. This stops extras and bonus from trying to unlock + + rom.write_bytes(0x0BA720, bytearray([0xA9, 0x00, 0x00])) # Always read level scores as 0. This stops extras and bonus from trying to unlock + + rom.write_bytes(0x03FE85, bytearray([0x5C, 0x09, 0xFB, 0x0B])) # Decrement the key counter when unlocking the 6-4 cork + + rom.write_bytes(0x06F1B4, bytearray([0x5C, 0x22, 0xFB, 0x0B])) # Mark the goal and bowser clear after defeating bowser + + rom.write_bytes(0x005FE2, bytearray([0x5C, 0x9C, 0xFB, 0x0B])) # Flag red coins as checked if the last one came from a pole + + rom.write_bytes(0x01C2E1, bytearray([0x80])) # Makes hidden clouds not flash + rom.write_bytes(0x0120C0, bytearray([0x80])) # Prevents bandit game doors from sealing + + rom.write_bytes(0x0382A7, bytearray([0x5C, 0xC2, 0xFB, 0x0B])) # Make cactus eggplants check the eggplant item correctly + + rom.write_bytes(0x025E71, bytearray([0x5C, 0xFA, 0xFB, 0x0B])) # Write the stored reverse value + + rom.write_bytes(0x00B587, bytearray([0x5C, 0x24, 0xFC, 0x0B])) # Store the reverse value and zero it + + rom.write_bytes(0x0B9932, bytearray([0x5C, 0x96, 0xFA, 0x0B])) # Get 16 bit life count + + rom.write_bytes(0x00C288, bytearray([0x00])) + rom.write_bytes(0x00C28B, bytearray([0x80])) # Disable baby mario tutorial text + + rom.write_bytes(0x01141F, bytearray([0x80])) # Disable Middle Ring tutorial + + rom.write_bytes(0x073534, bytearray([0x80])) # Disable Flower tutorial + + rom.write_bytes(0x065B24, bytearray([0x5C, 0x45, 0xFC, 0x0B])) # Fix boss cutscenes + + rom.write_bytes(0x011507, bytearray([0x5C, 0x70, 0xFC, 0x0B])) # Fix Hookbill middle ring during boss shuffle + + rom.write_bytes(0x019E98, bytearray([0x5C, 0xB4, 0xFC, 0x0B])) # Flag red coins as checked if the last one was eaten + + rom.write_bytes(0x011AB6, bytearray([0x5C, 0xD7, 0xFC, 0x0B])) # Check egg refills for how many eggs to spawn + + rom.write_bytes(0x00DCA6, bytearray([0x5C, 0x00, 0xFD, 0x0B])) # Check egg refill pause use + + rom.write_bytes(0x0BE06B, bytearray([0x5C, 0x56, 0xFD, 0x0B])) # Get level from shuffled order + + rom.write_bytes(0x00C14B, bytearray([0xAE, 0x7C, 0x02, 0x8E, 0x1A, 0x02])) # Return to the original list when exiting a level + + rom.write_bytes(0x00BEA8, bytearray([0x5C, 0x3F, 0xFE, 0x0B])) # Save the original level when beating a shuffled one. + + rom.write_bytes(0x00E702, bytearray([0xAD, 0x7C, 0x02, 0x8D, 0x1A, 0x02, 0x80, 0x05])) # Save the original level when leaving through death + + rom.write_bytes(0x0BE72A, bytearray([0x7C])) # Load yoshi colors by slot number not level number + + rom.write_bytes(0x003346, bytearray([0x22, 0x54, 0xFE, 0x0B, 0xEA, 0xEA])) # Fix World 6 levels using weird tilesets + + rom.write_bytes(0x003A37, bytearray([0x22, 0x54, 0xFE, 0x0B, 0xEA, 0xEA])) # Fix World 6 levels using weird tilesets + + rom.write_bytes(0x0B87D5, bytearray([0x5C, 0x67, 0xFE, 0x0B])) + + rom.write_bytes(0x07081F, bytearray([0x80])) # Fix for weird falling chomps. Why does this even read the world number????? + + rom.write_bytes(0x0BC0B2, bytearray([0x5C, 0xD0, 0xED, 0x01])) # Load randomized yoshi colors on the world map + + rom.write_bytes(0x0BC6F7, bytearray([0x5C, 0x04, 0xEE, 0x01])) # Load selected yoshi color on the world map + + rom.write_bytes(0x0BC0AB, bytearray([0x80])) # Skip special color check for world 6; Levels handle this anyway + + +def write_lives(rom: LocalRom) -> None: + rom.write_bytes(0x05FA96, bytearray([0xC2, 0x20, 0xAF, 0x89, 0xFC, 0x0D, 0x8D, 0x79, 0x03, 0xE2, 0x20, 0x5C, 0x37, 0x99, 0x17])) + rom.write_bytes(0x05FABF, bytearray([0x48, 0xE2, 0x20, 0xAD, 0xCC, 0x00, 0xF0, 0x06, 0xCE, 0xCC, 0x00, 0xCE, 0xCC, 0x00, 0xC2, 0x20, 0x68, 0x22, 0x87, 0xBF, 0x03, 0x5C, 0x89, 0xFE, 0x07])) + + +def bonus_checks(rom: LocalRom) -> None: + rom.write_bytes(0x082156, bytearray([0x5C, 0x5F, 0xFA, 0x0B])) # Write bonus check + + +def bandit_checks(rom: LocalRom) -> None: + rom.write_bytes(0x08C9E4, bytearray([0x5C, 0xF3, 0xF9, 0x0B])) # Write Bandit Checks + + +def Handle_Locations(rom: LocalRom) -> None: + rom.write_bytes(0x05F3B8, bytearray([0xAD, 0x67, 0x14, 0xF0, 0x59, 0xDA, 0xC9, 0x1F])) + rom.write_bytes(0x05F3C0, bytearray([0xF0, 0x16, 0xC9, 0x20, 0xB0, 0x27, 0xAA, 0xBF, 0xAA, 0xFE, 0x06, 0xAA, 0xA9, 0x01, 0x9D, 0x40])) + rom.write_bytes(0x05F3D0, bytearray([0x14, 0xA9, 0x43, 0x8D, 0x53, 0x00, 0x80, 0x67, 0xAD, 0x5D, 0x14, 0xD0, 0x01, 0x1A, 0xC9, 0x06])) + rom.write_bytes(0x05F3E0, bytearray([0xF0, 0x04, 0x1A, 0x8D, 0x5D, 0x14, 0xA9, 0x03, 0x8D, 0x53, 0x00, 0x80, 0x52, 0xC9, 0x26, 0xB0])) + rom.write_bytes(0x05F3F0, bytearray([0x27, 0xA2, 0x00, 0xDF, 0xC9, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0xCF, 0xFE, 0x06])) + rom.write_bytes(0x05F400, bytearray([0x8D, 0x4C, 0x00, 0xAD, 0x60, 0x14, 0x0C, 0x4C, 0x00, 0xAD, 0x4C, 0x00, 0x8D, 0x60, 0x14, 0xA9])) + rom.write_bytes(0x05F410, bytearray([0x97, 0x8D, 0x53, 0x00, 0x80, 0x29, 0x80, 0x70, 0xC9, 0x2D, 0xB0, 0x25, 0xA2, 0x00, 0xDF, 0xD5])) + rom.write_bytes(0x05F420, bytearray([0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0xE3, 0xFE, 0x06, 0x8D, 0xCF, 0x00, 0xAD, 0x61])) + rom.write_bytes(0x05F430, bytearray([0x14, 0x0C, 0xCF, 0x00, 0xAD, 0xCF, 0x00, 0x8D, 0x61, 0x14, 0xA9, 0x95, 0x8D, 0x53, 0x00, 0x80])) + rom.write_bytes(0x05F440, bytearray([0x78, 0xC9, 0x34, 0xB0, 0x25, 0xA2, 0x00, 0xDF, 0xDC, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7])) + rom.write_bytes(0x05F450, bytearray([0xBF, 0xE3, 0xFE, 0x06, 0x8D, 0xCF, 0x00, 0xAD, 0x62, 0x14, 0x0C, 0xCF, 0x00, 0xAD, 0xCF, 0x00])) + rom.write_bytes(0x05F460, bytearray([0x8D, 0x62, 0x14, 0xA9, 0x95, 0x8D, 0x53, 0x00, 0x80, 0x4F, 0xC9, 0x3D, 0xB0, 0x1C, 0xA2, 0x00])) + rom.write_bytes(0x05F470, bytearray([0xDF, 0xEA, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0xF6, 0xFE, 0x06, 0x22, 0xA6, 0x9C])) + rom.write_bytes(0x05F480, bytearray([0x10, 0xA9, 0x36, 0x8D, 0x53, 0x00, 0x80, 0x31, 0x80, 0x64, 0xC9, 0x41, 0xB0, 0x2D, 0xA2, 0x00])) + rom.write_bytes(0x05F490, bytearray([0xDF, 0xFF, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xA9, 0x00, 0xEB, 0xBF, 0x03, 0xFF, 0x06])) + rom.write_bytes(0x05F4A0, bytearray([0xAA, 0x18, 0xC2, 0x20, 0x6D, 0x79, 0x03, 0x8D, 0x79, 0x03, 0xE2, 0x20, 0xA9, 0x08, 0x22, 0xD2])) + rom.write_bytes(0x05F4B0, bytearray([0x85, 0x00, 0xCA, 0xE0, 0x00, 0xF0, 0x02, 0x80, 0xF5, 0x80, 0x51, 0xC9, 0x41, 0xF0, 0x1E, 0xC9])) + rom.write_bytes(0x05F4C0, bytearray([0x42, 0xF0, 0x2D, 0xC9, 0x43, 0xF0, 0x3A, 0xC2, 0x20, 0x5C, 0xFB, 0xB3, 0x21, 0x77, 0x14, 0xE2])) + rom.write_bytes(0x05F4D0, bytearray([0x20, 0xA9, 0x01, 0x8D, 0x7D, 0x02, 0xA9, 0x2E, 0x8D, 0x53, 0x00, 0x80, 0x2F, 0xA9, 0x01, 0x8D])) + rom.write_bytes(0x05F4E0, bytearray([0x68, 0x14, 0xC2, 0x20, 0xA9, 0x00, 0x04, 0x8D, 0x69, 0x14, 0xE2, 0x20, 0x80, 0x1E, 0x80, 0x22])) + rom.write_bytes(0x05F4F0, bytearray([0xC2, 0x20, 0xA9, 0x2C, 0x01, 0x8D, 0xCC, 0x0C, 0xE2, 0x20, 0xA9, 0xA0, 0x8D, 0x53, 0x00, 0x80])) + rom.write_bytes(0x05F500, bytearray([0x0B, 0xA9, 0x15, 0x8D, 0x53, 0x00, 0xA9, 0x05, 0x8F, 0xED, 0x61, 0x04, 0xFA, 0xA9, 0x00, 0x8D])) + rom.write_bytes(0x05F510, bytearray([0x67, 0x14, 0xA9, 0x10, 0x8D, 0x83, 0x0B, 0x5C, 0xDE, 0xC0, 0x01, 0xE2, 0x20, 0xAD, 0x7D, 0x02])) + rom.write_bytes(0x05F520, bytearray([0xF0, 0x25, 0xC2, 0x20, 0xAD, 0x7E, 0x02, 0xE2, 0x20, 0xF0, 0x12, 0xA9, 0x02, 0x8D, 0x00, 0x02])) + rom.write_bytes(0x05F530, bytearray([0xC2, 0x20, 0xAD, 0x7E, 0x02, 0x3A, 0x8D, 0x7E, 0x02, 0xE2, 0x20, 0x80, 0x0A, 0xA9, 0x0F, 0x8D])) + rom.write_bytes(0x05F540, bytearray([0x00, 0x02, 0xA9, 0x00, 0x8D, 0x7D, 0x02, 0xAD, 0x68, 0x14, 0xF0, 0x32, 0xC2, 0x20, 0xAD, 0x69])) + rom.write_bytes(0x05F550, bytearray([0x14, 0xF0, 0x1B, 0x3A, 0x8D, 0x69, 0x14, 0xE2, 0x20, 0x4C, 0x40, 0xFD, 0xE8, 0x1F, 0x70, 0xAD])) + rom.write_bytes(0x05F560, bytearray([0xD0, 0x00, 0xD0, 0x08, 0xEE, 0xD0, 0x00, 0xA9, 0x21, 0x8D, 0x53, 0x00, 0x80, 0x10, 0xE2, 0x20])) + rom.write_bytes(0x05F570, bytearray([0xA9, 0x22, 0x8D, 0x53, 0x00, 0xA9, 0x00, 0x8D, 0x68, 0x14, 0x8F, 0xE8, 0x1F, 0x70, 0x22, 0x59])) + rom.write_bytes(0x05F580, bytearray([0x82, 0x00, 0x5C, 0x8F, 0xC1, 0x01, 0xAC, 0xB4, 0x03, 0xC0, 0x14, 0x30, 0x20, 0x48, 0xDA, 0xE2])) + rom.write_bytes(0x05F590, bytearray([0x20, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9, 0x01, 0x0C, 0xD1, 0x00, 0xAD])) + rom.write_bytes(0x05F5A0, bytearray([0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xC2, 0x20, 0xFA, 0x68, 0x5C, 0x6C, 0xB3, 0x03, 0x5C, 0x6D, 0xB3])) + rom.write_bytes(0x05F5B0, bytearray([0x03, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9, 0x08, 0x0C, 0xD1, 0x00, 0xAD])) + rom.write_bytes(0x05F5C0, bytearray([0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xAE, 0x57, 0x0B, 0xE0, 0x0D, 0x5C, 0x85, 0xB5, 0x01, 0xA0, 0x05])) + rom.write_bytes(0x05F5D0, bytearray([0x8C, 0xB8, 0x03, 0x08, 0xE2, 0x20, 0xDA, 0x48, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1])) + rom.write_bytes(0x05F5E0, bytearray([0x00, 0xA9, 0x02, 0x0C, 0xD1, 0x00, 0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0x68, 0xFA, 0xC2, 0x20])) + rom.write_bytes(0x05F5F0, bytearray([0x28, 0x5C, 0xCB, 0xB4, 0x0E, 0xC2, 0x20, 0xAD, 0xB6, 0x03, 0xC9, 0x2C, 0x01, 0x90, 0x18, 0xE2])) + rom.write_bytes(0x05F600, bytearray([0x20, 0xDA, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9, 0x04, 0x0C, 0xD1, 0x00])) + rom.write_bytes(0x05F610, bytearray([0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xFA, 0x9C, 0x84, 0x0B, 0xE2, 0x20, 0xAD, 0x0F, 0x0D, 0x5C])) + rom.write_bytes(0x05F620, bytearray([0xE4, 0xC0, 0x01, 0xC2, 0x20, 0x48, 0xE2, 0x20, 0xA9, 0x1F, 0x8D, 0x18, 0x01, 0xDA, 0x5A, 0x8B])) + rom.write_bytes(0x05F630, bytearray([0x4C, 0xB2, 0xFA, 0xC2, 0x20, 0xC2, 0x10, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xAA, 0xE2, 0x20, 0xA9])) + rom.write_bytes(0x05F640, bytearray([0x00, 0xEB, 0xA9, 0x7F, 0xA0, 0x40, 0x14, 0x54, 0x7E, 0x70, 0xE2, 0x10, 0xAB, 0x7A, 0xFA, 0xC2])) + rom.write_bytes(0x05F650, bytearray([0x20, 0x68, 0xE2, 0x20, 0x5C, 0x3C, 0x99, 0x17, 0xC2, 0x20, 0x48, 0xC2, 0x10, 0xDA, 0x5A, 0x8B])) + rom.write_bytes(0x05F660, bytearray([0xAD, 0x0E, 0x03, 0x29, 0x0F, 0x00, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xA8, 0xE2, 0x20, 0xA9, 0x00])) + rom.write_bytes(0x05F670, bytearray([0xEB, 0xA9, 0x7F, 0xA2, 0x40, 0x14, 0x54, 0x70, 0x7E, 0xAB, 0x7A, 0xFA, 0xE2, 0x10, 0xC2, 0x20])) + rom.write_bytes(0x05F680, bytearray([0x68, 0xE2, 0x20, 0xAD, 0x3D, 0x09, 0x29, 0x20, 0x5C, 0x4F, 0xE1, 0x17, 0xE2, 0x20, 0xAD, 0xD2])) + rom.write_bytes(0x05F690, bytearray([0x00, 0xC2, 0x20, 0xD0, 0x09, 0xA9, 0xC1, 0xB1, 0x85, 0x10, 0x5C, 0xA4, 0xD0, 0x01, 0xA9, 0x00])) + rom.write_bytes(0x05F6A0, bytearray([0x00, 0x85, 0x10, 0x85, 0x12, 0x85, 0x14, 0x85, 0x16, 0x5C, 0xB3, 0xD0, 0x01, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x05F6B0, bytearray([0xD2, 0x00, 0xC2, 0x20, 0xD0, 0x09, 0xA9, 0x6F, 0x01, 0x05, 0x02, 0x5C, 0xBA, 0xBC, 0x01, 0x5C])) + rom.write_bytes(0x05F6C0, bytearray([0xBC, 0xBC, 0x01, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2, 0x20, 0xD0, 0x0B, 0xBF, 0xED, 0xB7, 0x01])) + rom.write_bytes(0x05F6D0, bytearray([0x29, 0xFF, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xBF, 0x48, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0xC9, 0x80])) + rom.write_bytes(0x05F6E0, bytearray([0x00, 0xF0, 0x04, 0x5C, 0x79, 0xD0, 0x01, 0xBF, 0x66, 0xD3, 0x22, 0xDA, 0xAA, 0xAD, 0xD3, 0x00])) + rom.write_bytes(0x05F6F0, bytearray([0x29, 0xFF, 0x00, 0xC9, 0x01, 0x00, 0xF0, 0x21, 0xC9, 0x02, 0x00, 0xF0, 0x38, 0xBD, 0x40, 0x14])) + rom.write_bytes(0x05F700, bytearray([0x29, 0xFF, 0x00, 0xF0, 0x0C, 0xFA, 0xBF, 0x87, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0x5C, 0x79, 0xD0])) + rom.write_bytes(0x05F710, bytearray([0x01, 0xFA, 0xA9, 0x4E, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xBD, 0x4A, 0x14, 0x29, 0xFF, 0x00, 0xF0])) + rom.write_bytes(0x05F720, bytearray([0x0C, 0xFA, 0xBF, 0xA5, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xFA, 0xA9, 0x4E])) + rom.write_bytes(0x05F730, bytearray([0x00, 0x5C, 0x79, 0xD0, 0x01, 0xE0, 0x09, 0xD0, 0x05, 0xAD, 0x64, 0x14, 0x80, 0x03, 0xBD, 0x54])) + rom.write_bytes(0x05F740, bytearray([0x14, 0x29, 0xFF, 0x00, 0xF0, 0x0C, 0xFA, 0xBF, 0xC3, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0x5C, 0x79])) + rom.write_bytes(0x05F750, bytearray([0xD0, 0x01, 0xFA, 0xA9, 0x4E, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2])) + rom.write_bytes(0x05F760, bytearray([0x20, 0xD0, 0x08, 0xBF, 0x5F, 0xB8, 0x01, 0x5C, 0x7E, 0xD0, 0x01, 0xAD, 0xD3, 0x00, 0x29, 0xFF])) + rom.write_bytes(0x05F770, bytearray([0x00, 0xC9, 0x01, 0x00, 0xF0, 0x3C, 0xC9, 0x02, 0x00, 0xF0, 0x4B, 0xBF, 0x5F, 0xB8, 0x01, 0x05])) + rom.write_bytes(0x05F780, bytearray([0x18, 0x99, 0xA1, 0xB1, 0xBF, 0xDD, 0xB8, 0x01, 0x05, 0x18, 0x99, 0xE1, 0xB1, 0xFA, 0xC8, 0xC8])) + rom.write_bytes(0x05F790, bytearray([0xE8, 0xE0, 0x1D, 0x90, 0x19, 0xEE, 0xD3, 0x00, 0xA0, 0x00, 0xA2, 0x00, 0xAD, 0xD3, 0x00, 0x29])) + rom.write_bytes(0x05F7A0, bytearray([0xFF, 0x00, 0xC9, 0x03, 0x00, 0xD0, 0x07, 0x9C, 0xD3, 0x00, 0x5C, 0x94, 0xD0, 0x01, 0x5C, 0x71])) + rom.write_bytes(0x05F7B0, bytearray([0xD0, 0x01, 0xBF, 0x5F, 0xB8, 0x01, 0x05, 0x18, 0x99, 0x21, 0xB2, 0xBF, 0xDD, 0xB8, 0x01, 0x05])) + rom.write_bytes(0x05F7C0, bytearray([0x18, 0x99, 0x61, 0xB2, 0x80, 0xC7, 0xBF, 0x5F, 0xB8, 0x01, 0x05, 0x18, 0x99, 0xA1, 0xB2, 0xBF])) + rom.write_bytes(0x05F7D0, bytearray([0xDD, 0xB8, 0x01, 0x05, 0x18, 0x99, 0xE1, 0xB2, 0x80, 0xB3, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2])) + rom.write_bytes(0x05F7E0, bytearray([0x20, 0xD0, 0x0A, 0x64, 0x18, 0xAF, 0xB8, 0x03, 0x00, 0x5C, 0x80, 0xD1, 0x01, 0x5C, 0x02, 0xD2])) + rom.write_bytes(0x05F7F0, bytearray([0x01, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2, 0x20, 0xD0, 0x08, 0x64, 0x18, 0xA0, 0x00, 0x5C, 0xE2])) + rom.write_bytes(0x05F800, bytearray([0xD0, 0x01, 0x5C, 0x02, 0xD2, 0x01, 0xAD, 0xD2, 0x00, 0x29, 0xFF, 0x00, 0xD0, 0x08, 0xBF, 0x35])) + rom.write_bytes(0x05F810, bytearray([0xB8, 0x01, 0x5C, 0xB8, 0xCF, 0x01, 0xBF, 0xE1, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0xC9, 0x80, 0x00])) + rom.write_bytes(0x05F820, bytearray([0xF0, 0x2E, 0xC9, 0x81, 0x00, 0xF0, 0x47, 0x5C, 0xB8, 0xCF, 0x01, 0xAD, 0xD2, 0x00, 0x29, 0xFF])) + rom.write_bytes(0x05F830, bytearray([0x00, 0xD0, 0x08, 0xBF, 0x4A, 0xB8, 0x01, 0x5C, 0xD4, 0xCF, 0x01, 0xBF, 0xF6, 0xD3, 0x22, 0x29])) + rom.write_bytes(0x05F840, bytearray([0xFF, 0x00, 0x4C, 0xB6, 0xFD, 0xF0, 0x18, 0xC9, 0x81, 0x00, 0xF0, 0x30, 0x5C, 0xD4, 0xCF, 0x01])) + rom.write_bytes(0x05F850, bytearray([0xDA, 0xE2, 0x20, 0xAD, 0xB3, 0x14, 0xAA, 0xC2, 0x20, 0x20, 0x8A, 0xF8, 0xFA, 0x80, 0xC8, 0xDA])) + rom.write_bytes(0x05F860, bytearray([0xE2, 0x20, 0xAD, 0xB3, 0x14, 0xAA, 0xC2, 0x20, 0x20, 0xBD, 0xF8, 0xFA, 0x80, 0xDE, 0xDA, 0xE2])) + rom.write_bytes(0x05F870, bytearray([0x20, 0xAF, 0x85, 0xFC, 0x0D, 0xAA, 0x20, 0x8A, 0xF8, 0xFA, 0x80, 0xAB, 0xDA, 0xE2, 0x20, 0xAF])) + rom.write_bytes(0x05F880, bytearray([0x86, 0xFC, 0x0D, 0xAA, 0x20, 0xBD, 0xF8, 0xFA, 0x80, 0xC2, 0xE2, 0x20, 0xC9, 0x0A, 0xB0, 0x1F])) + rom.write_bytes(0x05F890, bytearray([0xAD, 0xD5, 0x00, 0xD0, 0x0D, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xA9, 0x50, 0x00, 0xEE, 0xD5])) + rom.write_bytes(0x05F8A0, bytearray([0x00, 0x60, 0xBF, 0x0B, 0xD4, 0x22, 0x9C, 0xD4, 0x00, 0x9C, 0xD5, 0x00, 0xC2, 0x20, 0x60, 0xAD])) + rom.write_bytes(0x05F8B0, bytearray([0xD4, 0x00, 0xD0, 0xEE, 0xEE, 0xD4, 0x00, 0xC2, 0x20, 0xA9, 0x52, 0x00, 0x60, 0xE2, 0x20, 0xC9])) + rom.write_bytes(0x05F8C0, bytearray([0x0A, 0xB0, 0x1F, 0xAD, 0xD6, 0x00, 0xD0, 0x0D, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xA9, 0x50])) + rom.write_bytes(0x05F8D0, bytearray([0x00, 0xEE, 0xD6, 0x00, 0x60, 0xBF, 0x0B, 0xD4, 0x22, 0x9C, 0xD7, 0x00, 0x9C, 0xD6, 0x00, 0xC2])) + rom.write_bytes(0x05F8E0, bytearray([0x20, 0x60, 0xAD, 0xD7, 0x00, 0xD0, 0xEE, 0xEE, 0xD7, 0x00, 0xC2, 0x20, 0xA9, 0x52, 0x00, 0x60])) + rom.write_bytes(0x05F8F0, bytearray([0xAD, 0xD2, 0x00, 0x29, 0xFF, 0x00, 0xF0, 0x04, 0x5C, 0x74, 0xD2, 0x01, 0x64, 0x18, 0xA0, 0x00])) + rom.write_bytes(0x05F900, bytearray([0x5C, 0x07, 0xD2, 0x01, 0xAD, 0xD2, 0x00, 0x29, 0xFF, 0x00, 0xF0, 0x04, 0x5C, 0x74, 0xD2, 0x01])) + rom.write_bytes(0x05F910, bytearray([0xAF, 0x7C, 0x02, 0x00, 0x5C, 0x7B, 0xD2, 0x01, 0xA5, 0x38, 0x89, 0x20, 0xD0, 0x0A, 0x29, 0x10])) + rom.write_bytes(0x05F920, bytearray([0xF0, 0x02, 0xA9, 0x01, 0x5C, 0x08, 0xC1, 0x01, 0xEE, 0xD2, 0x00, 0x64, 0x38, 0x5C, 0x08, 0xC1])) + rom.write_bytes(0x05F930, bytearray([0x01, 0xAD, 0xD2, 0x00, 0xD0, 0x08, 0xA5, 0x38, 0x29, 0x20, 0x5C, 0x3B, 0xC1, 0x01, 0xA9, 0x00])) + rom.write_bytes(0x05F940, bytearray([0x80, 0xF8, 0xAD, 0x10, 0x0B, 0x49, 0x01, 0x9C, 0xD2, 0x00, 0x5C, 0x4D, 0xCE, 0x01, 0x9C, 0x01])) + rom.write_bytes(0x05F950, bytearray([0x02, 0xAD, 0x5E, 0x02, 0xF0, 0x16, 0xAD, 0xB0, 0x14, 0x89, 0x08, 0xD0, 0x15, 0xAD, 0xB3, 0x14])) + rom.write_bytes(0x05F960, bytearray([0xCF, 0x85, 0xFC, 0x0D, 0x90, 0x06, 0xA9, 0x80, 0x8F, 0x65, 0x02, 0x7E, 0xC2, 0x20, 0x5C, 0xBB])) + rom.write_bytes(0x05F970, bytearray([0xA5, 0x17, 0xA9, 0x01, 0x80, 0xF2, 0xE2, 0x20, 0xAF, 0x87, 0xFC, 0x0D, 0xC2, 0x20, 0xF0, 0x0D])) + rom.write_bytes(0x05F980, bytearray([0x4C, 0xBF, 0xFA, 0x8D, 0x1C, 0x0C, 0x8D, 0x1E, 0x0C, 0x5C, 0x4E, 0xD8, 0x03, 0xB9, 0x04, 0x0C])) + rom.write_bytes(0x05F990, bytearray([0x80, 0xF1, 0xE2, 0x20, 0xA9, 0x01, 0x8D, 0xD8, 0x00, 0xC2, 0x20, 0x22, 0xBE, 0xAE, 0x03, 0x5C])) + rom.write_bytes(0x05F9A0, bytearray([0xA2, 0xA0, 0x02, 0xE2, 0x20, 0xAD, 0xD8, 0x00, 0xD0, 0x0F, 0xC2, 0x20, 0xA9, 0x02, 0x00, 0x9D])) + rom.write_bytes(0x05F9B0, bytearray([0x96, 0x7A, 0xFE, 0x78, 0x79, 0x5C, 0xAF, 0xA3, 0x02, 0xAD, 0xB3, 0x14, 0xCF, 0x86, 0xFC, 0x0D])) + rom.write_bytes(0x05F9C0, bytearray([0xC2, 0x20, 0xB0, 0xE8, 0xC2, 0x20, 0x5C, 0x81, 0xA3, 0x02, 0xE2, 0x20, 0xDA, 0xAE, 0x1A, 0x02])) + rom.write_bytes(0x05F9D0, bytearray([0xBD, 0x6D, 0x14, 0x89, 0x20, 0xF0, 0x0D, 0xFA, 0xC2, 0x20, 0xAD, 0x02, 0x74, 0xC9, 0x32, 0x00])) + rom.write_bytes(0x05F9E0, bytearray([0x5C, 0x80, 0xDF, 0x02, 0x18, 0x69, 0x20, 0x9D, 0x6D, 0x14, 0xAD, 0xB3, 0x14, 0x1A, 0x8D, 0xB3])) + rom.write_bytes(0x05F9F0, bytearray([0x14, 0x80, 0xE4, 0xE2, 0x20, 0xDA, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9])) + rom.write_bytes(0x05FA00, bytearray([0x10, 0x0C, 0xD1, 0x00, 0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xFA, 0xC2, 0x20, 0xA9, 0x36, 0x00])) + rom.write_bytes(0x05FA10, bytearray([0x22, 0xD2, 0x85, 0x00, 0x5C, 0xEB, 0xC9, 0x11, 0xB9, 0xE4, 0xB9, 0xC0, 0x00, 0xF0, 0x03, 0xEE])) + rom.write_bytes(0x05FA20, bytearray([0xD9, 0x00, 0x5C, 0xBB, 0xB9, 0x10, 0xA9, 0x06, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0x19, 0xB0])) + rom.write_bytes(0x05FA30, bytearray([0x10, 0xA9, 0x05, 0x00, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0x9A, 0xD0, 0x10, 0xA9, 0x06, 0x85])) + rom.write_bytes(0x05FA40, bytearray([0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0xC9, 0xD2, 0x10, 0xA9, 0x05, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C])) + rom.write_bytes(0x05FA50, bytearray([0xEE, 0xC5, 0x10, 0xA9, 0x05, 0x00, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0x0F, 0xBE, 0x10, 0xDA])) + rom.write_bytes(0x05FA60, bytearray([0xE2, 0x20, 0xAD, 0xD9, 0x00, 0xF0, 0x26, 0xA2, 0x00, 0xAD, 0x1A, 0x02, 0xDF, 0x18, 0xD4, 0x22])) + rom.write_bytes(0x05FA70, bytearray([0xF0, 0x07, 0xE8, 0xE0, 0x06, 0xF0, 0x16, 0x80, 0xF3, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D])) + rom.write_bytes(0x05FA80, bytearray([0xD1, 0x00, 0xA9, 0x10, 0x0C, 0xD1, 0x00, 0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xFA, 0x22, 0x67])) + rom.write_bytes(0x05FA90, bytearray([0xFA, 0x04, 0x5C, 0x5A, 0xA1, 0x10])) + + rom.write_bytes(0x05FAB2, bytearray([0xA9, 0x00, 0xEB, 0xAD, 0x0E, 0x03, 0xC2, 0x20, 0xC2, 0x10, 0x4C, 0x37, 0xF6])) + rom.write_bytes(0x05FABF, bytearray([0xE2])) + rom.write_bytes(0x05FAC0, bytearray([0x20, 0xAD, 0x1A, 0x02, 0xDA, 0xA2, 0x00, 0x00, 0xDF, 0x1E, 0xD4, 0x22, 0xF0, 0x11, 0xE8, 0xE0])) + rom.write_bytes(0x05FAD0, bytearray([0x01, 0x00, 0xF0, 0x02, 0x80, 0xF2, 0xFA, 0xC2, 0x20, 0xA9, 0x00, 0x00, 0x4C, 0x83, 0xF9, 0xFA])) + rom.write_bytes(0x05FAE0, bytearray([0xC2, 0x20, 0x4C, 0x8D, 0xF9])) + rom.write_bytes(0x05FAE5, bytearray([0x48, 0xE2, 0x20, 0xAD, 0x5D, 0x14, 0xC9, 0x01, 0xF0, 0x07])) + rom.write_bytes(0x05FAEF, bytearray([0xC2, 0x20, 0x68, 0x5C, 0xCE, 0xBE, 0x03, 0xAD, 0xCC, 0x00, 0xD0, 0xF4, 0xAF, 0xFA, 0x1D, 0x70])) + rom.write_bytes(0x05FAFF, bytearray([0xF0, 0xEE, 0xA9, 0x00, 0x8F, 0xFA, 0x1D, 0x70, 0x80, 0xE6])) + rom.write_bytes(0x05FB09, bytearray([0x48, 0xE2, 0x20, 0xAD, 0xCC, 0x00, 0xF0, 0x06, 0xCE, 0xCC, 0x00, 0xCE, 0xCC, 0x00, 0xC2, 0x20, 0x68, 0x22, 0x87, 0xBF, 0x03, 0x5C, 0x89, 0xFE, 0x07])) + rom.write_bytes(0x05FB22, bytearray([0xA0, 0x0A, 0x8C, 0x4D, 0x00, 0xE2, 0x20, 0xA9, 0x08, 0x0C, 0xB0, 0x14, 0x8D, 0xB6, 0x14, 0xC2, 0x20, 0x5C, 0xB9, 0xF1, 0x0D, 0x0D, 0xA8, 0xE2])) + rom.write_bytes(0x05FB3A, bytearray([0x20, 0xA9, 0x08, 0x0C, 0xB0, 0x14, 0xA9, 0x00, 0xEB, 0xA9, 0x7F, 0xA2, 0x40, 0x14, 0x54, 0x70, 0x7E, 0xAB, 0x7A, 0xFA, 0x1A, 0xEE, 0x14, 0xC2, 0x20, 0x68, 0x5C, 0xB9, 0xF1, 0x0D])) + rom.write_bytes(0x05FB58, bytearray([0x4C, 0xDD, 0xFB, 0x04, 0xAF, 0xAC, 0x00, 0x70])) + rom.write_bytes(0x05FB60, bytearray([0xD0, 0x2C, 0xAD, 0x35, 0x00, 0xC9, 0x50, 0xD0, 0x25, 0xAD, 0xDA, 0x00, 0xC9, 0x80, 0xF0, 0x11])) + rom.write_bytes(0x05FB70, bytearray([0xC9, 0x00, 0xF0, 0x21, 0xC9, 0x2A, 0xF0, 0x1D, 0xC9, 0x54, 0xF0, 0x19, 0xEE, 0xDA, 0x00, 0x80])) + rom.write_bytes(0x05FB80, bytearray([0x10, 0xA9, 0x2F, 0x8D, 0x53, 0x00, 0xA9, 0x11, 0x8D, 0x18, 0x01, 0xEE, 0xDB, 0x00, 0x9C, 0xDA])) + rom.write_bytes(0x05FB90, bytearray([0x00, 0x5C, 0x93, 0xC1, 0x01, 0xA9, 0x28, 0x8D, 0x53, 0x00, 0x80, 0xE0])) + rom.write_bytes(0x05FB9C, bytearray([0xA9, 0x93, 0x00, 0xEE])) + rom.write_bytes(0x05FBA0, bytearray([0xB4, 0x03, 0xAC, 0xB4, 0x03, 0xC0, 0x14, 0x00, 0x90, 0x14, 0xE2, 0x20, 0xDA, 0xAE, 0x1A, 0x02])) + rom.write_bytes(0x05FBB0, bytearray([0xBD, 0x6D, 0x14, 0x09, 0x01, 0x9D, 0x6D, 0x14, 0xFA, 0xC2, 0x20, 0xA9, 0x94, 0x00, 0x5C, 0xF1, 0xDF, 0x00])) + rom.write_bytes(0x05FBC2, bytearray([0x48, 0xC9, 0x06, 0x00, 0xB0, 0x10, 0xE2, 0x20, 0xAD, 0x54, 0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0])) + rom.write_bytes(0x05FBD2, bytearray([0x05, 0x68, 0x5C, 0xAC, 0x82, 0x07, 0x68, 0x5C, 0xFB, 0x81, 0x07, 0xAD, 0x6A, 0x02, 0xF0, 0x11])) + rom.write_bytes(0x05FBE2, bytearray([0xC2, 0x20, 0xA9, 0x0E, 0x00, 0x22, 0xE2, 0xF6, 0x04, 0xA9, 0x00, 0x00, 0x8D, 0x6A, 0x02, 0xE2])) + rom.write_bytes(0x05FBF2, bytearray([0x20, 0x22, 0x28, 0xFD, 0x04, 0x4C, 0x5C, 0xFB, 0xAF, 0xB0, 0x23, 0x7E, 0xF0, 0x18, 0xAF, 0xAC])) + rom.write_bytes(0x05FC02, bytearray([0x00, 0x70, 0x29, 0xFF, 0x00, 0xD0, 0x0F, 0xAF, 0xB0, 0x23, 0x7E, 0x8F, 0xEC, 0x61, 0x04, 0xA9])) + rom.write_bytes(0x05FC12, bytearray([0x00, 0x00, 0x8F, 0xB0, 0x23, 0x7E, 0xBD, 0xD0, 0x61, 0xF0, 0x03, 0xDE, 0xD0, 0x61, 0x5C, 0x79])) + rom.write_bytes(0x05FC22, bytearray([0xDE, 0x04, 0x48, 0xC2, 0x20, 0xAF, 0xEC, 0x61, 0x04, 0xD0, 0x0B, 0xE2, 0x20, 0x68, 0x22, 0xCE])) + rom.write_bytes(0x05FC32, bytearray([0xC0, 0x01, 0x5C, 0x8B, 0xB5, 0x01, 0x8F, 0xB0, 0x23, 0x7E, 0xA9, 0x00, 0x00, 0x8F, 0xEC, 0x61])) + rom.write_bytes(0x05FC42, bytearray([0x04, 0x80, 0xE8, 0x48, 0xDA, 0xE2, 0x20, 0x4C, 0xA5, 0xFC, 0xA2, 0x00, 0xDF, 0x1F, 0xD4, 0x22])) + rom.write_bytes(0x05FC52, bytearray([0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0x8D, 0xFC, 0x0D, 0xAA, 0xBF, 0x2A, 0xD4, 0x22, 0x8D, 0xDC])) + rom.write_bytes(0x05FC62, bytearray([0x00, 0xC2, 0x20, 0xFA, 0x68, 0x0D, 0xDC, 0x00, 0x95, 0x76, 0x5C, 0x29, 0xDB, 0x0C, 0xE2, 0x20])) + rom.write_bytes(0x05FC72, bytearray([0xAD, 0x48, 0x0B, 0xF0, 0x23, 0xAF, 0xBE, 0x03, 0x02, 0xC9, 0x02, 0xD0, 0x1B, 0xAD, 0x1A, 0x02])) + rom.write_bytes(0x05FC82, bytearray([0xA2, 0x00, 0xDF, 0x1F, 0xD4, 0x22, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0x8A, 0x0A, 0xAA, 0xC2, 0x20])) + rom.write_bytes(0x05FC92, bytearray([0xBF, 0x35, 0xD4, 0x22, 0x8F, 0xBE, 0x03, 0x02, 0xC2, 0x20, 0xEE, 0xAC, 0x03, 0xC2, 0x10, 0x5C])) + rom.write_bytes(0x05FCA2, bytearray([0x0C, 0x95, 0x02, 0xAD, 0x1A, 0x02, 0xC9, 0x43, 0xF0, 0x03, 0x4C, 0x4C, 0xFC, 0xA9, 0x0A, 0x4C])) + rom.write_bytes(0x05FCB2, bytearray([0x60, 0xFC, 0xAC, 0xB4, 0x03, 0xC0, 0x14, 0x30, 0x14, 0x1A, 0xE2, 0x20, 0xDA, 0x48, 0xAE, 0x1A])) + rom.write_bytes(0x05FCC2, bytearray([0x02, 0xBD, 0x6D, 0x14, 0x09, 0x01, 0x9D, 0x6D, 0x14, 0x68, 0xFA, 0xC2, 0x20, 0x22, 0xD2, 0x85])) + rom.write_bytes(0x05FCD2, bytearray([0x00, 0x5C, 0xA4, 0x9E, 0x03, 0xE2, 0x20, 0xAD, 0xF6, 0x7D, 0xC9, 0x0C, 0xB0, 0x1A, 0xAD, 0xCC])) + rom.write_bytes(0x05FCE2, bytearray([0x00, 0xAD, 0x5E, 0x14, 0x38, 0xED, 0xCC, 0x00, 0x3A, 0x3A, 0x8D, 0xDE, 0x00, 0xAD, 0xF6, 0x7D])) + rom.write_bytes(0x05FCF2, bytearray([0x38, 0xED, 0xCC, 0x00, 0x18, 0xCD, 0xDE, 0x00, 0xC2, 0x20, 0x5C, 0xBC, 0x9A, 0x02, 0xE2, 0x20])) + rom.write_bytes(0x05FD02, bytearray([0xAD, 0x5D, 0x14, 0xF0, 0x33, 0xAA, 0xBF, 0xA3, 0xAF, 0x09, 0x18, 0x6D, 0xCC, 0x00, 0x8D, 0x5E])) + rom.write_bytes(0x05FD12, bytearray([0x14, 0xAD, 0xF6, 0x7D, 0xC9, 0x0C, 0xB0, 0x1A, 0xAD, 0xCC, 0x00, 0xAD, 0x5E, 0x14, 0x38, 0xED])) + rom.write_bytes(0x05FD22, bytearray([0xCC, 0x00, 0x3A, 0x3A, 0x8D, 0xDE, 0x00, 0xAD, 0xF6, 0x7D, 0x38, 0xED, 0xCC, 0x00, 0x18, 0xCD])) + rom.write_bytes(0x05FD32, bytearray([0xDE, 0x00, 0xC2, 0x20, 0x5C, 0xAC, 0xDC, 0x01, 0x1A, 0x8D, 0x5D, 0x14, 0x80, 0xC0, 0xA9, 0x00])) + rom.write_bytes(0x05FD42, bytearray([0x8F, 0xE8, 0x1F, 0x70, 0xAD, 0xAC, 0x60, 0xC9, 0x00, 0xD0, 0x06, 0xA9, 0x01, 0x8F, 0xE8, 0x1F])) + rom.write_bytes(0x05FD52, bytearray([0x70, 0x4C, 0x5F, 0xF5, 0xDA, 0xAD, 0x1A, 0x02, 0x8D, 0x7C, 0x02, 0xAD, 0x12, 0x11, 0xC9, 0x08])) + rom.write_bytes(0x05FD62, bytearray([0xB0, 0x1D, 0xAD, 0x18, 0x02, 0x4A, 0xAA, 0xA9, 0x00, 0xE0, 0x00, 0xF0, 0x06, 0x18, 0x69, 0x08])) + rom.write_bytes(0x05FD72, bytearray([0xCA, 0x80, 0xF6, 0x18, 0x6D, 0x12, 0x11, 0xAA, 0xBF, 0x4B, 0xD4, 0x22, 0x8D, 0x1A, 0x02, 0xFA])) + rom.write_bytes(0x05FD82, bytearray([0xA9, 0x02, 0x8D, 0x13, 0x11, 0x5C, 0x70, 0xE0, 0x17, 0xAC, 0x7C, 0x02, 0x8C, 0x1A, 0x02, 0xB9])) + rom.write_bytes(0x05FD92, bytearray([0x22, 0x02, 0x5C, 0xA7, 0x82, 0x10, 0xAD, 0x7C, 0x02, 0x8D, 0x1A, 0x02, 0xC2, 0x20, 0xE2, 0x10])) + rom.write_bytes(0x05FDA2, bytearray([0x5C, 0xBC, 0xB2, 0x01, 0xC9, 0x45, 0xB0, 0x03, 0x8D, 0x7C, 0x02, 0x8D, 0x1A, 0x02, 0x29, 0x07])) + rom.write_bytes(0x05FDB2, bytearray([0x5C, 0x3A, 0x81, 0x10, 0xC9, 0x82, 0x00, 0xF0, 0x2E, 0xC9, 0x83, 0x00, 0xF0, 0x40, 0xC9, 0x84])) + rom.write_bytes(0x05FDC2, bytearray([0x00, 0xF0, 0x0B, 0xC9, 0x85, 0x00, 0xF0, 0x0E, 0xC9, 0x80, 0x00, 0x4C, 0x45, 0xF8, 0xE2, 0x20])) + rom.write_bytes(0x05FDD2, bytearray([0xAF, 0x99, 0xFC, 0x0D, 0x80, 0x16, 0xDA, 0xE2, 0x20, 0xAD, 0xE3, 0x00, 0xD0, 0x4E, 0x9C, 0xE1])) + rom.write_bytes(0x05FDE2, bytearray([0x00, 0xAF, 0x99, 0xFC, 0x0D, 0x80, 0x25, 0xE2, 0x20, 0xAD, 0xB5, 0x14, 0xC9, 0x64, 0xC2, 0x20])) + rom.write_bytes(0x05FDF2, bytearray([0xB0, 0x06, 0xA9, 0x4E, 0x00, 0x4C, 0x4C, 0xF8, 0xA9, 0x52, 0x00, 0x4C, 0x4C, 0xF8, 0xDA, 0xE2])) + rom.write_bytes(0x05FE02, bytearray([0x20, 0xAD, 0xE3, 0x00, 0xD0, 0x26, 0x9C, 0xE1, 0x00, 0xAD, 0xB5, 0x14, 0xC9, 0x0A, 0x90, 0x08])) + rom.write_bytes(0x05FE12, bytearray([0x38, 0xE9, 0x0A, 0xEE, 0xE1, 0x00, 0x80, 0xF4, 0x8D, 0xE2, 0x00, 0xEE, 0xE3, 0x00, 0xAD, 0xE1])) + rom.write_bytes(0x05FE22, bytearray([0x00, 0xAA, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xFA, 0x4C, 0x46, 0xF8, 0x9C, 0xE3, 0x00, 0xAD])) + rom.write_bytes(0x05FE32, bytearray([0xE2, 0x00, 0xAA, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xFA, 0x4C, 0x4C, 0xF8, 0x22, 0xB7, 0xB2])) + rom.write_bytes(0x05FE42, bytearray([0x01, 0xEA, 0xEA, 0xEA, 0xAE, 0x7C, 0x02, 0x8E, 0x1A, 0x02, 0xEA, 0xEA, 0xEA, 0xEA, 0x5C, 0xAC])) + rom.write_bytes(0x05FE52, bytearray([0xBE, 0x01, 0xE2, 0x20, 0xAD, 0x1A, 0x02, 0xc9, 0x3C, 0xC2, 0x20, 0xB0, 0x04, 0xA9, 0x02, 0x00])) + rom.write_bytes(0x05FE62, bytearray([0x6B, 0xA9, 0x00, 0x00, 0x6B, 0xAD, 0x18, 0x01, 0xC9, 0x19, 0xD0, 0x3A, 0xC2, 0x20, 0x48, 0xA9])) + rom.write_bytes(0x05FE72, bytearray([0x00, 0x00, 0xE2, 0x20, 0xAF, 0x9A, 0xFC, 0x0D, 0xD0, 0x05, 0xA9, 0x08, 0x0C, 0xB0, 0x14, 0xC2])) + rom.write_bytes(0x05FE82, bytearray([0x10, 0xDA, 0x5A, 0x8B, 0xAD, 0x0E, 0x03, 0xC2, 0x20, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xA8, 0xE2])) + rom.write_bytes(0x05FE92, bytearray([0x20, 0xA9, 0x00, 0xEB, 0xA9, 0x7F, 0xA2, 0x40, 0x14, 0x54, 0x70, 0x7E, 0xAB, 0x7A, 0xFA, 0xC2])) + rom.write_bytes(0x05FEA2, bytearray([0x20, 0x68, 0xE2, 0x20, 0xE2, 0x10, 0x22, 0x4B, 0x82, 0x00, 0x5C, 0xD9, 0x87, 0x17])) + + rom.write_bytes(0x00EDD0, bytearray([0xda, 0xa2, 0x00, 0x00, 0xe2, 0x20, 0xc9, 0x00, 0xf0, 0x0b, 0x48, 0x8a, 0x18, 0x69, 0x0c, 0xaa])) + rom.write_bytes(0x00EDE0, bytearray([0x68, 0x3a, 0x3a, 0x80, 0xf1, 0x98, 0x4a, 0x8f, 0x80, 0x24, 0x7e, 0x18, 0x8a, 0x6f, 0x80, 0x24])) + rom.write_bytes(0x00EDF0, bytearray([0x7e, 0xaa, 0xbf, 0x00, 0x80, 0x02, 0x0a, 0xaa, 0xc2, 0x20, 0xbf, 0x8e, 0xd4, 0x22, 0xfa, 0x18])) + rom.write_bytes(0x00EE00, bytearray([0x5c, 0xb6, 0xc0, 0x17, 0xda, 0xe2, 0x20, 0xa2, 0x00, 0x00, 0xdf, 0x4b, 0xd4, 0x22, 0xf0, 0x0e])) + rom.write_bytes(0x00EE10, bytearray([0xe8, 0xe0, 0x30, 0x00, 0xb0, 0x02, 0x80, 0xf2, 0xa9, 0x00, 0xeb, 0xad, 0x1a, 0x02, 0xaa, 0xbf])) + rom.write_bytes(0x00EE20, bytearray([0x00, 0x80, 0x02, 0xaa, 0xbf, 0x9e, 0xd4, 0x22, 0xc2, 0x20, 0x29, 0xff, 0x00, 0xfa, 0x5c, 0xfd])) + rom.write_bytes(0x00EE30, bytearray([0xc6, 0x17])) + + +def ExtendedItemHandler(rom: LocalRom) -> None: + rom.write_bytes(0x10B3FB, bytearray([0xE2, 0x20, 0xC9, 0x45, 0xB0])) + rom.write_bytes(0x10B400, bytearray([0x0C, 0xC2, 0x20, 0xA9, 0x10, 0x03, 0x8D, 0x7E, 0x02, 0x5C, 0xCF, 0xF4, 0x0B, 0xAD, 0x0F, 0x0B])) + rom.write_bytes(0x10B410, bytearray([0xD0, 0x38, 0xEE, 0xB5, 0x14, 0xA9, 0x18, 0x8D, 0x53, 0x00, 0xAF, 0x9A, 0xFC, 0x0D, 0xF0, 0x09])) + rom.write_bytes(0x10B420, bytearray([0xAD, 0xB5, 0x14, 0xCF, 0x99, 0xFC, 0x0D, 0xB0, 0x04, 0x5C, 0x0A, 0xF5, 0x0B, 0xAD, 0xB6, 0x14])) + rom.write_bytes(0x10B430, bytearray([0xD0, 0xF7, 0xA9, 0x01, 0x8D, 0xB6, 0x14, 0xA9, 0x0A, 0x8D, 0x18, 0x02, 0xA9, 0x16, 0x8D, 0x18])) + rom.write_bytes(0x10B440, bytearray([0x01, 0xA9, 0x97, 0x8D, 0x53, 0x00, 0x5C, 0x0A, 0xF5, 0x0B, 0xFA, 0x5C, 0x10, 0xF5, 0x0B])) + + +def patch_rom(world: "YoshisIslandWorld", rom: LocalRom, player: int) -> None: + handle_items(rom) # Implement main item functionality + Item_Data(rom) # Pointers necessary for item functionality + write_lives(rom) # Writes the number of lives as set in AP + CodeHandler(rom) # Jumps to my code + Server_Data(rom) # Pointers mostly related to receiving items + Menu_Data(rom) # Data related to the AP menu + Handle_Locations(rom) + ExtendedItemHandler(rom) + rom.write_bytes(0x11544B, bytearray(world.global_level_list)) + rom.write_bytes(0x11547A, bytearray([0x43])) + + rom.write_bytes(0x06FC89, world.starting_lives) + rom.write_bytes(0x03464F, ([world.baby_mario_sfx])) + rom.write_bytes(0x06FC83, ([world.options.starting_world.value])) + rom.write_bytes(0x06FC84, ([world.options.hidden_object_visibility.value])) + rom.write_bytes(0x06FC88, ([world.options.shuffle_midrings.value])) + rom.write_bytes(0x06FC85, ([world.options.castle_open_condition.value])) + rom.write_bytes(0x06FC86, ([world.options.castle_clear_condition.value])) + rom.write_bytes(0x06FC87, ([world.options.disable_autoscroll.value])) + rom.write_bytes(0x06FC8B, ([world.options.minigame_checks.value])) + rom.write_byte(0x06FC8C, world.options.death_link.value) + rom.write_bytes(0x06FC8D, bytearray(world.boss_room_id)) + rom.write_bytes(0x06FC99, bytearray([world.options.luigi_pieces_required.value])) + rom.write_bytes(0x06FC9A, bytearray([world.options.goal.value])) + + if world.options.yoshi_colors != YoshiColors.option_normal: + rom.write_bytes(0x113A33, bytearray(world.bowser_text)) + + rom.write_bytes(0x0A060C, bytearray(world.boss_burt_data)) + rom.write_bytes(0x0A8666, bytearray(world.boss_slime_data)) + rom.write_bytes(0x0A9D90, bytearray(world.boss_boo_data)) + rom.write_bytes(0x0074EA, bytearray(world.boss_pot_data)) + rom.write_bytes(0x08DC0A, bytearray(world.boss_frog_data)) + rom.write_bytes(0x0A4440, bytearray(world.boss_plant_data)) + rom.write_bytes(0x0968A2, bytearray(world.boss_milde_data)) + rom.write_bytes(0x0B3E10, bytearray(world.boss_koop_data)) + rom.write_bytes(0x0B4BD0, bytearray(world.boss_slug_data)) + rom.write_bytes(0x0B6BBA, bytearray(world.boss_raph_data)) + rom.write_bytes(0x087BED, bytearray(world.boss_tap_data)) + + rom.write_bytes(0x07A00D, ([world.tap_tap_room])) + rom.write_bytes(0x079DF2, ([world.tap_tap_room])) + rom.write_bytes(0x079CCF, ([world.tap_tap_room])) + rom.write_bytes(0x079C4D, ([world.tap_tap_room])) + + rom.write_bytes(0x045A2E, bytearray(world.Stage11StageGFX)) + rom.write_bytes(0x045A31, bytearray(world.Stage12StageGFX)) + rom.write_bytes(0x045A34, bytearray(world.Stage13StageGFX)) + rom.write_bytes(0x045A37, bytearray(world.Stage14StageGFX)) + rom.write_bytes(0x045A3A, bytearray(world.Stage15StageGFX)) + rom.write_bytes(0x045A3D, bytearray(world.Stage16StageGFX)) + rom.write_bytes(0x045A40, bytearray(world.Stage17StageGFX)) + rom.write_bytes(0x045A43, bytearray(world.Stage18StageGFX)) + + rom.write_bytes(0x045A52, bytearray(world.Stage21StageGFX)) + rom.write_bytes(0x045A55, bytearray(world.Stage22StageGFX)) + rom.write_bytes(0x045A58, bytearray(world.Stage23StageGFX)) + rom.write_bytes(0x045A5B, bytearray(world.Stage24StageGFX)) + rom.write_bytes(0x045A5E, bytearray(world.Stage25StageGFX)) + rom.write_bytes(0x045A61, bytearray(world.Stage26StageGFX)) + rom.write_bytes(0x045A64, bytearray(world.Stage27StageGFX)) + rom.write_bytes(0x045A67, bytearray(world.Stage28StageGFX)) + + rom.write_bytes(0x045A76, bytearray(world.Stage31StageGFX)) + rom.write_bytes(0x045A79, bytearray(world.Stage32StageGFX)) + rom.write_bytes(0x045A7C, bytearray(world.Stage33StageGFX)) + rom.write_bytes(0x045A7F, bytearray(world.Stage34StageGFX)) + rom.write_bytes(0x045A82, bytearray(world.Stage35StageGFX)) + rom.write_bytes(0x045A85, bytearray(world.Stage36StageGFX)) + rom.write_bytes(0x045A88, bytearray(world.Stage37StageGFX)) + rom.write_bytes(0x045A8B, bytearray(world.Stage38StageGFX)) + + rom.write_bytes(0x045A9A, bytearray(world.Stage41StageGFX)) + rom.write_bytes(0x045A9D, bytearray(world.Stage42StageGFX)) + rom.write_bytes(0x045AA0, bytearray(world.Stage43StageGFX)) + rom.write_bytes(0x045AA3, bytearray(world.Stage44StageGFX)) + rom.write_bytes(0x045AA6, bytearray(world.Stage45StageGFX)) + rom.write_bytes(0x045AA9, bytearray(world.Stage46StageGFX)) + rom.write_bytes(0x045AAC, bytearray(world.Stage47StageGFX)) + rom.write_bytes(0x045AAF, bytearray(world.Stage48StageGFX)) + + rom.write_bytes(0x045ABE, bytearray(world.Stage51StageGFX)) + rom.write_bytes(0x045AC1, bytearray(world.Stage52StageGFX)) + rom.write_bytes(0x045AC4, bytearray(world.Stage53StageGFX)) + rom.write_bytes(0x045AC7, bytearray(world.Stage54StageGFX)) + rom.write_bytes(0x045ACA, bytearray(world.Stage55StageGFX)) + rom.write_bytes(0x045ACD, bytearray(world.Stage56StageGFX)) + rom.write_bytes(0x045AD0, bytearray(world.Stage57StageGFX)) + rom.write_bytes(0x045AD3, bytearray(world.Stage58StageGFX)) + + rom.write_bytes(0x045AE2, bytearray(world.Stage61StageGFX)) + rom.write_bytes(0x045AE5, bytearray(world.Stage62StageGFX)) + rom.write_bytes(0x045AE8, bytearray(world.Stage63StageGFX)) + rom.write_bytes(0x045AEB, bytearray(world.Stage64StageGFX)) + rom.write_bytes(0x045AEE, bytearray(world.Stage65StageGFX)) + rom.write_bytes(0x045AF1, bytearray(world.Stage66StageGFX)) + rom.write_bytes(0x045AF4, bytearray(world.Stage67StageGFX)) + + rom.write_bytes(0x0BDBAF, bytearray(world.level_gfx_table)) + rom.write_bytes(0x0BDC4F, bytearray(world.palette_panel_list)) + + if world.options.yoshi_colors == YoshiColors.option_random_order: + rom.write_bytes(0x010000, ([world.leader_color])) + rom.write_bytes(0x010008, ([world.leader_color])) + rom.write_bytes(0x010009, ([world.leader_color])) + rom.write_bytes(0x010001, bytearray(world.color_order)) + rom.write_bytes(0x01000C, ([world.leader_color])) + rom.write_bytes(0x010014, ([world.leader_color])) + rom.write_bytes(0x010015, ([world.leader_color])) + rom.write_bytes(0x01000D, bytearray(world.color_order)) + rom.write_bytes(0x010018, ([world.leader_color])) + rom.write_bytes(0x010020, ([world.leader_color])) + rom.write_bytes(0x010021, ([world.leader_color])) + rom.write_bytes(0x01001A, bytearray(world.color_order)) + rom.write_bytes(0x010024, ([world.leader_color])) + rom.write_bytes(0x01002C, ([world.leader_color])) + rom.write_bytes(0x01002D, ([world.leader_color])) + rom.write_bytes(0x010025, bytearray(world.color_order)) + rom.write_bytes(0x010030, ([world.leader_color])) + rom.write_bytes(0x010038, ([world.leader_color])) + rom.write_bytes(0x010039, ([world.leader_color])) + rom.write_bytes(0x010031, bytearray(world.color_order)) + rom.write_bytes(0x01003C, ([world.leader_color])) + rom.write_bytes(0x010044, ([world.leader_color])) + rom.write_bytes(0x010045, ([world.leader_color])) + rom.write_bytes(0x01003D, bytearray(world.color_order)) + rom.write_bytes(0x010043, ([world.leader_color])) + elif world.options.yoshi_colors in {YoshiColors.option_random_color, YoshiColors.option_singularity}: + rom.write_bytes(0x010000, bytearray(world.level_colors)) + + if world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + bonus_checks(rom) + + if world.options.minigame_checks in {MinigameChecks.option_bandit_games, MinigameChecks.option_both}: + bandit_checks(rom) + + rom.write_bytes(0x00BF2C, bytearray(world.world_bonus)) + + if world.options.softlock_prevention: + rom.write_bytes(0x00C18F, bytearray([0x5C, 0x58, 0xFB, 0x0B])) # R + X Code + + if world.options.bowser_door_mode != BowserDoor.option_manual: + rom.write_bytes(0x07891F, bytearray(world.castle_door)) # 1 Entry + rom.write_bytes(0x078923, bytearray(world.castle_door)) # 2 Entry + rom.write_bytes(0x078927, bytearray(world.castle_door)) # 3 Entry + rom.write_bytes(0x07892B, bytearray(world.castle_door)) # 4 Entry + + if world.options.bowser_door_mode == BowserDoor.option_gauntlet: + rom.write_bytes(0x0AF517, bytearray([0xC6, 0x07, 0x7A, 0x00])) # Door 2 + rom.write_bytes(0x0AF6B7, bytearray([0xCD, 0x05, 0x5B, 0x00])) # Door 3 + rom.write_bytes(0x0AF8F2, bytearray([0xD3, 0x00, 0x77, 0x06])) # Door 4 + + if world.options.goal == PlayerGoal.option_luigi_hunt: + rom.write_bytes(0x1153F6, bytearray([0x16, 0x28, 0x10, 0x0C, 0x10, 0x4E, 0x1E, 0x10, 0x08, 0x04, 0x08, 0x24, 0x36, 0x82, 0x83, 0x83, 0x34, 0x84, 0x85, 0x85])) # Luigi piece clear text + rom.write_bytes(0x06FC86, bytearray([0xFF])) # Boss clear goal = 255, renders bowser inaccessible + + from Main import __version__ + rom.name = bytearray(f'YOSHIAP{__version__.replace(".", "")[0:3]}_{player}_{world.multiworld.seed:11}\0', "utf8")[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x007FC0, rom.name) + + +class YoshisIslandDeltaPatch(APDeltaPatch): + hash = USHASH + game: str = "Yoshi's Island" + patch_file_ending = ".apyi" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if USHASH != basemd5.hexdigest(): + raise Exception("Supplied Base Rom does not match known MD5 for US(1.0) release. " + "Get the correct game and version, then dump it") + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + if not file_name: + file_name = get_settings()["yoshisisland_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/yoshisisland/Rules.py b/worlds/yoshisisland/Rules.py new file mode 100644 index 000000000000..68d4f29a7381 --- /dev/null +++ b/worlds/yoshisisland/Rules.py @@ -0,0 +1,612 @@ +from .level_logic import YoshiLogic +from worlds.generic.Rules import set_rule +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +def set_easy_rules(world: "YoshisIslandWorld") -> None: + logic = YoshiLogic(world) + player = world.player + + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Stars", player), lambda state: state.has_all({"Tulip", "Beanstalk", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Level Clear", player), lambda state: state.has("Beanstalk", player)) + + set_rule(world.multiworld.get_location("Watch Out Below!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Watch Out Below!: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Cave Of Chomp Rock: Red Coins", player), lambda state: state.has("Chomp Rock", player)) + set_rule(world.multiworld.get_location("The Cave Of Chomp Rock: Flowers", player), lambda state: state.has("Chomp Rock", player)) + + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Red Coins", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Stars", player), lambda state: state.has("Spring Ball", player) and (logic.has_midring(state) or state.has("Key", player))) + + set_rule(world.multiworld.get_location("Hop! Hop! Donut Lifts: Stars", player), lambda state: logic.has_midring(state) or logic.cansee_clouds(state)) + + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Flashing Eggs", "Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Stars", player), lambda state: (logic.has_midring(state) and state.has("Tulip", player) or logic.has_midring(state) and state.has("Beanstalk", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Beanstalk"}, player)) + + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Red Coins", player), lambda state: state.has_all({"Flashing Eggs", "Spring Ball", "Chomp Rock", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Stars", player), lambda state: logic.has_midring(state) or (logic.cansee_clouds and state.has_all({"Spring Ball", "Chomp Rock", "Beanstalk"}, player))) + + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Red Coins", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Flowers", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Stars", player), lambda state: logic.has_midring(state) and (state.has("Platform Ghost", player) or state.has_all({"Arrow Wheel", "Key"}, player))) + + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Red Coins", player), lambda state: state.has_all({"Poochy", "Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Baseball Boys: Red Coins", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Egg Launcher", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Flowers", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Egg Launcher", "Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Tulip", player))) and state.has_all({"Beanstalk", "Super Star", "Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Level Clear", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Egg Launcher", "Large Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Red Coins", player), lambda state: state.has("! Switch", player)) + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Flowers", player), lambda state: state.has_any({"Large Spring Ball", "Super Star"}, player)) + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Level Clear", player), lambda state: state.has_any({"Large Spring Ball", "Super Star"}, player)) + + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Red Coins", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Flowers", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Stars", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player) and logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Red Coins", player), lambda state: state.has("Chomp Rock", player)) + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Flowers", player), lambda state: state.has_all({"Key", "Train Morph", "Chomp Rock"}, player)) + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Level Clear", player), lambda state: state.has("Chomp Rock", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Lakitu's Wall: Red Coins", player), lambda state: (state.has_any({"Dashed Platform", "Giant Eggs"}, player) or logic.combat_item(state)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Lakitu's Wall: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player) and (logic.combat_item(state) or state.has("Giant Eggs", player))) + set_rule(world.multiworld.get_location("Lakitu's Wall: Stars", player), lambda state: state.has("Giant Eggs", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Lakitu's Wall: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Car Morph"}, player)) + + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1))) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1))) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Stars", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player) and logic.has_midring(state) and (state.has("Egg Capacity Upgrade", player, 1))) + + set_rule(world.multiworld.get_location("Welcome To Monkey World!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Level Clear", player), lambda state: state.has_all({"Dashed Stairs", "Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Red Coins", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Flowers", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Stars", player), lambda state: logic.has_midring(state) or state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Level Clear", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Red Coins", player), lambda state: state.has("Submarine Morph", player)) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 5) or logic.combat_item(state)) and (state.has("Dashed Platform", player))) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jammin' Through The Trees: Flowers", player), lambda state: state.has("Watermelon", player) or logic.melon_item(state)) + set_rule(world.multiworld.get_location("Jammin' Through The Trees: Stars", player), lambda state: ((logic.has_midring(state) or state.has("Tulip", player)) and logic.cansee_clouds(state)) or logic.has_midring(state) and state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Red Coins", player), lambda state: state.has_all({"Chomp Rock", "Beanstalk", "Mole Tank Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Flowers", player), lambda state: state.has_all({"Chomp Rock", "Beanstalk", "Mole Tank Morph", "Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Stars", player), lambda state: state.has_all({"Tulip", "Large Spring Ball", "Dashed Stairs", "Chomp Rock", "Beanstalk", "Mole Tank Morph"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Level Clear", player), lambda state: state.has_all({"Chomp Rock", "Large Spring Ball", "Key"}, player)) + + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Red Coins", player), lambda state: state.has_all({"! Switch", "Submarine Morph", "Large Spring Ball", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Flowers", player), lambda state: state.has_all({"Beanstalk", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state))) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Stars", player), lambda state: state.has_all({"Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Don't Look Back!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "! Switch", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Flowers", player), lambda state: state.has_all({"! Switch", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Stars", player), lambda state: (logic.has_midring(state) and state.has("Tulip", player)) and state.has("! Switch", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Level Clear", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Marching Milde's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Stars", player), lambda state: state.has("Dashed Stairs", player) and (logic.has_midring(state) or state.has("Vanishing Arrow Wheel", player) or logic.cansee_clouds(state))) + + set_rule(world.multiworld.get_location("Chomp Rock Zone: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Chomp Rock"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Flowers", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Spring Ball", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Stars", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Spring Ball", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("Lake Shore Paradise: Red Coins", player), lambda state: state.has_any({"Large Spring Ball", "Spring Ball"}, player) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Flowers", player), lambda state: state.has_any({"Large Spring Ball", "Spring Ball"}, player) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state))) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Level Clear", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + + set_rule(world.multiworld.get_location("Ride Like The Wind: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Stars", player), lambda state: (logic.has_midring(state) and state.has("Helicopter Morph", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Stars", player), lambda state: logic.has_midring(state) and (state.has_any({"Dashed Stairs", "Vanishing Arrow Wheel"}, player))) + + set_rule(world.multiworld.get_location("BLIZZARD!!!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("BLIZZARD!!!: Stars", player), lambda state: logic.cansee_clouds(state) or ((logic.has_midring(state) and state.has("Dashed Stairs", player)) or state.has("Tulip", player))) + + set_rule(world.multiworld.get_location("Ride The Ski Lifts: Stars", player), lambda state: logic.has_midring(state) or state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Red Coins", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and (state.has_all({"Bucket", "Spring Ball", "Super Star", "Skis", "Dashed Platform"}, player))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Flowers", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and state.has_all({"Spring Ball", "Skis", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Fire Melon", player) or logic.melon_item(state))) and state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Skis", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Dashed Platform", "Platform Ghost"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Stars", player), lambda state: ((state.has_all({"Dashed Stairs", "Platform Ghost"}, player)) and logic.has_midring(state)) or (logic.cansee_clouds(state) and state.has("Dashed Stairs", player) and state.has("Dashed Platform", player))) + + set_rule(world.multiworld.get_location("Goonie Rides!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Goonie Rides!: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Goonie Rides!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Goonie Rides!: Level Clear", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("Welcome To Cloud World: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Red Coins", player), lambda state: state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Flowers", player), lambda state: state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Stars", player), lambda state: logic.has_midring(state) and state.has("Arrow Wheel", player)) + + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Red Coins", player), lambda state: state.has_all({"Dashed Platform", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Flowers", player), lambda state: state.has_all({"Dashed Platform", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Stars", player), lambda state: state.has("Dashed Platform", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Level Clear", player), lambda state: state.has_all({"Dashed Platform", "Large Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Stars", player), lambda state: logic.cansee_clouds(state) or logic.has_midring(state)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Stars", player), lambda state: logic.has_midring(state) and state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, player)) + + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Red Coins", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Flowers", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Stars", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state)) and logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Red Coins", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Flowers", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Stars", player), lambda state: state.has_all({"Chomp Rock", "Tulip", "Key"}, player) or (logic.has_midring(state) and state.has_all({"Key", "Chomp Rock", "Large Spring Ball"}, player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Level Clear", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Flowers", player), lambda state: state.has("Egg Plant", player)) + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("King Bowser's Castle: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Stars", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68Route(state)) + + set_easy_extra_rules(world) + + +def set_easy_extra_rules(world: "YoshisIslandWorld") -> None: + player = world.player + logic = YoshiLogic(world) + if not world.options.extras_enabled: + return + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Red Coins", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Flowers", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Stars", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Level Clear", player), lambda state: state.has("Poochy", player)) + + set_rule(world.multiworld.get_location("Hit That Switch!!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("The Impossible? Maze: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph", "Flashing Eggs"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Stars", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Kamek's Revenge: Red Coins", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Flowers", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Stars", player), lambda state: state.has("! Switch", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Level Clear", player), lambda state: state.has_all({"Key", "Skis", "! Switch", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Stars", player), lambda state: logic.has_midring(state) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Level Clear", player), lambda state: (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + + +def set_normal_rules(world: "YoshisIslandWorld") -> None: + logic = YoshiLogic(world) + player = world.player + + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Red Coins", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Flowers", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Stars", player), lambda state: state.has_any({"Tulip", "Dashed Stairs"}, player)) + + set_rule(world.multiworld.get_location("Watch Out Below!: Red Coins", player), lambda state: state.has("Helicopter Morph", player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Flowers", player), lambda state: state.has("Helicopter Morph", player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Red Coins", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Stars", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Level Clear", player), lambda state: logic._14CanFightBoss(state)) + + + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Flashing Eggs", "Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Stars", player), lambda state: (logic.has_midring(state) and state.has_any(["Tulip", "Beanstalk"], player)) or (state.has_all(["Tulip", "Beanstalk", "Large Spring Ball"], player))) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Beanstalk"}, player)) + + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Red Coins", player), lambda state: state.has_all({"Flashing Eggs", "Spring Ball", "Chomp Rock", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Stars", player), lambda state: logic.has_midring(state) or (logic.cansee_clouds and state.has_all({"Spring Ball", "Chomp Rock", "Beanstalk"}, player))) + + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Red Coins", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Flowers", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Stars", player), lambda state: logic.has_midring(state) and (state.has("Platform Ghost", player) or state.has_all({"Arrow Wheel", "Key"}, player))) + + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Red Coins", player), lambda state: state.has_all({"Poochy", "Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Baseball Boys: Red Coins", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball", "Beanstalk", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player))) and state.has_all({"Beanstalk", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Level Clear", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Large Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Red Coins", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Red Coins", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Flowers", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Stars", player), lambda state: state.has_all({"! Switch", "Dashed Stairs"}, player)) + + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Flowers", player), lambda state: state.has_all({"Key", "Train Morph"}, player)) + + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Lakitu's Wall: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player) and (logic.combat_item(state) or state.has("Giant Eggs", player))) + set_rule(world.multiworld.get_location("Lakitu's Wall: Stars", player), lambda state: state.has("Giant Eggs", player) or logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Key", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Stars", player), lambda state: state.has("Arrow Wheel", player) and (logic.has_midring(state) or state.has("Key", player))) + + set_rule(world.multiworld.get_location("Welcome To Monkey World!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jungle Rhythm...: Red Coins", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Flowers", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Level Clear", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Red Coins", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Flowers", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Level Clear", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Red Coins", player), lambda state: state.has("Submarine Morph", player)) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 5) or logic.combat_item(state)) and (state.has("Dashed Platform", player) or logic.has_midring(state))) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jammin' Through The Trees: Flowers", player), lambda state: state.has("Watermelon", player) or logic.melon_item(state)) + + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Red Coins", player), lambda state: state.has_any({"Dashed Stairs", "Beanstalk"}, player) and state.has_all({"Mole Tank Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Flowers", player), lambda state: state.has_any({"Dashed Stairs", "Beanstalk"}, player) and state.has_all({"! Switch", "Mole Tank Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Stars", player), lambda state: (state.has_any({"Dashed Stairs", "Beanstalk"}, player) and state.has_all({"Mole Tank Morph", "Large Spring Ball"}, player)) and (logic.has_midring(state) or state.has("Tulip", player))) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Key"}, player)) + + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Red Coins", player), lambda state: state.has_all({"! Switch", "Submarine Morph", "Large Spring Ball", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Flowers", player), lambda state: state.has("Beanstalk", player)) + + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Stars", player), lambda state: (logic.has_midring(state) and state.has("Tulip", player)) and state.has("Egg Capacity Upgrade", player, 1)) + + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Don't Look Back!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "! Switch", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Stars", player), lambda state: (logic.has_midring(state) or state.has("Tulip", player)) and state.has("! Switch", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Level Clear", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Marching Milde's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Stars", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Chomp Rock Zone: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Chomp Rock"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Flowers", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Stars", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("Lake Shore Paradise: Red Coins", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Flowers", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state))) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Level Clear", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + + set_rule(world.multiworld.get_location("Ride Like The Wind: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Stars", player), lambda state: (logic.has_midring(state) or state.has("Helicopter Morph", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Stars", player), lambda state: logic.has_midring(state) or (state.has_any({"Dashed Stairs", "Vanishing Arrow Wheel"}, player))) + + set_rule(world.multiworld.get_location("BLIZZARD!!!: Red Coins", player), lambda state: state.has_any({"Dashed Stairs", "Ice Melon"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state) or state.has("Helicopter Morph", player))) + + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Red Coins", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and (state.has_all({"Spring Ball", "Skis"}, player)) and (state.has("Super Star", player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Flowers", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and state.has_all({"Spring Ball", "Skis"}, player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Fire Melon", player) or logic.melon_item(state))) or (logic.has_midring(state) and (state.has_all({"Tulip", "Dashed Platform"}, player)))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Skis"}, player)) + + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Dashed Platform", "Platform Ghost"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Stars", player), lambda state: ((state.has_all({"Dashed Stairs", "Platform Ghost"}, player))) or (logic.cansee_clouds(state) and state.has("Dashed Stairs", player))) + + set_rule(world.multiworld.get_location("Goonie Rides!: Red Coins", player), lambda state: state.has("Helicopter Morph", player)) + set_rule(world.multiworld.get_location("Goonie Rides!: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Stars", player), lambda state: logic.has_midring(state) and state.has_all({"Spring Ball", "Egg Plant", "Key"}, player)) + + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Red Coins", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Flowers", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Stars", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Red Coins", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Flowers", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Stars", player), lambda state: state.has_all({"Chomp Rock", "Tulip", "Key"}, player) or (logic.has_midring(state) and state.has_all({"Key", "Chomp Rock", "Large Spring Ball"}, player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Level Clear", player), lambda state: state.has_all({"Key", "Large Spring Ball", "Dashed Platform"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("King Bowser's Castle: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Stars", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68Route(state)) + + set_normal_extra_rules(world) + + +def set_normal_extra_rules(world: "YoshisIslandWorld") -> None: + player = world.player + logic = YoshiLogic(world) + if not world.options.extras_enabled: + return + + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Red Coins", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Flowers", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Stars", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Level Clear", player), lambda state: state.has("Poochy", player)) + + set_rule(world.multiworld.get_location("Hit That Switch!!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("The Impossible? Maze: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph", "Flashing Eggs"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Stars", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Kamek's Revenge: Red Coins", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Flowers", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Stars", player), lambda state: state.has("! Switch", player) or logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Level Clear", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Stars", player), lambda state: logic.has_midring(state) or state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Level Clear", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + + +def set_hard_rules(world: "YoshisIslandWorld"): + logic = YoshiLogic(world) + player = world.player + + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Red Coins", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Stars", player), lambda state: state.has("Spring Ball", player)) + + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Flashing Eggs", "Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Red Coins", player), lambda state: state.has_all({"Flashing Eggs", "Spring Ball", "Chomp Rock", "Beanstalk"}, player)) + + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Red Coins", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Flowers", player), lambda state: state.has("Platform Ghost", player)) + + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Red Coins", player), lambda state: state.has("Large Spring Ball", player) and (state.has("Poochy", player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Baseball Boys: Red Coins", player), lambda state: state.has("Mole Tank Morph", player) and (state.has_any({"Ice Melon", "Large Spring Ball"}, player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("The Baseball Boys: Flowers", player), lambda state: (state.has_any({"Ice Melon", "Large Spring Ball"}, player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("The Baseball Boys: Level Clear", player), lambda state: (state.has_any({"Ice Melon", "Large Spring Ball"}, player) or logic.melon_item(state))) + + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Red Coins", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Red Coins", player), lambda state: state.has_all({"! Switch", "Key"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Flowers", player), lambda state: state.has_all({"! Switch", "Key"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Stars", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Flowers", player), lambda state: state.has_all({"Key", "Train Morph"}, player)) + + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Lakitu's Wall: Flowers", player), lambda state: state.has("! Switch", player)) + set_rule(world.multiworld.get_location("Lakitu's Wall: Stars", player), lambda state: state.has("Giant Eggs", player) or logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Key", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Stars", player), lambda state: state.has("Arrow Wheel", player)) + + set_rule(world.multiworld.get_location("Welcome To Monkey World!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jungle Rhythm...: Red Coins", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Flowers", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Level Clear", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Red Coins", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Flowers", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Level Clear", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Red Coins", player), lambda state: state.has("Submarine Morph", player)) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 5) or logic.combat_item(state))) + + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Red Coins", player), lambda state: state.has("Mole Tank Morph", player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Flowers", player), lambda state: state.has_all({"Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Key"}, player)) + + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Red Coins", player), lambda state: state.has_all({"! Switch", "Submarine Morph"}, player)) + + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"! Switch", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Flowers", player), lambda state: state.has("Egg Launcher", player)) + + set_rule(world.multiworld.get_location("Don't Look Back!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("Marching Milde's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Stars", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Chomp Rock Zone: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Flowers", player), lambda state: state.has_all({"Chomp Rock", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Stars", player), lambda state: state.has_all({"Chomp Rock", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("Lake Shore Paradise: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state)))) + + set_rule(world.multiworld.get_location("Ride Like The Wind: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Stars", player), lambda state: (logic.has_midring(state) or state.has("Helicopter Morph", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Red Coins", player), lambda state: state.has_all({"Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Key"}, player)) + + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Red Coins", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and (state.has_all({"Spring Ball", "Skis"}, player)) and (state.has("Super Star", player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Flowers", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and state.has_all({"Spring Ball", "Skis"}, player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Fire Melon", player) or logic.melon_item(state))) or (logic.has_midring(state) and (state.has_all({"Tulip", "Dashed Platform"}, player)))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Skis"}, player)) + + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Dashed Platform", "Platform Ghost"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Platform Ghost"}, player)) + + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Red Coins", player), lambda state: state.has_all({"Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Flowers", player), lambda state: state.has_all({"Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Stars", player), lambda state: state.has("Egg Plant", player) and state.has("Key", player)) + + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Red Coins", player), lambda state: state.has_all({"Key", "Large Spring Ball"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Flowers", player), lambda state: state.has_all({"Key", "Large Spring Ball"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Stars", player), lambda state: state.has_all({"Chomp Rock", "Key"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Level Clear", player), lambda state: state.has_all({"Key", "Large Spring Ball", "Dashed Platform"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("King Bowser's Castle: Red Coins", player), lambda state: state.has("Helicopter Morph", player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Flowers", player), lambda state: state.has("Helicopter Morph", player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Stars", player), lambda state: state.has("Helicopter Morph", player) and logic._68Route(state)) + + set_hard_extra_rules(world) + + +def set_hard_extra_rules(world: "YoshisIslandWorld") -> None: + player = world.player + logic = YoshiLogic(world) + if not world.options.extras_enabled: + return + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Red Coins", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Flowers", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Stars", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Level Clear", player), lambda state: state.has("Poochy", player)) + + set_rule(world.multiworld.get_location("Hit That Switch!!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("The Impossible? Maze: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph", "Flashing Eggs"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Stars", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Kamek's Revenge: Red Coins", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Flowers", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Stars", player), lambda state: state.has("! Switch", player) or logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Level Clear", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Red Coins", player), lambda state: state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Flowers", player), lambda state: state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Stars", player), lambda state: True) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Level Clear", player), lambda state: state.has(("Large Spring Ball"), player)) diff --git a/worlds/yoshisisland/__init__.py b/worlds/yoshisisland/__init__.py new file mode 100644 index 000000000000..f1aba3018bdb --- /dev/null +++ b/worlds/yoshisisland/__init__.py @@ -0,0 +1,387 @@ +import base64 +import os +import typing +import threading + +from typing import List, Set, TextIO, Dict +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from worlds.AutoWorld import World, WebWorld +import settings +from .Items import get_item_names_per_category, item_table, filler_items, trap_items +from .Locations import get_locations +from .Regions import init_areas +from .Options import YoshisIslandOptions, PlayerGoal, ObjectVis, StageLogic, MinigameChecks +from .setup_game import setup_gamevars +from .Client import YoshisIslandSNIClient +from .Rules import set_easy_rules, set_normal_rules, set_hard_rules +from .Rom import LocalRom, patch_rom, get_base_rom_path, YoshisIslandDeltaPatch, USHASH + + +class YoshisIslandSettings(settings.Group): + class RomFile(settings.SNESRomPath): + """File name of the Yoshi's Island 1.0 US rom""" + description = "Yoshi's Island ROM File" + copy_to = "Super Mario World 2 - Yoshi's Island (U).sfc" + md5s = [USHASH] + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class YoshisIslandWeb(WebWorld): + theme = "ocean" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Yoshi's Island randomizer and connecting to an Archipelago server.", + "English", + "setup_en.md", + "setup/en", + ["Pink Switch"] + ) + + tutorials = [setup_en] + + +class YoshisIslandWorld(World): + """ + Yoshi's Island is a 2D platforming game. + During a delivery, Bowser's evil ward, Kamek, attacked the stork, kidnapping Luigi and dropping Mario onto Yoshi's Island. + As Yoshi, you must run, jump, and throw eggs to escort the baby Mario across the island to defeat Bowser and reunite the two brothers with their parents. + """ + game = "Yoshi's Island" + option_definitions = YoshisIslandOptions + required_client_version = (0, 4, 4) + + item_name_to_id = {item: item_table[item].code for item in item_table} + location_name_to_id = {location.name: location.code for location in get_locations(None)} + item_name_groups = get_item_names_per_category() + + web = YoshisIslandWeb() + settings: typing.ClassVar[YoshisIslandSettings] + # topology_present = True + + options_dataclass = YoshisIslandOptions + options: YoshisIslandOptions + + locked_locations: List[str] + set_req_bosses: str + lives_high: int + lives_low: int + castle_bosses: int + bowser_bosses: int + baby_mario_sfx: int + leader_color: int + boss_order: list + boss_burt: int + luigi_count: int + + rom_name: bytearray + + def __init__(self, multiworld: MultiWorld, player: int): + self.rom_name_available_event = threading.Event() + super().__init__(multiworld, player) + self.locked_locations = [] + + @classmethod + def stage_assert_generate(cls, multiworld: MultiWorld) -> None: + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + + def fill_slot_data(self) -> Dict[str, List[int]]: + return { + "world_1": self.world_1_stages, + "world_2": self.world_2_stages, + "world_3": self.world_3_stages, + "world_4": self.world_4_stages, + "world_5": self.world_5_stages, + "world_6": self.world_6_stages + } + + def write_spoiler_header(self, spoiler_handle: TextIO) -> None: + spoiler_handle.write(f"Burt The Bashful's Boss Door: {self.boss_order[0]}\n") + spoiler_handle.write(f"Salvo The Slime's Boss Door: {self.boss_order[1]}\n") + spoiler_handle.write(f"Bigger Boo's Boss Door: {self.boss_order[2]}\n") + spoiler_handle.write(f"Roger The Ghost's Boss Door: {self.boss_order[3]}\n") + spoiler_handle.write(f"Prince Froggy's Boss Door: {self.boss_order[4]}\n") + spoiler_handle.write(f"Naval Piranha's Boss Door: {self.boss_order[5]}\n") + spoiler_handle.write(f"Marching Milde's Boss Door: {self.boss_order[6]}\n") + spoiler_handle.write(f"Hookbill The Koopa's Boss Door: {self.boss_order[7]}\n") + spoiler_handle.write(f"Sluggy The Unshaven's Boss Door: {self.boss_order[8]}\n") + spoiler_handle.write(f"Raphael The Raven's Boss Door: {self.boss_order[9]}\n") + spoiler_handle.write(f"Tap-Tap The Red Nose's Boss Door: {self.boss_order[10]}\n") + spoiler_handle.write(f"\nLevels:\n1-1: {self.level_name_list[0]}\n") + spoiler_handle.write(f"1-2: {self.level_name_list[1]}\n") + spoiler_handle.write(f"1-3: {self.level_name_list[2]}\n") + spoiler_handle.write(f"1-4: {self.level_name_list[3]}\n") + spoiler_handle.write(f"1-5: {self.level_name_list[4]}\n") + spoiler_handle.write(f"1-6: {self.level_name_list[5]}\n") + spoiler_handle.write(f"1-7: {self.level_name_list[6]}\n") + spoiler_handle.write(f"1-8: {self.level_name_list[7]}\n") + + spoiler_handle.write(f"\n2-1: {self.level_name_list[8]}\n") + spoiler_handle.write(f"2-2: {self.level_name_list[9]}\n") + spoiler_handle.write(f"2-3: {self.level_name_list[10]}\n") + spoiler_handle.write(f"2-4: {self.level_name_list[11]}\n") + spoiler_handle.write(f"2-5: {self.level_name_list[12]}\n") + spoiler_handle.write(f"2-6: {self.level_name_list[13]}\n") + spoiler_handle.write(f"2-7: {self.level_name_list[14]}\n") + spoiler_handle.write(f"2-8: {self.level_name_list[15]}\n") + + spoiler_handle.write(f"\n3-1: {self.level_name_list[16]}\n") + spoiler_handle.write(f"3-2: {self.level_name_list[17]}\n") + spoiler_handle.write(f"3-3: {self.level_name_list[18]}\n") + spoiler_handle.write(f"3-4: {self.level_name_list[19]}\n") + spoiler_handle.write(f"3-5: {self.level_name_list[20]}\n") + spoiler_handle.write(f"3-6: {self.level_name_list[21]}\n") + spoiler_handle.write(f"3-7: {self.level_name_list[22]}\n") + spoiler_handle.write(f"3-8: {self.level_name_list[23]}\n") + + spoiler_handle.write(f"\n4-1: {self.level_name_list[24]}\n") + spoiler_handle.write(f"4-2: {self.level_name_list[25]}\n") + spoiler_handle.write(f"4-3: {self.level_name_list[26]}\n") + spoiler_handle.write(f"4-4: {self.level_name_list[27]}\n") + spoiler_handle.write(f"4-5: {self.level_name_list[28]}\n") + spoiler_handle.write(f"4-6: {self.level_name_list[29]}\n") + spoiler_handle.write(f"4-7: {self.level_name_list[30]}\n") + spoiler_handle.write(f"4-8: {self.level_name_list[31]}\n") + + spoiler_handle.write(f"\n5-1: {self.level_name_list[32]}\n") + spoiler_handle.write(f"5-2: {self.level_name_list[33]}\n") + spoiler_handle.write(f"5-3: {self.level_name_list[34]}\n") + spoiler_handle.write(f"5-4: {self.level_name_list[35]}\n") + spoiler_handle.write(f"5-5: {self.level_name_list[36]}\n") + spoiler_handle.write(f"5-6: {self.level_name_list[37]}\n") + spoiler_handle.write(f"5-7: {self.level_name_list[38]}\n") + spoiler_handle.write(f"5-8: {self.level_name_list[39]}\n") + + spoiler_handle.write(f"\n6-1: {self.level_name_list[40]}\n") + spoiler_handle.write(f"6-2: {self.level_name_list[41]}\n") + spoiler_handle.write(f"6-3: {self.level_name_list[42]}\n") + spoiler_handle.write(f"6-4: {self.level_name_list[43]}\n") + spoiler_handle.write(f"6-5: {self.level_name_list[44]}\n") + spoiler_handle.write(f"6-6: {self.level_name_list[45]}\n") + spoiler_handle.write(f"6-7: {self.level_name_list[46]}\n") + spoiler_handle.write("6-8: King Bowser's Castle") + + def create_item(self, name: str) -> Item: + data = item_table[name] + return Item(name, data.classification, data.code, self.player) + + def create_regions(self) -> None: + init_areas(self, get_locations(self)) + + def get_filler_item_name(self) -> str: + trap_chance: int = self.options.trap_percent.value + + if self.random.random() < (trap_chance / 100) and self.options.traps_enabled: + return self.random.choice(trap_items) + else: + return self.random.choice(filler_items) + + def set_rules(self) -> None: + rules_per_difficulty = { + 0: set_easy_rules, + 1: set_normal_rules, + 2: set_hard_rules + } + + rules_per_difficulty[self.options.stage_logic.value](self) + self.multiworld.completion_condition[self.player] = lambda state: state.has("Saved Baby Luigi", self.player) + self.get_location("Burt The Bashful's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Salvo The Slime's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Bigger Boo's Boss Room", ).place_locked_item(self.create_item("Boss Clear")) + self.get_location("Roger The Ghost's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Prince Froggy's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Naval Piranha's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Marching Milde's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Hookbill The Koopa's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Sluggy The Unshaven's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Raphael The Raven's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Tap-Tap The Red Nose's Boss Room").place_locked_item(self.create_item("Boss Clear")) + + if self.options.goal == PlayerGoal.option_luigi_hunt: + self.get_location("Reconstituted Luigi").place_locked_item(self.create_item("Saved Baby Luigi")) + else: + self.get_location("King Bowser's Castle: Level Clear").place_locked_item( + self.create_item("Saved Baby Luigi") + ) + + self.get_location("Touch Fuzzy Get Dizzy: Gather Coins").place_locked_item( + self.create_item("Bandit Consumables") + ) + self.get_location("The Cave Of the Mystery Maze: Seed Spitting Contest").place_locked_item( + self.create_item("Bandit Watermelons") + ) + self.get_location("Lakitu's Wall: Gather Coins").place_locked_item(self.create_item("Bandit Consumables")) + self.get_location("Ride Like The Wind: Gather Coins").place_locked_item(self.create_item("Bandit Consumables")) + + def generate_early(self) -> None: + setup_gamevars(self) + + def get_excluded_items(self) -> Set[str]: + excluded_items: Set[str] = set() + + starting_gate = ["World 1 Gate", "World 2 Gate", "World 3 Gate", + "World 4 Gate", "World 5 Gate", "World 6 Gate"] + + excluded_items.add(starting_gate[self.options.starting_world]) + + if not self.options.shuffle_midrings: + excluded_items.add("Middle Ring") + + if not self.options.add_secretlens: + excluded_items.add("Secret Lens") + + if not self.options.extras_enabled: + excluded_items.add("Extra Panels") + excluded_items.add("Extra 1") + excluded_items.add("Extra 2") + excluded_items.add("Extra 3") + excluded_items.add("Extra 4") + excluded_items.add("Extra 5") + excluded_items.add("Extra 6") + + if self.options.split_extras: + excluded_items.add("Extra Panels") + else: + excluded_items.add("Extra 1") + excluded_items.add("Extra 2") + excluded_items.add("Extra 3") + excluded_items.add("Extra 4") + excluded_items.add("Extra 5") + excluded_items.add("Extra 6") + + if self.options.split_bonus: + excluded_items.add("Bonus Panels") + else: + excluded_items.add("Bonus 1") + excluded_items.add("Bonus 2") + excluded_items.add("Bonus 3") + excluded_items.add("Bonus 4") + excluded_items.add("Bonus 5") + excluded_items.add("Bonus 6") + + return excluded_items + + def create_item_with_correct_settings(self, name: str) -> Item: + data = item_table[name] + item = Item(name, data.classification, data.code, self.player) + + if not item.advancement: + return item + + if name == "Car Morph" and self.options.stage_logic != StageLogic.option_strict: + item.classification = ItemClassification.useful + + secret_lens_visibility_check = ( + self.options.hidden_object_visibility >= ObjectVis.option_clouds_only + or self.options.stage_logic != StageLogic.option_strict + ) + if name == "Secret Lens" and secret_lens_visibility_check: + item.classification = ItemClassification.useful + + is_bonus_location = name in {"Bonus 1", "Bonus 2", "Bonus 3", "Bonus 4", "Bonus 5", "Bonus 6", "Bonus Panels"} + bonus_games_disabled = ( + self.options.minigame_checks not in {MinigameChecks.option_bonus_games, MinigameChecks.option_both} + ) + if is_bonus_location and bonus_games_disabled: + item.classification = ItemClassification.useful + + if name in {"Bonus 1", "Bonus 3", "Bonus 4", "Bonus Panels"} and self.options.item_logic: + item.classification = ItemClassification.progression + + if name == "Piece of Luigi" and self.options.goal == PlayerGoal.option_luigi_hunt: + if self.luigi_count >= self.options.luigi_pieces_required: + item.classification = ItemClassification.useful + else: + item.classification = ItemClassification.progression_skip_balancing + self.luigi_count += 1 + + return item + + def generate_filler(self, pool: List[Item]) -> None: + if self.options.goal == PlayerGoal.option_luigi_hunt: + for _ in range(self.options.luigi_pieces_in_pool.value): + item = self.create_item_with_correct_settings("Piece of Luigi") + pool.append(item) + + for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool) - 16): + item = self.create_item_with_correct_settings(self.get_filler_item_name()) + pool.append(item) + + def get_item_pool(self, excluded_items: Set[str]) -> List[Item]: + pool: List[Item] = [] + + for name, data in item_table.items(): + if name not in excluded_items: + for _ in range(data.amount): + item = self.create_item_with_correct_settings(name) + pool.append(item) + + return pool + + def create_items(self) -> None: + self.luigi_count = 0 + + if self.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + self.multiworld.get_location("Flip Cards", self.player).place_locked_item( + self.create_item("Bonus Consumables")) + self.multiworld.get_location("Drawing Lots", self.player).place_locked_item( + self.create_item("Bonus Consumables")) + self.multiworld.get_location("Match Cards", self.player).place_locked_item( + self.create_item("Bonus Consumables")) + + pool = self.get_item_pool(self.get_excluded_items()) + + self.generate_filler(pool) + + self.multiworld.itempool += pool + + def generate_output(self, output_directory: str) -> None: + rompath = "" # if variable is not declared finally clause may fail + try: + world = self.multiworld + player = self.player + rom = LocalRom(get_base_rom_path()) + patch_rom(self, rom, self.player) + + rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") + rom.write_to_file(rompath) + self.rom_name = rom.name + + patch = YoshisIslandDeltaPatch(os.path.splitext(rompath)[0] + YoshisIslandDeltaPatch.patch_file_ending, + player=player, player_name=world.player_name[player], patched_path=rompath) + patch.write() + finally: + self.rom_name_available_event.set() + if os.path.exists(rompath): + os.unlink(rompath) + + def modify_multidata(self, multidata: dict) -> None: + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] + + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]) -> None: + world_names = [f"World {i}" for i in range(1, 7)] + world_stages = [ + self.world_1_stages, self.world_2_stages, self.world_3_stages, + self.world_4_stages, self.world_5_stages, self.world_6_stages + ] + + stage_pos_data = {} + for loc in self.multiworld.get_locations(self.player): + if loc.address is None: + continue + + level_id = getattr(loc, "level_id") + for level, stages in zip(world_names, world_stages): + if level_id in stages: + stage_pos_data[loc.address] = level + break + + hint_data[self.player] = stage_pos_data diff --git a/worlds/yoshisisland/docs/en_Yoshi's Island.md b/worlds/yoshisisland/docs/en_Yoshi's Island.md new file mode 100644 index 000000000000..d6770c070b94 --- /dev/null +++ b/worlds/yoshisisland/docs/en_Yoshi's Island.md @@ -0,0 +1,71 @@ +# Yoshi's Island + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. + +## What does randomization do to this game? + +Certain interactable objects within levels will be unable to be used until the corresponding item is found. If the item is not in the player's posession, the object will flash and will not function. Objects include: +- Spring Ball +- Large Spring Ball +- ! Switch +- Dashed Platform +- Dashed Stairs +- Beanstalk +- Arrow Wheel +- Vanishing Arrow Wheel +- Ice, fire, and normal watermelons +- Super Star +- Flashing Eggs +- Giant Eggs +- Egg Launcher +- Egg Refill Plant +- Chomp Rock +- Poochy +- Transformation Morphs +- Skis +- Platform Ghost +- Middle Rings +- Buckets +- Tulips + +Yoshi will start out being able to carry only one egg, and 5 capacity upgrades can be found to bring the total up to 6. +The player will start with all levels unlocked in their starting world, and can collect 'World Gates' to unlock levels from other worlds. +Extra and Bonus stages will also start out locked, and require respective items to access them. 6-8 is locked, and will be unlocked +upon reaching the number of boss clears defined by the player. +Other checks will grant the player extra lives, consumables for use in the inventory, or traps. + +Additionally, the player is able to randomize the bosses found at the end of boss stages, the order of stages, +the world they start in, the starting amount of lives, route through 6-8, and the color of Yoshi for each stage. + +## What is the goal of Yoshi's Island when randomized? + +The player can choose one of two goals: +- Bowser: Defeat a pre-defined number of bosses, and defeat Bowser at the end of 6-8. +- Luigi Hunt: Collect a pre-defined number of 'Pieces of Luigi' within levels. + +## What items and locations get shuffled? + +Locations consist of 'level objectives', that being: +- Beating the stage +- Collecting 20 red coins. +- Collecting 5 flowers. +- Collecting 30 stars. + +Checks will be sent immediately upon achieving that objective, regardless of if the stage is cleared or not. +Additional checks can be placed on Bandit mini-battles, or overworld minigames. + + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + +## What does another world's item look like in Yoshi's Island + +Items do not have an appearance in Yoshi's Island + +## When the player receives an item, what happens? + +When the player recieves an item, a fanfare or sound will be heard to reflect the item received. Most items, aside from Egg Capacity and level unlocks, can be checked on the menu by pressing SELECT. +If an item is in the queue and has not been received, checks will not be processed. diff --git a/worlds/yoshisisland/docs/setup_en.md b/worlds/yoshisisland/docs/setup_en.md new file mode 100644 index 000000000000..d76144608914 --- /dev/null +++ b/worlds/yoshisisland/docs/setup_en.md @@ -0,0 +1,122 @@ +# Yoshi's Island Archipelago Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). + + +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI such as: + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [TASVideos](https://tasvideos.org/BizHawk) + - snes9x-nwa from: [snes9x nwa](https://github.com/Skarsnik/snes9x-emunwa/releases) + + NOTE: RetroArch and FXPakPro are not currently supported. +- Your legally obtained Yoshi's Island English 1.0 ROM file, probably named `Super Mario World 2 - Yoshi's Island (U).sfc` + + +## Installation Procedures + +### Windows Setup + +1. Download and install Archipelago from the link above, making sure to install the most recent version. +2. During generation/patching, you will be asked to locate your base ROM file. This is your Yoshi's Island ROM file. +3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The Player Options page on the website allows you to configure your personal options and export a config file from +them. + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whomever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch +files. Your patch file should have a `.apyi` extension. + +Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the +client, and will also create your ROM in the same place as your patch file. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x-rr + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/Connector.lua`. +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. This is done with the main menubar, under: + - (≤ 2.8) `Config` 〉 `Cores` 〉 `SNES` 〉 `BSNES` + - (≥ 2.9) `Config` 〉 `Preferred Cores` 〉 `SNES` 〉 `BSNESv115+` +2. Load your ROM file if it hasn't already been loaded. + If you changed your core preference after loading the ROM, don't forget to reload it (default hotkey: Ctrl+R). +3. Drag+drop the `Connector.lua` file included with your client onto the main EmuHawk window. + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only. + - You could instead open the Lua Console manually, click `Script` 〉 `Open Script`, and navigate to `Connector.lua` + with the file picker. + + + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our hosting service. The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the Generate page above. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing. diff --git a/worlds/yoshisisland/level_logic.py b/worlds/yoshisisland/level_logic.py new file mode 100644 index 000000000000..094e5efed12d --- /dev/null +++ b/worlds/yoshisisland/level_logic.py @@ -0,0 +1,482 @@ +from BaseClasses import CollectionState +from typing import TYPE_CHECKING + +from .Options import StageLogic, BowserDoor, ObjectVis + +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +class YoshiLogic: + player: int + game_logic: str + midring_start: bool + clouds_always_visible: bool + consumable_logic: bool + luigi_pieces: int + + def __init__(self, world: "YoshisIslandWorld") -> None: + self.player = world.player + self.boss_order = world.boss_order + self.luigi_pieces = world.options.luigi_pieces_required.value + + if world.options.stage_logic == StageLogic.option_strict: + self.game_logic = "Easy" + elif world.options.stage_logic == StageLogic.option_loose: + self.game_logic = "Normal" + else: + self.game_logic = "Hard" + + self.midring_start = not world.options.shuffle_midrings + self.consumable_logic = not world.options.item_logic + + self.clouds_always_visible = world.options.hidden_object_visibility >= ObjectVis.option_clouds_only + + self.bowser_door = world.options.bowser_door_mode.value + if self.bowser_door == BowserDoor.option_door_4: + self.bowser_door = BowserDoor.option_door_3 + + def has_midring(self, state: CollectionState) -> bool: + return self.midring_start or state.has("Middle Ring", self.player) + + def reconstitute_luigi(self, state: CollectionState) -> bool: + return state.has("Piece of Luigi", self.player, self.luigi_pieces) + + def bandit_bonus(self, state: CollectionState) -> bool: + return state.has("Bandit Consumables", self.player) or state.has("Bandit Watermelons", self.player) + + def item_bonus(self, state: CollectionState) -> bool: + return state.has("Bonus Consumables", self.player) + + def combat_item(self, state: CollectionState) -> bool: + if not self.consumable_logic: + return False + else: + if self.game_logic == "Easy": + return self.item_bonus(state) + else: + return self.bandit_bonus(state) or self.item_bonus(state) + + def melon_item(self, state: CollectionState) -> bool: + if not self.consumable_logic: + return False + else: + if self.game_logic == "Easy": + return self.item_bonus(state) + else: + return state.has("Bandit Watermelons", self.player) or self.item_bonus(state) + + def default_vis(self, state: CollectionState) -> bool: + if self.clouds_always_visible: + return True + else: + return False + + def cansee_clouds(self, state: CollectionState) -> bool: + if self.game_logic != "Easy": + return True + else: + return self.default_vis(state) or state.has("Secret Lens", self.player) or self.combat_item(state) + + def bowserdoor_1(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Egg Plant", "! Switch"}, self.player) and state.has("Egg Capacity Upgrade", self.player, 2) + elif self.game_logic == "Normal": + return state.has("Egg Plant", self.player) and state.has("Egg Capacity Upgrade", self.player, 1) + else: + return state.has("Egg Plant", self.player) + + def bowserdoor_2(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return ((state.has("Egg Capacity Upgrade", self.player, 3) and state.has("Egg Plant", self.player)) or self.combat_item(state)) and state.has("Key", self.player) + elif self.game_logic == "Normal": + return ((state.has("Egg Capacity Upgrade", self.player, 2) and state.has("Egg Plant", self.player)) or self.combat_item(state)) and state.has("Key", self.player) + else: + return ((state.has("Egg Capacity Upgrade", self.player, 1) and state.has("Egg Plant", self.player)) or self.combat_item(state)) and state.has("Key", self.player) + + def bowserdoor_3(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def bowserdoor_4(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _68Route(self, state: CollectionState) -> bool: + if self.bowser_door == 0: + return True + elif self.bowser_door == 1: + return self.bowserdoor_1(state) + elif self.bowser_door == 2: + return self.bowserdoor_2(state) + elif self.bowser_door == 3: + return True + elif self.bowser_door == 4: + return True + elif self.bowser_door == 5: + return self.bowserdoor_1(state) and self.bowserdoor_2(state) and self.bowserdoor_3(state) + + def _68CollectibleRoute(self, state: CollectionState) -> bool: + if self.bowser_door == 0: + return True + elif self.bowser_door == 1: + return self.bowserdoor_1(state) + elif self.bowser_door == 2: + return self.bowserdoor_2(state) + elif self.bowser_door == 3: + return True + elif self.bowser_door == 4: + return True + elif self.bowser_door == 5: + return self.bowserdoor_1(state) + + +############################################################################## + def _13Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _14Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Spring Ball", "Key"}, self.player) + else: + return state.has_all({"Spring Ball", "Key"}, self.player) + + def _14Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Egg Plant", self.player) + elif self.game_logic == "Normal": + return state.has("Egg Plant", self.player) + else: + return (state.has("Egg Capacity Upgrade", self.player, 5) or state.has("Egg Plant", self.player)) + + def _14CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[0], "Location", self.player): + return True +############################################################################## + def _17Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _18Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Arrow Wheel"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Key", "Arrow Wheel"}, self.player) + else: + return state.has_all({"Key", "Arrow Wheel"}, self.player) + + def _18Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _18CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[1], "Location", self.player): + return True +############################################################################## + def _21Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Poochy", "Large Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Poochy", "Large Spring Ball", "Key"}, self.player) + else: + return state.has_all({"Poochy", "Large Spring Ball", "Key"}, self.player) +############################################################################## + def _23Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Mole Tank Morph", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Mole Tank Morph", "Key"}, self.player) + else: + return state.has_all({"Mole Tank Morph", "Key"}, self.player) +############################################################################## + def _24Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"! Switch", "Key", "Dashed Stairs"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"! Switch", "Dashed Stairs"}, self.player) + else: + return state.has("! Switch", self.player) + + def _24Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _24CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[2], "Location", self.player): + return True +############################################################################## + def _26Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + else: + return state.has_all({"Large Spring Ball", "Key"}, self.player) +############################################################################## + def _27Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _28Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Arrow Wheel", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 1)) + elif self.game_logic == "Normal": + return state.has_all({"Arrow Wheel", "Key"}, self.player) + else: + return state.has_all({"Arrow Wheel", "Key"}, self.player) + + def _28Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _28CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[3], "Location", self.player): + return True +############################################################################## + def _32Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Dashed Stairs", "Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Dashed Stairs", "Key"}, self.player) + else: + return state.has_all({"Dashed Stairs", "Key"}, self.player) +############################################################################## + def _34Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Dashed Platform", self.player) + elif self.game_logic == "Normal": + return (state.has("Dashed Platform", self.player) or self.has_midring(state)) + else: + return True + + def _34Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Giant Eggs", self.player) + elif self.game_logic == "Normal": + return True + else: + return True + + def _34CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[4], "Location", self.player): + return True +############################################################################## + def _37Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _38Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has("Egg Capacity Upgrade", self.player, 3) or self.combat_item(state)) + elif self.game_logic == "Normal": + return (state.has("Egg Capacity Upgrade", self.player, 1) or self.combat_item(state)) + else: + return True + + def _38Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _38CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[5], "Location", self.player): + return True +############################################################################## + def _42Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _44Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 1) or self.combat_item(state)) + elif self.game_logic == "Normal": + return state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, self.player) + else: + return state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, self.player) + + def _44Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _44CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[6], "Location", self.player): + return True +######################################################################################################## + + def _46Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + else: + return state.has_all({"Key", "Large Spring Ball"}, self.player) + + def _47Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + else: + return state.has_all({"Key", "Large Spring Ball"}, self.player) +############################################################################## + def _48Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key", "Large Spring Ball"}, self.player)) + elif self.game_logic == "Normal": + return (state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key", "Large Spring Ball"}, self.player)) + else: + return (state.has_all({"Key", "Large Spring Ball"}, self.player)) + + def _48Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has("Egg Capacity Upgrade", self.player, 3)) + elif self.game_logic == "Normal": + return (state.has("Egg Capacity Upgrade", self.player, 2)) + else: + return (state.has("Egg Capacity Upgrade", self.player, 1)) + + def _48CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[7], "Location", self.player): + return True +###################################################################################################################### + def _51Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _54Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, self.player)) + elif self.game_logic == "Normal": + return (state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, self.player)) + else: + return (state.has_all({"Dashed Stairs", "Platform Ghost"}, self.player)) + + def _54Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has("Egg Capacity Upgrade", self.player, 2) and state.has("Egg Plant", self.player)) + elif self.game_logic == "Normal": + return ((state.has("Egg Capacity Upgrade", self.player, 1) and state.has("Egg Plant", self.player)) or (state.has("Egg Capacity Upgrade", self.player, 5) and self.has_midring(state))) + else: + return ((state.has("Egg Plant", self.player)) or (state.has("Egg Capacity Upgrade", self.player, 3) and self.has_midring(state))) + + def _54CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[8], "Location", self.player): + return True +################################################################################################### + def _58Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Arrow Wheel", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Arrow Wheel", "Large Spring Ball"}, self.player) + else: + return state.has_all({"Arrow Wheel", "Large Spring Ball"}, self.player) + + def _58Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _58CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[9], "Location", self.player): + return True +############################################################################## + def _61Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Dashed Platform", "Key", "Beanstalk"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Dashed Platform", "Key", "Beanstalk"}, self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _64Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 3) or self.combat_item(state)) + elif self.game_logic == "Normal": + return state.has_all({"Large Spring Ball", "Egg Plant", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 2) or self.combat_item(state)) + else: + return state.has_all({"Egg Plant", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 1) or self.combat_item(state)) + + def _64Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Egg Plant", self.player) + elif self.game_logic == "Normal": + return state.has("Egg Plant", self.player) + else: + return True + + def _64CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[10], "Location", self.player): + return True +############################################################################## + def _67Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _68Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Helicopter Morph", "Egg Plant", "Giant Eggs"}, self.player) and self._68Route(state) + elif self.game_logic == "Normal": + return state.has_all({"Helicopter Morph", "Egg Plant", "Giant Eggs"}, self.player) and self._68Route(state) + else: + return state.has_all({"Helicopter Morph", "Giant Eggs"}, self.player) and self._68Route(state) diff --git a/worlds/yoshisisland/setup_bosses.py b/worlds/yoshisisland/setup_bosses.py new file mode 100644 index 000000000000..bbefdd31a05c --- /dev/null +++ b/worlds/yoshisisland/setup_bosses.py @@ -0,0 +1,19 @@ +from BaseClasses import CollectionState +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +class BossReqs: + player: int + + def __init__(self, world: "YoshisIslandWorld") -> None: + self.player = world.player + self.castle_unlock = world.options.castle_open_condition.value + self.boss_unlock = world.options.castle_clear_condition.value + + def castle_access(self, state: CollectionState) -> bool: + return state.has("Boss Clear", self.player, self.castle_unlock) + + def castle_clear(self, state: CollectionState) -> bool: + return state.has("Boss Clear", self.player, self.boss_unlock) diff --git a/worlds/yoshisisland/setup_game.py b/worlds/yoshisisland/setup_game.py new file mode 100644 index 000000000000..04a35f7657b7 --- /dev/null +++ b/worlds/yoshisisland/setup_game.py @@ -0,0 +1,460 @@ +import struct +from typing import TYPE_CHECKING + +from .Options import YoshiColors, BabySound, LevelShuffle + +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +def setup_gamevars(world: "YoshisIslandWorld") -> None: + if world.options.luigi_pieces_in_pool < world.options.luigi_pieces_required: + world.options.luigi_pieces_in_pool.value = world.random.randint(world.options.luigi_pieces_required.value, 100) + world.starting_lives = struct.pack("H", world.options.starting_lives) + + world.level_colors = [] + world.color_order = [] + for i in range(72): + world.level_colors.append(world.random.randint(0, 7)) + if world.options.yoshi_colors == YoshiColors.option_singularity: + singularity_color = world.options.yoshi_singularity_color.value + for i in range(len(world.level_colors)): + world.level_colors[i] = singularity_color + elif world.options.yoshi_colors == YoshiColors.option_random_order: + world.leader_color = world.random.randint(0, 7) + for i in range(7): + world.color_order.append(world.random.randint(0, 7)) + + bonus_valid = [0x00, 0x02, 0x04, 0x06, 0x08, 0x0A] + + world.world_bonus = [] + for i in range(12): + world.world_bonus.append(world.random.choice(bonus_valid)) + + safe_baby_sounds = [0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, + 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, + 0x73, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2] + + if world.options.baby_mario_sound == BabySound.option_random_sound_effect: + world.baby_mario_sfx = world.random.choice(safe_baby_sounds) + elif world.options.baby_mario_sound == BabySound.option_disabled: + world.baby_mario_sfx = 0x42 + else: + world.baby_mario_sfx = 0x44 + + boss_list = ["Burt The Bashful's Boss Room", "Salvo The Slime's Boss Room", + "Bigger Boo's Boss Room", "Roger The Ghost's Boss Room", + "Prince Froggy's Boss Room", "Naval Piranha's Boss Room", + "Marching Milde's Boss Room", "Hookbill The Koopa's Boss Room", + "Sluggy The Unshaven's Boss Room", "Raphael The Raven's Boss Room", + "Tap-Tap The Red Nose's Boss Room"] + + world.boss_order = [] + + if world.options.boss_shuffle: + world.random.shuffle(boss_list) + world.boss_order = boss_list + + burt_pointers = [0x3D, 0x05, 0x63, 0x00] + slime_pointers = [0x70, 0x04, 0x78, 0x00] + boo_pointers = [0x74, 0xBB, 0x7A, 0x00] + pot_pointers = [0xCF, 0x04, 0x4D, 0x00] + frog_pointers = [0xBF, 0x12, 0x62, 0x04] + plant_pointers = [0x7F, 0x0D, 0x42, 0x00] + milde_pointers = [0x82, 0x06, 0x64, 0x00] + koop_pointers = [0x86, 0x0D, 0x78, 0x00] + slug_pointers = [0x8A, 0x09, 0x7A, 0x00] + raph_pointers = [0xC4, 0x03, 0x4B, 0x05] + tap_pointers = [0xCC, 0x49, 0x64, 0x02] + + boss_data_list = [ + burt_pointers, + slime_pointers, + boo_pointers, + pot_pointers, + frog_pointers, + plant_pointers, + milde_pointers, + koop_pointers, + slug_pointers, + raph_pointers, + tap_pointers + ] + + boss_levels = [0x03, 0x07, 0x0F, 0x13, 0x1B, 0x1F, 0x27, 0x2B, 0x33, 0x37, 0x3F] + + boss_room_idlist = { + "Burt The Bashful's Boss Room": 0, + "Salvo The Slime's Boss Room": 1, + "Bigger Boo's Boss Room": 2, + "Roger The Ghost's Boss Room": 3, + "Prince Froggy's Boss Room": 4, + "Naval Piranha's Boss Room": 5, + "Marching Milde's Boss Room": 6, + "Hookbill The Koopa's Boss Room": 7, + "Sluggy The Unshaven's Boss Room": 8, + "Raphael The Raven's Boss Room": 9, + "Tap-Tap The Red Nose's Boss Room": 10, + } + + boss_check_list = { + "Burt The Bashful's Boss Room": "Burt The Bashful Defeated", + "Salvo The Slime's Boss Room": "Salvo The Slime Defeated", + "Bigger Boo's Boss Room": "Bigger Boo Defeated", + "Roger The Ghost's Boss Room": "Roger The Ghost Defeated", + "Prince Froggy's Boss Room": "Prince Froggy Defeated", + "Naval Piranha's Boss Room": "Naval Piranha Defeated", + "Marching Milde's Boss Room": "Marching Milde Defeated", + "Hookbill The Koopa's Boss Room": "Hookbill The Koopa Defeated", + "Sluggy The Unshaven's Boss Room": "Sluggy The Unshaven Defeated", + "Raphael The Raven's Boss Room": "Raphael The Raven Defeated", + "Tap-Tap The Red Nose's Boss Room": "Tap-Tap The Red Nose Defeated", + } + + world.boss_room_id = [boss_room_idlist[roomnum] for roomnum in world.boss_order] + world.tap_tap_room = boss_levels[world.boss_room_id.index(10)] + world.boss_ap_loc = [boss_check_list[roomnum] for roomnum in world.boss_order] + + world.boss_burt_data = boss_data_list[world.boss_room_id[0]] + + world.boss_slime_data = boss_data_list[world.boss_room_id[1]] + + world.boss_boo_data = boss_data_list[world.boss_room_id[2]] + + world.boss_pot_data = boss_data_list[world.boss_room_id[3]] + + world.boss_frog_data = boss_data_list[world.boss_room_id[4]] + + world.boss_plant_data = boss_data_list[world.boss_room_id[5]] + + world.boss_milde_data = boss_data_list[world.boss_room_id[6]] + + world.boss_koop_data = boss_data_list[world.boss_room_id[7]] + + world.boss_slug_data = boss_data_list[world.boss_room_id[8]] + + world.boss_raph_data = boss_data_list[world.boss_room_id[9]] + + world.boss_tap_data = boss_data_list[world.boss_room_id[10]] + + world.global_level_list = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42] + level_id_list = { + 0x00: "1-1", + 0x01: "1-2", + 0x02: "1-3", + 0x03: "1-4", + 0x04: "1-5", + 0x05: "1-6", + 0x06: "1-7", + 0x07: "1-8", + 0x0C: "2-1", + 0x0D: "2-2", + 0x0E: "2-3", + 0x0F: "2-4", + 0x10: "2-5", + 0x11: "2-6", + 0x12: "2-7", + 0x13: "2-8", + 0x18: "3-1", + 0x19: "3-2", + 0x1A: "3-3", + 0x1B: "3-4", + 0x1C: "3-5", + 0x1D: "3-6", + 0x1E: "3-7", + 0x1F: "3-8", + 0x24: "4-1", + 0x25: "4-2", + 0x26: "4-3", + 0x27: "4-4", + 0x28: "4-5", + 0x29: "4-6", + 0x2A: "4-7", + 0x2B: "4-8", + 0x30: "5-1", + 0x31: "5-2", + 0x32: "5-3", + 0x33: "5-4", + 0x34: "5-5", + 0x35: "5-6", + 0x36: "5-7", + 0x37: "5-8", + 0x3C: "6-1", + 0x3D: "6-2", + 0x3E: "6-3", + 0x3F: "6-4", + 0x40: "6-5", + 0x41: "6-6", + 0x42: "6-7" + } + + level_names = { + 0x00: "Make Eggs, Throw Eggs", + 0x01: "Watch Out Below!", + 0x02: "The Cave Of Chomp Rock", + 0x03: "Burt The Bashful's Fort", + 0x04: "Hop! Hop! Donut Lifts", + 0x05: "Shy-Guys On Stilts", + 0x06: "Touch Fuzzy Get Dizzy", + 0x07: "Salvo The Slime's Castle", + 0x0C: "Visit Koopa And Para-Koopa", + 0x0D: "The Baseball Boys", + 0x0E: "What's Gusty Taste Like?", + 0x0F: "Bigger Boo's Fort", + 0x10: "Watch Out For Lakitu", + 0x11: "The Cave Of The Mystery Maze", + 0x12: "Lakitu's Wall", + 0x13: "The Potted Ghost's Castle", + 0x18: "Welcome To Monkey World!", + 0x19: "Jungle Rhythm...", + 0x1A: "Nep-Enuts' Domain", + 0x1B: "Prince Froggy's Fort", + 0x1C: "Jammin' Through The Trees", + 0x1D: "The Cave Of Harry Hedgehog", + 0x1E: "Monkeys' Favorite Lake", + 0x1F: "Naval Piranha's Castle", + 0x24: "GO! GO! MARIO!!", + 0x25: "The Cave Of The Lakitus", + 0x26: "Don't Look Back!", + 0x27: "Marching Milde's Fort", + 0x28: "Chomp Rock Zone", + 0x29: "Lake Shore Paradise", + 0x2A: "Ride Like The Wind", + 0x2B: "Hookbill The Koopa's Castle", + 0x30: "BLIZZARD!!!", + 0x31: "Ride The Ski Lifts", + 0x32: "Danger - Icy Conditions Ahead", + 0x33: "Sluggy The Unshaven's Fort", + 0x34: "Goonie Rides!", + 0x35: "Welcome To Cloud World", + 0x36: "Shifting Platforms Ahead", + 0x37: "Raphael The Raven's Castle", + 0x3C: "Scary Skeleton Goonies!", + 0x3D: "The Cave Of The Bandits", + 0x3E: "Beware The Spinning Logs", + 0x3F: "Tap-Tap The Red Nose's Fort", + 0x40: "The Very Loooooong Cave", + 0x41: "The Deep, Underground Maze", + 0x42: "KEEP MOVING!!!!" + } + + world_1_offsets = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00] + world_2_offsets = [0x01, 0x01, 0x00, 0x00, 0x00, 0x00] + world_3_offsets = [0x01, 0x01, 0x01, 0x00, 0x00, 0x00] + world_4_offsets = [0x01, 0x01, 0x01, 0x01, 0x00, 0x00] + world_5_offsets = [0x01, 0x01, 0x01, 0x01, 0x01, 0x00] + easy_start_lv = [0x02, 0x04, 0x06, 0x0E, 0x10, 0x18, 0x1C, 0x28, + 0x30, 0x31, 0x35, 0x36, 0x3E, 0x40, 0x42] + norm_start_lv = [0x00, 0x01, 0x02, 0x04, 0x06, 0x0E, 0x10, 0x12, 0x18, 0x1A, + 0x1C, 0x1E, 0x28, 0x30, 0x31, 0x34, 0x35, 0x36, 0x3D, 0x3E, 0x40, 0x42] + hard_start_lv = [0x00, 0x01, 0x02, 0x04, 0x06, 0x0D, 0x0E, 0x10, 0x11, 0x12, 0x18, 0x1A, 0x1C, + 0x1E, 0x24, 0x25, 0x26, 0x28, 0x29, 0x30, 0x31, 0x34, 0x35, 0x36, 0x3D, 0x3E, + 0x40, 0x42] + diff_index = [easy_start_lv, norm_start_lv, hard_start_lv] + diff_level = diff_index[world.options.stage_logic.value] + boss_lv = [0x03, 0x07, 0x0F, 0x13, 0x1B, 0x1F, 0x27, 0x2B, 0x33, 0x37, 0x3F] + world.world_start_lv = [0, 8, 16, 24, 32, 40] + if not world.options.shuffle_midrings: + easy_start_lv.extend([0x1A, 0x24, 0x34]) + norm_start_lv.extend([0x24, 0x3C]) + hard_start_lv.extend([0x1D, 0x3C]) + + if world.options.level_shuffle != LevelShuffle.option_bosses_guaranteed: + hard_start_lv.extend([0x07, 0x1B, 0x1F, 0x2B, 0x33, 0x37]) + if not world.options.shuffle_midrings: + easy_start_lv.extend([0x1B]) + norm_start_lv.extend([0x1B, 0x2B, 0x37]) + + starting_level = world.random.choice(diff_level) + + starting_level_entrance = world.world_start_lv[world.options.starting_world.value] + if world.options.level_shuffle: + world.global_level_list.remove(starting_level) + world.random.shuffle(world.global_level_list) + if world.options.level_shuffle == LevelShuffle.option_bosses_guaranteed: + for i in range(11): + world.global_level_list = [item for item in world.global_level_list + if item not in boss_lv] + world.random.shuffle(boss_lv) + + world.global_level_list.insert(3 - world_1_offsets[world.options.starting_world.value], boss_lv[0]) # 1 if starting world is 1, 0 otherwise + world.global_level_list.insert(7 - world_1_offsets[world.options.starting_world.value], boss_lv[1]) + world.global_level_list.insert(11 - world_2_offsets[world.options.starting_world.value], boss_lv[2]) + world.global_level_list.insert(15 - world_2_offsets[world.options.starting_world.value], boss_lv[3]) + world.global_level_list.insert(19 - world_3_offsets[world.options.starting_world.value], boss_lv[4]) + world.global_level_list.insert(23 - world_3_offsets[world.options.starting_world.value], boss_lv[5]) + world.global_level_list.insert(27 - world_4_offsets[world.options.starting_world.value], boss_lv[6]) + world.global_level_list.insert(31 - world_4_offsets[world.options.starting_world.value], boss_lv[7]) + world.global_level_list.insert(35 - world_5_offsets[world.options.starting_world.value], boss_lv[8]) + world.global_level_list.insert(39 - world_5_offsets[world.options.starting_world.value], boss_lv[9]) + world.global_level_list.insert(43 - 1, boss_lv[10]) + world.global_level_list.insert(starting_level_entrance, starting_level) + world.level_location_list = [level_id_list[LevelID] for LevelID in world.global_level_list] + world.level_name_list = [level_names[LevelID] for LevelID in world.global_level_list] + + level_panel_dict = { + 0x00: [0x04, 0x04, 0x53], + 0x01: [0x20, 0x04, 0x53], + 0x02: [0x3C, 0x04, 0x53], + 0x03: [0x58, 0x04, 0x53], + 0x04: [0x74, 0x04, 0x53], + 0x05: [0x90, 0x04, 0x53], + 0x06: [0xAC, 0x04, 0x53], + 0x07: [0xC8, 0x04, 0x53], + 0x0C: [0x04, 0x24, 0x53], + 0x0D: [0x20, 0x24, 0x53], + 0x0E: [0x3C, 0x24, 0x53], + 0x0F: [0x58, 0x24, 0x53], + 0x10: [0x74, 0x24, 0x53], + 0x11: [0x90, 0x24, 0x53], + 0x12: [0xAC, 0x24, 0x53], + 0x13: [0xC8, 0x24, 0x53], + 0x18: [0x04, 0x44, 0x53], + 0x19: [0x20, 0x44, 0x53], + 0x1A: [0x3C, 0x44, 0x53], + 0x1B: [0x58, 0x44, 0x53], + 0x1C: [0x74, 0x44, 0x53], + 0x1D: [0x90, 0x44, 0x53], + 0x1E: [0xAC, 0x44, 0x53], + 0x1F: [0xC8, 0x44, 0x53], + 0x24: [0x04, 0x64, 0x53], + 0x25: [0x20, 0x64, 0x53], + 0x26: [0x3C, 0x64, 0x53], + 0x27: [0x58, 0x64, 0x53], + 0x28: [0x74, 0x64, 0x53], + 0x29: [0x90, 0x64, 0x53], + 0x2A: [0xAC, 0x64, 0x53], + 0x2B: [0xC8, 0x64, 0x53], + 0x30: [0x04, 0x04, 0x53], + 0x31: [0x20, 0x04, 0x53], + 0x32: [0x3C, 0x04, 0x53], + 0x33: [0x58, 0x04, 0x53], + 0x34: [0x74, 0x04, 0x53], + 0x35: [0x90, 0x04, 0x53], + 0x36: [0xAC, 0x04, 0x53], + 0x37: [0xC8, 0x04, 0x53], + 0x3C: [0x04, 0x24, 0x53], + 0x3D: [0x20, 0x24, 0x53], + 0x3E: [0x3C, 0x24, 0x53], + 0x3F: [0x58, 0x24, 0x53], + 0x40: [0x74, 0x24, 0x53], + 0x41: [0x90, 0x24, 0x53], + 0x42: [0xAC, 0x24, 0x53], + } + panel_palette_1 = [0x00, 0x03, 0x04, 0x05, 0x0C, 0x10, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x24, 0x26, 0x27, 0x29, 0x2A, 0x2B, 0x30, 0x32, 0x34, + 0x35, 0x37, 0x3C, 0x3D, 0x40, 0x41] # 000C + panel_palette_2 = [0x01, 0x02, 0x06, 0x07, 0x0D, 0x0E, 0x0F, 0x11, 0x18, 0x1E, 0x1F, 0x25, 0x28, + 0x31, 0x33, 0x36, 0x3E, 0x3F, 0x42] # 0010 + + stage_number = 0 + world_number = 1 + for i in range(47): + stage_number += 1 + if stage_number >= 9: + world_number += 1 + stage_number = 1 + for _ in range(3): + setattr(world, f"Stage{world_number}{stage_number}StageGFX", + level_panel_dict[world.global_level_list[i]]) + + world.level_gfx_table = [] + world.palette_panel_list = [] + + for i in range(47): + if world.global_level_list[i] >= 0x30: + world.level_gfx_table.append(0x15) + else: + world.level_gfx_table.append(0x11) + + if world.global_level_list[i] in panel_palette_1: + world.palette_panel_list.extend([0x00, 0x0C]) + elif world.global_level_list[i] in panel_palette_2: + world.palette_panel_list.extend([0x00, 0x10]) + + world.palette_panel_list[16:16] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[40:40] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[64:64] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[88:88] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[112:112] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + + world.level_gfx_table.insert(8, 0x15) + world.level_gfx_table.insert(8, 0x15) + world.level_gfx_table.insert(8, 0x15) + world.level_gfx_table.insert(8, 0x11) + + world.level_gfx_table.insert(20, 0x15) + world.level_gfx_table.insert(20, 0x15) + world.level_gfx_table.insert(20, 0x15) + world.level_gfx_table.insert(20, 0x11) + + world.level_gfx_table.insert(32, 0x15) + world.level_gfx_table.insert(32, 0x15) + world.level_gfx_table.insert(32, 0x15) + world.level_gfx_table.insert(32, 0x11) + + world.level_gfx_table.insert(44, 0x15) + world.level_gfx_table.insert(44, 0x15) + world.level_gfx_table.insert(44, 0x15) + world.level_gfx_table.insert(44, 0x11) + + world.level_gfx_table.insert(56, 0x15) + world.level_gfx_table.insert(56, 0x15) + world.level_gfx_table.insert(56, 0x15) + world.level_gfx_table.insert(56, 0x15) + + castle_door_dict = { + 0: [0xB8, 0x05, 0x77, 0x00], + 1: [0xB8, 0x05, 0x77, 0x00], + 2: [0xC6, 0x07, 0x7A, 0x00], + 3: [0xCD, 0x05, 0x5B, 0x00], + 4: [0xD3, 0x00, 0x77, 0x06], + 5: [0xB8, 0x05, 0x77, 0x00], + } + + world.castle_door = castle_door_dict[world.options.bowser_door_mode.value] + + world.world_1_stages = world.global_level_list[0:8] + world.world_2_stages = world.global_level_list[8:16] + world.world_3_stages = world.global_level_list[16:24] + world.world_4_stages = world.global_level_list[24:32] + world.world_5_stages = world.global_level_list[32:40] + world.world_6_stages = world.global_level_list[40:47] + + world.world_1_stages.extend([0x08, 0x09]) + world.world_2_stages.extend([0x14, 0x15]) + world.world_3_stages.extend([0x20, 0x21]) + world.world_4_stages.extend([0x2C, 0x2D]) + world.world_5_stages.extend([0x38, 0x39]) + world.world_6_stages.extend([0x43, 0x44, 0x45]) + + bowser_text_table = { + 0: [0xDE, 0xEE, 0xDC, 0xDC, 0xE5], # Gween + 1: [0xE7, 0xE0, 0xE5, 0xE2, 0xD0], # Pink + 3: [0xEB, 0xDF, 0xF0, 0xD8, 0xE5], # Thyan + 2: [0xF0, 0xDC, 0xEE, 0xEE, 0xE6], # Yewow + 4: [0xE7, 0xEC, 0xDF, 0xE7, 0xE3], # puhpl + 5: [0xD9, 0xEE, 0xE6, 0xEE, 0xE5], # Bwown + 6: [0xEE, 0xDC, 0xDB, 0xD0, 0xD0], # Wed + 7: [0xD9, 0xEE, 0xEC, 0xDC, 0xD0], # Bwue + } + + if world.options.yoshi_colors == YoshiColors.option_random_order: + world.bowser_text = bowser_text_table[world.leader_color] + else: + world.bowser_text = bowser_text_table[world.level_colors[67]] diff --git a/worlds/yugioh06/__init__.py b/worlds/yugioh06/__init__.py new file mode 100644 index 000000000000..1cf44f090fed --- /dev/null +++ b/worlds/yugioh06/__init__.py @@ -0,0 +1,456 @@ +import os +import pkgutil +from typing import Any, ClassVar, Dict, List + +import settings +from BaseClasses import Entrance, Item, ItemClassification, Location, MultiWorld, Region, Tutorial + +import Utils +from worlds.AutoWorld import WebWorld, World + +from .boosterpacks import booster_contents as booster_contents +from .boosterpacks import get_booster_locations +from .items import ( + Banlist_Items, + booster_packs, + draft_boosters, + draft_opponents, + excluded_items, + item_to_index, + tier_1_opponents, + useful, +) +from .items import ( + challenges as challenges, +) +from .locations import ( + Bonuses, + Campaign_Opponents, + Limited_Duels, + Required_Cards, + Theme_Duels, + collection_events, + get_beat_challenge_events, + special, +) +from .logic import core_booster, yugioh06_difficulty +from .opponents import OpponentData, get_opponent_condition, get_opponent_locations, get_opponents +from .opponents import challenge_opponents as challenge_opponents +from .options import Yugioh06Options +from .rom import MD5America, MD5Europe, YGO06ProcedurePatch, write_tokens +from .rom import get_base_rom_path as get_base_rom_path +from .rom_values import banlist_ids as banlist_ids +from .rom_values import function_addresses as function_addresses +from .rom_values import structure_deck_selection as structure_deck_selection +from .rules import set_rules +from .structure_deck import get_deck_content_locations +from .client_bh import YuGiOh2006Client + + +class Yugioh06Web(WebWorld): + theme = "stone" + setup = Tutorial( + "Multiworld Setup Tutorial", + "A guide to setting up Yu-Gi-Oh! - Ultimate Masters Edition - World Championship Tournament 2006 " + "for Archipelago on your computer.", + "English", + "docs/setup_en.md", + "setup/en", + ["Rensen"], + ) + tutorials = [setup] + + +class Yugioh2006Setting(settings.Group): + class Yugioh2006RomFile(settings.UserFilePath): + """File name of your Yu-Gi-Oh 2006 ROM""" + + description = "Yu-Gi-Oh 2006 ROM File" + copy_to = "YuGiOh06.gba" + md5s = [MD5Europe, MD5America] + + rom_file: Yugioh2006RomFile = Yugioh2006RomFile(Yugioh2006RomFile.copy_to) + + +class Yugioh06World(World): + """ + Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 is the definitive Yu-Gi-Oh + simulator on the GBA. Featuring over 2000 cards and over 90 Challenges. + """ + + game = "Yu-Gi-Oh! 2006" + web = Yugioh06Web() + options: Yugioh06Options + options_dataclass = Yugioh06Options + settings_key = "yugioh06_settings" + settings: ClassVar[Yugioh2006Setting] + + item_name_to_id = {} + start_id = 5730000 + for k, v in item_to_index.items(): + item_name_to_id[k] = v + start_id + + location_name_to_id = {} + for k, v in Bonuses.items(): + location_name_to_id[k] = v + start_id + + for k, v in Limited_Duels.items(): + location_name_to_id[k] = v + start_id + + for k, v in Theme_Duels.items(): + location_name_to_id[k] = v + start_id + + for k, v in Campaign_Opponents.items(): + location_name_to_id[k] = v + start_id + + for k, v in special.items(): + location_name_to_id[k] = v + start_id + + for k, v in Required_Cards.items(): + location_name_to_id[k] = v + start_id + + item_name_groups = { + "Core Booster": core_booster, + "Campaign Boss Beaten": ["Tier 1 Beaten", "Tier 2 Beaten", "Tier 3 Beaten", "Tier 4 Beaten", "Tier 5 Beaten"], + } + + removed_challenges: List[str] + starting_booster: str + starting_opponent: str + campaign_opponents: List[OpponentData] + is_draft_mode: bool + + def __init__(self, world: MultiWorld, player: int): + super().__init__(world, player) + + def generate_early(self): + self.starting_opponent = "" + self.starting_booster = "" + self.removed_challenges = [] + # Universal tracker stuff, shouldn't do anything in standard gen + if hasattr(self.multiworld, "re_gen_passthrough"): + if "Yu-Gi-Oh! 2006" in self.multiworld.re_gen_passthrough: + # bypassing random yaml settings + slot_data = self.multiworld.re_gen_passthrough["Yu-Gi-Oh! 2006"] + self.options.structure_deck.value = slot_data["structure_deck"] + self.options.banlist.value = slot_data["banlist"] + self.options.final_campaign_boss_unlock_condition.value = slot_data[ + "final_campaign_boss_unlock_condition" + ] + self.options.fourth_tier_5_campaign_boss_unlock_condition.value = slot_data[ + "fourth_tier_5_campaign_boss_unlock_condition" + ] + self.options.third_tier_5_campaign_boss_unlock_condition.value = slot_data[ + "third_tier_5_campaign_boss_unlock_condition" + ] + self.options.final_campaign_boss_challenges.value = slot_data["final_campaign_boss_challenges"] + self.options.fourth_tier_5_campaign_boss_challenges.value = slot_data[ + "fourth_tier_5_campaign_boss_challenges" + ] + self.options.third_tier_5_campaign_boss_challenges.value = slot_data[ + "third_tier_5_campaign_boss_challenges" + ] + self.options.final_campaign_boss_campaign_opponents.value = slot_data[ + "final_campaign_boss_campaign_opponents" + ] + self.options.fourth_tier_5_campaign_boss_campaign_opponents.value = slot_data[ + "fourth_tier_5_campaign_boss_campaign_opponents" + ] + self.options.third_tier_5_campaign_boss_campaign_opponents.value = slot_data[ + "third_tier_5_campaign_boss_campaign_opponents" + ] + self.options.number_of_challenges.value = slot_data["number_of_challenges"] + self.removed_challenges = slot_data["removed challenges"] + self.starting_booster = slot_data["starting_booster"] + self.starting_opponent = slot_data["starting_opponent"] + + if self.options.structure_deck.current_key == "none": + self.is_draft_mode = True + boosters = draft_boosters + if self.options.campaign_opponents_shuffle.value: + opponents = tier_1_opponents + else: + opponents = draft_opponents + else: + self.is_draft_mode = False + boosters = booster_packs + opponents = tier_1_opponents + + if self.options.structure_deck.current_key == "random_deck": + self.options.structure_deck.value = self.random.randint(0, 5) + for item in self.options.start_inventory: + if item in opponents: + self.starting_opponent = item + if item in boosters: + self.starting_booster = item + if not self.starting_opponent: + self.starting_opponent = self.random.choice(opponents) + self.multiworld.push_precollected(self.create_item(self.starting_opponent)) + if not self.starting_booster: + self.starting_booster = self.random.choice(boosters) + self.multiworld.push_precollected(self.create_item(self.starting_booster)) + banlist = self.options.banlist.value + self.multiworld.push_precollected(self.create_item(Banlist_Items[banlist])) + + if not self.removed_challenges: + challenge = list(({**Limited_Duels, **Theme_Duels}).keys()) + noc = len(challenge) - max( + self.options.third_tier_5_campaign_boss_challenges.value + if self.options.third_tier_5_campaign_boss_unlock_condition == "challenges" + else 0, + self.options.fourth_tier_5_campaign_boss_challenges.value + if self.options.fourth_tier_5_campaign_boss_unlock_condition == "challenges" + else 0, + self.options.final_campaign_boss_challenges.value + if self.options.final_campaign_boss_unlock_condition == "challenges" + else 0, + self.options.number_of_challenges.value, + ) + + self.random.shuffle(challenge) + excluded = self.options.exclude_locations.value.intersection(challenge) + prio = self.options.priority_locations.value.intersection(challenge) + normal = [e for e in challenge if e not in excluded and e not in prio] + total = list(excluded) + normal + list(prio) + self.removed_challenges = total[:noc] + + self.campaign_opponents = get_opponents( + self.multiworld, self.player, self.options.campaign_opponents_shuffle.value + ) + + def create_region(self, name: str, locations=None, exits=None): + region = Region(name, self.player, self.multiworld) + if locations: + for location_name, lid in locations.items(): + if lid is not None and isinstance(lid, int): + lid = self.location_name_to_id[location_name] + else: + lid = None + location = Yugioh2006Location(self.player, location_name, lid, region) + region.locations.append(location) + + if exits: + for _exit in exits: + region.exits.append(Entrance(self.player, _exit, region)) + return region + + def create_regions(self): + structure_deck = self.options.structure_deck.current_key + self.multiworld.regions += [ + self.create_region("Menu", None, ["to Deck Edit", "to Campaign", "to Challenges", "to Card Shop"]), + self.create_region("Campaign", {**Bonuses, **Campaign_Opponents}), + self.create_region("Challenges"), + self.create_region("Card Shop", {**Required_Cards, **collection_events}), + self.create_region("Structure Deck", get_deck_content_locations(structure_deck)), + ] + + self.get_entrance("to Campaign").connect(self.get_region("Campaign")) + self.get_entrance("to Challenges").connect(self.get_region("Challenges")) + self.get_entrance("to Card Shop").connect(self.get_region("Card Shop")) + self.get_entrance("to Deck Edit").connect(self.get_region("Structure Deck")) + + campaign = self.get_region("Campaign") + # Campaign Opponents + for opponent in self.campaign_opponents: + unlock_item = "Campaign Tier " + str(opponent.tier) + " Column " + str(opponent.column) + region = self.create_region(opponent.name, get_opponent_locations(opponent)) + entrance = Entrance(self.player, unlock_item, campaign) + if opponent.tier == 5 and opponent.column > 2: + unlock_amount = 0 + is_challenge = True + if opponent.column == 3: + if self.options.third_tier_5_campaign_boss_unlock_condition.value == 1: + unlock_item = "Challenge Beaten" + unlock_amount = self.options.third_tier_5_campaign_boss_challenges.value + is_challenge = True + else: + unlock_item = "Campaign Boss Beaten" + unlock_amount = self.options.third_tier_5_campaign_boss_campaign_opponents.value + is_challenge = False + if opponent.column == 4: + if self.options.fourth_tier_5_campaign_boss_unlock_condition.value == 1: + unlock_item = "Challenge Beaten" + unlock_amount = self.options.fourth_tier_5_campaign_boss_challenges.value + is_challenge = True + else: + unlock_item = "Campaign Boss Beaten" + unlock_amount = self.options.fourth_tier_5_campaign_boss_campaign_opponents.value + is_challenge = False + if opponent.column == 5: + if self.options.final_campaign_boss_unlock_condition.value == 1: + unlock_item = "Challenge Beaten" + unlock_amount = self.options.final_campaign_boss_challenges.value + is_challenge = True + else: + unlock_item = "Campaign Boss Beaten" + unlock_amount = self.options.final_campaign_boss_campaign_opponents.value + is_challenge = False + entrance.access_rule = get_opponent_condition( + opponent, unlock_item, unlock_amount, self.player, is_challenge + ) + else: + entrance.access_rule = lambda state, unlock=unlock_item, opp=opponent: state.has( + unlock, self.player + ) and yugioh06_difficulty(state, self.player, opp.difficulty) + campaign.exits.append(entrance) + entrance.connect(region) + self.multiworld.regions.append(region) + + card_shop = self.get_region("Card Shop") + # Booster Contents + for booster in booster_packs: + region = self.create_region(booster, get_booster_locations(booster)) + entrance = Entrance(self.player, booster, card_shop) + entrance.access_rule = lambda state, unlock=booster: state.has(unlock, self.player) + card_shop.exits.append(entrance) + entrance.connect(region) + self.multiworld.regions.append(region) + + challenge_region = self.get_region("Challenges") + # Challenges + for challenge, lid in ({**Limited_Duels, **Theme_Duels}).items(): + if challenge in self.removed_challenges: + continue + region = self.create_region(challenge, {challenge: lid, challenge + " Complete": None}) + entrance = Entrance(self.player, challenge, challenge_region) + entrance.access_rule = lambda state, unlock=challenge: state.has(unlock + " Unlock", self.player) + challenge_region.exits.append(entrance) + entrance.connect(region) + self.multiworld.regions.append(region) + + def create_item(self, name: str) -> Item: + classification: ItemClassification = ItemClassification.progression + if name == "5000DP": + classification = ItemClassification.filler + if name in useful: + classification = ItemClassification.useful + return Item(name, classification, self.item_name_to_id[name], self.player) + + def create_filler(self) -> Item: + return self.create_item("5000DP") + + def get_filler_item_name(self) -> str: + return "5000DP" + + def create_items(self): + start_inventory = self.options.start_inventory.value.copy() + item_pool = [] + items = item_to_index.copy() + starting_list = Banlist_Items[self.options.banlist.value] + if not self.options.add_empty_banlist.value and starting_list != "No Banlist": + items.pop("No Banlist") + for rc in self.removed_challenges: + items.pop(rc + " Unlock") + items.pop(self.starting_opponent) + items.pop(self.starting_booster) + items.pop(starting_list) + for name in items: + if name in excluded_items or name in start_inventory: + continue + item = self.create_item(name) + item_pool.append(item) + + needed_item_pool_size = sum(loc not in self.removed_challenges for loc in self.location_name_to_id) + needed_filler_amount = needed_item_pool_size - len(item_pool) + item_pool += [self.create_item("5000DP") for _ in range(needed_filler_amount)] + + self.multiworld.itempool += item_pool + + for challenge in get_beat_challenge_events(self): + item = Yugioh2006Item("Challenge Beaten", ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(challenge, self.player) + location.place_locked_item(item) + + for opponent in self.campaign_opponents: + for location_name, event in get_opponent_locations(opponent).items(): + if event is not None and not isinstance(event, int): + item = Yugioh2006Item(event, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(location_name, self.player) + location.place_locked_item(item) + + for booster in booster_packs: + for location_name, content in get_booster_locations(booster).items(): + item = Yugioh2006Item(content, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(location_name, self.player) + location.place_locked_item(item) + + structure_deck = self.options.structure_deck.current_key + for location_name, content in get_deck_content_locations(structure_deck).items(): + item = Yugioh2006Item(content, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(location_name, self.player) + location.place_locked_item(item) + + for event in collection_events: + item = Yugioh2006Item(event, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(event, self.player) + location.place_locked_item(item) + + def set_rules(self): + set_rules(self) + + def generate_output(self, output_directory: str): + outfilepname = f"_P{self.player}" + outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" + self.rom_name_text = f'YGO06{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0' + self.romName = bytearray(self.rom_name_text, "utf8")[:0x20] + self.romName.extend([0] * (0x20 - len(self.romName))) + self.rom_name = self.romName + self.playerName = bytearray(self.multiworld.player_name[self.player], "utf8")[:0x20] + self.playerName.extend([0] * (0x20 - len(self.playerName))) + patch = YGO06ProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player]) + patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "patch.bsdiff4")) + procedure = [("apply_bsdiff4", ["base_patch.bsdiff4"]), ("apply_tokens", ["token_data.bin"])] + if self.is_draft_mode: + procedure.insert(1, ("apply_bsdiff4", ["draft_patch.bsdiff4"])) + patch.write_file("draft_patch.bsdiff4", pkgutil.get_data(__name__, "patches/draft.bsdiff4")) + if self.options.ocg_arts: + procedure.insert(1, ("apply_bsdiff4", ["ocg_patch.bsdiff4"])) + patch.write_file("ocg_patch.bsdiff4", pkgutil.get_data(__name__, "patches/ocg.bsdiff4")) + patch.procedure = procedure + write_tokens(self, patch) + + # Write Output + out_file_name = self.multiworld.get_out_file_name_base(self.player) + patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}")) + + def fill_slot_data(self) -> Dict[str, Any]: + slot_data: Dict[str, Any] = { + "structure_deck": self.options.structure_deck.value, + "banlist": self.options.banlist.value, + "final_campaign_boss_unlock_condition": self.options.final_campaign_boss_unlock_condition.value, + "fourth_tier_5_campaign_boss_unlock_condition": + self.options.fourth_tier_5_campaign_boss_unlock_condition.value, + "third_tier_5_campaign_boss_unlock_condition": + self.options.third_tier_5_campaign_boss_unlock_condition.value, + "final_campaign_boss_challenges": self.options.final_campaign_boss_challenges.value, + "fourth_tier_5_campaign_boss_challenges": + self.options.fourth_tier_5_campaign_boss_challenges.value, + "third_tier_5_campaign_boss_challenges": + self.options.third_tier_5_campaign_boss_campaign_opponents.value, + "final_campaign_boss_campaign_opponents": + self.options.final_campaign_boss_campaign_opponents.value, + "fourth_tier_5_campaign_boss_campaign_opponents": + self.options.fourth_tier_5_campaign_boss_unlock_condition.value, + "third_tier_5_campaign_boss_campaign_opponents": + self.options.third_tier_5_campaign_boss_campaign_opponents.value, + "number_of_challenges": self.options.number_of_challenges.value, + } + + slot_data["removed challenges"] = self.removed_challenges + slot_data["starting_booster"] = self.starting_booster + slot_data["starting_opponent"] = self.starting_opponent + return slot_data + + # for the universal tracker, doesn't get called in standard gen + @staticmethod + def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]: + # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough + return slot_data + + +class Yugioh2006Item(Item): + game: str = "Yu-Gi-Oh! 2006" + + +class Yugioh2006Location(Location): + game: str = "Yu-Gi-Oh! 2006" diff --git a/worlds/yugioh06/boosterpacks.py b/worlds/yugioh06/boosterpacks.py new file mode 100644 index 000000000000..645977d28def --- /dev/null +++ b/worlds/yugioh06/boosterpacks.py @@ -0,0 +1,923 @@ +from typing import Dict, List + +booster_contents: Dict[str, List[str]] = { + "LEGEND OF B.E.W.D.": [ + "Exodia", + "Dark Magician", + "Polymerization", + "Skull Servant" + ], + "METAL RAIDERS": [ + "Petit Moth", + "Cocoon of Evolution", + "Time Wizard", + "Gate Guardian", + "Kazejin", + "Suijin", + "Sanga of the Thunder", + "Sangan", + "Castle of Dark Illusions", + "Soul Release", + "Magician of Faith", + "Dark Elf", + "Summoned Skull", + "Sangan", + "7 Colored Fish", + "Tribute to the Doomed", + "Horn of Heaven", + "Magic Jammer", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Dream Clown", + "Heavy Storm" + ], + "PHARAOH'S SERVANT": [ + "Beast of Talwar", + "Jinzo", + "Gearfried the Iron Knight", + "Harpie's Brother", + "Gravity Bind", + "Solemn Wishes", + "Kiseitai", + "Morphing Jar #2", + "The Shallow Grave", + "Nobleman of Crossout", + "Magic Drain" + ], + "PHARAONIC GUARDIAN": [ + "Don Zaloog", + "Reasoning", + "Dark Snake Syndrome", + "Helpoemer", + "Newdoria", + "Spirit Reaper", + "Yomi Ship", + "Pyramid Turtle", + "Master Kyonshee", + "Book of Life", + "Call of the Mummy", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "A Cat of Ill Omen", + "Jowls of Dark Demise", + "Non Aggression Area", + "Terraforming", + "Des Lacooda", + "Swarm of Locusts", + "Swarm of Scarabs", + "Wandering Mummy", + "Royal Keeper", + "Book of Moon", + "Book of Taiyou", + "Dust Tornado", + "Raigeki Break" + ], + "SPELL RULER": [ + "Ritual", + "Messenger of Peace", + "Megamorph", + "Shining Angel", + "Mystic Tomato", + "Giant Rat", + "Mother Grizzly", + "UFO Turtle", + "Flying Kamakiri 1", + "Giant Germ", + "Nimble Momonga", + "Cyber Jar", + "Spear Cretin", + "Toon Mermaid", + "Toon Summoned Skull", + "Toon World", + "Rush Recklessly", + "The Reliable Guardian", + "Senju of the Thousand Hands", + "Sonic Bird", + "Mystical Space Typhoon" + ], + "LABYRINTH OF NIGHTMARE": [ + "Destiny Board", + "Spirit Message 'I'", + "Spirit Message 'N'", + "Spirit Message 'A'", + "Spirit Message 'L'", + "Fusion Gate", + "Jowgen the Spiritualist", + "Fairy Box", + "Aqua Spirit", + "Rock Spirit", + "Spirit of Flames", + "Garuda the Wind Spirit", + "Hysteric Fairy", + "Kycoo the Ghost Destroyer", + "Gemini Elf", + "Amphibian Beast", + "Revival Jam", + "Dancing Fairy", + "Cure Mermaid", + "The Last Warrior from Another Planet", + "United We Stand", + "Earthbound Spirit", + "The Masked Beast" + ], + "LEGACY OF DARKNESS": [ + "Last Turn", + "Yata-Garasu", + "Opticlops", + "Dark Ruler Ha Des", + "Exiled Force", + "Injection Fairy Lily", + "Spear Dragon", + "Luster Dragon #2", + "Twin-Headed Behemoth", + "Airknight Parshath", + "Freed the Matchless General", + "Marauding Captain", + "Reinforcement of the Army", + "Cave Dragon", + "Troop Dragon", + "Stamping Destruction", + "Creature Swap", + "Asura Priest", + "Fushi No Tori", + "Maharaghi", + "Susa Soldier", + "Emergency Provisions", + ], + "MAGICIAN'S FORCE": [ + "Huge Revolution", + "Oppressed People", + "United Resistance", + "People Running About", + "X-Head Cannon", + "Y-Dragon Head", + "Z-Metal Tank", + "XY-Dragon Cannon", + "XZ-Tank Cannon", + "YZ-Tank Dragon", + "XYZ-Dragon Cannon", + "Cliff the Trap Remover", + "Wave-Motion Cannon", + "Ritual", + "Magical Merchant", + "Poison of the Old Man", + "Chaos Command Magician", + "Skilled Dark Magician", + "Dark Blade", + "Great Angus", + "Luster Dragon", + "Breaker the magical Warrior", + "Old Vindictive Magician", + "Apprentice Magician", + "Burning Beast", + "Freezing Beast", + "Pitch-Dark Dragon", + "Giant Orc", + "Second Goblin", + "Decayed Commander", + "Zombie Tiger", + "Vampire Orchis", + "Des Dendle", + "Frontline Base", + "Formation Union", + "Pitch-Black Power Stone", + "Magical Marionette", + "Royal Magical Library", + "Spell Shield Type-8", + "Tribute Doll", + ], + "DARK CRISIS": [ + "Final Countdown", + "Ojama Green", + "Dark Scorpion Combination", + "Dark Scorpion - Chick the Yellow", + "Dark Scorpion - Meanae the Thorn", + "Dark Scorpion - Gorg the Strong", + "Ritual", + "Tsukuyomi", + "Ojama Trio", + "Kaiser Glider", + "D.D. Warrior Lady", + "Archfiend Soldier", + "Skull Archfiend of Lightning", + "Blindly Loyal Goblin", + "Gagagigo", + "Nin-Ken Dog", + "Zolga", + "Kelbek", + "Mudora", + "Cestus of Dagla", + "Vampire Lord", + "Metallizing Parasite - Lunatite", + "D. D. Trainer", + "Spell Reproduction", + "Contract with the Abyss", + "Dark Master - Zorc" + ], + "INVASION OF CHAOS": [ + "Ojama Delta Hurricane", + "Ojama Yellow", + "Ojama Black", + "Heart of the Underdog", + "Chaos Emperor Dragon - Envoy of the End", + "Self-Destruct Button", + "Manticore of Darkness", + "Dimension Fusion", + "Gigantes", + "Inferno", + "Silpheed", + "Mad Dog of Darkness", + "Ryu Kokki", + "Berserk Gorilla", + "Neo Bug", + "Dark Driceratops", + "Hyper Hammerhead", + "Sea Serpent Warrior of Darkness", + "Giga Gagagigo", + "Terrorking Salmon", + "Blazing Inpachi", + "Stealth Bird", + "Reload", + "Cursed Seal of the Forbidden Spell", + "Stray Lambs", + "Manju of the Ten Thousand Hands" + ], + "ANCIENT SANCTUARY": [ + "Monster Gate", + "Wall of Revealing Light", + "Mystik Wok", + "The Agent of Judgment - Saturn", + "Zaborg the Thunder Monarch", + "Regenerating Mummy", + "The End of Anubis", + "Solar Flare Dragon", + "Level Limit - Area B", + "King of the Swamp", + "Enemy Controller", + "Enchanting Fitting Room" + ], + "SOUL OF THE DUELIST": [ + "Ninja Grandmaster Sasuke", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Enraged Muka Muka", + "Mobius the Frost Monarch", + "Horus the Black Flame Dragon LV6", + "Ultimate Baseball Kid", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Masked Dragon", + "Element Dragon", + "Horus the Black Flame Dragon LV4", + "Level Up!", + "Howling Insect", + "Mobius the Frost Monarch" + ], + "RISE OF DESTINY": [ + "Homunculus the Alchemic Being", + "Thestalos the Firestorm Monarch", + "Roc from the Valley of Haze", + "Harpie Lady 1", + "Silent Swordsman Lv3", + "Mystic Swordsman LV6", + "Ultimate Insect Lv3", + "Divine Wrath", + "Serial Spell" + ], + "FLAMING ETERNITY": [ + "Insect Knight", + "Chiron the Mage", + "Granmarg the Rock Monarch", + "Silent Swordsman Lv5", + "The Dark - Hex-Sealed Fusion", + "The Earth - Hex-Sealed Fusion", + "The Light - Hex-Sealed Fusion", + "Ultimate Insect Lv5", + "Blast Magician", + "Golem Sentry", + "Rescue Cat", + "Blade Rabbit" + ], + "THE LOST MILLENIUM": [ + "Ritual", + "Megarock Dragon", + "D.D. Survivor", + "Hieracosphinx", + "Elemental Hero Flame Wingman", + "Elemental Hero Avian", + "Elemental Hero Burstinatrix", + "Elemental Hero Clayman", + "Elemental Hero Sparkman", + "Elemental Hero Thunder Giant", + "Aussa the Earth Charmer", + "Brain Control" + ], + "CYBERNETIC REVOLUTION": [ + "Power Bond", + "Cyber Dragon", + "Cyber Twin Dragon", + "Cybernetic Magician", + "Indomitable Fighter Lei Lei", + "Protective Soul Ailin", + "Miracle Fusion", + "Elemental Hero Bubbleman", + "Jerry Beans Man" + ], + "ELEMENTAL ENERGY": [ + "V-Tiger Jet", + "W-Wing Catapult", + "VW-Tiger Catapult", + "VWXYZ-Dragon Catapult Cannon", + "Zure, Knight of Dark World", + "Brron, Mad King of Dark World", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Oxygeddon", + "Roll Out!", + "Dark World Lightning", + "Elemental Hero Rampart Blaster", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge", + "Elemental Hero Wildheart", + "Elemental Hero Bladedge", + "Pot of Avarice", + "B.E.S. Tetran" + ], + "SHADOW OF INFINITY": [ + "Hamon, Lord of Striking Thunder", + "Raviel, Lord of Phantasms", + "Uria, Lord of Searing Flames", + "Ritual", + "Treeborn Frog", + "Saber Beetle", + "Tenkabito Shien", + "Princess Pikeru", + "Gokipon", + "Demise, King of Armageddon", + "Anteatereatingant" + ], + "GAME GIFT COLLECTION": [ + "Ritual", + "Valkyrion the Magna Warrior", + "Alpha the Magnet Warrior", + "Beta the Magnet Warrior", + "Gamma the Magnet Warrior", + "Magical Blast", + "Dunames Dark Witch", + "Vorse Raider", + "Exarion Universe", + "Abyss Soldier", + "Slate Warrior", + "Cyber-Tech Alligator", + "D.D. Assailant", + "Goblin Zombie", + "Elemental Hero Madballman", + "Mind Control", + "Toon Dark Magician Girl", + "Great Spirit", + "Graceful Dice", + "Negate Attack", + "Foolish Burial", + "Card Destruction", + "Dark Magic Ritual", + "Calamity of the Wicked" + ], + "Special Gift Collection": [ + "Gate Guardian", + "Scapegoat", + "Gil Garth", + "La Jinn the Mystical Genie of the Lamp", + "Summoned Skull", + "Inferno Hammer", + "Gemini Elf", + "Cyber Harpie Lady", + "Dandylion", + "Blade Knight", + "Curse of Vampire", + "Elemental Hero Flame Wingman", + "Magician of Black Chaos" + ], + "Fairy Collection": [ + "Silpheed", + "Dunames Dark Witch", + "Hysteric Fairy", + "The Agent of Judgment - Saturn", + "Shining Angel", + "Airknight Parshath", + "Dancing Fairy", + "Zolga", + "Kelbek", + "Mudora", + "Protective Soul Ailin", + "Marshmallon", + "Goddess with the Third Eye", + "Asura Priest", + "Manju of the Ten Thousand Hands", + "Senju of the Thousand Hands" + ], + "Dragon Collection": [ + "Victory D.", + "Chaos Emperor Dragon - Envoy of the End", + "Kaiser Glider", + "Horus the Black Flame Dragon LV6", + "Luster Dragon", + "Luster Dragon #2" + "Spear Dragon", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Twin-Headed Behemoth", + "Cave Dragon", + "Masked Dragon", + "Element Dragon", + "Troop Dragon", + "Horus the Black Flame Dragon LV4", + "Pitch-Dark Dragon" + ], + "Warrior Collection A": [ + "Gate Guardian", + "Gearfried the Iron Knight", + "Dimensional Warrior", + "Command Knight", + "The Last Warrior from Another Planet", + "Dream Clown" + ], + "Warrior Collection B": [ + "Don Zaloog", + "Dark Scorpion - Chick the Yellow", + "Dark Scorpion - Meanae the Thorn", + "Dark Scorpion - Gorg the Strong", + "Cliff the Trap Remover", + "Ninja Grandmaster Sasuke", + "D.D. Warrior Lady", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Mystic Swordsman LV6", + "Dark Blade", + "Blindly Loyal Goblin", + "Exiled Force", + "Ultimate Baseball Kid", + "Freed the Matchless General", + "Holy Knight Ishzark", + "Silent Swordsman Lv3", + "Silent Swordsman Lv5", + "Warrior Lady of the Wasteland", + "D.D. Assailant", + "Blade Knight", + "Marauding Captain", + "Toon Goblin Attack Force" + ], + "Fiend Collection A": [ + "Sangan", + "Castle of Dark Illusions", + "Barox", + "La Jinn the Mystical Genie of the Lamp", + "Summoned Skull", + "Beast of Talwar", + "Sangan", + "Giant Germ", + "Spear Cretin", + "Versago the Destroyer", + "Toon Summoned Skull" + ], + "Fiend Collection B": [ + "Raviel, Lord of Phantasms", + "Yata-Garasu", + "Helpoemer", + "Archfiend Soldier", + "Skull Descovery Knight", + "Gil Garth", + "Opticlops", + "Zure, Knight of Dark World", + "Brron, Mad King of Dark World", + "D.D. Survivor", + "Skull Archfiend of Lightning", + "The End of Anubis", + "Dark Ruler Ha Des", + "Inferno Hammer", + "Legendary Fiend", + "Newdoria", + "Slate Warrior", + "Giant Orc", + "Second Goblin", + "Kiseitai", + "Jowls of Dark Demise", + "D. D. Trainer", + "Earthbound Spirit" + ], + "Machine Collection A": [ + "Cyber-Stein", + "Mechanicalchaser", + "Jinzo", + "UFO Turtle", + "Cyber-Tech Alligator" + ], + "Machine Collection B": [ + "X-Head Cannon", + "Y-Dragon Head", + "Z-Metal Tank", + "XY-Dragon Cannon", + "XZ-Tank Cannon", + "YZ-Tank Dragon", + "XYZ-Dragon Cannon", + "V-Tiger Jet", + "W-Wing Catapult", + "VW-Tiger Catapult", + "VWXYZ-Dragon Catapult Cannon", + "Cyber Dragon", + "Cyber Twin Dragon", + "Green Gadget", + "Red Gadget", + "Yellow Gadget", + "B.E.S. Tetran" + ], + "Spellcaster Collection A": [ + "Exodia", + "Dark Sage", + "Dark Magician", + "Time Wizard", + "Kazejin", + "Magician of Faith", + "Dark Elf", + "Gemini Elf", + "Injection Fairy Lily", + "Cosmo Queen", + "Magician of Black Chaos" + ], + "Spellcaster Collection B": [ + "Jowgen the Spiritualist", + "Tsukuyomi", + "Manticore of Darkness", + "Chaos Command Magician", + "Cybernetic Magician", + "Skilled Dark Magician", + "Kycoo the Ghost Destroyer", + "Toon Gemini Elf", + "Toon Masked Sorcerer", + "Toon Dark Magician Girl", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Breaker the magical Warrior", + "The Tricky", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "Summon Priest", + "Old Vindictive Magician", + "Apprentice Magician", + "Princess Pikeru", + "Blast Magician", + "Magical Marionette", + "Mythical Beast Cerberus", + "Royal Magical Library", + "Aussa the Earth Charmer", + + ], + "Zombie Collection": [ + "Skull Servant", + "Regenerating Mummy", + "Ryu Kokki", + "Spirit Reaper", + "Pyramid Turtle", + "Master Kyonshee", + "Curse of Vampire", + "Vampire Lord", + "Goblin Zombie", + "Decayed Commander", + "Zombie Tiger", + "Des Lacooda", + "Wandering Mummy", + "Royal Keeper" + ], + "Special Monsters A": [ + "X-Head Cannon", + "Y-Dragon Head", + "Z-Metal Tank", + "V-Tiger Jet", + "W-Wing Catapult", + "Yata-Garasu", + "Tsukuyomi", + "Dark Blade", + "Toon Gemini Elf", + "Toon Goblin Attack Force", + "Toon Masked Sorcerer", + "Toon Mermaid", + "Toon Dark Magician Girl", + "Toon Summoned Skull", + "Toon World", + "Burning Beast", + "Freezing Beast", + "Metallizing Parasite - Lunatite", + "Pitch-Dark Dragon", + "Giant Orc", + "Second Goblin", + "Decayed Commander", + "Zombie Tiger", + "Vampire Orchis", + "Des Dendle", + "Indomitable Fighter Lei Lei", + "Protective Soul Ailin", + "Frontline Base", + "Formation Union", + "Roll Out!", + "Asura Priest", + "Fushi No Tori", + "Maharaghi", + "Susa Soldier" + ], + "Special Monsters B": [ + "Polymerization", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Mystic Swordsman LV6", + "Horus the Black Flame Dragon LV6", + "Horus the Black Flame Dragon LV4", + "Armed Dragon LV3" + "Armed Dragon LV5", + "Silent Swordsman Lv3", + "Silent Swordsman Lv5", + "Elemental Hero Flame Wingman", + "Elemental Hero Avian", + "Elemental Hero Burstinatrix", + "Miracle Fusion", + "Elemental Hero Madballman", + "Elemental Hero Bubbleman", + "Elemental Hero Clayman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Sparkman", + "Elemental Hero Steam Healer", + "Elemental Hero Thunder Giant", + "Elemental Hero Wildedge", + "Elemental Hero Wildheart", + "Elemental Hero Bladedge", + "Level Up!", + "Ultimate Insect Lv3", + "Ultimate Insect Lv5" + ], + "Reverse Collection": [ + "Magical Merchant", + "Castle of Dark Illusions", + "Magician of Faith", + "Penguin Soldier", + "Blade Knight", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "Old Vindictive Magician", + "A Cat of Ill Omen", + "Jowls of Dark Demise", + "Cyber Jar", + "Morphing Jar", + "Morphing Jar #2", + "Needle Worm", + "Spear Cretin", + "Nobleman of Crossout", + "Aussa the Earth Charmer" + ], + "LP Recovery Collection": [ + "Mystik Wok", + "Poison of the Old Man", + "Hysteric Fairy", + "Dancing Fairy", + "Zolga", + "Cestus of Dagla", + "Nimble Momonga", + "Solemn Wishes", + "Cure Mermaid", + "Princess Pikeru", + "Kiseitai", + "Elemental Hero Steam Healer", + "Fushi No Tori", + "Emergency Provisions" + ], + "Special Summon Collection A": [ + "Perfectly Ultimate Great Moth", + "Dark Sage", + "Polymerization", + "Ritual", + "Cyber-Stein", + "Scapegoat", + "Aqua Spirit", + "Rock Spirit", + "Spirit of Flames", + "Garuda the Wind Spirit", + "Shining Angel", + "Mystic Tomato", + "Giant Rat", + "Mother Grizzly", + "UFO Turtle", + "Flying Kamakiri 1", + "Giant Germ", + "Revival Jam", + "Pyramid Turtle", + "Troop Dragon", + "Gravekeeper's Spy", + "Pitch-Dark Dragon", + "Decayed Commander", + "Zombie Tiger", + "Vampire Orchis", + "Des Dendle", + "Nimble Momonga", + "The Last Warrior from Another Planet", + "Embodiment of Apophis", + "Cyber Jar", + "Morphing Jar #2", + "Spear Cretin", + "Dark Magic Curtain" + ], + "Special Summon Collection B": [ + "Monster Gate", + "Chaos Emperor Dragon - Envoy of the End", + "Ojama Trio", + "Dimension Fusion", + "Return from the Different Dimension", + "Gigantes", + "Inferno", + "Silpheed", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Skilled Dark Magician", + "Horus the Black Flame Dragon LV6", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Marauding Captain", + "Masked Dragon", + "The Tricky", + "Magical Dimension", + "Frontline Base", + "Formation Union", + "Princess Pikeru", + "Skull Zoma", + "Metal Reflect Slime" + "Level Up!", + "Howling Insect", + "Tribute Doll", + "Enchanting Fitting Room", + "Stray Lambs" + ], + "Special Summon Collection C": [ + "Hamon, Lord of Striking Thunder", + "Raviel, Lord of Phantasms", + "Uria, Lord of Searing Flames", + "Treeborn Frog", + "Cyber Dragon", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Silent Swordsman Lv3", + "Silent Swordsman Lv5", + "Warrior Lady of the Wasteland", + "Dandylion", + "Curse of Vampire", + "Summon Priest", + "Miracle Fusion", + "Elemental Hero Bubbleman", + "The Dark - Hex-Sealed Fusion", + "The Earth - Hex-Sealed Fusion", + "The Light - Hex-Sealed Fusion", + "Ultimate Insect Lv3", + "Ultimate Insect Lv5", + "Rescue Cat", + "Anteatereatingant" + ], + "Equipment Collection": [ + "Megamorph", + "Cestus of Dagla", + "United We Stand" + ], + "Continuous Spell/Trap A": [ + "Destiny Board", + "Spirit Message 'I'", + "Spirit Message 'N'", + "Spirit Message 'A'", + "Spirit Message 'L'", + "Messenger of Peace", + "Fairy Box", + "Ultimate Offering", + "Gravity Bind", + "Solemn Wishes", + "Embodiment of Apophis", + "Toon World" + ], + "Continuous Spell/Trap B": [ + "Hamon, Lord of Striking Thunder", + "Uria, Lord of Searing Flames", + "Wave-Motion Cannon", + "Heart of the Underdog", + "Wall of Revealing Light", + "Dark Snake Syndrome", + "Call of the Mummy", + "Frontline Base", + "Level Limit - Area B", + "Skull Zoma", + "Pitch-Black Power Stone", + "Metal Reflect Slime" + ], + "Quick/Counter Collection": [ + "Mystik Wok", + "Poison of the Old Man", + "Scapegoat", + "Magical Dimension", + "Enemy Controller", + "Collapse", + "Emergency Provisions", + "Graceful Dice", + "Offerings to the Doomed", + "Reload", + "Rush Recklessly", + "The Reliable Guardian", + "Cursed Seal of the Forbidden Spell", + "Divine Wrath", + "Horn of Heaven", + "Magic Drain", + "Magic Jammer", + "Negate Attack", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Spell Shield Type-8", + "Book of Moon", + "Serial Spell", + "Mystical Space Typhoon" + ], + "Direct Damage Collection": [ + "Hamon, Lord of Striking Thunder", + "Chaos Emperor Dragon - Envoy of the End", + "Dark Snake Syndrome", + "Inferno", + "Exarion Universe", + "Kycoo the Ghost Destroyer", + "Giant Germ", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Dark Driceratops", + "Saber Beetle", + "Thestalos the Firestorm Monarch", + "Solar Flare Dragon", + "Ultimate Baseball Kid", + "Spear Dragon", + "Oxygeddon", + "Airknight Parshath", + "Vampire Lord", + "Stamping Destruction", + "Decayed Commander", + "Jowls of Dark Demise", + "Stealth Bird", + "Elemental Hero Bladedge", + ], + "Direct Attack Collection": [ + "Victory D.", + "Dark Scorpion Combination", + "Spirit Reaper", + "Elemental Hero Rampart Blaster", + "Toon Gemini Elf", + "Toon Goblin Attack Force", + "Toon Masked Sorcerer", + "Toon Mermaid", + "Toon Summoned Skull", + "Toon Dark Magician Girl" + ], + "Monster Destroy Collection": [ + "Hamon, Lord of Striking Thunder", + "Inferno", + "Ninja Grandmaster Sasuke", + "Zaborg the Thunder Monarch", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Mystic Swordsman LV6", + "Skull Descovery Knight", + "Inferno Hammer", + "Ryu Kokki", + "Newdoria", + "Exiled Force", + "Yomi Ship", + "Armed Dragon LV5", + "Element Dragon", + "Old Vindictive Magician", + "Magical Dimension", + "Des Dendle", + "Nobleman of Crossout", + "Shield Crash", + "Tribute to the Doomed", + "Elemental Hero Flame Wingman", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Steam Healer", + "Blast Magician", + "Magical Marionette", + "Swarm of Scarabs", + "Offerings to the Doomed", + "Divine Wrath", + "Dream Clown" + ], +} + + +def get_booster_locations(booster: str) -> Dict[str, str]: + return { + f"{booster} {i}": content + for i, content in enumerate(booster_contents[booster], 1) + } diff --git a/worlds/yugioh06/client_bh.py b/worlds/yugioh06/client_bh.py new file mode 100644 index 000000000000..ecbe48110a6c --- /dev/null +++ b/worlds/yugioh06/client_bh.py @@ -0,0 +1,139 @@ +import math +from typing import TYPE_CHECKING, List, Optional, Set + +from NetUtils import ClientStatus, NetworkItem + +import worlds._bizhawk as bizhawk +from worlds._bizhawk.client import BizHawkClient +from . import item_to_index + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class YuGiOh2006Client(BizHawkClient): + game = "Yu-Gi-Oh! 2006" + system = "GBA" + patch_suffix = ".apygo06" + local_checked_locations: Set[int] + goal_flag: int + rom_slot_name: Optional[str] + + def __init__(self) -> None: + super().__init__() + self.local_checked_locations = set() + self.rom_slot_name = None + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + from CommonClient import logger + + try: + # Check if ROM is some version of Yu-Gi-Oh! 2006 + game_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0xA0, 11, "ROM")]))[0]).decode("ascii") + if game_name != "YUGIOHWCT06": + return False + + # Check if we can read the slot name. Doing this here instead of set_auth as a protection against + # validating a ROM where there's no slot name to read. + try: + slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(0x30, 32, "ROM")]))[0] + self.rom_slot_name = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8") + except UnicodeDecodeError: + logger.info("Could not read slot name from ROM. Are you sure this ROM matches this client version?") + return False + except UnicodeDecodeError: + return False + except bizhawk.RequestFailedError: + return False # Should verify on the next pass + + ctx.game = self.game + ctx.items_handling = 0b001 + ctx.want_slot_data = False + return True + + async def set_auth(self, ctx: "BizHawkClientContext") -> None: + ctx.auth = self.rom_slot_name + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + try: + read_state = await bizhawk.read( + ctx.bizhawk_ctx, + [ + (0x0, 8, "EWRAM"), + (0x52E8, 32, "EWRAM"), + (0x5308, 32, "EWRAM"), + (0x5325, 1, "EWRAM"), + (0x6C38, 4, "EWRAM"), + ], + ) + game_state = read_state[0].decode("utf-8") + locations = read_state[1] + items = read_state[2] + amount_items = int.from_bytes(read_state[3], "little") + money = int.from_bytes(read_state[4], "little") + + # make sure save was created + if game_state != "YWCT2006": + return + local_items = bytearray(items) + await bizhawk.guarded_write( + ctx.bizhawk_ctx, + [(0x5308, parse_items(bytearray(items), ctx.items_received), "EWRAM")], + [(0x5308, local_items, "EWRAM")], + ) + money_received = 0 + for item in ctx.items_received: + if item.item == item_to_index["5000DP"] + 5730000: + money_received += 1 + if money_received > amount_items: + await bizhawk.guarded_write( + ctx.bizhawk_ctx, + [ + (0x6C38, (money + (money_received - amount_items) * 5000).to_bytes(4, "little"), "EWRAM"), + (0x5325, money_received.to_bytes(2, "little"), "EWRAM"), + ], + [ + (0x6C38, money.to_bytes(4, "little"), "EWRAM"), + (0x5325, amount_items.to_bytes(2, "little"), "EWRAM"), + ], + ) + + locs_to_send = set() + + # Check for set location flags. + for byte_i, byte in enumerate(bytearray(locations)): + for i in range(8): + and_value = 1 << i + if byte & and_value != 0: + flag_id = byte_i * 8 + i + + location_id = flag_id + 5730001 + if location_id in ctx.server_locations: + locs_to_send.add(location_id) + + # Send locations if there are any to send. + if locs_to_send != self.local_checked_locations: + self.local_checked_locations = locs_to_send + + if locs_to_send is not None: + await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}]) + + # Send game clear if we're in either any ending cutscene or the credits state. + if not ctx.finished_game and locations[18] & (1 << 5) != 0: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + + except bizhawk.RequestFailedError: + # Exit handler and return to main loop to reconnect. + pass + + +# Parses bit-map for local items and adds the received items to that bit-map +def parse_items(local_items: bytearray, items: List[NetworkItem]) -> bytearray: + array = local_items + for item in items: + index = item.item - 5730001 + if index != 254: + byte = math.floor(index / 8) + bit = index % 8 + array[byte] = array[byte] | (1 << bit) + return array diff --git a/worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md b/worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md new file mode 100644 index 000000000000..ee8c95a3b193 --- /dev/null +++ b/worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md @@ -0,0 +1,53 @@ +# Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and +export a config file. + +## What does randomization do to this game? + +Unlocking Booster Packs, Campaign, Limited and Theme Duel Opponents has been changed. +You only need to beat each Campaign Opponent once. +Logic expects you to have access to the Booster Packs necessary to get the locations at a reasonable pace and consistency. +Logic remains, so the game is always able to be completed, but because of the shuffle, the player may need to defeat certain opponents before they +would in the vanilla game. + +You can change how much money you receive and how much booster packs cost. + +## What is the goal of Yu-Gi-Oh! 2006 when randomized? + +Defeat a certain amount of Limited/Theme Duels to Unlock the final Campaign Opponent and beat it. + +## What items and locations get shuffled? + +Locations in which items can be found: +- Getting a Duel Bonus for the first time +- Beating a certain amount campaign opponents of the same level. +- Beating a Limited/Theme Duel +- Obtaining certain cards (same that unlock a theme duel in vanilla) + +Items that are shuffled: +- Unlocking Booster Packs (the "ALL" Booster Packs are excluded) +- Unlocking Campaign Opponents +- Unlocking Limited/Theme Duels +- Banlists + +## What items are _not_ randomized? +Certain Key Items are kept in their original locations: +- Duel Puzzles +- Survival Mode +- Booster Pack Contents + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + + +## What does another world's item look like in Yu-Gi-Oh! 2006? + +You can only tell when and what you got via the client. + +## When the player receives an item, what happens? + +The Opponent/Pack becomes available to you. diff --git a/worlds/yugioh06/docs/setup_en.md b/worlds/yugioh06/docs/setup_en.md new file mode 100644 index 000000000000..1beeaa6c625e --- /dev/null +++ b/worlds/yugioh06/docs/setup_en.md @@ -0,0 +1,72 @@ +# Setup Guide for Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 Archipelago + +## Important + +As we are using Bizhawk, this guide is only applicable to Windows and Linux systems. + +## Required Software + +- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory) + - Version 2.7.0 and later are supported. + - Detailed installation instructions for Bizhawk can be found at the above link. + - Windows users must run the prereq installer first, which can also be found at the above link. +- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases) +- A US or European Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 Rom + +## Configuring Bizhawk + +Once Bizhawk has been installed, open Bizhawk and change the following settings: + +- Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to + "Lua+LuaInterface". This is required for the Lua script to function correctly. + **NOTE: Even if "Lua+LuaInterface" is already selected, toggle between the two options and reselect it. Fresh installs** + **of newer versions of Bizhawk have a tendency to show "Lua+LuaInterface" as the default selected option but still load** + **"NLua+KopiLua" until this step is done.** +- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button. + This reduces the possibility of losing save data in emulator crashes. +- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to + continue playing in the background, even if another window is selected, such as the Client. +- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want + to disable most of these, which you can do quickly using `Esc`. + +It is strongly recommended to associate GBA rom extensions (\*.gba) to the Bizhawk we've just installed. +To do so, we simply have to search any GBA rom we happened to own, right click and select "Open with...", unfold +the list that appears and select the bottom option "Look for another application", then browse to the Bizhawk folder +and select EmuHawk.exe. + +## Configuring your YAML file + +### What is a YAML file and why do I need one? + +Your YAML file contains a set of configuration options which provide the generator with information about how it should +generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy +an experience customized for their taste, and different players in the same multiworld can all have different options. + +### Where do I get a YAML file? + +You can customize your options by visiting the +[Yu-Gi-Oh! 2006 Player Options Page](/games/Yu-Gi-Oh!%202006/player-options) + +## Joining a MultiWorld Game + +### Obtain your GBA patch file + +When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your data file, or with a zip file containing everyone's data +files. Your data file should have a `.apygo06` extension. + +Double-click on your `.apygo06` file to start your client and start the ROM patch process. Once the process is finished +(this can take a while), the client and the emulator will be started automatically (if you associated the extension +to the emulator as recommended). + +### Connect to the Multiserver + +Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools" +menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script. + +Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`. + +To connect the client to the multiserver simply put `

    T7vt?tqkY^LMQTj(J9idCk*n~d<$~M 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%0D~~sk2r-7;>%B}WvEs<#&7hq4MN^cyD6z~h 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 literal 0 HcmV?d00001 diff --git a/worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak b/worlds/wargroove2/data/save/campaign-45747c660b6a2f09601327a18d662a7d.cmp.bak new file mode 100644 index 0000000000000000000000000000000000000000..95cac9ac173669e314a5a05064e3b278fd93547d GIT binary patch literal 221024 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